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#[derive(Clone, Debug)]
23pub enum ActiveTransferFunction {
24 Single(Function),
26 Four([Function; 4]),
28}
29
30impl ActiveTransferFunction {
31 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 pub(crate) graphics_state: GraphicsState<'a>,
68 pub(crate) text_state: TextState<'a>,
69 pub(crate) ctm: Affine,
70 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 Font(Font<'a>),
120 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 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 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 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 pub(crate) stroke_props: StrokeProps,
257
258 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 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 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(&iter.next()?)?,
344 Function::new(&iter.next()?)?,
345 Function::new(&iter.next()?)?,
346 Function::new(&iter.next()?)?,
347 ];
348
349 Some(ActiveTransferFunction::Four(functions))
350 }
351 Object::Name(_) => None,
353 o => Some(ActiveTransferFunction::Single(Function::new(&o)?)),
354 };
355
356 context.get_mut().graphics_state.transfer_function = function;
357 }
358 "SMask" => {
359 if let Some(name) = dict.get::<Name>(SMASK) {
360 if name.deref() == b"None" {
361 context.get_mut().graphics_state.soft_mask = None;
362 }
363 } else {
364 context.get_mut().graphics_state.soft_mask = dict
365 .get::<Dict<'_>>(SMASK)
366 .and_then(|d| SoftMask::new(&d, context, parent_resources.clone()));
367 }
368 }
369 "BM" => {
370 if let Some(name) = dict.get::<Name>(key.clone()) {
371 if let Some(bm) = convert_blend_mode(name.as_str()) {
372 context.get_mut().graphics_state.blend_mode = bm;
373
374 return Some(());
375 }
376 } else if let Some(arr) = dict.get::<Array<'_>>(key) {
377 for name in arr.iter::<Name>() {
378 if let Some(bm) = convert_blend_mode(name.as_str()) {
379 context.get_mut().graphics_state.blend_mode = bm;
380
381 return Some(());
382 }
383 }
384 }
385
386 warn!("unknown blend mode, defaulting to Normal");
387 context.get_mut().graphics_state.blend_mode = BlendMode::Normal;
388 }
389 "Font" => {
390 let arr = dict.get::<Array<'_>>(FONT)?;
391 let mut iter = arr.iter::<Object<'_>>();
392 let font_dict = iter.next()?.into_dict()?;
393 let size = iter.next()?.into_number()?.as_f32();
394
395 let font = context.resolve_font(&font_dict);
396 context.get_mut().text_state.font_size = size;
397 context.get_mut().text_state.font = font;
398 }
399 "D" => {
400 let arr = dict.get::<Array<'_>>(key)?;
401 let mut iter = arr.iter::<Object<'_>>();
402 let dash_array = iter.next()?.into_array()?;
403 let dash_phase = iter.next()?.into_number()?.as_f32();
404
405 context.get_mut().graphics_state.stroke_props.dash_offset = dash_phase;
406 context.get_mut().graphics_state.stroke_props.dash_array = dash_array
407 .iter::<f32>()
408 .map(|n| if n == 0.0 { 0.01 } else { n })
410 .collect();
411 }
412 "Type" => {}
413 _ => {}
414 }
415
416 Some(())
417}
418
419fn convert_blend_mode(name: &str) -> Option<BlendMode> {
420 let bm = match name {
421 "Normal" => BlendMode::Normal,
422 "Multiply" => BlendMode::Multiply,
423 "Screen" => BlendMode::Screen,
424 "Overlay" => BlendMode::Overlay,
425 "Darken" => BlendMode::Darken,
426 "Lighten" => BlendMode::Lighten,
427 "ColorDodge" => BlendMode::ColorDodge,
428 "ColorBurn" => BlendMode::ColorBurn,
429 "HardLight" => BlendMode::HardLight,
430 "SoftLight" => BlendMode::SoftLight,
431 "Difference" => BlendMode::Difference,
432 "Exclusion" => BlendMode::Exclusion,
433 "Hue" => BlendMode::Hue,
434 "Saturation" => BlendMode::Saturation,
435 "Color" => BlendMode::Color,
436 "Luminosity" => BlendMode::Luminosity,
437 _ => return None,
438 };
439
440 Some(bm)
441}