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