use crate::content::{TypedIter, UntypedIter};
use crate::object::Array;
use crate::object::Dict;
use crate::object::Name;
use crate::object::Rect;
use crate::object::Stream;
use crate::object::dict::keys::*;
use crate::object::{Object, ObjectLike};
use crate::reader::ReaderContext;
use crate::sync::OnceLock;
use crate::transform::Transform;
use crate::util::FloatExt;
use crate::xref::XRef;
use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
use core::ops::Deref;
#[derive(Debug, Clone)]
struct PagesContext {
media_box: Option<Rect>,
crop_box: Option<Rect>,
rotate: Option<i32>,
}
impl PagesContext {
fn new() -> Self {
Self {
media_box: None,
crop_box: None,
rotate: None,
}
}
}
pub struct Pages<'a> {
pages: Vec<Page<'a>>,
xref: &'a XRef,
}
impl<'a> Pages<'a> {
pub(crate) fn new(
pages_dict: &Dict<'a>,
ctx: &ReaderContext<'a>,
xref: &'a XRef,
) -> Option<Self> {
let mut pages = vec![];
let pages_ctx = PagesContext::new();
resolve_pages(
pages_dict,
&mut pages,
pages_ctx,
Resources::new(Dict::empty(), None, ctx),
)?;
Some(Self { pages, xref })
}
pub(crate) fn new_brute_force(ctx: &ReaderContext<'a>, xref: &'a XRef) -> Option<Self> {
let mut pages = vec![];
for object in xref.objects() {
if let Some(dict) = object.into_dict()
&& let Some(page) = Page::new(
&dict,
&PagesContext::new(),
Resources::new(Dict::empty(), None, ctx),
true,
)
{
pages.push(page);
}
}
if pages.is_empty() {
return None;
}
Some(Self { pages, xref })
}
pub fn xref(&self) -> &'a XRef {
self.xref
}
}
impl<'a> Deref for Pages<'a> {
type Target = [Page<'a>];
fn deref(&self) -> &Self::Target {
&self.pages
}
}
fn resolve_pages<'a>(
pages_dict: &Dict<'a>,
entries: &mut Vec<Page<'a>>,
mut ctx: PagesContext,
resources: Resources<'a>,
) -> Option<()> {
if let Some(media_box) = pages_dict.get::<Rect>(MEDIA_BOX) {
ctx.media_box = Some(media_box);
}
if let Some(crop_box) = pages_dict.get::<Rect>(CROP_BOX) {
ctx.crop_box = Some(crop_box);
}
if let Some(rotate) = pages_dict.get::<i32>(ROTATE) {
ctx.rotate = Some(rotate);
}
let resources = Resources::from_parent(
pages_dict.get::<Dict<'_>>(RESOURCES).unwrap_or_default(),
resources.clone(),
);
let kids = pages_dict.get::<Array<'a>>(KIDS)?;
for dict in kids.iter::<Dict<'_>>() {
match dict.get::<Name<'_>>(TYPE).as_deref() {
Some(PAGES) => {
resolve_pages(&dict, entries, ctx.clone(), resources.clone());
}
_ => {
if let Some(page) = Page::new(&dict, &ctx, resources.clone(), false) {
entries.push(page);
}
}
}
}
Some(())
}
#[derive(Debug, Copy, Clone)]
pub enum Rotation {
None,
Horizontal,
Flipped,
FlippedHorizontal,
}
pub struct Page<'a> {
inner: Dict<'a>,
media_box: Rect,
crop_box: Rect,
rotation: Rotation,
page_streams: OnceLock<Option<Vec<u8>>>,
resources: Resources<'a>,
ctx: ReaderContext<'a>,
}
impl<'a> Page<'a> {
fn new(
dict: &Dict<'a>,
ctx: &PagesContext,
resources: Resources<'a>,
brute_force: bool,
) -> Option<Self> {
if brute_force && !dict.contains_key(CONTENTS) {
return None;
}
let media_box = dict.get::<Rect>(MEDIA_BOX).or(ctx.media_box).unwrap_or(A4);
let crop_box = dict
.get::<Rect>(CROP_BOX)
.or(ctx.crop_box)
.unwrap_or(media_box);
let rotation = match dict
.get::<i32>(ROTATE)
.or(ctx.rotate)
.unwrap_or(0)
.rem_euclid(360)
{
0 => Rotation::None,
90 => Rotation::Horizontal,
180 => Rotation::Flipped,
270 => Rotation::FlippedHorizontal,
_ => Rotation::None,
};
let ctx = resources.ctx.clone();
let resources = Resources::from_parent(
dict.get::<Dict<'_>>(RESOURCES).unwrap_or_default(),
resources,
);
Some(Self {
inner: dict.clone(),
media_box,
crop_box,
rotation,
page_streams: OnceLock::new(),
resources,
ctx,
})
}
fn operations_impl(&self) -> Option<UntypedIter<'_>> {
let stream = self.page_stream()?;
let iter = UntypedIter::new(stream);
Some(iter)
}
pub fn page_stream(&self) -> Option<&[u8]> {
let convert_single = |s: Stream<'_>| {
let data = s.decoded().ok()?;
Some(data.to_vec())
};
self.page_streams
.get_or_init(|| {
if let Some(stream) = self.inner.get::<Stream<'_>>(CONTENTS) {
convert_single(stream)
} else if let Some(array) = self.inner.get::<Array<'_>>(CONTENTS) {
let streams = array.iter::<Stream<'_>>().flat_map(convert_single);
let mut collected = vec![];
for stream in streams {
collected.extend(stream);
collected.push(b' ');
}
Some(collected)
} else {
warn!("contents entry of page was neither stream nor array of streams");
None
}
})
.as_ref()
.map(|d| d.as_slice())
}
pub fn resources(&self) -> &Resources<'a> {
&self.resources
}
pub fn media_box(&self) -> Rect {
self.media_box
}
pub fn rotation(&self) -> Rotation {
self.rotation
}
pub fn crop_box(&self) -> Rect {
self.crop_box
}
pub fn intersected_crop_box(&self) -> Rect {
self.crop_box().intersect(self.media_box())
}
pub fn base_dimensions(&self) -> (f32, f32) {
let crop_box = self.intersected_crop_box();
if (crop_box.width() as f32).is_nearly_zero() || (crop_box.height() as f32).is_nearly_zero()
{
(A4.width() as f32, A4.height() as f32)
} else {
(
crop_box.width().max(1.0) as f32,
crop_box.height().max(1.0) as f32,
)
}
}
pub fn render_dimensions(&self) -> (f32, f32) {
let (mut base_width, mut base_height) = self.base_dimensions();
if matches!(
self.rotation(),
Rotation::Horizontal | Rotation::FlippedHorizontal
) {
core::mem::swap(&mut base_width, &mut base_height);
}
(base_width, base_height)
}
pub fn operations(&self) -> UntypedIter<'_> {
self.operations_impl().unwrap_or(UntypedIter::empty())
}
pub fn raw(&self) -> &Dict<'a> {
&self.inner
}
pub fn xref(&self) -> &'a XRef {
self.ctx.xref()
}
pub fn typed_operations(&self) -> TypedIter<'_> {
TypedIter::from_untyped(self.operations())
}
pub fn initial_transform(&self, invert_y: bool) -> Transform {
let crop_box = self.intersected_crop_box();
let (_, base_height) = self.base_dimensions();
let (width, height) = self.render_dimensions();
let horizontal_t = Transform::ROTATE_CW_90 * Transform::translate((0.0, -width as f64));
let flipped_horizontal_t =
Transform::translate((0.0, height as f64)) * Transform::ROTATE_CCW_90;
let rotation_transform = match self.rotation() {
Rotation::None => Transform::IDENTITY,
Rotation::Horizontal => {
if invert_y {
horizontal_t
} else {
flipped_horizontal_t
}
}
Rotation::Flipped => {
Transform::scale(-1.0) * Transform::translate((-width as f64, -height as f64))
}
Rotation::FlippedHorizontal => {
if invert_y {
flipped_horizontal_t
} else {
horizontal_t
}
}
};
let inversion_transform = if invert_y {
Transform::new([1.0, 0.0, 0.0, -1.0, 0.0, base_height as f64])
} else {
Transform::IDENTITY
};
rotation_transform
* inversion_transform
* Transform::translate((-crop_box.x0, -crop_box.y0))
}
}
#[derive(Clone, Debug)]
pub struct Resources<'a> {
parent: Option<Box<Self>>,
ctx: ReaderContext<'a>,
pub ext_g_states: Dict<'a>,
pub fonts: Dict<'a>,
pub properties: Dict<'a>,
pub color_spaces: Dict<'a>,
pub x_objects: Dict<'a>,
pub patterns: Dict<'a>,
pub shadings: Dict<'a>,
}
impl<'a> Resources<'a> {
pub fn from_parent(resources: Dict<'a>, parent: Self) -> Self {
let ctx = parent.ctx.clone();
Self::new(resources, Some(parent), &ctx)
}
pub(crate) fn new(resources: Dict<'a>, parent: Option<Self>, ctx: &ReaderContext<'a>) -> Self {
let ext_g_states = resources.get::<Dict<'_>>(EXT_G_STATE).unwrap_or_default();
let fonts = resources.get::<Dict<'_>>(FONT).unwrap_or_default();
let color_spaces = resources.get::<Dict<'_>>(COLORSPACE).unwrap_or_default();
let x_objects = resources.get::<Dict<'_>>(XOBJECT).unwrap_or_default();
let patterns = resources.get::<Dict<'_>>(PATTERN).unwrap_or_default();
let shadings = resources.get::<Dict<'_>>(SHADING).unwrap_or_default();
let properties = resources.get::<Dict<'_>>(PROPERTIES).unwrap_or_default();
let parent = parent.map(Box::new);
Self {
parent,
ext_g_states,
fonts,
color_spaces,
properties,
x_objects,
patterns,
shadings,
ctx: ctx.clone(),
}
}
fn get_resource<T: ObjectLike<'a>>(&self, name: &Name<'_>, dict: &Dict<'a>) -> Option<T> {
dict.get::<T>(name.deref())
}
pub fn parent(&self) -> Option<&Self> {
self.parent.as_deref()
}
pub fn get_ext_g_state(&self, name: &Name<'_>) -> Option<Dict<'a>> {
self.get_resource::<Dict<'_>>(name, &self.ext_g_states)
.or_else(|| self.parent.as_ref().and_then(|p| p.get_ext_g_state(name)))
}
pub fn get_color_space(&self, name: &Name<'_>) -> Option<Object<'a>> {
self.get_resource::<Object<'_>>(name, &self.color_spaces)
.or_else(|| self.parent.as_ref().and_then(|p| p.get_color_space(name)))
}
pub fn get_font(&self, name: &Name<'_>) -> Option<Dict<'a>> {
self.get_resource::<Dict<'_>>(name, &self.fonts)
.or_else(|| self.parent.as_ref().and_then(|p| p.get_font(name)))
}
pub fn get_pattern(&self, name: &Name<'_>) -> Option<Object<'a>> {
self.get_resource::<Object<'_>>(name, &self.patterns)
.or_else(|| self.parent.as_ref().and_then(|p| p.get_pattern(name)))
}
pub fn get_x_object(&self, name: &Name<'_>) -> Option<Stream<'a>> {
self.get_resource::<Stream<'_>>(name, &self.x_objects)
.or_else(|| self.parent.as_ref().and_then(|p| p.get_x_object(name)))
}
pub fn get_shading(&self, name: &Name<'_>) -> Option<Object<'a>> {
self.get_resource::<Object<'_>>(name, &self.shadings)
.or_else(|| self.parent.as_ref().and_then(|p| p.get_shading(name)))
}
}
const POINTS_PER_INCH: f64 = 72.0;
const POINTS_PER_MM: f64 = 1.0 / (10.0 * 2.54) * POINTS_PER_INCH;
pub const A4: Rect = Rect {
x0: 0.0,
y0: 0.0,
x1: 210.0 * POINTS_PER_MM,
y1: 297.0 * POINTS_PER_MM,
};
pub(crate) mod cached {
use crate::page::Pages;
use crate::reader::ReaderContext;
use crate::xref::XRef;
use core::ops::Deref;
#[cfg(feature = "std")]
pub(crate) use std::sync::Arc;
#[cfg(not(feature = "std"))]
pub(crate) use alloc::rc::Rc as Arc;
pub(crate) struct CachedPages {
pages: Pages<'static>,
_xref: Arc<XRef>,
}
impl CachedPages {
pub(crate) fn new(xref: Arc<XRef>) -> Option<Self> {
let xref_reference: &'static XRef = unsafe { core::mem::transmute(xref.deref()) };
let ctx = ReaderContext::new(xref_reference, false);
let pages = xref_reference
.get_with(xref.trailer_data().pages_ref, &ctx)
.and_then(|p| Pages::new(&p, &ctx, xref_reference))
.or_else(|| Pages::new_brute_force(&ctx, xref_reference))?;
Some(Self { pages, _xref: xref })
}
pub(crate) fn get(&self) -> &Pages<'_> {
&self.pages
}
}
}