, which is not \
+ supported by all assistive technology",
+ level, level + 1;
+ hint: "HTML only supports
to , not ", level + 1;
+ hint: "you may want to restructure your document so that \
+ it doesn't contain deep headings"
+ ));
+ HtmlElem::new(tag::div)
+ .with_body(Some(realized))
+ .with_attr(attr::role, "heading")
+ .with_attr(attr::aria_level, eco_format!("{}", level + 1))
+ .pack()
+ .spanned(span)
+ } else {
+ let t = [tag::h2, tag::h3, tag::h4, tag::h5, tag::h6][level - 1];
+ HtmlElem::new(t).with_body(Some(realized)).pack().spanned(span)
+ })
+};
+
+const FIGURE_RULE: ShowFn = |elem, _, styles| {
+ let span = elem.span();
+ let mut realized = elem.body.clone();
+
+ // Build the caption, if any.
+ if let Some(caption) = elem.caption.get_cloned(styles) {
+ realized = match caption.position.get(styles) {
+ OuterVAlignment::Top => caption.pack() + realized,
+ OuterVAlignment::Bottom => realized + caption.pack(),
+ };
+ }
+
+ // Ensure that the body is considered a paragraph.
+ realized += ParbreakElem::shared().clone().spanned(span);
+
+ Ok(HtmlElem::new(tag::figure)
+ .with_body(Some(realized))
+ .pack()
+ .spanned(span))
+};
+
+const FIGURE_CAPTION_RULE: ShowFn = |elem, engine, styles| {
+ Ok(HtmlElem::new(tag::figcaption)
+ .with_body(Some(elem.realize(engine, styles)?))
+ .pack()
+ .spanned(elem.span()))
+};
+
+const QUOTE_RULE: ShowFn = |elem, _, styles| {
+ let span = elem.span();
+ let block = elem.block.get(styles);
+
+ let mut realized = elem.body.clone();
+
+ if elem.quotes.get(styles).unwrap_or(!block) {
+ realized = QuoteElem::quoted(realized, styles);
+ }
+
+ let attribution = elem.attribution.get_ref(styles);
+
+ if block {
+ let mut blockquote = HtmlElem::new(tag::blockquote).with_body(Some(realized));
+ if let Some(Attribution::Content(attribution)) = attribution {
+ if let Some(link) = attribution.to_packed::() {
+ if let LinkTarget::Dest(Destination::Url(url)) = &link.dest {
+ blockquote =
+ blockquote.with_attr(attr::cite, url.clone().into_inner());
+ }
+ }
+ }
+
+ realized = blockquote.pack().spanned(span);
+
+ if let Some(attribution) = attribution.as_ref() {
+ realized += attribution.realize(span);
+ }
+ } else if let Some(Attribution::Label(label)) = attribution {
+ realized += SpaceElem::shared().clone();
+ realized += CiteElem::new(*label).pack().spanned(span);
+ }
+
+ Ok(realized)
+};
+
+const REF_RULE: ShowFn = |elem, engine, styles| elem.realize(engine, styles);
+
+const CITE_GROUP_RULE: ShowFn = |elem, engine, _| elem.realize(engine);
+
+const TABLE_RULE: ShowFn = |elem, engine, styles| {
+ // The locator is not used by HTML export, so we can just fabricate one.
+ let locator = Locator::root();
+ Ok(show_cellgrid(table_to_cellgrid(elem, engine, locator, styles)?, styles))
+};
+
+fn show_cellgrid(grid: CellGrid, styles: StyleChain) -> Content {
+ let elem = |tag, body| HtmlElem::new(tag).with_body(Some(body)).pack();
+ let mut rows: Vec<_> = grid.entries.chunks(grid.non_gutter_column_count()).collect();
+
+ let tr = |tag, row: &[Entry]| {
+ let row = row
+ .iter()
+ .flat_map(|entry| entry.as_cell())
+ .map(|cell| show_cell(tag, cell, styles));
+ elem(tag::tr, Content::sequence(row))
+ };
+
+ // TODO(subfooters): similarly to headers, take consecutive footers from
+ // the end for 'tfoot'.
+ let footer = grid.footer.map(|ft| {
+ let rows = rows.drain(ft.start..);
+ elem(tag::tfoot, Content::sequence(rows.map(|row| tr(tag::td, row))))
+ });
+
+ // Store all consecutive headers at the start in 'thead'. All remaining
+ // headers are just 'th' rows across the table body.
+ let mut consecutive_header_end = 0;
+ let first_mid_table_header = grid
+ .headers
+ .iter()
+ .take_while(|hd| {
+ let is_consecutive = hd.range.start == consecutive_header_end;
+ consecutive_header_end = hd.range.end;
+ is_consecutive
+ })
+ .count();
+
+ let (y_offset, header) = if first_mid_table_header > 0 {
+ let removed_header_rows =
+ grid.headers.get(first_mid_table_header - 1).unwrap().range.end;
+ let rows = rows.drain(..removed_header_rows);
+
+ (
+ removed_header_rows,
+ Some(elem(tag::thead, Content::sequence(rows.map(|row| tr(tag::th, row))))),
+ )
+ } else {
+ (0, None)
+ };
+
+ // TODO: Consider improving accessibility properties of multi-level headers
+ // inside tables in the future, e.g. indicating which columns they are
+ // relative to and so on. See also:
+ // https://www.w3.org/WAI/tutorials/tables/multi-level/
+ let mut next_header = first_mid_table_header;
+ let mut body =
+ Content::sequence(rows.into_iter().enumerate().map(|(relative_y, row)| {
+ let y = relative_y + y_offset;
+ if let Some(current_header) =
+ grid.headers.get(next_header).filter(|h| h.range.contains(&y))
+ {
+ if y + 1 == current_header.range.end {
+ next_header += 1;
+ }
+
+ tr(tag::th, row)
+ } else {
+ tr(tag::td, row)
+ }
+ }));
+
+ if header.is_some() || footer.is_some() {
+ body = elem(tag::tbody, body);
+ }
+
+ let content = header.into_iter().chain(core::iter::once(body)).chain(footer);
+ elem(tag::table, Content::sequence(content))
+}
+
+fn show_cell(tag: HtmlTag, cell: &Cell, styles: StyleChain) -> Content {
+ let cell = cell.body.clone();
+ let Some(cell) = cell.to_packed::() else { return cell };
+ let mut attrs = HtmlAttrs::default();
+ let span = |n: NonZeroUsize| (n != NonZeroUsize::MIN).then(|| n.to_string());
+ if let Some(colspan) = span(cell.colspan.get(styles)) {
+ attrs.push(attr::colspan, colspan);
+ }
+ if let Some(rowspan) = span(cell.rowspan.get(styles)) {
+ attrs.push(attr::rowspan, rowspan);
+ }
+ HtmlElem::new(tag)
+ .with_body(Some(cell.body.clone()))
+ .with_attrs(attrs)
+ .pack()
+ .spanned(cell.span())
+}
+
+const SUB_RULE: ShowFn = |elem, _, _| {
+ Ok(HtmlElem::new(tag::sub)
+ .with_body(Some(elem.body.clone()))
+ .pack()
+ .spanned(elem.span()))
+};
+
+const SUPER_RULE: ShowFn = |elem, _, _| {
+ Ok(HtmlElem::new(tag::sup)
+ .with_body(Some(elem.body.clone()))
+ .pack()
+ .spanned(elem.span()))
+};
+
+const UNDERLINE_RULE: ShowFn = |elem, _, _| {
+ // Note: In modern HTML, `` is not the underline element, but
+ // rather an "Unarticulated Annotation" element (see HTML spec
+ // 4.5.22). Using `text-decoration` instead is recommended by MDN.
+ Ok(HtmlElem::new(tag::span)
+ .with_attr(attr::style, "text-decoration: underline")
+ .with_body(Some(elem.body.clone()))
+ .pack())
+};
+
+const OVERLINE_RULE: ShowFn = |elem, _, _| {
+ Ok(HtmlElem::new(tag::span)
+ .with_attr(attr::style, "text-decoration: overline")
+ .with_body(Some(elem.body.clone()))
+ .pack())
+};
+
+const STRIKE_RULE: ShowFn =
+ |elem, _, _| Ok(HtmlElem::new(tag::s).with_body(Some(elem.body.clone())).pack());
+
+const HIGHLIGHT_RULE: ShowFn =
+ |elem, _, _| Ok(HtmlElem::new(tag::mark).with_body(Some(elem.body.clone())).pack());
+
+const RAW_RULE: ShowFn = |elem, _, styles| {
+ let lines = elem.lines.as_deref().unwrap_or_default();
+
+ let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1));
+ for (i, line) in lines.iter().enumerate() {
+ if i != 0 {
+ seq.push(LinebreakElem::shared().clone());
+ }
+
+ seq.push(line.clone().pack());
+ }
+
+ Ok(HtmlElem::new(if elem.block.get(styles) { tag::pre } else { tag::code })
+ .with_body(Some(Content::sequence(seq)))
+ .pack()
+ .spanned(elem.span()))
+};
+
+const RAW_LINE_RULE: ShowFn = |elem, _, _| Ok(elem.body.clone());
diff --git a/crates/typst-layout/src/lib.rs b/crates/typst-layout/src/lib.rs
index 443e90d61..361bab463 100644
--- a/crates/typst-layout/src/lib.rs
+++ b/crates/typst-layout/src/lib.rs
@@ -10,21 +10,11 @@ mod modifiers;
mod pad;
mod pages;
mod repeat;
+mod rules;
mod shapes;
mod stack;
mod transforms;
-pub use self::flow::{layout_columns, layout_fragment, layout_frame};
-pub use self::grid::{layout_grid, layout_table};
-pub use self::image::layout_image;
-pub use self::lists::{layout_enum, layout_list};
-pub use self::math::{layout_equation_block, layout_equation_inline};
-pub use self::pad::layout_pad;
+pub use self::flow::{layout_fragment, layout_frame};
pub use self::pages::layout_document;
-pub use self::repeat::layout_repeat;
-pub use self::shapes::{
- layout_circle, layout_curve, layout_ellipse, layout_line, layout_path,
- layout_polygon, layout_rect, layout_square,
-};
-pub use self::stack::layout_stack;
-pub use self::transforms::{layout_move, layout_rotate, layout_scale, layout_skew};
+pub use self::rules::register;
diff --git a/crates/typst-layout/src/rules.rs b/crates/typst-layout/src/rules.rs
new file mode 100644
index 000000000..ebccd92e8
--- /dev/null
+++ b/crates/typst-layout/src/rules.rs
@@ -0,0 +1,890 @@
+use std::num::NonZeroUsize;
+
+use comemo::Track;
+use ecow::{eco_format, EcoVec};
+use smallvec::smallvec;
+use typst_library::diag::{bail, At, SourceResult};
+use typst_library::foundations::{
+ dict, Content, Context, NativeElement, NativeRuleMap, Packed, Resolve, ShowFn, Smart,
+ StyleChain, Target,
+};
+use typst_library::introspection::{Counter, Locator, LocatorLink};
+use typst_library::layout::{
+ Abs, AlignElem, Alignment, Axes, BlockBody, BlockElem, ColumnsElem, Em, GridCell,
+ GridChild, GridElem, GridItem, HAlignment, HElem, HideElem, InlineElem, LayoutElem,
+ Length, MoveElem, OuterVAlignment, PadElem, PlaceElem, PlacementScope, Region, Rel,
+ RepeatElem, RotateElem, ScaleElem, Sides, Size, Sizing, SkewElem, Spacing,
+ StackChild, StackElem, TrackSizings, VElem,
+};
+use typst_library::math::EquationElem;
+use typst_library::model::{
+ Attribution, BibliographyElem, CiteElem, CiteGroup, CslSource, Destination, EmphElem,
+ EnumElem, FigureCaption, FigureElem, FootnoteElem, FootnoteEntry, HeadingElem,
+ LinkElem, LinkTarget, ListElem, Outlinable, OutlineElem, OutlineEntry, ParElem,
+ ParbreakElem, QuoteElem, RefElem, StrongElem, TableCell, TableElem, TermsElem, Works,
+};
+use typst_library::pdf::EmbedElem;
+use typst_library::text::{
+ DecoLine, Decoration, HighlightElem, ItalicToggle, LinebreakElem, LocalName,
+ OverlineElem, RawElem, RawLine, ScriptKind, ShiftSettings, Smallcaps, SmallcapsElem,
+ SpaceElem, StrikeElem, SubElem, SuperElem, TextElem, TextSize, UnderlineElem,
+ WeightDelta,
+};
+use typst_library::visualize::{
+ CircleElem, CurveElem, EllipseElem, ImageElem, LineElem, PathElem, PolygonElem,
+ RectElem, SquareElem, Stroke,
+};
+use typst_utils::{Get, NonZeroExt, Numeric};
+
+/// Register show rules for the [paged target](Target::Paged).
+pub fn register(rules: &mut NativeRuleMap) {
+ use Target::Paged;
+
+ // Model.
+ rules.register(Paged, STRONG_RULE);
+ rules.register(Paged, EMPH_RULE);
+ rules.register(Paged, LIST_RULE);
+ rules.register(Paged, ENUM_RULE);
+ rules.register(Paged, TERMS_RULE);
+ rules.register(Paged, LINK_RULE);
+ rules.register(Paged, HEADING_RULE);
+ rules.register(Paged, FIGURE_RULE);
+ rules.register(Paged, FIGURE_CAPTION_RULE);
+ rules.register(Paged, QUOTE_RULE);
+ rules.register(Paged, FOOTNOTE_RULE);
+ rules.register(Paged, FOOTNOTE_ENTRY_RULE);
+ rules.register(Paged, OUTLINE_RULE);
+ rules.register(Paged, OUTLINE_ENTRY_RULE);
+ rules.register(Paged, REF_RULE);
+ rules.register(Paged, CITE_GROUP_RULE);
+ rules.register(Paged, BIBLIOGRAPHY_RULE);
+ rules.register(Paged, TABLE_RULE);
+ rules.register(Paged, TABLE_CELL_RULE);
+
+ // Text.
+ rules.register(Paged, SUB_RULE);
+ rules.register(Paged, SUPER_RULE);
+ rules.register(Paged, UNDERLINE_RULE);
+ rules.register(Paged, OVERLINE_RULE);
+ rules.register(Paged, STRIKE_RULE);
+ rules.register(Paged, HIGHLIGHT_RULE);
+ rules.register(Paged, SMALLCAPS_RULE);
+ rules.register(Paged, RAW_RULE);
+ rules.register(Paged, RAW_LINE_RULE);
+
+ // Layout.
+ rules.register(Paged, ALIGN_RULE);
+ rules.register(Paged, PAD_RULE);
+ rules.register(Paged, COLUMNS_RULE);
+ rules.register(Paged, STACK_RULE);
+ rules.register(Paged, GRID_RULE);
+ rules.register(Paged, GRID_CELL_RULE);
+ rules.register(Paged, MOVE_RULE);
+ rules.register(Paged, SCALE_RULE);
+ rules.register(Paged, ROTATE_RULE);
+ rules.register(Paged, SKEW_RULE);
+ rules.register(Paged, REPEAT_RULE);
+ rules.register(Paged, HIDE_RULE);
+ rules.register(Paged, LAYOUT_RULE);
+
+ // Visualize.
+ rules.register(Paged, IMAGE_RULE);
+ rules.register(Paged, LINE_RULE);
+ rules.register(Paged, RECT_RULE);
+ rules.register(Paged, SQUARE_RULE);
+ rules.register(Paged, ELLIPSE_RULE);
+ rules.register(Paged, CIRCLE_RULE);
+ rules.register(Paged, POLYGON_RULE);
+ rules.register(Paged, CURVE_RULE);
+ rules.register(Paged, PATH_RULE);
+
+ // Math.
+ rules.register(Paged, EQUATION_RULE);
+
+ // PDF.
+ rules.register(Paged, EMBED_RULE);
+}
+
+const STRONG_RULE: ShowFn = |elem, _, styles| {
+ Ok(elem
+ .body
+ .clone()
+ .set(TextElem::delta, WeightDelta(elem.delta.get(styles))))
+};
+
+const EMPH_RULE: ShowFn =
+ |elem, _, _| Ok(elem.body.clone().set(TextElem::emph, ItalicToggle(true)));
+
+const LIST_RULE: ShowFn = |elem, _, styles| {
+ let tight = elem.tight.get(styles);
+
+ let mut realized = BlockElem::multi_layouter(elem.clone(), crate::lists::layout_list)
+ .pack()
+ .spanned(elem.span());
+
+ if tight {
+ let spacing = elem
+ .spacing
+ .get(styles)
+ .unwrap_or_else(|| styles.get(ParElem::leading));
+ let v = VElem::new(spacing.into()).with_weak(true).with_attach(true).pack();
+ realized = v + realized;
+ }
+
+ Ok(realized)
+};
+
+const ENUM_RULE: ShowFn = |elem, _, styles| {
+ let tight = elem.tight.get(styles);
+
+ let mut realized = BlockElem::multi_layouter(elem.clone(), crate::lists::layout_enum)
+ .pack()
+ .spanned(elem.span());
+
+ if tight {
+ let spacing = elem
+ .spacing
+ .get(styles)
+ .unwrap_or_else(|| styles.get(ParElem::leading));
+ let v = VElem::new(spacing.into()).with_weak(true).with_attach(true).pack();
+ realized = v + realized;
+ }
+
+ Ok(realized)
+};
+
+const TERMS_RULE: ShowFn = |elem, _, styles| {
+ let span = elem.span();
+ let tight = elem.tight.get(styles);
+
+ let separator = elem.separator.get_ref(styles);
+ let indent = elem.indent.get(styles);
+ let hanging_indent = elem.hanging_indent.get(styles);
+ let gutter = elem.spacing.get(styles).unwrap_or_else(|| {
+ if tight {
+ styles.get(ParElem::leading)
+ } else {
+ styles.get(ParElem::spacing)
+ }
+ });
+
+ let pad = hanging_indent + indent;
+ let unpad = (!hanging_indent.is_zero())
+ .then(|| HElem::new((-hanging_indent).into()).pack().spanned(span));
+
+ let mut children = vec![];
+ for child in elem.children.iter() {
+ let mut seq = vec![];
+ seq.extend(unpad.clone());
+ seq.push(child.term.clone().strong());
+ seq.push(separator.clone());
+ seq.push(child.description.clone());
+
+ // Text in wide term lists shall always turn into paragraphs.
+ if !tight {
+ seq.push(ParbreakElem::shared().clone());
+ }
+
+ children.push(StackChild::Block(Content::sequence(seq)));
+ }
+
+ let padding =
+ Sides::default().with(styles.resolve(TextElem::dir).start(), pad.into());
+
+ let mut realized = StackElem::new(children)
+ .with_spacing(Some(gutter.into()))
+ .pack()
+ .spanned(span)
+ .padded(padding)
+ .set(TermsElem::within, true);
+
+ if tight {
+ let spacing = elem
+ .spacing
+ .get(styles)
+ .unwrap_or_else(|| styles.get(ParElem::leading));
+ let v = VElem::new(spacing.into())
+ .with_weak(true)
+ .with_attach(true)
+ .pack()
+ .spanned(span);
+ realized = v + realized;
+ }
+
+ Ok(realized)
+};
+
+const LINK_RULE: ShowFn = |elem, engine, _| {
+ let body = elem.body.clone();
+ Ok(match &elem.dest {
+ LinkTarget::Dest(dest) => body.linked(dest.clone()),
+ LinkTarget::Label(label) => {
+ let elem = engine.introspector.query_label(*label).at(elem.span())?;
+ let dest = Destination::Location(elem.location().unwrap());
+ body.linked(dest)
+ }
+ })
+};
+
+const HEADING_RULE: ShowFn = |elem, engine, styles| {
+ const SPACING_TO_NUMBERING: Em = Em::new(0.3);
+
+ let span = elem.span();
+ let mut realized = elem.body.clone();
+
+ let hanging_indent = elem.hanging_indent.get(styles);
+ let mut indent = match hanging_indent {
+ Smart::Custom(length) => length.resolve(styles),
+ Smart::Auto => Abs::zero(),
+ };
+
+ if let Some(numbering) = elem.numbering.get_ref(styles).as_ref() {
+ let location = elem.location().unwrap();
+ let numbering = Counter::of(HeadingElem::ELEM)
+ .display_at_loc(engine, location, styles, numbering)?
+ .spanned(span);
+
+ if hanging_indent.is_auto() {
+ let pod = Region::new(Axes::splat(Abs::inf()), Axes::splat(false));
+
+ // We don't have a locator for the numbering here, so we just
+ // use the measurement infrastructure for now.
+ let link = LocatorLink::measure(location);
+ let size = (engine.routines.layout_frame)(
+ engine,
+ &numbering,
+ Locator::link(&link),
+ styles,
+ pod,
+ )?
+ .size();
+
+ indent = size.x + SPACING_TO_NUMBERING.resolve(styles);
+ }
+
+ let spacing = HElem::new(SPACING_TO_NUMBERING.into()).with_weak(true).pack();
+
+ realized = numbering + spacing + realized;
+ }
+
+ let block = if indent != Abs::zero() {
+ let body = HElem::new((-indent).into()).pack() + realized;
+ let inset = Sides::default()
+ .with(styles.resolve(TextElem::dir).start(), Some(indent.into()));
+ BlockElem::new()
+ .with_body(Some(BlockBody::Content(body)))
+ .with_inset(inset)
+ } else {
+ BlockElem::new().with_body(Some(BlockBody::Content(realized)))
+ };
+
+ Ok(block.pack().spanned(span))
+};
+
+const FIGURE_RULE: ShowFn = |elem, _, styles| {
+ let span = elem.span();
+ let mut realized = elem.body.clone();
+
+ // Build the caption, if any.
+ if let Some(caption) = elem.caption.get_cloned(styles) {
+ let (first, second) = match caption.position.get(styles) {
+ OuterVAlignment::Top => (caption.pack(), realized),
+ OuterVAlignment::Bottom => (realized, caption.pack()),
+ };
+ realized = Content::sequence(vec![
+ first,
+ VElem::new(elem.gap.get(styles).into())
+ .with_weak(true)
+ .pack()
+ .spanned(span),
+ second,
+ ]);
+ }
+
+ // Ensure that the body is considered a paragraph.
+ realized += ParbreakElem::shared().clone().spanned(span);
+
+ // Wrap the contents in a block.
+ realized = BlockElem::new()
+ .with_body(Some(BlockBody::Content(realized)))
+ .pack()
+ .spanned(span);
+
+ // Wrap in a float.
+ if let Some(align) = elem.placement.get(styles) {
+ realized = PlaceElem::new(realized)
+ .with_alignment(align.map(|align| HAlignment::Center + align))
+ .with_scope(elem.scope.get(styles))
+ .with_float(true)
+ .pack()
+ .spanned(span);
+ } else if elem.scope.get(styles) == PlacementScope::Parent {
+ bail!(
+ span,
+ "parent-scoped placement is only available for floating figures";
+ hint: "you can enable floating placement with `figure(placement: auto, ..)`"
+ );
+ }
+
+ Ok(realized)
+};
+
+const FIGURE_CAPTION_RULE: ShowFn = |elem, engine, styles| {
+ Ok(BlockElem::new()
+ .with_body(Some(BlockBody::Content(elem.realize(engine, styles)?)))
+ .pack()
+ .spanned(elem.span()))
+};
+
+const QUOTE_RULE: ShowFn = |elem, _, styles| {
+ let span = elem.span();
+ let block = elem.block.get(styles);
+
+ let mut realized = elem.body.clone();
+
+ if elem.quotes.get(styles).unwrap_or(!block) {
+ // Add zero-width weak spacing to make the quotes "sticky".
+ let hole = HElem::hole().pack();
+ let sticky = Content::sequence([hole.clone(), realized, hole]);
+ realized = QuoteElem::quoted(sticky, styles);
+ }
+
+ let attribution = elem.attribution.get_ref(styles);
+
+ if block {
+ realized = BlockElem::new()
+ .with_body(Some(BlockBody::Content(realized)))
+ .pack()
+ .spanned(span);
+
+ if let Some(attribution) = attribution.as_ref() {
+ // Bring the attribution a bit closer to the quote.
+ let gap = Spacing::Rel(Em::new(0.9).into());
+ let v = VElem::new(gap).with_weak(true).pack();
+ realized += v;
+ realized += BlockElem::new()
+ .with_body(Some(BlockBody::Content(attribution.realize(span))))
+ .pack()
+ .aligned(Alignment::END);
+ }
+
+ realized = PadElem::new(realized).pack();
+ } else if let Some(Attribution::Label(label)) = attribution {
+ realized += SpaceElem::shared().clone();
+ realized += CiteElem::new(*label).pack().spanned(span);
+ }
+
+ Ok(realized)
+};
+
+const FOOTNOTE_RULE: ShowFn = |elem, engine, styles| {
+ let span = elem.span();
+ let loc = elem.declaration_location(engine).at(span)?;
+ let numbering = elem.numbering.get_ref(styles);
+ let counter = Counter::of(FootnoteElem::ELEM);
+ let num = counter.display_at_loc(engine, loc, styles, numbering)?;
+ let sup = SuperElem::new(num).pack().spanned(span);
+ let loc = loc.variant(1);
+ // Add zero-width weak spacing to make the footnote "sticky".
+ Ok(HElem::hole().pack() + sup.linked(Destination::Location(loc)))
+};
+
+const FOOTNOTE_ENTRY_RULE: ShowFn = |elem, engine, styles| {
+ let span = elem.span();
+ let number_gap = Em::new(0.05);
+ let default = StyleChain::default();
+ let numbering = elem.note.numbering.get_ref(default);
+ let counter = Counter::of(FootnoteElem::ELEM);
+ let Some(loc) = elem.note.location() else {
+ bail!(
+ span, "footnote entry must have a location";
+ hint: "try using a query or a show rule to customize the footnote instead"
+ );
+ };
+
+ let num = counter.display_at_loc(engine, loc, styles, numbering)?;
+ let sup = SuperElem::new(num)
+ .pack()
+ .spanned(span)
+ .linked(Destination::Location(loc))
+ .located(loc.variant(1));
+
+ Ok(Content::sequence([
+ HElem::new(elem.indent.get(styles).into()).pack(),
+ sup,
+ HElem::new(number_gap.into()).with_weak(true).pack(),
+ elem.note.body_content().unwrap().clone(),
+ ]))
+};
+
+const OUTLINE_RULE: ShowFn = |elem, engine, styles| {
+ let span = elem.span();
+
+ // Build the outline title.
+ let mut seq = vec![];
+ if let Some(title) = elem.title.get_cloned(styles).unwrap_or_else(|| {
+ Some(TextElem::packed(Packed::::local_name_in(styles)).spanned(span))
+ }) {
+ seq.push(
+ HeadingElem::new(title)
+ .with_depth(NonZeroUsize::ONE)
+ .pack()
+ .spanned(span),
+ );
+ }
+
+ let elems = engine.introspector.query(&elem.target.get_ref(styles).0);
+ let depth = elem.depth.get(styles).unwrap_or(NonZeroUsize::MAX);
+
+ // Build the outline entries.
+ for elem in elems {
+ let Some(outlinable) = elem.with::() else {
+ bail!(span, "cannot outline {}", elem.func().name());
+ };
+
+ let level = outlinable.level();
+ if outlinable.outlined() && level <= depth {
+ let entry = OutlineEntry::new(level, elem);
+ seq.push(entry.pack().spanned(span));
+ }
+ }
+
+ Ok(Content::sequence(seq))
+};
+
+const OUTLINE_ENTRY_RULE: ShowFn = |elem, engine, styles| {
+ let span = elem.span();
+ let context = Context::new(None, Some(styles));
+ let context = context.track();
+
+ let prefix = elem.prefix(engine, context, span)?;
+ let inner = elem.inner(engine, context, span)?;
+ let block = if elem.element.is::() {
+ let body = prefix.unwrap_or_default() + inner;
+ BlockElem::new()
+ .with_body(Some(BlockBody::Content(body)))
+ .pack()
+ .spanned(span)
+ } else {
+ elem.indented(engine, context, span, prefix, inner, Em::new(0.5).into())?
+ };
+
+ let loc = elem.element_location().at(span)?;
+ Ok(block.linked(Destination::Location(loc)))
+};
+
+const REF_RULE: ShowFn = |elem, engine, styles| elem.realize(engine, styles);
+
+const CITE_GROUP_RULE: ShowFn = |elem, engine, _| elem.realize(engine);
+
+const BIBLIOGRAPHY_RULE: ShowFn = |elem, engine, styles| {
+ const COLUMN_GUTTER: Em = Em::new(0.65);
+ const INDENT: Em = Em::new(1.5);
+
+ let span = elem.span();
+
+ let mut seq = vec![];
+ if let Some(title) = elem.title.get_ref(styles).clone().unwrap_or_else(|| {
+ Some(
+ TextElem::packed(Packed::::local_name_in(styles))
+ .spanned(span),
+ )
+ }) {
+ seq.push(
+ HeadingElem::new(title)
+ .with_depth(NonZeroUsize::ONE)
+ .pack()
+ .spanned(span),
+ );
+ }
+
+ let works = Works::generate(engine).at(span)?;
+ let references = works
+ .references
+ .as_ref()
+ .ok_or_else(|| match elem.style.get_ref(styles).source {
+ CslSource::Named(style) => eco_format!(
+ "CSL style \"{}\" is not suitable for bibliographies",
+ style.display_name()
+ ),
+ CslSource::Normal(..) => {
+ "CSL style is not suitable for bibliographies".into()
+ }
+ })
+ .at(span)?;
+
+ if references.iter().any(|(prefix, _)| prefix.is_some()) {
+ let row_gutter = styles.get(ParElem::spacing);
+
+ let mut cells = vec![];
+ for (prefix, reference) in references {
+ cells.push(GridChild::Item(GridItem::Cell(
+ Packed::new(GridCell::new(prefix.clone().unwrap_or_default()))
+ .spanned(span),
+ )));
+ cells.push(GridChild::Item(GridItem::Cell(
+ Packed::new(GridCell::new(reference.clone())).spanned(span),
+ )));
+ }
+ seq.push(
+ GridElem::new(cells)
+ .with_columns(TrackSizings(smallvec![Sizing::Auto; 2]))
+ .with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()]))
+ .with_row_gutter(TrackSizings(smallvec![row_gutter.into()]))
+ .pack()
+ .spanned(span),
+ );
+ } else {
+ for (_, reference) in references {
+ let realized = reference.clone();
+ let block = if works.hanging_indent {
+ let body = HElem::new((-INDENT).into()).pack() + realized;
+ let inset = Sides::default()
+ .with(styles.resolve(TextElem::dir).start(), Some(INDENT.into()));
+ BlockElem::new()
+ .with_body(Some(BlockBody::Content(body)))
+ .with_inset(inset)
+ } else {
+ BlockElem::new().with_body(Some(BlockBody::Content(realized)))
+ };
+
+ seq.push(block.pack().spanned(span));
+ }
+ }
+
+ Ok(Content::sequence(seq))
+};
+
+const TABLE_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::multi_layouter(elem.clone(), crate::grid::layout_table)
+ .pack()
+ .spanned(elem.span()))
+};
+
+const TABLE_CELL_RULE: ShowFn = |elem, _, styles| {
+ show_cell(elem.body.clone(), elem.inset.get(styles), elem.align.get(styles))
+};
+
+const SUB_RULE: ShowFn = |elem, _, styles| {
+ show_script(
+ styles,
+ elem.body.clone(),
+ elem.typographic.get(styles),
+ elem.baseline.get(styles),
+ elem.size.get(styles),
+ ScriptKind::Sub,
+ )
+};
+
+const SUPER_RULE: ShowFn = |elem, _, styles| {
+ show_script(
+ styles,
+ elem.body.clone(),
+ elem.typographic.get(styles),
+ elem.baseline.get(styles),
+ elem.size.get(styles),
+ ScriptKind::Super,
+ )
+};
+
+fn show_script(
+ styles: StyleChain,
+ body: Content,
+ typographic: bool,
+ baseline: Smart,
+ size: Smart,
+ kind: ScriptKind,
+) -> SourceResult {
+ let font_size = styles.resolve(TextElem::size);
+ Ok(body.set(
+ TextElem::shift_settings,
+ Some(ShiftSettings {
+ typographic,
+ shift: baseline.map(|l| -Em::from_length(l, font_size)),
+ size: size.map(|t| Em::from_length(t.0, font_size)),
+ kind,
+ }),
+ ))
+}
+
+const UNDERLINE_RULE: ShowFn = |elem, _, styles| {
+ Ok(elem.body.clone().set(
+ TextElem::deco,
+ smallvec![Decoration {
+ line: DecoLine::Underline {
+ stroke: elem.stroke.resolve(styles).unwrap_or_default(),
+ offset: elem.offset.resolve(styles),
+ evade: elem.evade.get(styles),
+ background: elem.background.get(styles),
+ },
+ extent: elem.extent.resolve(styles),
+ }],
+ ))
+};
+
+const OVERLINE_RULE: ShowFn = |elem, _, styles| {
+ Ok(elem.body.clone().set(
+ TextElem::deco,
+ smallvec![Decoration {
+ line: DecoLine::Overline {
+ stroke: elem.stroke.resolve(styles).unwrap_or_default(),
+ offset: elem.offset.resolve(styles),
+ evade: elem.evade.get(styles),
+ background: elem.background.get(styles),
+ },
+ extent: elem.extent.resolve(styles),
+ }],
+ ))
+};
+
+const STRIKE_RULE: ShowFn = |elem, _, styles| {
+ Ok(elem.body.clone().set(
+ TextElem::deco,
+ smallvec![Decoration {
+ // Note that we do not support evade option for strikethrough.
+ line: DecoLine::Strikethrough {
+ stroke: elem.stroke.resolve(styles).unwrap_or_default(),
+ offset: elem.offset.resolve(styles),
+ background: elem.background.get(styles),
+ },
+ extent: elem.extent.resolve(styles),
+ }],
+ ))
+};
+
+const HIGHLIGHT_RULE: ShowFn = |elem, _, styles| {
+ Ok(elem.body.clone().set(
+ TextElem::deco,
+ smallvec![Decoration {
+ line: DecoLine::Highlight {
+ fill: elem.fill.get_cloned(styles),
+ stroke: elem
+ .stroke
+ .resolve(styles)
+ .unwrap_or_default()
+ .map(|stroke| stroke.map(Stroke::unwrap_or_default)),
+ top_edge: elem.top_edge.get(styles),
+ bottom_edge: elem.bottom_edge.get(styles),
+ radius: elem.radius.resolve(styles).unwrap_or_default(),
+ },
+ extent: elem.extent.resolve(styles),
+ }],
+ ))
+};
+
+const SMALLCAPS_RULE: ShowFn = |elem, _, styles| {
+ let sc = if elem.all.get(styles) { Smallcaps::All } else { Smallcaps::Minuscules };
+ Ok(elem.body.clone().set(TextElem::smallcaps, Some(sc)))
+};
+
+const RAW_RULE: ShowFn = |elem, _, styles| {
+ let lines = elem.lines.as_deref().unwrap_or_default();
+
+ let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1));
+ for (i, line) in lines.iter().enumerate() {
+ if i != 0 {
+ seq.push(LinebreakElem::shared().clone());
+ }
+
+ seq.push(line.clone().pack());
+ }
+
+ let mut realized = Content::sequence(seq);
+
+ if elem.block.get(styles) {
+ // Align the text before inserting it into the block.
+ realized = realized.aligned(elem.align.get(styles).into());
+ realized = BlockElem::new()
+ .with_body(Some(BlockBody::Content(realized)))
+ .pack()
+ .spanned(elem.span());
+ }
+
+ Ok(realized)
+};
+
+const RAW_LINE_RULE: ShowFn = |elem, _, _| Ok(elem.body.clone());
+
+const ALIGN_RULE: ShowFn =
+ |elem, _, styles| Ok(elem.body.clone().aligned(elem.alignment.get(styles)));
+
+const PAD_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::multi_layouter(elem.clone(), crate::pad::layout_pad)
+ .pack()
+ .spanned(elem.span()))
+};
+
+const COLUMNS_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::multi_layouter(elem.clone(), crate::flow::layout_columns)
+ .pack()
+ .spanned(elem.span()))
+};
+
+const STACK_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::multi_layouter(elem.clone(), crate::stack::layout_stack)
+ .pack()
+ .spanned(elem.span()))
+};
+
+const GRID_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::multi_layouter(elem.clone(), crate::grid::layout_grid)
+ .pack()
+ .spanned(elem.span()))
+};
+
+const GRID_CELL_RULE: ShowFn = |elem, _, styles| {
+ show_cell(elem.body.clone(), elem.inset.get(styles), elem.align.get(styles))
+};
+
+/// Function with common code to display a grid cell or table cell.
+fn show_cell(
+ mut body: Content,
+ inset: Smart>>>,
+ align: Smart,
+) -> SourceResult {
+ let inset = inset.unwrap_or_default().map(Option::unwrap_or_default);
+
+ if inset != Sides::default() {
+ // Only pad if some inset is not 0pt.
+ // Avoids a bug where using .padded() in any way inside Show causes
+ // alignment in align(...) to break.
+ body = body.padded(inset);
+ }
+
+ if let Smart::Custom(alignment) = align {
+ body = body.aligned(alignment);
+ }
+
+ Ok(body)
+}
+
+const MOVE_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_move)
+ .pack()
+ .spanned(elem.span()))
+};
+
+const SCALE_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_scale)
+ .pack()
+ .spanned(elem.span()))
+};
+
+const ROTATE_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_rotate)
+ .pack()
+ .spanned(elem.span()))
+};
+
+const SKEW_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_skew)
+ .pack()
+ .spanned(elem.span()))
+};
+
+const REPEAT_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::single_layouter(elem.clone(), crate::repeat::layout_repeat)
+ .pack()
+ .spanned(elem.span()))
+};
+
+const HIDE_RULE: ShowFn =
+ |elem, _, _| Ok(elem.body.clone().set(HideElem::hidden, true));
+
+const LAYOUT_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::multi_layouter(
+ elem.clone(),
+ |elem, engine, locator, styles, regions| {
+ // Gets the current region's base size, which will be the size of the
+ // outer container, or of the page if there is no such container.
+ let Size { x, y } = regions.base();
+ let loc = elem.location().unwrap();
+ let context = Context::new(Some(loc), Some(styles));
+ let result = elem
+ .func
+ .call(engine, context.track(), [dict! { "width" => x, "height" => y }])?
+ .display();
+ crate::flow::layout_fragment(engine, &result, locator, styles, regions)
+ },
+ )
+ .pack()
+ .spanned(elem.span()))
+};
+
+const IMAGE_RULE: ShowFn = |elem, _, styles| {
+ Ok(BlockElem::single_layouter(elem.clone(), crate::image::layout_image)
+ .with_width(elem.width.get(styles))
+ .with_height(elem.height.get(styles))
+ .pack()
+ .spanned(elem.span()))
+};
+
+const LINE_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_line)
+ .pack()
+ .spanned(elem.span()))
+};
+
+const RECT_RULE: ShowFn = |elem, _, styles| {
+ Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_rect)
+ .with_width(elem.width.get(styles))
+ .with_height(elem.height.get(styles))
+ .pack()
+ .spanned(elem.span()))
+};
+
+const SQUARE_RULE: ShowFn = |elem, _, styles| {
+ Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_square)
+ .with_width(elem.width.get(styles))
+ .with_height(elem.height.get(styles))
+ .pack()
+ .spanned(elem.span()))
+};
+
+const ELLIPSE_RULE: ShowFn = |elem, _, styles| {
+ Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_ellipse)
+ .with_width(elem.width.get(styles))
+ .with_height(elem.height.get(styles))
+ .pack()
+ .spanned(elem.span()))
+};
+
+const CIRCLE_RULE: ShowFn = |elem, _, styles| {
+ Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_circle)
+ .with_width(elem.width.get(styles))
+ .with_height(elem.height.get(styles))
+ .pack()
+ .spanned(elem.span()))
+};
+
+const POLYGON_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_polygon)
+ .pack()
+ .spanned(elem.span()))
+};
+
+const CURVE_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_curve)
+ .pack()
+ .spanned(elem.span()))
+};
+
+const PATH_RULE: ShowFn = |elem, _, _| {
+ Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_path)
+ .pack()
+ .spanned(elem.span()))
+};
+
+const EQUATION_RULE: ShowFn = |elem, _, styles| {
+ if elem.block.get(styles) {
+ Ok(BlockElem::multi_layouter(elem.clone(), crate::math::layout_equation_block)
+ .pack()
+ .spanned(elem.span()))
+ } else {
+ Ok(InlineElem::layouter(elem.clone(), crate::math::layout_equation_inline)
+ .pack()
+ .spanned(elem.span()))
+ }
+};
+
+const EMBED_RULE: ShowFn = |_, _, _| Ok(Content::empty());
diff --git a/crates/typst-library/src/foundations/content/element.rs b/crates/typst-library/src/foundations/content/element.rs
index 49b0b0f9b..65c2e28b5 100644
--- a/crates/typst-library/src/foundations/content/element.rs
+++ b/crates/typst-library/src/foundations/content/element.rs
@@ -246,12 +246,6 @@ pub trait Synthesize {
-> SourceResult<()>;
}
-/// Defines a built-in show rule for an element.
-pub trait Show {
- /// Execute the base recipe for this element.
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult;
-}
-
/// Defines built-in show set rules for an element.
///
/// This is a bit more powerful than a user-defined show-set because it can
diff --git a/crates/typst-library/src/foundations/context.rs b/crates/typst-library/src/foundations/context.rs
index bf4bdcd25..56d87775e 100644
--- a/crates/typst-library/src/foundations/context.rs
+++ b/crates/typst-library/src/foundations/context.rs
@@ -3,7 +3,7 @@ use comemo::Track;
use crate::diag::{bail, Hint, HintedStrResult, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- elem, Args, Construct, Content, Func, Packed, Show, StyleChain, Value,
+ elem, Args, Construct, Content, Func, ShowFn, StyleChain, Value,
};
use crate::introspection::{Locatable, Location};
@@ -61,7 +61,7 @@ fn require(val: Option) -> HintedStrResult {
}
/// Executes a `context` block.
-#[elem(Construct, Locatable, Show)]
+#[elem(Construct, Locatable)]
pub struct ContextElem {
/// The function to call with the context.
#[required]
@@ -75,11 +75,8 @@ impl Construct for ContextElem {
}
}
-impl Show for Packed {
- #[typst_macros::time(name = "context", span = self.span())]
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult {
- let loc = self.location().unwrap();
- let context = Context::new(Some(loc), Some(styles));
- Ok(self.func.call::<[Value; 0]>(engine, context.track(), [])?.display())
- }
-}
+pub const CONTEXT_RULE: ShowFn = |elem, engine, styles| {
+ let loc = elem.location().unwrap();
+ let context = Context::new(Some(loc), Some(styles));
+ Ok(elem.func.call::<[Value; 0]>(engine, context.track(), [])?.display())
+};
diff --git a/crates/typst-library/src/foundations/styles.rs b/crates/typst-library/src/foundations/styles.rs
index 978b47d5f..0da036f6f 100644
--- a/crates/typst-library/src/foundations/styles.rs
+++ b/crates/typst-library/src/foundations/styles.rs
@@ -1,4 +1,5 @@
use std::any::{Any, TypeId};
+use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::{mem, ptr};
@@ -13,7 +14,7 @@ use crate::diag::{SourceResult, Trace, Tracepoint};
use crate::engine::Engine;
use crate::foundations::{
cast, ty, Content, Context, Element, Field, Func, NativeElement, OneOrMultiple,
- RefableProperty, Repr, Selector, SettableProperty,
+ Packed, RefableProperty, Repr, Selector, SettableProperty, Target,
};
use crate::text::{FontFamily, FontList, TextElem};
@@ -938,3 +939,129 @@ fn block_wrong_type(func: Element, id: u8, value: &Block) -> ! {
value
)
}
+
+/// Holds native show rules.
+pub struct NativeRuleMap {
+ rules: HashMap<(Element, Target), NativeShowRule>,
+}
+
+/// The signature of a native show rule.
+pub type ShowFn = fn(
+ elem: &Packed,
+ engine: &mut Engine,
+ styles: StyleChain,
+) -> SourceResult;
+
+impl NativeRuleMap {
+ /// Creates a new rule map.
+ ///
+ /// Should be populated with rules for all target-element combinations that
+ /// are supported.
+ ///
+ /// Contains built-in rules for a few special elements.
+ pub fn new() -> Self {
+ let mut rules = Self { rules: HashMap::new() };
+
+ // ContextElem is as special as SequenceElem and StyledElem and could,
+ // in theory, also be special cased in realization.
+ rules.register_builtin(crate::foundations::CONTEXT_RULE);
+
+ // CounterDisplayElem only exists because the compiler can't currently
+ // express the equivalent of `context counter(..).display(..)` in native
+ // code (no native closures).
+ rules.register_builtin(crate::introspection::COUNTER_DISPLAY_RULE);
+
+ // These are all only for introspection and empty on all targets.
+ rules.register_empty::();
+ rules.register_empty::();
+ rules.register_empty::();
+ rules.register_empty::();
+
+ rules
+ }
+
+ /// Registers a rule for all targets.
+ fn register_empty(&mut self) {
+ self.register_builtin::(|_, _, _| Ok(Content::empty()));
+ }
+
+ /// Registers a rule for all targets.
+ fn register_builtin(&mut self, f: ShowFn) {
+ self.register(Target::Paged, f);
+ self.register(Target::Html, f);
+ }
+
+ /// Registers a rule for a target.
+ ///
+ /// Panics if a rule already exists for this target-element combination.
+ pub fn register(&mut self, target: Target, f: ShowFn) {
+ let res = self.rules.insert((T::ELEM, target), NativeShowRule::new(f));
+ if res.is_some() {
+ panic!(
+ "duplicate native show rule for `{}` on {target:?} target",
+ T::ELEM.name()
+ )
+ }
+ }
+
+ /// Retrieves the rule that applies to the `content` on the current
+ /// `target`.
+ pub fn get(&self, target: Target, content: &Content) -> Option {
+ self.rules.get(&(content.func(), target)).copied()
+ }
+}
+
+impl Default for NativeRuleMap {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+pub use rule::NativeShowRule;
+
+mod rule {
+ use super::*;
+
+ /// The show rule for a native element.
+ #[derive(Copy, Clone)]
+ pub struct NativeShowRule {
+ /// The element to which this rule applies.
+ elem: Element,
+ /// Must only be called with content of the appropriate type.
+ f: unsafe fn(
+ elem: &Content,
+ engine: &mut Engine,
+ styles: StyleChain,
+ ) -> SourceResult,
+ }
+
+ impl NativeShowRule {
+ /// Create a new type-erased show rule.
+ pub fn new(f: ShowFn) -> Self {
+ Self {
+ elem: T::ELEM,
+ // Safety: The two function pointer types only differ in the
+ // first argument, which changes from `&Packed` to
+ // `&Content`. `Packed` is a transparent wrapper around
+ // `Content`. The resulting function is unsafe to call because
+ // content of the correct type must be passed to it.
+ #[allow(clippy::missing_transmute_annotations)]
+ f: unsafe { std::mem::transmute(f) },
+ }
+ }
+
+ /// Applies the rule to content. Panics if the content is of the wrong
+ /// type.
+ pub fn apply(
+ &self,
+ content: &Content,
+ engine: &mut Engine,
+ styles: StyleChain,
+ ) -> SourceResult {
+ assert_eq!(content.elem(), self.elem);
+
+ // Safety: We just checked that the element is of the correct type.
+ unsafe { (self.f)(content, engine, styles) }
+ }
+ }
+}
diff --git a/crates/typst-library/src/foundations/target.rs b/crates/typst-library/src/foundations/target.rs
index 71e7554e0..ff90f1f7b 100644
--- a/crates/typst-library/src/foundations/target.rs
+++ b/crates/typst-library/src/foundations/target.rs
@@ -4,7 +4,7 @@ use crate::diag::HintedStrResult;
use crate::foundations::{elem, func, Cast, Context};
/// The export target.
-#[derive(Debug, Default, Copy, Clone, PartialEq, Hash, Cast)]
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum Target {
/// The target that is used for paged, fully laid-out content.
#[default]
diff --git a/crates/typst-library/src/introspection/counter.rs b/crates/typst-library/src/introspection/counter.rs
index a7925e13a..b3c52de4e 100644
--- a/crates/typst-library/src/introspection/counter.rs
+++ b/crates/typst-library/src/introspection/counter.rs
@@ -12,7 +12,7 @@ use crate::engine::{Engine, Route, Sink, Traced};
use crate::foundations::{
cast, elem, func, scope, select_where, ty, Args, Array, Construct, Content, Context,
Element, Func, IntoValue, Label, LocatableSelector, NativeElement, Packed, Repr,
- Selector, Show, Smart, Str, StyleChain, Value,
+ Selector, ShowFn, Smart, Str, StyleChain, Value,
};
use crate::introspection::{Introspector, Locatable, Location, Tag};
use crate::layout::{Frame, FrameItem, PageElem};
@@ -683,8 +683,8 @@ cast! {
}
/// Executes an update of a counter.
-#[elem(Construct, Locatable, Show, Count)]
-struct CounterUpdateElem {
+#[elem(Construct, Locatable, Count)]
+pub struct CounterUpdateElem {
/// The key that identifies the counter.
#[required]
key: CounterKey,
@@ -701,12 +701,6 @@ impl Construct for CounterUpdateElem {
}
}
-impl Show for Packed {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult {
- Ok(Content::empty())
- }
-}
-
impl Count for Packed {
fn update(&self) -> Option {
Some(self.update.clone())
@@ -714,7 +708,7 @@ impl Count for Packed {
}
/// Executes a display of a counter.
-#[elem(Construct, Locatable, Show)]
+#[elem(Construct, Locatable)]
pub struct CounterDisplayElem {
/// The counter.
#[required]
@@ -738,20 +732,18 @@ impl Construct for CounterDisplayElem {
}
}
-impl Show for Packed {
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult {
- Ok(self
- .counter
- .display_impl(
- engine,
- self.location().unwrap(),
- self.numbering.clone(),
- self.both,
- Some(styles),
- )?
- .display())
- }
-}
+pub const COUNTER_DISPLAY_RULE: ShowFn = |elem, engine, styles| {
+ Ok(elem
+ .counter
+ .display_impl(
+ engine,
+ elem.location().unwrap(),
+ elem.numbering.clone(),
+ elem.both,
+ Some(styles),
+ )?
+ .display())
+};
/// An specialized handler of the page counter that tracks both the physical
/// and the logical page counter.
diff --git a/crates/typst-library/src/introspection/metadata.rs b/crates/typst-library/src/introspection/metadata.rs
index 06000174f..8ad74b96b 100644
--- a/crates/typst-library/src/introspection/metadata.rs
+++ b/crates/typst-library/src/introspection/metadata.rs
@@ -1,6 +1,4 @@
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::foundations::{elem, Content, Packed, Show, StyleChain, Value};
+use crate::foundations::{elem, Value};
use crate::introspection::Locatable;
/// Exposes a value to the query system without producing visible content.
@@ -24,15 +22,9 @@ use crate::introspection::Locatable;
/// query().first().value
/// }
/// ```
-#[elem(Show, Locatable)]
+#[elem(Locatable)]
pub struct MetadataElem {
/// The value to embed into the document.
#[required]
pub value: Value,
}
-
-impl Show for Packed {
- fn show(&self, _: &mut Engine, _styles: StyleChain) -> SourceResult {
- Ok(Content::empty())
- }
-}
diff --git a/crates/typst-library/src/introspection/state.rs b/crates/typst-library/src/introspection/state.rs
index 784f2acb2..2d15a5de0 100644
--- a/crates/typst-library/src/introspection/state.rs
+++ b/crates/typst-library/src/introspection/state.rs
@@ -6,8 +6,7 @@ use crate::diag::{bail, At, SourceResult};
use crate::engine::{Engine, Route, Sink, Traced};
use crate::foundations::{
cast, elem, func, scope, select_where, ty, Args, Construct, Content, Context, Func,
- LocatableSelector, NativeElement, Packed, Repr, Selector, Show, Str, StyleChain,
- Value,
+ LocatableSelector, NativeElement, Repr, Selector, Str, Value,
};
use crate::introspection::{Introspector, Locatable, Location};
use crate::routines::Routines;
@@ -372,8 +371,8 @@ cast! {
}
/// Executes a display of a state.
-#[elem(Construct, Locatable, Show)]
-struct StateUpdateElem {
+#[elem(Construct, Locatable)]
+pub struct StateUpdateElem {
/// The key that identifies the state.
#[required]
key: Str,
@@ -389,9 +388,3 @@ impl Construct for StateUpdateElem {
bail!(args.span, "cannot be constructed manually");
}
}
-
-impl Show for Packed {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult {
- Ok(Content::empty())
- }
-}
diff --git a/crates/typst-library/src/layout/align.rs b/crates/typst-library/src/layout/align.rs
index e5ceddf65..447648f01 100644
--- a/crates/typst-library/src/layout/align.rs
+++ b/crates/typst-library/src/layout/align.rs
@@ -2,11 +2,10 @@ use std::ops::Add;
use ecow::{eco_format, EcoString};
-use crate::diag::{bail, HintedStrResult, SourceResult, StrResult};
-use crate::engine::Engine;
+use crate::diag::{bail, HintedStrResult, StrResult};
use crate::foundations::{
- cast, elem, func, scope, ty, CastInfo, Content, Fold, FromValue, IntoValue, Packed,
- Reflect, Repr, Resolve, Show, StyleChain, Value,
+ cast, elem, func, scope, ty, CastInfo, Content, Fold, FromValue, IntoValue, Reflect,
+ Repr, Resolve, StyleChain, Value,
};
use crate::layout::{Abs, Axes, Axis, Dir, Side};
use crate::text::TextElem;
@@ -73,7 +72,7 @@ use crate::text::TextElem;
/// ```example
/// Start #h(1fr) End
/// ```
-#[elem(Show)]
+#[elem]
pub struct AlignElem {
/// The [alignment] along both axes.
///
@@ -97,13 +96,6 @@ pub struct AlignElem {
pub body: Content,
}
-impl Show for Packed {
- #[typst_macros::time(name = "align", span = self.span())]
- fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult {
- Ok(self.body.clone().aligned(self.alignment.get(styles)))
- }
-}
-
/// Where to align something along an axis.
///
/// Possible values are:
diff --git a/crates/typst-library/src/layout/columns.rs b/crates/typst-library/src/layout/columns.rs
index 1cea52759..e7bce393b 100644
--- a/crates/typst-library/src/layout/columns.rs
+++ b/crates/typst-library/src/layout/columns.rs
@@ -1,9 +1,7 @@
use std::num::NonZeroUsize;
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
-use crate::layout::{BlockElem, Length, Ratio, Rel};
+use crate::foundations::{elem, Content};
+use crate::layout::{Length, Ratio, Rel};
/// Separates a region into multiple equally sized columns.
///
@@ -41,7 +39,7 @@ use crate::layout::{BlockElem, Length, Ratio, Rel};
///
/// #lorem(40)
/// ```
-#[elem(Show)]
+#[elem]
pub struct ColumnsElem {
/// The number of columns.
#[positional]
@@ -57,14 +55,6 @@ pub struct ColumnsElem {
pub body: Content,
}
-impl Show for Packed {
- fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult {
- Ok(BlockElem::multi_layouter(self.clone(), engine.routines.layout_columns)
- .pack()
- .spanned(self.span()))
- }
-}
-
/// Forces a column break.
///
/// The function will behave like a [page break]($pagebreak) when used in a
diff --git a/crates/typst-library/src/layout/grid/mod.rs b/crates/typst-library/src/layout/grid/mod.rs
index 64e7464be..658523ec6 100644
--- a/crates/typst-library/src/layout/grid/mod.rs
+++ b/crates/typst-library/src/layout/grid/mod.rs
@@ -11,10 +11,10 @@ use crate::diag::{bail, At, HintedStrResult, HintedString, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Array, CastInfo, Content, Context, Fold, FromValue, Func,
- IntoValue, NativeElement, Packed, Reflect, Resolve, Show, Smart, StyleChain, Value,
+ IntoValue, Packed, Reflect, Resolve, Smart, StyleChain, Value,
};
use crate::layout::{
- Alignment, BlockElem, Length, OuterHAlignment, OuterVAlignment, Rel, Sides, Sizing,
+ Alignment, Length, OuterHAlignment, OuterVAlignment, Rel, Sides, Sizing,
};
use crate::model::{TableCell, TableFooter, TableHLine, TableHeader, TableVLine};
use crate::visualize::{Paint, Stroke};
@@ -136,7 +136,7 @@ use crate::visualize::{Paint, Stroke};
///
/// Furthermore, strokes of a repeated grid header or footer will take
/// precedence over regular cell strokes.
-#[elem(scope, Show)]
+#[elem(scope)]
pub struct GridElem {
/// The column sizes.
///
@@ -320,14 +320,6 @@ impl GridElem {
type GridFooter;
}
-impl Show for Packed {
- fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult {
- Ok(BlockElem::multi_layouter(self.clone(), engine.routines.layout_grid)
- .pack()
- .spanned(self.span()))
- }
-}
-
/// Track sizing definitions.
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct TrackSizings(pub SmallVec<[Sizing; 4]>);
@@ -648,7 +640,7 @@ pub struct GridVLine {
/// which allows you, for example, to apply styles based on a cell's position.
/// Refer to the examples of the [`table.cell`]($table.cell) element to learn
/// more about this.
-#[elem(name = "cell", title = "Grid Cell", Show)]
+#[elem(name = "cell", title = "Grid Cell")]
pub struct GridCell {
/// The cell's body.
#[required]
@@ -748,12 +740,6 @@ cast! {
v: Content => v.into(),
}
-impl Show for Packed {
- fn show(&self, _engine: &mut Engine, styles: StyleChain) -> SourceResult {
- show_grid_cell(self.body.clone(), self.inset.get(styles), self.align.get(styles))
- }
-}
-
impl Default for Packed {
fn default() -> Self {
Packed::new(
@@ -774,28 +760,6 @@ impl From for GridCell {
}
}
-/// Function with common code to display a grid cell or table cell.
-pub(crate) fn show_grid_cell(
- mut body: Content,
- inset: Smart>>>,
- align: Smart,
-) -> SourceResult {
- let inset = inset.unwrap_or_default().map(Option::unwrap_or_default);
-
- if inset != Sides::default() {
- // Only pad if some inset is not 0pt.
- // Avoids a bug where using .padded() in any way inside Show causes
- // alignment in align(...) to break.
- body = body.padded(inset);
- }
-
- if let Smart::Custom(alignment) = align {
- body = body.aligned(alignment);
- }
-
- Ok(body)
-}
-
/// A value that can be configured per cell.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Celled {
diff --git a/crates/typst-library/src/layout/hide.rs b/crates/typst-library/src/layout/hide.rs
index 5f3a5a2d3..bb40447d5 100644
--- a/crates/typst-library/src/layout/hide.rs
+++ b/crates/typst-library/src/layout/hide.rs
@@ -1,6 +1,4 @@
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::foundations::{elem, Content, Packed, Show, StyleChain};
+use crate::foundations::{elem, Content};
/// Hides content without affecting layout.
///
@@ -14,7 +12,7 @@ use crate::foundations::{elem, Content, Packed, Show, StyleChain};
/// Hello Jane \
/// #hide[Hello] Joe
/// ```
-#[elem(Show)]
+#[elem]
pub struct HideElem {
/// The content to hide.
#[required]
@@ -25,10 +23,3 @@ pub struct HideElem {
#[ghost]
pub hidden: bool,
}
-
-impl Show for Packed {
- #[typst_macros::time(name = "hide", span = self.span())]
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult {
- Ok(self.body.clone().set(HideElem::hidden, true))
- }
-}
diff --git a/crates/typst-library/src/layout/layout.rs b/crates/typst-library/src/layout/layout.rs
index 46271ff22..00897bcfe 100644
--- a/crates/typst-library/src/layout/layout.rs
+++ b/crates/typst-library/src/layout/layout.rs
@@ -1,13 +1,7 @@
-use comemo::Track;
use typst_syntax::Span;
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::foundations::{
- dict, elem, func, Content, Context, Func, NativeElement, Packed, Show, StyleChain,
-};
+use crate::foundations::{elem, func, Content, Func, NativeElement};
use crate::introspection::Locatable;
-use crate::layout::{BlockElem, Size};
/// Provides access to the current outer container's (or page's, if none)
/// dimensions (width and height).
@@ -86,37 +80,9 @@ pub fn layout(
}
/// Executes a `layout` call.
-#[elem(Locatable, Show)]
-struct LayoutElem {
+#[elem(Locatable)]
+pub struct LayoutElem {
/// The function to call with the outer container's (or page's) size.
#[required]
- func: Func,
-}
-
-impl Show for Packed {
- fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult {
- Ok(BlockElem::multi_layouter(
- self.clone(),
- |elem, engine, locator, styles, regions| {
- // Gets the current region's base size, which will be the size of the
- // outer container, or of the page if there is no such container.
- let Size { x, y } = regions.base();
- let loc = elem.location().unwrap();
- let context = Context::new(Some(loc), Some(styles));
- let result = elem
- .func
- .call(
- engine,
- context.track(),
- [dict! { "width" => x, "height" => y }],
- )?
- .display();
- (engine.routines.layout_fragment)(
- engine, &result, locator, styles, regions,
- )
- },
- )
- .pack()
- .spanned(self.span()))
- }
+ pub func: Func,
}
diff --git a/crates/typst-library/src/layout/pad.rs b/crates/typst-library/src/layout/pad.rs
index 1dc6d1316..d533df35b 100644
--- a/crates/typst-library/src/layout/pad.rs
+++ b/crates/typst-library/src/layout/pad.rs
@@ -1,7 +1,5 @@
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
-use crate::layout::{BlockElem, Length, Rel};
+use crate::foundations::{elem, Content};
+use crate::layout::{Length, Rel};
/// Adds spacing around content.
///
@@ -16,7 +14,7 @@ use crate::layout::{BlockElem, Length, Rel};
/// _Typing speeds can be
/// measured in words per minute._
/// ```
-#[elem(title = "Padding", Show)]
+#[elem(title = "Padding")]
pub struct PadElem {
/// The padding at the left side.
#[parse(
@@ -55,11 +53,3 @@ pub struct PadElem {
#[required]
pub body: Content,
}
-
-impl Show for Packed {
- fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult {
- Ok(BlockElem::multi_layouter(self.clone(), engine.routines.layout_pad)
- .pack()
- .spanned(self.span()))
- }
-}
diff --git a/crates/typst-library/src/layout/repeat.rs b/crates/typst-library/src/layout/repeat.rs
index 9579f1856..a38d5f896 100644
--- a/crates/typst-library/src/layout/repeat.rs
+++ b/crates/typst-library/src/layout/repeat.rs
@@ -1,7 +1,5 @@
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
-use crate::layout::{BlockElem, Length};
+use crate::foundations::{elem, Content};
+use crate::layout::Length;
/// Repeats content to the available space.
///
@@ -24,7 +22,7 @@ use crate::layout::{BlockElem, Length};
/// Berlin, the 22nd of December, 2022
/// ]
/// ```
-#[elem(Show)]
+#[elem]
pub struct RepeatElem {
/// The content to repeat.
#[required]
@@ -39,11 +37,3 @@ pub struct RepeatElem {
#[default(true)]
pub justify: bool,
}
-
-impl Show for Packed {
- fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult {
- Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_repeat)
- .pack()
- .spanned(self.span()))
- }
-}
diff --git a/crates/typst-library/src/layout/stack.rs b/crates/typst-library/src/layout/stack.rs
index 5fc78480e..fca1ecb86 100644
--- a/crates/typst-library/src/layout/stack.rs
+++ b/crates/typst-library/src/layout/stack.rs
@@ -1,9 +1,7 @@
use std::fmt::{self, Debug, Formatter};
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::foundations::{cast, elem, Content, NativeElement, Packed, Show, StyleChain};
-use crate::layout::{BlockElem, Dir, Spacing};
+use crate::foundations::{cast, elem, Content};
+use crate::layout::{Dir, Spacing};
/// Arranges content and spacing horizontally or vertically.
///
@@ -19,7 +17,7 @@ use crate::layout::{BlockElem, Dir, Spacing};
/// rect(width: 90pt),
/// )
/// ```
-#[elem(Show)]
+#[elem]
pub struct StackElem {
/// The direction along which the items are stacked. Possible values are:
///
@@ -47,14 +45,6 @@ pub struct StackElem {
pub children: Vec,
}
-impl Show for Packed {
- fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult {
- Ok(BlockElem::multi_layouter(self.clone(), engine.routines.layout_stack)
- .pack()
- .spanned(self.span()))
- }
-}
-
/// A child of a stack element.
#[derive(Clone, PartialEq, Hash)]
pub enum StackChild {
diff --git a/crates/typst-library/src/layout/transform.rs b/crates/typst-library/src/layout/transform.rs
index d153d97db..c2d9a21c7 100644
--- a/crates/typst-library/src/layout/transform.rs
+++ b/crates/typst-library/src/layout/transform.rs
@@ -1,11 +1,5 @@
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::foundations::{
- cast, elem, Content, NativeElement, Packed, Show, Smart, StyleChain,
-};
-use crate::layout::{
- Abs, Alignment, Angle, BlockElem, HAlignment, Length, Ratio, Rel, VAlignment,
-};
+use crate::foundations::{cast, elem, Content, Smart};
+use crate::layout::{Abs, Alignment, Angle, HAlignment, Length, Ratio, Rel, VAlignment};
/// Moves content without affecting layout.
///
@@ -25,7 +19,7 @@ use crate::layout::{
/// )
/// ))
/// ```
-#[elem(Show)]
+#[elem]
pub struct MoveElem {
/// The horizontal displacement of the content.
pub dx: Rel,
@@ -38,14 +32,6 @@ pub struct MoveElem {
pub body: Content,
}
-impl Show for Packed {
- fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult {
- Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_move)
- .pack()
- .spanned(self.span()))
- }
-}
-
/// Rotates content without affecting layout.
///
/// Rotates an element by a given angle. The layout will act as if the element
@@ -60,7 +46,7 @@ impl Show for Packed {
/// .map(i => rotate(24deg * i)[X]),
/// )
/// ```
-#[elem(Show)]
+#[elem]
pub struct RotateElem {
/// The amount of rotation.
///
@@ -107,14 +93,6 @@ pub struct RotateElem {
pub body: Content,
}
-impl Show for Packed {
- fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult {
- Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_rotate)
- .pack()
- .spanned(self.span()))
- }
-}
-
/// Scales content without affecting layout.
///
/// Lets you mirror content by specifying a negative scale on a single axis.
@@ -125,7 +103,7 @@ impl Show for Packed {
/// #scale(x: -100%)[This is mirrored.]
/// #scale(x: -100%, reflow: true)[This is mirrored.]
/// ```
-#[elem(Show)]
+#[elem]
pub struct ScaleElem {
/// The scaling factor for both axes, as a positional argument. This is just
/// an optional shorthand notation for setting `x` and `y` to the same
@@ -179,14 +157,6 @@ pub struct ScaleElem {
pub body: Content,
}
-impl Show for Packed {
- fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult {
- Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_scale)
- .pack()
- .spanned(self.span()))
- }
-}
-
/// To what size something shall be scaled.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ScaleAmount {
@@ -215,7 +185,7 @@ cast! {
/// This is some fake italic text.
/// ]
/// ```
-#[elem(Show)]
+#[elem]
pub struct SkewElem {
/// The horizontal skewing angle.
///
@@ -265,14 +235,6 @@ pub struct SkewElem {
pub body: Content,
}
-impl Show for Packed {
- fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult {
- Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_skew)
- .pack()
- .spanned(self.span()))
- }
-}
-
/// A scale-skew-translate transformation.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Transform {
diff --git a/crates/typst-library/src/math/equation.rs b/crates/typst-library/src/math/equation.rs
index b97bb18da..0c9ba11df 100644
--- a/crates/typst-library/src/math/equation.rs
+++ b/crates/typst-library/src/math/equation.rs
@@ -6,13 +6,11 @@ use unicode_math_class::MathClass;
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{
- elem, Content, NativeElement, Packed, Show, ShowSet, Smart, StyleChain, Styles,
- Synthesize,
+ elem, Content, NativeElement, Packed, ShowSet, Smart, StyleChain, Styles, Synthesize,
};
use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
use crate::layout::{
- AlignElem, Alignment, BlockElem, InlineElem, OuterHAlignment, SpecificAlignment,
- VAlignment,
+ AlignElem, Alignment, BlockElem, OuterHAlignment, SpecificAlignment, VAlignment,
};
use crate::math::{MathSize, MathVariant};
use crate::model::{Numbering, Outlinable, ParLine, Refable, Supplement};
@@ -46,7 +44,7 @@ use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem};
/// least one space lifts it into a separate block that is centered
/// horizontally. For more details about math syntax, see the
/// [main math page]($category/math).
-#[elem(Locatable, Synthesize, Show, ShowSet, Count, LocalName, Refable, Outlinable)]
+#[elem(Locatable, Synthesize, ShowSet, Count, LocalName, Refable, Outlinable)]
pub struct EquationElem {
/// Whether the equation is displayed as a separate block.
#[default(false)]
@@ -165,23 +163,6 @@ impl Synthesize for Packed {
}
}
-impl Show for Packed {
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult {
- if self.block.get(styles) {
- Ok(BlockElem::multi_layouter(
- self.clone(),
- engine.routines.layout_equation_block,
- )
- .pack()
- .spanned(self.span()))
- } else {
- Ok(InlineElem::layouter(self.clone(), engine.routines.layout_equation_inline)
- .pack()
- .spanned(self.span()))
- }
- }
-}
-
impl ShowSet for Packed {
fn show_set(&self, styles: StyleChain) -> Styles {
let mut out = Styles::new();
diff --git a/crates/typst-library/src/model/bibliography.rs b/crates/typst-library/src/model/bibliography.rs
index c44748a95..188af4da1 100644
--- a/crates/typst-library/src/model/bibliography.rs
+++ b/crates/typst-library/src/model/bibliography.rs
@@ -2,7 +2,6 @@ use std::any::TypeId;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fmt::{self, Debug, Formatter};
-use std::num::NonZeroUsize;
use std::path::Path;
use std::sync::{Arc, LazyLock};
@@ -17,7 +16,7 @@ use hayagriva::{
use indexmap::IndexMap;
use smallvec::{smallvec, SmallVec};
use typst_syntax::{Span, Spanned, SyntaxMode};
-use typst_utils::{Get, ManuallyHash, NonZeroExt, PicoStr};
+use typst_utils::{ManuallyHash, PicoStr};
use crate::diag::{
bail, error, At, HintedStrResult, LoadError, LoadResult, LoadedWithin, ReportPos,
@@ -26,18 +25,17 @@ use crate::diag::{
use crate::engine::{Engine, Sink};
use crate::foundations::{
elem, Bytes, CastInfo, Content, Derived, FromValue, IntoValue, Label, NativeElement,
- OneOrMultiple, Packed, Reflect, Scope, Show, ShowSet, Smart, StyleChain, Styles,
+ OneOrMultiple, Packed, Reflect, Scope, ShowSet, Smart, StyleChain, Styles,
Synthesize, Value,
};
use crate::introspection::{Introspector, Locatable, Location};
use crate::layout::{
BlockBody, BlockElem, Em, GridCell, GridChild, GridElem, GridItem, HElem, PadElem,
- Sides, Sizing, TrackSizings,
+ Sizing, TrackSizings,
};
use crate::loading::{format_yaml_error, DataSource, Load, LoadSource, Loaded};
use crate::model::{
- CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, ParElem,
- Url,
+ CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, Url,
};
use crate::routines::Routines;
use crate::text::{
@@ -88,7 +86,7 @@ use crate::World;
///
/// #bibliography("works.bib")
/// ```
-#[elem(Locatable, Synthesize, Show, ShowSet, LocalName)]
+#[elem(Locatable, Synthesize, ShowSet, LocalName)]
pub struct BibliographyElem {
/// One or multiple paths to or raw bytes for Hayagriva `.yaml` and/or
/// BibLaTeX `.bib` files.
@@ -203,84 +201,6 @@ impl Synthesize for Packed {
}
}
-impl Show for Packed {
- #[typst_macros::time(name = "bibliography", span = self.span())]
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult {
- const COLUMN_GUTTER: Em = Em::new(0.65);
- const INDENT: Em = Em::new(1.5);
-
- let span = self.span();
-
- let mut seq = vec![];
- if let Some(title) = self.title.get_ref(styles).clone().unwrap_or_else(|| {
- Some(TextElem::packed(Self::local_name_in(styles)).spanned(span))
- }) {
- seq.push(
- HeadingElem::new(title)
- .with_depth(NonZeroUsize::ONE)
- .pack()
- .spanned(span),
- );
- }
-
- let works = Works::generate(engine).at(span)?;
- let references = works
- .references
- .as_ref()
- .ok_or_else(|| match self.style.get_ref(styles).source {
- CslSource::Named(style) => eco_format!(
- "CSL style \"{}\" is not suitable for bibliographies",
- style.display_name()
- ),
- CslSource::Normal(..) => {
- "CSL style is not suitable for bibliographies".into()
- }
- })
- .at(span)?;
-
- if references.iter().any(|(prefix, _)| prefix.is_some()) {
- let row_gutter = styles.get(ParElem::spacing);
-
- let mut cells = vec![];
- for (prefix, reference) in references {
- cells.push(GridChild::Item(GridItem::Cell(
- Packed::new(GridCell::new(prefix.clone().unwrap_or_default()))
- .spanned(span),
- )));
- cells.push(GridChild::Item(GridItem::Cell(
- Packed::new(GridCell::new(reference.clone())).spanned(span),
- )));
- }
- seq.push(
- GridElem::new(cells)
- .with_columns(TrackSizings(smallvec![Sizing::Auto; 2]))
- .with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()]))
- .with_row_gutter(TrackSizings(smallvec![row_gutter.into()]))
- .pack()
- .spanned(span),
- );
- } else {
- for (_, reference) in references {
- let realized = reference.clone();
- let block = if works.hanging_indent {
- let body = HElem::new((-INDENT).into()).pack() + realized;
- let inset = Sides::default()
- .with(styles.resolve(TextElem::dir).start(), Some(INDENT.into()));
- BlockElem::new()
- .with_body(Some(BlockBody::Content(body)))
- .with_inset(inset)
- } else {
- BlockElem::new().with_body(Some(BlockBody::Content(realized)))
- };
-
- seq.push(block.pack().spanned(span));
- }
- }
-
- Ok(Content::sequence(seq))
- }
-}
-
impl ShowSet for Packed {
fn show_set(&self, _: StyleChain) -> Styles {
const INDENT: Em = Em::new(1.0);
@@ -564,7 +484,7 @@ impl IntoValue for CslSource {
/// memoization) for the whole document. This setup is necessary because
/// citation formatting is inherently stateful and we need access to all
/// citations to do it.
-pub(super) struct Works {
+pub struct Works {
/// Maps from the location of a citation group to its rendered content.
pub citations: HashMap>,
/// Lists all references in the bibliography, with optional prefix, or
diff --git a/crates/typst-library/src/model/cite.rs b/crates/typst-library/src/model/cite.rs
index 195139907..b3ae3e522 100644
--- a/crates/typst-library/src/model/cite.rs
+++ b/crates/typst-library/src/model/cite.rs
@@ -3,8 +3,7 @@ use typst_syntax::Spanned;
use crate::diag::{error, At, HintedString, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, Cast, Content, Derived, Label, Packed, Show, Smart, StyleChain,
- Synthesize,
+ cast, elem, Cast, Content, Derived, Label, Packed, Smart, StyleChain, Synthesize,
};
use crate::introspection::Locatable;
use crate::model::bibliography::Works;
@@ -153,16 +152,15 @@ pub enum CitationForm {
///
/// This is automatically created from adjacent citations during show rule
/// application.
-#[elem(Locatable, Show)]
+#[elem(Locatable)]
pub struct CiteGroup {
/// The citations.
#[required]
pub children: Vec>,
}
-impl Show for Packed {
- #[typst_macros::time(name = "cite", span = self.span())]
- fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult {
+impl Packed {
+ pub fn realize(&self, engine: &mut Engine) -> SourceResult {
let location = self.location().unwrap();
let span = self.span();
Works::generate(engine)
diff --git a/crates/typst-library/src/model/emph.rs b/crates/typst-library/src/model/emph.rs
index 2d9cbec12..6267736b3 100644
--- a/crates/typst-library/src/model/emph.rs
+++ b/crates/typst-library/src/model/emph.rs
@@ -1,10 +1,4 @@
-use crate::diag::SourceResult;
-use crate::engine::Engine;
-use crate::foundations::{
- elem, Content, NativeElement, Packed, Show, StyleChain, TargetElem,
-};
-use crate::html::{tag, HtmlElem};
-use crate::text::{ItalicToggle, TextElem};
+use crate::foundations::{elem, Content};
/// Emphasizes content by toggling italics.
///
@@ -29,24 +23,9 @@ use crate::text::{ItalicToggle, TextElem};
/// This function also has dedicated syntax: To emphasize content, simply
/// enclose it in underscores (`_`). Note that this only works at word
/// boundaries. To emphasize part of a word, you have to use the function.
-#[elem(title = "Emphasis", keywords = ["italic"], Show)]
+#[elem(title = "Emphasis", keywords = ["italic"])]
pub struct EmphElem {
/// The content to emphasize.
#[required]
pub body: Content,
}
-
-impl Show for Packed {
- #[typst_macros::time(name = "emph", span = self.span())]
- fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult {
- let body = self.body.clone();
- Ok(if styles.get(TargetElem::target).is_html() {
- HtmlElem::new(tag::em)
- .with_body(Some(body))
- .pack()
- .spanned(self.span())
- } else {
- body.set(TextElem::emph, ItalicToggle(true))
- })
- }
-}
diff --git a/crates/typst-library/src/model/enum.rs b/crates/typst-library/src/model/enum.rs
index 8c1916581..388fb9eda 100644
--- a/crates/typst-library/src/model/enum.rs
+++ b/crates/typst-library/src/model/enum.rs
@@ -1,19 +1,11 @@
use std::str::FromStr;
-use ecow::eco_format;
use smallvec::SmallVec;
-use crate::diag::{bail, SourceResult};
-use crate::engine::Engine;
-use crate::foundations::{
- cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain,
- Styles, TargetElem,
-};
-use crate::html::{attr, tag, HtmlElem};
-use crate::layout::{Alignment, BlockElem, Em, HAlignment, Length, VAlignment, VElem};
-use crate::model::{
- ListItemLike, ListLike, Numbering, NumberingPattern, ParElem, ParbreakElem,
-};
+use crate::diag::bail;
+use crate::foundations::{cast, elem, scope, Array, Content, Packed, Smart, Styles};
+use crate::layout::{Alignment, Em, HAlignment, Length, VAlignment};
+use crate::model::{ListItemLike, ListLike, Numbering, NumberingPattern};
/// A numbered list.
///
@@ -71,7 +63,7 @@ use crate::model::{
/// Enumeration items can contain multiple paragraphs and other block-level
/// content. All content that is indented more than an item's marker becomes
/// part of that item.
-#[elem(scope, title = "Numbered List", Show)]
+#[elem(scope, title = "Numbered List")]
pub struct EnumElem {
/// Defines the default [spacing]($enum.spacing) of the enumeration. If it
/// is `{false}`, the items are spaced apart with
@@ -223,51 +215,6 @@ impl EnumElem {
type EnumItem;
}
-impl Show for Packed {
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult {
- let tight = self.tight.get(styles);
-
- if styles.get(TargetElem::target).is_html() {
- let mut elem = HtmlElem::new(tag::ol);
- if self.reversed.get(styles) {
- elem = elem.with_attr(attr::reversed, "reversed");
- }
- if let Some(n) = self.start.get(styles).custom() {
- elem = elem.with_attr(attr::start, eco_format!("{n}"));
- }
- let body = Content::sequence(self.children.iter().map(|item| {
- let mut li = HtmlElem::new(tag::li);
- if let Some(nr) = item.number.get(styles) {
- li = li.with_attr(attr::value, eco_format!("{nr}"));
- }
- // Text in wide enums shall always turn into paragraphs.
- let mut body = item.body.clone();
- if !tight {
- body += ParbreakElem::shared();
- }
- li.with_body(Some(body)).pack().spanned(item.span())
- }));
- return Ok(elem.with_body(Some(body)).pack().spanned(self.span()));
- }
-
- let mut realized =
- BlockElem::multi_layouter(self.clone(), engine.routines.layout_enum)
- .pack()
- .spanned(self.span());
-
- if tight {
- let spacing = self
- .spacing
- .get(styles)
- .unwrap_or_else(|| styles.get(ParElem::leading));
- let v = VElem::new(spacing.into()).with_weak(true).with_attach(true).pack();
- realized = v + realized;
- }
-
- Ok(realized)
- }
-}
-
/// An enumeration item.
#[elem(name = "item", title = "Numbered List Item")]
pub struct EnumItem {
diff --git a/crates/typst-library/src/model/figure.rs b/crates/typst-library/src/model/figure.rs
index 7ac659a93..ac3676eea 100644
--- a/crates/typst-library/src/model/figure.rs
+++ b/crates/typst-library/src/model/figure.rs
@@ -9,19 +9,16 @@ use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, select_where, Content, Element, NativeElement, Packed, Selector,
- Show, ShowSet, Smart, StyleChain, Styles, Synthesize, TargetElem,
+ ShowSet, Smart, StyleChain, Styles, Synthesize,
};
-use crate::html::{tag, HtmlElem};
use crate::introspection::{
Count, Counter, CounterKey, CounterUpdate, Locatable, Location,
};
use crate::layout::{
- AlignElem, Alignment, BlockBody, BlockElem, Em, HAlignment, Length, OuterVAlignment,
- PlaceElem, PlacementScope, VAlignment, VElem,
-};
-use crate::model::{
- Numbering, NumberingPattern, Outlinable, ParbreakElem, Refable, Supplement,
+ AlignElem, Alignment, BlockElem, Em, Length, OuterVAlignment, PlacementScope,
+ VAlignment,
};
+use crate::model::{Numbering, NumberingPattern, Outlinable, Refable, Supplement};
use crate::text::{Lang, Region, TextElem};
use crate::visualize::ImageElem;
@@ -104,7 +101,7 @@ use crate::visualize::ImageElem;
/// caption: [I'm up here],
/// )
/// ```
-#[elem(scope, Locatable, Synthesize, Count, Show, ShowSet, Refable, Outlinable)]
+#[elem(scope, Locatable, Synthesize, Count, ShowSet, Refable, Outlinable)]
pub struct FigureElem {
/// The content of the figure. Often, an [image].
#[required]
@@ -328,65 +325,6 @@ impl Synthesize for Packed {
}
}
-impl Show for Packed {
- #[typst_macros::time(name = "figure", span = self.span())]
- fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult {
- let span = self.span();
- let target = styles.get(TargetElem::target);
- let mut realized = self.body.clone();
-
- // Build the caption, if any.
- if let Some(caption) = self.caption.get_cloned(styles) {
- let (first, second) = match caption.position.get(styles) {
- OuterVAlignment::Top => (caption.pack(), realized),
- OuterVAlignment::Bottom => (realized, caption.pack()),
- };
- let mut seq = Vec::with_capacity(3);
- seq.push(first);
- if !target.is_html() {
- let v = VElem::new(self.gap.get(styles).into()).with_weak(true);
- seq.push(v.pack().spanned(span))
- }
- seq.push(second);
- realized = Content::sequence(seq)
- }
-
- // Ensure that the body is considered a paragraph.
- realized += ParbreakElem::shared().clone().spanned(span);
-
- if target.is_html() {
- return Ok(HtmlElem::new(tag::figure)
- .with_body(Some(realized))
- .pack()
- .spanned(span));
- }
-
- // Wrap the contents in a block.
- realized = BlockElem::new()
- .with_body(Some(BlockBody::Content(realized)))
- .pack()
- .spanned(span);
-
- // Wrap in a float.
- if let Some(align) = self.placement.get(styles) {
- realized = PlaceElem::new(realized)
- .with_alignment(align.map(|align| HAlignment::Center + align))
- .with_scope(self.scope.get(styles))
- .with_float(true)
- .pack()
- .spanned(span);
- } else if self.scope.get(styles) == PlacementScope::Parent {
- bail!(
- span,
- "parent-scoped placement is only available for floating figures";
- hint: "you can enable floating placement with `figure(placement: auto, ..)`"
- );
- }
-
- Ok(realized)
- }
-}
-
impl ShowSet for Packed {
fn show_set(&self, _: StyleChain) -> Styles {
// Still allows breakable figures with
@@ -471,7 +409,7 @@ impl Outlinable for Packed {
/// caption: [A rectangle],
/// )
/// ```
-#[elem(name = "caption", Synthesize, Show)]
+#[elem(name = "caption", Synthesize)]
pub struct FigureCaption {
/// The caption's position in the figure. Either `{top}` or `{bottom}`.
///
@@ -559,6 +497,35 @@ pub struct FigureCaption {
}
impl FigureCaption {
+ /// Realizes the textual caption content.
+ pub fn realize(
+ &self,
+ engine: &mut Engine,
+ styles: StyleChain,
+ ) -> SourceResult {
+ let mut realized = self.body.clone();
+
+ if let (
+ Some(Some(mut supplement)),
+ Some(Some(numbering)),
+ Some(Some(counter)),
+ Some(Some(location)),
+ ) = (
+ self.supplement.clone(),
+ &self.numbering,
+ &self.counter,
+ &self.figure_location,
+ ) {
+ let numbers = counter.display_at_loc(engine, *location, styles, numbering)?;
+ if !supplement.is_empty() {
+ supplement += TextElem::packed('\u{a0}');
+ }
+ realized = supplement + numbers + self.get_separator(styles) + realized;
+ }
+
+ Ok(realized)
+ }
+
/// Gets the default separator in the given language and (optionally)
/// region.
fn local_separator(lang: Lang, _: Option) -> &'static str {
@@ -588,43 +555,6 @@ impl Synthesize for Packed {
}
}
-impl Show for Packed {
- #[typst_macros::time(name = "figure.caption", span = self.span())]
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult {
- let mut realized = self.body.clone();
-
- if let (
- Some(Some(mut supplement)),
- Some(Some(numbering)),
- Some(Some(counter)),
- Some(Some(location)),
- ) = (
- self.supplement.clone(),
- &self.numbering,
- &self.counter,
- &self.figure_location,
- ) {
- let numbers = counter.display_at_loc(engine, *location, styles, numbering)?;
- if !supplement.is_empty() {
- supplement += TextElem::packed('\u{a0}');
- }
- realized = supplement + numbers + self.get_separator(styles) + realized;
- }
-
- Ok(if styles.get(TargetElem::target).is_html() {
- HtmlElem::new(tag::figcaption)
- .with_body(Some(realized))
- .pack()
- .spanned(self.span())
- } else {
- BlockElem::new()
- .with_body(Some(BlockBody::Content(realized)))
- .pack()
- .spanned(self.span())
- })
- }
-}
-
cast! {
FigureCaption,
v: Content => v.unpack::().unwrap_or_else(Self::new),
diff --git a/crates/typst-library/src/model/footnote.rs b/crates/typst-library/src/model/footnote.rs
index 63a461bd6..b920a8ae4 100644
--- a/crates/typst-library/src/model/footnote.rs
+++ b/crates/typst-library/src/model/footnote.rs
@@ -3,16 +3,16 @@ use std::str::FromStr;
use typst_utils::NonZeroExt;
-use crate::diag::{bail, At, SourceResult, StrResult};
+use crate::diag::{bail, StrResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, scope, Content, Label, NativeElement, Packed, Show, ShowSet, Smart,
- StyleChain, Styles,
+ cast, elem, scope, Content, Label, NativeElement, Packed, ShowSet, Smart, StyleChain,
+ Styles,
};
-use crate::introspection::{Count, Counter, CounterUpdate, Locatable, Location};
-use crate::layout::{Abs, Em, HElem, Length, Ratio};
-use crate::model::{Destination, Numbering, NumberingPattern, ParElem};
-use crate::text::{SuperElem, TextElem, TextSize};
+use crate::introspection::{Count, CounterUpdate, Locatable, Location};
+use crate::layout::{Abs, Em, Length, Ratio};
+use crate::model::{Numbering, NumberingPattern, ParElem};
+use crate::text::{TextElem, TextSize};
use crate::visualize::{LineElem, Stroke};
/// A footnote.
@@ -51,7 +51,7 @@ use crate::visualize::{LineElem, Stroke};
/// apply to the footnote's content. See [here][issue] for more information.
///
/// [issue]: https://github.com/typst/typst/issues/1467#issuecomment-1588799440
-#[elem(scope, Locatable, Show, Count)]
+#[elem(scope, Locatable, Count)]
pub struct FootnoteElem {
/// How to number footnotes.
///
@@ -135,21 +135,6 @@ impl Packed {
}
}
-impl Show for Packed {
- #[typst_macros::time(name = "footnote", span = self.span())]
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult {
- let span = self.span();
- let loc = self.declaration_location(engine).at(span)?;
- let numbering = self.numbering.get_ref(styles);
- let counter = Counter::of(FootnoteElem::ELEM);
- let num = counter.display_at_loc(engine, loc, styles, numbering)?;
- let sup = SuperElem::new(num).pack().spanned(span);
- let loc = loc.variant(1);
- // Add zero-width weak spacing to make the footnote "sticky".
- Ok(HElem::hole().pack() + sup.linked(Destination::Location(loc)))
- }
-}
-
impl Count for Packed {
fn update(&self) -> Option {
(!self.is_ref()).then(|| CounterUpdate::Step(NonZeroUsize::ONE))
@@ -191,7 +176,7 @@ cast! {
/// page run is a sequence of pages without an explicit pagebreak in between).
/// For this reason, set and show rules for footnote entries should be defined
/// before any page content, typically at the very start of the document.
-#[elem(name = "entry", title = "Footnote Entry", Show, ShowSet)]
+#[elem(name = "entry", title = "Footnote Entry", ShowSet)]
pub struct FootnoteEntry {
/// The footnote for this entry. Its location can be used to determine
/// the footnote counter state.
@@ -274,37 +259,6 @@ pub struct FootnoteEntry {
pub indent: Length,
}
-impl Show for Packed {
- #[typst_macros::time(name = "footnote.entry", span = self.span())]
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult {
- let span = self.span();
- let number_gap = Em::new(0.05);
- let default = StyleChain::default();
- let numbering = self.note.numbering.get_ref(default);
- let counter = Counter::of(FootnoteElem::ELEM);
- let Some(loc) = self.note.location() else {
- bail!(
- span, "footnote entry must have a location";
- hint: "try using a query or a show rule to customize the footnote instead"
- );
- };
-
- let num = counter.display_at_loc(engine, loc, styles, numbering)?;
- let sup = SuperElem::new(num)
- .pack()
- .spanned(span)
- .linked(Destination::Location(loc))
- .located(loc.variant(1));
-
- Ok(Content::sequence([
- HElem::new(self.indent.get(styles).into()).pack(),
- sup,
- HElem::new(number_gap.into()).with_weak(true).pack(),
- self.note.body_content().unwrap().clone(),
- ]))
- }
-}
-
impl ShowSet for Packed {
fn show_set(&self, _: StyleChain) -> Styles {
let mut out = Styles::new();
diff --git a/crates/typst-library/src/model/heading.rs b/crates/typst-library/src/model/heading.rs
index d6f6d01f9..0f2a1d338 100644
--- a/crates/typst-library/src/model/heading.rs
+++ b/crates/typst-library/src/model/heading.rs
@@ -1,21 +1,16 @@
use std::num::NonZeroUsize;
-use ecow::eco_format;
-use typst_utils::{Get, NonZeroExt};
+use typst_utils::NonZeroExt;
-use crate::diag::{warning, SourceResult};
+use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{
- elem, Content, NativeElement, Packed, Resolve, Show, ShowSet, Smart, StyleChain,
- Styles, Synthesize, TargetElem,
+ elem, Content, NativeElement, Packed, ShowSet, Smart, StyleChain, Styles, Synthesize,
};
-use crate::html::{attr, tag, HtmlElem};
-use crate::introspection::{
- Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink,
-};
-use crate::layout::{Abs, Axes, BlockBody, BlockElem, Em, HElem, Length, Region, Sides};
+use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
+use crate::layout::{BlockElem, Em, Length};
use crate::model::{Numbering, Outlinable, Refable, Supplement};
-use crate::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize};
+use crate::text::{FontWeight, LocalName, TextElem, TextSize};
/// A section heading.
///
@@ -49,7 +44,7 @@ use crate::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize};
/// one or multiple equals signs, followed by a space. The number of equals
/// signs determines the heading's logical nesting depth. The `{offset}` field
/// can be set to configure the starting depth.
-#[elem(Locatable, Synthesize, Count, Show, ShowSet, LocalName, Refable, Outlinable)]
+#[elem(Locatable, Synthesize, Count, ShowSet, LocalName, Refable, Outlinable)]
pub struct HeadingElem {
/// The absolute nesting depth of the heading, starting from one. If set
/// to `{auto}`, it is computed from `{offset + depth}`.
@@ -215,96 +210,6 @@ impl Synthesize for Packed {
}
}
-impl Show for Packed {
- #[typst_macros::time(name = "heading", span = self.span())]
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult {
- let html = styles.get(TargetElem::target).is_html();
-
- const SPACING_TO_NUMBERING: Em = Em::new(0.3);
-
- let span = self.span();
- let mut realized = self.body.clone();
-
- let hanging_indent = self.hanging_indent.get(styles);
- let mut indent = match hanging_indent {
- Smart::Custom(length) => length.resolve(styles),
- Smart::Auto => Abs::zero(),
- };
-
- if let Some(numbering) = self.numbering.get_ref(styles).as_ref() {
- let location = self.location().unwrap();
- let numbering = Counter::of(HeadingElem::ELEM)
- .display_at_loc(engine, location, styles, numbering)?
- .spanned(span);
-
- if hanging_indent.is_auto() && !html {
- let pod = Region::new(Axes::splat(Abs::inf()), Axes::splat(false));
-
- // We don't have a locator for the numbering here, so we just
- // use the measurement infrastructure for now.
- let link = LocatorLink::measure(location);
- let size = (engine.routines.layout_frame)(
- engine,
- &numbering,
- Locator::link(&link),
- styles,
- pod,
- )?
- .size();
-
- indent = size.x + SPACING_TO_NUMBERING.resolve(styles);
- }
-
- let spacing = if html {
- SpaceElem::shared().clone()
- } else {
- HElem::new(SPACING_TO_NUMBERING.into()).with_weak(true).pack()
- };
-
- realized = numbering + spacing + realized;
- }
-
- Ok(if html {
- // HTML's h1 is closer to a title element. There should only be one.
- // Meanwhile, a level 1 Typst heading is a section heading. For this
- // reason, levels are offset by one: A Typst level 1 heading becomes
- // a ``.
- let level = self.resolve_level(styles).get();
- if level >= 6 {
- engine.sink.warn(warning!(span,
- "heading of level {} was transformed to \
- , which is not \
- supported by all assistive technology",
- level, level + 1;
- hint: "HTML only supports
to , not ", level + 1;
- hint: "you may want to restructure your document so that \
- it doesn't contain deep headings"));
- HtmlElem::new(tag::div)
- .with_body(Some(realized))
- .with_attr(attr::role, "heading")
- .with_attr(attr::aria_level, eco_format!("{}", level + 1))
- .pack()
- .spanned(span)
- } else {
- let t = [tag::h2, tag::h3, tag::h4, tag::h5, tag::h6][level - 1];
- HtmlElem::new(t).with_body(Some(realized)).pack().spanned(span)
- }
- } else {
- let block = if indent != Abs::zero() {
- let body = HElem::new((-indent).into()).pack() + realized;
- let inset = Sides::default()
- .with(styles.resolve(TextElem::dir).start(), Some(indent.into()));
- BlockElem::new()
- .with_body(Some(BlockBody::Content(body)))
- .with_inset(inset)
- } else {
- BlockElem::new().with_body(Some(BlockBody::Content(realized)))
- };
- block.pack().spanned(span)
- })
- }
-}
-
impl ShowSet for Packed {
fn show_set(&self, styles: StyleChain) -> Styles {
let level = self.resolve_level(styles).get();
diff --git a/crates/typst-library/src/model/link.rs b/crates/typst-library/src/model/link.rs
index 1e2c708e8..c630835e0 100644
--- a/crates/typst-library/src/model/link.rs
+++ b/crates/typst-library/src/model/link.rs
@@ -2,13 +2,10 @@ use std::ops::Deref;
use ecow::{eco_format, EcoString};
-use crate::diag::{bail, warning, At, SourceResult, StrResult};
-use crate::engine::Engine;
+use crate::diag::{bail, StrResult};
use crate::foundations::{
- cast, elem, Content, Label, NativeElement, Packed, Repr, Show, ShowSet, Smart,
- StyleChain, Styles, TargetElem,
+ cast, elem, Content, Label, Packed, Repr, ShowSet, Smart, StyleChain, Styles,
};
-use crate::html::{attr, tag, HtmlElem};
use crate::introspection::Location;
use crate::layout::Position;
use crate::text::TextElem;
@@ -38,7 +35,7 @@ use crate::text::TextElem;
/// # Syntax
/// This function also has dedicated syntax: Text that starts with `http://` or
/// `https://` is automatically turned into a link.
-#[elem(Show)]
+#[elem]
pub struct LinkElem {
/// The destination the link points to.
///
@@ -103,38 +100,6 @@ impl LinkElem {
}
}
-impl Show for Packed {
- #[typst_macros::time(name = "link", span = self.span())]
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult {
- let body = self.body.clone();
-
- Ok(if styles.get(TargetElem::target).is_html() {
- if let LinkTarget::Dest(Destination::Url(url)) = &self.dest {
- HtmlElem::new(tag::a)
- .with_attr(attr::href, url.clone().into_inner())
- .with_body(Some(body))
- .pack()
- .spanned(self.span())
- } else {
- engine.sink.warn(warning!(
- self.span(),
- "non-URL links are not yet supported by HTML export"
- ));
- body
- }
- } else {
- match &self.dest {
- LinkTarget::Dest(dest) => body.linked(dest.clone()),
- LinkTarget::Label(label) => {
- let elem = engine.introspector.query_label(*label).at(self.span())?;
- let dest = Destination::Location(elem.location().unwrap());
- body.clone().linked(dest)
- }
- }
- })
- }
-}
-
impl ShowSet for Packed {
fn show_set(&self, _: StyleChain) -> Styles {
let mut out = Styles::new();
diff --git a/crates/typst-library/src/model/list.rs b/crates/typst-library/src/model/list.rs
index 5e6db1faa..660716de7 100644
--- a/crates/typst-library/src/model/list.rs
+++ b/crates/typst-library/src/model/list.rs
@@ -3,12 +3,10 @@ use comemo::Track;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, scope, Array, Content, Context, Depth, Func, NativeElement, Packed, Show,
- Smart, StyleChain, Styles, TargetElem, Value,
+ cast, elem, scope, Array, Content, Context, Depth, Func, NativeElement, Packed,
+ Smart, StyleChain, Styles, Value,
};
-use crate::html::{tag, HtmlElem};
-use crate::layout::{BlockElem, Em, Length, VElem};
-use crate::model::{ParElem, ParbreakElem};
+use crate::layout::{Em, Length};
use crate::text::TextElem;
/// A bullet list.
@@ -42,7 +40,7 @@ use crate::text::TextElem;
/// followed by a space to create a list item. A list item can contain multiple
/// paragraphs and other block-level content. All content that is indented
/// more than an item's marker becomes part of that item.
-#[elem(scope, title = "Bullet List", Show)]
+#[elem(scope, title = "Bullet List")]
pub struct ListElem {
/// Defines the default [spacing]($list.spacing) of the list. If it is
/// `{false}`, the items are spaced apart with
@@ -136,45 +134,6 @@ impl ListElem {
type ListItem;
}
-impl Show for Packed {
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult {
- let tight = self.tight.get(styles);
-
- if styles.get(TargetElem::target).is_html() {
- return Ok(HtmlElem::new(tag::ul)
- .with_body(Some(Content::sequence(self.children.iter().map(|item| {
- // Text in wide lists shall always turn into paragraphs.
- let mut body = item.body.clone();
- if !tight {
- body += ParbreakElem::shared();
- }
- HtmlElem::new(tag::li)
- .with_body(Some(body))
- .pack()
- .spanned(item.span())
- }))))
- .pack()
- .spanned(self.span()));
- }
-
- let mut realized =
- BlockElem::multi_layouter(self.clone(), engine.routines.layout_list)
- .pack()
- .spanned(self.span());
-
- if tight {
- let spacing = self
- .spacing
- .get(styles)
- .unwrap_or_else(|| styles.get(ParElem::leading));
- let v = VElem::new(spacing.into()).with_weak(true).with_attach(true).pack();
- realized = v + realized;
- }
-
- Ok(realized)
- }
-}
-
/// A bullet list item.
#[elem(name = "item", title = "Bullet List Item")]
pub struct ListItem {
diff --git a/crates/typst-library/src/model/mod.rs b/crates/typst-library/src/model/mod.rs
index 9bdbf0013..a0f7e11af 100644
--- a/crates/typst-library/src/model/mod.rs
+++ b/crates/typst-library/src/model/mod.rs
@@ -46,23 +46,23 @@ use crate::foundations::Scope;
pub fn define(global: &mut Scope) {
global.start_category(crate::Category::Model);
global.define_elem::();
- global.define_elem::();
+ global.define_elem::();
+ global.define_elem::();
+ global.define_elem::();
+ global.define_elem::();
+ global.define_elem::();
+ global.define_elem::();
+ global.define_elem::();
global.define_elem::();
- global.define_elem::();
global.define_elem::();
global.define_elem::();
- global.define_elem::();
global.define_elem::();
+ global.define_elem::();
+ global.define_elem::();
+ global.define_elem::();
global.define_elem::();
global.define_elem::();
- global.define_elem::();
- global.define_elem::();
- global.define_elem::();
- global.define_elem::();
global.define_elem::();
- global.define_elem::();
- global.define_elem::();
- global.define_elem::();
global.define_func::();
global.reset_category();
}
diff --git a/crates/typst-library/src/model/outline.rs b/crates/typst-library/src/model/outline.rs
index bb061fb7b..4bda02ba3 100644
--- a/crates/typst-library/src/model/outline.rs
+++ b/crates/typst-library/src/model/outline.rs
@@ -1,7 +1,7 @@
use std::num::NonZeroUsize;
use std::str::FromStr;
-use comemo::{Track, Tracked};
+use comemo::Tracked;
use smallvec::SmallVec;
use typst_syntax::Span;
use typst_utils::{Get, NonZeroExt};
@@ -10,7 +10,7 @@ use crate::diag::{bail, error, At, HintedStrResult, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, func, scope, select_where, Args, Construct, Content, Context, Func,
- LocatableSelector, NativeElement, Packed, Resolve, Show, ShowSet, Smart, StyleChain,
+ LocatableSelector, NativeElement, Packed, Resolve, ShowSet, Smart, StyleChain,
Styles,
};
use crate::introspection::{
@@ -20,8 +20,7 @@ use crate::layout::{
Abs, Axes, BlockBody, BlockElem, BoxElem, Dir, Em, Fr, HElem, Length, Region, Rel,
RepeatElem, Sides,
};
-use crate::math::EquationElem;
-use crate::model::{Destination, HeadingElem, NumberingPattern, ParElem, Refable};
+use crate::model::{HeadingElem, NumberingPattern, ParElem, Refable};
use crate::text::{LocalName, SpaceElem, TextElem};
/// A table of contents, figures, or other elements.
@@ -147,7 +146,7 @@ use crate::text::{LocalName, SpaceElem, TextElem};
///
/// [^1]: The outline of equations is the exception to this rule as it does not
/// have a body and thus does not use indented layout.
-#[elem(scope, keywords = ["Table of Contents", "toc"], Show, ShowSet, LocalName, Locatable)]
+#[elem(scope, keywords = ["Table of Contents", "toc"], ShowSet, LocalName, Locatable)]
pub struct OutlineElem {
/// The title of the outline.
///
@@ -249,44 +248,6 @@ impl OutlineElem {
type OutlineEntry;
}
-impl Show for Packed {
- #[typst_macros::time(name = "outline", span = self.span())]
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult {
- let span = self.span();
-
- // Build the outline title.
- let mut seq = vec![];
- if let Some(title) = self.title.get_cloned(styles).unwrap_or_else(|| {
- Some(TextElem::packed(Self::local_name_in(styles)).spanned(span))
- }) {
- seq.push(
- HeadingElem::new(title)
- .with_depth(NonZeroUsize::ONE)
- .pack()
- .spanned(span),
- );
- }
-
- let elems = engine.introspector.query(&self.target.get_ref(styles).0);
- let depth = self.depth.get(styles).unwrap_or(NonZeroUsize::MAX);
-
- // Build the outline entries.
- for elem in elems {
- let Some(outlinable) = elem.with::() else {
- bail!(span, "cannot outline {}", elem.func().name());
- };
-
- let level = outlinable.level();
- if outlinable.outlined() && level <= depth {
- let entry = OutlineEntry::new(level, elem);
- seq.push(entry.pack().spanned(span));
- }
- }
-
- Ok(Content::sequence(seq))
- }
-}
-
impl ShowSet for Packed {
fn show_set(&self, styles: StyleChain) -> Styles {
let mut out = Styles::new();
@@ -363,7 +324,7 @@ pub trait Outlinable: Refable {
/// With show-set and show rules on outline entries, you can richly customize
/// the outline's appearance. See the
/// [section on styling the outline]($outline/#styling-the-outline) for details.
-#[elem(scope, name = "entry", title = "Outline Entry", Show)]
+#[elem(scope, name = "entry", title = "Outline Entry")]
pub struct OutlineEntry {
/// The nesting level of this outline entry. Starts at `{1}` for top-level
/// entries.
@@ -408,30 +369,6 @@ pub struct OutlineEntry {
pub parent: Option>,
}
-impl Show for Packed {
- #[typst_macros::time(name = "outline.entry", span = self.span())]
- fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult {
- let span = self.span();
- let context = Context::new(None, Some(styles));
- let context = context.track();
-
- let prefix = self.prefix(engine, context, span)?;
- let inner = self.inner(engine, context, span)?;
- let block = if self.element.is::