///|
fn pdf_squeeze_name(text : String) -> PdfName {
pdf_name_of_bytes(@ascii.encode(text))
}
///|
let pdf_squeeze_type_key_name : PdfName = pdf_squeeze_name("/Type")
///|
fn pdf_squeeze_type_key() -> PdfName {
pdf_squeeze_type_key_name
}
///|
let pdf_squeeze_metadata_name_value : PdfName = pdf_squeeze_name("/Metadata")
///|
fn pdf_squeeze_metadata_name() -> PdfName {
pdf_squeeze_metadata_name_value
}
///|
let pdf_squeeze_page_name_value : PdfName = pdf_squeeze_name("/Page")
///|
fn pdf_squeeze_page_name() -> PdfName {
pdf_squeeze_page_name_value
}
///|
let pdf_squeeze_subtype_key_name : PdfName = pdf_squeeze_name("/Subtype")
///|
fn pdf_squeeze_subtype_key() -> PdfName {
pdf_squeeze_subtype_key_name
}
///|
fn pdf_squeeze_old_filter(name : PdfName) -> Bool {
name == pdf_filter_ascii_hex_decode_name ||
name == pdf_filter_ascii_hex_short_name ||
name == pdf_filter_ascii85_decode_name ||
name == pdf_filter_ascii85_short_name ||
name == pdf_filter_lzw_decode_name ||
name == pdf_filter_lzw_short_name ||
name == pdf_filter_run_length_decode_name ||
name == pdf_filter_run_length_short_name
}
///|
fn pdf_squeeze_annotation_subtype(name : PdfName) -> Bool {
name == pdf_squeeze_name("/Text") ||
name == pdf_squeeze_name("/Link") ||
name == pdf_squeeze_name("/FreeText") ||
name == pdf_squeeze_name("/Line") ||
name == pdf_squeeze_name("/Square") ||
name == pdf_squeeze_name("/Circle") ||
name == pdf_squeeze_name("/Polygon") ||
name == pdf_squeeze_name("/PolyLine") ||
name == pdf_squeeze_name("/Highlight") ||
name == pdf_squeeze_name("/Underline") ||
name == pdf_squeeze_name("/Squiggly") ||
name == pdf_squeeze_name("/StrikeOut") ||
name == pdf_squeeze_name("/Caret") ||
name == pdf_squeeze_name("/Stamp") ||
name == pdf_squeeze_name("/Ink") ||
name == pdf_squeeze_name("/Popup") ||
name == pdf_squeeze_name("/FileAttachment") ||
name == pdf_squeeze_name("/Sound") ||
name == pdf_squeeze_name("/Movie") ||
name == pdf_squeeze_name("/Screen") ||
name == pdf_squeeze_name("/Widget") ||
name == pdf_squeeze_name("/PrinterMark") ||
name == pdf_squeeze_name("/TrapNet") ||
name == pdf_squeeze_name("/3D") ||
name == pdf_squeeze_name("/Redact") ||
name == pdf_squeeze_name("/Projection") ||
name == pdf_squeeze_name("/RichMedia")
}
///|
priv struct PdfSqueezeDuplicateCandidate {
number : Int
object : PdfObject
}
///|
fn pdf_squeeze_materialized_object(
object : PdfObject,
) -> PdfObject raise PdfError {
match object {
PdfStreamObject(stream) =>
PdfStreamObject({
dictionary: stream.dictionary,
data: StreamGot(pdf_stream_data_bytes(stream.data)),
})
_ => object
}
}
///|
fn pdf_squeeze_duplicate_candidate(
number : Int,
object : PdfObject,
) -> PdfSqueezeDuplicateCandidate raise PdfError {
{ number, object: pdf_squeeze_materialized_object(object) }
}
///|
fn PdfDocument::pdf_squeeze_skip_duplicate_candidate(
self : PdfDocument,
object : PdfObject,
) -> Bool {
match self.lookup_direct(pdf_squeeze_type_key(), object) {
Some(PdfNameObject(name)) if name == pdf_squeeze_page_name() => true
_ =>
match self.lookup_direct(pdf_squeeze_subtype_key(), object) {
Some(PdfNameObject(name)) => pdf_squeeze_annotation_subtype(name)
_ => false
}
}
}
///|
fn PdfDocument::pdf_squeeze_duplicate_object_changes(
self : PdfDocument,
) -> Array[(Int, Int)] raise PdfError {
let entries = self.parsed_object_entries()
let unique : Array[PdfSqueezeDuplicateCandidate] = Array::new(
capacity=entries.length(),
)
let changes : Array[(Int, Int)] = Array::new(capacity=entries.length())
for entry in entries {
let (number, _, object) = entry
let candidate = pdf_squeeze_duplicate_candidate(number, object)
if !self.pdf_squeeze_skip_duplicate_candidate(candidate.object) {
let duplicate = for existing in unique {
if existing.object == candidate.object {
changes.push((candidate.number, existing.number))
break true
}
} nobreak {
false
}
if !duplicate {
unique.push(candidate)
}
}
}
changes
}
///|
fn PdfDocument::pdf_squeeze_apply_duplicate_changes(
self : PdfDocument,
changes : ArrayView[(Int, Int)],
) -> Unit {
if changes.length() > 0 {
let change_map = pdf_page_reference_map(changes)
self.pdf_page_renumber_references(changes)
self.set_trailer_dict(
pdf_page_renumber_object_with_map(self.trailer_dict(), change_map),
)
for change in changes {
let (duplicate_number, _) = change
self.remove_object(duplicate_number)
}
}
}
///|
fn PdfDocument::pdf_squeeze_prune_duplicate_objects_once(
self : PdfDocument,
) -> Unit raise PdfError {
self.pdf_squeeze_apply_duplicate_changes(
self.pdf_squeeze_duplicate_object_changes(),
)
}
///|
fn pdf_squeeze_stream_dictionary(
dictionary : PdfObject,
length : Int,
) -> PdfObject raise PdfError {
dictionary
.remove_dict_entry(pdf_filter_key())
.remove_dict_entry(pdf_short_filter_key())
.remove_dict_entry(pdf_decodeparms_key())
.remove_dict_entry(pdf_short_decodeparms_key())
.add_dict_entry(pdf_page_length_key_name, PdfInteger(length))
}
///|
fn PdfDocument::pdf_squeeze_content_stream_numbers(
self : PdfDocument,
page_number : Int,
) -> Array[Int] {
let page = self.lookup_object_or_null(page_number)
let contents = self.pdf_page_content_list(self.pdf_page_content_object(page))
let numbers : Array[Int] = Array::new(capacity=contents.length())
for content in contents {
match content {
PdfIndirect(number) => numbers.push(number)
_ => ()
}
}
numbers
}
///|
fn PdfDocument::pdf_squeeze_all_page_content_numbers(
self : PdfDocument,
page_numbers : ArrayView[Int],
) -> Array[Int] {
let numbers : Array[Int] = Array::new(capacity=page_numbers.length())
for page_number in page_numbers {
for number in self.pdf_squeeze_content_stream_numbers(page_number) {
numbers.push(number)
}
}
numbers
}
///|
fn pdf_squeeze_occurrences(values : ArrayView[Int], needle : Int) -> Int {
let mut count = 0
for value in values {
if value == needle {
count += 1
}
}
count
}
///|
fn pdf_squeeze_content_streams_are_unshared(
all_content_numbers : ArrayView[Int],
contents : ArrayView[PdfObject],
) -> Bool {
for content in contents {
match content {
PdfIndirect(number) =>
if pdf_squeeze_occurrences(all_content_numbers, number) > 1 {
break false
}
_ => ()
}
} nobreak {
true
}
}
///|
fn PdfDocument::pdf_squeeze_replace_page_contents(
self : PdfDocument,
page_number : Int,
page_object : PdfObject,
resources : PdfObject,
contents : ArrayView[PdfObject],
) -> Unit raise PdfError {
if contents.length() > 0 {
let ops = self.parse_content_ops_with_resources(resources, contents)
let stream_number = self.add_object(pdf_content_stream_of_ops(ops))
self.add_object_given_number(
page_number,
page_object.add_dict_entry(
pdf_page_contents_key(),
PdfIndirect(stream_number),
),
)
}
}
///|
fn PdfDocument::pdf_squeeze_form_xobject(
self : PdfDocument,
object_number : Int,
done : @hashmap.HashMap[Int, Bool],
) -> Unit raise PdfError {
if !done.contains(object_number) {
done[object_number] = true
let object = self.lookup_object_or_null(object_number)
let resources = match self.lookup_direct(pdf_page_resources_key(), object) {
Some(resources) => resources
None => PdfDictionary([])
}
self.pdf_squeeze_form_xobjects_in_resources(resources, done)
match self.lookup_direct(pdf_page_subtype_key(), object) {
Some(PdfNameObject(name)) if name == pdf_page_type_form() =>
match object {
PdfStreamObject(original_stream) => {
let ops = self.parse_content_ops_with_resources(resources, [object])
let rewritten = pdf_content_stream_of_ops(ops)
let data = rewritten.stream_bytes()
self.add_object_given_number(
object_number,
PdfStreamObject({
dictionary: pdf_squeeze_stream_dictionary(
original_stream.dictionary,
data.length(),
),
data: StreamGot(data),
}),
)
}
_ => raise ParseStreamExpected
}
_ => ()
}
}
}
///|
fn PdfDocument::pdf_squeeze_form_xobjects_in_resources(
self : PdfDocument,
resources : PdfObject,
done : @hashmap.HashMap[Int, Bool],
) -> Unit raise PdfError {
match self.lookup_direct(pdf_page_xobject_key(), resources) {
Some(PdfDictionary(entries)) =>
for entry in entries {
let (_, value) = entry
match value {
PdfIndirect(number) => self.pdf_squeeze_form_xobject(number, done)
_ => raise DictionaryExpected
}
}
_ => ()
}
}
///|
fn PdfDocument::pdf_squeeze_page_data(
self : PdfDocument,
) -> Unit raise PdfError {
let page_reference_numbers = self.page_reference_numbers()
let all_content_numbers = self.pdf_squeeze_all_page_content_numbers(
page_reference_numbers,
)
let page_numbers = self.select_object_numbers(fn(object) {
match self.lookup_direct(pdf_page_type_key(), object) {
Some(PdfNameObject(name)) => name == pdf_page_type_page()
_ => false
}
})
let xobjects_done : @hashmap.HashMap[Int, Bool] = HashMap([])
for page_number in page_numbers {
let page = self.lookup_object_or_null(page_number)
let resources = match self.lookup_direct(pdf_page_resources_key(), page) {
Some(resources) => resources
None => PdfDictionary([])
}
let contents = self.pdf_page_content_list(
self.pdf_page_content_object(page),
)
if pdf_squeeze_content_streams_are_unshared(all_content_numbers, contents) {
self.pdf_squeeze_replace_page_contents(
page_number, page, resources, contents,
)
}
self.pdf_squeeze_form_xobjects_in_resources(resources, xobjects_done)
}
}
///|
fn PdfDocument::pdf_squeeze_should_recompress_stream(
self : PdfDocument,
stream : PdfObject,
) -> Bool {
match self.lookup_direct(pdf_squeeze_type_key(), stream) {
Some(PdfNameObject(name)) if name == pdf_squeeze_metadata_name() => false
_ =>
match self.lookup_direct(pdf_filter_key(), stream) {
None =>
match self.lookup_direct(pdf_short_filter_key(), stream) {
None => true
Some(PdfNameObject(name)) => pdf_squeeze_old_filter(name)
Some(PdfArray([])) => true
Some(PdfArray([PdfNameObject(name), ..])) =>
pdf_squeeze_old_filter(name)
_ => false
}
Some(PdfNameObject(name)) => pdf_squeeze_old_filter(name)
Some(PdfArray([])) => true
Some(PdfArray([PdfNameObject(name), ..])) =>
pdf_squeeze_old_filter(name)
_ => false
}
}
}
///|
fn PdfDocument::pdf_squeeze_recompress_stream(
self : PdfDocument,
stream : PdfObject,
) -> PdfObject raise PdfError {
if !self.pdf_squeeze_should_recompress_stream(stream) {
stream
} else {
try self.pdf_decode_stream_until_unknown(stream) catch {
_ => stream
} noraise {
decoded =>
pdf_encode_stream_with_filter(
pdf_filter_flate_decode_name,
decoded,
only_if_smaller=true,
)
}
}
}
///|
fn PdfDocument::pdf_squeeze_update_stream_objects(
self : PdfDocument,
transform : (PdfObject) -> PdfObject raise PdfError,
) -> Unit raise PdfError {
for number in self.objects.object_numbers() {
let entry = self.objects.get_entry(number).unwrap()
let replacement = match entry.data {
ObjectParsed(PdfStreamObject(_) as stream) => {
let updated = transform(stream)
if updated == stream {
None
} else {
Some(ObjectParsed(updated))
}
}
ObjectParsedAlreadyDecrypted(PdfStreamObject(_) as stream) => {
let updated = transform(stream)
if updated == stream {
None
} else {
Some(ObjectParsedAlreadyDecrypted(updated))
}
}
_ => None
}
match replacement {
Some(data) => {
self.objects.set_entry(number, { data, generation: entry.generation })
self.objects.log.push((number, ObjectAltered))
}
None => ()
}
}
}
///|
/// Compress uncompressed streams, and streams using older lossless filters, with
/// `/FlateDecode`.
///
/// Metadata streams and encrypted documents are left unchanged. The source
/// document is copied before mutation, matching the rest of the high-level
/// pdflite editing APIs.
pub fn PdfDocument::recompress(
self : PdfDocument,
) -> PdfDocument raise PdfError {
let document = self.copy()
if !document.is_encrypted() {
document.pdf_squeeze_update_stream_objects(stream => {
document.pdf_squeeze_recompress_stream(stream)
})
}
document
}
///|
/// Compatibility wrapper for `PdfDocument::recompress`.
pub fn recompress_pdf(document : PdfDocument) -> PdfDocument raise PdfError {
document.recompress()
}
///|
/// `pdf_`-prefixed compatibility alias for cpdfsqueeze's `recompress_pdf`.
pub fn pdf_recompress_pdf(document : PdfDocument) -> PdfDocument raise PdfError {
recompress_pdf(document)
}
///|
/// Decode supported filter stages from every parsed stream in the document.
///
/// Encrypted documents are left unchanged. Unsupported filters are preserved
/// with their remaining encoded bytes, matching `pdf_decode_stream_until_unknown`.
pub fn PdfDocument::decompress(
self : PdfDocument,
) -> PdfDocument raise PdfError {
let document = self.copy()
if !document.is_encrypted() {
document.pdf_decode_streams_until_unknown()
}
document
}
///|
/// Compatibility wrapper for `PdfDocument::decompress`.
pub fn decompress_pdf(document : PdfDocument) -> PdfDocument raise PdfError {
document.decompress()
}
///|
/// `pdf_`-prefixed compatibility alias for cpdfsqueeze's `decompress_pdf`.
pub fn pdf_decompress_pdf(document : PdfDocument) -> PdfDocument raise PdfError {
decompress_pdf(document)
}
///|
/// Remove duplicate non-page, non-annotation objects, rewrite references to the
/// first matching object, and recompress eligible streams.
///
/// When `page_data` is true, page content streams and referenced Form XObjects
/// are parsed and serialized into normalized uncompressed streams when doing so
/// will not rewrite shared page content streams.
pub fn PdfDocument::squeeze(
self : PdfDocument,
page_data? : Bool = true,
) -> PdfDocument raise PdfError {
let document = self.copy()
let mut previous_count = -1
while previous_count != document.object_count() {
previous_count = document.object_count()
document.pdf_squeeze_prune_duplicate_objects_once()
}
if page_data {
document.pdf_squeeze_page_data()
}
document.recompress()
}
///|
/// Compatibility wrapper for `PdfDocument::squeeze`.
pub fn squeeze(
document : PdfDocument,
page_data? : Bool = true,
) -> PdfDocument raise PdfError {
document.squeeze(page_data~)
}
///|
/// `pdf_`-prefixed compatibility alias for cpdfsqueeze's `squeeze`.
pub fn pdf_squeeze(
document : PdfDocument,
page_data? : Bool = true,
) -> PdfDocument raise PdfError {
squeeze(document, page_data~)
}