///|
fn pdf_skip_comments(lexemes : ArrayView[PdfLexeme], index : Int) -> Int {
let mut current = index
let mut done = false
while !done && current < lexemes.length() {
match lexemes[current] {
LexComment(_) => current += 1
_ => done = true
}
}
current
}
///|
fn pdf_lexeme_indirect_reference_number(
lexemes : ArrayView[PdfLexeme],
index : Int,
) -> Int? {
if index + 2 < lexemes.length() {
match (lexemes[index], lexemes[index + 1], lexemes[index + 2]) {
(LexInt(object_number), LexInt(_), LexR) => Some(object_number)
_ => None
}
} else {
None
}
}
///|
fn pdf_lexeme_indirect_object_header_at(
lexemes : ArrayView[PdfLexeme],
index : Int,
) -> Bool {
if index + 2 < lexemes.length() {
match (lexemes[index], lexemes[index + 1], lexemes[index + 2]) {
(LexInt(_), LexInt(_), LexObj) => true
_ => false
}
} else {
false
}
}
///|
fn pdf_parse_array_at(
lexemes : ArrayView[PdfLexeme],
index : Int,
) -> (PdfObject, Int) raise PdfError {
let values : Array[PdfObject] = []
let mut current = index
let mut done = false
while !done && current < lexemes.length() {
current = pdf_skip_comments(lexemes, current)
if current >= lexemes.length() {
done = true
} else {
match lexemes[current] {
LexRightSquare => {
current += 1
done = true
}
_ => {
let (value, next) = pdf_parse_value_at(lexemes, current)
values.push(value)
current = next
}
}
}
}
(PdfArray(values), current)
}
///|
fn pdf_skip_dictionary_tail(lexemes : ArrayView[PdfLexeme], index : Int) -> Int {
let mut current = index
let mut depth = 0
while current < lexemes.length() {
match lexemes[current] {
LexLeftDict => depth += 1
LexRightDict => if depth == 0 { break current + 1 } else { depth -= 1 }
_ => ()
}
current += 1
} nobreak {
current
}
}
///|
fn pdf_parse_dictionary_at(
lexemes : ArrayView[PdfLexeme],
index : Int,
) -> (PdfObject, Int) raise PdfError {
let entries : Array[(PdfName, PdfObject)] = []
let mut current = index
let mut malformed = false
let mut done = false
while !done && current < lexemes.length() {
current = pdf_skip_comments(lexemes, current)
if current >= lexemes.length() {
done = true
} else {
match lexemes[current] {
LexRightDict => {
current += 1
done = true
}
_ => {
let (key, after_key) = pdf_parse_value_at(lexemes, current)
match key {
PdfNameObject(name) =>
if after_key >= lexemes.length() {
malformed = true
current = after_key
done = true
} else {
let value_start = pdf_skip_comments(lexemes, after_key)
if value_start >= lexemes.length() {
malformed = true
current = value_start
done = true
} else {
match lexemes[value_start] {
LexRightDict => {
malformed = true
current = value_start + 1
done = true
}
_ => {
let (value, after_value) = pdf_parse_value_at(
lexemes, value_start,
)
entries.push((name, value))
current = after_value
}
}
}
}
_ => {
malformed = true
current = pdf_skip_dictionary_tail(lexemes, after_key)
done = true
}
}
}
}
}
}
if malformed {
(PdfDictionary([]), current)
} else {
(PdfDictionary(entries), current)
}
}
///|
fn pdf_parse_value_at(
lexemes : ArrayView[PdfLexeme],
index : Int,
) -> (PdfObject, Int) raise PdfError {
let current = pdf_skip_comments(lexemes, index)
if current >= lexemes.length() {
raise ParseObjectExpected
}
match pdf_lexeme_indirect_reference_number(lexemes, current) {
Some(object_number) => (PdfIndirect(object_number), current + 3)
None =>
match lexemes[current] {
LexNull => (PdfNull, current + 1)
LexBool(value) => (PdfBoolean(value), current + 1)
LexInt(value) => (PdfInteger(value), current + 1)
LexReal(value) => (PdfReal(value), current + 1)
LexString(value) => (PdfString(value), current + 1)
LexName(value) => (PdfNameObject(value), current + 1)
LexLeftSquare => pdf_parse_array_at(lexemes, current + 1)
LexLeftDict => pdf_parse_dictionary_at(lexemes, current + 1)
_ => raise ParseObjectExpected
}
}
}
///|
/// Parses all top-level PDF objects from lexemes.
///
/// Comments are skipped. Arrays and dictionaries are parsed recursively, and
/// indirect-reference triples are collapsed into `PdfIndirect` objects.
pub fn pdf_parse_lexemes(
lexemes : ArrayView[PdfLexeme],
) -> Array[PdfObject] raise PdfError {
let objects : Array[PdfObject] = []
let mut current = 0
while current < lexemes.length() {
current = pdf_skip_comments(lexemes, current)
if current < lexemes.length() {
let (object, next) = pdf_parse_value_at(lexemes, current)
objects.push(object)
current = next
}
}
objects
}
///|
/// Parses exactly one top-level PDF object from lexemes.
///
/// Raises `PdfError::ParseSingleObjectExpected` if the input contains zero or
/// more than one object.
pub fn pdf_parse_single_lexeme_object(
lexemes : ArrayView[PdfLexeme],
) -> PdfObject raise PdfError {
let objects = pdf_parse_lexemes(lexemes)
if objects.length() == 1 {
objects[0]
} else {
raise ParseSingleObjectExpected
}
}
///|
/// Lexes and parses exactly one PDF object from a byte view.
pub fn pdf_parse_object_from_view(data : BytesView) -> PdfObject raise PdfError {
pdf_parse_single_lexeme_object(pdf_lex_view(data))
}
///|
/// Lexes and parses exactly one PDF object from owned PDF bytes.
pub fn pdf_parse_object_from_bytes(data : PdfBytes) -> PdfObject raise PdfError {
pdf_parse_object_from_view(data)
}
///|
/// A parsed indirect object with object number, generation, and payload.
pub(all) struct PdfParsedIndirectObject {
number : Int
generation : Int
object : PdfObject
} derive(Debug, Eq, ToJson)
///|
fn pdf_parse_indirect_object_at(
lexemes : ArrayView[PdfLexeme],
index : Int,
) -> (PdfParsedIndirectObject, Int) raise PdfError {
let (object, next, complete) = pdf_parse_indirect_object_segment_at(
lexemes, index,
)
ignore(complete)
(object, next)
}
///|
fn pdf_parse_indirect_object_segment_at(
lexemes : ArrayView[PdfLexeme],
index : Int,
) -> (PdfParsedIndirectObject, Int, Bool) raise PdfError {
let first = pdf_skip_comments(lexemes, index)
if first + 2 >= lexemes.length() {
raise ParseIndirectObjectExpected
}
match (lexemes[first], lexemes[first + 1], lexemes[first + 2]) {
(LexInt(number), LexInt(generation), LexObj) => {
let object_start = pdf_skip_comments(lexemes, first + 3)
if object_start < lexemes.length() && lexemes[object_start] == LexEndObj {
({ number, generation, object: PdfNull }, object_start + 1, true)
} else {
let (object, after_object) = pdf_parse_value_at(lexemes, object_start)
let end_position = pdf_skip_comments(lexemes, after_object)
if end_position < lexemes.length() && lexemes[end_position] == LexEndObj {
({ number, generation, object }, end_position + 1, true)
} else {
({ number, generation, object }, after_object, false)
}
}
}
_ => raise ParseIndirectObjectExpected
}
}
///|
fn pdf_parse_indirect_object_prefix_lexemes(
lexemes : ArrayView[PdfLexeme],
) -> PdfParsedIndirectObject raise PdfError {
let first = pdf_skip_comments(lexemes, 0)
if first + 2 >= lexemes.length() {
raise ParseIndirectObjectExpected
}
match (lexemes[first], lexemes[first + 1], lexemes[first + 2]) {
(LexInt(number), LexInt(generation), LexObj) => {
let object_start = pdf_skip_comments(lexemes, first + 3)
let object = if object_start < lexemes.length() &&
lexemes[object_start] == LexEndObj {
PdfNull
} else {
let (object, _) = pdf_parse_value_at(lexemes, object_start)
object
}
{ number, generation, object }
}
_ => raise ParseIndirectObjectExpected
}
}
///|
/// Parses one indirect object from lexemes.
///
/// Raises `PdfError::ParseIndirectObjectExpected` if the input does not start
/// with an indirect object header.
pub fn pdf_parse_indirect_object_lexemes(
lexemes : ArrayView[PdfLexeme],
) -> PdfParsedIndirectObject raise PdfError {
let (object, _) = pdf_parse_indirect_object_at(lexemes, 0)
object
}
///|
/// Parses one indirect object from owned PDF bytes.
pub fn pdf_parse_indirect_object_from_bytes(
data : PdfBytes,
) -> PdfParsedIndirectObject raise PdfError {
pdf_parse_indirect_object_from_view(data)
}
///|
/// Parses one indirect object from a byte view.
pub fn pdf_parse_indirect_object_from_view(
data : BytesView,
) -> PdfParsedIndirectObject raise PdfError {
pdf_parse_indirect_object_lexemes(pdf_lex_view(data))
}
///|
/// Parses every complete indirect object from a lexeme sequence.
///
/// Raises `PdfError::ParseIndirectObjectExpected` when an expected indirect
/// object header is malformed.
pub fn pdf_parse_indirect_objects(
lexemes : ArrayView[PdfLexeme],
) -> Array[PdfParsedIndirectObject] raise PdfError {
let objects : Array[PdfParsedIndirectObject] = []
let mut current = 0
while current < lexemes.length() {
current = pdf_skip_comments(lexemes, current)
if current < lexemes.length() {
let (object, next) = pdf_parse_indirect_object_at(lexemes, current)
objects.push(object)
current = next
}
}
objects
}
///|
fn pdf_parse_indirect_object_segments(
lexemes : ArrayView[PdfLexeme],
) -> (Array[PdfParsedIndirectObject], PdfParsedIndirectObject?) raise PdfError {
let objects : Array[PdfParsedIndirectObject] = []
let mut prefix : PdfParsedIndirectObject? = None
let mut current = 0
let mut done = false
while !done && current < lexemes.length() {
current = pdf_skip_comments(lexemes, current)
if current < lexemes.length() {
let (object, next, complete) = pdf_parse_indirect_object_segment_at(
lexemes, current,
)
if complete {
objects.push(object)
current = next
} else {
let next_object = pdf_skip_comments(lexemes, next)
if pdf_lexeme_indirect_object_header_at(lexemes, next_object) {
objects.push(object)
current = next_object
} else {
prefix = Some(object)
done = true
}
}
}
}
(objects, prefix)
}