typst_layout/
rules.rs

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
38/// Register show rules for the [paged target](Target::Paged).
39pub fn register(rules: &mut NativeRuleMap) {
40    use Target::Paged;
41
42    // Model.
43    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    // Text.
69    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    // Layout.
80    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    // Visualize.
95    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    // Math.
106    rules.register(Paged, EQUATION_RULE);
107
108    // PDF.
109    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        // Text in wide term lists shall always turn into paragraphs.
186        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    // Manually construct link marker that spans the whole link elem, not just
228    // the body.
229    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            // We don't have a locator for the numbering here, so we just
269            // use the measurement infrastructure for now.
270            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    // Build the caption, if any.
307    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    // Ensure that the body is considered a paragraph.
323    realized += ParbreakElem::shared().clone().spanned(span);
324
325    // Wrap the contents in a block.
326    realized = BlockElem::new()
327        .with_body(Some(BlockBody::Content(realized)))
328        .pack()
329        .spanned(span);
330
331    // Wrap in a float.
332    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        // Add zero-width weak spacing to make the quotes "sticky".
364        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            // Bring the attribution a bit closer to the quote.
379            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        // Equation has no body and no levels, so indenting makes no sense.
450        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        // Directly build the block element to avoid the show step for the grid
504        // element. This will not generate introspection tags for the element.
505        let block = BlockElem::multi_layouter(packed, crate::grid::layout_grid).pack();
506
507        // TODO(accessibility): infer list numbering from style?
508        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            // Note that we do not support evade option for strikethrough.
623            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        // Align the text before inserting it into the block.
674        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
709/// Function with common code to display a grid cell or table cell.
710fn 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        // Only pad if some inset is not 0pt.
719        // Avoids a bug where using .padded() in any way inside Show causes
720        // alignment in align(...) to break.
721        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            // Gets the current region's base size, which will be the size of the
759            // outer container, or of the page if there is no such container.
760            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());