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: u32,
58) -> Result<ObjRef, Error> {
59 let pages_node = init_dest(dest)?;
60 let page = Dict::from_pairs([
61 (names::TYPE.clone(), Object::Name(names::PAGE.clone())),
62 (
63 names::PARENT.clone(),
64 Object::Ref(ObjRef::new(pages_node, 0)),
65 ),
66 (
67 names::MEDIA_BOX.clone(),
68 rect_object([0.0, 0.0, width, height]),
69 ),
70 ]);
71 let created = dest.add(Object::Dict(page));
72 insert_into_tree(dest, pages_node, &[created], at);
73 Ok(created)
74}
75
76pub fn delete_pages(dest: &mut EditDoc<'_>, range: &PageRange) -> Result<(), Error> {
87 let mut refs = Vec::with_capacity(range.indices().len());
88 for &index in range.indices() {
89 let page = dest
90 .base()
91 .page(index)
92 .map_err(|_| Error::PageIndexOutOfRange(index))?;
93 let Some(reference) = page.reference else {
94 return Err(Error::PageIndexOutOfRange(index));
97 };
98 if !refs.contains(&reference) {
99 refs.push(reference);
100 }
101 }
102 for reference in refs {
103 unlink(dest, reference);
104 }
105 Ok(())
106}
107
108fn unlink(dest: &mut EditDoc<'_>, page: ObjRef) {
110 let Some(page_dict) = dict_of(dest, page) else {
111 return;
112 };
113 let Some(Object::Ref(parent_ref)) = page_dict.raw(names::PARENT).cloned() else {
114 return;
115 };
116 let Some(mut parent) = dict_of(dest, parent_ref) else {
117 return;
118 };
119 let kids: Array = parent
120 .raw(names::KIDS)
121 .and_then(Object::as_array)
122 .map(|kids| {
123 kids.iter()
124 .filter(|kid| !matches!(kid, Object::Ref(r) if *r == page))
125 .cloned()
126 .collect()
127 })
128 .unwrap_or_default();
129 parent.insert(names::KIDS.clone(), Object::Array(kids));
130 dest.replace(parent_ref, Object::Dict(parent));
131 decrement_counts(dest, parent_ref);
132}
133
134fn decrement_counts(dest: &mut EditDoc<'_>, mut node_ref: ObjRef) {
136 for _ in 0..64 {
138 let Some(mut node) = dict_of(dest, node_ref) else {
139 return;
140 };
141 let count = node
142 .raw(names::COUNT)
143 .and_then(Object::as_int)
144 .unwrap_or(0)
145 .saturating_sub(1)
146 .max(0);
147 node.insert(names::COUNT.clone(), Object::Int(count));
148 let parent = node.raw(names::PARENT).cloned();
149 dest.replace(node_ref, Object::Dict(node));
150 match parent {
151 Some(Object::Ref(parent_ref)) => node_ref = parent_ref,
152 _ => return,
153 }
154 }
155}
156
157pub fn set_page_rotation(
165 dest: &mut EditDoc<'_>,
166 index: PageIndex,
167 degrees: i32,
168) -> Result<(), Error> {
169 let quarter_turns =
172 (degrees.div_euclid(90) + i32::from(degrees.rem_euclid(90) >= 45)).rem_euclid(4);
173 let quarter_turns = i64::from(quarter_turns);
174 edit_page(dest, index, |page| {
175 page.insert(names::ROTATE.clone(), Object::Int(quarter_turns * 90));
176 })
177}
178
179pub fn set_page_box(
185 dest: &mut EditDoc<'_>,
186 index: PageIndex,
187 which: PageBox,
188 rect: [f32; 4],
189) -> Result<(), Error> {
190 edit_page(dest, index, |page| {
191 page.insert(which.key().clone(), rect_object(rect));
192 })
193}
194
195fn edit_page(
197 dest: &mut EditDoc<'_>,
198 index: PageIndex,
199 change: impl FnOnce(&mut Dict),
200) -> Result<(), Error> {
201 let page = dest
202 .base()
203 .page(index)
204 .map_err(|_| Error::PageIndexOutOfRange(index))?;
205 let Some(reference) = page.reference else {
206 return Err(Error::PageIndexOutOfRange(index));
207 };
208 let mut dict = dict_of(dest, reference).unwrap_or(page.dict);
209 change(&mut dict);
210 dest.replace(reference, Object::Dict(dict));
211 Ok(())
212}
213
214fn dict_of(dest: &EditDoc<'_>, reference: ObjRef) -> Option<Dict> {
216 dest.fetch(reference)
217 .ok()
218 .and_then(|object| object.as_dict().cloned())
219}
220
221fn rect_object(rect: [f32; 4]) -> Object {
222 Object::Array(rect.into_iter().map(Object::Real).collect())
223}