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::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 let mut builder = SvgPath2DBuilder(String::new());
343
344 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 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 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 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 };
454
455 Some(((*pos).into_typst(), is_link, item))
456 });
457 items.par_extend(items_iter);
458
459 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn gc(&mut self, threshold: u64) -> Vec<Fingerprint> {
1143 let gc_items = Arc::new(Mutex::new(vec![]));
1144
1145 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 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 pub fn finalize_delta(&mut self) -> Module {
1176 let (fonts, glyphs) = self.glyphs.finalize_delta();
1178
1179 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
1190static 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 });