///|
pub(all) struct PdfVersion {
major : Int
minor : Int
} derive(Debug, Eq, ToJson)
///|
pub let pdf_default_version : PdfVersion = { major: 2, minor: 0 }
///|
fn pdf_is_digit_byte(value : Int) -> Bool {
value >= 48 && value <= 57
}
///|
fn ByteCursor::pdf_header_at(self : ByteCursor, position : Int) -> PdfVersion? {
if position < 0 || position + 7 >= self.data.length() {
None
} else if self.data[position].to_int() == 37 &&
self.data[position + 1].to_int() == 80 &&
self.data[position + 2].to_int() == 68 &&
self.data[position + 3].to_int() == 70 &&
self.data[position + 4].to_int() == 45 &&
self.data[position + 6].to_int() == 46 {
let major = self.data[position + 5].to_int()
let minor = self.data[position + 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
}
}
///|
pub fn ByteCursor::read_pdf_header(
self : ByteCursor,
) -> PdfVersion raise PdfError {
let max_probe = if self.data.length() < 1024 {
self.data.length()
} else {
1024
}
for position = 0; position <= max_probe; position = position + 1 {
match self.pdf_header_at(position) {
Some(version) => {
self.set_offset(position)
self.position = position + 8
break version
}
None => ()
}
} nobreak {
pdf_default_version
}
}
///|
pub fn pdf_read_header_from_view(data : BytesView) -> PdfVersion raise PdfError {
byte_cursor_of_view(data).read_pdf_header()
}
///|
pub fn pdf_read_header_from_bytes(data : PdfBytes) -> PdfVersion raise PdfError {
pdf_read_header_from_view(data[:])
}
///|
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
}
///|
pub fn pdf_read_primitive_document_from_view(
data : BytesView,
) -> PdfDocument raise PdfError {
let cursor = byte_cursor_of_view(data)
let version = cursor.read_pdf_header()
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(
cursor.lex_pdf_tokens(),
)
for parsed in objects {
document.load_object_given_number(
parsed.number,
parsed.object,
generation=parsed.generation,
)
}
match prefix {
Some(prefix) =>
if cursor.stopped_at_pdf_stream_candidate() {
let stream = cursor.finish_pdf_stream_object(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
}
///|
pub fn pdf_read_primitive_document_from_bytes(
data : PdfBytes,
) -> PdfDocument raise PdfError {
pdf_read_primitive_document_from_view(data[:])
}
///|
struct PdfClassicXRefEntry {
object_number : Int
offset : Int
generation : Int
in_use : Bool
object_stream : ObjectStreamReference?
} derive(Debug, Eq, ToJson)
///|
priv struct PdfXRefSection {
entries : Array[PdfClassicXRefEntry]
trailer : PdfObject
stream_object_number : Int?
stream_object_offset : Int?
}
///|
fn ByteCursor::seek_pdf_absolute(
self : ByteCursor,
position : Int,
) -> Unit raise PdfError {
if position < 0 || position > self.data.length() {
raise InvalidCursorPosition(position)
}
self.position = position
}
///|
fn pdf_view_matches_ascii_at(
view : BytesView,
position : Int,
values : ArrayView[Int],
) -> Bool {
if position < 0 || position + values.length() > view.length() {
false
} else {
for i = 0; i < values.length(); i = i + 1 {
if view[position + i].to_int() != values[i] {
break false
}
} nobreak {
true
}
}
}
///|
fn pdf_view_equals_ascii(view : BytesView, values : ArrayView[Int]) -> Bool {
view.length() == values.length() && pdf_view_matches_ascii_at(view, 0, values)
}
///|
fn pdf_find_last_ascii(view : BytesView, values : ArrayView[Int]) -> Int {
if values.length() == 0 {
view.length()
} else if view.length() < values.length() {
-1
} else {
for start = view.length() - values.length(); start >= 0; start = start - 1 {
if pdf_view_matches_ascii_at(view, start, values) {
break start
}
} nobreak {
-1
}
}
}
///|
fn pdf_startxref_search_view(data : BytesView) -> BytesView {
let eof = pdf_find_last_ascii(data, [37, 37, 69, 79, 70])
if eof < 0 {
data
} else {
data[:eof]
}
}
///|
fn pdf_find_ascii_from(
view : BytesView,
start : Int,
values : ArrayView[Int],
) -> Int {
for position = start
position <= view.length() - values.length()
position = position + 1 {
if pdf_view_matches_ascii_at(view, position, values) {
break position
}
} nobreak {
-1
}
}
///|
fn pdf_reader_skip_digits(data : BytesView, position : Int) -> Int {
let mut current = position
while current < data.length() && pdf_is_digit_byte(data[current].to_int()) {
current += 1
}
current
}
///|
fn pdf_reader_skip_required_whitespace(
data : BytesView,
position : Int,
) -> Int? {
if position >= data.length() ||
!pdf_is_whitespace_byte(data[position].to_int()) {
None
} else {
let mut current = position
while current < data.length() &&
pdf_is_whitespace_byte(data[current].to_int()) {
current += 1
}
Some(current)
}
}
///|
fn pdf_reader_reconstruction_indirect_header_at(
data : BytesView,
position : Int,
) -> Bool {
if position < 0 ||
position >= data.length() ||
!pdf_is_digit_byte(data[position].to_int()) {
false
} else {
let after_object_number = pdf_reader_skip_digits(data, position)
match pdf_reader_skip_required_whitespace(data, after_object_number) {
Some(generation_start) => {
let after_generation = pdf_reader_skip_digits(data, generation_start)
match pdf_reader_skip_required_whitespace(data, after_generation) {
Some(keyword_start) => {
let after_keyword = keyword_start + 3
pdf_view_matches_ascii_at(data, keyword_start, [111, 98, 106]) &&
(
after_keyword >= data.length() ||
pdf_is_whitespace_or_delimiter_byte(data[after_keyword].to_int())
)
}
None => false
}
}
None => false
}
}
}
///|
fn pdf_reader_line_end(data : BytesView, position : Int) -> Int {
let mut current = position
while current < data.length() && !pdf_is_newline_byte(data[current].to_int()) {
current += 1
}
current
}
///|
fn pdf_reader_after_line_break(data : BytesView, line_end : Int) -> Int {
if line_end >= data.length() {
data.length()
} else if data[line_end].to_int() == 13 &&
line_end + 1 < data.length() &&
data[line_end + 1].to_int() == 10 {
line_end + 2
} else {
line_end + 1
}
}
///|
fn pdf_reader_trimmed_line_starts_or_ends_with_endobj(
data : BytesView,
line_start : Int,
line_end : Int,
) -> Bool {
let mut start = line_start
while start < line_end && pdf_is_whitespace_byte(data[start].to_int()) {
start += 1
}
let mut end = line_end
while end > start && pdf_is_whitespace_byte(data[end - 1].to_int()) {
end -= 1
}
end - start >= 6 &&
(
pdf_view_matches_ascii_at(data, start, [101, 110, 100, 111, 98, 106]) ||
pdf_view_matches_ascii_at(data, end - 6, [101, 110, 100, 111, 98, 106])
)
}
///|
fn pdf_reader_reconstruction_skip_malformed_object(
data : BytesView,
position : Int,
) -> Int? {
if !pdf_reader_reconstruction_indirect_header_at(data, position) {
None
} else {
let mut line_start = position
let mut next = data.length()
let mut done = false
while !done && line_start < data.length() {
let line_end = pdf_reader_line_end(data, line_start)
if pdf_reader_trimmed_line_starts_or_ends_with_endobj(
data, line_start, line_end,
) {
next = pdf_reader_after_line_break(data, line_end)
done = true
} else {
let after_line = pdf_reader_after_line_break(data, line_end)
line_start = after_line
}
}
Some(next)
}
}
///|
fn pdf_parse_ascii_int_view(view : BytesView) -> Int raise PdfError {
if view.length() == 0 {
raise NumberExpected
}
let limit = 2147483647L
let mut value = 0L
for byte in view {
let digit = byte.to_int()
if !pdf_is_digit_byte(digit) {
raise NumberExpected
}
value = value * 10L + (digit - 48).to_int64()
if value > limit {
raise NumberExpected
}
}
value.to_int()
}
///|
fn pdf_split_whitespace_tokens(view : BytesView) -> Array[BytesView] {
let tokens : Array[BytesView] = []
let mut index = 0
while index < view.length() {
while index < view.length() && pdf_is_whitespace_byte(view[index].to_int()) {
index += 1
}
let start = index
while index < view.length() && !pdf_is_whitespace_byte(view[index].to_int()) {
index += 1
}
if start < index {
tokens.push(view[start:index])
}
}
tokens
}
///|
fn pdf_xref_malformed_zero_offset_in_use_free(
tokens : ArrayView[BytesView],
) -> Bool {
tokens.length() >= 3 &&
pdf_view_equals_ascii(tokens[0], [48, 48, 48, 48, 48, 48, 48, 48, 48, 48]) &&
tokens[2].length() > 0 &&
tokens[2][0].to_int() == 110
}
///|
fn pdf_xref_marker_token(token : BytesView) -> Bool {
pdf_view_equals_ascii(token, [120, 114, 101, 102])
}
///|
fn pdf_xref_fixed_width_digits(
line : BytesView,
start : Int,
end : Int,
) -> Bool {
for i in start.. PdfClassicXRefEntry? raise PdfError {
if line.length() >= 18 &&
pdf_xref_fixed_width_digits(line, 0, 10) &&
pdf_xref_fixed_width_digits(line, 11, 16) {
let in_use = match line[17].to_int() {
110 => true
102 => false
_ => raise XRefEntryExpected
}
Some({
object_number,
offset: pdf_parse_ascii_int_view(line[0:10]),
generation: pdf_parse_ascii_int_view(line[11:16]),
in_use,
object_stream: None,
})
} else {
None
}
}
///|
fn pdf_read_startxref_position(data : BytesView) -> Int raise PdfError {
let search = pdf_startxref_search_view(data)
let start = pdf_find_last_ascii(search, [
115, 116, 97, 114, 116, 120, 114, 101, 102,
])
if start < 0 {
raise StartXRefExpected
}
let rest = search[start + 9:]
let mut index = 0
while index < rest.length() && !pdf_is_digit_byte(rest[index].to_int()) {
index += 1
}
let digits_start = index
while index < rest.length() && pdf_is_digit_byte(rest[index].to_int()) {
index += 1
}
pdf_parse_ascii_int_view(rest[digits_start:index])
}
///|
fn pdf_xref_token_in_use(token : BytesView) -> Bool raise PdfError {
if token.length() == 0 {
raise XRefEntryExpected
}
match token[0].to_int() {
110 => true
102 => false
_ => raise XRefEntryExpected
}
}
///|
fn pdf_classic_xref_lookup(
xrefs : ArrayView[PdfClassicXRefEntry],
object_number : Int,
) -> PdfClassicXRefEntry? {
for xref in xrefs {
if xref.in_use && xref.object_number == object_number {
break Some(xref)
}
} nobreak {
None
}
}
///|
fn pdf_classic_xrefs_add_newer_first(
target : Array[PdfClassicXRefEntry],
entries : ArrayView[PdfClassicXRefEntry],
) -> Unit {
let seen = pdf_number_set([])
for xref in target {
seen.add(xref.object_number)
}
for entry in entries {
if !seen.contains(entry.object_number) {
target.push(entry)
seen.add(entry.object_number)
}
}
}
///|
fn ByteCursor::read_pdf_classic_xref_entries(
self : ByteCursor,
) -> Array[PdfClassicXRefEntry] raise PdfError {
self.drop_pdf_whitespace()
self.consume_pdf_ascii([120, 114, 101, 102], XRefExpected)
let entries : Array[PdfClassicXRefEntry] = []
let mut object_number = 0
let mut done = false
while !done {
self.drop_pdf_whitespace()
let position = self.absolute_position()
if self.matches_pdf_ascii_at(position, [116, 114, 97, 105, 108, 101, 114]) {
done = true
} else if self.peek_byte() == pdf_no_more {
raise TrailerExpected
} else {
let line = self.read_line_view()
let tokens = pdf_split_whitespace_tokens(line)
match pdf_xref_fixed_width_entry(line, object_number) {
Some(entry) => {
entries.push(entry)
object_number += 1
}
None =>
if tokens.length() == 1 && pdf_xref_marker_token(tokens[0]) {
()
} else if tokens.length() == 2 {
object_number = pdf_parse_ascii_int_view(tokens[0])
ignore(pdf_parse_ascii_int_view(tokens[1]))
} else if tokens.length() == 3 && pdf_xref_marker_token(tokens[0]) {
object_number = pdf_parse_ascii_int_view(tokens[1])
ignore(pdf_parse_ascii_int_view(tokens[2]))
} else if tokens.length() >= 3 {
entries.push({
object_number,
offset: pdf_parse_ascii_int_view(tokens[0]),
generation: if pdf_xref_malformed_zero_offset_in_use_free(tokens) {
0
} else {
pdf_parse_ascii_int_view(tokens[1])
},
in_use: if pdf_xref_malformed_zero_offset_in_use_free(tokens) {
false
} else {
pdf_xref_token_in_use(tokens[2])
},
object_stream: None,
})
object_number += 1
} else {
raise XRefEntryExpected
}
}
}
}
entries
}
///|
fn pdf_reader_name(text : String) -> PdfName {
pdf_name_of_bytes(@ascii.encode(text))
}
///|
let pdf_reader_type_key_name : PdfName = pdf_reader_name("/Type")
///|
let pdf_reader_xref_key_name : PdfName = pdf_reader_name("/XRef")
///|
let pdf_reader_objstm_key_name : PdfName = pdf_reader_name("/ObjStm")
///|
let pdf_reader_w_key_name : PdfName = pdf_reader_name("/W")
///|
let pdf_reader_index_key_name : PdfName = pdf_reader_name("/Index")
///|
let pdf_reader_size_key_name : PdfName = pdf_reader_name("/Size")
///|
let pdf_reader_n_key_name : PdfName = pdf_reader_name("/N")
///|
let pdf_reader_first_key_name : PdfName = pdf_reader_name("/First")
///|
let pdf_reader_linearized_key_name : PdfName = pdf_reader_name("/Linearized")
///|
let pdf_reader_filter_key_name : PdfName = pdf_reader_name("/Filter")
///|
let pdf_reader_short_filter_key_name : PdfName = pdf_reader_name("/F")
///|
let pdf_reader_decodeparms_key_name : PdfName = pdf_reader_name("/DecodeParms")
///|
let pdf_reader_short_decodeparms_key_name : PdfName = pdf_reader_name("/DP")
///|
let pdf_reader_root_key_name : PdfName = pdf_reader_name("/Root")
///|
let pdf_reader_prev_key_name : PdfName = pdf_reader_name("/Prev")
///|
let pdf_reader_xrefstm_key_name : PdfName = pdf_reader_name("/XRefStm")
///|
let pdf_length_key_name : PdfName = pdf_reader_name("/Length")
///|
fn pdf_reader_type_name() -> PdfName {
pdf_reader_type_key_name
}
///|
fn pdf_reader_xref_name() -> PdfName {
pdf_reader_xref_key_name
}
///|
fn pdf_reader_objstm_name() -> PdfName {
pdf_reader_objstm_key_name
}
///|
fn pdf_reader_w_name() -> PdfName {
pdf_reader_w_key_name
}
///|
fn pdf_reader_index_name() -> PdfName {
pdf_reader_index_key_name
}
///|
fn pdf_reader_size_name() -> PdfName {
pdf_reader_size_key_name
}
///|
fn pdf_reader_n_name() -> PdfName {
pdf_reader_n_key_name
}
///|
fn pdf_reader_first_name() -> PdfName {
pdf_reader_first_key_name
}
///|
fn pdf_reader_linearized_name() -> PdfName {
pdf_reader_linearized_key_name
}
///|
fn pdf_reader_filter_name() -> PdfName {
pdf_reader_filter_key_name
}
///|
fn pdf_reader_short_filter_name() -> PdfName {
pdf_reader_short_filter_key_name
}
///|
fn pdf_reader_decodeparms_name() -> PdfName {
pdf_reader_decodeparms_key_name
}
///|
fn pdf_reader_short_decodeparms_name() -> PdfName {
pdf_reader_short_decodeparms_key_name
}
///|
priv struct PdfReaderXRefStreamRefSet {
values : @hashmap.HashMap[Int, PdfNumberSet]
}
///|
fn pdf_reader_xref_stream_ref_set(
refs : ArrayView[(Int, Int)],
) -> PdfReaderXRefStreamRefSet {
let values : @hashmap.HashMap[Int, PdfNumberSet] = @hashmap.HashMap::new()
for item in refs {
let (object_number, offset) = item
match values.get(object_number) {
Some(offsets) => offsets.add(offset)
None => {
let offsets = pdf_number_set([])
offsets.add(offset)
values[object_number] = offsets
}
}
}
{ values, }
}
///|
fn PdfReaderXRefStreamRefSet::contains(
self : PdfReaderXRefStreamRefSet,
object_number : Int,
offset : Int,
) -> Bool {
match self.values.get(object_number) {
Some(offsets) => offsets.contains(offset)
None => false
}
}
///|
fn pdf_reader_stream_dictionary_without_xref_keys(
dictionary : PdfObject,
) -> PdfObject raise PdfError {
dictionary
.remove_dict_entry(pdf_reader_type_name())
.remove_dict_entry(pdf_reader_w_name())
.remove_dict_entry(pdf_reader_index_name())
.remove_dict_entry(pdf_length_name())
.remove_dict_entry(pdf_reader_filter_name())
.remove_dict_entry(pdf_reader_short_filter_name())
.remove_dict_entry(pdf_reader_decodeparms_name())
.remove_dict_entry(pdf_reader_short_decodeparms_name())
}
///|
fn pdf_reader_xref_stream_type_ok(dictionary : PdfObject) -> Bool {
match dictionary.lookup_immediate(pdf_reader_type_name()) {
Some(PdfNameObject(name)) => name == pdf_reader_xref_name()
Some(_) => false
None => dictionary.lookup_immediate(pdf_reader_w_name()) != None
}
}
///|
fn pdf_reader_object_is_xref_stream(object : PdfObject) -> Bool {
match object {
PdfStreamObject(stream) => pdf_reader_xref_stream_type_ok(stream.dictionary)
_ => false
}
}
///|
fn pdf_xref_stream_int_array(
dictionary : PdfObject,
key : PdfName,
) -> Array[Int]? raise PdfError {
match dictionary.lookup_immediate(key) {
Some(PdfArray(values)) => {
let output : Array[Int] = []
for value in values {
match value {
PdfInteger(number) => output.push(number)
_ => raise XRefEntryExpected
}
}
Some(output)
}
Some(_) => raise XRefEntryExpected
None => None
}
}
///|
fn pdf_xref_stream_widths(
dictionary : PdfObject,
) -> (Int, Int, Int) raise PdfError {
match pdf_xref_stream_int_array(dictionary, pdf_reader_w_name()) {
Some([w0, w1, w2]) if w0 >= 0 && w1 >= 0 && w2 >= 0 && w0 + w1 + w2 > 0 =>
(w0, w1, w2)
_ => raise XRefEntryExpected
}
}
///|
fn pdf_xref_stream_index_pairs(
dictionary : PdfObject,
) -> Array[(Int, Int)] raise PdfError {
match pdf_xref_stream_int_array(dictionary, pdf_reader_index_name()) {
Some(values) => {
if values.length() % 2 != 0 {
raise XRefEntryExpected
}
let pairs : Array[(Int, Int)] = []
for i = 0; i < values.length(); i = i + 2 {
let start = values[i]
let length = values[i + 1]
if start < 0 || length < 0 {
raise XRefEntryExpected
}
pairs.push((start, length))
}
pairs
}
None =>
match dictionary.lookup_immediate(pdf_reader_size_name()) {
Some(PdfInteger(size)) if size >= 0 => [(0, size)]
_ => raise XRefEntryExpected
}
}
}
///|
fn pdf_xref_stream_read_field(
data : BytesView,
position : Int,
width : Int,
) -> (Int, Int) raise PdfError {
if width == 0 {
(0, position)
} else if position < 0 || position + width > data.length() {
raise XRefEntryExpected
} else {
let mut value = 0
for i in position..<(position + width) {
value = value * 256 + data[i].to_int()
}
(value, position + width)
}
}
///|
fn pdf_xref_stream_read_entry(
data : BytesView,
position : Int,
w0 : Int,
w1 : Int,
w2 : Int,
) -> (Int, Int, Int, Int) raise PdfError {
let (raw_kind, after_kind) = pdf_xref_stream_read_field(data, position, w0)
let (field2, after_field2) = pdf_xref_stream_read_field(data, after_kind, w1)
let (field3, after_field3) = pdf_xref_stream_read_field(
data, after_field2, w2,
)
let kind = if w0 == 0 { 1 } else { raw_kind }
(kind, field2, field3, after_field3)
}
///|
fn pdf_xref_stream_entries(
data : BytesView,
widths : (Int, Int, Int),
index_pairs : ArrayView[(Int, Int)],
) -> Array[PdfClassicXRefEntry] raise PdfError {
let (w0, w1, w2) = widths
let entries : Array[PdfClassicXRefEntry] = []
let mut position = 0
for pair in index_pairs {
let (start_object, count) = pair
for offset in 0..
entries.push({
object_number,
offset: field2,
generation: field3,
in_use: false,
object_stream: None,
})
1 =>
entries.push({
object_number,
offset: field2,
generation: field3,
in_use: true,
object_stream: None,
})
// Object-stream entries hide older xrefs and are loaded after their
// containing object stream has been parsed as a plain entry.
2 =>
entries.push({
object_number,
offset: field2,
generation: field3,
in_use: true,
object_stream: Some({ object_stream_number: field2, index: field3 }),
})
// Unknown entry types are retained as non-loadable markers so a newer
// xref stream can still hide older or physically reconstructed objects.
_ =>
entries.push({
object_number,
offset: field2,
generation: field3,
in_use: false,
object_stream: None,
})
}
}
}
entries
}
///|
fn PdfDocument::pdf_object_stream_required_int(
self : PdfDocument,
dictionary : PdfObject,
key : PdfName,
) -> Int raise PdfError {
match self.lookup_direct(key, dictionary) {
Some(PdfInteger(value)) if value >= 0 => value
_ => raise XRefEntryExpected
}
}
///|
fn pdf_object_stream_number_offsets(
data : BytesView,
first : Int,
count : Int,
) -> Array[(Int, Int)] raise PdfError {
if first < 0 || first > data.length() || count < 0 {
raise StreamDataExpected
}
let values = pdf_parse_lexemes(pdf_lex_view(data[0:first]))
if values.length() < count * 2 {
raise XRefEntryExpected
}
let pairs : Array[(Int, Int)] = []
for i in 0.. 0 &&
offset >= 0 => pairs.push((object_number, offset))
_ => raise XRefEntryExpected
}
}
pairs
}
///|
fn pdf_object_stream_end_offsets(
pairs : ArrayView[(Int, Int)],
data_length : Int,
first : Int,
) -> Array[Int] {
let default_end = data_length - first
let sorted : Array[(Int, Int)] = []
for index = 0; index < pairs.length(); index = index + 1 {
sorted.push((pairs[index].1, index))
}
sorted.sort_by(fn(left, right) { left.0.compare(right.0) })
let output = [ for _ in pairs => default_end ]
let mut start = 0
while start < sorted.length() {
let offset = sorted[start].0
let mut end = start + 1
while end < sorted.length() && sorted[end].0 == offset {
end += 1
}
let next_offset = if end < sorted.length() && sorted[end].0 < default_end {
sorted[end].0
} else {
default_end
}
for index in start.. PdfObjectStreamContext raise PdfError {
match object_stream_object {
PdfStreamObject(stream) => {
match stream.dictionary.lookup_immediate(pdf_reader_type_name()) {
Some(PdfNameObject(name)) if name == pdf_reader_objstm_name() => ()
Some(_) => raise XRefEntryExpected
None => ()
}
let decoded_stream = self.pdf_decode_stream_value_direct(stream)
let count = self.pdf_object_stream_required_int(
decoded_stream.dictionary,
pdf_reader_n_name(),
)
let first = self.pdf_object_stream_required_int(
decoded_stream.dictionary,
pdf_reader_first_name(),
)
let data = pdf_stream_data_bytes(decoded_stream.data)
let pairs = pdf_object_stream_number_offsets(data[:], first, count)
let end_offsets = pdf_object_stream_end_offsets(
pairs[:],
data.length(),
first,
)
{ data, first, pairs, end_offsets }
}
_ => raise XRefEntryExpected
}
}
///|
fn PdfObjectStreamContext::read_reference(
self : PdfObjectStreamContext,
reference : ObjectStreamReference,
) -> (Int, PdfObject) raise PdfError {
if reference.index < 0 || reference.index >= self.pairs.length() {
raise XRefEntryExpected
}
let next_offset = self.end_offsets[reference.index]
let (object_number, offset) = self.pairs[reference.index]
let start = self.first + offset
let end = self.first + next_offset
if start < self.first || start > end || end > self.data.length() {
raise StreamDataExpected
}
(
object_number,
pdf_parse_object_stream_member_from_view(self.data[start:end]),
)
}
///|
fn PdfDocument::read_object_stream_context(
self : PdfDocument,
object_stream_number : Int,
) -> PdfObjectStreamContext raise PdfError {
self.read_object_stream_context_from_object(
self.lookup_object_or_null(object_stream_number),
)
}
///|
fn PdfDocument::read_object_stream_context_with_passwords(
self : PdfDocument,
object_stream_number : Int,
user_password : BytesView?,
owner_password : BytesView?,
) -> (PdfObjectStreamContext, Bool) raise PdfError {
if !self.is_encrypted() {
(self.read_object_stream_context(object_stream_number), false)
} else {
let entry = match self.objects.get_entry(object_stream_number) {
Some(entry) => entry
None => raise XRefEntryExpected
}
let object_stream_object = self.lookup_object_or_null(object_stream_number)
match
self.decrypt_single_stream(
object_stream_number,
entry.generation,
object_stream_object,
user_password~,
owner_password~,
) {
Some(decrypted_stream) =>
(self.read_object_stream_context_from_object(decrypted_stream), true)
None => raise EncryptionAuthenticationUnsupported(PdfCryptAESV2)
}
}
}
///|
fn pdf_reader_object_stream_groups(
xrefs : ArrayView[PdfClassicXRefEntry],
) -> Array[(Int, Array[(PdfClassicXRefEntry, ObjectStreamReference)])] {
let groups : Array[(Int, Array[(PdfClassicXRefEntry, ObjectStreamReference)])] = []
let indexes : @hashmap.HashMap[Int, Int] = @hashmap.HashMap::new()
for xref in xrefs {
match xref.object_stream {
Some(reference) =>
if xref.in_use && xref.object_number != 0 {
match indexes.get(reference.object_stream_number) {
Some(index) => groups[index].1.push((xref, reference))
None => {
indexes[reference.object_stream_number] = groups.length()
groups.push((reference.object_stream_number, [(xref, reference)]))
}
}
}
None => ()
}
}
groups
}
///|
fn PdfDocument::load_embedded_objects_from_context(
self : PdfDocument,
xrefs : ArrayView[(PdfClassicXRefEntry, ObjectStreamReference)],
object_stream_number : Int,
context : PdfObjectStreamContext,
already_decrypted : Bool,
) -> Unit raise PdfError {
for entry in xrefs {
let xref = entry.0
let reference = entry.1
if reference.object_stream_number == object_stream_number {
let (object_number, object) = context.read_reference(reference)
if object_number != xref.object_number {
raise XRefEntryExpected
}
if already_decrypted {
self.load_object_given_number_already_decrypted(
xref.object_number,
object,
)
} else {
self.load_object_given_number(xref.object_number, object)
}
}
}
}
///|
fn ByteCursor::read_pdf_xref_stream_section(
self : ByteCursor,
) -> PdfXRefSection raise PdfError {
let stream_object_offset = self.absolute_position()
if !pdf_is_digit_byte(self.peek_byte()) {
raise XRefExpected
}
let parsed = self.read_first_indirect_object_at_current([])
guard parsed.object is PdfStreamObject(stream) else { raise XRefExpected }
if !pdf_reader_xref_stream_type_ok(stream.dictionary) {
raise XRefExpected
}
let decoded_stream = pdf_decode_stream_value(stream)
let widths = pdf_xref_stream_widths(decoded_stream.dictionary)
let index_pairs = pdf_xref_stream_index_pairs(decoded_stream.dictionary)
let data = pdf_stream_data_bytes(decoded_stream.data)
{
entries: pdf_xref_stream_entries(data[:], widths, index_pairs[:]),
trailer: pdf_reader_stream_dictionary_without_xref_keys(
decoded_stream.dictionary,
),
stream_object_number: Some(parsed.number),
stream_object_offset: Some(stream_object_offset),
}
}
///|
fn ByteCursor::read_pdf_xref_section(
self : ByteCursor,
) -> PdfXRefSection raise PdfError {
let position = self.absolute_position()
if self.matches_pdf_ascii_at(position, [120, 114, 101, 102]) {
let entries = self.read_pdf_classic_xref_entries()
let trailer = self.read_pdf_trailer_dictionary()
{ entries, trailer, stream_object_number: None, stream_object_offset: None }
} else {
self.read_pdf_xref_stream_section()
}
}
///|
fn ByteCursor::read_pdf_trailer_dictionary(
self : ByteCursor,
) -> PdfObject raise PdfError {
self.drop_pdf_whitespace()
self.consume_pdf_ascii([116, 114, 97, 105, 108, 101, 114], TrailerExpected)
self.drop_pdf_whitespace()
let object = pdf_parse_single_lexeme_object(self.lex_pdf_tokens())
match object {
PdfDictionary(_) => object
_ => raise TrailerExpected
}
}
///|
fn ByteCursor::read_plain_indirect_object_at_current(
self : ByteCursor,
) -> PdfParsedIndirectObject raise PdfError {
let (objects, prefix) = pdf_parse_indirect_object_segments(
self.lex_pdf_tokens(),
)
if objects.length() > 0 {
objects[0]
} else {
match prefix {
Some(prefix) =>
if self.stopped_at_pdf_stream_candidate() {
raise StreamLengthExpected
} else {
prefix
}
None => raise ParseIndirectObjectExpected
}
}
}
///|
fn ByteCursor::stopped_at_pdf_stream_candidate(self : ByteCursor) -> Bool {
let position = self.absolute_position()
self.matches_pdf_ascii_at(position, [115]) &&
!self.matches_pdf_ascii_at(position, [
115, 116, 97, 114, 116, 120, 114, 101, 102,
])
}
///|
fn ByteCursor::read_first_indirect_object_segment_at_current(
self : ByteCursor,
) -> (PdfParsedIndirectObject, Bool) raise PdfError {
let (object, _, complete) = pdf_parse_indirect_object_segment_at(
self.lex_pdf_tokens(),
0,
)
(object, complete)
}
///|
fn ByteCursor::read_pdf_linearized_marker(
self : ByteCursor,
) -> Bool raise PdfError {
ignore(self.read_pdf_header())
let parsed = self.read_plain_indirect_object_at_current()
match parsed.object.lookup_immediate(pdf_reader_linearized_name()) {
Some(PdfInteger(_)) | Some(PdfReal(_)) => true
_ => false
}
}
///|
fn pdf_read_was_linearized(data : BytesView) -> Bool {
let result : Result[Bool, Error] = try? byte_cursor_of_view(data).read_pdf_linearized_marker()
match result {
Ok(value) => value
Err(_) => false
}
}
///|
pub fn pdf_is_linearized(data : BytesView) -> Bool {
pdf_read_was_linearized(data)
}
///|
pub fn pdf_revisions_from_view(data : BytesView) -> Int raise PdfError {
let cursor = byte_cursor_of_view(data)
ignore(cursor.read_pdf_header())
let mut current_xref = pdf_read_startxref_position(data)
let mut revisions = 0
let mut done = false
while !done {
if revisions >= 1024 {
raise XRefExpected
}
cursor.seek_pdf_absolute(current_xref)
let section = cursor.read_pdf_xref_section()
revisions += 1
match pdf_trailer_prev_position(section.trailer) {
Some(previous) => current_xref = previous
None => done = true
}
}
if pdf_is_linearized(data) && revisions > 1 {
revisions - 1
} else {
revisions
}
}
///|
pub fn pdf_revisions_from_bytes(data : PdfBytes) -> Int raise PdfError {
pdf_revisions_from_view(data[:])
}
///|
fn ByteCursor::resolve_classic_stream_length(
self : ByteCursor,
object : PdfObject,
xrefs : ArrayView[PdfClassicXRefEntry],
) -> Int raise PdfError {
match object.lookup_immediate(pdf_length_name()) {
Some(PdfInteger(length)) if length >= 0 => length
Some(PdfIndirect(number)) =>
match pdf_classic_xref_lookup(xrefs, number) {
Some(xref) => {
let saved_position = self.absolute_position()
self.seek_pdf_absolute(xref.offset)
let parsed_result : Result[(PdfParsedIndirectObject, Bool), Error] = try? self.read_first_indirect_object_segment_at_current()
self.seek_pdf_absolute(saved_position)
let (parsed, complete) = match parsed_result {
Ok(parsed) => parsed
Err(_) => raise StreamLengthExpected
}
if parsed.number != number || parsed.generation != xref.generation {
raise XRefEntryExpected
}
if !complete {
raise StreamLengthExpected
}
match parsed.object {
PdfInteger(length) if length >= 0 => length
_ => raise StreamLengthExpected
}
}
None => raise StreamLengthExpected
}
_ => raise StreamLengthExpected
}
}
///|
fn ByteCursor::read_first_indirect_object_at_current(
self : ByteCursor,
xrefs : ArrayView[PdfClassicXRefEntry],
) -> PdfParsedIndirectObject raise PdfError {
let (object, _, complete) = pdf_parse_indirect_object_segment_at(
self.lex_pdf_tokens(),
0,
)
if complete {
object
} else if !self.stopped_at_pdf_stream_candidate() {
object
} else {
let length_result : Result[Int, Error] = try? self.resolve_classic_stream_length(
object.object,
xrefs,
)
match length_result {
Ok(length) =>
self.finish_pdf_stream_object_with_length(
object,
length,
defer_stream_data=true,
)
Err(_) => self.finish_pdf_malformed_stream_object(object)
}
}
}
///|
fn pdf_reader_root_name() -> PdfName {
pdf_reader_root_key_name
}
///|
fn pdf_reader_prev_name() -> PdfName {
pdf_reader_prev_key_name
}
///|
fn pdf_reader_xrefstm_name() -> PdfName {
pdf_reader_xrefstm_key_name
}
///|
fn pdf_trailer_prev_position(trailer : PdfObject) -> Int? raise PdfError {
match trailer.lookup_immediate(pdf_reader_prev_name()) {
Some(PdfInteger(position)) if position >= 0 => Some(position)
Some(_) => raise TrailerExpected
None => None
}
}
///|
fn pdf_trailer_xref_stream_position(trailer : PdfObject) -> Int? raise PdfError {
match trailer.lookup_immediate(pdf_reader_xrefstm_name()) {
Some(PdfInteger(position)) if position >= 0 => Some(position)
Some(PdfInteger(_)) => raise XRefExpected
_ => None
}
}
///|
fn pdf_reader_sanitize_trailer(
trailer : PdfObject,
size : Int,
) -> PdfObject raise PdfError {
trailer
.add_dict_entry(pdf_reader_size_name(), PdfInteger(size))
.remove_dict_entry(pdf_reader_w_name())
.remove_dict_entry(pdf_reader_type_name())
.remove_dict_entry(pdf_reader_index_name())
.remove_dict_entry(pdf_reader_prev_name())
.remove_dict_entry(pdf_reader_xrefstm_name())
.remove_dict_entry(pdf_reader_filter_name())
.remove_dict_entry(pdf_reader_decodeparms_name())
}
///|
fn ByteCursor::read_reconstructed_object_at(
self : ByteCursor,
position : Int,
xrefs : ArrayView[PdfClassicXRefEntry],
) -> PdfParsedIndirectObject raise PdfError {
self.seek_pdf_absolute(position)
self.read_reconstructed_indirect_object_at_current(xrefs)
}
///|
fn ByteCursor::lex_reconstructed_indirect_object_tokens_at_current(
self : ByteCursor,
) -> Array[PdfLexeme] raise PdfError {
let start = self.absolute_position()
let mut search = start
let mut done = false
let mut found = false
let mut output : Array[PdfLexeme] = []
while !done {
let endobj = pdf_find_ascii_from(self.data, search, [
101, 110, 100, 111, 98, 106,
])
let end = if endobj < 0 { self.data.length() } else { endobj + 6 }
let cursor = byte_cursor_of_view(self.data[start:end])
let tokens : Result[Array[PdfLexeme], Error] = try? cursor.lex_pdf_tokens()
match tokens {
Ok(tokens) => {
output = tokens
self.seek_pdf_absolute(start + cursor.absolute_position())
found = true
done = true
}
Err(_) => if endobj < 0 { done = true } else { search = endobj + 1 }
}
}
if found {
output
} else {
raise ParseIndirectObjectExpected
}
}
///|
fn ByteCursor::read_reconstructed_indirect_prefix_at_current(
self : ByteCursor,
) -> PdfParsedIndirectObject raise PdfError {
let (object, _, _) = pdf_parse_indirect_object_segment_at(
self.lex_reconstructed_indirect_object_tokens_at_current(),
0,
)
object
}
///|
fn ByteCursor::read_reconstructed_indirect_scan_candidate_at_current(
self : ByteCursor,
xrefs : ArrayView[PdfClassicXRefEntry],
) -> (PdfParsedIndirectObject, Int?) raise PdfError {
let (object, _, complete) = pdf_parse_indirect_object_segment_at(
self.lex_reconstructed_indirect_object_tokens_at_current(),
0,
)
if complete {
(object, Some(self.absolute_position()))
} else if self.stopped_at_pdf_stream_candidate() {
let length_result : Result[Int, Error] = try? self.resolve_classic_stream_length(
object.object,
xrefs,
)
let stream = match length_result {
Ok(length) =>
self.finish_pdf_stream_object_with_length(
object,
length,
defer_stream_data=true,
)
Err(_) => self.finish_pdf_malformed_stream_object(object)
}
(stream, Some(self.absolute_position()))
} else {
(object, None)
}
}
///|
fn ByteCursor::read_reconstructed_indirect_object_at_current(
self : ByteCursor,
xrefs : ArrayView[PdfClassicXRefEntry],
) -> PdfParsedIndirectObject raise PdfError {
let (object, _) = self.read_reconstructed_indirect_scan_candidate_at_current(
xrefs,
)
object
}
///|
fn pdf_read_reconstructed_xrefs(
data : BytesView,
) -> Array[PdfClassicXRefEntry] raise PdfError {
let xrefs : Array[PdfClassicXRefEntry] = []
let cursor = byte_cursor_of_view(data)
let mut position = 0
while position < data.length() {
if pdf_reader_reconstruction_indirect_header_at(data, position) {
cursor.seek_pdf_absolute(position)
let parsed : Result[(PdfParsedIndirectObject, Int?), Error] = try? cursor.read_reconstructed_indirect_scan_candidate_at_current(
xrefs[:],
)
match parsed {
Ok((object, next_position)) => {
if object.number > 0 {
xrefs.push({
object_number: object.number,
offset: position,
generation: object.generation,
in_use: true,
object_stream: None,
})
}
match next_position {
Some(next) if next > position => position = next
_ => position += 1
}
}
Err(_) =>
match
pdf_reader_reconstruction_skip_malformed_object(data, position) {
Some(next) if next > position => position = next
_ => position += 1
}
}
} else {
position += 1
}
}
xrefs
}
///|
fn ByteCursor::read_reconstructed_trailer_at(
self : ByteCursor,
position : Int,
) -> PdfObject raise PdfError {
self.seek_pdf_absolute(position)
self.read_pdf_trailer_dictionary()
}
///|
fn ByteCursor::read_reconstructed_xref_stream_trailer_at(
self : ByteCursor,
position : Int,
) -> PdfObject raise PdfError {
self.seek_pdf_absolute(position)
let parsed = self.read_reconstructed_indirect_prefix_at_current()
match parsed.object {
PdfDictionary(_) as dictionary =>
if pdf_reader_xref_stream_type_ok(dictionary) {
pdf_reader_stream_dictionary_without_xref_keys(dictionary)
} else {
raise XRefExpected
}
_ => raise XRefExpected
}
}
///|
fn pdf_read_reconstructed_trailers(
data : BytesView,
include_xref_streams? : Bool = true,
) -> Array[PdfObject] {
let trailers : Array[PdfObject] = []
let cursor = byte_cursor_of_view(data)
for position = 0; position < data.length(); position = position + 1 {
if pdf_view_matches_ascii_at(data, position, [
116, 114, 97, 105, 108, 101, 114,
]) {
let trailer : Result[PdfObject, Error] = try? cursor.read_reconstructed_trailer_at(
position,
)
match trailer {
Ok(object) => trailers.push(object)
Err(_) => ()
}
} else if include_xref_streams &&
pdf_reader_reconstruction_indirect_header_at(data, position) {
let trailer : Result[PdfObject, Error] = try? cursor.read_reconstructed_xref_stream_trailer_at(
position,
)
match trailer {
Ok(object) => trailers.push(object)
Err(_) => ()
}
}
}
trailers
}
///|
fn ByteCursor::read_reconstructed_xref_stream_section_at(
self : ByteCursor,
position : Int,
) -> (PdfXRefSection, Int) raise PdfError {
self.seek_pdf_absolute(position)
let section = self.read_pdf_xref_stream_section()
(section, self.absolute_position())
}
///|
fn ByteCursor::read_reconstructed_classic_xref_section_at(
self : ByteCursor,
position : Int,
) -> (PdfXRefSection, Int) raise PdfError {
self.seek_pdf_absolute(position)
let section = self.read_pdf_xref_section()
guard section.stream_object_number is None else { raise XRefExpected }
(section, self.absolute_position())
}
///|
fn pdf_read_reconstructed_classic_xref_sections(
data : BytesView,
) -> Array[(Int, PdfXRefSection)] {
let sections : Array[(Int, PdfXRefSection)] = []
let cursor = byte_cursor_of_view(data)
let mut position = 0
while position < data.length() {
if pdf_view_matches_ascii_at(data, position, [120, 114, 101, 102]) {
let parsed : Result[(PdfXRefSection, Int), Error] = try? cursor.read_reconstructed_classic_xref_section_at(
position,
)
match parsed {
Ok((section, next_position)) => {
sections.push((position, section))
position = next_position
}
Err(_) => position += 4
}
} else {
position += 1
}
}
sections
}
///|
fn pdf_read_reconstructed_xref_stream_sections(
data : BytesView,
) -> Array[PdfXRefSection] {
let sections : Array[PdfXRefSection] = []
let cursor = byte_cursor_of_view(data)
let mut position = 0
while position < data.length() {
if pdf_reader_reconstruction_indirect_header_at(data, position) {
let parsed : Result[(PdfXRefSection, Int), Error] = try? cursor.read_reconstructed_xref_stream_section_at(
position,
)
match parsed {
Ok((section, next_position)) => {
sections.push(section)
position = next_position
}
Err(_) =>
position = pdf_reader_reconstruction_skip_malformed_object(
data, position,
).unwrap()
}
} else {
position += 1
}
}
sections
}
///|
fn pdf_reconstructed_xref_stream_entries(
sections : ArrayView[PdfXRefSection],
) -> Array[PdfClassicXRefEntry] {
let xrefs : Array[PdfClassicXRefEntry] = []
for index = sections.length() - 1; index >= 0; index = index - 1 {
pdf_classic_xrefs_add_newer_first(xrefs, sections[index].entries[:])
}
xrefs
}
///|
fn pdf_reconstructed_classic_xref_entries(
sections : ArrayView[(Int, PdfXRefSection)],
) -> Array[PdfClassicXRefEntry] {
let xrefs : Array[PdfClassicXRefEntry] = []
for index = sections.length() - 1; index >= 0; index = index - 1 {
pdf_classic_xrefs_add_newer_first(xrefs, sections[index].1.entries[:])
}
xrefs
}
///|
fn pdf_reconstructed_xref_stream_refs(
sections : ArrayView[PdfXRefSection],
) -> Array[(Int, Int)] {
let refs : Array[(Int, Int)] = []
for section in sections {
refs.push(
(
section.stream_object_number.unwrap(),
section.stream_object_offset.unwrap(),
),
)
}
refs
}
///|
fn PdfDocument::load_reconstructed_xref_stream_plain_objects(
self : PdfDocument,
data : BytesView,
sections : ArrayView[PdfXRefSection],
xrefs : ArrayView[PdfClassicXRefEntry],
) -> Unit {
let cursor = byte_cursor_of_view(data)
let xref_stream_refs = pdf_reconstructed_xref_stream_refs(sections)
let xref_stream_ref_set = pdf_reader_xref_stream_ref_set(xref_stream_refs[:])
for xref in xrefs {
match xref.object_stream {
Some(_) => ()
None =>
if xref.in_use &&
xref.object_number != 0 &&
!xref_stream_ref_set.contains(xref.object_number, xref.offset) {
try {
cursor.seek_pdf_absolute(xref.offset)
let parsed = cursor.read_first_indirect_object_at_current(xrefs)
if parsed.number == xref.object_number &&
parsed.generation == xref.generation {
self.load_object_given_number(
parsed.number,
parsed.object,
generation=parsed.generation,
)
}
} catch {
_ => ()
}
}
}
}
}
///|
fn PdfDocument::load_reconstructed_classic_xref_plain_objects(
self : PdfDocument,
data : BytesView,
xrefs : ArrayView[PdfClassicXRefEntry],
) -> Unit {
let cursor = byte_cursor_of_view(data)
for xref in xrefs {
if xref.in_use && xref.object_number != 0 {
try {
cursor.seek_pdf_absolute(xref.offset)
let parsed = cursor.read_first_indirect_object_at_current(xrefs)
if parsed.number == xref.object_number &&
parsed.generation == xref.generation {
self.load_object_given_number(
parsed.number,
parsed.object,
generation=parsed.generation,
)
}
} catch {
_ => ()
}
}
}
}
///|
fn PdfDocument::load_reconstructed_xref_stream_embedded_objects(
self : PdfDocument,
xrefs : ArrayView[PdfClassicXRefEntry],
) -> Unit {
for group in pdf_reader_object_stream_groups(xrefs) {
let object_stream_number = group.0
let context_result : Result[PdfObjectStreamContext, Error] = try? self.read_object_stream_context(
object_stream_number,
)
match context_result {
Ok(context) =>
for entry in group.1 {
let xref = entry.0
let reference = entry.1
if reference.object_stream_number == object_stream_number {
try {
let (object_number, object) = context.read_reference(reference)
if object_number == xref.object_number {
self.load_object_given_number(xref.object_number, object)
}
} catch {
_ => ()
}
}
}
Err(_) => ()
}
}
}
///|
fn PdfDocument::load_reconstructed_xref_stream_embedded_objects_with_passwords(
self : PdfDocument,
xrefs : ArrayView[PdfClassicXRefEntry],
user_password : BytesView?,
owner_password : BytesView?,
) -> Unit {
for group in pdf_reader_object_stream_groups(xrefs) {
let object_stream_number = group.0
let context_result : Result[(PdfObjectStreamContext, Bool), Error] = try? self.read_object_stream_context_with_passwords(
object_stream_number, user_password, owner_password,
)
match context_result {
Ok((context, already_decrypted)) =>
for entry in group.1 {
let xref = entry.0
let reference = entry.1
if reference.object_stream_number == object_stream_number {
try {
let (object_number, object) = context.read_reference(reference)
if object_number == xref.object_number {
if already_decrypted {
self.load_object_given_number_already_decrypted(
xref.object_number,
object,
)
} else {
self.load_object_given_number(xref.object_number, object)
}
}
} catch {
_ => ()
}
}
}
Err(_) => ()
}
}
}
///|
fn PdfDocument::load_reconstructed_xref_stream_objects(
self : PdfDocument,
data : BytesView,
sections : ArrayView[PdfXRefSection],
) -> Unit {
let xrefs = pdf_reconstructed_xref_stream_entries(sections)
self.load_reconstructed_xref_stream_plain_objects(data, sections, xrefs[:])
self.load_reconstructed_xref_stream_embedded_objects(xrefs[:])
}
///|
priv struct PdfReconstructedXRefCoverage {
sections : Array[(Int, PdfNumberSet)]
}
///|
fn pdf_reconstructed_classic_xref_entry_is_verified(
data : BytesView,
entries : ArrayView[PdfClassicXRefEntry],
xref : PdfClassicXRefEntry,
) -> Bool {
if !xref.in_use {
true
} else {
let cursor = byte_cursor_of_view(data)
try {
cursor.seek_pdf_absolute(xref.offset)
let parsed = cursor.read_first_indirect_object_at_current(entries)
parsed.number == xref.object_number &&
parsed.generation == xref.generation
} catch {
_ => false
}
}
}
///|
fn pdf_reconstructed_xref_coverage(
data : BytesView,
classic_sections : ArrayView[(Int, PdfXRefSection)],
xref_stream_sections : ArrayView[PdfXRefSection],
) -> PdfReconstructedXRefCoverage {
let covered : Array[(Int, PdfNumberSet)] = []
for item in classic_sections {
let (offset, section) = item
let numbers = pdf_number_set([])
for xref in section.entries {
if xref.object_number > 0 &&
pdf_reconstructed_classic_xref_entry_is_verified(
data,
section.entries,
xref,
) {
numbers.add(xref.object_number)
}
}
covered.push((offset, numbers))
}
for section in xref_stream_sections {
match section.stream_object_offset {
Some(offset) => {
let numbers = pdf_number_set([])
for xref in section.entries {
if xref.object_number > 0 {
numbers.add(xref.object_number)
}
}
covered.push((offset, numbers))
}
None => ()
}
}
{ sections: covered }
}
///|
fn PdfReconstructedXRefCoverage::contains(
self : PdfReconstructedXRefCoverage,
object_number : Int,
object_offset : Int,
) -> Bool {
for section in self.sections {
let (section_offset, object_numbers) = section
if object_offset <= section_offset && object_numbers.contains(object_number) {
break true
}
} nobreak {
false
}
}
///|
fn PdfDocument::select_reconstructed_trailer(
self : PdfDocument,
trailers : ArrayView[PdfObject],
) -> PdfObject raise PdfError {
for index = trailers.length() - 1; index >= 0; index = index - 1 {
let trailer = trailers[index]
match trailer.lookup_immediate(pdf_reader_root_name()) {
Some(PdfIndirect(root)) =>
match self.lookup_object_or_null(root) {
PdfDictionary(_) => break trailer
_ => ()
}
_ => ()
}
} nobreak {
raise RootExpected
}
}
///|
fn PdfDocument::set_trailer_and_root(
self : PdfDocument,
trailer : PdfObject,
) -> Unit raise PdfError {
self.set_trailer_dict(trailer)
match trailer.lookup_immediate(pdf_reader_root_name()) {
Some(PdfIndirect(root)) => self.set_root(root)
_ => raise RootExpected
}
}
///|
fn pdf_read_reconstructed_document_from_view(
data : BytesView,
include_xref_stream_trailers? : Bool = true,
user_password? : BytesView? = None,
owner_password? : BytesView? = None,
) -> PdfDocument raise PdfError {
let cursor = byte_cursor_of_view(data)
let version = cursor.read_pdf_header()
let document = pdf_document_empty()
document.set_version(version.major, version.minor)
document.set_first_xref(0)
document.set_was_linearized(pdf_read_was_linearized(data))
let classic_xref_sections = pdf_read_reconstructed_classic_xref_sections(data)
let xref_stream_sections : Array[PdfXRefSection] = if include_xref_stream_trailers {
pdf_read_reconstructed_xref_stream_sections(data)
} else {
[]
}
let xref_coverage = pdf_reconstructed_xref_coverage(
data,
classic_xref_sections[:],
xref_stream_sections[:],
)
let classic_xrefs = pdf_reconstructed_classic_xref_entries(
classic_xref_sections[:],
)
document.load_reconstructed_classic_xref_plain_objects(data, classic_xrefs[:])
let reconstructed_xrefs = pdf_read_reconstructed_xrefs(data)
for xref in reconstructed_xrefs {
let parsed_result : Result[PdfParsedIndirectObject, Error] = try? cursor.read_reconstructed_object_at(
xref.offset,
reconstructed_xrefs[:],
)
match parsed_result {
Ok(parsed) =>
if parsed.number > 0 &&
parsed.number == xref.object_number &&
parsed.generation == xref.generation &&
!xref_coverage.contains(parsed.number, xref.offset) &&
!pdf_reader_object_is_xref_stream(parsed.object) {
document.load_object_given_number(
parsed.number,
parsed.object,
generation=parsed.generation,
)
}
Err(_) => ()
}
}
if include_xref_stream_trailers {
document.load_reconstructed_xref_stream_objects(
data,
xref_stream_sections[:],
)
}
document.set_trailer_and_root(
document.select_reconstructed_trailer(
pdf_read_reconstructed_trailers(
data,
include_xref_streams=include_xref_stream_trailers,
)[:],
),
)
if include_xref_stream_trailers && document.is_encrypted() {
document.load_reconstructed_xref_stream_embedded_objects_with_passwords(
pdf_reconstructed_xref_stream_entries(xref_stream_sections[:])[:],
user_password,
owner_password,
)
}
document.set_trailer_dict(
pdf_reader_sanitize_trailer(
document.trailer_dict(),
document.objects.max_object_number() + 1,
),
)
document
}
///|
fn pdf_read_reconstructed_or_raise(
data : BytesView,
original_error : PdfError,
include_xref_stream_trailers? : Bool = false,
user_password? : BytesView? = None,
owner_password? : BytesView? = None,
) -> PdfDocument raise PdfError {
pdf_read_reconstructed_document_from_view(
data,
include_xref_stream_trailers~,
user_password~,
owner_password~,
) catch {
_ => raise original_error
}
}
///|
fn pdf_read_probe_page_tree_or_reconstruct(
data : BytesView,
document : PdfDocument,
) -> PdfDocument raise PdfError {
let catalog = Some(document.catalog()) catch { _ => None }
match catalog {
Some(catalog) if catalog.lookup_immediate(pdf_page_pages_key()) is Some(_) =>
try {
ignore(document.endpage())
document
} catch {
error =>
pdf_read_reconstructed_or_raise(
data,
error,
include_xref_stream_trailers=true,
)
}
Some(_) => document
None => pdf_read_reconstruct_valid_page_tree_or_original(data, document)
}
}
///|
fn pdf_read_reconstruct_valid_page_tree_or_original(
data : BytesView,
document : PdfDocument,
) -> PdfDocument {
let reconstructed = pdf_read_reconstructed_document_from_view(
data,
include_xref_stream_trailers=true,
) catch {
_ => document
}
try {
ignore(reconstructed.endpage())
reconstructed
} catch {
_ => document
}
}
///|
fn pdf_read_reader_revision_target(revision : Int?) -> Int? raise PdfError {
match revision {
Some(value) if value < 1 => raise BadRevision
_ => revision
}
}
///|
fn pdf_reader_revision_reads_section(
revision : Int?,
current_revision : Int,
) -> Bool {
match revision {
Some(target) => target <= current_revision
None => true
}
}
///|
fn pdf_reader_revision_selects_trailer(
revision : Int?,
current_revision : Int,
depth : Int,
) -> Bool {
match revision {
Some(target) => target == current_revision
None => depth == 0
}
}
///|
fn pdf_read_classic_document_from_view_at_revision(
data : BytesView,
revision? : Int? = None,
user_password? : BytesView? = None,
owner_password? : BytesView? = None,
) -> PdfDocument raise PdfError {
let revision = pdf_read_reader_revision_target(revision)
let was_linearized = pdf_read_was_linearized(data)
let cursor = byte_cursor_of_view(data)
let version = cursor.read_pdf_header()
let xref_position = pdf_read_startxref_position(cursor.data)
let xrefs : Array[PdfClassicXRefEntry] = []
let mut trailer = PdfDictionary([])
let xref_stream_refs : Array[(Int, Int)] = []
let mut current_xref = xref_position
let mut done = false
let mut depth = 0
while !done {
if depth >= 1024 {
raise XRefExpected
}
cursor.seek_pdf_absolute(current_xref)
let section = cursor.read_pdf_xref_section()
let current_revision = depth + 1
let read_section = pdf_reader_revision_reads_section(
revision, current_revision,
)
if read_section {
pdf_classic_xrefs_add_newer_first(xrefs, section.entries)
match section.stream_object_number {
Some(number) => xref_stream_refs.push((number, current_xref))
None => ()
}
}
let current_trailer = section.trailer
if pdf_reader_revision_selects_trailer(revision, current_revision, depth) {
trailer = current_trailer
}
if read_section {
match pdf_trailer_xref_stream_position(current_trailer) {
Some(xref_stream_position) => {
cursor.seek_pdf_absolute(xref_stream_position)
let stream_section = cursor.read_pdf_xref_stream_section()
pdf_classic_xrefs_add_newer_first(xrefs, stream_section.entries)
xref_stream_refs.push(
(stream_section.stream_object_number.unwrap(), xref_stream_position),
)
}
None => ()
}
}
match pdf_trailer_prev_position(current_trailer) {
Some(previous) => current_xref = previous
None => if read_section { done = true } else { raise BadRevision }
}
depth += 1
}
let document = pdf_document_empty()
document.set_version(version.major, version.minor)
document.set_first_xref(xref_position)
document.set_was_linearized(was_linearized)
match revision {
Some(value) => document.set_revision_read(value)
None => ()
}
document.set_trailer_and_root(trailer)
let xref_stream_ref_set = pdf_reader_xref_stream_ref_set(xref_stream_refs[:])
for xref in xrefs {
match xref.object_stream {
Some(_) => ()
None =>
if xref.in_use &&
xref.object_number != 0 &&
!xref_stream_ref_set.contains(xref.object_number, xref.offset) {
cursor.seek_pdf_absolute(xref.offset)
let parsed = cursor.read_first_indirect_object_at_current(xrefs)
if parsed.number != xref.object_number ||
parsed.generation != xref.generation {
raise XRefEntryExpected
}
document.load_object_given_number(
parsed.number,
parsed.object,
generation=parsed.generation,
)
}
}
}
for group in pdf_reader_object_stream_groups(xrefs[:]) {
let object_stream_number = group.0
let (context, already_decrypted) = document.read_object_stream_context_with_passwords(
object_stream_number, user_password, owner_password,
)
document.load_embedded_objects_from_context(
group.1[:],
object_stream_number,
context,
already_decrypted,
)
}
document.set_trailer_dict(
pdf_reader_sanitize_trailer(
document.trailer_dict(),
document.objects.max_object_number() + 1,
),
)
document
}
///|
pub fn pdf_read_classic_document_from_view(
data : BytesView,
) -> PdfDocument raise PdfError {
pdf_read_classic_document_from_view_at_revision(data)
}
///|
pub fn pdf_read_classic_document_from_bytes(
data : PdfBytes,
) -> PdfDocument raise PdfError {
pdf_read_classic_document_from_view(data[:])
}
///|
pub fn pdf_read_classic_document_revision_from_view(
data : BytesView,
revision : Int,
) -> PdfDocument raise PdfError {
pdf_read_classic_document_from_view_at_revision(data, revision=Some(revision))
}
///|
pub fn pdf_read_classic_document_revision_from_bytes(
data : PdfBytes,
revision : Int,
) -> PdfDocument raise PdfError {
pdf_read_classic_document_revision_from_view(data[:], revision)
}
///|
pub fn pdf_read_document_from_view(
data : BytesView,
) -> PdfDocument raise PdfError {
pdf_read_probe_page_tree_or_reconstruct(
data,
pdf_read_classic_document_from_view(data),
) catch {
StartXRefExpected => pdf_read_reconstructed_document_from_view(data)
XRefExpected =>
pdf_read_reconstructed_or_raise(
data,
XRefExpected,
include_xref_stream_trailers=true,
)
XRefEntryExpected =>
pdf_read_reconstructed_or_raise(data, XRefEntryExpected)
TrailerExpected => pdf_read_reconstructed_or_raise(data, TrailerExpected)
RootExpected => pdf_read_reconstructed_or_raise(data, RootExpected)
NumberExpected => pdf_read_reconstructed_or_raise(data, NumberExpected)
ParseIndirectObjectExpected =>
pdf_read_reconstructed_or_raise(data, ParseIndirectObjectExpected)
ParseStreamExpected =>
pdf_read_reconstructed_or_raise(data, ParseStreamExpected)
ParseEndStreamExpected =>
pdf_read_reconstructed_or_raise(data, ParseEndStreamExpected)
ParseEndObjectExpected =>
pdf_read_reconstructed_or_raise(data, ParseEndObjectExpected)
EndOfInput => pdf_read_reconstructed_or_raise(data, EndOfInput)
ParseObjectExpected =>
pdf_read_reconstructed_or_raise(data, ParseObjectExpected)
InvalidHexEscape => pdf_read_reconstructed_or_raise(data, InvalidHexEscape)
InvalidCursorPosition(position) =>
pdf_read_reconstructed_or_raise(
data,
InvalidCursorPosition(position),
include_xref_stream_trailers=true,
)
error => raise error
}
}
///|
pub fn pdf_read_document_revision_from_view(
data : BytesView,
revision : Int,
) -> PdfDocument raise PdfError {
pdf_read_classic_document_revision_from_view(data, revision)
}
///|
pub fn pdf_read_document_revision_from_bytes(
data : PdfBytes,
revision : Int,
) -> PdfDocument raise PdfError {
pdf_read_document_revision_from_view(data[:], revision)
}
///|
pub fn pdf_read_document_from_bytes(
data : PdfBytes,
) -> PdfDocument raise PdfError {
pdf_read_document_from_view(data[:])
}
///|
fn pdf_reader_validate_xref_metadata_for_reconstruction(
data : BytesView,
) -> Unit raise PdfError {
let cursor = byte_cursor_of_view(data)
ignore(cursor.read_pdf_header())
let mut current_xref = pdf_read_startxref_position(cursor.data)
let mut done = false
let mut depth = 0
while !done {
if depth >= 1024 {
raise XRefExpected
}
cursor.seek_pdf_absolute(current_xref)
let section = cursor.read_pdf_xref_section()
match pdf_trailer_xref_stream_position(section.trailer) {
Some(xref_stream_position) => {
cursor.seek_pdf_absolute(xref_stream_position)
ignore(cursor.read_pdf_xref_stream_section())
}
None => ()
}
match pdf_trailer_prev_position(section.trailer) {
Some(previous) => current_xref = previous
None => done = true
}
depth += 1
}
}
///|
fn pdf_reader_xref_entry_error_from_metadata(data : BytesView) -> Bool {
let result : Result[Unit, Error] = try? pdf_reader_validate_xref_metadata_for_reconstruction(
data,
)
result is Err(PdfError::XRefEntryExpected)
}
///|
fn pdf_reader_password_error_allows_reconstruction(
data : BytesView,
error : PdfError,
) -> Bool {
match error {
StartXRefExpected
| XRefExpected
| TrailerExpected
| RootExpected
| NumberExpected
| ParseIndirectObjectExpected
| ParseStreamExpected
| ParseEndStreamExpected
| ParseEndObjectExpected
| EndOfInput
| ParseObjectExpected
| InvalidHexEscape
| InvalidCursorPosition(_) => true
XRefEntryExpected => pdf_reader_xref_entry_error_from_metadata(data)
_ => false
}
}
///|
pub fn pdf_read_document_revision_from_view_with_passwords(
data : BytesView,
revision : Int,
user_password? : BytesView? = None,
owner_password? : BytesView? = None,
) -> PdfDecryptionResult? raise PdfError {
let document = pdf_read_classic_document_from_view_at_revision(
data,
revision=Some(revision),
user_password~,
owner_password~,
)
document.decrypt_with_passwords(user_password~, owner_password~)
}
///|
pub fn pdf_read_document_revision_from_bytes_with_passwords(
data : PdfBytes,
revision : Int,
user_password? : BytesView? = None,
owner_password? : BytesView? = None,
) -> PdfDecryptionResult? raise PdfError {
pdf_read_document_revision_from_view_with_passwords(
data[:],
revision,
user_password~,
owner_password~,
)
}
///|
pub fn pdf_read_document_from_view_with_passwords(
data : BytesView,
user_password? : BytesView? = None,
owner_password? : BytesView? = None,
) -> PdfDecryptionResult? raise PdfError {
let document = pdf_read_probe_page_tree_or_reconstruct(
data,
pdf_read_classic_document_from_view_at_revision(
data,
user_password~,
owner_password~,
),
) catch {
error =>
if pdf_reader_password_error_allows_reconstruction(data, error) {
pdf_read_reconstructed_document_from_view(
data,
include_xref_stream_trailers=true,
user_password~,
owner_password~,
)
} else {
raise error
}
}
document.decrypt_with_passwords(user_password~, owner_password~)
}
///|
pub fn pdf_read_document_from_bytes_with_passwords(
data : PdfBytes,
user_password? : BytesView? = None,
owner_password? : BytesView? = None,
) -> PdfDecryptionResult? raise PdfError {
pdf_read_document_from_view_with_passwords(
data[:],
user_password~,
owner_password~,
)
}
///|
fn pdf_length_name() -> PdfName {
pdf_length_key_name
}
///|
fn pdf_stream_length(object : PdfObject) -> Int raise PdfError {
match object.lookup_immediate(pdf_length_name()) {
Some(PdfInteger(length)) if length >= 0 => length
_ => raise StreamLengthExpected
}
}
///|
fn ByteCursor::consume_pdf_ascii(
self : ByteCursor,
values : ArrayView[Int],
error : PdfError,
) -> Unit raise PdfError {
let position = self.absolute_position()
if self.matches_pdf_ascii_at(position, values) {
self.seek(position + values.length())
} else {
raise error
}
}
///|
fn ByteCursor::consume_pdf_stream_line_break(self : ByteCursor) -> Unit {
let value = self.peek_byte()
if value == 13 {
self.nudge()
if self.peek_byte() == 10 {
self.nudge()
}
} else if value == 10 {
self.nudge()
}
}
///|
fn pdf_stream_start_padding_byte(value : Int) -> Bool {
value == 32 || value == 0 || value == 12 || value == 9
}
///|
fn ByteCursor::consume_pdf_stream_start_line_break(self : ByteCursor) -> Unit {
while pdf_stream_start_padding_byte(self.peek_byte()) {
self.nudge()
}
self.consume_pdf_stream_line_break()
}
///|
fn pdf_reader_stream_end_marker_at(data : BytesView, position : Int) -> Bool {
pdf_view_matches_ascii_at(data, position, [
101, 110, 100, 115, 116, 114, 101, 97, 109,
]) ||
pdf_view_matches_ascii_at(data, position, [
10, 101, 110, 100, 115, 116, 114, 101, 97, 109,
]) ||
pdf_view_matches_ascii_at(data, position, [
13, 101, 110, 100, 115, 116, 114, 101, 97, 109,
]) ||
pdf_view_matches_ascii_at(data, position, [
13, 10, 101, 110, 100, 115, 116, 114, 101, 97, 109,
])
}
///|
fn ByteCursor::finish_pdf_stream_object(
self : ByteCursor,
prefix : PdfParsedIndirectObject,
) -> PdfParsedIndirectObject raise PdfError {
let length_result : Result[Int, Error] = try? pdf_stream_length(prefix.object)
match length_result {
Ok(length) => self.finish_pdf_stream_object_with_length(prefix, length)
Err(_) => self.finish_pdf_malformed_stream_object(prefix)
}
}
///|
fn ByteCursor::finish_pdf_stream_object_with_length(
self : ByteCursor,
prefix : PdfParsedIndirectObject,
length : Int,
defer_stream_data? : Bool = false,
) -> PdfParsedIndirectObject raise PdfError {
let stream_keyword_position = self.absolute_position()
self.consume_pdf_ascii([115, 116, 114, 101, 97, 109], ParseStreamExpected)
self.consume_pdf_stream_start_line_break()
let data_position = self.position()
if self.absolute_position() + length > self.data.length() {
raise EndOfInput
}
let stream_data = if defer_stream_data {
self.seek_pdf_absolute(self.absolute_position() + length)
StreamToGet(stream_to_get(self.deep_copy(), data_position, length))
} else {
StreamGot(self.read_bytes(length))
}
if !pdf_reader_stream_end_marker_at(self.data[:], self.absolute_position()) {
self.seek_pdf_absolute(stream_keyword_position)
self.finish_pdf_malformed_stream_object(prefix)
} else {
self.consume_pdf_stream_line_break()
self.drop_pdf_whitespace()
self.consume_pdf_ascii(
[101, 110, 100, 115, 116, 114, 101, 97, 109],
ParseEndStreamExpected,
)
self.drop_pdf_whitespace()
self.consume_pdf_ascii(
[101, 110, 100, 111, 98, 106],
ParseEndObjectExpected,
)
{
number: prefix.number,
generation: prefix.generation,
object: pdf_stream(prefix.object, stream_data),
}
}
}
///|
fn pdf_reader_stream_data_end_before_marker(
data : BytesView,
marker_position : Int,
) -> Int {
if marker_position > 1 &&
data[marker_position - 1].to_int() == 10 &&
data[marker_position - 2].to_int() == 13 {
marker_position - 2
} else if marker_position > 0 &&
(
data[marker_position - 1].to_int() == 10 ||
data[marker_position - 1].to_int() == 13
) {
marker_position - 1
} else {
marker_position
}
}
///|
fn ByteCursor::finish_pdf_malformed_stream_object(
self : ByteCursor,
prefix : PdfParsedIndirectObject,
) -> PdfParsedIndirectObject raise PdfError {
self.consume_pdf_ascii([115, 116, 114, 101, 97, 109], ParseStreamExpected)
self.consume_pdf_stream_start_line_break()
let data_start = self.absolute_position()
let endstream = pdf_find_ascii_from(self.data, data_start, [
101, 110, 100, 115, 116, 114, 101, 97, 109,
])
if endstream < 0 {
raise ParseEndStreamExpected
}
let data_end = pdf_reader_stream_data_end_before_marker(
self.data[:],
endstream,
)
let stream_data = self.data[data_start:data_end].to_owned()
self.seek_pdf_absolute(endstream + 9)
self.drop_pdf_whitespace()
self.consume_pdf_ascii([101, 110, 100, 111, 98, 106], ParseEndObjectExpected)
{
number: prefix.number,
generation: prefix.generation,
object: pdf_stream(
prefix.object.add_dict_entry(
pdf_length_name(),
PdfInteger(stream_data.length()),
),
StreamGot(stream_data),
),
}
}
///|
fn ByteCursor::finish_pdf_object_stream_member_with_length(
self : ByteCursor,
prefix : PdfParsedIndirectObject,
length : Int,
) -> PdfParsedIndirectObject raise PdfError {
let stream_keyword_position = self.absolute_position()
self.consume_pdf_ascii([115, 116, 114, 101, 97, 109], ParseStreamExpected)
self.consume_pdf_stream_start_line_break()
if self.absolute_position() + length > self.data.length() {
raise EndOfInput
}
let stream_data = self.read_bytes(length)
if !pdf_reader_stream_end_marker_at(self.data[:], self.absolute_position()) {
self.seek_pdf_absolute(stream_keyword_position)
self.finish_pdf_malformed_object_stream_member(prefix)
} else {
self.consume_pdf_stream_line_break()
self.drop_pdf_whitespace()
self.consume_pdf_ascii(
[101, 110, 100, 115, 116, 114, 101, 97, 109],
ParseEndStreamExpected,
)
{
number: prefix.number,
generation: prefix.generation,
object: pdf_stream(prefix.object, StreamGot(stream_data)),
}
}
}
///|
fn ByteCursor::finish_pdf_malformed_object_stream_member(
self : ByteCursor,
prefix : PdfParsedIndirectObject,
) -> PdfParsedIndirectObject raise PdfError {
self.consume_pdf_ascii([115, 116, 114, 101, 97, 109], ParseStreamExpected)
self.consume_pdf_stream_start_line_break()
let data_start = self.absolute_position()
let endstream = pdf_find_ascii_from(self.data, data_start, [
101, 110, 100, 115, 116, 114, 101, 97, 109,
])
if endstream < 0 {
raise ParseEndStreamExpected
}
let data_end = pdf_reader_stream_data_end_before_marker(
self.data[:],
endstream,
)
let stream_data = self.data[data_start:data_end].to_owned()
self.seek_pdf_absolute(endstream + 9)
{
number: prefix.number,
generation: prefix.generation,
object: pdf_stream(
prefix.object.add_dict_entry(
pdf_length_name(),
PdfInteger(stream_data.length()),
),
StreamGot(stream_data),
),
}
}
///|
fn ByteCursor::finish_pdf_object_stream_member(
self : ByteCursor,
prefix : PdfParsedIndirectObject,
) -> PdfParsedIndirectObject raise PdfError {
let stream_position = self.absolute_position()
let length_result : Result[Int, Error] = try? pdf_stream_length(prefix.object)
match length_result {
Ok(length) =>
self.finish_pdf_object_stream_member_with_length(prefix, length) catch {
_ => {
self.seek_pdf_absolute(stream_position)
self.finish_pdf_malformed_object_stream_member(prefix)
}
}
Err(_) => self.finish_pdf_malformed_object_stream_member(prefix)
}
}
///|
fn pdf_parse_object_stream_member_from_view(
data : BytesView,
) -> PdfObject raise PdfError {
let cursor = byte_cursor_of_view(data)
let object = pdf_parse_single_lexeme_object(cursor.lex_pdf_tokens())
if cursor.stopped_at_pdf_stream_candidate() {
match object {
PdfDictionary(_) =>
cursor.finish_pdf_object_stream_member({
number: 0,
generation: 0,
object,
}).object
_ => raise ParseSingleObjectExpected
}
} else {
object
}
}
///|
pub fn pdf_parse_stream_object_from_bytes(
data : PdfBytes,
) -> PdfParsedIndirectObject raise PdfError {
pdf_parse_stream_object_from_view(data[:])
}
///|
pub fn pdf_parse_stream_object_from_view(
data : BytesView,
) -> PdfParsedIndirectObject raise PdfError {
let cursor = byte_cursor_of_view(data)
let prefix = pdf_parse_indirect_object_prefix_lexemes(cursor.lex_pdf_tokens())
cursor.finish_pdf_stream_object(prefix)
}