1use crate::content::{TypedIter, UntypedIter};
4use crate::object::Array;
5use crate::object::Dict;
6use crate::object::Name;
7use crate::object::Rect;
8use crate::object::Stream;
9use crate::object::array::ArrayIter;
10use crate::object::dict::keys::*;
11use crate::object::{MaybeRef, Object};
12use crate::reader::ReaderContext;
13use crate::sync::{FxHashSet, OnceLock};
14use crate::transform::Transform;
15use crate::util::FloatExt;
16use crate::xref::XRef;
17use alloc::vec;
18use alloc::vec::Vec;
19use core::ops::Deref;
20
21#[derive(Debug, Clone)]
23struct PagesContext<'a> {
24 media_box: Option<Rect>,
25 crop_box: Option<Rect>,
26 rotate: Option<i32>,
27 resources: Dict<'a>,
28}
29
30impl PagesContext<'_> {
31 fn new() -> Self {
32 Self {
33 media_box: None,
34 crop_box: None,
35 rotate: None,
36 resources: Dict::empty(),
37 }
38 }
39}
40
41pub struct Pages<'a> {
43 pages: Vec<Page<'a>>,
44 xref: &'a XRef,
45}
46
47impl<'a> Pages<'a> {
48 pub(crate) fn new(
50 pages_dict: &Dict<'a>,
51 ctx: &ReaderContext<'a>,
52 xref: &'a XRef,
53 ) -> Option<Self> {
54 let mut pages = vec![];
55 let pages_ctx = PagesContext::new();
56 resolve_pages(pages_dict, &mut pages, pages_ctx, ctx)?;
57
58 Some(Self { pages, xref })
59 }
60
61 pub(crate) fn new_brute_force(ctx: &ReaderContext<'a>, xref: &'a XRef) -> Option<Self> {
66 let mut pages = vec![];
67
68 for object in xref.objects() {
69 if let Some(dict) = object.into_dict()
70 && let Some(page) = Page::new(&dict, &PagesContext::new(), ctx, true)
71 {
72 pages.push(page);
73 }
74 }
75
76 if pages.is_empty() {
77 return None;
78 }
79
80 Some(Self { pages, xref })
81 }
82
83 pub fn xref(&self) -> &'a XRef {
85 self.xref
86 }
87}
88
89impl<'a> Deref for Pages<'a> {
90 type Target = [Page<'a>];
91
92 fn deref(&self) -> &Self::Target {
93 &self.pages
94 }
95}
96
97struct PageTreeFrame<'a> {
98 kids: ArrayIter<'a>,
99 ctx: PagesContext<'a>,
100}
101
102impl<'a> PageTreeFrame<'a> {
103 fn new(dict: &Dict<'a>, mut ctx: PagesContext<'a>) -> Option<Self> {
104 ctx.media_box = dict.get::<Rect>(MEDIA_BOX).or(ctx.media_box);
105 ctx.crop_box = dict.get::<Rect>(CROP_BOX).or(ctx.crop_box);
106 ctx.rotate = dict.get::<i32>(ROTATE).or(ctx.rotate);
107 if let Some(resources) = dict.get::<Dict<'a>>(RESOURCES) {
108 ctx.resources = resources;
109 }
110
111 Some(Self {
112 kids: dict.get::<Array<'a>>(KIDS)?.raw_iter(),
113 ctx,
114 })
115 }
116}
117
118fn resolve_pages<'a>(
119 pages_dict: &Dict<'a>,
120 entries: &mut Vec<Page<'a>>,
121 ctx: PagesContext<'a>,
122 reader_ctx: &ReaderContext<'a>,
123) -> Option<()> {
124 let mut visited = FxHashSet::default();
125
126 if let Some(id) = pages_dict.obj_id() {
127 visited.insert(id);
128 }
129
130 let mut stack = vec![PageTreeFrame::new(pages_dict, ctx)?];
131
132 while let Some(frame) = stack.last_mut() {
133 let Some(kid) = frame.kids.next() else {
134 stack.pop();
135
136 continue;
137 };
138
139 let dict = match kid {
140 MaybeRef::Ref(id) => {
141 if !visited.insert(id.into()) {
142 continue;
143 }
144
145 reader_ctx
146 .xref()
147 .get_with::<Dict<'a>>(id.into(), reader_ctx)
148 }
149 MaybeRef::NotRef(object) => object.into_dict(),
150 };
151
152 let Some(dict) = dict else {
153 continue;
154 };
155
156 match dict.get::<Name<'_>>(TYPE).as_deref() {
157 Some(PAGES) => {
158 if let Some(child) = PageTreeFrame::new(&dict, frame.ctx.clone()) {
159 stack.push(child);
160 }
161 }
162 _ => {
165 if let Some(page) = Page::new(&dict, &frame.ctx, reader_ctx, false) {
166 entries.push(page);
167 }
168 }
169 }
170 }
171
172 Some(())
173}
174
175#[derive(Debug, Copy, Clone)]
177pub enum Rotation {
178 None,
180 Horizontal,
182 Flipped,
184 FlippedHorizontal,
186}
187
188pub struct Page<'a> {
190 inner: Dict<'a>,
191 media_box: Rect,
192 crop_box: Rect,
193 rotation: Rotation,
194 page_streams: OnceLock<Option<Vec<u8>>>,
195 resources: Resources<'a>,
196 ctx: ReaderContext<'a>,
197}
198
199impl<'a> Page<'a> {
200 fn new(
201 dict: &Dict<'a>,
202 ctx: &PagesContext<'a>,
203 reader_ctx: &ReaderContext<'a>,
204 brute_force: bool,
205 ) -> Option<Self> {
206 if brute_force && !dict.contains_key(CONTENTS) {
209 return None;
210 }
211
212 let media_box = dict.get::<Rect>(MEDIA_BOX).or(ctx.media_box).unwrap_or(A4);
213
214 let crop_box = dict
215 .get::<Rect>(CROP_BOX)
216 .or(ctx.crop_box)
217 .unwrap_or(media_box);
218
219 let rotation = match dict
220 .get::<i32>(ROTATE)
221 .or(ctx.rotate)
222 .unwrap_or(0)
223 .rem_euclid(360)
224 {
225 0 => Rotation::None,
226 90 => Rotation::Horizontal,
227 180 => Rotation::Flipped,
228 270 => Rotation::FlippedHorizontal,
229 _ => Rotation::None,
230 };
231
232 let resources = Resources::new(
233 dict.get::<Dict<'a>>(RESOURCES)
234 .unwrap_or_else(|| ctx.resources.clone()),
235 );
236
237 Some(Self {
238 inner: dict.clone(),
239 media_box,
240 crop_box,
241 rotation,
242 page_streams: OnceLock::new(),
243 resources,
244 ctx: reader_ctx.clone(),
245 })
246 }
247
248 fn operations_impl(&self) -> Option<UntypedIter<'_>> {
249 let stream = self.page_stream()?;
250 let iter = UntypedIter::new(stream);
251
252 Some(iter)
253 }
254
255 pub fn page_stream(&self) -> Option<&[u8]> {
257 let convert_single = |s: Stream<'_>| {
258 let data = s.decoded().ok()?;
259 Some(data.to_vec())
260 };
261
262 self.page_streams
263 .get_or_init(|| {
264 if let Some(stream) = self.inner.get::<Stream<'_>>(CONTENTS) {
265 convert_single(stream)
266 } else if let Some(array) = self.inner.get::<Array<'_>>(CONTENTS) {
267 let streams = array.iter::<Stream<'_>>().flat_map(convert_single);
268
269 let mut collected = vec![];
270
271 for stream in streams {
272 collected.extend(stream);
273 collected.push(b' ');
275 }
276
277 Some(collected)
278 } else {
279 warn!("contents entry of page was neither stream nor array of streams");
280
281 None
282 }
283 })
284 .as_ref()
285 .map(|d| d.as_slice())
286 }
287
288 pub fn resources(&self) -> &Resources<'a> {
290 &self.resources
291 }
292
293 pub fn media_box(&self) -> Rect {
295 self.media_box
296 }
297
298 pub fn rotation(&self) -> Rotation {
300 self.rotation
301 }
302
303 pub fn crop_box(&self) -> Rect {
305 self.crop_box
306 }
307
308 pub fn intersected_crop_box(&self) -> Rect {
310 self.crop_box().intersect(self.media_box())
311 }
312
313 pub fn base_dimensions(&self) -> (f32, f32) {
316 let crop_box = self.intersected_crop_box();
317
318 if (crop_box.width() as f32).is_nearly_zero() || (crop_box.height() as f32).is_nearly_zero()
319 {
320 (A4.width() as f32, A4.height() as f32)
321 } else {
322 (
323 crop_box.width().max(1.0) as f32,
324 crop_box.height().max(1.0) as f32,
325 )
326 }
327 }
328
329 pub fn render_dimensions(&self) -> (f32, f32) {
334 let (mut base_width, mut base_height) = self.base_dimensions();
335
336 if matches!(
337 self.rotation(),
338 Rotation::Horizontal | Rotation::FlippedHorizontal
339 ) {
340 core::mem::swap(&mut base_width, &mut base_height);
341 }
342
343 (base_width, base_height)
344 }
345
346 pub fn operations(&self) -> UntypedIter<'_> {
348 self.operations_impl().unwrap_or(UntypedIter::empty())
349 }
350
351 pub fn raw(&self) -> &Dict<'a> {
353 &self.inner
354 }
355
356 pub fn xref(&self) -> &'a XRef {
358 self.ctx.xref()
359 }
360
361 pub fn typed_operations(&self) -> TypedIter<'_> {
363 TypedIter::from_untyped(self.operations())
364 }
365
366 pub fn initial_transform(&self, invert_y: bool) -> Transform {
372 let crop_box = self.intersected_crop_box();
373 let (_, base_height) = self.base_dimensions();
374 let (width, height) = self.render_dimensions();
375
376 let horizontal_t = Transform::ROTATE_CW_90 * Transform::translate((0.0, -width as f64));
377 let flipped_horizontal_t =
378 Transform::translate((0.0, height as f64)) * Transform::ROTATE_CCW_90;
379
380 let rotation_transform = match self.rotation() {
381 Rotation::None => Transform::IDENTITY,
382 Rotation::Horizontal => {
383 if invert_y {
384 horizontal_t
385 } else {
386 flipped_horizontal_t
387 }
388 }
389 Rotation::Flipped => {
390 Transform::scale(-1.0) * Transform::translate((-width as f64, -height as f64))
391 }
392 Rotation::FlippedHorizontal => {
393 if invert_y {
394 flipped_horizontal_t
395 } else {
396 horizontal_t
397 }
398 }
399 };
400
401 let inversion_transform = if invert_y {
402 Transform::new([1.0, 0.0, 0.0, -1.0, 0.0, base_height as f64])
403 } else {
404 Transform::IDENTITY
405 };
406
407 rotation_transform
408 * inversion_transform
409 * Transform::translate((-crop_box.x0, -crop_box.y0))
410 }
411}
412
413#[derive(Clone, Debug)]
415pub struct Resources<'a> {
416 pub ext_g_states: Dict<'a>,
418 pub fonts: Dict<'a>,
420 pub properties: Dict<'a>,
422 pub color_spaces: Dict<'a>,
424 pub x_objects: Dict<'a>,
426 pub patterns: Dict<'a>,
428 pub shadings: Dict<'a>,
430}
431
432impl<'a> Resources<'a> {
433 pub fn new(resources: Dict<'a>) -> Self {
435 let ext_g_states = resources.get::<Dict<'_>>(EXT_G_STATE).unwrap_or_default();
436 let fonts = resources.get::<Dict<'_>>(FONT).unwrap_or_default();
437 let color_spaces = resources.get::<Dict<'_>>(COLORSPACE).unwrap_or_default();
438 let x_objects = resources.get::<Dict<'_>>(XOBJECT).unwrap_or_default();
439 let patterns = resources.get::<Dict<'_>>(PATTERN).unwrap_or_default();
440 let shadings = resources.get::<Dict<'_>>(SHADING).unwrap_or_default();
441 let properties = resources.get::<Dict<'_>>(PROPERTIES).unwrap_or_default();
442
443 Self {
444 ext_g_states,
445 fonts,
446 color_spaces,
447 properties,
448 x_objects,
449 patterns,
450 shadings,
451 }
452 }
453
454 pub fn get_ext_g_state(&self, name: &Name<'_>) -> Option<Dict<'a>> {
456 self.ext_g_states.get(name)
457 }
458
459 pub fn get_color_space(&self, name: &Name<'_>) -> Option<Object<'a>> {
461 self.color_spaces.get(name)
462 }
463
464 pub fn get_font(&self, name: &Name<'_>) -> Option<Dict<'a>> {
466 self.fonts.get(name)
467 }
468
469 pub fn get_pattern(&self, name: &Name<'_>) -> Option<Object<'a>> {
471 self.patterns.get(name)
472 }
473
474 pub fn get_x_object(&self, name: &Name<'_>) -> Option<Stream<'a>> {
476 self.x_objects.get(name)
477 }
478
479 pub fn get_shading(&self, name: &Name<'_>) -> Option<Object<'a>> {
481 self.shadings.get(name)
482 }
483}
484
485const POINTS_PER_INCH: f64 = 72.0;
487const POINTS_PER_MM: f64 = 1.0 / (10.0 * 2.54) * POINTS_PER_INCH;
488
489pub const A4: Rect = Rect {
491 x0: 0.0,
492 y0: 0.0,
493 x1: 210.0 * POINTS_PER_MM,
494 y1: 297.0 * POINTS_PER_MM,
495};
496
497pub(crate) mod cached {
498 use crate::page::Pages;
499 use crate::reader::ReaderContext;
500 use crate::xref::XRef;
501 use core::ops::Deref;
502
503 #[cfg(feature = "std")]
506 pub(crate) use std::sync::Arc;
507
508 #[cfg(not(feature = "std"))]
509 pub(crate) use alloc::rc::Rc as Arc;
510
511 pub(crate) struct CachedPages {
512 pages: Pages<'static>,
513 _xref: Arc<XRef>,
516 }
517
518 impl CachedPages {
519 pub(crate) fn new(xref: Arc<XRef>) -> Option<Self> {
520 let xref_reference: &'static XRef = unsafe { core::mem::transmute(xref.deref()) };
528
529 let ctx = ReaderContext::new(xref_reference, false);
530 let pages = xref_reference
531 .get_with(xref.trailer_data().pages_ref, &ctx)
532 .and_then(|p| Pages::new(&p, &ctx, xref_reference))
533 .or_else(|| Pages::new_brute_force(&ctx, xref_reference))?;
534
535 Some(Self { pages, _xref: xref })
536 }
537
538 pub(crate) fn get(&self) -> &Pages<'_> {
539 &self.pages
540 }
541 }
542}