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
46pub 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 pub transform: Transform,
54 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 pub fn pre_translate(self, pos: Point) -> Self {
69 self.pre_concat(Transform::from_translate(pos.x, pos.y))
70 }
71
72 pub fn pre_concat(self, transform: ir::Transform) -> Self {
74 Self {
75 transform: self.transform.pre_concat(transform),
76 ..self
77 }
78 }
79
80 pub fn with_size(self, size: ir::Size) -> Self {
82 Self { size, ..self }
83 }
84
85 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
110pub 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<false>;
126pub type IncrTypst2VecPass = Typst2VecPassImpl<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 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 let mut builder = SvgPath2DBuilder(String::new());
348
349 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 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 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 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 };
464
465 Some(((*pos).into_typst(), is_link, item))
466 });
467 items.par_extend(items_iter);
468
469 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn gc(&mut self, threshold: u64) -> Vec<Fingerprint> {
1153 let gc_items = Arc::new(Mutex::new(vec![]));
1154
1155 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 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 pub fn finalize_delta(&mut self) -> Module {
1186 let (fonts, glyphs) = self.glyphs.finalize_delta();
1188
1189 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
1200static 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 });