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#[derive(Clone, Debug)]
22pub enum ActiveTransferFunction {
23 Single(Function),
25 Four([Function; 4]),
27}
28
29impl ActiveTransferFunction {
30 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 pub(crate) graphics_state: GraphicsState<'a>,
67 pub(crate) text_state: TextState<'a>,
68 pub(crate) ctm: Affine,
69 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 Font(Font<'a>),
119 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 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 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 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 pub(crate) stroke_props: StrokeProps,
256
257 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 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 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 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 .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}