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