Skip to main content

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