///|
fn pdf_ocg_representative_number(
representatives : ArrayView[(@core.PdfBytes, Int)],
name : @core.PdfBytes,
) -> Int? {
for representative in representatives {
if representative.0 == name {
break Some(representative.1)
}
} nobreak {
None
}
}
///|
fn pdf_ocg_changed_number(changes : ArrayView[(Int, Int)], number : Int) -> Int {
for change in changes {
if change.0 == number {
break change.1
}
} nobreak {
number
}
}
///|
fn pdf_ocg_push_unique_number(output : Array[Int], number : Int) -> Unit {
if !output.contains(number) {
output.push(number)
}
}
///|
fn pdf_ocg_coalesce_numbers(
changes : ArrayView[(Int, Int)],
numbers : ArrayView[Int],
) -> Array[Int] {
let output : Array[Int] = []
for number in numbers {
pdf_ocg_push_unique_number(output, pdf_ocg_changed_number(changes, number))
}
output
}
///|
fn pdf_ocg_coalesce_order(
changes : ArrayView[(Int, Int)],
order : ArrayView[(@core.PdfBytes?, Array[Int])],
) -> Array[(@core.PdfBytes?, Array[Int])] {
let output : Array[(@core.PdfBytes?, Array[Int])] = []
let seen : Array[Int] = []
for item in order {
let numbers : Array[Int] = []
for number in item.1 {
let changed = pdf_ocg_changed_number(changes, number)
if !seen.contains(changed) {
seen.push(changed)
numbers.push(changed)
}
}
if numbers.length() > 0 {
output.push((item.0, numbers))
}
}
output
}
///|
fn pdf_ocg_coalesce_appdict(
changes : ArrayView[(Int, Int)],
appdict : @ocg.PdfOcgAppDict,
) -> @ocg.PdfOcgAppDict {
{ ..appdict, ocgs: pdf_ocg_coalesce_numbers(changes, appdict.ocgs), }
}
///|
fn pdf_ocg_coalesce_config(
changes : ArrayView[(Int, Int)],
config : @ocg.PdfOcgConfig,
) -> @ocg.PdfOcgConfig {
{
..config,
on: pdf_ocg_coalesce_numbers(changes, config.on),
off: pdf_ocg_coalesce_numbers(changes, config.off),
usage_applications: [
for appdict in config.usage_applications => {
pdf_ocg_coalesce_appdict(changes, appdict)
}
],
order: match config.order {
Some(order) => Some(pdf_ocg_coalesce_order(changes, order))
None => None
},
radio_button_groups: match config.radio_button_groups {
Some(groups) =>
Some(
[
for group in groups => pdf_ocg_coalesce_numbers(changes, group)
],
)
None => None
},
locked: pdf_ocg_coalesce_numbers(changes, config.locked),
}
}
///|
fn pdf_ocg_coalesce_content(
content : @ocg.PdfOptionalContent,
) -> (@ocg.PdfOptionalContent, Array[(Int, Int)]) {
let representatives : Array[(@core.PdfBytes, Int)] = []
let ocgs : Array[(Int, @ocg.PdfOcg)] = []
let changes : Array[(Int, Int)] = []
for entry in content.ocgs {
let (number, ocg) = entry
match pdf_ocg_representative_number(representatives, ocg.name) {
Some(representative) =>
if representative != number {
changes.push((number, representative))
}
None => {
representatives.push((ocg.name, number))
ocgs.push((number, pdf_ocg_copy_ocg(ocg)))
}
}
}
(
{
ocgs,
default_config: pdf_ocg_coalesce_config(changes, content.default_config),
configs: [
for config in content.configs => {
pdf_ocg_coalesce_config(changes, config)
}
],
},
changes,
)
}
///|
/// Return a copy with same-named optional-content groups coalesced.
///
/// The first group for each raw PDF name is kept. Duplicate group references in
/// optional-content metadata and parsed document objects are rewritten to the
/// kept object number, and duplicate OCG objects are removed.
pub fn PdfDocument::ocg_coalesce(
self : PdfDocument,
) -> PdfDocument raise @core.PdfError {
match self.read_optional_content_groups() {
None => self.copy()
Some(content) => {
let (coalesced, changes) = pdf_ocg_coalesce_content(content)
if changes.length() == 0 {
self.copy()
} else {
let pdf = self.write_optional_content_groups(coalesced)
pdf.pdf_page_renumber_references(changes)
for change in changes {
pdf.remove_object(change.0)
}
pdf
}
}
}
}
///|
/// Compatibility wrapper for `PdfDocument::ocg_coalesce`.
pub fn pdf_ocg_coalesce(
document : PdfDocument,
) -> PdfDocument raise @core.PdfError {
document.ocg_coalesce()
}