Skip to main content

pdf_interpret/interpret/
state.rs

1use crate::StrokeProps;
2use crate::color::{AlphaColor, ColorComponents, ColorSpace};
3use crate::context::Context;
4use crate::convert::{convert_line_cap, convert_line_join};
5use crate::font::{Font, UNITS_PER_EM};
6use crate::function::Function;
7use crate::interpret::text::TextRenderingMode;
8use crate::pattern::Pattern;
9use crate::soft_mask::SoftMask;
10use crate::types::BlendMode;
11use crate::util::OptionLog;
12use kurbo::{Affine, BezPath, Vec2};
13use log::warn;
14use pdf_syntax::content::ops::{LineCap, LineJoin};
15use pdf_syntax::object::dict::keys::{FONT, SMASK, TR, TR2};
16use pdf_syntax::object::{Array, Dict, Name, Number, Object};
17use pdf_syntax::page::Resources;
18use smallvec::smallvec;
19use std::ops::Deref;
20
21/// A transfer function.
22#[derive(Clone, Debug)]
23pub enum ActiveTransferFunction {
24    /// A single transfer function applied to all components.
25    Single(Function),
26    /// Four transfer functions, one for each component.
27    Four([Function; 4]),
28}
29
30impl ActiveTransferFunction {
31    /// Apply the transfer function to the RGB channels of an RGBA color.
32    /// The alpha channel is left unchanged.
33    pub fn apply(&self, color: &AlphaColor) -> AlphaColor {
34        let mut rgba = color.components();
35
36        match self {
37            Self::Single(f) => {
38                for c in &mut rgba[..3] {
39                    if let Some(out) = f.eval(smallvec![*c]) {
40                        *c = out[0];
41                    }
42                }
43            }
44            Self::Four(functions) => {
45                for (i, f) in functions[..3].iter().enumerate() {
46                    if let Some(out) = f.eval(smallvec![rgba[i]]) {
47                        rgba[i] = out[0];
48                    }
49                }
50            }
51        }
52
53        AlphaColor::new(rgba)
54    }
55}
56
57#[derive(Clone, Debug)]
58pub(crate) enum ClipType {
59    Dummy,
60    Real,
61}
62
63#[derive(Clone, Debug)]
64pub(crate) struct State<'a> {
65    // Note that the text state and ctm are theoretically part of the graphics state,
66    // but we keep them separate for simplicity.
67    pub(crate) graphics_state: GraphicsState<'a>,
68    pub(crate) text_state: TextState<'a>,
69    pub(crate) ctm: Affine,
70    // Strictly speaking not part of the graphics state, but we keep it there for
71    // consistency.
72    pub(crate) clips: Vec<ClipType>,
73}
74
75impl Default for State<'_> {
76    fn default() -> Self {
77        State {
78            ctm: Affine::IDENTITY,
79            clips: vec![],
80            text_state: TextState::default(),
81            graphics_state: GraphicsState::default(),
82        }
83    }
84}
85
86impl<'a> State<'a> {
87    pub(crate) fn new(initial_transform: Affine) -> Self {
88        Self {
89            ctm: initial_transform,
90            ..Self::default()
91        }
92    }
93
94    pub(crate) fn stroke_data(&self) -> PaintData<'a> {
95        PaintData {
96            alpha: self.graphics_state.stroke_alpha,
97            color: self.graphics_state.stroke_color.clone(),
98            color_space: self.graphics_state.stroke_cs.clone(),
99            pattern: self.graphics_state.stroke_pattern.clone(),
100            transfer_function: self.graphics_state.transfer_function.clone(),
101        }
102    }
103
104    pub(crate) fn non_stroke_data(&self) -> PaintData<'a> {
105        PaintData {
106            alpha: self.graphics_state.non_stroke_alpha,
107            color: self.graphics_state.non_stroke_color.clone(),
108            color_space: self.graphics_state.none_stroke_cs.clone(),
109            pattern: self.graphics_state.non_stroke_pattern.clone(),
110            transfer_function: self.graphics_state.transfer_function.clone(),
111        }
112    }
113}
114
115#[derive(Clone, Debug)]
116pub(crate) enum TextStateFont<'a> {
117    /// The font in the text state was explicitly set and resolves to a valid
118    /// font.
119    Font(Font<'a>),
120    /// The font was not set or an invalid font was set.
121    Fallback(Font<'a>),
122}
123
124impl<'a> Deref for TextStateFont<'a> {
125    type Target = Font<'a>;
126
127    fn deref(&self) -> &Self::Target {
128        match self {
129            TextStateFont::Font(f) => f,
130            TextStateFont::Fallback(f) => f,
131        }
132    }
133}
134
135#[derive(Clone, Debug)]
136pub(crate) struct TextState<'a> {
137    pub(crate) char_space: f32,
138    pub(crate) word_space: f32,
139    // Note that this stores 1/100 of the actual scaling.
140    pub(crate) horizontal_scaling: f32,
141    pub(crate) leading: f32,
142    pub(crate) font: Option<TextStateFont<'a>>,
143    pub(crate) font_size: f32,
144    pub(crate) rise: f32,
145    pub(crate) render_mode: TextRenderingMode,
146
147    pub(crate) text_matrix: Affine,
148    pub(crate) text_line_matrix: Affine,
149
150    // When setting the text rendering mode to `clip`, the glyphs should instead be collected
151    // as paths and then applied as 1 single clip path. This field stores those clip paths.
152    pub(crate) clip_paths: BezPath,
153}
154
155impl<'a> TextState<'a> {
156    fn temp_transform(&self) -> Affine {
157        Affine::new([
158            self.font_size as f64 * self.horizontal_scaling() as f64,
159            0.0,
160            0.0,
161            self.font_size as f64,
162            0.0,
163            self.rise as f64,
164        ])
165    }
166
167    fn horizontal_scaling(&self) -> f32 {
168        self.horizontal_scaling / 100.0
169    }
170
171    fn font_horizontal(&self) -> bool {
172        self.font
173            .as_ref()
174            .map(|f| f.is_horizontal())
175            .unwrap_or(false)
176    }
177
178    pub(crate) fn apply_adjustment(&mut self, adjustment: f32) {
179        let horizontal = self.font_horizontal();
180
181        let horizontal_scaling = if horizontal {
182            self.horizontal_scaling()
183        } else {
184            1.0
185        };
186
187        let scaled_adjustment = -adjustment / UNITS_PER_EM * self.font_size * horizontal_scaling;
188        let (tx, ty) = if horizontal {
189            (scaled_adjustment, 0.0)
190        } else {
191            (0.0, scaled_adjustment)
192        };
193
194        self.text_matrix *= Affine::new([1.0, 0.0, 0.0, 1.0, tx as f64, ty as f64]);
195    }
196
197    pub(crate) fn apply_code_advance(&mut self, char_code: u32, code_len: usize) {
198        let glyph_advance = self
199            .font
200            .as_ref()
201            .map(|f| f.code_advance(char_code))
202            .unwrap_or(Vec2::ZERO);
203        let horizontal = self.font_horizontal();
204
205        let word_space = if char_code == 32 && code_len == 1 {
206            self.word_space
207        } else {
208            0.0
209        };
210
211        let base_advance =
212            |advance: f32| advance / UNITS_PER_EM * self.font_size + self.char_space + word_space;
213
214        let tx = if horizontal {
215            base_advance(glyph_advance.x as f32) * self.horizontal_scaling()
216        } else {
217            0.0
218        };
219
220        let ty = if !horizontal {
221            base_advance(glyph_advance.y as f32)
222        } else {
223            0.0
224        };
225
226        self.text_matrix *= Affine::new([1.0, 0.0, 0.0, 1.0, tx as f64, ty as f64]);
227    }
228
229    pub(crate) fn full_transform(&self) -> Affine {
230        self.text_matrix * self.temp_transform()
231    }
232}
233
234impl Default for TextState<'_> {
235    fn default() -> Self {
236        Self {
237            char_space: 0.0,
238            word_space: 0.0,
239            horizontal_scaling: 100.0,
240            leading: 0.0,
241            font: None,
242            // Not in the specification, but we just define it so we don't need to use an option.
243            font_size: 1.0,
244            render_mode: TextRenderingMode::default(),
245            text_matrix: Affine::IDENTITY,
246            text_line_matrix: Affine::IDENTITY,
247            rise: 0.0,
248            clip_paths: BezPath::default(),
249        }
250    }
251}
252
253#[derive(Clone, Debug)]
254pub(crate) struct GraphicsState<'a> {
255    // Stroke parameters.
256    pub(crate) stroke_props: StrokeProps,
257
258    // Stroke paint parameters.
259    pub(crate) stroke_color: ColorComponents,
260    pub(crate) stroke_pattern: Option<Pattern<'a>>,
261    pub(crate) stroke_cs: ColorSpace,
262    pub(crate) stroke_alpha: f32,
263
264    // Non-stroke paint parameters.
265    pub(crate) non_stroke_color: ColorComponents,
266    pub(crate) non_stroke_pattern: Option<Pattern<'a>>,
267    pub(crate) none_stroke_cs: ColorSpace,
268    pub(crate) non_stroke_alpha: f32,
269
270    pub(crate) soft_mask: Option<SoftMask<'a>>,
271    pub(crate) transfer_function: Option<ActiveTransferFunction>,
272    pub(crate) blend_mode: BlendMode,
273}
274
275impl Default for GraphicsState<'_> {
276    fn default() -> Self {
277        GraphicsState {
278            stroke_props: StrokeProps::default(),
279            non_stroke_alpha: 1.0,
280            stroke_cs: ColorSpace::device_gray(),
281            stroke_color: smallvec![0.0,],
282            none_stroke_cs: ColorSpace::device_gray(),
283            non_stroke_color: smallvec![0.0],
284            stroke_alpha: 1.0,
285            stroke_pattern: None,
286            non_stroke_pattern: None,
287            soft_mask: None,
288            transfer_function: None,
289            blend_mode: BlendMode::default(),
290        }
291    }
292}
293
294pub(crate) struct PaintData<'a> {
295    pub(crate) alpha: f32,
296    pub(crate) color: ColorComponents,
297    pub(crate) color_space: ColorSpace,
298    pub(crate) pattern: Option<Pattern<'a>>,
299    pub(crate) transfer_function: Option<ActiveTransferFunction>,
300}
301
302pub(crate) fn handle_gs<'a>(
303    dict: &Dict<'a>,
304    context: &mut Context<'a>,
305    parent_resources: &Resources<'a>,
306) {
307    for key in dict.keys() {
308        handle_gs_single(dict, key.clone(), context, parent_resources).warn_none(&format!(
309            "invalid value in graphics state for {}",
310            key.as_str()
311        ));
312    }
313}
314
315pub(crate) fn handle_gs_single<'a>(
316    dict: &Dict<'a>,
317    key: Name,
318    context: &mut Context<'a>,
319    parent_resources: &Resources<'a>,
320) -> Option<()> {
321    // TODO Can we use constants here somehow?
322    match key.as_str() {
323        "LW" => context.get_mut().graphics_state.stroke_props.line_width = dict.get::<f32>(key)?,
324        "LC" => {
325            context.get_mut().graphics_state.stroke_props.line_cap =
326                convert_line_cap(LineCap(dict.get::<Number>(key)?));
327        }
328        "LJ" => {
329            context.get_mut().graphics_state.stroke_props.line_join =
330                convert_line_join(LineJoin(dict.get::<Number>(key)?));
331        }
332        "ML" => context.get_mut().graphics_state.stroke_props.miter_limit = dict.get::<f32>(key)?,
333        "CA" => context.get_mut().graphics_state.stroke_alpha = dict.get::<f32>(key)?,
334        "ca" => context.get_mut().graphics_state.non_stroke_alpha = dict.get::<f32>(key)?,
335        "TR" | "TR2" => {
336            let function = match dict
337                .get::<Object<'_>>(TR2)
338                .or_else(|| dict.get::<Object<'_>>(TR))?
339            {
340                Object::Array(array) => {
341                    let mut iter = array.iter::<Object<'_>>();
342                    let functions = [
343                        Function::new_with_sink(&iter.next()?, &context.settings.warning_sink)?,
344                        Function::new_with_sink(&iter.next()?, &context.settings.warning_sink)?,
345                        Function::new_with_sink(&iter.next()?, &context.settings.warning_sink)?,
346                        Function::new_with_sink(&iter.next()?, &context.settings.warning_sink)?,
347                    ];
348
349                    Some(ActiveTransferFunction::Four(functions))
350                }
351                // Only `Identity` and `Default` are valid, which both just reset it.
352                Object::Name(_) => None,
353                o => Some(ActiveTransferFunction::Single(Function::new_with_sink(
354                    &o,
355                    &context.settings.warning_sink,
356                )?)),
357            };
358
359            context.get_mut().graphics_state.transfer_function = function;
360        }
361        "SMask" => {
362            if let Some(name) = dict.get::<Name>(SMASK) {
363                if name.deref() == b"None" {
364                    context.get_mut().graphics_state.soft_mask = None;
365                }
366            } else {
367                context.get_mut().graphics_state.soft_mask = dict
368                    .get::<Dict<'_>>(SMASK)
369                    .and_then(|d| SoftMask::new(&d, context, parent_resources.clone()));
370            }
371        }
372        "BM" => {
373            if let Some(name) = dict.get::<Name>(key.clone()) {
374                if let Some(bm) = convert_blend_mode(name.as_str()) {
375                    context.get_mut().graphics_state.blend_mode = bm;
376
377                    return Some(());
378                }
379            } else if let Some(arr) = dict.get::<Array<'_>>(key) {
380                for name in arr.iter::<Name>() {
381                    if let Some(bm) = convert_blend_mode(name.as_str()) {
382                        context.get_mut().graphics_state.blend_mode = bm;
383
384                        return Some(());
385                    }
386                }
387            }
388
389            warn!("unknown blend mode, defaulting to Normal");
390            context.get_mut().graphics_state.blend_mode = BlendMode::Normal;
391        }
392        "Font" => {
393            let arr = dict.get::<Array<'_>>(FONT)?;
394            let mut iter = arr.iter::<Object<'_>>();
395            let font_dict = iter.next()?.into_dict()?;
396            let size = iter.next()?.into_number()?.as_f32();
397
398            let font = context.resolve_font(&font_dict);
399            context.get_mut().text_state.font_size = size;
400            context.get_mut().text_state.font = font;
401        }
402        "D" => {
403            let arr = dict.get::<Array<'_>>(key)?;
404            let mut iter = arr.iter::<Object<'_>>();
405            let dash_array = iter.next()?.into_array()?;
406            let dash_phase = iter.next()?.into_number()?.as_f32();
407
408            context.get_mut().graphics_state.stroke_props.dash_offset = dash_phase;
409            context.get_mut().graphics_state.stroke_props.dash_array = dash_array
410                .iter::<f32>()
411                // kurbo apparently cannot properly deal with offsets that are exactly 0.
412                .map(|n| if n == 0.0 { 0.01 } else { n })
413                .collect();
414        }
415        "Type" => {}
416        _ => {}
417    }
418
419    Some(())
420}
421
422fn convert_blend_mode(name: &str) -> Option<BlendMode> {
423    let bm = match name {
424        "Normal" => BlendMode::Normal,
425        "Multiply" => BlendMode::Multiply,
426        "Screen" => BlendMode::Screen,
427        "Overlay" => BlendMode::Overlay,
428        "Darken" => BlendMode::Darken,
429        "Lighten" => BlendMode::Lighten,
430        "ColorDodge" => BlendMode::ColorDodge,
431        "ColorBurn" => BlendMode::ColorBurn,
432        "HardLight" => BlendMode::HardLight,
433        "SoftLight" => BlendMode::SoftLight,
434        "Difference" => BlendMode::Difference,
435        "Exclusion" => BlendMode::Exclusion,
436        "Hue" => BlendMode::Hue,
437        "Saturation" => BlendMode::Saturation,
438        "Color" => BlendMode::Color,
439        "Luminosity" => BlendMode::Luminosity,
440        _ => return None,
441    };
442
443    Some(bm)
444}