1use pdfrum_common::PageIndex;
10use pdfrum_object::{Array, Dict, Name, ObjRef, Object, Resolve, names};
11
12use crate::doc::EditDoc;
13use crate::error::Error;
14use crate::import::{PageRange, init_dest, insert_into_tree};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum PageBox {
19 Media,
21 Crop,
23 Bleed,
25 Trim,
27 Art,
29}
30
31impl PageBox {
32 fn key(self) -> &'static Name {
33 match self {
34 Self::Media => names::MEDIA_BOX,
35 Self::Crop => names::CROP_BOX,
36 Self::Bleed => names::BLEED_BOX,
37 Self::Trim => names::TRIM_BOX,
38 Self::Art => names::ART_BOX,
39 }
40 }
41}
42
43pub fn add_blank_page(
54 dest: &mut EditDoc<'_>,
55 width: f32,
56 height: f32,
57 at: impl Into<PageIndex>,
58) -> Result<ObjRef, Error> {
59 let at = u32::from(at.into());
60 let pages_node = init_dest(dest)?;
61 let page = Dict::from_pairs([
62 (names::TYPE.clone(), Object::Name(names::PAGE.clone())),
63 (
64 names::PARENT.clone(),
65 Object::Ref(ObjRef::new(pages_node, 0)),
66 ),
67 (
68 names::MEDIA_BOX.clone(),
69 rect_object([0.0, 0.0, width, height]),
70 ),
71 ]);
72 let created = dest.add(Object::Dict(page));
73 insert_into_tree(dest, pages_node, &[created], at);
74 Ok(created)
75}
76
77pub fn delete_pages(dest: &mut EditDoc<'_>, range: &PageRange) -> Result<(), Error> {
88 let mut refs = Vec::with_capacity(range.indices().len());
89 for &index in range.indices() {
90 let page = dest
91 .base()
92 .page(index)
93 .map_err(|_| Error::PageIndexOutOfRange(index))?;
94 let Some(reference) = page.reference else {
95 return Err(Error::PageIndexOutOfRange(index));
98 };
99 if !refs.contains(&reference) {
100 refs.push(reference);
101 }
102 }
103 for reference in refs {
104 unlink(dest, reference);
105 }
106 Ok(())
107}
108
109fn unlink(dest: &mut EditDoc<'_>, page: ObjRef) {
111 let Some(page_dict) = dict_of(dest, page) else {
112 return;
113 };
114 let Some(Object::Ref(parent_ref)) = page_dict.raw(names::PARENT).cloned() else {
115 return;
116 };
117 let Some(mut parent) = dict_of(dest, parent_ref) else {
118 return;
119 };
120 let kids: Array = parent
121 .raw(names::KIDS)
122 .and_then(Object::as_array)
123 .map(|kids| {
124 kids.iter()
125 .filter(|kid| !matches!(kid, Object::Ref(r) if *r == page))
126 .cloned()
127 .collect()
128 })
129 .unwrap_or_default();
130 parent.insert(names::KIDS.clone(), Object::Array(kids));
131 dest.replace(parent_ref, Object::Dict(parent));
132 decrement_counts(dest, parent_ref);
133}
134
135fn decrement_counts(dest: &mut EditDoc<'_>, mut node_ref: ObjRef) {
137 for _ in 0..64 {
139 let Some(mut node) = dict_of(dest, node_ref) else {
140 return;
141 };
142 let count = node
143 .raw(names::COUNT)
144 .and_then(Object::as_int)
145 .unwrap_or(0)
146 .saturating_sub(1)
147 .max(0);
148 node.insert(names::COUNT.clone(), Object::Int(count));
149 let parent = node.raw(names::PARENT).cloned();
150 dest.replace(node_ref, Object::Dict(node));
151 match parent {
152 Some(Object::Ref(parent_ref)) => node_ref = parent_ref,
153 _ => return,
154 }
155 }
156}
157
158pub fn set_page_rotation(
166 dest: &mut EditDoc<'_>,
167 index: impl Into<PageIndex>,
168 degrees: i32,
169) -> Result<(), Error> {
170 let index = index.into();
171 let quarter_turns =
174 (degrees.div_euclid(90) + i32::from(degrees.rem_euclid(90) >= 45)).rem_euclid(4);
175 let quarter_turns = i64::from(quarter_turns);
176 edit_page(dest, index, |page| {
177 page.insert(names::ROTATE.clone(), Object::Int(quarter_turns * 90));
178 })
179}
180
181pub fn set_page_rotation_to(
208 dest: &mut EditDoc<'_>,
209 index: impl Into<PageIndex>,
210 rotation: pdfrum_page::Rotation,
211) -> Result<(), Error> {
212 let index = index.into();
213 edit_page(dest, index, |page| {
214 page.insert(
215 names::ROTATE.clone(),
216 Object::Int(i64::from(rotation.degrees())),
217 );
218 })
219}
220
221pub fn set_page_box(
227 dest: &mut EditDoc<'_>,
228 index: impl Into<PageIndex>,
229 which: PageBox,
230 rect: [f32; 4],
231) -> Result<(), Error> {
232 let index = index.into();
233 edit_page(dest, index, |page| {
234 page.insert(which.key().clone(), rect_object(rect));
235 })
236}
237
238fn edit_page(
240 dest: &mut EditDoc<'_>,
241 index: PageIndex,
242 change: impl FnOnce(&mut Dict),
243) -> Result<(), Error> {
244 let page = dest
245 .base()
246 .page(index)
247 .map_err(|_| Error::PageIndexOutOfRange(index))?;
248 let Some(reference) = page.reference else {
249 return Err(Error::PageIndexOutOfRange(index));
250 };
251 let mut dict = dict_of(dest, reference).unwrap_or(page.dict);
252 change(&mut dict);
253 dest.replace(reference, Object::Dict(dict));
254 Ok(())
255}
256
257fn dict_of(dest: &EditDoc<'_>, reference: ObjRef) -> Option<Dict> {
259 dest.fetch(reference)
260 .ok()
261 .and_then(|object| object.as_dict().cloned())
262}
263
264fn rect_object(rect: [f32; 4]) -> Object {
265 Object::Array(rect.into_iter().map(Object::Real).collect())
266}
267
268pub fn reorder_pages(dest: &mut EditDoc<'_>, order: &[usize]) -> Result<(), Error> {
302 let count = usize::try_from(dest.base().page_count()).unwrap_or(usize::MAX);
303 if order.len() != count {
304 return Err(Error::PageIndexOutOfRange(PageIndex::from(
308 u32::try_from(order.len().min(count)).unwrap_or(0),
309 )));
310 }
311 let mut seen = vec![false; count];
312 for &index in order {
313 let slot = seen
314 .get_mut(index)
315 .ok_or(Error::PageIndexOutOfRange(PageIndex::from(
316 u32::try_from(index).unwrap_or(u32::MAX),
317 )))?;
318 if *slot {
319 return Err(Error::PageIndexOutOfRange(PageIndex::from(
320 u32::try_from(index).unwrap_or(u32::MAX),
321 )));
322 }
323 *slot = true;
324 }
325
326 let mut pages = Vec::with_capacity(count);
329 for &index in order {
330 let page_index = PageIndex::from(u32::try_from(index).unwrap_or(u32::MAX));
331 let page = dest
332 .base()
333 .page(page_index)
334 .map_err(|_| Error::PageIndexOutOfRange(page_index))?;
335 let reference = page
336 .reference
337 .ok_or(Error::PageIndexOutOfRange(page_index))?;
338 pages.push((reference, page.dict.clone()));
339 }
340
341 let Some(root_ref) = dest.base().trailer().reference(names::ROOT) else {
342 return Err(Error::NoDestinationCatalog);
343 };
344 let Some(catalog) = dict_of(dest, root_ref) else {
345 return Err(Error::NoDestinationCatalog);
346 };
347 let Some(Object::Ref(tree_ref)) = catalog.raw(names::PAGES).cloned() else {
348 return Err(Error::NoDestinationCatalog);
349 };
350
351 for (reference, mut dict) in pages {
352 for key in [
355 names::RESOURCES.clone(),
356 names::MEDIA_BOX.clone(),
357 names::CROP_BOX.clone(),
358 names::ROTATE.clone(),
359 ] {
360 if !dict.contains_key(&key)
361 && let Some(inherited) = inherited_of(dest, &dict, &key)
362 {
363 dict.insert(key, inherited);
364 }
365 }
366 dict.insert(names::PARENT.clone(), Object::Ref(tree_ref));
367 dest.replace(reference, Object::Dict(dict));
368 }
369
370 let kids: Array = order
371 .iter()
372 .filter_map(|&index| {
373 let page_index = PageIndex::from(u32::try_from(index).unwrap_or(u32::MAX));
374 dest.base()
375 .page(page_index)
376 .ok()
377 .and_then(|page| page.reference)
378 .map(Object::Ref)
379 })
380 .collect();
381
382 let mut tree = dict_of(dest, tree_ref).unwrap_or_default();
383 tree.insert(names::TYPE.clone(), Object::Name(names::PAGES.clone()));
384 tree.insert(
385 names::COUNT.clone(),
386 Object::Int(i64::try_from(order.len()).unwrap_or(0)),
387 );
388 tree.insert(names::KIDS.clone(), Object::Array(kids));
389 tree.remove(names::PARENT);
390 dest.replace(tree_ref, Object::Dict(tree));
391 Ok(())
392}
393
394fn inherited_of(dest: &EditDoc<'_>, dict: &Dict, key: &Name) -> Option<Object> {
396 let mut current = dict.clone();
397 for _ in 0..64 {
400 if let Some(value) = current.raw(key) {
401 return Some(value.clone());
402 }
403 let Some(Object::Ref(parent)) = current.raw(names::PARENT).cloned() else {
404 return None;
405 };
406 current = dict_of(dest, parent)?;
407 }
408 None
409}