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