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