reflexo_typst2vec/pass/
typst2vec.rs

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    /// The transform of the current item.
46    pub transform: Transform,
47    /// The size of the first hard frame in the hierarchy.
48    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    /// Pre translate the current item's transform.
61    pub fn pre_translate(self, pos: Point) -> Self {
62        self.pre_concat(Transform::from_translate(pos.x, pos.y))
63    }
64
65    /// Pre concat the current item's transform.
66    pub fn pre_concat(self, transform: ir::Transform) -> Self {
67        Self {
68            transform: self.transform.pre_concat(transform),
69            ..self
70        }
71    }
72
73    /// Sets the size of the first hard frame in the hierarchy.
74    pub fn with_size(self, size: ir::Size) -> Self {
75        Self { size, ..self }
76    }
77
78    /// Sets the current item's transform.
79    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
103/// Intermediate representation of a flatten vector item.
104pub 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</* ENABLE_REF_CNT */ false>;
119pub type IncrTypst2VecPass = Typst2VecPassImpl</* ENABLE_REF_CNT */ 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                // todo: here introduces risk to font collision
155                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                        // todo: merge
287                        let mut builder = SvgPath2DBuilder(String::new());
288
289                        // to ensure that our shape focus on the original point
290                        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                    // todo: fill rule
355                    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                // Meta::Link(_) => Fingerprint::from_u128(0),
377                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                            // todo: process location before lowering
384                            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                // todo: support page label
398            };
399
400            Some(((*pos).into_typst(), is_link, item))
401        });
402        items.par_extend(items_iter);
403
404        // swap link items
405        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                // todo: cache content hint
426                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    /// Increases the lifetime of an item.
456    ///
457    /// Note: See [`Self::increment_lifetime`], the `self.lifetime` increases by
458    /// 2 each time.
459    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            // Note that the Vec2Item is locked by mutable reference. And during update,
463            // lifetime will be updated to either self.lifetime or self.lifetime
464            // - 1. This indicates that it is fine to ignore the result of compare_exchange.
465            //
466            // If compare_exchange fails, it means that it is updated to self.lifetime
467            // Otherwise, it is updated to self.lifetime - 1
468            //
469            // Both cases are fine, as we renew the lifetime of the item.
470            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    /// Convert a text into vector item.
580    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 mut extras = ExtraSvgItems::default();
634
635            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        // todo: default miter_limit, thickness
677        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    // /// Convert a geometrical shape into vector item.
701    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            // let mut extras = ExtraSvgItems::default();
739
740            // to ensure that our shape focus on the original point
741            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            // Edge cases for strokes.
802            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    // /// Convert a link into vector item.
847    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    // /// Convert a document position into vector item.
855    // #[comemo::memoize]
856    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                    // Edge cases for strokes.
881                    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    /// Increment the lifetime of the module.
1035    /// It increments by 2 which is used to distinguish between the
1036    /// retained items and the new items.
1037    /// Assuming that the old lifetime is 'l,
1038    /// the retained and new lifetime will be 'l + 1 and 'l + 2, respectively.
1039    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    /// Perform garbage collection with given threshold.
1048    pub fn gc(&mut self, threshold: u64) -> Vec<Fingerprint> {
1049        let gc_items = Arc::new(Mutex::new(vec![]));
1050
1051        // a threshold is set by current lifetime subtracted by the given threshold.
1052        // It uses saturating_sub to prevent underflow (u64).
1053        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        // Same as above
1067        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    /// Finalize modules containing new vector items.
1081    pub fn finalize_delta(&mut self) -> Module {
1082        // filter glyphs by lifetime
1083        let (fonts, glyphs) = self.glyphs.finalize_delta();
1084
1085        // filter items by lifetime
1086        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
1096// impl<'m, const ENABLE_REF_CNT: bool> ItemIndice<'m> for
1097// ConvertImpl<ENABLE_REF_CNT> {     fn get_item(&self, value: &Fingerprint) ->
1098// Option<&'m VecItem> {         self.items.get(value).map(|item| &item.1)
1099//     }
1100// }
1101
1102static 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    });