fop_render/pdf/document/
outline.rs1use super::types::{PdfOutline, PdfOutlineItem};
6
7pub(super) fn count_outline_objects(outline: &PdfOutline) -> usize {
9 1 + count_outline_items(&outline.items)
10}
11
12pub(super) fn count_outline_items(items: &[PdfOutlineItem]) -> usize {
14 let mut count = items.len();
15 for item in items {
16 count += count_outline_items(&item.children);
17 }
18 count
19}
20
21pub(super) fn write_outline_objects(
23 outline: &PdfOutline,
24 bytes: &mut Vec<u8>,
25 xref_offsets: &mut Vec<usize>,
26 first_obj_id: usize,
27 page_obj_ids: &[usize],
28) {
29 let mut next_obj_id = first_obj_id;
30
31 let outlines_root_id = next_obj_id;
33 next_obj_id += 1;
34
35 let total_count = count_outline_items(&outline.items);
36
37 xref_offsets.push(bytes.len());
39 bytes.extend_from_slice(format!("{} 0 obj\n", outlines_root_id).as_bytes());
40 bytes.extend_from_slice(b"<<\n");
41 bytes.extend_from_slice(b"/Type /Outlines\n");
42
43 if !outline.items.is_empty() {
44 let first_child_id = next_obj_id;
45 let last_child_id = next_obj_id + outline.items.len() - 1;
46 bytes.extend_from_slice(format!("/First {} 0 R\n", first_child_id).as_bytes());
47 bytes.extend_from_slice(format!("/Last {} 0 R\n", last_child_id).as_bytes());
48 }
49
50 bytes.extend_from_slice(format!("/Count {}\n", total_count).as_bytes());
51 bytes.extend_from_slice(b">>\n");
52 bytes.extend_from_slice(b"endobj\n");
53
54 if !outline.items.is_empty() {
56 write_outline_items(
57 &outline.items,
58 bytes,
59 xref_offsets,
60 &mut next_obj_id,
61 outlines_root_id,
62 page_obj_ids,
63 );
64 }
65}
66
67#[allow(clippy::too_many_arguments)]
69pub(super) fn write_outline_items(
70 items: &[PdfOutlineItem],
71 bytes: &mut Vec<u8>,
72 xref_offsets: &mut Vec<usize>,
73 next_obj_id: &mut usize,
74 parent_id: usize,
75 page_obj_ids: &[usize],
76) {
77 let start_obj_id = *next_obj_id;
78 let item_count = items.len();
79
80 let end_obj_id = start_obj_id + item_count;
82
83 for (idx, item) in items.iter().enumerate() {
84 let obj_id = start_obj_id + idx;
85 *next_obj_id = obj_id + 1;
86
87 xref_offsets.push(bytes.len());
88 bytes.extend_from_slice(format!("{} 0 obj\n", obj_id).as_bytes());
89 bytes.extend_from_slice(b"<<\n");
90
91 let escaped_title = escape_pdf_string(&item.title);
93 bytes.extend_from_slice(format!("/Title ({})\n", escaped_title).as_bytes());
94 bytes.extend_from_slice(format!("/Parent {} 0 R\n", parent_id).as_bytes());
95
96 if idx > 0 {
98 bytes.extend_from_slice(format!("/Prev {} 0 R\n", obj_id - 1).as_bytes());
99 }
100 if idx < item_count - 1 {
101 bytes.extend_from_slice(format!("/Next {} 0 R\n", obj_id + 1).as_bytes());
102 }
103
104 if let Some(page_idx) = item.page_index {
106 if page_idx < page_obj_ids.len() {
107 let page_obj_id = page_obj_ids[page_idx];
108 bytes.extend_from_slice(
109 format!("/Dest [{} 0 R /XYZ 0 792 0]\n", page_obj_id).as_bytes(),
110 );
111 }
112 }
113
114 if !item.children.is_empty() {
116 let first_child_id = end_obj_id + count_child_offset(items, idx);
117 let child_count = item.children.len();
118 let last_child_id = first_child_id + child_count - 1;
119
120 bytes.extend_from_slice(format!("/First {} 0 R\n", first_child_id).as_bytes());
121 bytes.extend_from_slice(format!("/Last {} 0 R\n", last_child_id).as_bytes());
122 bytes.extend_from_slice(format!("/Count {}\n", child_count).as_bytes());
123 }
124
125 bytes.extend_from_slice(b">>\n");
126 bytes.extend_from_slice(b"endobj\n");
127 }
128
129 *next_obj_id = end_obj_id;
131 for (idx, item) in items.iter().enumerate() {
132 if !item.children.is_empty() {
133 let parent = start_obj_id + idx;
134 write_outline_items(
135 &item.children,
136 bytes,
137 xref_offsets,
138 next_obj_id,
139 parent,
140 page_obj_ids,
141 );
142 }
143 }
144}
145
146fn count_child_offset(items: &[PdfOutlineItem], current_idx: usize) -> usize {
148 let mut offset = 0;
149 for item in items.iter().take(current_idx) {
150 offset += count_outline_items(&item.children);
151 }
152 offset
153}
154
155pub(super) fn escape_pdf_string(s: &str) -> String {
157 s.replace('\\', "\\\\")
158 .replace('(', "\\(")
159 .replace(')', "\\)")
160 .replace('\r', "\\r")
161 .replace('\n', "\\n")
162}