1mod copy;
71mod inherit;
72mod nup;
73mod range;
74mod viewer;
75
76use pdfrum_common::PageIndex;
77use pdfrum_object::{Array, Dict, Name, ObjRef, Object, Resolve, names};
78use pdfrum_parser::Document;
79
80use crate::doc::EditDoc;
81use crate::error::Error;
82use crate::names as edit_names;
83
84use copy::ObjectMap;
85use inherit::inheritable;
86use nup::{NupGrid, sub_page_fragment};
87
88pub use range::PageRange;
89
90const LETTER: [i64; 4] = [0, 0, 612, 792];
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
95pub struct ImportOptions {
96 pub at: PageIndex,
99 pub viewer_preferences: bool,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq)]
105pub struct NUpOptions {
106 pub sheet: (f32, f32),
108 pub grid: (u32, u32),
110}
111
112impl Default for NUpOptions {
113 fn default() -> Self {
114 Self {
115 sheet: (612.0, 792.0),
116 grid: (2, 1),
117 }
118 }
119}
120
121pub(crate) fn init_dest(dest: &mut EditDoc<'_>) -> Result<u32, Error> {
137 let catalog_ref = dest
138 .base()
139 .trailer()
140 .reference(names::ROOT)
141 .ok_or(Error::NoDestinationCatalog)?;
142 let catalog = dest
143 .fetch(catalog_ref)
144 .ok()
145 .and_then(|o| o.as_dict().cloned())
146 .ok_or(Error::NoDestinationCatalog)?;
147
148 let mut catalog = catalog;
149
150 if catalog
153 .name(names::TYPE)
154 .is_none_or(|n| n.as_bytes().is_empty())
155 {
156 set(
157 &mut catalog,
158 names::TYPE,
159 Object::Name(edit_names::CATALOG.clone()),
160 );
161 }
162
163 let pages_ref = match catalog.raw(names::PAGES) {
166 Some(Object::Ref(r)) if dest.fetch(*r).is_ok_and(|o| o.as_dict().is_some()) => *r,
167 _ => {
168 let fresh = fresh_pages_node(dest);
169 set(&mut catalog, names::PAGES, Object::Ref(fresh));
170 fresh
171 }
172 };
173
174 let mut pages = dest
175 .fetch(pages_ref)
176 .ok()
177 .and_then(|o| o.as_dict().cloned())
178 .unwrap_or_default();
179 if pages
180 .name(names::TYPE)
181 .is_none_or(|n| n.as_bytes().is_empty())
182 {
183 set(&mut pages, names::TYPE, Object::Name(names::PAGES.clone()));
184 }
185 if !matches!(pages.raw(names::KIDS), Some(Object::Array(_))) {
188 set(&mut pages, names::KIDS, Object::Array(Array::new()));
189 set(&mut pages, names::COUNT, Object::Int(0));
190 }
191
192 dest.replace(pages_ref, Object::Dict(pages));
193 dest.replace(catalog_ref, Object::Dict(catalog));
194 Ok(pages_ref.num)
195}
196
197fn fresh_pages_node(dest: &mut EditDoc<'_>) -> ObjRef {
199 dest.add(Object::Dict(Dict::from_pairs([
200 (names::TYPE.clone(), Object::Name(names::PAGES.clone())),
201 (names::COUNT.clone(), Object::Int(0)),
202 (names::KIDS.clone(), Object::Array(Array::new())),
203 ])))
204}
205
206pub fn import_pages(
221 dest: &mut EditDoc<'_>,
222 src: &Document,
223 pages: &PageRange,
224 opts: &ImportOptions,
225) -> Result<(), Error> {
226 for index in pages.indices() {
229 if index.get() >= src.page_count() {
230 return Err(Error::PageIndexOutOfRange(*index));
231 }
232 }
233
234 let pages_node = init_dest(dest)?;
235 let mut map = ObjectMap::new();
236 let mut created = Vec::new();
237
238 for index in pages.indices() {
239 let page = src
240 .page(*index)
241 .map_err(|_| Error::PageIndexOutOfRange(*index))?;
242 let dest_page = dest.add(Object::Null);
243
244 if let Some(source_ref) = page.reference {
248 map.record(source_ref.num, dest_page.num);
249 }
250
251 let built = build_page(dest, src, &page.dict, pages_node, &mut map, dest_page);
252 dest.replace(dest_page, Object::Dict(built));
253 created.push(dest_page);
254 }
255
256 insert_into_tree(dest, pages_node, &created, opts.at.get());
257
258 if opts.viewer_preferences
259 && let Ok(catalog) = src.catalog()
260 && let Some(prefs) = viewer::filtered(&catalog, src)
261 {
262 copy_viewer_preferences(dest, prefs);
263 }
264 Ok(())
265}
266
267fn build_page(
269 dest: &mut EditDoc<'_>,
270 src: &Document,
271 source: &Dict,
272 pages_node: u32,
273 map: &mut ObjectMap,
274 self_ref: ObjRef,
275) -> Dict {
276 let mut out = Dict::from_pairs([
277 (names::TYPE.clone(), Object::Name(names::PAGE.clone())),
278 (
279 names::PARENT.clone(),
280 Object::Ref(ObjRef::new(pages_node, 0)),
281 ),
282 ]);
283
284 for (key, value) in source.iter() {
287 if key == names::TYPE || key == names::PARENT {
288 continue;
289 }
290 let mut value = value.clone();
291 if rewrite_value(dest, src, &mut value, map, pages_node) {
292 out.push(key.clone(), value);
293 }
294 }
295
296 flatten_inherited(dest, src, source, &mut out, map, pages_node);
297 let _ = self_ref;
298 out
299}
300
301fn flatten_inherited(
303 dest: &mut EditDoc<'_>,
304 src: &Document,
305 source: &Dict,
306 out: &mut Dict,
307 map: &mut ObjectMap,
308 pages_node: u32,
309) {
310 if !copy_inherited(dest, src, source, out, names::MEDIA_BOX, map, pages_node)
313 && !copy_inherited(dest, src, source, out, names::CROP_BOX, map, pages_node)
314 {
315 set(
317 out,
318 names::MEDIA_BOX,
319 Object::Array(Array::of(LETTER.map(Object::Int))),
320 );
321 } else if !out.contains_key(names::MEDIA_BOX) {
322 if let Some(crop) = out.raw(names::CROP_BOX).cloned() {
324 set(out, names::MEDIA_BOX, crop);
325 }
326 }
327
328 if !copy_inherited(dest, src, source, out, names::RESOURCES, map, pages_node) {
330 set(out, names::RESOURCES, Object::Dict(Dict::new()));
331 }
332
333 copy_inherited(dest, src, source, out, names::CROP_BOX, map, pages_node);
337 copy_inherited(dest, src, source, out, names::ROTATE, map, pages_node);
338}
339
340fn copy_inherited(
343 dest: &mut EditDoc<'_>,
344 src: &Document,
345 source: &Dict,
346 out: &mut Dict,
347 key: &Name,
348 map: &mut ObjectMap,
349 pages_node: u32,
350) -> bool {
351 if out.contains_key(key) {
352 return true;
353 }
354 let Some(mut value) = inheritable(source, key, src) else {
355 return false;
356 };
357 if !rewrite_value(dest, src, &mut value, map, pages_node) {
358 return false;
359 }
360 out.push(key.clone(), value);
361 true
362}
363
364fn rewrite_value(
366 dest: &mut EditDoc<'_>,
367 src: &Document,
368 value: &mut Object,
369 map: &mut ObjectMap,
370 pages_node: u32,
371) -> bool {
372 copy::rewrite_in_place(dest, src, value, map, pages_node)
373}
374
375pub(crate) fn insert_into_tree(
377 dest: &mut EditDoc<'_>,
378 pages_node: u32,
379 created: &[ObjRef],
380 at: u32,
381) {
382 let node_ref = ObjRef::new(pages_node, 0);
383 let mut node = dest
384 .fetch(node_ref)
385 .ok()
386 .and_then(|o| o.as_dict().cloned())
387 .unwrap_or_default();
388
389 let existing: Vec<Object> = node
390 .raw(names::KIDS)
391 .and_then(Object::as_array)
392 .map(|a| a.iter().cloned().collect())
393 .unwrap_or_default();
394
395 let split = (at as usize).min(existing.len());
398 let mut kids = Array::new();
399 for value in existing.get(..split).unwrap_or_default() {
400 kids.push(value.clone());
401 }
402 for page in created {
403 kids.push(Object::Ref(*page));
404 }
405 for value in existing.get(split..).unwrap_or_default() {
406 kids.push(value.clone());
407 }
408
409 let count = i64::try_from(kids.len()).unwrap_or(i64::MAX);
410 set(&mut node, names::KIDS, Object::Array(kids));
411 set(&mut node, names::COUNT, Object::Int(count));
412 dest.replace(node_ref, Object::Dict(node));
413}
414
415fn copy_viewer_preferences(dest: &mut EditDoc<'_>, prefs: Dict) {
418 let Some(catalog_ref) = dest.base().trailer().reference(names::ROOT) else {
419 return;
420 };
421 let Some(mut catalog) = dest
422 .fetch(catalog_ref)
423 .ok()
424 .and_then(|o| o.as_dict().cloned())
425 else {
426 return;
427 };
428 set(&mut catalog, names::VIEWER_PREFERENCES, Object::Dict(prefs));
430 dest.replace(catalog_ref, Object::Dict(catalog));
431}
432
433pub fn n_page_to_one(
450 dest: &mut EditDoc<'_>,
451 src: &Document,
452 pages: &PageRange,
453 opts: &NUpOptions,
454) -> Result<(), Error> {
455 let (x, y) = opts.grid;
456 let (width, height) = opts.sheet;
457 if x == 0 || y == 0 || width <= 0.0 || height <= 0.0 {
458 return Err(Error::BadNupParams);
459 }
460 for index in pages.indices() {
461 if index.get() >= src.page_count() {
462 return Err(Error::PageIndexOutOfRange(*index));
463 }
464 }
465
466 let pages_node = init_dest(dest)?;
467 let grid = NupGrid {
468 sheet_width: width,
469 sheet_height: height,
470 x,
471 y,
472 };
473
474 let mut map = ObjectMap::new();
475 let mut forms: Vec<(u32, ObjRef)> = Vec::new();
478 let mut created = Vec::new();
479
480 for chunk in pages.indices().chunks(grid.per_sheet().max(1) as usize) {
481 let mut content = String::new();
482 let mut xobjects = Dict::new();
483
484 for (slot, index) in chunk.iter().enumerate() {
485 let page = src
486 .page(*index)
487 .map_err(|_| Error::PageIndexOutOfRange(*index))?;
488 let key = page.reference.map_or(u32::MAX - index.get(), |r| r.num);
489
490 let form = if let Some((_, existing)) = forms.iter().find(|(k, _)| *k == key) {
491 *existing
492 } else {
493 let made = make_form(dest, src, &page.dict, pages_node, &mut map);
494 forms.push((key, made));
495 made
496 };
497
498 let (page_w, page_h) = page_size(&page.dict, src);
499 #[expect(
500 clippy::cast_possible_truncation,
501 reason = "a slot index is bounded by the grid, which is a u32"
502 )]
503 let edit = grid.edit(slot as u32, page_w, page_h);
504 let name = format!("X{}", xobjects.len() + 1);
505 content.push_str(&sub_page_fragment(&name, edit));
506 xobjects.push(Name::from(name.as_str()), Object::Ref(form));
509 }
510
511 created.push(make_sheet(
512 dest, pages_node, &content, xobjects, width, height,
513 ));
514 }
515
516 insert_into_tree(dest, pages_node, &created, 0);
517 Ok(())
518}
519
520fn make_form(
522 dest: &mut EditDoc<'_>,
523 src: &Document,
524 page: &Dict,
525 pages_node: u32,
526 map: &mut ObjectMap,
527) -> ObjRef {
528 let content = assemble_content(page, src);
532
533 let mut resources = Dict::new();
534 let mut carrier = Dict::new();
535 if copy_inherited(
536 dest,
537 src,
538 page,
539 &mut carrier,
540 names::RESOURCES,
541 map,
542 pages_node,
543 ) && let Some(found) = carrier.raw(names::RESOURCES).and_then(Object::as_dict)
544 {
545 resources = found.clone();
546 }
547
548 let (media, crop) = boxes(page, src);
549 let bbox = intersect(media, crop);
550
551 let dict = Dict::from_pairs([
555 (names::RESOURCES.clone(), Object::Dict(resources)),
556 (
557 edit_names::BBOX.clone(),
558 Object::Array(Array::of(bbox.map(Object::Real))),
559 ),
560 (
561 edit_names::MATRIX.clone(),
562 Object::Array(Array::of(
563 [1.0, 0.0, 0.0, 1.0, -bbox[0], -bbox[1]].map(Object::Real),
564 )),
565 ),
566 (
567 names::TYPE.clone(),
568 Object::Name(edit_names::XOBJECT.clone()),
569 ),
570 (
571 names::SUBTYPE.clone(),
572 Object::Name(edit_names::FORM.clone()),
573 ),
574 (edit_names::FORM_TYPE.clone(), Object::Int(1)),
575 (
576 names::LENGTH.clone(),
577 Object::Int(i64::try_from(content.len()).unwrap_or(0)),
578 ),
579 ]);
580
581 dest.add(Object::Stream(Box::new(pdfrum_object::Stream::new(
582 dict,
583 pdfrum_object::ByteSpan::from(content),
584 ))))
585}
586
587fn make_sheet(
589 dest: &mut EditDoc<'_>,
590 pages_node: u32,
591 content: &str,
592 xobjects: Dict,
593 width: f32,
594 height: f32,
595) -> ObjRef {
596 let stream = dest.add(Object::Stream(Box::new(pdfrum_object::Stream::new(
597 Dict::from_pairs([(
598 names::LENGTH.clone(),
599 Object::Int(i64::try_from(content.len()).unwrap_or(0)),
600 )]),
601 pdfrum_object::ByteSpan::from(content.as_bytes().to_vec()),
602 ))));
603
604 dest.add(Object::Dict(Dict::from_pairs([
605 (names::TYPE.clone(), Object::Name(names::PAGE.clone())),
606 (
607 names::PARENT.clone(),
608 Object::Ref(ObjRef::new(pages_node, 0)),
609 ),
610 (
611 names::MEDIA_BOX.clone(),
612 Object::Array(Array::of([
613 Object::Int(0),
614 Object::Int(0),
615 Object::Real(width),
616 Object::Real(height),
617 ])),
618 ),
619 (
620 names::RESOURCES.clone(),
621 Object::Dict(Dict::from_pairs([(
622 edit_names::XOBJECT.clone(),
623 Object::Dict(xobjects),
624 )])),
625 ),
626 (names::CONTENTS.clone(), Object::Ref(stream)),
627 ])))
628}
629
630fn assemble_content(page: &Dict, src: &Document) -> Vec<u8> {
634 let limits = pdfrum_common::Limits::default();
635 let mut diags = pdfrum_common::Diagnostics::default();
636 let mut out = Vec::new();
637
638 match page.get(names::CONTENTS, src).as_deref() {
639 Some(Object::Stream(s)) => {
640 out = pdfrum_parser::decoded_stream(s, src, &limits, &mut diags);
641 }
642 Some(Object::Array(a)) => {
643 for i in 0..a.len() {
644 let Some(s) = a.stream_at(i, src) else {
645 continue;
646 };
647 out.extend_from_slice(&pdfrum_parser::decoded_stream(&s, src, &limits, &mut diags));
648 out.push(b'\n');
649 }
650 }
651 _ => {}
653 }
654 out
655}
656
657fn boxes(page: &Dict, src: &Document) -> ([f32; 4], Option<[f32; 4]>) {
659 let read = |key: &Name| -> Option<[f32; 4]> {
660 let a = match inheritable(page, key, src) {
661 Some(Object::Array(a)) => a,
662 _ => page.array(key, src)?,
663 };
664 if a.len() < 4 {
665 return None;
666 }
667 Some(normalize([
668 a.number_at_or_zero(0),
669 a.number_at_or_zero(1),
670 a.number_at_or_zero(2),
671 a.number_at_or_zero(3),
672 ]))
673 };
674 let media = read(names::MEDIA_BOX).unwrap_or([0.0, 0.0, 612.0, 792.0]);
675 (media, read(names::CROP_BOX))
676}
677
678fn normalize(b: [f32; 4]) -> [f32; 4] {
680 [
681 b[0].min(b[2]),
682 b[1].min(b[3]),
683 b[0].max(b[2]),
684 b[1].max(b[3]),
685 ]
686}
687
688fn intersect(media: [f32; 4], crop: Option<[f32; 4]>) -> [f32; 4] {
690 let Some(crop) = crop else {
691 return media;
692 };
693 [
694 media[0].max(crop[0]),
695 media[1].max(crop[1]),
696 media[2].min(crop[2]),
697 media[3].min(crop[3]),
698 ]
699}
700
701fn page_size(page: &Dict, src: &Document) -> (f32, f32) {
703 let (media, crop) = boxes(page, src);
704 let b = intersect(media, crop);
705 let (w, h) = (b[2] - b[0], b[3] - b[1]);
706 let quarter = matches!(
708 inheritable(page, names::ROTATE, src)
709 .or_else(|| page.raw(names::ROTATE).cloned())
710 .and_then(|o| o.as_int())
711 .map(|v| ((v / 90) % 4 + 4) % 4),
712 Some(1 | 3)
713 );
714 if quarter { (h, w) } else { (w, h) }
715}
716
717fn set(dict: &mut Dict, key: &Name, value: Object) {
719 if dict.contains_key(key) {
720 *dict = Dict::from_pairs(dict.iter().map(|(k, v)| {
721 if k == key {
722 (k.clone(), value.clone())
723 } else {
724 (k.clone(), v.clone())
725 }
726 }));
727 } else {
728 dict.push(key.clone(), value);
729 }
730}