1use std::cell::RefCell;
15use std::collections::HashMap;
16use std::rc::Rc;
17
18use lopdf::{Dictionary, Document, Object, ObjectId};
19use tiny_skia::{
20 BlendMode, FillRule, FilterQuality, GradientStop, LineCap, LineJoin, Mask, Paint, Path,
21 PathBuilder, Pixmap, PixmapPaint, Point, SpreadMode, Stroke, StrokeDash, Transform,
22};
23
24use super::color::{to_u8, CmykCache, ColorSpace};
25use super::font::{FontCache, LoadedFont};
26use super::function::Function;
27use super::geom::{Box2, Mat};
28use super::image;
29use super::objects::{
30 as_dict, as_stream, deref, get, get_bool, get_dict, get_int, get_name, get_num, name, num,
31 nums, resource,
32};
33
34const MAX_OPS: usize = 2_000_000;
35const MAX_DEPTH: usize = 14;
36
37type Vertex = ((f64, f64), [f64; 3]);
39type Triangle = ([(f64, f64); 3], [f64; 3]);
41type Patch = (Vec<(f64, f64)>, [f64; 3]);
43
44#[derive(Clone)]
45struct TextState {
46 font: Option<Rc<LoadedFont>>,
47 size: f64,
48 char_spacing: f64,
49 word_spacing: f64,
50 hscale: f64,
51 leading: f64,
52 rise: f64,
53 render_mode: i64,
54}
55
56#[derive(Clone)]
57struct GState {
58 ctm: Mat,
59 fill_cs: ColorSpace,
60 stroke_cs: ColorSpace,
61 fill_rgb: Option<[f64; 3]>,
63 stroke_rgb: Option<[f64; 3]>,
64 fill_pattern: Option<Object>,
66 stroke_pattern: Option<Object>,
67 line_width: f64,
68 line_cap: LineCap,
69 line_join: LineJoin,
70 miter_limit: f64,
71 dash: Option<(Vec<f64>, f64)>,
72 fill_alpha: f64,
73 stroke_alpha: f64,
74 blend: BlendMode,
75 clip_box: Box2,
78 clip_mask: Option<Rc<Mask>>,
79 text: TextState,
80 fixed_color: bool,
82 group_alpha: f64,
87 group_blend: BlendMode,
88}
89
90impl GState {
91 fn eff_fill_alpha(&self) -> f64 {
92 self.fill_alpha * self.group_alpha
93 }
94 fn eff_stroke_alpha(&self) -> f64 {
95 self.stroke_alpha * self.group_alpha
96 }
97 fn eff_blend(&self) -> BlendMode {
99 if self.blend == BlendMode::SourceOver {
100 self.group_blend
101 } else {
102 self.blend
103 }
104 }
105}
106
107pub struct Shared {
113 fonts: RefCell<FontCache>,
114 cmyk: RefCell<CmykCache>,
115 images: RefCell<ImageStore>,
116 bitmap_hint: f64,
123}
124
125impl Default for Shared {
126 fn default() -> Self {
127 Shared {
128 fonts: RefCell::default(),
129 cmyk: RefCell::default(),
130 images: RefCell::default(),
131 bitmap_hint: 1.0,
132 }
133 }
134}
135
136impl Shared {
137 pub fn with_bitmap_hint(bitmap_hint: f64) -> Shared {
140 Shared {
141 bitmap_hint,
142 ..Shared::default()
143 }
144 }
145
146 pub fn bitmap_hint(&self) -> f64 {
148 self.bitmap_hint
149 }
150}
151
152#[derive(Default)]
155struct ImageStore {
156 by_id: HashMap<(ObjectId, u32), Rc<image::LoadedImage>>,
157 bytes: usize,
158}
159
160const IMAGE_BUDGET: usize = 256 << 20;
164
165impl Shared {
166 fn font(&self, doc: &Document, obj: &Object) -> Option<Rc<LoadedFont>> {
167 self.fonts.borrow_mut().get(doc, obj)
168 }
169
170 fn load_image(
173 &self,
174 doc: &Document,
175 stream: &lopdf::Stream,
176 id: Option<ObjectId>,
177 res: Option<&Dictionary>,
178 reduction_shift: u32,
179 ) -> Result<Rc<image::LoadedImage>, String> {
180 if let Some(id) = id {
181 if let Some(img) = self.images.borrow().by_id.get(&(id, reduction_shift)) {
182 return Ok(img.clone());
183 }
184 }
185 let img = Rc::new(image::load(doc, stream, res, reduction_shift)?);
186 if let Some(id) = id {
187 let mut store = self.images.borrow_mut();
188 let bytes = img.bytes();
189 if store.bytes + bytes > IMAGE_BUDGET {
190 store.by_id.clear();
191 store.bytes = 0;
192 }
193 if bytes <= IMAGE_BUDGET {
194 store.bytes += bytes;
195 store.by_id.insert((id, reduction_shift), img.clone());
196 }
197 }
198 Ok(img)
199 }
200}
201
202pub struct Interp<'a> {
203 doc: &'a Document,
204 canvas: Pixmap,
205 device: Box2,
207 shared: Rc<Shared>,
209 images: HashMap<(ObjectId, u32, u32, [u8; 3]), Rc<image::DecodedImage>>,
212 rect_masks: HashMap<[i32; 4], Rc<Mask>>,
214 shape_masks: HashMap<(u64, usize, bool), Rc<Mask>>,
217 ops: usize,
218 warned_no_face: bool,
220 type3_depth: usize,
222 base: Mat,
224 bitmap_hint: f64,
228}
229
230struct PathState {
232 builder: PathBuilder,
233 current: Option<(f64, f64)>,
235 start: Option<(f64, f64)>,
236 rects: Vec<[f64; 4]>,
238 only_rects: bool,
239 pending_clip: Option<FillRule>,
240 empty: bool,
241}
242
243impl PathState {
244 fn new() -> PathState {
245 PathState {
246 builder: PathBuilder::new(),
247 current: None,
248 start: None,
249 rects: Vec::new(),
250 only_rects: true,
251 pending_clip: None,
252 empty: true,
253 }
254 }
255}
256
257impl<'a> Interp<'a> {
258 pub fn new(
260 doc: &'a Document,
261 width: u32,
262 height: u32,
263 shared: Rc<Shared>,
264 bitmap_hint: f64,
265 ) -> Option<Interp<'a>> {
266 let mut canvas = Pixmap::new(width, height)?;
267 canvas.fill(tiny_skia::Color::WHITE);
268 Some(Interp {
269 doc,
270 canvas,
271 device: Box2::new(0.0, 0.0, f64::from(width), f64::from(height)),
272 shared,
273 bitmap_hint,
274 images: HashMap::new(),
275 rect_masks: HashMap::new(),
276 shape_masks: HashMap::new(),
277 ops: 0,
278 warned_no_face: false,
279 type3_depth: 0,
280 base: Mat::IDENTITY,
281 })
282 }
283
284 pub fn into_canvas(self) -> Pixmap {
285 self.canvas
286 }
287
288 fn initial_state(&self, ctm: Mat) -> GState {
289 GState {
290 ctm,
291 fill_cs: ColorSpace::DeviceGray,
292 stroke_cs: ColorSpace::DeviceGray,
293 fill_rgb: Some([0.0, 0.0, 0.0]),
294 stroke_rgb: Some([0.0, 0.0, 0.0]),
295 fill_pattern: None,
296 stroke_pattern: None,
297 line_width: 1.0,
298 line_cap: LineCap::Butt,
299 line_join: LineJoin::Miter,
300 miter_limit: 10.0,
301 dash: None,
302 fill_alpha: 1.0,
303 stroke_alpha: 1.0,
304 blend: BlendMode::SourceOver,
305 clip_box: self.device,
306 clip_mask: None,
307 text: TextState {
308 font: None,
309 size: 0.0,
310 char_spacing: 0.0,
311 word_spacing: 0.0,
312 hscale: 1.0,
313 leading: 0.0,
314 rise: 0.0,
315 render_mode: 0,
316 },
317 fixed_color: false,
318 group_alpha: 1.0,
319 group_blend: BlendMode::SourceOver,
320 }
321 }
322
323 pub fn run_page(&mut self, content: &[u8], resources: Option<&Dictionary>, base: Mat) {
325 self.base = base;
326 let st = self.initial_state(base);
327 self.run(content, resources, st, 0);
328 }
329
330 pub fn run_widgets(&mut self, page: &Dictionary, base: Mat) {
333 let doc = self.doc;
334 let Some(Object::Array(annots)) = get(doc, page, b"Annots") else {
335 return;
336 };
337 for a in annots {
338 let Some(ad) = as_dict(doc, a) else { continue };
339 if get_name(doc, ad, b"Subtype") != Some(b"Widget") {
340 continue;
341 }
342 let flags = get_int(doc, ad, b"F").unwrap_or(0);
343 if flags & 2 != 0 || flags & 32 != 0 {
344 continue;
345 }
346 let Some(rect) = get(doc, ad, b"Rect")
347 .and_then(|o| nums(doc, o))
348 .filter(|r| r.len() == 4)
349 else {
350 continue;
351 };
352 let Some(ap) = get_dict(doc, ad, b"AP") else {
353 continue;
354 };
355 let Some(n) = get(doc, ap, b"N") else {
356 continue;
357 };
358 let stream = match n {
359 Object::Stream(s) => Some(s),
360 Object::Dictionary(states) => {
361 let as_name = get_name(doc, ad, b"AS");
362 match as_name {
363 Some(st) => states.get(st).ok().and_then(|o| as_stream(doc, o)),
364 None if states.len() == 1 => {
365 states.iter().next().and_then(|(_, o)| as_stream(doc, o))
366 }
367 None => None,
368 }
369 }
370 _ => None,
371 };
372 let Some(stream) = stream else { continue };
373 let bbox = get(doc, &stream.dict, b"BBox")
375 .and_then(|o| nums(doc, o))
376 .filter(|b| b.len() == 4);
377 let matrix = get(doc, &stream.dict, b"Matrix")
378 .and_then(|o| nums(doc, o))
379 .and_then(|v| Mat::from_slice(&v))
380 .unwrap_or(Mat::IDENTITY);
381 let rx0 = rect[0].min(rect[2]);
382 let ry0 = rect[1].min(rect[3]);
383 let rx1 = rect[0].max(rect[2]);
384 let ry1 = rect[1].max(rect[3]);
385 let a = match bbox {
386 Some(b) => {
387 let tb = Box2::new(b[0], b[1], b[2], b[3]).transformed(matrix);
388 let sx = if tb.width() > 1e-9 {
389 (rx1 - rx0) / tb.width()
390 } else {
391 1.0
392 };
393 let sy = if tb.height() > 1e-9 {
394 (ry1 - ry0) / tb.height()
395 } else {
396 1.0
397 };
398 Mat::new(sx, 0.0, 0.0, sy, rx0 - tb.x0 * sx, ry0 - tb.y0 * sy)
399 }
400 None => Mat::IDENTITY,
401 };
402 let mut st = self.initial_state(a.then(base));
403 st.clip_box = self.device;
404 self.draw_form(stream, &st, None, 1);
405 }
406 }
407
408 fn run(&mut self, content: &[u8], resources: Option<&Dictionary>, init: GState, depth: usize) {
409 if depth > MAX_DEPTH {
410 return;
411 }
412 let prepared = super::prepass::prepare(content, self.doc, resources);
415 let Ok(ops) = lopdf::content::Content::decode(&prepared.content) else {
416 return;
417 };
418 let inline = &prepared.inline;
419 let doc = self.doc;
420 let mut stack: Vec<GState> = Vec::new();
421 let mut st = init.clone();
422 let mut path = PathState::new();
423 let mut tm = Mat::IDENTITY;
425 let mut tlm = Mat::IDENTITY;
426 let mut compat = 0usize;
427 for op in &ops.operations {
428 self.ops += 1;
429 if self.ops > MAX_OPS {
430 return;
431 }
432 let args = &op.operands;
433 let n = |i: usize| args.get(i).and_then(num).unwrap_or(0.0);
434 match op.operator.as_str() {
435 "q" => {
437 stack.push(st.clone());
438 if stack.len() > 64 {
439 stack.remove(0);
440 }
441 }
442 "Q" => {
443 if let Some(s) = stack.pop() {
444 st = s;
445 }
446 }
447 "cm" => {
448 if args.len() >= 6 {
449 let v: Vec<f64> = (0..6).map(n).collect();
450 if let Some(m) = Mat::from_slice(&v) {
451 st.ctm = m.then(st.ctm);
452 }
453 }
454 }
455 "w" => st.line_width = n(0).abs(),
456 "J" => {
457 st.line_cap = match n(0) as i64 {
458 1 => LineCap::Round,
459 2 => LineCap::Square,
460 _ => LineCap::Butt,
461 }
462 }
463 "j" => {
464 st.line_join = match n(0) as i64 {
465 1 => LineJoin::Round,
466 2 => LineJoin::Bevel,
467 _ => LineJoin::Miter,
468 }
469 }
470 "M" => st.miter_limit = n(0).max(1.0),
471 "d" => {
472 let arr: Vec<f64> = args
473 .first()
474 .and_then(|o| nums(doc, o))
475 .unwrap_or_default()
476 .into_iter()
477 .filter(|v| v.is_finite() && *v >= 0.0)
478 .collect();
479 st.dash = if arr.is_empty() || arr.iter().all(|v| *v <= 0.0) {
480 None
481 } else {
482 Some((arr, n(1)))
483 };
484 }
485 "ri" | "i" => {}
486 "gs" => {
487 if let Some(gs) = args
488 .first()
489 .and_then(name)
490 .and_then(|nm| resource(doc, resources, b"ExtGState", nm))
491 .and_then(|o| as_dict(doc, o))
492 {
493 self.apply_extgstate(&mut st, gs, resources);
494 }
495 }
496 "m" => {
498 if args.len() >= 2 {
499 let (x, y) = (n(0), n(1));
500 path.builder.move_to(x as f32, y as f32);
501 path.current = Some((x, y));
502 path.start = Some((x, y));
503 path.only_rects = false;
504 path.empty = false;
505 }
506 }
507 "l" => {
508 if args.len() >= 2 {
509 let (x, y) = (n(0), n(1));
510 if path.current.is_none() {
511 path.builder.move_to(x as f32, y as f32);
512 path.start = Some((x, y));
513 } else {
514 path.builder.line_to(x as f32, y as f32);
515 }
516 path.current = Some((x, y));
517 path.only_rects = false;
518 path.empty = false;
519 }
520 }
521 "c" | "v" | "y" => {
522 let cur = path.current.unwrap_or((n(0), n(1)));
523 let (x1, y1, x2, y2, x3, y3) = match op.operator.as_str() {
524 "c" if args.len() >= 6 => (n(0), n(1), n(2), n(3), n(4), n(5)),
525 "v" if args.len() >= 4 => (cur.0, cur.1, n(0), n(1), n(2), n(3)),
526 "y" if args.len() >= 4 => (n(0), n(1), n(2), n(3), n(2), n(3)),
527 _ => continue,
528 };
529 if path.current.is_none() {
530 path.builder.move_to(cur.0 as f32, cur.1 as f32);
531 path.start = Some(cur);
532 }
533 path.builder.cubic_to(
534 x1 as f32, y1 as f32, x2 as f32, y2 as f32, x3 as f32, y3 as f32,
535 );
536 path.current = Some((x3, y3));
537 path.only_rects = false;
538 path.empty = false;
539 }
540 "h" => {
541 if path.current.is_some() {
542 path.builder.close();
543 path.current = path.start;
544 }
545 }
546 "re" => {
547 if args.len() >= 4 {
548 let (x, y, w, h) = (n(0), n(1), n(2), n(3));
549 path.builder.move_to(x as f32, y as f32);
550 path.builder.line_to((x + w) as f32, y as f32);
551 path.builder.line_to((x + w) as f32, (y + h) as f32);
552 path.builder.line_to(x as f32, (y + h) as f32);
553 path.builder.close();
554 path.current = Some((x, y));
555 path.start = Some((x, y));
556 path.rects.push([x, y, w, h]);
557 path.empty = false;
558 }
559 }
560 "S" | "s" | "f" | "F" | "f*" | "B" | "B*" | "b" | "b*" | "n" => {
562 let o = op.operator.as_str();
563 if matches!(o, "s" | "b" | "b*") && path.current.is_some() {
564 path.builder.close();
565 }
566 let rule = if o.ends_with('*') {
567 FillRule::EvenOdd
568 } else {
569 FillRule::Winding
570 };
571 let fill = matches!(o, "f" | "F" | "f*" | "B" | "B*" | "b" | "b*");
572 let stroke = matches!(o, "S" | "s" | "B" | "B*" | "b" | "b*");
573 let mut finished = std::mem::replace(&mut path, PathState::new());
574 let user_path =
575 std::mem::replace(&mut finished.builder, PathBuilder::new()).finish();
576 if let Some(up) = &user_path {
577 if let Some(dev) = up.clone().transform(st.ctm.to_ts()) {
578 if fill {
579 self.fill_device_path(&dev, rule, &st, resources, false);
580 }
581 if stroke {
582 self.stroke_device_path(&dev, up, &st, resources);
583 }
584 }
585 }
586 if let Some(clip_rule) = finished.pending_clip {
587 crate::timing::timed("render.apply_clip", || {
588 self.apply_clip(&mut st, user_path.as_ref(), &finished, clip_rule)
589 });
590 }
591 }
592 "W" => path.pending_clip = Some(FillRule::Winding),
593 "W*" => path.pending_clip = Some(FillRule::EvenOdd),
594 "CS" | "cs" => {
596 if st.fixed_color {
597 continue;
598 }
599 let cs = args
600 .first()
601 .and_then(|o| ColorSpace::parse(doc, o, resources))
602 .unwrap_or(ColorSpace::DeviceGray);
603 let initial = cs.initial();
604 let rgb = match cs {
605 ColorSpace::Pattern(_) => None,
606 _ => cs.to_rgb(&initial),
607 };
608 if op.operator == "cs" {
609 st.fill_cs = cs;
610 st.fill_rgb = rgb;
611 st.fill_pattern = None;
612 } else {
613 st.stroke_cs = cs;
614 st.stroke_rgb = rgb;
615 st.stroke_pattern = None;
616 }
617 }
618 "SC" | "SCN" | "sc" | "scn" => {
619 if st.fixed_color {
620 continue;
621 }
622 let is_fill = op.operator.starts_with('s');
623 let cs = if is_fill { &st.fill_cs } else { &st.stroke_cs };
624 let (rgb, pattern) = match cs {
625 ColorSpace::Pattern(base) => {
626 let pat_name = args.last().and_then(name);
627 let pat = pat_name
628 .and_then(|nm| resource(doc, resources, b"Pattern", nm))
629 .cloned();
630 let comps: Vec<f64> = args.iter().filter_map(num).collect();
632 let rgb = match (base, comps.is_empty()) {
633 (Some(b), false) => b.to_rgb(&comps),
634 _ => Some([0.5, 0.5, 0.5]),
635 };
636 (rgb, pat)
637 }
638 cs => {
639 let comps: Vec<f64> = args.iter().filter_map(num).collect();
640 if comps.is_empty() {
641 continue;
642 }
643 (cs.to_rgb(&comps), None)
644 }
645 };
646 if is_fill {
647 st.fill_rgb = rgb;
648 st.fill_pattern = pattern;
649 } else {
650 st.stroke_rgb = rgb;
651 st.stroke_pattern = pattern;
652 }
653 }
654 "g" | "G" | "rg" | "RG" | "k" | "K" => {
655 if st.fixed_color {
656 continue;
657 }
658 let cs = match op.operator.as_str() {
659 "g" | "G" => ColorSpace::DeviceGray,
660 "rg" | "RG" => ColorSpace::DeviceRGB,
661 _ => ColorSpace::DeviceCMYK,
662 };
663 let comps: Vec<f64> = args.iter().filter_map(num).collect();
664 let rgb = cs.to_rgb(&comps);
665 if op
666 .operator
667 .chars()
668 .next()
669 .is_some_and(|c| c.is_ascii_lowercase())
670 {
671 st.fill_cs = cs;
672 st.fill_rgb = rgb;
673 st.fill_pattern = None;
674 } else {
675 st.stroke_cs = cs;
676 st.stroke_rgb = rgb;
677 st.stroke_pattern = None;
678 }
679 }
680 "BT" => {
682 tm = Mat::IDENTITY;
683 tlm = Mat::IDENTITY;
684 }
685 "ET" => {}
686 "Tc" => st.text.char_spacing = n(0),
687 "Tw" => st.text.word_spacing = n(0),
688 "Tz" => st.text.hscale = n(0) / 100.0,
689 "TL" => st.text.leading = n(0),
690 "Ts" => st.text.rise = n(0),
691 "Tr" => st.text.render_mode = n(0) as i64,
692 "Tf" => {
693 st.text.size = n(1);
694 st.text.font = args
695 .first()
696 .and_then(name)
697 .and_then(|nm| resource(doc, resources, b"Font", nm))
698 .and_then(|o| self.shared.font(doc, o));
699 if st.text.font.is_none() {
700 let mut d = Dictionary::new();
703 d.set("BaseFont", Object::Name(b"Helvetica".to_vec()));
704 st.text.font = self.shared.font(doc, &Object::Dictionary(d));
705 }
706 }
707 "Td" => {
708 tlm = Mat::translate(n(0), n(1)).then(tlm);
709 tm = tlm;
710 }
711 "TD" => {
712 st.text.leading = -n(1);
713 tlm = Mat::translate(n(0), n(1)).then(tlm);
714 tm = tlm;
715 }
716 "Tm" => {
717 if args.len() >= 6 {
718 let v: Vec<f64> = (0..6).map(n).collect();
719 if let Some(m) = Mat::from_slice(&v) {
720 tlm = m;
721 tm = tlm;
722 }
723 }
724 }
725 "T*" => {
726 tlm = Mat::translate(0.0, -st.text.leading).then(tlm);
727 tm = tlm;
728 }
729 "Tj" | "'" | "\"" => {
730 if op.operator != "Tj" {
731 if op.operator == "\"" && args.len() >= 3 {
732 st.text.word_spacing = n(0);
733 st.text.char_spacing = n(1);
734 }
735 tlm = Mat::translate(0.0, -st.text.leading).then(tlm);
736 tm = tlm;
737 }
738 if let Some(Object::String(s, _)) = args.last() {
739 self.show_text(s, &mut tm, &st, resources, depth);
740 }
741 }
742 "TJ" => {
743 if let Some(Object::Array(items)) = args.first() {
744 for it in items {
745 match it {
746 Object::String(s, _) => {
747 self.show_text(s, &mut tm, &st, resources, depth)
748 }
749 o => {
750 if let Some(adj) = num(o) {
751 let tx = -adj / 1000.0 * st.text.size * st.text.hscale;
752 let vertical =
753 st.text.font.as_ref().is_some_and(|f| f.vertical);
754 tm = if vertical {
755 Mat::translate(0.0, -adj / 1000.0 * st.text.size)
756 .then(tm)
757 } else {
758 Mat::translate(tx, 0.0).then(tm)
759 };
760 }
761 }
762 }
763 }
764 }
765 }
766 "dZero" => {}
770 "dOne" => st.fixed_color = true,
771 "Do" => {
773 if let Some(xobj) = args
774 .first()
775 .and_then(name)
776 .and_then(|nm| resource(doc, resources, b"XObject", nm))
777 {
778 let id = match xobj {
779 Object::Reference(id) => Some(*id),
780 _ => None,
781 };
782 if let Some(stream) = as_stream(doc, xobj) {
783 match get_name(doc, &stream.dict, b"Subtype") {
784 Some(b"Image") => crate::timing::timed("render.image", || {
785 self.draw_image(stream, id, &st, resources)
786 }),
787 Some(b"Form") => self.draw_form(stream, &st, resources, depth + 1),
788 Some(b"PS") => {}
789 _ => {
790 if stream.dict.has(b"BBox") {
791 self.draw_form(stream, &st, resources, depth + 1);
792 } else if stream.dict.has(b"Width") {
793 self.draw_image(stream, id, &st, resources);
794 }
795 }
796 }
797 }
798 }
799 }
800 "BIX" => {
801 if let Some(s) = args
803 .first()
804 .and_then(super::prepass::inline_index)
805 .and_then(|n| inline.get(n))
806 {
807 self.draw_image(s, None, &st, resources);
808 }
809 }
810 "sh" => {
811 if let Some(sh) = args
812 .first()
813 .and_then(name)
814 .and_then(|nm| resource(doc, resources, b"Shading", nm))
815 {
816 let sh_obj = deref(doc, sh).clone();
817 crate::timing::timed("render.sh", || {
818 self.paint_shading(
819 &sh_obj,
820 st.ctm,
821 &st,
822 None,
823 st.eff_fill_alpha(),
824 true,
825 )
826 });
827 }
828 }
829 "BX" => compat += 1,
830 "EX" => compat = compat.saturating_sub(1),
831 "BMC" | "BDC" | "EMC" | "MP" | "DP" => {}
832 _ => {}
833 }
834 }
835 let _ = compat;
836 }
837
838 fn apply_extgstate(
843 &mut self,
844 st: &mut GState,
845 gs: &Dictionary,
846 resources: Option<&Dictionary>,
847 ) {
848 let doc = self.doc;
849 for (k, v) in gs.iter() {
850 let v = deref(doc, v);
851 match k.as_slice() {
852 b"LW" => {
853 if let Some(w) = num(v) {
854 st.line_width = w.abs();
855 }
856 }
857 b"LC" => {
858 st.line_cap = match num(v).unwrap_or(0.0) as i64 {
859 1 => LineCap::Round,
860 2 => LineCap::Square,
861 _ => LineCap::Butt,
862 }
863 }
864 b"LJ" => {
865 st.line_join = match num(v).unwrap_or(0.0) as i64 {
866 1 => LineJoin::Round,
867 2 => LineJoin::Bevel,
868 _ => LineJoin::Miter,
869 }
870 }
871 b"ML" => {
872 if let Some(m) = num(v) {
873 st.miter_limit = m.max(1.0);
874 }
875 }
876 b"D" => {
877 if let Object::Array(a) = v {
878 let arr: Vec<f64> =
879 a.first().and_then(|o| nums(doc, o)).unwrap_or_default();
880 let phase = a.get(1).and_then(|o| num(deref(doc, o))).unwrap_or(0.0);
881 st.dash = if arr.is_empty() || arr.iter().all(|x| *x <= 0.0) {
882 None
883 } else {
884 Some((arr, phase))
885 };
886 }
887 }
888 b"CA" => {
889 if let Some(a) = num(v) {
890 st.stroke_alpha = a.clamp(0.0, 1.0);
891 }
892 }
893 b"ca" => {
894 if let Some(a) = num(v) {
895 st.fill_alpha = a.clamp(0.0, 1.0);
896 }
897 }
898 b"BM" => {
899 let nm = match v {
900 Object::Name(n) => Some(n.as_slice()),
901 Object::Array(a) => a.first().and_then(name),
902 _ => None,
903 };
904 st.blend = match nm {
905 Some(b"Multiply") => BlendMode::Multiply,
906 Some(b"Screen") => BlendMode::Screen,
907 Some(b"Overlay") => BlendMode::Overlay,
908 Some(b"Darken") => BlendMode::Darken,
909 Some(b"Lighten") => BlendMode::Lighten,
910 Some(b"ColorDodge") => BlendMode::ColorDodge,
911 Some(b"ColorBurn") => BlendMode::ColorBurn,
912 Some(b"HardLight") => BlendMode::HardLight,
913 Some(b"SoftLight") => BlendMode::SoftLight,
914 Some(b"Difference") => BlendMode::Difference,
915 Some(b"Exclusion") => BlendMode::Exclusion,
916 Some(b"Hue") => BlendMode::Hue,
917 Some(b"Saturation") => BlendMode::Saturation,
918 Some(b"Color") => BlendMode::Color,
919 Some(b"Luminosity") => BlendMode::Luminosity,
920 _ => BlendMode::SourceOver,
921 };
922 }
923 b"Font" => {
924 if let Object::Array(a) = v {
925 if let (Some(f), Some(sz)) =
926 (a.first(), a.get(1).and_then(|o| num(deref(doc, o))))
927 {
928 if let Some(font) = self.shared.font(doc, f) {
929 st.text.font = Some(font);
930 st.text.size = sz;
931 }
932 }
933 }
934 }
935 _ => {}
936 }
937 }
938 let _ = resources;
939 }
940
941 fn apply_clip(
947 &mut self,
948 st: &mut GState,
949 user_path: Option<&Path>,
950 ps: &PathState,
951 rule: FillRule,
952 ) {
953 let Some(up) = user_path else {
955 st.clip_box = Box2::new(0.0, 0.0, 0.0, 0.0);
956 return;
957 };
958 if ps.only_rects && ps.rects.len() == 1 && st.ctm.b.abs() < 1e-9 && st.ctm.c.abs() < 1e-9 {
960 let r = ps.rects[0];
961 let b = Box2::new(r[0], r[1], r[0] + r[2], r[1] + r[3]).transformed(st.ctm);
962 st.clip_box = st.clip_box.intersect(b);
963 if st.clip_box.is_empty() {
964 st.clip_box = Box2::new(0.0, 0.0, 0.0, 0.0);
965 }
966 return;
967 }
968 let Some(dev) = up.clone().transform(st.ctm.to_ts()) else {
969 return;
970 };
971 let bb = dev.bounds();
972 let pb = Box2::new(
973 f64::from(bb.left()),
974 f64::from(bb.top()),
975 f64::from(bb.right()),
976 f64::from(bb.bottom()),
977 );
978 if device_path_is_rect(&dev) {
981 st.clip_box = st.clip_box.intersect(pb);
982 if st.clip_box.is_empty() {
983 st.clip_box = Box2::new(0.0, 0.0, 0.0, 0.0);
984 }
985 return;
986 }
987 let new_box = st.clip_box.intersect(pb);
988 if new_box.is_empty() {
989 st.clip_box = Box2::new(0.0, 0.0, 0.0, 0.0);
990 st.clip_mask = None;
991 return;
992 }
993 let parent_ptr = st
994 .clip_mask
995 .as_ref()
996 .map(|m| Rc::as_ptr(m) as usize)
997 .unwrap_or(0);
998 let key = (
999 path_hash(&dev, st.clip_box),
1000 parent_ptr,
1001 rule == FillRule::EvenOdd,
1002 );
1003 if let Some(m) = self.shape_masks.get(&key) {
1004 st.clip_mask = Some(m.clone());
1005 st.clip_box = new_box;
1006 return;
1007 }
1008 let mask = crate::timing::timed("render.clip_mask", || {
1009 shape_mask(
1010 self.canvas.width(),
1011 self.canvas.height(),
1012 &dev,
1013 rule,
1014 new_box,
1015 st.clip_mask.as_deref(),
1016 )
1017 });
1018 let Some(mask) = mask else { return };
1019 let mask = Rc::new(mask);
1020 if self.shape_masks.len() > 256 {
1021 self.shape_masks.clear();
1022 }
1023 self.shape_masks.insert(key, mask.clone());
1024 st.clip_mask = Some(mask);
1025 st.clip_box = new_box;
1026 }
1027
1028 fn clip_mask_for(&mut self, st: &GState, bounds: Option<tiny_skia::Rect>) -> Option<Rc<Mask>> {
1033 if let Some(m) = &st.clip_mask {
1034 return Some(m.clone());
1035 }
1036 let cb = st.clip_box;
1037 if cb.x0 <= 0.0 && cb.y0 <= 0.0 && cb.x1 >= self.device.x1 && cb.y1 >= self.device.y1 {
1038 return None;
1039 }
1040 if let Some(b) = bounds {
1041 if f64::from(b.left()) >= cb.x0 - 1e-6
1042 && f64::from(b.top()) >= cb.y0 - 1e-6
1043 && f64::from(b.right()) <= cb.x1 + 1e-6
1044 && f64::from(b.bottom()) <= cb.y1 + 1e-6
1045 {
1046 return None;
1047 }
1048 }
1049 let key = [
1050 (cb.x0 * 4.0).round() as i32,
1051 (cb.y0 * 4.0).round() as i32,
1052 (cb.x1 * 4.0).round() as i32,
1053 (cb.y1 * 4.0).round() as i32,
1054 ];
1055 if let Some(m) = self.rect_masks.get(&key) {
1056 return Some(m.clone());
1057 }
1058 let mut m = Mask::new(self.canvas.width(), self.canvas.height())?;
1059 let r = tiny_skia::Rect::from_ltrb(cb.x0 as f32, cb.y0 as f32, cb.x1 as f32, cb.y1 as f32)?;
1060 m.fill_path(
1061 &PathBuilder::from_rect(r),
1062 FillRule::Winding,
1063 true,
1064 Transform::identity(),
1065 );
1066 let m = Rc::new(m);
1067 if self.rect_masks.len() > 64 {
1068 self.rect_masks.clear();
1069 }
1070 self.rect_masks.insert(key, m.clone());
1071 Some(m)
1072 }
1073
1074 fn visible(&self, st: &GState, dev_bounds: tiny_skia::Rect) -> bool {
1075 let b = Box2::new(
1076 f64::from(dev_bounds.left()),
1077 f64::from(dev_bounds.top()),
1078 f64::from(dev_bounds.right()),
1079 f64::from(dev_bounds.bottom()),
1080 );
1081 !st.clip_box.intersect(b).is_empty()
1082 || (b.width() == 0.0 || b.height() == 0.0) && !st.clip_box.is_empty()
1083 }
1084
1085 fn paint_for(rgb: [f64; 3], alpha: f64, blend: BlendMode) -> Paint<'static> {
1090 let mut p = Paint::default();
1091 p.set_color_rgba8(to_u8(rgb[0]), to_u8(rgb[1]), to_u8(rgb[2]), to_u8(alpha));
1092 p.anti_alias = true;
1093 p.blend_mode = blend;
1094 p
1095 }
1096
1097 fn fill_device_path(
1099 &mut self,
1100 dev: &Path,
1101 rule: FillRule,
1102 st: &GState,
1103 resources: Option<&Dictionary>,
1104 is_text: bool,
1105 ) {
1106 if st.eff_fill_alpha() <= 1.0 / 512.0 || !self.visible(st, dev.bounds()) {
1107 return;
1108 }
1109 if let Some(pat) = st.fill_pattern.clone() {
1110 crate::timing::timed("render.pattern_fill", || {
1111 self.fill_with_pattern(dev, rule, &pat, st, resources)
1112 });
1113 return;
1114 }
1115 let Some(rgb) = st.fill_rgb else { return };
1116 let paint = Self::paint_for(rgb, st.eff_fill_alpha(), st.eff_blend());
1117 let mask = self.clip_mask_for(st, Some(dev.bounds()));
1118 let stage = if is_text {
1119 "render.glyph_fill"
1120 } else {
1121 "render.path_fill"
1122 };
1123 crate::timing::timed(stage, || {
1124 self.canvas
1125 .fill_path(dev, &paint, rule, Transform::identity(), mask.as_deref())
1126 });
1127 }
1128
1129 fn stroke_of(&self, st: &GState) -> Stroke {
1130 let scale = st.ctm.mean_scale();
1131 let width = (st.line_width * scale).max(1.0);
1132 let dash = st.dash.as_ref().and_then(|(arr, phase)| {
1133 let mut a: Vec<f32> = arr.iter().map(|v| (v * scale) as f32).collect();
1134 if a.len() % 2 == 1 {
1135 let c = a.clone();
1136 a.extend(c);
1137 }
1138 if a.iter().all(|v| *v <= 0.0) || a.iter().any(|v| !v.is_finite()) {
1139 return None;
1140 }
1141 StrokeDash::new(a, (phase * scale) as f32)
1142 });
1143 Stroke {
1144 width: width as f32,
1145 miter_limit: st.miter_limit as f32,
1146 line_cap: st.line_cap,
1147 line_join: st.line_join,
1148 dash,
1149 }
1150 }
1151
1152 fn stroke_device_path(
1153 &mut self,
1154 dev: &Path,
1155 _user: &Path,
1156 st: &GState,
1157 resources: Option<&Dictionary>,
1158 ) {
1159 if st.eff_stroke_alpha() <= 1.0 / 512.0 {
1160 return;
1161 }
1162 let stroke = self.stroke_of(st);
1163 let bb = dev.bounds();
1164 let grow = stroke.width;
1165 let Some(bb) = tiny_skia::Rect::from_ltrb(
1166 bb.left() - grow,
1167 bb.top() - grow,
1168 bb.right() + grow,
1169 bb.bottom() + grow,
1170 ) else {
1171 return;
1172 };
1173 if !self.visible(st, bb) {
1174 return;
1175 }
1176 if let Some(pat) = st.stroke_pattern.clone() {
1177 if let Some(outline) = dev.stroke(&stroke, 1.0) {
1179 self.fill_with_pattern(&outline, FillRule::Winding, &pat, st, resources);
1180 }
1181 return;
1182 }
1183 let Some(rgb) = st.stroke_rgb else { return };
1184 let paint = Self::paint_for(rgb, st.eff_stroke_alpha(), st.eff_blend());
1185 let mask = self.clip_mask_for(st, Some(bb));
1186 crate::timing::timed("render.stroke", || {
1187 self.canvas
1188 .stroke_path(dev, &paint, &stroke, Transform::identity(), mask.as_deref())
1189 });
1190 }
1191
1192 fn show_text(
1197 &mut self,
1198 bytes: &[u8],
1199 tm: &mut Mat,
1200 st: &GState,
1201 resources: Option<&Dictionary>,
1202 depth: usize,
1203 ) {
1204 let Some(font) = st.text.font.clone() else {
1205 return;
1206 };
1207 let ts = &st.text;
1208 let mode = ts.render_mode;
1209 let invisible = mode == 3 || mode == 7;
1210 let do_fill = matches!(mode, 0 | 2 | 4 | 6);
1211 let do_stroke = matches!(mode, 1 | 2 | 5 | 6);
1212 for (code, cid, single_byte) in font.decode(bytes) {
1213 let w0 = font.advance(code, cid);
1214 let trm = Mat::new(ts.size * ts.hscale, 0.0, 0.0, ts.size, 0.0, ts.rise)
1215 .then(*tm)
1216 .then(st.ctm);
1217 if !invisible {
1218 if let Some(t3) = &font.type3 {
1219 self.draw_type3_glyph(&font, t3, code, trm, st, resources, depth);
1220 } else if let Some(glyph) = font.glyph(code, cid) {
1221 if let Some(dev) = (*glyph).clone().transform(trm.to_ts()) {
1222 if do_fill {
1223 self.fill_device_path(&dev, FillRule::Winding, st, resources, true);
1224 }
1225 if do_stroke {
1226 let mut sst = st.clone();
1227 sst.line_width = st.line_width;
1229 self.stroke_device_path(&dev, &dev, &sst, resources);
1230 }
1231 }
1232 } else if !font.has_program() && (do_fill || do_stroke) {
1233 self.draw_missing_glyph_box(w0, trm, st);
1234 }
1235 }
1236 let mut adv = if font.vertical {
1237 0.0
1239 } else {
1240 w0 * ts.size + ts.char_spacing
1241 };
1242 if single_byte && code == 32 {
1243 adv += ts.word_spacing;
1244 }
1245 if font.vertical {
1246 let ty = -(ts.size
1247 + ts.char_spacing
1248 + if single_byte && code == 32 {
1249 ts.word_spacing
1250 } else {
1251 0.0
1252 });
1253 *tm = Mat::translate(0.0, ty).then(*tm);
1254 } else {
1255 *tm = Mat::translate(adv * ts.hscale, 0.0).then(*tm);
1256 }
1257 }
1258 }
1259
1260 fn draw_missing_glyph_box(&mut self, w0: f64, trm: Mat, st: &GState) {
1263 if !self.warned_no_face {
1264 self.warned_no_face = true;
1265 eprintln!(
1266 "docling-pdf: no fallback font face on this host — text without an embedded font is drawn as boxes \
1267 (install Liberation/DejaVu fonts, or point DOCLING_RS_FONT_DIRS at a font directory)"
1268 );
1269 }
1270 let w = if w0 > 0.0 { w0 } else { 0.5 };
1271 let pts = [
1272 trm.apply(0.0, -0.2),
1273 trm.apply(w, -0.2),
1274 trm.apply(w, 0.8),
1275 trm.apply(0.0, 0.8),
1276 ];
1277 let mut pb = PathBuilder::new();
1278 pb.move_to(pts[0].0 as f32, pts[0].1 as f32);
1279 for p in &pts[1..] {
1280 pb.line_to(p.0 as f32, p.1 as f32);
1281 }
1282 pb.close();
1283 let Some(path) = pb.finish() else { return };
1284 let mut paint = Paint::default();
1285 paint.set_color_rgba8(0x10, 0x70, 0xC0, 0xFF);
1286 paint.anti_alias = true;
1287 let stroke = Stroke {
1288 width: 0.5,
1289 ..Stroke::default()
1290 };
1291 let mask = self.clip_mask_for(st, Some(path.bounds()));
1292 self.canvas.stroke_path(
1293 &path,
1294 &paint,
1295 &stroke,
1296 Transform::identity(),
1297 mask.as_deref(),
1298 );
1299 }
1300
1301 #[allow(clippy::too_many_arguments)]
1302 fn draw_type3_glyph(
1303 &mut self,
1304 font: &LoadedFont,
1305 t3: &super::font::Type3,
1306 code: u32,
1307 trm: Mat,
1308 st: &GState,
1309 resources: Option<&Dictionary>,
1310 depth: usize,
1311 ) {
1312 if self.type3_depth > 4 || depth > MAX_DEPTH {
1313 return;
1314 }
1315 let name = font_type3_name(font, code);
1316 let Some(name) = name else { return };
1317 let Some(id) = t3.char_procs.get(&name) else {
1318 return;
1319 };
1320 let Ok(Object::Stream(proc_stream)) = self.doc.get_object(*id) else {
1321 return;
1322 };
1323 let Ok(content) = proc_stream.decompressed_content() else {
1324 return;
1325 };
1326 let mut gst = st.clone();
1327 gst.ctm = t3.font_matrix.then(trm);
1328 let res = t3.resources.as_ref().or(resources);
1331 self.type3_depth += 1;
1332 self.run(&content, res, gst, depth + 1);
1333 self.type3_depth -= 1;
1334 }
1335
1336 fn draw_form(
1341 &mut self,
1342 stream: &lopdf::Stream,
1343 st: &GState,
1344 parent_res: Option<&Dictionary>,
1345 depth: usize,
1346 ) {
1347 if depth > MAX_DEPTH {
1348 return;
1349 }
1350 let doc = self.doc;
1351 let d = &stream.dict;
1352 let mut gst = st.clone();
1353 if get_dict(doc, d, b"Group").is_some() {
1354 gst.group_alpha = st.eff_fill_alpha();
1359 gst.group_blend = st.eff_blend();
1360 gst.fill_alpha = 1.0;
1361 gst.stroke_alpha = 1.0;
1362 gst.blend = BlendMode::SourceOver;
1363 }
1364 if let Some(m) = get(doc, d, b"Matrix")
1365 .and_then(|o| nums(doc, o))
1366 .and_then(|v| Mat::from_slice(&v))
1367 {
1368 gst.ctm = m.then(gst.ctm);
1369 }
1370 if let Some(b) = get(doc, d, b"BBox")
1371 .and_then(|o| nums(doc, o))
1372 .filter(|b| b.len() == 4)
1373 {
1374 let mut pb = PathBuilder::new();
1376 let (x0, y0, x1, y1) = (
1377 b[0].min(b[2]),
1378 b[1].min(b[3]),
1379 b[0].max(b[2]),
1380 b[1].max(b[3]),
1381 );
1382 pb.move_to(x0 as f32, y0 as f32);
1383 pb.line_to(x1 as f32, y0 as f32);
1384 pb.line_to(x1 as f32, y1 as f32);
1385 pb.line_to(x0 as f32, y1 as f32);
1386 pb.close();
1387 let ps = PathState {
1388 builder: PathBuilder::new(),
1389 current: None,
1390 start: None,
1391 rects: vec![[x0, y0, x1 - x0, y1 - y0]],
1392 only_rects: true,
1393 pending_clip: None,
1394 empty: false,
1395 };
1396 let up = pb.finish();
1397 self.apply_clip(&mut gst, up.as_ref(), &ps, FillRule::Winding);
1398 if gst.clip_box.is_empty() {
1399 return;
1400 }
1401 }
1402 let res = get_dict(doc, d, b"Resources").or(parent_res);
1403 let Ok(content) = stream.decompressed_content() else {
1404 return;
1405 };
1406 self.run(&content, res, gst, depth);
1407 }
1408
1409 fn draw_image(
1410 &mut self,
1411 stream: &lopdf::Stream,
1412 id: Option<ObjectId>,
1413 st: &GState,
1414 resources: Option<&Dictionary>,
1415 ) {
1416 if st.eff_fill_alpha() <= 1.0 / 512.0 {
1417 return;
1418 }
1419 let doc = self.doc;
1420 let (bx0, by0, bx1, by1) = st.ctm.unit_bbox();
1422 let dev_box = Box2::new(bx0, by0, bx1, by1);
1423 if st.clip_box.intersect(dev_box).is_empty() {
1424 return;
1425 }
1426 let dst_w = st.ctm.apply_vec(1.0, 0.0);
1427 let dst_h = st.ctm.apply_vec(0.0, 1.0);
1428 let dst_w = dst_w.0.hypot(dst_w.1).max(1.0);
1429 let dst_h = dst_h.0.hypot(dst_h.1).max(1.0);
1430 let target = (dst_w.ceil() as u32, dst_h.ceil() as u32);
1431 let is_mask = get_bool(doc, &stream.dict, b"ImageMask")
1432 .or_else(|| get_bool(doc, &stream.dict, b"IM"))
1433 .unwrap_or(false);
1434 let fill = match (is_mask, st.fill_rgb) {
1435 (true, Some(rgb)) => [to_u8(rgb[0]), to_u8(rgb[1]), to_u8(rgb[2])],
1436 (true, None) => return,
1437 _ => [0, 0, 0],
1438 };
1439 let reduction = image::declared_size(doc, &stream.dict)
1444 .map(|(sw, sh)| {
1445 let ux = self.base.a.hypot(self.base.b).max(1e-9);
1446 let uy = self.base.c.hypot(self.base.d).max(1e-9);
1447 image::codec_reduction_shift(
1448 (bx1 - bx0) / ux,
1449 (by1 - by0) / uy,
1450 sw,
1451 sh,
1452 self.bitmap_hint,
1453 )
1454 })
1455 .unwrap_or(0);
1456 let key = id.map(|id| (id, target.0, target.1, fill));
1457 let img = match key.and_then(|k| self.images.get(&k).cloned()) {
1458 Some(i) => i,
1459 None => {
1460 let decoded = match crate::timing::timed("render.image_decode", || {
1461 let loaded = self
1462 .shared
1463 .load_image(doc, stream, id, resources, reduction)?;
1464 let mut cmyk = self.shared.cmyk.borrow_mut();
1465 image::rasterize(&loaded, fill, Some(target), &mut cmyk)
1466 }) {
1467 Ok(d) => Rc::new(d),
1468 Err(e) => {
1469 docling_core::debug_log!("docling-pdf render: image skipped ({e})");
1470 return;
1471 }
1472 };
1473 if let Some(k) = key {
1474 if self.images.len() > 256 {
1475 self.images.clear();
1476 }
1477 self.images.insert(k, decoded.clone());
1478 }
1479 decoded
1480 }
1481 };
1482 let (pw, ph) = (
1484 f64::from(img.pixmap.width()),
1485 f64::from(img.pixmap.height()),
1486 );
1487 docling_core::debug_log!(
1488 "docling-pdf render: image {:?} reduction 1/{} → pixmap {pw}×{ph} (src {}×{}) for {target:?} px",
1489 id,
1490 1u32 << reduction,
1491 img.src_width,
1492 img.src_height
1493 );
1494 let to_unit = Mat::new(1.0 / pw, 0.0, 0.0, -1.0 / ph, 0.0, 1.0);
1495 let m = to_unit.then(st.ctm);
1496 let paint = PixmapPaint {
1497 opacity: st.eff_fill_alpha() as f32,
1498 blend_mode: st.eff_blend(),
1499 quality: if dst_w >= pw && dst_h >= ph && pw * ph < 4.0 {
1500 FilterQuality::Nearest
1501 } else {
1502 FilterQuality::Bilinear
1503 },
1504 };
1505 let img_bounds = tiny_skia::Rect::from_ltrb(
1506 dev_box.x0 as f32,
1507 dev_box.y0 as f32,
1508 dev_box.x1 as f32,
1509 dev_box.y1 as f32,
1510 );
1511 let mask = self.clip_mask_for(st, img_bounds);
1512 self.canvas.draw_pixmap(
1513 0,
1514 0,
1515 img.pixmap.as_ref(),
1516 &paint,
1517 m.to_ts(),
1518 mask.as_deref(),
1519 );
1520 }
1521
1522 fn paint_shading(
1529 &mut self,
1530 sh: &Object,
1531 matrix: Mat,
1532 st: &GState,
1533 area: Option<(&Path, FillRule)>,
1534 alpha: f64,
1535 background: bool,
1536 ) {
1537 let doc = self.doc;
1538 let Some(d) = as_dict(doc, sh) else { return };
1539 let stype = get_int(doc, d, b"ShadingType").unwrap_or(0);
1540 let cs = get(doc, d, b"ColorSpace")
1541 .and_then(|o| ColorSpace::parse(doc, o, None))
1542 .unwrap_or(ColorSpace::DeviceRGB);
1543 let func = d
1544 .get(b"Function")
1545 .ok()
1546 .and_then(|o| Function::parse(doc, o));
1547 let mask = self.clip_mask_for(st, area.map(|(p, _)| p.bounds()));
1548 let region: Path = match area {
1550 Some((p, _)) => p.clone(),
1551 None => {
1552 let cb = st.clip_box;
1553 let Some(r) = tiny_skia::Rect::from_ltrb(
1554 cb.x0 as f32,
1555 cb.y0 as f32,
1556 cb.x1 as f32,
1557 cb.y1 as f32,
1558 ) else {
1559 return;
1560 };
1561 PathBuilder::from_rect(r)
1562 }
1563 };
1564 let rule = area.map(|(_, r)| r).unwrap_or(FillRule::Winding);
1565 let _ = background;
1566 match stype {
1567 2 | 3 => {
1568 let Some(coords) = get(doc, d, b"Coords").and_then(|o| nums(doc, o)) else {
1569 return;
1570 };
1571 let domain = get(doc, d, b"Domain")
1572 .and_then(|o| nums(doc, o))
1573 .unwrap_or(vec![0.0, 1.0]);
1574 let extend = get(doc, d, b"Extend")
1575 .and_then(|o| match o {
1576 Object::Array(a) => Some(
1577 a.iter()
1578 .map(|b| matches!(deref(doc, b), Object::Boolean(true)))
1579 .collect::<Vec<bool>>(),
1580 ),
1581 _ => None,
1582 })
1583 .unwrap_or(vec![false, false]);
1584 let (e0, e1) = (
1585 extend.first().copied().unwrap_or(false),
1586 extend.get(1).copied().unwrap_or(false),
1587 );
1588 let Some(f) = func.as_ref() else { return };
1589 let stops = sample_stops(f, &cs, &domain, alpha, e0, e1);
1590 if stops.len() < 2 {
1591 return;
1592 }
1593 let shader = if stype == 2 {
1594 if coords.len() < 4 {
1595 return;
1596 }
1597 tiny_skia::LinearGradient::new(
1598 Point::from_xy(coords[0] as f32, coords[1] as f32),
1599 Point::from_xy(coords[2] as f32, coords[3] as f32),
1600 stops,
1601 SpreadMode::Pad,
1602 matrix.to_ts(),
1603 )
1604 } else {
1605 if coords.len() < 6 {
1606 return;
1607 }
1608 tiny_skia::RadialGradient::new(
1609 Point::from_xy(coords[0] as f32, coords[1] as f32),
1610 coords[2].max(0.0) as f32,
1611 Point::from_xy(coords[3] as f32, coords[4] as f32),
1612 coords[5].max(0.0) as f32,
1613 stops,
1614 SpreadMode::Pad,
1615 matrix.to_ts(),
1616 )
1617 };
1618 let Some(shader) = shader else { return };
1619 let paint = Paint {
1620 shader,
1621 blend_mode: st.eff_blend(),
1622 anti_alias: true,
1623 force_hq_pipeline: false,
1624 colorspace: tiny_skia::ColorSpace::Linear,
1625 };
1626 self.canvas.fill_path(
1627 ®ion,
1628 &paint,
1629 rule,
1630 Transform::identity(),
1631 mask.as_deref(),
1632 );
1633 }
1634 1 => {
1635 let domain = get(doc, d, b"Domain")
1637 .and_then(|o| nums(doc, o))
1638 .unwrap_or(vec![0.0, 1.0, 0.0, 1.0]);
1639 let fm = get(doc, d, b"Matrix")
1640 .and_then(|o| nums(doc, o))
1641 .and_then(|v| Mat::from_slice(&v))
1642 .unwrap_or(Mat::IDENTITY);
1643 let Some(f) = func.as_ref() else { return };
1644 if domain.len() < 4 {
1645 return;
1646 }
1647 const N: u32 = 64;
1648 let Some(mut pm) = Pixmap::new(N, N) else {
1649 return;
1650 };
1651 {
1652 let px = pm.pixels_mut();
1653 for j in 0..N {
1654 for i in 0..N {
1655 let x = domain[0]
1656 + (domain[1] - domain[0]) * (f64::from(i) + 0.5) / f64::from(N);
1657 let y = domain[2]
1658 + (domain[3] - domain[2]) * (f64::from(j) + 0.5) / f64::from(N);
1659 let out = f.eval(&[x, y]);
1660 let rgb = cs.to_rgb(&out).unwrap_or([0.0, 0.0, 0.0]);
1661 px[(j * N + i) as usize] = tiny_skia::PremultipliedColorU8::from_rgba(
1662 to_u8(rgb[0]),
1663 to_u8(rgb[1]),
1664 to_u8(rgb[2]),
1665 255,
1666 )
1667 .unwrap_or(tiny_skia::PremultipliedColorU8::TRANSPARENT);
1668 }
1669 }
1670 }
1671 let to_domain = Mat::new(
1673 (domain[1] - domain[0]) / f64::from(N),
1674 0.0,
1675 0.0,
1676 (domain[3] - domain[2]) / f64::from(N),
1677 domain[0],
1678 domain[2],
1679 );
1680 let m = to_domain.then(fm).then(matrix);
1681 let shader = tiny_skia::Pattern::new(
1682 pm.as_ref(),
1683 SpreadMode::Pad,
1684 FilterQuality::Bilinear,
1685 alpha as f32,
1686 m.to_ts(),
1687 );
1688 let paint = Paint {
1689 shader,
1690 blend_mode: st.eff_blend(),
1691 anti_alias: true,
1692 force_hq_pipeline: false,
1693 colorspace: tiny_skia::ColorSpace::Linear,
1694 };
1695 self.canvas.fill_path(
1696 ®ion,
1697 &paint,
1698 rule,
1699 Transform::identity(),
1700 mask.as_deref(),
1701 );
1702 }
1703 4..=7 => {
1704 let Some(stream) = as_stream(doc, sh) else {
1705 return;
1706 };
1707 let Ok(data) = stream.decompressed_content() else {
1708 return;
1709 };
1710 let region_mask = if area.is_some() {
1711 let mut m = match mask.as_deref() {
1713 Some(m) => m.clone(),
1714 None => {
1715 let Some(m) = Mask::new(self.canvas.width(), self.canvas.height())
1716 else {
1717 return;
1718 };
1719 let mut m = m;
1720 m.fill_path(
1721 &PathBuilder::from_rect(
1722 tiny_skia::Rect::from_ltrb(
1723 0.0,
1724 0.0,
1725 self.device.x1 as f32,
1726 self.device.y1 as f32,
1727 )
1728 .unwrap(),
1729 ),
1730 FillRule::Winding,
1731 true,
1732 Transform::identity(),
1733 );
1734 m
1735 }
1736 };
1737 m.intersect_path(®ion, rule, true, Transform::identity());
1738 Some(Rc::new(m))
1739 } else {
1740 mask.clone()
1741 };
1742 self.paint_mesh(
1743 stype,
1744 d,
1745 &data,
1746 &cs,
1747 func.as_ref(),
1748 matrix,
1749 st,
1750 region_mask.as_deref(),
1751 alpha,
1752 );
1753 }
1754 _ => {}
1755 }
1756 }
1757
1758 #[allow(clippy::too_many_arguments)]
1761 fn paint_mesh(
1762 &mut self,
1763 stype: i64,
1764 d: &Dictionary,
1765 data: &[u8],
1766 cs: &ColorSpace,
1767 func: Option<&Function>,
1768 matrix: Mat,
1769 st: &GState,
1770 mask: Option<&Mask>,
1771 alpha: f64,
1772 ) {
1773 let doc = self.doc;
1774 let bpc = get_int(doc, d, b"BitsPerCoordinate")
1775 .unwrap_or(16)
1776 .clamp(1, 32) as u32;
1777 let bpcomp = get_int(doc, d, b"BitsPerComponent")
1778 .unwrap_or(16)
1779 .clamp(1, 16) as u32;
1780 let bpf = get_int(doc, d, b"BitsPerFlag").unwrap_or(8).clamp(2, 8) as u32;
1781 let decode = get(doc, d, b"Decode")
1782 .and_then(|o| nums(doc, o))
1783 .unwrap_or_default();
1784 let ncomp = if func.is_some() { 1 } else { cs.components() };
1785 if decode.len() < 4 + 2 * ncomp {
1786 return;
1787 }
1788 let mut reader = BitReader::new(data);
1789 let read_val = |r: &mut BitReader, bits: u32, dmin: f64, dmax: f64| -> Option<f64> {
1790 let v = r.read(bits)?;
1791 let max = if bits >= 32 {
1792 u32::MAX as f64
1793 } else {
1794 ((1u64 << bits) - 1) as f64
1795 };
1796 Some(dmin + f64::from(v) * (dmax - dmin) / max)
1797 };
1798 let color_of = |comps: &[f64]| -> [f64; 3] {
1799 let out = match func {
1800 Some(f) => f.eval(&comps[..1]),
1801 None => comps.to_vec(),
1802 };
1803 cs.to_rgb(&out).unwrap_or([0.0, 0.0, 0.0])
1804 };
1805 let mut triangles: Vec<Triangle> = Vec::new();
1806 let mut patches: Vec<Patch> = Vec::new();
1807 let read_vertex = |r: &mut BitReader| -> Option<Vertex> {
1808 let x = read_val(r, bpc, decode[0], decode[1])?;
1809 let y = read_val(r, bpc, decode[2], decode[3])?;
1810 let mut comps = Vec::with_capacity(ncomp);
1811 for c in 0..ncomp {
1812 comps.push(read_val(r, bpcomp, decode[4 + 2 * c], decode[5 + 2 * c])?);
1813 }
1814 Some(((x, y), color_of(&comps)))
1815 };
1816 match stype {
1817 4 => {
1818 let mut prev: Vec<((f64, f64), [f64; 3])> = Vec::new();
1819 let mut count = 0;
1820 while let Some(flag) = reader.read(bpf) {
1821 let Some(v) = read_vertex(&mut reader) else {
1822 break;
1823 };
1824 reader.align();
1825 if flag == 0 {
1826 let mut tri = vec![v];
1828 for _ in 0..2 {
1829 let _ = reader.read(bpf);
1830 let Some(v2) = read_vertex(&mut reader) else {
1831 break;
1832 };
1833 reader.align();
1834 tri.push(v2);
1835 }
1836 if tri.len() == 3 {
1837 prev = tri;
1838 } else {
1839 break;
1840 }
1841 } else if prev.len() == 3 {
1842 if flag == 1 {
1843 prev = vec![prev[1], prev[2], v];
1844 } else {
1845 prev = vec![prev[0], prev[2], v];
1846 }
1847 } else {
1848 break;
1849 }
1850 let pts = [prev[0].0, prev[1].0, prev[2].0];
1851 let col = mean3(&[prev[0].1, prev[1].1, prev[2].1]);
1852 triangles.push((pts, col));
1853 count += 1;
1854 if count > 200_000 {
1855 break;
1856 }
1857 }
1858 }
1859 5 => {
1860 let per_row = get_int(doc, d, b"VerticesPerRow").unwrap_or(2).max(2) as usize;
1861 let mut rows: Vec<Vec<Vertex>> = Vec::new();
1862 loop {
1863 let mut row = Vec::with_capacity(per_row);
1864 for _ in 0..per_row {
1865 match read_vertex(&mut reader) {
1866 Some(v) => row.push(v),
1867 None => break,
1868 }
1869 }
1870 if row.len() < per_row {
1871 break;
1872 }
1873 rows.push(row);
1874 if rows.len() > 4096 {
1875 break;
1876 }
1877 }
1878 for r in 1..rows.len() {
1879 for c in 1..per_row {
1880 let (a, b, cc, dd) = (
1881 rows[r - 1][c - 1],
1882 rows[r - 1][c],
1883 rows[r][c - 1],
1884 rows[r][c],
1885 );
1886 triangles.push(([a.0, b.0, cc.0], mean3(&[a.1, b.1, cc.1])));
1887 triangles.push(([b.0, dd.0, cc.0], mean3(&[b.1, dd.1, cc.1])));
1888 }
1889 }
1890 }
1891 6 | 7 => {
1892 let npts = if stype == 6 { 12 } else { 16 };
1893 let mut prev_pts: Vec<(f64, f64)> = Vec::new();
1894 let mut prev_cols: Vec<[f64; 3]> = Vec::new();
1895 while let Some(flag) = reader.read(bpf) {
1896 let (np, nc) = if flag == 0 { (npts, 4) } else { (npts - 4, 2) };
1897 let mut pts = Vec::with_capacity(16);
1898 let mut ok = true;
1899 for _ in 0..np {
1900 match (
1901 read_val(&mut reader, bpc, decode[0], decode[1]),
1902 read_val(&mut reader, bpc, decode[2], decode[3]),
1903 ) {
1904 (Some(x), Some(y)) => pts.push((x, y)),
1905 _ => {
1906 ok = false;
1907 break;
1908 }
1909 }
1910 }
1911 if !ok {
1912 break;
1913 }
1914 let mut cols = Vec::with_capacity(4);
1915 for _ in 0..nc {
1916 let mut comps = Vec::with_capacity(ncomp);
1917 for c in 0..ncomp {
1918 match read_val(
1919 &mut reader,
1920 bpcomp,
1921 decode[4 + 2 * c],
1922 decode[5 + 2 * c],
1923 ) {
1924 Some(v) => comps.push(v),
1925 None => {
1926 ok = false;
1927 break;
1928 }
1929 }
1930 }
1931 if !ok {
1932 break;
1933 }
1934 cols.push(color_of(&comps));
1935 }
1936 if !ok {
1937 break;
1938 }
1939 reader.align();
1940 let (full_pts, full_cols) = if flag == 0 || prev_pts.len() < 12 {
1942 if flag != 0 {
1943 break;
1944 }
1945 (pts.clone(), cols.clone())
1946 } else {
1947 let p = &prev_pts;
1948 let edge: [(f64, f64); 4] = match flag {
1949 1 => [p[3], p[4], p[5], p[6]],
1950 2 => [p[6], p[7], p[8], p[9]],
1951 _ => [p[9], p[10], p[11], p[0]],
1952 };
1953 let pc = &prev_cols;
1954 let ec: [[f64; 3]; 2] = match flag {
1955 1 => [pc[1], pc[2]],
1956 2 => [pc[2], pc[3]],
1957 _ => [pc[3], pc[0]],
1958 };
1959 let mut fp = edge.to_vec();
1960 fp.extend(pts.iter().copied());
1961 let mut fc = ec.to_vec();
1962 fc.extend(cols.iter().copied());
1963 (fp, fc)
1964 };
1965 if full_pts.len() < 12 || full_cols.len() < 4 {
1966 break;
1967 }
1968 patches.push((full_pts[..12].to_vec(), mean4(&full_cols)));
1969 prev_pts = full_pts;
1970 prev_cols = full_cols;
1971 if patches.len() > 50_000 {
1972 break;
1973 }
1974 }
1975 }
1976 _ => {}
1977 }
1978 let ts = matrix.to_ts();
1979 for (pts, col) in &triangles {
1980 let mut pb = PathBuilder::new();
1981 pb.move_to(pts[0].0 as f32, pts[0].1 as f32);
1982 pb.line_to(pts[1].0 as f32, pts[1].1 as f32);
1983 pb.line_to(pts[2].0 as f32, pts[2].1 as f32);
1984 pb.close();
1985 if let Some(p) = pb.finish().and_then(|p| p.transform(ts)) {
1986 let mut paint = Self::paint_for(*col, alpha, st.eff_blend());
1987 paint.anti_alias = false;
1988 self.canvas
1989 .fill_path(&p, &paint, FillRule::Winding, Transform::identity(), mask);
1990 }
1991 }
1992 for (pts, col) in &patches {
1993 let mut pb = PathBuilder::new();
1995 pb.move_to(pts[0].0 as f32, pts[0].1 as f32);
1996 for k in 0..4 {
1997 let (a, b, c) = (
1998 pts[(3 * k + 1) % 12],
1999 pts[(3 * k + 2) % 12],
2000 pts[(3 * k + 3) % 12],
2001 );
2002 pb.cubic_to(
2003 a.0 as f32, a.1 as f32, b.0 as f32, b.1 as f32, c.0 as f32, c.1 as f32,
2004 );
2005 }
2006 pb.close();
2007 if let Some(p) = pb.finish().and_then(|p| p.transform(ts)) {
2008 let mut paint = Self::paint_for(*col, alpha, st.eff_blend());
2009 paint.anti_alias = false;
2010 self.canvas
2011 .fill_path(&p, &paint, FillRule::Winding, Transform::identity(), mask);
2012 }
2013 }
2014 }
2015
2016 fn fill_with_pattern(
2020 &mut self,
2021 dev: &Path,
2022 rule: FillRule,
2023 pat: &Object,
2024 st: &GState,
2025 resources: Option<&Dictionary>,
2026 ) {
2027 let doc = self.doc;
2028 let Some(pd) = as_dict(doc, pat) else { return };
2029 let ptype = get_int(doc, pd, b"PatternType").unwrap_or(2);
2030 let pmatrix = get(doc, pd, b"Matrix")
2031 .and_then(|o| nums(doc, o))
2032 .and_then(|v| Mat::from_slice(&v))
2033 .unwrap_or(Mat::IDENTITY);
2034 let base = self.pattern_base(st);
2037 let m = pmatrix.then(base);
2038 if ptype == 2 {
2039 let Some(sh) = get(doc, pd, b"Shading") else {
2040 return;
2041 };
2042 let sh = sh.clone();
2043 self.paint_shading(&sh, m, st, Some((dev, rule)), st.eff_fill_alpha(), false);
2044 return;
2045 }
2046 let Some(stream) = as_stream(doc, pat) else {
2048 return;
2049 };
2050 let Some(bbox) = get(doc, pd, b"BBox")
2051 .and_then(|o| nums(doc, o))
2052 .filter(|b| b.len() == 4)
2053 else {
2054 return;
2055 };
2056 let mut xstep = get_num(doc, pd, b"XStep")
2057 .unwrap_or(bbox[2] - bbox[0])
2058 .abs();
2059 let mut ystep = get_num(doc, pd, b"YStep")
2060 .unwrap_or(bbox[3] - bbox[1])
2061 .abs();
2062 if xstep < 1e-9 {
2063 xstep = (bbox[2] - bbox[0]).abs().max(1e-3);
2064 }
2065 if ystep < 1e-9 {
2066 ystep = (bbox[3] - bbox[1]).abs().max(1e-3);
2067 }
2068 let paint_type = get_int(doc, pd, b"PaintType").unwrap_or(1);
2069 let scale = m.max_scale().max(1e-6);
2070 let cell_w = (xstep * scale).ceil().clamp(1.0, 2048.0);
2071 let cell_h = (ystep * scale).ceil().clamp(1.0, 2048.0);
2072 let s_x = cell_w / xstep;
2073 let s_y = cell_h / ystep;
2074 let (x0, y0) = (bbox[0].min(bbox[2]), bbox[1].min(bbox[3]));
2075 let to_tile = Mat::new(s_x, 0.0, 0.0, -s_y, -x0 * s_x, (y0 + ystep) * s_y);
2077 let Some(mut tile) = Pixmap::new(cell_w as u32, cell_h as u32) else {
2078 return;
2079 };
2080 tile.fill(tiny_skia::Color::TRANSPARENT);
2081 let Ok(content) = stream.decompressed_content() else {
2082 return;
2083 };
2084 let res = get_dict(doc, &stream.dict, b"Resources").or(resources);
2085 let mut inner = Interp {
2087 doc,
2088 canvas: tile,
2089 device: Box2::new(0.0, 0.0, cell_w, cell_h),
2090 shared: self.shared.clone(),
2091 images: HashMap::new(),
2092 rect_masks: HashMap::new(),
2093 shape_masks: HashMap::new(),
2094 ops: self.ops,
2095 warned_no_face: self.warned_no_face,
2096 type3_depth: self.type3_depth,
2097 base: to_tile,
2098 bitmap_hint: self.bitmap_hint,
2099 };
2100 let mut gst = inner.initial_state(to_tile);
2101 if paint_type == 2 {
2102 gst.fixed_color = true;
2103 gst.fill_rgb = st.fill_rgb;
2104 gst.stroke_rgb = st.fill_rgb;
2105 }
2106 inner.run(&content, res, gst, MAX_DEPTH - 2);
2107 self.ops = inner.ops;
2108 let tile = inner.canvas;
2109 let Some(from_tile) = to_tile.invert() else {
2111 return;
2112 };
2113 let shader_m = from_tile.then(m);
2114 let shader = tiny_skia::Pattern::new(
2115 tile.as_ref(),
2116 SpreadMode::Repeat,
2117 FilterQuality::Bilinear,
2118 st.eff_fill_alpha() as f32,
2119 shader_m.to_ts(),
2120 );
2121 let paint = Paint {
2122 shader,
2123 blend_mode: st.eff_blend(),
2124 anti_alias: true,
2125 force_hq_pipeline: false,
2126 colorspace: tiny_skia::ColorSpace::Linear,
2127 };
2128 let mask = self.clip_mask_for(st, Some(dev.bounds()));
2129 self.canvas
2130 .fill_path(dev, &paint, rule, Transform::identity(), mask.as_deref());
2131 }
2132
2133 fn pattern_base(&self, _st: &GState) -> Mat {
2138 self.base
2139 }
2140}
2141
2142fn shape_mask(
2152 w: u32,
2153 h: u32,
2154 dev: &Path,
2155 rule: FillRule,
2156 keep: Box2,
2157 parent: Option<&Mask>,
2158) -> Option<Mask> {
2159 let mut m = Mask::new(w, h)?;
2160 m.fill_path(dev, rule, true, Transform::identity());
2161 let bb = dev.bounds();
2162 let (w, h) = (w as usize, h as usize);
2163 let bx0 = (bb.left().floor().max(0.0) as usize).min(w);
2165 let by0 = (bb.top().floor().max(0.0) as usize).min(h);
2166 let bx1 = ((bb.right().ceil().max(0.0) as usize) + 1).min(w);
2167 let by1 = ((bb.bottom().ceil().max(0.0) as usize) + 1).min(h);
2168 let kx0 = (keep.x0.floor().max(0.0) as usize).clamp(bx0, bx1);
2172 let ky0 = (keep.y0.floor().max(0.0) as usize).clamp(by0, by1);
2173 let kx1 = (keep.x1.ceil().max(0.0) as usize).clamp(kx0, bx1);
2174 let ky1 = (keep.y1.ceil().max(0.0) as usize).clamp(ky0, by1);
2175 let edge = |i: usize, lo: f64, hi: f64| -> f64 {
2178 let (a, b) = (i as f64, i as f64 + 1.0);
2179 (b.min(hi) - a.max(lo)).clamp(0.0, 1.0)
2180 };
2181 let fx: Vec<f64> = (kx0..kx1).map(|x| edge(x, keep.x0, keep.x1)).collect();
2182 let plain_x = fx.iter().all(|&f| f >= 1.0);
2183 let data = m.data_mut();
2184 for y in by0..by1 {
2185 let row = &mut data[y * w..(y + 1) * w];
2186 if y < ky0 || y >= ky1 {
2187 row[bx0..bx1].fill(0);
2188 continue;
2189 }
2190 row[bx0..kx0].fill(0);
2191 row[kx1..bx1].fill(0);
2192 let fy = edge(y, keep.y0, keep.y1);
2193 let prow = parent.map(|p| &p.data()[y * w..(y + 1) * w]);
2194 if prow.is_none() && plain_x && fy >= 1.0 {
2195 continue;
2196 }
2197 for x in kx0..kx1 {
2198 let mut c = f64::from(row[x]) * fy * fx[x - kx0];
2199 if let Some(prow) = prow {
2200 c = c * f64::from(prow[x]) / 255.0;
2201 }
2202 row[x] = c.round().clamp(0.0, 255.0) as u8;
2203 }
2204 }
2205 Some(m)
2206}
2207
2208fn path_hash(p: &Path, clip_box: Box2) -> u64 {
2210 use std::hash::{Hash, Hasher};
2211 let mut h = std::collections::hash_map::DefaultHasher::new();
2212 for pt in p.points() {
2213 pt.x.to_bits().hash(&mut h);
2214 pt.y.to_bits().hash(&mut h);
2215 }
2216 for v in p.verbs() {
2217 (*v as u8).hash(&mut h);
2218 }
2219 for v in [clip_box.x0, clip_box.y0, clip_box.x1, clip_box.y1] {
2220 v.to_bits().hash(&mut h);
2221 }
2222 h.finish()
2223}
2224
2225fn device_path_is_rect(p: &Path) -> bool {
2228 let b = p.bounds();
2229 if b.width() <= 1e-3 || b.height() <= 1e-3 {
2230 return false;
2231 }
2232 let mut moves = 0;
2233 let mut points = 0;
2234 for seg in p.segments() {
2235 match seg {
2236 tiny_skia::PathSegment::MoveTo(pt) => {
2237 moves += 1;
2238 if moves > 1 || !on_corner(pt, &b) {
2239 return false;
2240 }
2241 points += 1;
2242 }
2243 tiny_skia::PathSegment::LineTo(pt) => {
2244 if !on_corner(pt, &b) {
2245 return false;
2246 }
2247 points += 1;
2248 }
2249 tiny_skia::PathSegment::Close => {}
2250 _ => return false,
2251 }
2252 }
2253 (4..=5).contains(&points)
2254}
2255
2256fn on_corner(pt: Point, b: &tiny_skia::Rect) -> bool {
2257 let eps = 1e-3;
2258 let on_x = (pt.x - b.left()).abs() <= eps || (pt.x - b.right()).abs() <= eps;
2259 let on_y = (pt.y - b.top()).abs() <= eps || (pt.y - b.bottom()).abs() <= eps;
2260 on_x && on_y
2261}
2262
2263fn font_type3_name(font: &LoadedFont, code: u32) -> Option<String> {
2265 font.type3_glyph_name(code)
2266}
2267
2268fn mean3(c: &[[f64; 3]; 3]) -> [f64; 3] {
2269 [
2270 (c[0][0] + c[1][0] + c[2][0]) / 3.0,
2271 (c[0][1] + c[1][1] + c[2][1]) / 3.0,
2272 (c[0][2] + c[1][2] + c[2][2]) / 3.0,
2273 ]
2274}
2275
2276fn mean4(c: &[[f64; 3]]) -> [f64; 3] {
2277 let n = c.len().max(1) as f64;
2278 let mut out = [0.0; 3];
2279 for col in c {
2280 for k in 0..3 {
2281 out[k] += col[k] / n;
2282 }
2283 }
2284 out
2285}
2286
2287fn sample_stops(
2292 f: &Function,
2293 cs: &ColorSpace,
2294 domain: &[f64],
2295 alpha: f64,
2296 e0: bool,
2297 e1: bool,
2298) -> Vec<GradientStop> {
2299 let (t0, t1) = (
2300 domain.first().copied().unwrap_or(0.0),
2301 domain.get(1).copied().unwrap_or(1.0),
2302 );
2303 const N: usize = 64;
2304 let mut stops = Vec::with_capacity(N + 3);
2305 let a = to_u8(alpha);
2306 let guard = 1.0 / 1024.0;
2307 for i in 0..=N {
2308 let u = i as f64 / N as f64;
2309 let t = t0 + (t1 - t0) * u;
2310 let out = f.eval(&[t]);
2311 let rgb = cs.to_rgb(&out).unwrap_or([0.0, 0.0, 0.0]);
2312 let color = tiny_skia::Color::from_rgba8(to_u8(rgb[0]), to_u8(rgb[1]), to_u8(rgb[2]), a);
2313 let pos = if !e0 && i == 0 {
2314 guard
2315 } else if !e1 && i == N {
2316 1.0 - guard
2317 } else {
2318 u
2319 };
2320 stops.push(GradientStop::new(pos as f32, color));
2321 }
2322 if !e0 {
2323 stops.insert(0, GradientStop::new(0.0, tiny_skia::Color::TRANSPARENT));
2324 }
2325 if !e1 {
2326 stops.push(GradientStop::new(1.0, tiny_skia::Color::TRANSPARENT));
2327 }
2328 stops
2329}
2330
2331struct BitReader<'a> {
2332 data: &'a [u8],
2333 pos: usize,
2334}
2335
2336impl<'a> BitReader<'a> {
2337 fn new(data: &'a [u8]) -> BitReader<'a> {
2338 BitReader { data, pos: 0 }
2339 }
2340
2341 fn read(&mut self, bits: u32) -> Option<u32> {
2342 if self.pos + bits as usize > self.data.len() * 8 {
2343 return None;
2344 }
2345 let mut v: u64 = 0;
2346 for _ in 0..bits {
2347 let byte = self.data[self.pos / 8];
2348 let bit = (byte >> (7 - self.pos % 8)) & 1;
2349 v = (v << 1) | u64::from(bit);
2350 self.pos += 1;
2351 }
2352 Some(v as u32)
2353 }
2354
2355 fn align(&mut self) {
2356 self.pos = self.pos.div_ceil(8) * 8;
2357 }
2358}