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