//! PDF patterns. use crate::cache::Cache; use crate::color::{Color, ColorSpace}; use crate::context::{Context, InterpreterCache}; use crate::device::Device; use crate::font::Glyph; use crate::interpret::state::{ActiveTransferFunction, State}; use crate::shading::Shading; use crate::soft_mask::SoftMask; use crate::util::{Float32Ext, RectExt, hash128}; use crate::{BlendMode, CacheKey, ClipPath, GlyphDrawMode, Image, PathDrawMode}; use crate::{FillRule, InterpreterSettings, Paint, interpret}; use hayro_syntax::content::TypedIter; use hayro_syntax::object::Dict; use hayro_syntax::object::Stream; use hayro_syntax::object::dict::keys::{ BBOX, EXT_G_STATE, MATRIX, PAINT_TYPE, RESOURCES, SHADING, X_STEP, Y_STEP, }; use hayro_syntax::object::{Object, dict_or_stream}; use hayro_syntax::page::Resources; use hayro_syntax::xref::XRef; use kurbo::{Affine, BezPath, Rect, Shape}; use std::fmt::{Debug, Formatter}; use std::sync::Arc; /// A PDF pattern. #[derive(Debug, Clone)] pub enum Pattern<'a> { /// A shading pattern. Shading(ShadingPattern), /// A tiling pattern. Tiling(Box>), } impl<'a> Pattern<'a> { pub(crate) fn new( object: Object<'a>, ctx: &Context<'a>, resources: &Resources<'a>, ) -> Option { match object { Object::Dict(dict) => Some(Self::Shading(ShadingPattern::new( &dict, &ctx.interpreter_cache.object_cache, ctx.get().graphics_state.non_stroke_alpha, )?)), Object::Stream(stream) => Some(Self::Tiling(Box::new(TilingPattern::new( stream, ctx, resources, )?))), _ => None, } } pub(crate) fn pre_concat_transform(&mut self, transform: Affine) { match self { Self::Shading(p) => { p.matrix = transform * p.matrix; let transformed_clip_path = p.shading.clip_path.clone().map(|r| p.matrix * r); Arc::make_mut(&mut p.shading).clip_path = transformed_clip_path; } Self::Tiling(p) => p.matrix = transform * p.matrix, } } pub(crate) fn set_transfer_function(&mut self, tf: ActiveTransferFunction) { if let Self::Shading(p) = self { p.transfer_function = Some(tf); } } } impl CacheKey for Pattern<'_> { fn cache_key(&self) -> u128 { match self { Self::Shading(p) => p.cache_key(), Self::Tiling(p) => p.cache_key(), } } } /// A shading pattern. #[derive(Clone, Debug)] pub struct ShadingPattern { /// The underlying shading of the pattern. pub shading: Arc, /// A transformation matrix to apply prior to rendering. pub matrix: Affine, /// An additional opacity to apply to the shading pattern. pub opacity: f32, /// An optional transfer function to apply to the shading's output colors. pub transfer_function: Option, } impl ShadingPattern { pub(crate) fn new(dict: &Dict<'_>, cache: &Cache, opacity: f32) -> Option { let shading = dict.get::>(SHADING).and_then(|o| { let (dict, stream) = dict_or_stream(&o)?; Shading::new(dict, stream, cache) })?; let matrix = dict .get::<[f64; 6]>(MATRIX) .map(Affine::new) .unwrap_or_default(); if dict.contains_key(EXT_G_STATE) { warn!("shading patterns with ext_g_state are not supported yet"); } Some(Self { shading: Arc::new(shading), opacity, matrix, transfer_function: None, }) } } impl CacheKey for ShadingPattern { fn cache_key(&self) -> u128 { hash128(&(self.shading.cache_key(), self.matrix.cache_key())) } } /// A tiling pattern. #[derive(Clone)] pub struct TilingPattern<'a> { cache_key: u128, ctx_bbox: Rect, /// The bbox of the tiling pattern. pub bbox: Rect, /// The step in the x direction. pub x_step: f32, /// The step in the y direction. pub y_step: f32, /// A transformation to apply prior to rendering. pub matrix: Affine, stream: Stream<'a>, is_color: bool, pub(crate) stroke_paint: Color, pub(crate) non_stroking_paint: Color, pub(crate) parent_resources: Resources<'a>, pub(crate) cache: InterpreterCache<'a>, pub(crate) settings: InterpreterSettings, pub(crate) xref: &'a XRef, nesting_depth: u32, } impl Debug for TilingPattern<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str("TilingPattern") } } impl<'a> TilingPattern<'a> { pub(crate) fn new( stream: Stream<'a>, ctx: &Context<'a>, resources: &Resources<'a>, ) -> Option { let cache_key = stream.cache_key(); let dict = stream.dict(); let bbox = dict.get::(BBOX)?.to_kurbo(); let x_step = dict.get::(X_STEP)?; let y_step = dict.get::(Y_STEP)?; if x_step.is_nearly_zero() || y_step.is_nearly_zero() || bbox.is_zero_area() { return None; } let is_color = dict.get::(PAINT_TYPE)? == 1; let matrix = dict .get::<[f64; 6]>(MATRIX) .map(Affine::new) .unwrap_or_default(); let state = ctx.get().clone(); let ctx_bbox = ctx.bbox(); let fill_cs = state .graphics_state .none_stroke_cs .pattern_cs() .unwrap_or(ColorSpace::device_gray()); let stroke_cs = state .graphics_state .stroke_cs .pattern_cs() .unwrap_or(ColorSpace::device_gray()); let non_stroking_paint = Color::new( fill_cs, state.graphics_state.non_stroke_color.clone(), state.graphics_state.non_stroke_alpha, ); let stroke_paint = Color::new( stroke_cs, state.graphics_state.stroke_color.clone(), state.graphics_state.stroke_alpha, ); let nesting_depth = ctx.nesting_depth() + 1; Some(Self { cache_key, bbox, x_step, y_step, matrix, ctx_bbox, is_color, stream, stroke_paint, non_stroking_paint, settings: ctx.settings.clone(), parent_resources: resources.clone(), cache: ctx.interpreter_cache.clone(), xref: ctx.xref, nesting_depth, }) } /// Interpret the contents of the pattern into the given device. pub fn interpret( &self, device: &mut impl Device<'a>, initial_transform: Affine, is_stroke: bool, ) -> Option<()> { // PdfCraft patch: a tiling pattern can paint itself (directly, or through resources it // inherits when its own are missing), which recursed until the stack overflowed. Bound // the nesting like XObjects do, tighter because every level rasterizes a tile. if self.nesting_depth > crate::context::MAX_PAINT_NESTING || !crate::context::take_nested_paint(self.nesting_depth) { warn!("tiling pattern nesting depth or paint budget exceeded"); return None; } let state = State::new(initial_transform); let mut context = Context::new_with( state.ctm, // TODO: bbox? (initial_transform * self.ctx_bbox.to_path(0.1)).bounding_box(), &self.cache, self.xref, self.settings.clone(), state, self.nesting_depth, ); // PdfCraft patch: see `context::MAX_PAGE_CONTENT`. let decoded = crate::context::decode_content(&self.stream)?; let resources = Resources::from_parent( self.stream.dict().get(RESOURCES).unwrap_or_default(), self.parent_resources.clone(), ); let iter = TypedIter::new(decoded.as_ref()); let clip_path = ClipPath { path: initial_transform * self.bbox.to_path(0.1), fill: FillRule::NonZero, }; device.push_clip_path(&clip_path); if self.is_color { interpret(iter, &resources, &mut context, device); } else { let paint = if !is_stroke { Paint::Color(self.non_stroking_paint.clone()) } else { Paint::Color(self.stroke_paint.clone()) }; let mut device = StencilPatternDevice::new(device, paint.clone()); interpret(iter, &resources, &mut context, &mut device); } device.pop_clip_path(); Some(()) } } impl CacheKey for TilingPattern<'_> { fn cache_key(&self) -> u128 { self.cache_key } } struct StencilPatternDevice<'a, 'b, T: Device<'a>> { inner: &'b mut T, paint: Paint<'a>, } impl<'a, 'b, T: Device<'a>> StencilPatternDevice<'a, 'b, T> { pub(crate) fn new(device: &'b mut T, paint: Paint<'a>) -> Self { Self { inner: device, paint, } } } // Only filling, stroking of paths and stencil masks are allowed. impl<'a, T: Device<'a>> Device<'a> for StencilPatternDevice<'a, '_, T> { fn draw_path( &mut self, path: &BezPath, transform: Affine, _: &Paint<'_>, draw_mode: &PathDrawMode, ) { self.inner .draw_path(path, transform, &self.paint, draw_mode); } fn set_soft_mask(&mut self, _: Option>) {} fn push_clip_path(&mut self, clip_path: &ClipPath) { self.inner.push_clip_path(clip_path); } fn push_transparency_group(&mut self, _: f32, _: Option>, _: BlendMode) {} fn draw_glyph( &mut self, g: &Glyph<'a>, transform: Affine, glyph_transform: Affine, p: &Paint<'a>, draw_mode: &GlyphDrawMode, ) { self.inner .draw_glyph(g, transform, glyph_transform, p, draw_mode); } fn draw_image(&mut self, image: Image<'a, '_>, transform: Affine) { if let Image::Stencil(mut s) = image { s.paint = self.paint.clone(); self.inner.draw_image(Image::Stencil(s), transform); } } fn pop_clip_path(&mut self) { self.inner.pop_clip_path(); } fn pop_transparency_group(&mut self) {} fn set_blend_mode(&mut self, _: BlendMode) {} }