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#[derive(Clone, Debug)]
22pub enum ActiveTransferFunction {
23 Single(TransferFunction),
25 Four([TransferFunction; 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 *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 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 pub(crate) graphics_state: GraphicsState<'a>,
75 pub(crate) text_state: TextState<'a>,
76 pub(crate) ctm: Affine,
77 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 Font(Font<'a>),
127 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 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 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 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 pub(crate) stroke_props: StrokeProps,
264
265 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 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 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 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 .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}