1use std::{
2 borrow::Cow,
3 hash::Hash,
4 ops::DerefMut,
5 sync::{atomic::AtomicU64, Arc},
6};
7
8use parking_lot::Mutex;
9use rayon::iter::{
10 IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelExtend,
11 ParallelIterator,
12};
13use reflexo::ImmutStr;
14use ttf_parser::{GlyphId, OutlineBuilder};
15use typst::{
16 foundations::{Bytes, Smart},
17 introspection::{Introspector, Tag},
18 layout::{
19 Abs as TypstAbs, Axes, Dir, Frame, FrameItem, FrameKind, Position, Ratio as TypstRatio,
20 Size, Transform as TypstTransform,
21 },
22 model::{Destination, Document as TypstDocument},
23 syntax::Span,
24 text::TextItem as TypstTextItem,
25 visualize::{
26 FillRule, FixedStroke, Geometry, Gradient, Image as TypstImage, LineCap, LineJoin, Paint,
27 PathItem as TypstPathItem, Pattern, RelativeTo, Shape,
28 },
29};
30
31use crate::{
32 font::GlyphProvider,
33 hash::{Fingerprint, FingerprintBuilder},
34 ir::{self, *},
35 path2d::SvgPath2DBuilder,
36 utils::{AbsExt, ToCssExt},
37 FromTypst, IntoTypst,
38};
39
40use super::{SourceNodeKind, SourceRegion, Span2VecPass, TGlyph2VecPass};
41
42#[derive(Clone, Copy)]
43struct State<'a> {
44 introspector: &'a Introspector,
45 pub transform: Transform,
47 pub size: ir::Size,
49}
50
51impl State<'_> {
52 fn new(introspector: &Introspector, size: ir::Size) -> State {
53 State {
54 introspector,
55 transform: Transform::identity(),
56 size,
57 }
58 }
59
60 pub fn pre_translate(self, pos: Point) -> Self {
62 self.pre_concat(Transform::from_translate(pos.x, pos.y))
63 }
64
65 pub fn pre_concat(self, transform: ir::Transform) -> Self {
67 Self {
68 transform: self.transform.pre_concat(transform),
69 ..self
70 }
71 }
72
73 pub fn with_size(self, size: ir::Size) -> Self {
75 Self { size, ..self }
76 }
77
78 pub fn with_transform(self, transform: ir::Transform) -> Self {
80 Self { transform, ..self }
81 }
82
83 pub fn inv_transform(&self) -> ir::Transform {
84 self.transform.invert().unwrap()
85 }
86
87 pub fn body_inv_transform(&self) -> ir::Transform {
88 ir::Transform::from_scale(self.size.x, self.size.y)
89 .post_concat(self.transform.invert().unwrap())
90 }
91}
92
93pub trait CommandExecutor {
94 fn execute(&self, cmd: Bytes, size: Option<Size>) -> Option<VecItem>;
95}
96
97impl CommandExecutor for () {
98 fn execute(&self, _: Bytes, _: Option<Size>) -> Option<VecItem> {
99 None
100 }
101}
102
103pub struct Typst2VecPassImpl<const ENABLE_REF_CNT: bool = false> {
105 pub glyphs: TGlyph2VecPass<ENABLE_REF_CNT>,
106 pub spans: Span2VecPass,
107 pub cache_items: RefItemMapT<(AtomicU64, Fingerprint, VecItem)>,
108 pub items: RefItemMapSync,
109 pub new_items: Mutex<Vec<(Fingerprint, VecItem)>>,
110
111 pub command_executor: Arc<dyn CommandExecutor + Send + Sync>,
112
113 fingerprint_builder: FingerprintBuilder,
114
115 pub lifetime: u64,
116}
117
118pub type Typst2VecPass = Typst2VecPassImpl<false>;
119pub type IncrTypst2VecPass = Typst2VecPassImpl<true>;
120
121impl<const ENABLE_REF_CNT: bool> Default for Typst2VecPassImpl<ENABLE_REF_CNT> {
122 fn default() -> Self {
123 let glyphs = TGlyph2VecPass::new(GlyphProvider::default(), true);
124 let spans = Span2VecPass::default();
125
126 Self {
127 lifetime: 0,
128 glyphs,
129 spans,
130 cache_items: Default::default(),
131 items: Default::default(),
132 new_items: Default::default(),
133 fingerprint_builder: Default::default(),
134 command_executor: Arc::new(()),
135 }
136 }
137}
138
139impl Typst2VecPass {
140 pub fn intern(&mut self, m: &Module, f: &Fingerprint) {
141 let item = m.get_item(f).unwrap();
142 self.insert(*f, Cow::Borrowed(item));
143 match item {
144 VecItem::None
145 | VecItem::Link(_)
146 | VecItem::Image(_)
147 | VecItem::Path(_)
148 | VecItem::Color32(_)
149 | VecItem::Gradient(_)
150 | VecItem::ContentHint(_)
151 | VecItem::ColorTransform(_)
152 | VecItem::Html(..) => {}
153 VecItem::Text(t) => {
154 self.glyphs.used_fonts.insert(t.shape.font);
156 self.glyphs
157 .used_glyphs
158 .extend(t.content.glyphs.iter().map(|(_, _, glyph_idx)| GlyphRef {
159 font_hash: t.shape.font.hash,
160 glyph_idx: *glyph_idx,
161 }));
162 }
163 VecItem::Pattern(p) => {
164 if !self.items.contains_key(&p.frame) {
165 self.intern(m, &p.frame);
166 }
167 }
168 VecItem::Item(t) => {
169 if !self.items.contains_key(&t.1) {
170 self.intern(m, &t.1);
171 }
172 }
173 VecItem::Group(g) => {
174 for (_, id) in g.0.iter() {
175 if !self.items.contains_key(id) {
176 self.intern(m, id);
177 }
178 }
179 }
180 }
181 }
182}
183
184impl<const ENABLE_REF_CNT: bool> Typst2VecPassImpl<ENABLE_REF_CNT> {
185 pub fn reset(&mut self) {}
186
187 pub fn finalize(self) -> Module {
188 let (fonts, glyphs) = self.glyphs.finalize();
189 Module {
190 fonts,
191 glyphs,
192 items: self.items.to_item_map(),
193 }
194 }
195
196 pub fn doc(&self, introspector: &Introspector, doc: &TypstDocument) -> Vec<Page> {
197 let doc_reg = self.spans.start();
198
199 let pages = doc
200 .pages
201 .par_iter()
202 .enumerate()
203 .map(|(idx, p)| {
204 let page_reg = self.spans.start();
205
206 let state = State::new(introspector, p.frame.size().into_typst());
207 let abs_ref = self.frame_(state, &p.frame, page_reg, idx, p.fill_or_transparent());
208
209 self.spans.push_span(SourceRegion {
210 region: doc_reg,
211 idx: idx as u32,
212 kind: SourceNodeKind::Page { region: page_reg },
213 item: abs_ref,
214 });
215
216 Page {
217 content: abs_ref,
218 size: p.frame.size().into_typst(),
219 }
220 })
221 .collect();
222
223 self.spans
224 .doc_region
225 .store(doc_reg, std::sync::atomic::Ordering::SeqCst);
226
227 pages
228 }
229
230 fn frame(&self, state: State, frame: &Frame, parent: usize, index: usize) -> Fingerprint {
231 self.frame_(state, frame, parent, index, None)
232 }
233
234 fn frame_(
235 &self,
236 mut state: State,
237 frame: &Frame,
238 parent: usize,
239 index: usize,
240 fill: Option<Paint>,
241 ) -> Fingerprint {
242 let src_reg = self.spans.start();
243
244 let frame_size = match frame.kind() {
245 FrameKind::Hard => Some(frame.size().into_typst()),
246 FrameKind::Soft => None,
247 };
248 if let Some(sz) = &frame_size {
249 state = state.with_transform(Transform::identity()).with_size(*sz);
250 }
251 let state = state;
252
253 let fill_adjust = if fill.is_some() { 1 } else { 0 };
254 let mut items = Vec::with_capacity(frame.items().len() + fill_adjust);
255 if let Some(fill) = fill {
256 let shape = Shape {
257 geometry: Geometry::Rect(frame.size()),
258 fill: Some(fill),
259 fill_rule: FillRule::default(),
260 stroke: None,
261 };
262
263 let fg = self.shape(state, &shape);
264 items.push((Point::default(), false, fg));
265
266 self.spans.push_span(SourceRegion {
267 region: src_reg,
268 idx: 0,
269 kind: SourceNodeKind::Shape(Span::detached()),
270 item: fg,
271 });
272 }
273
274 let items_iter = frame.items().as_slice().par_iter().enumerate();
275 let items_iter = items_iter.flat_map(|(idx, (pos, item))| {
276 let idx = fill_adjust + idx;
277 let mut is_link = false;
278 let state = state.pre_translate((*pos).into_typst());
279 let item = match item {
280 FrameItem::Group(group) => {
281 let state = state.pre_concat(group.transform.into_typst());
282
283 let mut inner = self.frame(state, &group.frame, src_reg, idx);
284
285 if let Some(p) = group.clip_path.as_ref() {
286 let mut builder = SvgPath2DBuilder(String::new());
288
289 builder.move_to(0., 0.);
291 for elem in &p.0 {
292 match elem {
293 TypstPathItem::MoveTo(p) => {
294 builder.move_to(p.x.to_f32(), p.y.to_f32());
295 }
296 TypstPathItem::LineTo(p) => {
297 builder.line_to(p.x.to_f32(), p.y.to_f32());
298 }
299 TypstPathItem::CubicTo(p1, p2, p3) => {
300 builder.curve_to(
301 p1.x.to_f32(),
302 p1.y.to_f32(),
303 p2.x.to_f32(),
304 p2.y.to_f32(),
305 p3.x.to_f32(),
306 p3.y.to_f32(),
307 );
308 }
309 TypstPathItem::ClosePath => {
310 builder.close();
311 }
312 };
313 }
314 let d = builder.0.into();
315
316 inner = self.store(VecItem::Item(TransformedRef(
317 TransformItem::Clip(Arc::new(PathItem {
318 d,
319 size: None,
320 styles: vec![],
321 })),
322 inner,
323 )));
324 };
325
326 if group.transform != TypstTransform::identity() {
327 inner = self.store(VecItem::Item(TransformedRef(
328 TransformItem::Matrix(Arc::new(group.transform.into_typst())),
329 inner,
330 )));
331 }
332
333 inner
334 }
335 FrameItem::Text(text) => {
336 let i = self.text(state, text);
337
338 self.spans.push_span(SourceRegion {
339 region: src_reg,
340 idx: idx as u32,
341 kind: if text.glyphs.len() == 1 {
342 SourceNodeKind::Char(text.glyphs[0].span)
343 } else {
344 SourceNodeKind::Text(text.glyphs.iter().map(|g| g.span).collect())
345 },
346 item: i,
347 });
348
349 i
350 }
351 FrameItem::Shape(shape, s) => {
352 let i = self.shape(state, shape);
353
354 self.spans.push_span(SourceRegion {
356 region: src_reg,
357 idx: idx as u32,
358 kind: SourceNodeKind::Shape(*s),
359 item: i,
360 });
361
362 i
363 }
364 FrameItem::Image(image, size, s) => {
365 let i = self.image(image, *size);
366
367 self.spans.push_span(SourceRegion {
368 region: src_reg,
369 idx: idx as u32,
370 kind: SourceNodeKind::Image(*s),
371 item: i,
372 });
373
374 i
375 }
376 FrameItem::Link(lnk, size) => {
378 is_link = true;
379 self.store(match lnk {
380 Destination::Url(url) => self.link(url, *size),
381 Destination::Position(dest) => self.position(*dest, *size),
382 Destination::Location(loc) => {
383 let dest = state.introspector.position(*loc);
385 self.position(dest, *size)
386 }
387 })
388 }
389 FrameItem::Tag(Tag::Start(elem)) => {
390 if !LINE_HINT_ELEMENTS.contains(elem.func().name()) {
391 return None;
392 }
393
394 self.store(VecItem::ContentHint('\n'))
395 }
396 FrameItem::Tag(Tag::End(..)) => return None,
397 };
399
400 Some(((*pos).into_typst(), is_link, item))
401 });
402 items.par_extend(items_iter);
403
404 items.sort_by(|x, y| {
406 let x_is_link = x.1;
407 let y_is_link = y.1;
408 if x_is_link || y_is_link {
409 if x_is_link && y_is_link {
410 return std::cmp::Ordering::Equal;
411 } else if x_is_link {
412 return std::cmp::Ordering::Greater;
413 } else {
414 return std::cmp::Ordering::Less;
415 }
416 }
417
418 std::cmp::Ordering::Equal
419 });
420
421 #[cfg(not(feature = "no-content-hint"))]
422 {
423 let c = frame.content_hint();
424 if c != '\0' {
425 items.push((Point::default(), false, self.store(VecItem::ContentHint(c))));
427 }
428 }
429
430 let g = self.store(VecItem::Group(GroupRef(
431 items.into_iter().map(|(x, _, y)| (x, y)).collect(),
432 )));
433
434 self.spans.push_span(SourceRegion {
435 region: parent,
436 idx: index as u32,
437 kind: SourceNodeKind::Group { region: src_reg },
438 item: g,
439 });
440
441 g
442 }
443
444 fn store_cached<T: Hash>(&self, cond: &T, f: impl FnOnce() -> VecItem) -> Fingerprint {
445 let cond_fg = self.fingerprint_builder.resolve_unchecked(cond);
446 self.insert_if(cond_fg, f)
447 }
448
449 fn store(&self, item: VecItem) -> Fingerprint {
450 let fingerprint = self.fingerprint_builder.resolve(&item);
451 self.insert(fingerprint, Cow::Owned(item));
452 fingerprint
453 }
454
455 fn increase_lifetime_for_item(&self, pos: &AtomicU64) {
460 let c = pos.load(std::sync::atomic::Ordering::Relaxed);
461 if ENABLE_REF_CNT && c < self.lifetime - 1 {
462 let _ = pos.compare_exchange(
471 c,
472 self.lifetime - 1,
473 std::sync::atomic::Ordering::SeqCst,
474 std::sync::atomic::Ordering::SeqCst,
475 );
476 }
477 }
478
479 fn insert_if(&self, cond: Fingerprint, f: impl FnOnce() -> VecItem) -> Fingerprint {
480 let shard = &self.cache_items.shard(cond);
481 let shard_read = shard.read();
482 if let Some(pos) = shard_read.get(&cond) {
483 self.increase_lifetime_for_item(&pos.0);
484 self.insert(pos.1, Cow::Borrowed(&pos.2));
485 return pos.1;
486 }
487
488 drop(shard_read);
489
490 let item = f();
491 let flat_fg = self.fingerprint_builder.resolve(&item);
492 self.insert(flat_fg, Cow::Borrowed(&item));
493
494 {
495 let mut shard_write = shard.write();
496 shard_write.insert(
497 cond,
498 if ENABLE_REF_CNT {
499 (AtomicU64::new(self.lifetime), flat_fg, item)
500 } else {
501 (AtomicU64::new(0), flat_fg, item)
502 },
503 );
504 }
505
506 flat_fg
507 }
508
509 fn insert(&self, fg: Fingerprint, item: Cow<VecItem>) -> bool {
510 let shard = self.items.shard(fg);
511 let shard_read = shard.read();
512 if let Some(pos) = shard_read.get(&fg) {
513 self.increase_lifetime_for_item(&pos.0);
514 return true;
515 }
516
517 let item_resolution = if ENABLE_REF_CNT {
518 self.new_items.lock().push((fg, item.into_owned()));
519 (AtomicU64::new(self.lifetime), VecItem::None)
520 } else {
521 (AtomicU64::new(0), item.into_owned())
522 };
523
524 drop(shard_read);
525 let mut shard_write = shard.write();
526 shard_write.insert(fg, item_resolution);
527 false
528 }
529
530 #[cfg(feature = "item-dashmap")]
531 fn insert_if(&self, cond: Fingerprint, f: impl FnOnce() -> VecItem) -> Fingerprint {
532 use dashmap::mapref::entry::Entry::*;
533 match self.cache_items.entry(cond) {
534 Occupied(pos) => {
535 let pos = pos.into_ref();
536 self.increase_lifetime(&pos.0);
537 self.insert(pos.1, Cow::Borrowed(&pos.2));
538 pos.1
539 }
540 Vacant(pos) => {
541 let item = f();
542 let flat_fg = self.fingerprint_builder.resolve(&item);
543 self.insert(flat_fg, Cow::Borrowed(&item));
544
545 pos.insert(if ENABLE_REF_CNT {
546 (AtomicU64::new(self.lifetime), flat_fg, item)
547 } else {
548 (AtomicU64::new(0), flat_fg, item)
549 });
550
551 flat_fg
552 }
553 }
554 }
555
556 #[cfg(feature = "item-dashmap")]
557 fn insert(&self, fg: Fingerprint, item: Cow<VecItem>) -> bool {
558 use dashmap::mapref::entry::Entry::*;
559 match self.items.entry(fg) {
560 Occupied(pos) => {
561 let pos = pos.into_ref();
562 self.increase_lifetime(&pos.0);
563 true
564 }
565 Vacant(pos) => {
566 let item_resolution = if ENABLE_REF_CNT {
567 self.new_items.lock().push((fg, item.into_owned()));
568 (AtomicU64::new(self.lifetime), VecItem::None)
569 } else {
570 (AtomicU64::new(0), item.into_owned())
571 };
572
573 pos.insert(item_resolution);
574 false
575 }
576 }
577 }
578
579 fn text(&self, state: State, text: &TypstTextItem) -> Fingerprint {
581 let stateful_fill = match text.fill {
582 Paint::Pattern(..) | Paint::Gradient(..) => {
583 Some(self.paint_text(state, text, &text.fill))
584 }
585 _ => None,
586 };
587
588 let stateful_stroke = match &text.stroke {
589 Some(FixedStroke {
590 paint: Paint::Pattern(..) | Paint::Gradient(..),
591 ..
592 }) => Some(self.paint_text(state, text, &text.stroke.as_ref().unwrap().paint)),
593 _ => None,
594 };
595
596 #[derive(Hash)]
597 struct TextHashKey<'i> {
598 stateful_fill: Option<Arc<str>>,
599 stateful_stroke: Option<Arc<str>>,
600 text: &'i TypstTextItem,
601 }
602
603 let cond = TextHashKey {
604 stateful_fill: stateful_fill.clone(),
605 stateful_stroke: stateful_stroke.clone(),
606 text,
607 };
608
609 let stateful_fill =
610 || stateful_fill.unwrap_or_else(|| self.paint_text(state, text, &text.fill));
611
612 let stateful_stroke = || {
613 stateful_stroke.unwrap_or_else(|| {
614 self.paint_text(state, text, &text.stroke.as_ref().unwrap().paint)
615 })
616 };
617
618 self.store_cached(&cond, || {
619 let font = self.glyphs.build_font(&text.font);
620
621 let mut glyphs = Vec::with_capacity(text.glyphs.len());
622 for glyph in &text.glyphs {
623 self.glyphs
624 .build_glyph(font, GlyphItem::Raw(text.font.clone(), GlyphId(glyph.id)));
625 glyphs.push((
626 glyph.x_offset.at(text.size).into_typst(),
627 glyph.x_advance.at(text.size).into_typst(),
628 glyph.id as u32,
629 ));
630 }
631
632 let glyph_chars: String = text.text.to_string();
633 let font = self.glyphs.build_font(&text.font);
636
637 let mut styles = vec![PathStyle::Fill(stateful_fill())];
638 if let Some(stroke) = text.stroke.as_ref() {
639 self.stroke(stateful_stroke, stroke, &mut styles);
640 }
641
642 VecItem::Text(TextItem {
643 content: Arc::new(TextItemContent {
644 content: glyph_chars.into(),
645 glyphs: glyphs.into(),
646 }),
647 shape: Arc::new(TextShape {
648 font,
649 size: Scalar(text.size.to_f32()),
650 dir: match text.lang.dir() {
651 Dir::LTR => "ltr",
652 Dir::RTL => "rtl",
653 Dir::TTB => "ttb",
654 Dir::BTT => "btt",
655 }
656 .into(),
657 styles,
658 }),
659 })
660 })
661 }
662
663 fn stroke(
664 &self,
665 stateful_stroke: impl FnOnce() -> ImmutStr,
666 FixedStroke {
667 paint: _,
668 thickness,
669 cap,
670 join,
671 dash,
672 miter_limit,
673 }: &FixedStroke,
674 styles: &mut Vec<PathStyle>,
675 ) {
676 if let Some(pattern) = dash.as_ref() {
678 styles.push(PathStyle::StrokeDashOffset(pattern.phase.into_typst()));
679 let d = pattern.array.clone();
680 let d = d.into_iter().map(Scalar::from_typst).collect();
681 styles.push(PathStyle::StrokeDashArray(d));
682 }
683
684 styles.push(PathStyle::StrokeWidth((*thickness).into_typst()));
685 styles.push(PathStyle::StrokeMitterLimit((*miter_limit).into_typst()));
686 match cap {
687 LineCap::Butt => {}
688 LineCap::Round => styles.push(PathStyle::StrokeLineCap("round".into())),
689 LineCap::Square => styles.push(PathStyle::StrokeLineCap("square".into())),
690 };
691 match join {
692 LineJoin::Miter => {}
693 LineJoin::Bevel => styles.push(PathStyle::StrokeLineJoin("bevel".into())),
694 LineJoin::Round => styles.push(PathStyle::StrokeLineJoin("round".into())),
695 }
696
697 styles.push(PathStyle::Stroke(stateful_stroke()));
698 }
699
700 fn shape(&self, state: State, shape: &Shape) -> Fingerprint {
702 #[derive(Hash)]
703 struct ShapeKey<'i> {
704 stateful_fill: Option<Arc<str>>,
705 stateful_stroke: Option<Arc<str>>,
706 shape: &'i Shape,
707 }
708
709 let stateful_fill = match shape.fill {
710 Some(Paint::Pattern(..) | Paint::Gradient(..)) => {
711 Some(self.paint_shape(state, shape, shape.fill.as_ref().unwrap()))
712 }
713 _ => None,
714 };
715
716 let stateful_stroke = match shape.stroke {
717 Some(FixedStroke {
718 paint: Paint::Pattern(..) | Paint::Gradient(..),
719 ..
720 }) => Some(self.paint_shape(state, shape, &shape.stroke.as_ref().unwrap().paint)),
721 _ => None,
722 };
723
724 let cond = &ShapeKey {
725 stateful_fill: stateful_fill.clone(),
726 stateful_stroke: stateful_stroke.clone(),
727 shape,
728 };
729
730 let stateful_stroke = || {
731 stateful_stroke.unwrap_or_else(|| {
732 self.paint_shape(state, shape, &shape.stroke.as_ref().unwrap().paint)
733 })
734 };
735
736 self.store_cached(cond, || {
737 let mut builder = SvgPath2DBuilder(String::new());
738 builder.move_to(0., 0.);
742 match shape.geometry {
743 Geometry::Line(target) => {
744 builder.line_to(target.x.to_f32(), target.y.to_f32());
745 }
746 Geometry::Rect(size) => {
747 let w = size.x.to_f32();
748 let h = size.y.to_f32();
749 builder.line_to(0., h);
750 builder.line_to(w, h);
751 builder.line_to(w, 0.);
752 builder.close();
753 }
754 Geometry::Path(ref path) => {
755 for elem in &path.0 {
756 match elem {
757 TypstPathItem::MoveTo(p) => {
758 builder.move_to(p.x.to_f32(), p.y.to_f32());
759 }
760 TypstPathItem::LineTo(p) => {
761 builder.line_to(p.x.to_f32(), p.y.to_f32());
762 }
763 TypstPathItem::CubicTo(p1, p2, p3) => {
764 builder.curve_to(
765 p1.x.to_f32(),
766 p1.y.to_f32(),
767 p2.x.to_f32(),
768 p2.y.to_f32(),
769 p3.x.to_f32(),
770 p3.y.to_f32(),
771 );
772 }
773 TypstPathItem::ClosePath => {
774 builder.close();
775 }
776 };
777 }
778 }
779 };
780
781 let d = builder.0.into();
782
783 let mut styles = Vec::new();
784
785 if let Some(paint_fill) = &shape.fill {
786 styles.push(PathStyle::Fill(
787 stateful_fill.unwrap_or_else(|| self.paint_shape(state, shape, paint_fill)),
788 ));
789 }
790
791 if let Some(stroke) = &shape.stroke {
792 self.stroke(stateful_stroke, stroke, &mut styles);
793 }
794
795 match shape.fill_rule {
796 FillRule::NonZero => styles.push(PathStyle::FillRule("nonzero".into())),
797 FillRule::EvenOdd => styles.push(PathStyle::FillRule("evenodd".into())),
798 }
799
800 let mut shape_size = shape.geometry.bbox_size();
801 if shape_size.x.to_pt() == 0.0 {
803 shape_size.x = TypstAbs::pt(1.0);
804 }
805
806 if shape_size.y.to_pt() == 0.0 {
807 shape_size.y = TypstAbs::pt(1.0);
808 }
809
810 let item = PathItem {
811 d,
812 size: Some(shape_size.into_typst()),
813 styles,
814 };
815
816 VecItem::Path(item)
817 })
818 }
819
820 pub fn image(&self, image: &TypstImage, size: Axes<TypstAbs>) -> Fingerprint {
821 #[derive(Hash)]
822 struct ImageKey<'i> {
823 image: &'i TypstImage,
824 size: Axes<TypstAbs>,
825 }
826
827 let cond = ImageKey { image, size };
828
829 self.store_cached(&cond, || {
830 if matches!(image.alt(), Some("!typst-embed-command")) {
831 if let Some(item) = self
832 .command_executor
833 .execute(image.data().clone(), Some(size))
834 {
835 return item;
836 }
837 }
838
839 VecItem::Image(ImageItem {
840 image: Arc::new(image.clone().into_typst()),
841 size: size.into_typst(),
842 })
843 })
844 }
845
846 fn link(&self, url: &str, size: Size) -> VecItem {
848 VecItem::Link(LinkItem {
849 href: url.into(),
850 size: size.into_typst(),
851 })
852 }
853
854 fn position(&self, pos: Position, size: Size) -> VecItem {
857 let lnk = LinkItem {
858 href: format!(
859 "@typst:handleTypstLocation(this, {}, {}, {})",
860 pos.page,
861 pos.point.x.to_f32(),
862 pos.point.y.to_f32()
863 )
864 .into(),
865 size: size.into_typst(),
866 };
867
868 VecItem::Link(lnk)
869 }
870
871 #[inline]
872 fn paint_shape(&self, state: State, shape: &Shape, g: &Paint) -> ImmutStr {
873 self.paint(state, g, |relative_to_self, is_gradient| {
874 self.paint_transform(
875 state,
876 relative_to_self,
877 || {
878 let bbox = shape.geometry.bbox_size();
879
880 let (mut x, mut y) = (bbox.x.to_f32(), bbox.y.to_f32());
882 if x == 0.0 {
883 x = 1.0;
884 }
885 if y == 0.0 {
886 y = 1.0;
887 }
888
889 ir::Transform::from_scale(ir::Scalar(x), ir::Scalar(y))
890 },
891 false,
892 is_gradient,
893 )
894 })
895 }
896
897 #[inline]
898 fn paint_text(&self, state: State, text: &TypstTextItem, g: &Paint) -> ImmutStr {
899 self.paint(state, g, |relative_to_self, is_gradient| {
900 self.paint_transform(
901 state,
902 relative_to_self,
903 || {
904 let upem = text.font.units_per_em() as f32;
905 let text_size = text.size.to_f32();
906 let text_scale = upem / text_size;
907 ir::Transform::from_scale(ir::Scalar(text_scale), ir::Scalar(-text_scale))
908 },
909 true,
910 is_gradient,
911 )
912 })
913 }
914
915 #[inline]
916 fn paint(
917 &self,
918 state: State,
919 g: &Paint,
920 mk_transform: impl FnOnce(Smart<RelativeTo>, bool) -> Transform,
921 ) -> ImmutStr {
922 match g {
923 Paint::Solid(c) => c.to_css().into(),
924 Paint::Pattern(e) => {
925 let fingerprint = self.pattern(state, e, mk_transform(e.relative(), false));
926 format!("@{}", fingerprint.as_svg_id("p")).into()
927 }
928 Paint::Gradient(g) => {
929 let fingerprint = self.gradient(g, mk_transform(g.relative(), true));
930 format!("@{}", fingerprint.as_svg_id("g")).into()
931 }
932 }
933 }
934
935 #[inline]
936 fn paint_transform(
937 &self,
938 state: State,
939 relative_to_self: Smart<RelativeTo>,
940 scale_ts: impl FnOnce() -> ir::Transform,
941 is_text: bool,
942 is_gradient: bool,
943 ) -> ir::Transform {
944 let relative_to_self = match relative_to_self {
945 Smart::Auto => !is_text,
946 Smart::Custom(t) => t == RelativeTo::Self_,
947 };
948
949 let transform = match (is_gradient, relative_to_self) {
950 (true, true) => return scale_ts(),
951 (false, true) if is_text => return scale_ts(),
952 (false, true) => return ir::Transform::identity(),
953 (true, false) => state.body_inv_transform(),
954 (false, false) => state.inv_transform(),
955 };
956
957 if is_text {
958 transform.post_concat(scale_ts())
959 } else {
960 transform
961 }
962 }
963
964 fn gradient(&self, g: &Gradient, transform: ir::Transform) -> Fingerprint {
965 let mut stops = Vec::with_capacity(g.stops_ref().len());
966 for (c, step) in g.stops_ref() {
967 let [r, g, b, a] = c.to_rgb().to_vec4_u8();
968 stops.push((Rgba8Item { r, g, b, a }, (*step).into_typst()))
969 }
970
971 let anti_alias = g.anti_alias();
972 let space = g.space().into_typst();
973
974 let mut styles = Vec::new();
975 let kind = match g {
976 Gradient::Linear(l) => GradientKind::Linear(l.angle.into_typst()),
977 Gradient::Radial(l) => {
978 if l.center.x != TypstRatio::new(0.5) || l.center.y != TypstRatio::new(0.5) {
979 styles.push(GradientStyle::Center(l.center.into_typst()));
980 }
981
982 if l.focal_center.x != TypstRatio::new(0.5)
983 || l.focal_center.y != TypstRatio::new(0.5)
984 {
985 styles.push(GradientStyle::FocalCenter(l.focal_center.into_typst()));
986 }
987
988 if l.focal_radius != TypstRatio::zero() {
989 styles.push(GradientStyle::FocalRadius(l.focal_radius.into_typst()));
990 }
991
992 GradientKind::Radial(l.radius.into_typst())
993 }
994 Gradient::Conic(l) => {
995 if l.center.x != TypstRatio::new(0.5) || l.center.y != TypstRatio::new(0.5) {
996 styles.push(GradientStyle::Center(l.center.into_typst()));
997 }
998
999 GradientKind::Conic(l.angle.into_typst())
1000 }
1001 };
1002
1003 let item = self.store(VecItem::Gradient(Arc::new(GradientItem {
1004 stops,
1005 anti_alias,
1006 space,
1007 kind,
1008 styles,
1009 })));
1010
1011 self.store(VecItem::ColorTransform(Arc::new(ColorTransform {
1012 transform,
1013 item,
1014 })))
1015 }
1016
1017 fn pattern(&self, state: State, g: &Pattern, transform: ir::Transform) -> Fingerprint {
1018 let frame = self.frame(state, g.frame(), 0, 0);
1019
1020 let item = self.store(VecItem::Pattern(Arc::new(PatternItem {
1021 frame,
1022 size: g.size().into_typst(),
1023 spacing: g.spacing().into_typst(),
1024 })));
1025
1026 self.store(VecItem::ColorTransform(Arc::new(ColorTransform {
1027 transform,
1028 item,
1029 })))
1030 }
1031}
1032
1033impl IncrTypst2VecPass {
1034 pub fn increment_lifetime(&mut self) {
1040 self.new_items.get_mut().clear();
1041 self.glyphs.new_fonts.get_mut().clear();
1042 self.glyphs.new_glyphs.get_mut().clear();
1043 self.lifetime += 2;
1044 self.glyphs.lifetime = self.lifetime;
1045 }
1046
1047 pub fn gc(&mut self, threshold: u64) -> Vec<Fingerprint> {
1049 let gc_items = Arc::new(Mutex::new(vec![]));
1050
1051 let gc_threshold = self.lifetime.saturating_sub(threshold);
1054
1055 self.items.as_mut_slice().par_iter_mut().for_each(|e| {
1056 e.get_mut().retain(|k, v| {
1057 if v.0.load(std::sync::atomic::Ordering::Relaxed) < gc_threshold {
1058 gc_items.lock().push(*k);
1059 false
1060 } else {
1061 true
1062 }
1063 });
1064 });
1065
1066 let cache_threshold = self.lifetime.saturating_sub(threshold);
1068 self.cache_items
1069 .as_mut_slice()
1070 .par_iter_mut()
1071 .for_each(|e| {
1072 e.get_mut().retain(|_, v| {
1073 v.0.load(std::sync::atomic::Ordering::Relaxed) >= cache_threshold
1074 });
1075 });
1076
1077 Arc::try_unwrap(gc_items).unwrap().into_inner()
1078 }
1079
1080 pub fn finalize_delta(&mut self) -> Module {
1082 let (fonts, glyphs) = self.glyphs.finalize_delta();
1084
1085 let items = { ItemMap::from_iter(std::mem::take(self.new_items.lock().deref_mut())) };
1087
1088 Module {
1089 fonts,
1090 glyphs,
1091 items,
1092 }
1093 }
1094}
1095
1096static LINE_HINT_ELEMENTS: std::sync::LazyLock<std::collections::HashSet<&'static str>> =
1103 std::sync::LazyLock::new(|| {
1104 let mut set = std::collections::HashSet::new();
1105 set.insert("heading");
1106 set
1107 });