Skip to main content

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, DividerElem, EmphElem, EnumElem, FigureCaption,
21    FigureElem, 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, PolygonElem, RectElem,
34    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, DIVIDER_RULE);
52    rules.register(Paged, TITLE_RULE);
53    rules.register(Paged, HEADING_RULE);
54    rules.register(Paged, FIGURE_RULE);
55    rules.register(Paged, FIGURE_CAPTION_RULE);
56    rules.register(Paged, QUOTE_RULE);
57    rules.register(Paged, FOOTNOTE_RULE);
58    rules.register(Paged, FOOTNOTE_ENTRY_RULE);
59    rules.register(Paged, OUTLINE_RULE);
60    rules.register(Paged, OUTLINE_ENTRY_RULE);
61    rules.register(Paged, REF_RULE);
62    rules.register(Paged, CITE_GROUP_RULE);
63    rules.register(Paged, BIBLIOGRAPHY_RULE);
64    rules.register(Paged, CSL_LIGHT_RULE);
65    rules.register(Paged, CSL_INDENT_RULE);
66    rules.register(Paged, TABLE_RULE);
67    rules.register(Paged, TABLE_CELL_RULE);
68
69    // Text.
70    rules.register(Paged, SUB_RULE);
71    rules.register(Paged, SUPER_RULE);
72    rules.register(Paged, UNDERLINE_RULE);
73    rules.register(Paged, OVERLINE_RULE);
74    rules.register(Paged, STRIKE_RULE);
75    rules.register(Paged, HIGHLIGHT_RULE);
76    rules.register(Paged, SMALLCAPS_RULE);
77    rules.register(Paged, RAW_RULE);
78    rules.register(Paged, RAW_LINE_RULE);
79
80    // Layout.
81    rules.register(Paged, ALIGN_RULE);
82    rules.register(Paged, PAD_RULE);
83    rules.register(Paged, COLUMNS_RULE);
84    rules.register(Paged, STACK_RULE);
85    rules.register(Paged, GRID_RULE);
86    rules.register(Paged, GRID_CELL_RULE);
87    rules.register(Paged, MOVE_RULE);
88    rules.register(Paged, SCALE_RULE);
89    rules.register(Paged, ROTATE_RULE);
90    rules.register(Paged, SKEW_RULE);
91    rules.register(Paged, REPEAT_RULE);
92    rules.register(Paged, HIDE_RULE);
93    rules.register(Paged, LAYOUT_RULE);
94
95    // Visualize.
96    rules.register(Paged, IMAGE_RULE);
97    rules.register(Paged, LINE_RULE);
98    rules.register(Paged, RECT_RULE);
99    rules.register(Paged, SQUARE_RULE);
100    rules.register(Paged, ELLIPSE_RULE);
101    rules.register(Paged, CIRCLE_RULE);
102    rules.register(Paged, POLYGON_RULE);
103    rules.register(Paged, CURVE_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_early(engine, span)?;
226    let alt = dest.alt_text(engine, styles, span)?;
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 DIVIDER_RULE: ShowFn<DividerElem> =
241    |elem, _, _| Ok(LineElem::new().pack().spanned(elem.span()));
242
243const TITLE_RULE: ShowFn<TitleElem> =
244    |elem, _, styles| Ok(BlockElem::packed(elem.resolve_body(styles).at(elem.span())?));
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(engine, location, styles, numbering, span)?
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, span);
271            let size = (engine.library.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    Ok(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_inset(inset)
294            .with_body(Some(BlockBody::Content(body)))
295            .pack()
296    } else {
297        BlockElem::packed(realized)
298    })
299};
300
301const FIGURE_RULE: ShowFn<FigureElem> = |elem, _, styles| {
302    let span = elem.span();
303    let mut realized = elem.body.clone();
304
305    // Build the caption, if any.
306    if let Some(caption) = elem.caption.get_cloned(styles) {
307        let (first, second) = match caption.position.get(styles) {
308            OuterVAlignment::Top => (caption.pack(), realized),
309            OuterVAlignment::Bottom => (realized, caption.pack()),
310        };
311        realized = Content::sequence(vec![
312            first,
313            VElem::new(elem.gap.get(styles).into())
314                .with_weak(true)
315                .pack()
316                .spanned(span),
317            second,
318        ]);
319    }
320
321    // Ensure that the body is considered a paragraph.
322    realized += ParbreakElem::shared().clone().spanned(span);
323
324    // Wrap the contents in a block.
325    realized = BlockElem::packed(realized).spanned(span);
326
327    // Wrap in a float.
328    if let Some(align) = elem.placement.get(styles) {
329        realized = PlaceElem::new(realized)
330            .with_alignment(align.map(|align| HAlignment::Center + align))
331            .with_scope(elem.scope.get(styles))
332            .with_float(true)
333            .pack()
334            .spanned(span);
335    } else if elem.scope.get(styles) == PlacementScope::Parent {
336        bail!(
337            span,
338            "parent-scoped placement is only available for floating figures";
339            hint: "you can enable floating placement with `figure(placement: auto, ..)`";
340        );
341    }
342
343    Ok(realized)
344};
345
346const FIGURE_CAPTION_RULE: ShowFn<FigureCaption> =
347    |elem, engine, styles| Ok(BlockElem::packed(elem.realize(engine, styles)?));
348
349const QUOTE_RULE: ShowFn<QuoteElem> = |elem, _, styles| {
350    let span = elem.span();
351    let block = elem.block.get(styles);
352
353    let mut realized = elem.body.clone();
354
355    if elem.quotes.get(styles).unwrap_or(!block) {
356        // Add zero-width weak spacing to make the quotes "sticky".
357        let hole = HElem::hole();
358        let sticky = Content::sequence([hole.clone(), realized, hole.clone()]);
359        realized = QuoteElem::quoted(sticky, styles);
360    }
361
362    let attribution = elem.attribution.get_ref(styles);
363
364    if block {
365        realized = BlockElem::packed(realized).spanned(span);
366
367        if let Some(attribution) = attribution.as_ref() {
368            // Bring the attribution a bit closer to the quote.
369            let gap = Spacing::Rel(Em::new(0.9).into());
370            let v = VElem::new(gap).with_weak(true).pack();
371            realized += v;
372            realized +=
373                BlockElem::packed(attribution.realize(span)).aligned(Alignment::END);
374        }
375
376        realized = PadElem::new(realized).pack();
377    } else if let Some(Attribution::Label(label)) = attribution {
378        realized += SpaceElem::shared().clone();
379        realized += CiteElem::new(*label).pack().spanned(span);
380    }
381
382    Ok(realized)
383};
384
385const FOOTNOTE_RULE: ShowFn<FootnoteElem> = |elem, engine, styles| {
386    // The footnote number that links to the footnote entry.
387    let link = elem.realize(engine, styles)?;
388    let sup = SuperElem::new(link).pack().spanned(elem.span());
389    Ok(HElem::hole().clone() + PdfMarkerTag::Label(sup))
390};
391
392const FOOTNOTE_ENTRY_RULE: ShowFn<FootnoteEntry> = |elem, engine, styles| {
393    let number_gap = Em::new(0.05);
394    let (sup, body) = elem.realize(engine, styles)?;
395    let prefix = PdfMarkerTag::Label(sup);
396    Ok(Content::sequence([
397        HElem::new(elem.indent.get(styles).into()).pack(),
398        prefix,
399        HElem::new(number_gap.into()).with_weak(true).pack(),
400        body,
401    ]))
402};
403
404const OUTLINE_RULE: ShowFn<OutlineElem> = |elem, engine, styles| {
405    let title = elem.realize_title(styles);
406    let entries = elem.realize_flat(engine, styles)?;
407    let entries = entries.into_iter().map(|entry| entry.pack());
408    let body = PdfMarkerTag::OutlineBody(Content::sequence(entries));
409    Ok(Content::sequence(title.into_iter().chain(Some(body))))
410};
411
412const OUTLINE_ENTRY_RULE: ShowFn<OutlineEntry> = |elem, engine, styles| {
413    let span = elem.span();
414    let context = Context::new(None, Some(styles));
415    let context = context.track();
416
417    let prefix = elem.prefix(engine, context, span)?;
418    let body = elem.body().at(span)?;
419    let page = elem.page(engine, context, span)?;
420    let alt = {
421        let prefix = prefix.as_ref().map(|p| p.plain_text()).unwrap_or_default();
422        let body = body.plain_text();
423        let page_str = PageElem::local_name_in(styles);
424        let page_nr = page.plain_text();
425        let quotes = SmartQuotes::get(
426            styles.get_ref(SmartQuoteElem::quotes),
427            styles.get(TextElem::lang),
428            styles.get(TextElem::region),
429            styles.get(SmartQuoteElem::alternative),
430        );
431        let open = quotes.double_open;
432        let close = quotes.double_close;
433        eco_format!("{prefix} {open}{body}{close} {page_str} {page_nr}",)
434    };
435    let inner = elem.build_inner(context, span, body, page)?;
436    let block = if elem.element.is::<EquationElem>() {
437        // Equation has no body and no levels, so indenting makes no sense.
438        let body = prefix.unwrap_or_default() + inner;
439        BlockElem::packed(body).spanned(span)
440    } else {
441        elem.indented(engine, context, span, prefix, inner, Em::new(0.5).into())?
442    };
443
444    let loc = elem.element_location().at(span)?;
445    Ok(block.linked(Destination::Location(loc), Some(alt)))
446};
447
448const REF_RULE: ShowFn<RefElem> = |elem, engine, styles| elem.realize(engine, styles);
449
450const CITE_GROUP_RULE: ShowFn<CiteGroup> = |elem, engine, _| elem.realize(engine);
451
452const BIBLIOGRAPHY_RULE: ShowFn<BibliographyElem> = |elem, engine, styles| {
453    const COLUMN_GUTTER: Em = Em::new(0.65);
454    const INDENT: Em = Em::new(1.5);
455
456    let loc = elem.location().unwrap();
457    let span = elem.span();
458
459    let mut seq = vec![];
460    seq.extend(elem.realize_title(styles));
461
462    let works = Works::generate(engine, elem.span())?;
463    let bibliography = works.bibliography(loc, span)?;
464
465    if bibliography.entries.iter().any(|entry| entry.prefix.is_some()) {
466        let row_gutter = styles.get(ParElem::spacing);
467
468        let mut cells = vec![];
469        for entry in &bibliography.entries {
470            let prefix = PdfMarkerTag::ListItemLabel(
471                entry.prefix.clone().unwrap_or_default().located(entry.backlink),
472            );
473            cells.push(GridChild::Item(GridItem::Cell(
474                Packed::new(GridCell::new(prefix)).spanned(span),
475            )));
476
477            let reference = PdfMarkerTag::BibEntry(entry.body.clone());
478            cells.push(GridChild::Item(GridItem::Cell(
479                Packed::new(GridCell::new(reference)).spanned(span),
480            )));
481        }
482
483        let grid = GridElem::new(cells)
484            .with_columns(TrackSizings(smallvec![Sizing::Auto; 2]))
485            .with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()]))
486            .with_row_gutter(TrackSizings(smallvec![row_gutter.into()]));
487        let mut packed = Packed::new(grid).spanned(span);
488        packed.synthesize(engine, styles)?;
489        // Directly build the block element to avoid the show step for the grid
490        // element. This will not generate introspection tags for the element.
491        let block = BlockElem::multi_layouter(packed, crate::grid::layout_grid).pack();
492
493        // TODO(accessibility): infer list numbering from style?
494        seq.push(PdfMarkerTag::Bibliography(true, block));
495    } else {
496        let mut body = vec![];
497        for entry in &bibliography.entries {
498            let realized =
499                PdfMarkerTag::BibEntry(entry.body.clone().located(entry.backlink));
500            let block = if bibliography.hanging_indent {
501                let body = HElem::new((-INDENT).into()).pack() + realized;
502                let inset = Sides::default()
503                    .with(styles.resolve(TextElem::dir).start(), Some(INDENT.into()));
504                BlockElem::new()
505                    .with_inset(inset)
506                    .with_body(Some(BlockBody::Content(body)))
507                    .pack()
508            } else {
509                BlockElem::packed(realized)
510            };
511
512            body.push(block.spanned(span));
513        }
514        seq.push(PdfMarkerTag::Bibliography(false, Content::sequence(body)));
515    }
516
517    Ok(Content::sequence(seq))
518};
519
520const CSL_LIGHT_RULE: ShowFn<CslLightElem> =
521    |elem, _, _| Ok(elem.body.clone().set(TextElem::delta, WeightDelta(-100)));
522
523const CSL_INDENT_RULE: ShowFn<CslIndentElem> =
524    |elem, _, _| Ok(PadElem::new(elem.body.clone()).pack());
525
526const TABLE_RULE: ShowFn<TableElem> = |elem, _, _| {
527    Ok(BlockElem::multi_layouter(elem.clone(), crate::grid::layout_table).pack())
528};
529
530const TABLE_CELL_RULE: ShowFn<TableCell> = |elem, _, styles| {
531    show_cell(elem.body.clone(), elem.inset.get(styles), elem.align.get(styles))
532};
533
534const SUB_RULE: ShowFn<SubElem> = |elem, _, styles| {
535    show_script(
536        styles,
537        elem.body.clone(),
538        elem.typographic.get(styles),
539        elem.baseline.get(styles),
540        elem.size.get(styles),
541        ScriptKind::Sub,
542    )
543};
544
545const SUPER_RULE: ShowFn<SuperElem> = |elem, _, styles| {
546    show_script(
547        styles,
548        elem.body.clone(),
549        elem.typographic.get(styles),
550        elem.baseline.get(styles),
551        elem.size.get(styles),
552        ScriptKind::Super,
553    )
554};
555
556fn show_script(
557    styles: StyleChain,
558    body: Content,
559    typographic: bool,
560    baseline: Smart<Length>,
561    size: Smart<TextSize>,
562    kind: ScriptKind,
563) -> SourceResult<Content> {
564    let font_size = styles.resolve(TextElem::size);
565    Ok(body.set(
566        TextElem::shift_settings,
567        Some(ShiftSettings {
568            typographic,
569            shift: baseline.map(|l| -Em::from_length(l, font_size)),
570            size: size.map(|t| Em::from_length(t.0, font_size)),
571            kind,
572        }),
573    ))
574}
575
576const UNDERLINE_RULE: ShowFn<UnderlineElem> = |elem, _, styles| {
577    Ok(elem.body.clone().set(
578        TextElem::deco,
579        smallvec![Decoration {
580            line: DecoLine::Underline {
581                stroke: elem.stroke.resolve(styles).unwrap_or_default(),
582                offset: elem.offset.resolve(styles),
583                evade: elem.evade.get(styles),
584                background: elem.background.get(styles),
585            },
586            extent: elem.extent.resolve(styles),
587        }],
588    ))
589};
590
591const OVERLINE_RULE: ShowFn<OverlineElem> = |elem, _, styles| {
592    Ok(elem.body.clone().set(
593        TextElem::deco,
594        smallvec![Decoration {
595            line: DecoLine::Overline {
596                stroke: elem.stroke.resolve(styles).unwrap_or_default(),
597                offset: elem.offset.resolve(styles),
598                evade: elem.evade.get(styles),
599                background: elem.background.get(styles),
600            },
601            extent: elem.extent.resolve(styles),
602        }],
603    ))
604};
605
606const STRIKE_RULE: ShowFn<StrikeElem> = |elem, _, styles| {
607    Ok(elem.body.clone().set(
608        TextElem::deco,
609        smallvec![Decoration {
610            // Note that we do not support evade option for strikethrough.
611            line: DecoLine::Strikethrough {
612                stroke: elem.stroke.resolve(styles).unwrap_or_default(),
613                offset: elem.offset.resolve(styles),
614                background: elem.background.get(styles),
615            },
616            extent: elem.extent.resolve(styles),
617        }],
618    ))
619};
620
621const HIGHLIGHT_RULE: ShowFn<HighlightElem> = |elem, _, styles| {
622    Ok(elem.body.clone().set(
623        TextElem::deco,
624        smallvec![Decoration {
625            line: DecoLine::Highlight {
626                fill: elem.fill.get_cloned(styles),
627                stroke: elem
628                    .stroke
629                    .resolve(styles)
630                    .unwrap_or_default()
631                    .map(|stroke| stroke.map(Stroke::unwrap_or_default)),
632                top_edge: elem.top_edge.get(styles),
633                bottom_edge: elem.bottom_edge.get(styles),
634                radius: elem.radius.resolve(styles).unwrap_or_default(),
635            },
636            extent: elem.extent.resolve(styles),
637        }],
638    ))
639};
640
641const SMALLCAPS_RULE: ShowFn<SmallcapsElem> = |elem, _, styles| {
642    let sc = if elem.all.get(styles) { Smallcaps::All } else { Smallcaps::Minuscules };
643    Ok(elem.body.clone().set(TextElem::smallcaps, Some(sc)))
644};
645
646const RAW_RULE: ShowFn<RawElem> = |elem, _, styles| {
647    let lines = elem.lines.as_deref().unwrap_or_default();
648
649    let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1));
650    for (i, line) in lines.iter().enumerate() {
651        if i != 0 {
652            seq.push(LinebreakElem::shared().clone());
653        }
654
655        seq.push(line.clone().pack());
656    }
657
658    let mut realized = Content::sequence(seq);
659
660    if elem.block.get(styles) {
661        // Align the text before inserting it into the block.
662        realized = realized.aligned(elem.align.get(styles).into());
663        realized = BlockElem::packed(realized).spanned(elem.span());
664    }
665
666    Ok(realized)
667};
668
669const RAW_LINE_RULE: ShowFn<RawLine> = |elem, _, _| Ok(elem.body.clone());
670
671const ALIGN_RULE: ShowFn<AlignElem> =
672    |elem, _, styles| Ok(elem.body.clone().aligned(elem.alignment.get(styles)));
673
674const PAD_RULE: ShowFn<PadElem> = |elem, _, _| {
675    Ok(BlockElem::multi_layouter(elem.clone(), crate::pad::layout_pad).pack())
676};
677
678const COLUMNS_RULE: ShowFn<ColumnsElem> = |elem, _, _| {
679    Ok(BlockElem::multi_layouter(elem.clone(), crate::flow::layout_columns).pack())
680};
681
682const STACK_RULE: ShowFn<StackElem> = |elem, _, _| {
683    Ok(BlockElem::multi_layouter(elem.clone(), crate::stack::layout_stack).pack())
684};
685
686const GRID_RULE: ShowFn<GridElem> = |elem, _, _| {
687    Ok(BlockElem::multi_layouter(elem.clone(), crate::grid::layout_grid).pack())
688};
689
690const GRID_CELL_RULE: ShowFn<GridCell> = |elem, _, styles| {
691    show_cell(elem.body.clone(), elem.inset.get(styles), elem.align.get(styles))
692};
693
694/// Function with common code to display a grid cell or table cell.
695fn show_cell(
696    mut body: Content,
697    inset: Smart<Sides<Option<Rel<Length>>>>,
698    align: Smart<Alignment>,
699) -> SourceResult<Content> {
700    let inset = inset.unwrap_or_default().map(Option::unwrap_or_default);
701
702    if inset != Sides::default() {
703        // Only pad if some inset is not 0pt.
704        // Avoids a bug where using .padded() in any way inside Show causes
705        // alignment in align(...) to break.
706        body = body.padded(inset);
707    }
708
709    if let Smart::Custom(alignment) = align {
710        body = body.aligned(alignment);
711    }
712
713    Ok(body)
714}
715
716const MOVE_RULE: ShowFn<MoveElem> = |elem, _, _| {
717    Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_move).pack())
718};
719
720const SCALE_RULE: ShowFn<ScaleElem> = |elem, _, _| {
721    Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_scale).pack())
722};
723
724const ROTATE_RULE: ShowFn<RotateElem> = |elem, _, _| {
725    Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_rotate).pack())
726};
727
728const SKEW_RULE: ShowFn<SkewElem> = |elem, _, _| {
729    Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_skew).pack())
730};
731
732const REPEAT_RULE: ShowFn<RepeatElem> = |elem, _, _| {
733    Ok(BlockElem::single_layouter(elem.clone(), crate::repeat::layout_repeat).pack())
734};
735
736const HIDE_RULE: ShowFn<HideElem> =
737    |elem, _, _| Ok(elem.body.clone().set(HideElem::hidden, true));
738
739const LAYOUT_RULE: ShowFn<LayoutElem> = |elem, _, _| {
740    Ok(BlockElem::multi_layouter(
741        elem.clone(),
742        |elem, engine, locator, styles, regions| {
743            // Gets the current region's base size, which will be the size of the
744            // outer container, or of the page if there is no such container.
745            let Size { x, y } = regions.base();
746            let loc = elem.location().unwrap();
747            let context = Context::new(Some(loc), Some(styles));
748            let result = elem
749                .func
750                .call(engine, context.track(), [dict! { "width" => x, "height" => y }])?
751                .display();
752            crate::flow::layout_fragment(engine, &result, locator, styles, regions)
753        },
754    )
755    .pack())
756};
757
758const IMAGE_RULE: ShowFn<ImageElem> = |elem, _, styles| {
759    Ok(BlockElem::single_layouter(elem.clone(), crate::image::layout_image)
760        .with_width(elem.width.get(styles))
761        .with_height(elem.height.get(styles))
762        .pack())
763};
764
765const LINE_RULE: ShowFn<LineElem> = |elem, _, _| {
766    Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_line).pack())
767};
768
769const RECT_RULE: ShowFn<RectElem> = |elem, _, styles| {
770    Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_rect)
771        .with_width(elem.width.get(styles))
772        .with_height(elem.height.get(styles))
773        .pack())
774};
775
776const SQUARE_RULE: ShowFn<SquareElem> = |elem, _, styles| {
777    Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_square)
778        .with_width(elem.width.get(styles))
779        .with_height(elem.height.get(styles))
780        .pack())
781};
782
783const ELLIPSE_RULE: ShowFn<EllipseElem> = |elem, _, styles| {
784    Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_ellipse)
785        .with_width(elem.width.get(styles))
786        .with_height(elem.height.get(styles))
787        .pack())
788};
789
790const CIRCLE_RULE: ShowFn<CircleElem> = |elem, _, styles| {
791    Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_circle)
792        .with_width(elem.width.get(styles))
793        .with_height(elem.height.get(styles))
794        .pack())
795};
796
797const POLYGON_RULE: ShowFn<PolygonElem> = |elem, _, _| {
798    Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_polygon).pack())
799};
800
801const CURVE_RULE: ShowFn<CurveElem> = |elem, _, _| {
802    Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_curve).pack())
803};
804
805const EQUATION_RULE: ShowFn<EquationElem> = |elem, _, styles| {
806    if elem.block.get(styles) {
807        Ok(BlockElem::multi_layouter(elem.clone(), crate::math::layout_equation_block)
808            .pack())
809    } else {
810        Ok(InlineElem::layouter(elem.clone(), crate::math::layout_equation_inline).pack())
811    }
812};
813
814const ATTACH_RULE: ShowFn<AttachElem> = |_, _, _| Ok(Content::empty());
815
816const ARTIFACT_RULE: ShowFn<ArtifactElem> = |elem, _, _| Ok(elem.body.clone());
817
818const PDF_MARKER_TAG_RULE: ShowFn<PdfMarkerTag> = |elem, _, _| Ok(elem.body.clone());