///|
priv struct PdfUAStructTreeRoleMap {
entries : Array[(@core.PdfName, @core.PdfName)]
}
///|
priv struct PdfUAStructTreeTextNode {
name : @core.PdfName
page_ref : Int?
children : Array[PdfUAStructTreeTextNode]
}
///|
fn PdfUAStructTreeRoleMap::mapped(
self : PdfUAStructTreeRoleMap,
name : @core.PdfName,
) -> @core.PdfName? {
for entry in self.entries {
if entry.0 == name {
return Some(entry.1)
}
}
None
}
///|
fn PdfUAStructTreeRoleMap::rewrite_name(
self : PdfUAStructTreeRoleMap,
name : @core.PdfName,
) -> @core.PdfName {
let mut current = name
let mut remaining = self.entries.length() + 1
let mut keep_going = true
while keep_going && remaining > 0 {
match self.mapped(current) {
Some(next) => {
current = next
remaining -= 1
}
None => keep_going = false
}
}
current
}
///|
fn PdfDocument::pdf_ua_struct_tree_rolemap(
self : PdfDocument,
) -> PdfUAStructTreeRoleMap {
let entries : Array[(@core.PdfName, @core.PdfName)] = []
let catalog = self.catalog() catch { _ => return { entries, } }
match catalog.lookup_immediate(pdf_ua_struct_tree_root_key()) {
Some(struct_tree_root) =>
match self.lookup_direct(pdf_ua_rolemap_key(), struct_tree_root) {
Some(PdfDictionary(role_entries)) =>
for entry in role_entries {
match entry.1 {
PdfNameObject(mapped) => entries.push((entry.0, mapped))
_ => ()
}
}
_ => ()
}
None => ()
}
{ entries, }
}
///|
fn PdfDocument::pdf_ua_struct_tree_page_ref(
self : PdfDocument,
node : @syntax.PdfObject,
) -> Int? {
match self.direct(node).lookup_immediate(pdf_ua_pg_key()) {
Some(PdfIndirect(number)) => Some(number)
_ => None
}
}
///|
fn PdfDocument::pdf_ua_read_struct_tree_text_inner(
self : PdfDocument,
node : @syntax.PdfObject,
rolemap : PdfUAStructTreeRoleMap,
depth : Int,
) -> PdfUAStructTreeTextNode raise @core.PdfError {
if depth > 10000 {
raise SoftError("malformed st node")
}
let name = match self.lookup_direct(pdf_ua_s_key(), node) {
Some(PdfNameObject(raw_name)) => rolemap.rewrite_name(raw_name)
_ => pdf_ua_empty_name()
}
let page_ref = self.pdf_ua_struct_tree_page_ref(node)
let children = match self.lookup_direct(pdf_ua_k_key(), node) {
None => []
Some(PdfDictionary(_) as child) =>
[self.pdf_ua_read_struct_tree_text_inner(child, rolemap, depth + 1)]
Some(PdfInteger(_)) => []
Some(PdfArray(items)) => {
let output : Array[PdfUAStructTreeTextNode] = []
for item in items {
output.push(
self.pdf_ua_read_struct_tree_text_inner(item, rolemap, depth + 1),
)
}
output
}
_ => raise SoftError("malformed st node")
}
{ name, page_ref, children, }
}
///|
fn PdfDocument::pdf_ua_read_struct_tree_text(
self : PdfDocument,
rolemap : PdfUAStructTreeRoleMap,
) -> PdfUAStructTreeTextNode raise @core.PdfError {
let catalog = self.catalog() catch {
_ => raise SoftError("struct_tree_text: no root")
}
let root_name = rolemap.rewrite_name(pdf_ua_struct_tree_root_key())
match catalog.lookup_immediate(pdf_ua_struct_tree_root_key()) {
None => { name: root_name, page_ref: None, children: [], }
Some(struct_tree_root) => {
let tree = self.pdf_ua_read_struct_tree_text_inner(
struct_tree_root, rolemap, 0,
)
{ name: root_name, page_ref: tree.page_ref, children: tree.children, }
}
}
}
///|
fn pdf_ua_struct_tree_text_node_is_empty_leaf(
node : PdfUAStructTreeTextNode,
) -> Bool {
node.name.byte_length() == 0 && node.children.length() == 0
}
///|
fn pdf_ua_remove_empty_struct_tree_text_nodes(
node : PdfUAStructTreeTextNode,
) -> PdfUAStructTreeTextNode {
let children : Array[PdfUAStructTreeTextNode] = []
for child in node.children {
let pruned = pdf_ua_remove_empty_struct_tree_text_nodes(child)
if !pdf_ua_struct_tree_text_node_is_empty_leaf(pruned) {
children.push(pruned)
}
}
{ name: node.name, page_ref: node.page_ref, children, }
}
///|
fn pdf_ua_struct_tree_text_name_length(
node : PdfUAStructTreeTextNode,
page_lookup : @hashmap.HashMap[Int, Int],
) -> Int {
let bytes = node.name.bytes()
let start = if bytes.length() > 0 { 1 } else { 0 }
let mut length = bytes.length() - start
match node.page_ref {
Some(page_ref) =>
match page_lookup.get(page_ref) {
Some(page_number) if page_number > 0 =>
length += pdf_ua_ascii_length(" (") +
pdf_ua_ascii_length(page_number.to_string()) +
pdf_ua_ascii_length(")")
_ => ()
}
None => ()
}
length
}
///|
fn pdf_ua_write_struct_tree_text_name(
output : Array[Byte],
position : Int,
node : PdfUAStructTreeTextNode,
page_lookup : @hashmap.HashMap[Int, Int],
) -> Int {
let bytes = node.name.bytes()
let start = if bytes.length() > 0 { 1 } else { 0 }
let mut current = position
for i in start..
match page_lookup.get(page_ref) {
Some(page_number) if page_number > 0 => {
current = pdf_ua_write_ascii(output, current, " (")
current = pdf_ua_write_ascii(output, current, page_number.to_string())
current = pdf_ua_write_ascii(output, current, ")")
}
_ => ()
}
None => ()
}
current
}
///|
fn pdf_ua_struct_tree_text_root_length(
node : PdfUAStructTreeTextNode,
indent : String,
page_lookup : @hashmap.HashMap[Int, Int],
) -> Int {
let mut length = pdf_ua_struct_tree_text_name_length(node, page_lookup) + 1
for index in 0.. Int {
let mut current = pdf_ua_write_struct_tree_text_name(
output, position, node, page_lookup,
)
output[current] = 10
current += 1
for index in 0.. @hashmap.HashMap[Int, Int] {
let lookup : @hashmap.HashMap[Int, Int] = HashMap([])
for index, page_number in page_numbers {
lookup[page_number] = index + 1
}
lookup
}
///|
/// Render cpdf's printed PDF/UA structure tree as UTF-8 bytes.
pub fn PdfDocument::struct_tree_text(
self : PdfDocument,
) -> @core.PdfBytes raise @core.PdfError {
let rolemap = self.pdf_ua_struct_tree_rolemap()
let tree = self.pdf_ua_read_struct_tree_text(rolemap)
if tree.name == pdf_ua_struct_tree_root_key() && tree.children.length() == 0 {
Bytes::new(0)
} else {
let pruned = pdf_ua_remove_empty_struct_tree_text_nodes(tree)
let page_lookup = pdf_ua_page_lookup(self.page_reference_numbers())
let output = Array::make(
pdf_ua_struct_tree_text_root_length(pruned, "", page_lookup),
b'\x00',
)
let _ = pdf_ua_write_struct_tree_text_root(
output, 0, pruned, "", page_lookup,
)
Bytes::from_array(output)
}
}