///|
/// Parsed PDF header version.
///
/// The fields correspond to the digits in a `%PDF-major.minor` header.
pub(all) struct PdfVersion {
major : Int
minor : Int
} derive(Debug, Eq, ToJson)
///|
/// Fallback version used when a PDF header cannot be found.
pub let pdf_default_version : PdfVersion = { major: 2, minor: 0 }
///|
fn pdf_is_digit_byte(value : Int) -> Bool {
value >= 48 && value <= 57
}
///|
fn pdf_header_at(cursor : ByteCursor, position : Int) -> PdfVersion? {
let header : Result[BytesView, Error] = try? cursor.view_at_absolute(
position, 8,
)
match header {
Ok(header) =>
if header[0].to_int() == 37 &&
header[1].to_int() == 80 &&
header[2].to_int() == 68 &&
header[3].to_int() == 70 &&
header[4].to_int() == 45 &&
header[6].to_int() == 46 {
let major = header[5].to_int()
let minor = header[7].to_int()
if pdf_is_digit_byte(major) && pdf_is_digit_byte(minor) {
Some({ major: major - 48, minor: minor - 48 })
} else {
None
}
} else {
None
}
Err(_) => None
}
}
///|
fn pdf_header_probe_length(length : Int) -> Int {
if length < 1024 {
length
} else {
1024
}
}
///|
fn pdf_finish_header_at(
cursor : ByteCursor,
position : Int,
version : PdfVersion,
) -> PdfVersion raise PdfError {
cursor.set_offset(position)
cursor.seek_absolute(position + 8)
version
}
///|
/// Read the PDF header from the current cursor buffer.
///
/// The reader probes the first 1024 bytes for `%PDF-x.y`. When found, the cursor
/// offset is set to the header position and the cursor advances past the header.
/// If no header is found, `pdf_default_version` is returned.
pub fn pdf_read_header_from_cursor(
cursor : ByteCursor,
) -> PdfVersion raise PdfError {
let max_probe = pdf_header_probe_length(cursor.length())
for position in 0..<=max_probe {
match pdf_header_at(cursor, position) {
Some(version) => break pdf_finish_header_at(cursor, position, version)
None => ()
}
} nobreak {
pdf_default_version
}
}
///|
/// Read a PDF header version from a borrowed byte view.
pub fn pdf_read_header_from_view(data : BytesView) -> PdfVersion raise PdfError {
pdf_read_header_from_cursor(byte_cursor_of_view(data))
}
///|
/// Read a PDF header version from owned PDF bytes.
pub fn pdf_read_header_from_bytes(data : PdfBytes) -> PdfVersion raise PdfError {
pdf_read_header_from_view(data)
}
///|
/// Build a document from already parsed indirect objects.
///
/// Objects are loaded at their existing object numbers and generations without
/// recording mutation events. The document version defaults to
/// `pdf_default_version`.
pub fn pdf_document_of_parsed_indirect_objects(
objects : ArrayView[PdfParsedIndirectObject],
version? : PdfVersion = pdf_default_version,
) -> PdfDocument {
let document = pdf_document_empty()
document.set_version(version.major, version.minor)
for parsed in objects {
document.load_object_given_number(
parsed.number,
parsed.object,
generation=parsed.generation,
)
}
document
}
///|
/// Parse indirect objects by scanning the byte stream without xref tables.
///
/// This primitive reader is useful for fixtures and damaged files. It reads the
/// header, then scans indirect object syntax and stream bodies in sequence; it
/// does not interpret trailer/xref metadata or decrypt the document.
pub fn pdf_read_primitive_document_from_view(
data : BytesView,
) -> PdfDocument raise PdfError {
let cursor = byte_cursor_of_view(data)
let version = pdf_read_header_from_cursor(cursor)
let document = pdf_document_empty()
document.set_version(version.major, version.minor)
let mut done = false
while !done {
let (objects, prefix) = pdf_parse_indirect_object_segments(
pdf_cursor_lex_tokens(cursor),
)
for parsed in objects {
document.load_object_given_number(
parsed.number,
parsed.object,
generation=parsed.generation,
)
}
match prefix {
Some(prefix) =>
if pdf_cursor_stopped_at_stream_candidate(cursor) {
let stream = pdf_cursor_finish_stream_object(cursor, prefix)
document.load_object_given_number(
stream.number,
stream.object,
generation=stream.generation,
)
} else {
document.load_object_given_number(
prefix.number,
prefix.object,
generation=prefix.generation,
)
done = true
}
None => done = true
}
}
document
}
///|
/// Parse a primitive document from owned PDF bytes.
pub fn pdf_read_primitive_document_from_bytes(
data : PdfBytes,
) -> PdfDocument raise PdfError {
pdf_read_primitive_document_from_view(data)
}