1use comemo::Track;
2use ecow::{EcoVec, eco_format};
3use smallvec::smallvec;
4use typst_library::diag::{At, SourceResult, bail};
5use typst_library::foundations::{
6 Content, Context, NativeElement, NativeRuleMap, Packed, Resolve, ShowFn, Smart,
7 StyleChain, Synthesize, Target, dict,
8};
9use typst_library::introspection::{Counter, Locator, LocatorLink};
10use typst_library::layout::{
11 Abs, AlignElem, Alignment, Axes, BlockBody, BlockElem, ColumnsElem, Em,
12 FixedAlignment, GridCell, GridChild, GridElem, GridItem, HAlignment, HElem, HideElem,
13 InlineElem, LayoutElem, Length, MoveElem, OuterVAlignment, PadElem, PageElem,
14 PlaceElem, PlacementScope, Region, Rel, RepeatElem, RotateElem, ScaleElem, Sides,
15 Size, Sizing, SkewElem, Spacing, StackChild, StackElem, TrackSizings, VElem,
16};
17use typst_library::math::EquationElem;
18use typst_library::model::{
19 Attribution, BibliographyElem, CiteElem, CiteGroup, CslIndentElem, CslLightElem,
20 Destination, DirectLinkElem, EmphElem, EnumElem, FigureCaption, FigureElem,
21 FootnoteElem, FootnoteEntry, HeadingElem, LinkElem, LinkMarker, ListElem,
22 OutlineElem, OutlineEntry, ParElem, ParbreakElem, QuoteElem, RefElem, StrongElem,
23 TableCell, TableElem, TermsElem, TitleElem, Works,
24};
25use typst_library::pdf::{ArtifactElem, ArtifactKind, AttachElem, PdfMarkerTag};
26use typst_library::text::{
27 DecoLine, Decoration, HighlightElem, ItalicToggle, LinebreakElem, LocalName,
28 OverlineElem, RawElem, RawLine, ScriptKind, ShiftSettings, Smallcaps, SmallcapsElem,
29 SmartQuoteElem, SmartQuotes, SpaceElem, StrikeElem, SubElem, SuperElem, TextElem,
30 TextSize, UnderlineElem, WeightDelta,
31};
32use typst_library::visualize::{
33 CircleElem, CurveElem, EllipseElem, ImageElem, LineElem, PathElem, PolygonElem,
34 RectElem, SquareElem, Stroke,
35};
36use typst_utils::{Get, Numeric};
37
38pub fn register(rules: &mut NativeRuleMap) {
40 use Target::Paged;
41
42 rules.register(Paged, STRONG_RULE);
44 rules.register(Paged, EMPH_RULE);
45 rules.register(Paged, LIST_RULE);
46 rules.register(Paged, ENUM_RULE);
47 rules.register(Paged, TERMS_RULE);
48 rules.register(Paged, LINK_MARKER_RULE);
49 rules.register(Paged, LINK_RULE);
50 rules.register(Paged, DIRECT_LINK_RULE);
51 rules.register(Paged, TITLE_RULE);
52 rules.register(Paged, HEADING_RULE);
53 rules.register(Paged, FIGURE_RULE);
54 rules.register(Paged, FIGURE_CAPTION_RULE);
55 rules.register(Paged, QUOTE_RULE);
56 rules.register(Paged, FOOTNOTE_RULE);
57 rules.register(Paged, FOOTNOTE_ENTRY_RULE);
58 rules.register(Paged, OUTLINE_RULE);
59 rules.register(Paged, OUTLINE_ENTRY_RULE);
60 rules.register(Paged, REF_RULE);
61 rules.register(Paged, CITE_GROUP_RULE);
62 rules.register(Paged, BIBLIOGRAPHY_RULE);
63 rules.register(Paged, CSL_LIGHT_RULE);
64 rules.register(Paged, CSL_INDENT_RULE);
65 rules.register(Paged, TABLE_RULE);
66 rules.register(Paged, TABLE_CELL_RULE);
67
68 rules.register(Paged, SUB_RULE);
70 rules.register(Paged, SUPER_RULE);
71 rules.register(Paged, UNDERLINE_RULE);
72 rules.register(Paged, OVERLINE_RULE);
73 rules.register(Paged, STRIKE_RULE);
74 rules.register(Paged, HIGHLIGHT_RULE);
75 rules.register(Paged, SMALLCAPS_RULE);
76 rules.register(Paged, RAW_RULE);
77 rules.register(Paged, RAW_LINE_RULE);
78
79 rules.register(Paged, ALIGN_RULE);
81 rules.register(Paged, PAD_RULE);
82 rules.register(Paged, COLUMNS_RULE);
83 rules.register(Paged, STACK_RULE);
84 rules.register(Paged, GRID_RULE);
85 rules.register(Paged, GRID_CELL_RULE);
86 rules.register(Paged, MOVE_RULE);
87 rules.register(Paged, SCALE_RULE);
88 rules.register(Paged, ROTATE_RULE);
89 rules.register(Paged, SKEW_RULE);
90 rules.register(Paged, REPEAT_RULE);
91 rules.register(Paged, HIDE_RULE);
92 rules.register(Paged, LAYOUT_RULE);
93
94 rules.register(Paged, IMAGE_RULE);
96 rules.register(Paged, LINE_RULE);
97 rules.register(Paged, RECT_RULE);
98 rules.register(Paged, SQUARE_RULE);
99 rules.register(Paged, ELLIPSE_RULE);
100 rules.register(Paged, CIRCLE_RULE);
101 rules.register(Paged, POLYGON_RULE);
102 rules.register(Paged, CURVE_RULE);
103 rules.register(Paged, PATH_RULE);
104
105 rules.register(Paged, EQUATION_RULE);
107
108 rules.register(Paged, ATTACH_RULE);
110 rules.register(Paged, ARTIFACT_RULE);
111 rules.register(Paged, PDF_MARKER_TAG_RULE);
112}
113
114const STRONG_RULE: ShowFn<StrongElem> = |elem, _, styles| {
115 Ok(elem
116 .body
117 .clone()
118 .set(TextElem::delta, WeightDelta(elem.delta.get(styles))))
119};
120
121const EMPH_RULE: ShowFn<EmphElem> =
122 |elem, _, _| Ok(elem.body.clone().set(TextElem::emph, ItalicToggle(true)));
123
124const LIST_RULE: ShowFn<ListElem> = |elem, _, styles| {
125 let tight = elem.tight.get(styles);
126
127 let mut realized = BlockElem::multi_layouter(elem.clone(), crate::lists::layout_list)
128 .pack()
129 .spanned(elem.span());
130
131 if tight {
132 let spacing = elem
133 .spacing
134 .get(styles)
135 .unwrap_or_else(|| styles.get(ParElem::leading));
136 let v = VElem::new(spacing.into()).with_weak(true).with_attach(true).pack();
137 realized = v + realized;
138 }
139
140 Ok(realized)
141};
142
143const ENUM_RULE: ShowFn<EnumElem> = |elem, _, styles| {
144 let tight = elem.tight.get(styles);
145
146 let mut realized = BlockElem::multi_layouter(elem.clone(), crate::lists::layout_enum)
147 .pack()
148 .spanned(elem.span());
149
150 if tight {
151 let spacing = elem
152 .spacing
153 .get(styles)
154 .unwrap_or_else(|| styles.get(ParElem::leading));
155 let v = VElem::new(spacing.into()).with_weak(true).with_attach(true).pack();
156 realized = v + realized;
157 }
158
159 Ok(realized)
160};
161
162const TERMS_RULE: ShowFn<TermsElem> = |elem, _, styles| {
163 let span = elem.span();
164 let tight = elem.tight.get(styles);
165
166 let separator = elem.separator.get_ref(styles);
167 let indent = elem.indent.get(styles);
168 let hanging_indent = elem.hanging_indent.get(styles);
169 let gutter = elem.spacing.get(styles).unwrap_or_else(|| {
170 if tight { styles.get(ParElem::leading) } else { styles.get(ParElem::spacing) }
171 });
172
173 let pad = hanging_indent + indent;
174 let unpad = (!hanging_indent.is_zero())
175 .then(|| HElem::new((-hanging_indent).into()).pack().spanned(span));
176
177 let mut children = vec![];
178 for child in elem.children.iter() {
179 let mut seq = vec![];
180 seq.extend(unpad.clone());
181 seq.push(PdfMarkerTag::TermsItemLabel(child.term.clone().strong()));
182 seq.push(separator.clone().artifact(ArtifactKind::Other));
183 seq.push(child.description.clone());
184
185 if !tight {
187 seq.push(ParbreakElem::shared().clone());
188 }
189
190 let item = Content::sequence(seq).spanned(child.span());
191 children.push(StackChild::Block(PdfMarkerTag::TermsItemBody(item)));
192 }
193
194 let padding =
195 Sides::default().with(styles.resolve(TextElem::dir).start(), pad.into());
196
197 let mut realized = StackElem::new(children)
198 .with_spacing(Some(gutter.into()))
199 .pack()
200 .spanned(span)
201 .padded(padding)
202 .set(TermsElem::within, true);
203
204 if tight {
205 let spacing = elem
206 .spacing
207 .get(styles)
208 .unwrap_or_else(|| styles.get(ParElem::leading));
209 let v = VElem::new(spacing.into())
210 .with_weak(true)
211 .with_attach(true)
212 .pack()
213 .spanned(span);
214 realized = v + realized;
215 }
216
217 Ok(realized)
218};
219
220const LINK_MARKER_RULE: ShowFn<LinkMarker> = |elem, _, _| Ok(elem.body.clone());
221
222const LINK_RULE: ShowFn<LinkElem> = |elem, engine, styles| {
223 let span = elem.span();
224 let body = elem.body.clone();
225 let dest = elem.dest.resolve(engine.introspector).at(elem.span())?;
226 let alt = dest.alt_text(engine, styles)?;
227 Ok(LinkMarker::new(body, Some(alt))
230 .pack()
231 .spanned(span)
232 .set(LinkElem::current, Some(dest)))
233};
234
235const DIRECT_LINK_RULE: ShowFn<DirectLinkElem> = |elem, _, _| {
236 let dest = Destination::Location(elem.loc);
237 Ok(elem.body.clone().linked(dest, elem.alt.clone()))
238};
239
240const TITLE_RULE: ShowFn<TitleElem> = |elem, _, styles| {
241 Ok(BlockElem::new()
242 .with_body(Some(BlockBody::Content(elem.resolve_body(styles).at(elem.span())?)))
243 .pack())
244};
245
246const HEADING_RULE: ShowFn<HeadingElem> = |elem, engine, styles| {
247 const SPACING_TO_NUMBERING: Em = Em::new(0.3);
248
249 let span = elem.span();
250 let mut realized = elem.body.clone();
251
252 let hanging_indent = elem.hanging_indent.get(styles);
253 let mut indent = match hanging_indent {
254 Smart::Custom(length) => length.resolve(styles),
255 Smart::Auto => Abs::zero(),
256 };
257
258 if let Some(numbering) = elem.numbering.get_ref(styles).as_ref() {
259 let location = elem.location().unwrap();
260 let numbering = Counter::of(HeadingElem::ELEM)
261 .display_at_loc(engine, location, styles, numbering)?
262 .spanned(span);
263 let align = styles.resolve(AlignElem::alignment);
264
265 if hanging_indent.is_auto() && align.x == FixedAlignment::Start {
266 let pod = Region::new(Axes::splat(Abs::inf()), Axes::splat(false));
267
268 let link = LocatorLink::measure(location);
271 let size = (engine.routines.layout_frame)(
272 engine,
273 &numbering,
274 Locator::link(&link),
275 styles,
276 pod,
277 )?
278 .size();
279
280 indent = size.x + SPACING_TO_NUMBERING.resolve(styles);
281 }
282
283 let spacing = HElem::new(SPACING_TO_NUMBERING.into()).with_weak(true).pack();
284
285 realized = numbering + spacing + realized;
286 }
287
288 let block = if indent != Abs::zero() {
289 let body = HElem::new((-indent).into()).pack() + realized;
290 let inset = Sides::default()
291 .with(styles.resolve(TextElem::dir).start(), Some(indent.into()));
292 BlockElem::new()
293 .with_body(Some(BlockBody::Content(body)))
294 .with_inset(inset)
295 } else {
296 BlockElem::new().with_body(Some(BlockBody::Content(realized)))
297 };
298
299 Ok(block.pack())
300};
301
302const FIGURE_RULE: ShowFn<FigureElem> = |elem, _, styles| {
303 let span = elem.span();
304 let mut realized = elem.body.clone();
305
306 if let Some(caption) = elem.caption.get_cloned(styles) {
308 let (first, second) = match caption.position.get(styles) {
309 OuterVAlignment::Top => (caption.pack(), realized),
310 OuterVAlignment::Bottom => (realized, caption.pack()),
311 };
312 realized = Content::sequence(vec![
313 first,
314 VElem::new(elem.gap.get(styles).into())
315 .with_weak(true)
316 .pack()
317 .spanned(span),
318 second,
319 ]);
320 }
321
322 realized += ParbreakElem::shared().clone().spanned(span);
324
325 realized = BlockElem::new()
327 .with_body(Some(BlockBody::Content(realized)))
328 .pack()
329 .spanned(span);
330
331 if let Some(align) = elem.placement.get(styles) {
333 realized = PlaceElem::new(realized)
334 .with_alignment(align.map(|align| HAlignment::Center + align))
335 .with_scope(elem.scope.get(styles))
336 .with_float(true)
337 .pack()
338 .spanned(span);
339 } else if elem.scope.get(styles) == PlacementScope::Parent {
340 bail!(
341 span,
342 "parent-scoped placement is only available for floating figures";
343 hint: "you can enable floating placement with `figure(placement: auto, ..)`"
344 );
345 }
346
347 Ok(realized)
348};
349
350const FIGURE_CAPTION_RULE: ShowFn<FigureCaption> = |elem, engine, styles| {
351 Ok(BlockElem::new()
352 .with_body(Some(BlockBody::Content(elem.realize(engine, styles)?)))
353 .pack())
354};
355
356const QUOTE_RULE: ShowFn<QuoteElem> = |elem, _, styles| {
357 let span = elem.span();
358 let block = elem.block.get(styles);
359
360 let mut realized = elem.body.clone();
361
362 if elem.quotes.get(styles).unwrap_or(!block) {
363 let hole = HElem::hole();
365 let sticky = Content::sequence([hole.clone(), realized, hole.clone()]);
366 realized = QuoteElem::quoted(sticky, styles);
367 }
368
369 let attribution = elem.attribution.get_ref(styles);
370
371 if block {
372 realized = BlockElem::new()
373 .with_body(Some(BlockBody::Content(realized)))
374 .pack()
375 .spanned(span);
376
377 if let Some(attribution) = attribution.as_ref() {
378 let gap = Spacing::Rel(Em::new(0.9).into());
380 let v = VElem::new(gap).with_weak(true).pack();
381 realized += v;
382 realized += BlockElem::new()
383 .with_body(Some(BlockBody::Content(attribution.realize(span))))
384 .pack()
385 .aligned(Alignment::END);
386 }
387
388 realized = PadElem::new(realized).pack();
389 } else if let Some(Attribution::Label(label)) = attribution {
390 realized += SpaceElem::shared().clone();
391 realized += CiteElem::new(*label).pack().spanned(span);
392 }
393
394 Ok(realized)
395};
396
397const FOOTNOTE_RULE: ShowFn<FootnoteElem> = |elem, engine, styles| {
398 let span = elem.span();
399 let (dest, num) = elem.realize(engine, styles)?;
400 let alt = FootnoteElem::alt_text(styles, &num.plain_text());
401 let sup = SuperElem::new(num).pack().spanned(span).linked(dest, Some(alt));
402 Ok(HElem::hole().clone() + PdfMarkerTag::Label(sup))
403};
404
405const FOOTNOTE_ENTRY_RULE: ShowFn<FootnoteEntry> = |elem, engine, styles| {
406 let number_gap = Em::new(0.05);
407 let (prefix, body) = elem.realize(engine, styles)?;
408 Ok(Content::sequence([
409 HElem::new(elem.indent.get(styles).into()).pack(),
410 PdfMarkerTag::Label(prefix),
411 HElem::new(number_gap.into()).with_weak(true).pack(),
412 body,
413 ]))
414};
415
416const OUTLINE_RULE: ShowFn<OutlineElem> = |elem, engine, styles| {
417 let title = elem.realize_title(styles);
418 let entries = elem.realize_flat(engine, styles)?;
419 let entries = entries.into_iter().map(|entry| entry.pack());
420 let body = PdfMarkerTag::OutlineBody(Content::sequence(entries));
421 Ok(Content::sequence(title.into_iter().chain(Some(body))))
422};
423
424const OUTLINE_ENTRY_RULE: ShowFn<OutlineEntry> = |elem, engine, styles| {
425 let span = elem.span();
426 let context = Context::new(None, Some(styles));
427 let context = context.track();
428
429 let prefix = elem.prefix(engine, context, span)?;
430 let body = elem.body().at(span)?;
431 let page = elem.page(engine, context, span)?;
432 let alt = {
433 let prefix = prefix.as_ref().map(|p| p.plain_text()).unwrap_or_default();
434 let body = body.plain_text();
435 let page_str = PageElem::local_name_in(styles);
436 let page_nr = page.plain_text();
437 let quotes = SmartQuotes::get(
438 styles.get_ref(SmartQuoteElem::quotes),
439 styles.get(TextElem::lang),
440 styles.get(TextElem::region),
441 styles.get(SmartQuoteElem::alternative),
442 );
443 let open = quotes.double_open;
444 let close = quotes.double_close;
445 eco_format!("{prefix} {open}{body}{close} {page_str} {page_nr}",)
446 };
447 let inner = elem.build_inner(context, span, body, page)?;
448 let block = if elem.element.is::<EquationElem>() {
449 let body = prefix.unwrap_or_default() + inner;
451 BlockElem::new()
452 .with_body(Some(BlockBody::Content(body)))
453 .pack()
454 .spanned(span)
455 } else {
456 elem.indented(engine, context, span, prefix, inner, Em::new(0.5).into())?
457 };
458
459 let loc = elem.element_location().at(span)?;
460 Ok(block.linked(Destination::Location(loc), Some(alt)))
461};
462
463const REF_RULE: ShowFn<RefElem> = |elem, engine, styles| elem.realize(engine, styles);
464
465const CITE_GROUP_RULE: ShowFn<CiteGroup> = |elem, engine, _| elem.realize(engine);
466
467const BIBLIOGRAPHY_RULE: ShowFn<BibliographyElem> = |elem, engine, styles| {
468 const COLUMN_GUTTER: Em = Em::new(0.65);
469 const INDENT: Em = Em::new(1.5);
470
471 let span = elem.span();
472
473 let mut seq = vec![];
474 seq.extend(elem.realize_title(styles));
475
476 let works = Works::generate(engine).at(span)?;
477 let references = works.references(elem, styles)?;
478
479 if references.iter().any(|(prefix, ..)| prefix.is_some()) {
480 let row_gutter = styles.get(ParElem::spacing);
481
482 let mut cells = vec![];
483 for (prefix, reference, loc) in references {
484 let prefix = PdfMarkerTag::ListItemLabel(
485 prefix.clone().unwrap_or_default().located(*loc),
486 );
487 cells.push(GridChild::Item(GridItem::Cell(
488 Packed::new(GridCell::new(prefix)).spanned(span),
489 )));
490
491 let reference = PdfMarkerTag::BibEntry(reference.clone());
492 cells.push(GridChild::Item(GridItem::Cell(
493 Packed::new(GridCell::new(reference)).spanned(span),
494 )));
495 }
496
497 let grid = GridElem::new(cells)
498 .with_columns(TrackSizings(smallvec![Sizing::Auto; 2]))
499 .with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()]))
500 .with_row_gutter(TrackSizings(smallvec![row_gutter.into()]));
501 let mut packed = Packed::new(grid).spanned(span);
502 packed.synthesize(engine, styles)?;
503 let block = BlockElem::multi_layouter(packed, crate::grid::layout_grid).pack();
506
507 seq.push(PdfMarkerTag::Bibliography(true, block));
509 } else {
510 let mut body = vec![];
511 for (_, reference, loc) in references {
512 let realized = PdfMarkerTag::BibEntry(reference.clone().located(*loc));
513 let block = if works.hanging_indent {
514 let body = HElem::new((-INDENT).into()).pack() + realized;
515 let inset = Sides::default()
516 .with(styles.resolve(TextElem::dir).start(), Some(INDENT.into()));
517 BlockElem::new()
518 .with_body(Some(BlockBody::Content(body)))
519 .with_inset(inset)
520 } else {
521 BlockElem::new().with_body(Some(BlockBody::Content(realized)))
522 };
523
524 body.push(block.pack().spanned(span));
525 }
526 seq.push(PdfMarkerTag::Bibliography(false, Content::sequence(body)));
527 }
528
529 Ok(Content::sequence(seq))
530};
531
532const CSL_LIGHT_RULE: ShowFn<CslLightElem> =
533 |elem, _, _| Ok(elem.body.clone().set(TextElem::delta, WeightDelta(-100)));
534
535const CSL_INDENT_RULE: ShowFn<CslIndentElem> =
536 |elem, _, _| Ok(PadElem::new(elem.body.clone()).pack());
537
538const TABLE_RULE: ShowFn<TableElem> = |elem, _, _| {
539 Ok(BlockElem::multi_layouter(elem.clone(), crate::grid::layout_table).pack())
540};
541
542const TABLE_CELL_RULE: ShowFn<TableCell> = |elem, _, styles| {
543 show_cell(elem.body.clone(), elem.inset.get(styles), elem.align.get(styles))
544};
545
546const SUB_RULE: ShowFn<SubElem> = |elem, _, styles| {
547 show_script(
548 styles,
549 elem.body.clone(),
550 elem.typographic.get(styles),
551 elem.baseline.get(styles),
552 elem.size.get(styles),
553 ScriptKind::Sub,
554 )
555};
556
557const SUPER_RULE: ShowFn<SuperElem> = |elem, _, styles| {
558 show_script(
559 styles,
560 elem.body.clone(),
561 elem.typographic.get(styles),
562 elem.baseline.get(styles),
563 elem.size.get(styles),
564 ScriptKind::Super,
565 )
566};
567
568fn show_script(
569 styles: StyleChain,
570 body: Content,
571 typographic: bool,
572 baseline: Smart<Length>,
573 size: Smart<TextSize>,
574 kind: ScriptKind,
575) -> SourceResult<Content> {
576 let font_size = styles.resolve(TextElem::size);
577 Ok(body.set(
578 TextElem::shift_settings,
579 Some(ShiftSettings {
580 typographic,
581 shift: baseline.map(|l| -Em::from_length(l, font_size)),
582 size: size.map(|t| Em::from_length(t.0, font_size)),
583 kind,
584 }),
585 ))
586}
587
588const UNDERLINE_RULE: ShowFn<UnderlineElem> = |elem, _, styles| {
589 Ok(elem.body.clone().set(
590 TextElem::deco,
591 smallvec![Decoration {
592 line: DecoLine::Underline {
593 stroke: elem.stroke.resolve(styles).unwrap_or_default(),
594 offset: elem.offset.resolve(styles),
595 evade: elem.evade.get(styles),
596 background: elem.background.get(styles),
597 },
598 extent: elem.extent.resolve(styles),
599 }],
600 ))
601};
602
603const OVERLINE_RULE: ShowFn<OverlineElem> = |elem, _, styles| {
604 Ok(elem.body.clone().set(
605 TextElem::deco,
606 smallvec![Decoration {
607 line: DecoLine::Overline {
608 stroke: elem.stroke.resolve(styles).unwrap_or_default(),
609 offset: elem.offset.resolve(styles),
610 evade: elem.evade.get(styles),
611 background: elem.background.get(styles),
612 },
613 extent: elem.extent.resolve(styles),
614 }],
615 ))
616};
617
618const STRIKE_RULE: ShowFn<StrikeElem> = |elem, _, styles| {
619 Ok(elem.body.clone().set(
620 TextElem::deco,
621 smallvec![Decoration {
622 line: DecoLine::Strikethrough {
624 stroke: elem.stroke.resolve(styles).unwrap_or_default(),
625 offset: elem.offset.resolve(styles),
626 background: elem.background.get(styles),
627 },
628 extent: elem.extent.resolve(styles),
629 }],
630 ))
631};
632
633const HIGHLIGHT_RULE: ShowFn<HighlightElem> = |elem, _, styles| {
634 Ok(elem.body.clone().set(
635 TextElem::deco,
636 smallvec![Decoration {
637 line: DecoLine::Highlight {
638 fill: elem.fill.get_cloned(styles),
639 stroke: elem
640 .stroke
641 .resolve(styles)
642 .unwrap_or_default()
643 .map(|stroke| stroke.map(Stroke::unwrap_or_default)),
644 top_edge: elem.top_edge.get(styles),
645 bottom_edge: elem.bottom_edge.get(styles),
646 radius: elem.radius.resolve(styles).unwrap_or_default(),
647 },
648 extent: elem.extent.resolve(styles),
649 }],
650 ))
651};
652
653const SMALLCAPS_RULE: ShowFn<SmallcapsElem> = |elem, _, styles| {
654 let sc = if elem.all.get(styles) { Smallcaps::All } else { Smallcaps::Minuscules };
655 Ok(elem.body.clone().set(TextElem::smallcaps, Some(sc)))
656};
657
658const RAW_RULE: ShowFn<RawElem> = |elem, _, styles| {
659 let lines = elem.lines.as_deref().unwrap_or_default();
660
661 let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1));
662 for (i, line) in lines.iter().enumerate() {
663 if i != 0 {
664 seq.push(LinebreakElem::shared().clone());
665 }
666
667 seq.push(line.clone().pack());
668 }
669
670 let mut realized = Content::sequence(seq);
671
672 if elem.block.get(styles) {
673 realized = realized.aligned(elem.align.get(styles).into());
675 realized = BlockElem::new()
676 .with_body(Some(BlockBody::Content(realized)))
677 .pack()
678 .spanned(elem.span());
679 }
680
681 Ok(realized)
682};
683
684const RAW_LINE_RULE: ShowFn<RawLine> = |elem, _, _| Ok(elem.body.clone());
685
686const ALIGN_RULE: ShowFn<AlignElem> =
687 |elem, _, styles| Ok(elem.body.clone().aligned(elem.alignment.get(styles)));
688
689const PAD_RULE: ShowFn<PadElem> = |elem, _, _| {
690 Ok(BlockElem::multi_layouter(elem.clone(), crate::pad::layout_pad).pack())
691};
692
693const COLUMNS_RULE: ShowFn<ColumnsElem> = |elem, _, _| {
694 Ok(BlockElem::multi_layouter(elem.clone(), crate::flow::layout_columns).pack())
695};
696
697const STACK_RULE: ShowFn<StackElem> = |elem, _, _| {
698 Ok(BlockElem::multi_layouter(elem.clone(), crate::stack::layout_stack).pack())
699};
700
701const GRID_RULE: ShowFn<GridElem> = |elem, _, _| {
702 Ok(BlockElem::multi_layouter(elem.clone(), crate::grid::layout_grid).pack())
703};
704
705const GRID_CELL_RULE: ShowFn<GridCell> = |elem, _, styles| {
706 show_cell(elem.body.clone(), elem.inset.get(styles), elem.align.get(styles))
707};
708
709fn show_cell(
711 mut body: Content,
712 inset: Smart<Sides<Option<Rel<Length>>>>,
713 align: Smart<Alignment>,
714) -> SourceResult<Content> {
715 let inset = inset.unwrap_or_default().map(Option::unwrap_or_default);
716
717 if inset != Sides::default() {
718 body = body.padded(inset);
722 }
723
724 if let Smart::Custom(alignment) = align {
725 body = body.aligned(alignment);
726 }
727
728 Ok(body)
729}
730
731const MOVE_RULE: ShowFn<MoveElem> = |elem, _, _| {
732 Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_move).pack())
733};
734
735const SCALE_RULE: ShowFn<ScaleElem> = |elem, _, _| {
736 Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_scale).pack())
737};
738
739const ROTATE_RULE: ShowFn<RotateElem> = |elem, _, _| {
740 Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_rotate).pack())
741};
742
743const SKEW_RULE: ShowFn<SkewElem> = |elem, _, _| {
744 Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_skew).pack())
745};
746
747const REPEAT_RULE: ShowFn<RepeatElem> = |elem, _, _| {
748 Ok(BlockElem::single_layouter(elem.clone(), crate::repeat::layout_repeat).pack())
749};
750
751const HIDE_RULE: ShowFn<HideElem> =
752 |elem, _, _| Ok(elem.body.clone().set(HideElem::hidden, true));
753
754const LAYOUT_RULE: ShowFn<LayoutElem> = |elem, _, _| {
755 Ok(BlockElem::multi_layouter(
756 elem.clone(),
757 |elem, engine, locator, styles, regions| {
758 let Size { x, y } = regions.base();
761 let loc = elem.location().unwrap();
762 let context = Context::new(Some(loc), Some(styles));
763 let result = elem
764 .func
765 .call(engine, context.track(), [dict! { "width" => x, "height" => y }])?
766 .display();
767 crate::flow::layout_fragment(engine, &result, locator, styles, regions)
768 },
769 )
770 .pack())
771};
772
773const IMAGE_RULE: ShowFn<ImageElem> = |elem, _, styles| {
774 Ok(BlockElem::single_layouter(elem.clone(), crate::image::layout_image)
775 .with_width(elem.width.get(styles))
776 .with_height(elem.height.get(styles))
777 .pack())
778};
779
780const LINE_RULE: ShowFn<LineElem> = |elem, _, _| {
781 Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_line).pack())
782};
783
784const RECT_RULE: ShowFn<RectElem> = |elem, _, styles| {
785 Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_rect)
786 .with_width(elem.width.get(styles))
787 .with_height(elem.height.get(styles))
788 .pack())
789};
790
791const SQUARE_RULE: ShowFn<SquareElem> = |elem, _, styles| {
792 Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_square)
793 .with_width(elem.width.get(styles))
794 .with_height(elem.height.get(styles))
795 .pack())
796};
797
798const ELLIPSE_RULE: ShowFn<EllipseElem> = |elem, _, styles| {
799 Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_ellipse)
800 .with_width(elem.width.get(styles))
801 .with_height(elem.height.get(styles))
802 .pack())
803};
804
805const CIRCLE_RULE: ShowFn<CircleElem> = |elem, _, styles| {
806 Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_circle)
807 .with_width(elem.width.get(styles))
808 .with_height(elem.height.get(styles))
809 .pack())
810};
811
812const POLYGON_RULE: ShowFn<PolygonElem> = |elem, _, _| {
813 Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_polygon).pack())
814};
815
816const CURVE_RULE: ShowFn<CurveElem> = |elem, _, _| {
817 Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_curve).pack())
818};
819
820const PATH_RULE: ShowFn<PathElem> = |elem, _, _| {
821 Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_path).pack())
822};
823
824const EQUATION_RULE: ShowFn<EquationElem> = |elem, _, styles| {
825 if elem.block.get(styles) {
826 Ok(BlockElem::multi_layouter(elem.clone(), crate::math::layout_equation_block)
827 .pack())
828 } else {
829 Ok(InlineElem::layouter(elem.clone(), crate::math::layout_equation_inline).pack())
830 }
831};
832
833const ATTACH_RULE: ShowFn<AttachElem> = |_, _, _| Ok(Content::empty());
834
835const ARTIFACT_RULE: ShowFn<ArtifactElem> = |elem, _, _| Ok(elem.body.clone());
836
837const PDF_MARKER_TAG_RULE: ShowFn<PdfMarkerTag> = |elem, _, _| Ok(elem.body.clone());