///|
fn PdfDocument::pdf_page_object_numbers_in_map(
self : PdfDocument,
) -> Array[Int] {
let object_numbers = self.object_numbers()
let page_numbers : Array[Int] = Array(capacity=object_numbers.length())
for number in object_numbers {
let object = self.lookup_object_or_null(number)
match object.lookup_immediate(pdf_page_type_key()) {
Some(PdfNameObject(name)) if name == pdf_page_type_page() =>
page_numbers.push(number)
_ => ()
}
}
page_numbers
}
///|
fn pdf_page_nullify_references(
object : @syntax.PdfObject,
deleted_pages : @core.PdfNumberSet,
) -> @syntax.PdfObject {
match object {
PdfIndirect(number) if deleted_pages.contains(number) => PdfNull
PdfArray(items) => {
let output : Array[@syntax.PdfObject] = Array(capacity=items.length())
for item in items {
output.push(pdf_page_nullify_references(item, deleted_pages))
}
PdfArray(output)
}
PdfDictionary(entries) => {
let output : Array[(@core.PdfName, @syntax.PdfObject)] = Array(
capacity=entries.length(),
)
for entry in entries {
let (key, value) = entry
output.push((key, pdf_page_nullify_references(value, deleted_pages)))
}
PdfDictionary(output)
}
PdfStreamObject(stream) =>
PdfStreamObject({
dictionary: pdf_page_nullify_references(
stream.dictionary,
deleted_pages,
),
data: stream.data,
})
_ => object
}
}
///|
fn pdf_page_nullify_entry(
entry : PdfObjectEntry,
deleted_pages : @core.PdfNumberSet,
) -> PdfObjectEntry {
match entry.data {
ObjectParsed(object) =>
{
data: ObjectParsed(pdf_page_nullify_references(object, deleted_pages)),
generation: entry.generation,
}
ObjectParsedAlreadyDecrypted(object) =>
{
data: ObjectParsedAlreadyDecrypted(
pdf_page_nullify_references(object, deleted_pages),
),
generation: entry.generation,
}
_ => { data: entry.data, generation: entry.generation, }
}
}
///|
fn PdfDocument::pdf_page_nullify_deleted_page_references(
self : PdfDocument,
) -> Unit raise @core.PdfError {
let page_numbers = self.pdf_page_object_numbers_in_map()
let active_page_numbers = self.page_reference_numbers()
if page_numbers.length() != active_page_numbers.length() {
let deleted_pages = pdf_page_deleted_numbers(
page_numbers, active_page_numbers,
)
let deleted_page_set = @core.pdf_number_set(deleted_pages)
for number in self.object_numbers() {
let entry = self.objects.get_entry(number).unwrap()
self.objects.set_entry(
number,
pdf_page_nullify_entry(entry, deleted_page_set),
)
}
}
}
///|
fn PdfDocument::pdf_referenced_from_object(
self : PdfDocument,
found : @hashmap.HashMap[Int, Bool],
object : @syntax.PdfObject,
) -> Unit {
match object {
PdfIndirect(number) =>
if number > 0 && !found.contains(number) {
found[number] = true
let resolved = self.lookup_object_or_null(number)
if resolved != PdfNull {
self.pdf_referenced_from_object(found, resolved)
}
}
PdfArray(items) =>
for item in items {
self.pdf_referenced_from_object(found, item)
}
PdfDictionary(entries) =>
for entry in entries {
let (_, value) = entry
self.pdf_referenced_from_object(found, value)
}
PdfStreamObject(stream) =>
self.pdf_referenced_from_object(found, stream.dictionary)
_ => ()
}
}
///|
/// Remove objects that are no longer reachable from the trailer or root.
///
/// References to deleted pages are first nulled from destinations and actions.
/// The document is mutated in place.
pub fn PdfDocument::remove_unreferenced(
self : PdfDocument,
) -> Unit raise @core.PdfError {
self.pdf_page_nullify_deleted_page_references()
let found : @hashmap.HashMap[Int, Bool] = HashMap([])
if self.trailer_dict().lookup_immediate(pdf_page_root_key()) is None &&
self.root() > 0 {
found[self.root()] = true
self.pdf_referenced_from_object(
found,
self.lookup_object_or_null(self.root()),
)
}
self.pdf_referenced_from_object(found, self.trailer_dict())
let object_numbers = self.object_numbers()
let to_remove : Array[Int] = Array(capacity=object_numbers.length())
for number in object_numbers {
if !found.contains(number) {
to_remove.push(number)
}
}
for number in to_remove {
self.remove_object(number)
}
}