///|
fn pdf_reader_reconstruction_xref_stream_entry_capacity(
sections : ArrayView[PdfXRefSection],
) -> Int {
let mut count = 0
for section in sections {
count += section.entries.length()
}
count
}
///|
fn pdf_reader_reconstruction_classic_xref_entry_capacity(
sections : ArrayView[(Int, PdfXRefSection)],
) -> Int {
let mut count = 0
for section in sections {
count += section.1.entries.length()
}
count
}
///|
/// Merge reconstructed xref-stream entries, newest section first.
pub fn pdf_reader_reconstruction_xref_stream_entries(
sections : ArrayView[PdfXRefSection],
) -> Array[PdfClassicXRefEntry] {
let xrefs : Array[PdfClassicXRefEntry] = Array(
capacity=pdf_reader_reconstruction_xref_stream_entry_capacity(sections),
)
let seen = @core.pdf_number_set([])
for offset in 0.. Array[PdfClassicXRefEntry] {
let xrefs : Array[PdfClassicXRefEntry] = Array(
capacity=pdf_reader_reconstruction_classic_xref_entry_capacity(sections),
)
let seen = @core.pdf_number_set([])
for offset in 0.. Array[(Int, Int)] raise @core.PdfError {
let refs : Array[(Int, Int)] = Array(capacity=sections.length())
for section in sections {
guard section.stream_object_number is Some(number) &&
section.stream_object_offset is Some(offset) else {
raise XRefEntryExpected
}
refs.push((number, offset))
}
refs
}
///|
/// Scan a byte view for physically reconstructable indirect-object xref entries.
///
/// The caller supplies `read_xref_at_current` because turning a candidate
/// indirect object into an xref entry depends on higher-level object parsing
/// and stream-length recovery outside this package.
pub fn pdf_reader_reconstruction_object_xrefs(
data : BytesView,
read_xref_at_current : (
@core.ByteCursor,
Int,
ArrayView[PdfClassicXRefEntry],
PdfClassicXRefIndex,
) -> (PdfClassicXRefEntry?, Int?) raise @core.PdfError,
) -> Array[PdfClassicXRefEntry] raise @core.PdfError {
let xrefs : Array[PdfClassicXRefEntry] = Array(capacity=16)
let cursor = @core.byte_cursor_of_view(data)
let xref_index = pdf_classic_xref_index(xrefs)
let mut position = 0
while position < data.length() {
if pdf_reader_reconstruction_indirect_header_at(data, position) {
cursor.seek_absolute(position)
let parsed : Result[(PdfClassicXRefEntry?, Int?), Error] = try
read_xref_at_current(cursor, position, xrefs, xref_index)
catch {
err => Err(err)
} noraise {
value => Ok(value)
}
match parsed {
Ok((xref, next_position)) => {
match xref {
Some(xref) => {
xrefs.push(xref)
if !xref_index.entries.contains(xref.object_number) {
xref_index.entries[xref.object_number] = xref
}
}
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) => position = next
// Defensive: the candidate matched an indirect-object header
// above, so the skip scanner accepts the same position.
None => raise XRefEntryExpected
}
}
} else {
position += 1
}
}
xrefs
}
///|
/// Coverage map used to avoid loading objects already owned by reconstructed xref sections.
pub(all) struct PdfReconstructedXRefCoverage {
sections : Array[(Int, @core.PdfNumberSet)]
}
///|
fn pdf_reader_reconstruction_trailer_root_numbers(
classic_sections : ArrayView[(Int, PdfXRefSection)],
xref_stream_sections : ArrayView[PdfXRefSection],
) -> @core.PdfNumberSet {
let roots = @core.pdf_number_set([])
for item in classic_sections {
match item.1.trailer.lookup_immediate(pdf_reader_root_name()) {
Some(PdfIndirect(root)) => roots.add(root)
_ => ()
}
}
for section in xref_stream_sections {
match section.trailer.lookup_immediate(pdf_reader_root_name()) {
Some(PdfIndirect(root)) => roots.add(root)
_ => ()
}
}
roots
}
///|
/// Build coverage for reconstructed classic and xref-stream sections.
///
/// The caller supplies `classic_entry_is_verified` because validating a classic
/// entry requires the higher-level indirect-object parser owned by the root
/// reader package.
pub fn pdf_reader_reconstruction_xref_coverage(
data : BytesView,
classic_sections : ArrayView[(Int, PdfXRefSection)],
xref_stream_sections : ArrayView[PdfXRefSection],
classic_entry_is_verified : (
BytesView,
ArrayView[PdfClassicXRefEntry],
PdfClassicXRefIndex,
PdfClassicXRefEntry,
) -> Bool,
) -> PdfReconstructedXRefCoverage {
let covered : Array[(Int, @core.PdfNumberSet)] = Array(
capacity=classic_sections.length() + xref_stream_sections.length(),
)
let trailer_roots = pdf_reader_reconstruction_trailer_root_numbers(
classic_sections, xref_stream_sections,
)
for item in classic_sections {
let (offset, section) = item
let numbers = @core.pdf_number_set([])
let xref_index = pdf_classic_xref_index(section.entries)
for xref in section.entries {
if xref.object_number > 0 &&
(xref.in_use || !trailer_roots.contains(xref.object_number)) &&
classic_entry_is_verified(data, section.entries, xref_index, 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 = @core.pdf_number_set([])
for xref in section.entries {
if xref.object_number > 0 &&
(xref.in_use || !trailer_roots.contains(xref.object_number)) {
numbers.add(xref.object_number)
}
}
covered.push((offset, numbers))
}
None => ()
}
}
{ sections: covered, }
}
///|
/// Return whether an object offset is covered by a reconstructed xref section.
pub 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
}
}