From e5e1dcd9c01341d2cd3473ac94a70223d5966086 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 9 Jul 2025 10:16:36 +0200 Subject: [PATCH] Target-specific native show rules (#6569) --- crates/typst-html/src/lib.rs | 2 + crates/typst-html/src/rules.rs | 411 ++++++++ crates/typst-layout/src/lib.rs | 16 +- crates/typst-layout/src/rules.rs | 890 ++++++++++++++++++ .../src/foundations/content/element.rs | 6 - .../typst-library/src/foundations/context.rs | 17 +- .../typst-library/src/foundations/styles.rs | 129 ++- .../typst-library/src/foundations/target.rs | 2 +- .../src/introspection/counter.rs | 40 +- .../src/introspection/metadata.rs | 12 +- .../typst-library/src/introspection/state.rs | 13 +- crates/typst-library/src/layout/align.rs | 16 +- crates/typst-library/src/layout/columns.rs | 16 +- crates/typst-library/src/layout/grid/mod.rs | 44 +- crates/typst-library/src/layout/hide.rs | 13 +- crates/typst-library/src/layout/layout.rs | 42 +- crates/typst-library/src/layout/pad.rs | 16 +- crates/typst-library/src/layout/repeat.rs | 16 +- crates/typst-library/src/layout/stack.rs | 16 +- crates/typst-library/src/layout/transform.rs | 50 +- crates/typst-library/src/math/equation.rs | 25 +- .../typst-library/src/model/bibliography.rs | 92 +- crates/typst-library/src/model/cite.rs | 10 +- crates/typst-library/src/model/emph.rs | 25 +- crates/typst-library/src/model/enum.rs | 63 +- crates/typst-library/src/model/figure.rs | 140 +-- crates/typst-library/src/model/footnote.rs | 64 +- crates/typst-library/src/model/heading.rs | 109 +-- crates/typst-library/src/model/link.rs | 41 +- crates/typst-library/src/model/list.rs | 49 +- crates/typst-library/src/model/mod.rs | 20 +- crates/typst-library/src/model/outline.rs | 86 +- crates/typst-library/src/model/quote.rs | 155 +-- crates/typst-library/src/model/reference.rs | 20 +- crates/typst-library/src/model/strong.rs | 25 +- crates/typst-library/src/model/table.rs | 133 +-- crates/typst-library/src/model/terms.rs | 104 +- crates/typst-library/src/pdf/embed.rs | 21 +- crates/typst-library/src/routines.rs | 237 +---- crates/typst-library/src/text/deco.rs | 123 +-- crates/typst-library/src/text/raw.rs | 59 +- crates/typst-library/src/text/shift.rs | 83 +- crates/typst-library/src/text/smallcaps.rs | 16 +- crates/typst-library/src/text/smartquote.rs | 13 +- crates/typst-library/src/visualize/curve.rs | 19 +- .../typst-library/src/visualize/image/mod.rs | 20 +- crates/typst-library/src/visualize/line.rs | 16 +- crates/typst-library/src/visualize/path.rs | 20 +- crates/typst-library/src/visualize/polygon.rs | 18 +- crates/typst-library/src/visualize/shape.rs | 56 +- crates/typst-realize/src/lib.rs | 75 +- crates/typst/src/lib.rs | 37 +- 52 files changed, 1778 insertions(+), 1963 deletions(-) create mode 100644 crates/typst-html/src/rules.rs create mode 100644 crates/typst-layout/src/rules.rs diff --git a/crates/typst-html/src/lib.rs b/crates/typst-html/src/lib.rs index 60ffa78ee..19eb14464 100644 --- a/crates/typst-html/src/lib.rs +++ b/crates/typst-html/src/lib.rs @@ -1,8 +1,10 @@ //! Typst's HTML exporter. mod encode; +mod rules; pub use self::encode::html; +pub use self::rules::register; use comemo::{Track, Tracked, TrackedMut}; use typst_library::diag::{bail, warning, At, SourceResult}; diff --git a/crates/typst-html/src/rules.rs b/crates/typst-html/src/rules.rs new file mode 100644 index 000000000..f361bfbb3 --- /dev/null +++ b/crates/typst-html/src/rules.rs @@ -0,0 +1,411 @@ +use std::num::NonZeroUsize; + +use ecow::{eco_format, EcoVec}; +use typst_library::diag::warning; +use typst_library::foundations::{ + Content, NativeElement, NativeRuleMap, ShowFn, StyleChain, Target, +}; +use typst_library::html::{attr, tag, HtmlAttrs, HtmlElem, HtmlTag}; +use typst_library::introspection::{Counter, Locator}; +use typst_library::layout::resolve::{table_to_cellgrid, Cell, CellGrid, Entry}; +use typst_library::layout::OuterVAlignment; +use typst_library::model::{ + Attribution, CiteElem, CiteGroup, Destination, EmphElem, EnumElem, FigureCaption, + FigureElem, HeadingElem, LinkElem, LinkTarget, ListElem, ParbreakElem, QuoteElem, + RefElem, StrongElem, TableCell, TableElem, TermsElem, +}; +use typst_library::text::{ + HighlightElem, LinebreakElem, OverlineElem, RawElem, RawLine, SpaceElem, StrikeElem, + SubElem, SuperElem, UnderlineElem, +}; + +/// Register show rules for the [HTML target](Target::Html). +pub fn register(rules: &mut NativeRuleMap) { + use Target::Html; + + // Model. + rules.register(Html, STRONG_RULE); + rules.register(Html, EMPH_RULE); + rules.register(Html, LIST_RULE); + rules.register(Html, ENUM_RULE); + rules.register(Html, TERMS_RULE); + rules.register(Html, LINK_RULE); + rules.register(Html, HEADING_RULE); + rules.register(Html, FIGURE_RULE); + rules.register(Html, FIGURE_CAPTION_RULE); + rules.register(Html, QUOTE_RULE); + rules.register(Html, REF_RULE); + rules.register(Html, CITE_GROUP_RULE); + rules.register(Html, TABLE_RULE); + + // Text. + rules.register(Html, SUB_RULE); + rules.register(Html, SUPER_RULE); + rules.register(Html, UNDERLINE_RULE); + rules.register(Html, OVERLINE_RULE); + rules.register(Html, STRIKE_RULE); + rules.register(Html, HIGHLIGHT_RULE); + rules.register(Html, RAW_RULE); + rules.register(Html, RAW_LINE_RULE); +} + +const STRONG_RULE: ShowFn = |elem, _, _| { + Ok(HtmlElem::new(tag::strong) + .with_body(Some(elem.body.clone())) + .pack() + .spanned(elem.span())) +}; + +const EMPH_RULE: ShowFn = |elem, _, _| { + Ok(HtmlElem::new(tag::em) + .with_body(Some(elem.body.clone())) + .pack() + .spanned(elem.span())) +}; + +const LIST_RULE: ShowFn = |elem, _, styles| { + Ok(HtmlElem::new(tag::ul) + .with_body(Some(Content::sequence(elem.children.iter().map(|item| { + // Text in wide lists shall always turn into paragraphs. + let mut body = item.body.clone(); + if !elem.tight.get(styles) { + body += ParbreakElem::shared(); + } + HtmlElem::new(tag::li) + .with_body(Some(body)) + .pack() + .spanned(item.span()) + })))) + .pack() + .spanned(elem.span())) +}; + +const ENUM_RULE: ShowFn = |elem, _, styles| { + let mut ol = HtmlElem::new(tag::ol); + + if elem.reversed.get(styles) { + ol = ol.with_attr(attr::reversed, "reversed"); + } + + if let Some(n) = elem.start.get(styles).custom() { + ol = ol.with_attr(attr::start, eco_format!("{n}")); + } + + let body = Content::sequence(elem.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 !elem.tight.get(styles) { + body += ParbreakElem::shared(); + } + li.with_body(Some(body)).pack().spanned(item.span()) + })); + + Ok(ol.with_body(Some(body)).pack().spanned(elem.span())) +}; + +const TERMS_RULE: ShowFn = |elem, _, styles| { + Ok(HtmlElem::new(tag::dl) + .with_body(Some(Content::sequence(elem.children.iter().flat_map(|item| { + // Text in wide term lists shall always turn into paragraphs. + let mut description = item.description.clone(); + if !elem.tight.get(styles) { + description += ParbreakElem::shared(); + } + + [ + HtmlElem::new(tag::dt) + .with_body(Some(item.term.clone())) + .pack() + .spanned(item.term.span()), + HtmlElem::new(tag::dd) + .with_body(Some(description)) + .pack() + .spanned(item.description.span()), + ] + })))) + .pack()) +}; + +const LINK_RULE: ShowFn = |elem, engine, _| { + let body = elem.body.clone(); + Ok(if let LinkTarget::Dest(Destination::Url(url)) = &elem.dest { + HtmlElem::new(tag::a) + .with_attr(attr::href, url.clone().into_inner()) + .with_body(Some(body)) + .pack() + .spanned(elem.span()) + } else { + engine.sink.warn(warning!( + elem.span(), + "non-URL links are not yet supported by HTML export" + )); + body + }) +}; + +const HEADING_RULE: ShowFn = |elem, engine, styles| { + let span = elem.span(); + + let mut realized = elem.body.clone(); + 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); + realized = numbering + SpaceElem::shared().clone() + realized; + } + + // 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 = elem.resolve_level(styles).get(); + Ok(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) + }) +}; + +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::() { - let body = prefix.unwrap_or_default() + inner; - BlockElem::new() - .with_body(Some(BlockBody::Content(body))) - .pack() - .spanned(span) - } else { - self.indented(engine, context, span, prefix, inner, Em::new(0.5).into())? - }; - - let loc = self.element_location().at(span)?; - Ok(block.linked(Destination::Location(loc))) - } -} - #[scope] impl OutlineEntry { /// A helper function for producing an indented entry layout: Lays out a @@ -654,7 +591,8 @@ impl OutlineEntry { .ok_or_else(|| error!("cannot outline {}", self.element.func().name())) } - fn element_location(&self) -> HintedStrResult { + /// Returns the location of the outlined element. + pub fn element_location(&self) -> HintedStrResult { let elem = &self.element; elem.location().ok_or_else(|| { if elem.can::() && elem.can::() { @@ -730,8 +668,8 @@ fn query_prefix_widths( } /// Helper type for introspection-based prefix alignment. -#[elem(Construct, Locatable, Show)] -struct PrefixInfo { +#[elem(Construct, Locatable)] +pub(crate) struct PrefixInfo { /// The location of the outline this prefix is part of. This is used to /// scope prefix computations to a specific outline. #[required] @@ -753,9 +691,3 @@ impl Construct for PrefixInfo { 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/model/quote.rs b/crates/typst-library/src/model/quote.rs index a8cf3eaef..9960b7587 100644 --- a/crates/typst-library/src/model/quote.rs +++ b/crates/typst-library/src/model/quote.rs @@ -1,16 +1,13 @@ -use crate::diag::SourceResult; -use crate::engine::Engine; +use typst_syntax::Span; + use crate::foundations::{ - cast, elem, Content, Depth, Label, NativeElement, Packed, Show, ShowSet, Smart, - StyleChain, Styles, TargetElem, + cast, elem, Content, Depth, Label, NativeElement, Packed, ShowSet, Smart, StyleChain, + Styles, }; -use crate::html::{attr, tag, HtmlElem}; use crate::introspection::Locatable; -use crate::layout::{ - Alignment, BlockBody, BlockElem, Em, HElem, PadElem, Spacing, VElem, -}; -use crate::model::{CitationForm, CiteElem, Destination, LinkElem, LinkTarget}; -use crate::text::{SmartQuoteElem, SmartQuotes, SpaceElem, TextElem}; +use crate::layout::{BlockElem, Em, PadElem}; +use crate::model::{CitationForm, CiteElem}; +use crate::text::{SmartQuotes, SpaceElem, TextElem}; /// Displays a quote alongside an optional attribution. /// @@ -44,7 +41,7 @@ use crate::text::{SmartQuoteElem, SmartQuotes, SpaceElem, TextElem}; /// flame of Udûn. Go back to the Shadow! You cannot pass. /// ] /// ``` -#[elem(Locatable, ShowSet, Show)] +#[elem(Locatable, ShowSet)] pub struct QuoteElem { /// Whether this is a block quote. /// @@ -62,7 +59,7 @@ pub struct QuoteElem { /// Ich bin ein Berliner. /// ] /// ``` - block: bool, + pub block: bool, /// Whether double quotes should be added around this quote. /// @@ -88,7 +85,7 @@ pub struct QuoteElem { /// translate the quote: /// #quote[I am a Berliner.] /// ``` - quotes: Smart, + pub quotes: Smart, /// The attribution of this quote, usually the author or source. Can be a /// label pointing to a bibliography entry or any content. By default only @@ -123,17 +120,36 @@ pub struct QuoteElem { /// /// #bibliography("works.bib", style: "apa") /// ``` - attribution: Option, + pub attribution: Option, /// The quote. #[required] - body: Content, + pub body: Content, /// The nesting depth. #[internal] #[fold] #[ghost] - depth: Depth, + pub depth: Depth, +} + +impl QuoteElem { + /// Quotes the body content with the appropriate quotes based on the current + /// styles and surroundings. + pub fn quoted(body: Content, styles: StyleChain<'_>) -> Content { + let quotes = SmartQuotes::get_in(styles); + + // Alternate between single and double quotes. + let Depth(depth) = styles.get(QuoteElem::depth); + let double = depth % 2 == 0; + + Content::sequence([ + TextElem::packed(quotes.open(double)), + body, + TextElem::packed(quotes.close(double)), + ]) + .set(QuoteElem::depth, Depth(1)) + } } /// Attribution for a [quote](QuoteElem). @@ -143,6 +159,23 @@ pub enum Attribution { Label(Label), } +impl Attribution { + /// Realize as an em dash followed by text or a citation. + pub fn realize(&self, span: Span) -> Content { + Content::sequence([ + TextElem::packed('—'), + SpaceElem::shared().clone(), + match self { + Attribution::Content(content) => content.clone(), + Attribution::Label(label) => CiteElem::new(*label) + .with_form(Some(CitationForm::Prose)) + .pack() + .spanned(span), + }, + ]) + } +} + cast! { Attribution, self => match self { @@ -153,96 +186,6 @@ cast! { label: Label => Self::Label(label), } -impl Show for Packed { - #[typst_macros::time(name = "quote", span = self.span())] - fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - let mut realized = self.body.clone(); - let block = self.block.get(styles); - let html = styles.get(TargetElem::target).is_html(); - - if self.quotes.get(styles).unwrap_or(!block) { - let quotes = SmartQuotes::get( - styles.get_ref(SmartQuoteElem::quotes), - styles.get(TextElem::lang), - styles.get(TextElem::region), - styles.get(SmartQuoteElem::alternative), - ); - - // Alternate between single and double quotes. - let Depth(depth) = styles.get(QuoteElem::depth); - let double = depth % 2 == 0; - - if !html { - // Add zero-width weak spacing to make the quotes "sticky". - let hole = HElem::hole().pack(); - realized = Content::sequence([hole.clone(), realized, hole]); - } - realized = Content::sequence([ - TextElem::packed(quotes.open(double)), - realized, - TextElem::packed(quotes.close(double)), - ]) - .set(QuoteElem::depth, Depth(1)); - } - - let attribution = self.attribution.get_ref(styles); - - if block { - realized = if html { - let mut elem = 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 { - elem = elem.with_attr(attr::cite, url.clone().into_inner()); - } - } - } - elem.pack() - } else { - BlockElem::new().with_body(Some(BlockBody::Content(realized))).pack() - } - .spanned(self.span()); - - if let Some(attribution) = attribution { - let attribution = match attribution { - Attribution::Content(content) => content.clone(), - Attribution::Label(label) => CiteElem::new(*label) - .with_form(Some(CitationForm::Prose)) - .pack() - .spanned(self.span()), - }; - let attribution = Content::sequence([ - TextElem::packed('—'), - SpaceElem::shared().clone(), - attribution, - ]); - - if html { - realized += attribution; - } else { - // 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))) - .pack() - .aligned(Alignment::END); - } - } - - if !html { - realized = PadElem::new(realized).pack(); - } - } else if let Some(Attribution::Label(label)) = attribution { - realized += SpaceElem::shared().clone() - + CiteElem::new(*label).pack().spanned(self.span()); - } - - Ok(realized) - } -} - impl ShowSet for Packed { fn show_set(&self, styles: StyleChain) -> Styles { let mut out = Styles::new(); diff --git a/crates/typst-library/src/model/reference.rs b/crates/typst-library/src/model/reference.rs index 2d04a97a4..4877409fa 100644 --- a/crates/typst-library/src/model/reference.rs +++ b/crates/typst-library/src/model/reference.rs @@ -5,7 +5,7 @@ use crate::diag::{bail, At, Hint, SourceResult}; use crate::engine::Engine; use crate::foundations::{ cast, elem, Cast, Content, Context, Func, IntoValue, Label, NativeElement, Packed, - Repr, Show, Smart, StyleChain, Synthesize, + Repr, Smart, StyleChain, Synthesize, }; use crate::introspection::{Counter, CounterKey, Locatable}; use crate::math::EquationElem; @@ -134,7 +134,7 @@ use crate::text::TextElem; /// In @beginning we prove @pythagoras. /// $ a^2 + b^2 = c^2 $ /// ``` -#[elem(title = "Reference", Synthesize, Locatable, Show)] +#[elem(title = "Reference", Synthesize, Locatable)] pub struct RefElem { /// The target label that should be referenced. /// @@ -220,9 +220,13 @@ impl Synthesize for Packed { } } -impl Show for Packed { - #[typst_macros::time(name = "ref", span = self.span())] - fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { +impl Packed { + /// Realize as a linked, textual reference. + pub fn realize( + &self, + engine: &mut Engine, + styles: StyleChain, + ) -> SourceResult { let elem = engine.introspector.query_label(self.target); let span = self.span(); @@ -242,7 +246,7 @@ impl Show for Packed { .at(span)?; let supplement = engine.introspector.page_supplement(loc); - return show_reference( + return realize_reference( self, engine, styles, @@ -306,7 +310,7 @@ impl Show for Packed { )) .at(span)?; - show_reference( + realize_reference( self, engine, styles, @@ -319,7 +323,7 @@ impl Show for Packed { } /// Show a reference. -fn show_reference( +fn realize_reference( reference: &Packed, engine: &mut Engine, styles: StyleChain, diff --git a/crates/typst-library/src/model/strong.rs b/crates/typst-library/src/model/strong.rs index 08cf48391..7751c95bc 100644 --- a/crates/typst-library/src/model/strong.rs +++ b/crates/typst-library/src/model/strong.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::{TextElem, WeightDelta}; +use crate::foundations::{elem, Content}; /// Strongly emphasizes content by increasing the font weight. /// @@ -24,7 +18,7 @@ use crate::text::{TextElem, WeightDelta}; /// simply enclose it in stars/asterisks (`*`). Note that this only works at /// word boundaries. To strongly emphasize part of a word, you have to use the /// function. -#[elem(title = "Strong Emphasis", keywords = ["bold", "weight"], Show)] +#[elem(title = "Strong Emphasis", keywords = ["bold", "weight"])] pub struct StrongElem { /// The delta to apply on the font weight. /// @@ -39,18 +33,3 @@ pub struct StrongElem { #[required] pub body: Content, } - -impl Show for Packed { - #[typst_macros::time(name = "strong", 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::strong) - .with_body(Some(body)) - .pack() - .spanned(self.span()) - } else { - body.set(TextElem::delta, WeightDelta(self.delta.get(styles))) - }) - } -} diff --git a/crates/typst-library/src/model/table.rs b/crates/typst-library/src/model/table.rs index 72c5acc5d..e46efc818 100644 --- a/crates/typst-library/src/model/table.rs +++ b/crates/typst-library/src/model/table.rs @@ -3,19 +3,11 @@ use std::sync::Arc; use typst_utils::NonZeroExt; -use crate::diag::{bail, HintedStrResult, HintedString, SourceResult}; -use crate::engine::Engine; -use crate::foundations::{ - cast, elem, scope, Content, NativeElement, Packed, Show, Smart, StyleChain, - TargetElem, -}; -use crate::html::{attr, tag, HtmlAttrs, HtmlElem, HtmlTag}; -use crate::introspection::Locator; -use crate::layout::grid::resolve::{table_to_cellgrid, Cell, CellGrid, Entry}; +use crate::diag::{bail, HintedStrResult, HintedString}; +use crate::foundations::{cast, elem, scope, Content, Packed, Smart}; use crate::layout::{ - show_grid_cell, Abs, Alignment, BlockElem, Celled, GridCell, GridFooter, GridHLine, - GridHeader, GridVLine, Length, OuterHAlignment, OuterVAlignment, Rel, Sides, - TrackSizings, + Abs, Alignment, Celled, GridCell, GridFooter, GridHLine, GridHeader, GridVLine, + Length, OuterHAlignment, OuterVAlignment, Rel, Sides, TrackSizings, }; use crate::model::Figurable; use crate::text::LocalName; @@ -121,7 +113,7 @@ use crate::visualize::{Paint, Stroke}; /// [Robert], b, a, b, /// ) /// ``` -#[elem(scope, Show, LocalName, Figurable)] +#[elem(scope, LocalName, Figurable)] pub struct TableElem { /// The column sizes. See the [grid documentation]($grid) for more /// information on track sizing. @@ -255,113 +247,6 @@ impl TableElem { type TableFooter; } -fn show_cell_html(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()) -} - -fn show_cellgrid_html(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_html(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)) -} - -impl Show for Packed { - fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { - Ok(if styles.get(TargetElem::target).is_html() { - // TODO: This is a hack, it is not clear whether the locator is actually used by HTML. - // How can we find out whether locator is actually used? - let locator = Locator::root(); - show_cellgrid_html(table_to_cellgrid(self, engine, locator, styles)?, styles) - } else { - BlockElem::multi_layouter(self.clone(), engine.routines.layout_table).pack() - } - .spanned(self.span())) - } -} - impl LocalName for Packed { const KEY: &'static str = "table"; } @@ -761,7 +646,7 @@ pub struct TableVLine { /// [Vikram], [49], [Perseverance], /// ) /// ``` -#[elem(name = "cell", title = "Table Cell", Show)] +#[elem(name = "cell", title = "Table Cell")] pub struct TableCell { /// The cell's body. #[required] @@ -808,12 +693,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( diff --git a/crates/typst-library/src/model/terms.rs b/crates/typst-library/src/model/terms.rs index 280c2d67e..71b1bad6d 100644 --- a/crates/typst-library/src/model/terms.rs +++ b/crates/typst-library/src/model/terms.rs @@ -1,15 +1,9 @@ -use typst_utils::{Get, Numeric}; - -use crate::diag::{bail, SourceResult}; -use crate::engine::Engine; +use crate::diag::bail; use crate::foundations::{ - cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain, - Styles, TargetElem, + cast, elem, scope, Array, Content, NativeElement, Packed, Smart, Styles, }; -use crate::html::{tag, HtmlElem}; -use crate::layout::{Em, HElem, Length, Sides, StackChild, StackElem, VElem}; -use crate::model::{ListItemLike, ListLike, ParElem, ParbreakElem}; -use crate::text::TextElem; +use crate::layout::{Em, HElem, Length}; +use crate::model::{ListItemLike, ListLike}; /// A list of terms and their descriptions. /// @@ -27,7 +21,7 @@ use crate::text::TextElem; /// # Syntax /// This function also has dedicated syntax: Starting a line with a slash, /// followed by a term, a colon and a description creates a term list item. -#[elem(scope, title = "Term List", Show)] +#[elem(scope, title = "Term List")] pub struct TermsElem { /// Defines the default [spacing]($terms.spacing) of the term list. If it is /// `{false}`, the items are spaced apart with @@ -117,94 +111,6 @@ impl TermsElem { type TermItem; } -impl Show for Packed { - fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - let span = self.span(); - let tight = self.tight.get(styles); - - if styles.get(TargetElem::target).is_html() { - return Ok(HtmlElem::new(tag::dl) - .with_body(Some(Content::sequence(self.children.iter().flat_map( - |item| { - // Text in wide term lists shall always turn into paragraphs. - let mut description = item.description.clone(); - if !tight { - description += ParbreakElem::shared(); - } - - [ - HtmlElem::new(tag::dt) - .with_body(Some(item.term.clone())) - .pack() - .spanned(item.term.span()), - HtmlElem::new(tag::dd) - .with_body(Some(description)) - .pack() - .spanned(item.description.span()), - ] - }, - )))) - .pack()); - } - - let separator = self.separator.get_ref(styles); - let indent = self.indent.get(styles); - let hanging_indent = self.hanging_indent.get(styles); - let gutter = self.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 self.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 = self - .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) - } -} - /// A term list item. #[elem(name = "item", title = "Term List Item")] pub struct TermItem { diff --git a/crates/typst-library/src/pdf/embed.rs b/crates/typst-library/src/pdf/embed.rs index 0f93f95af..3aba85623 100644 --- a/crates/typst-library/src/pdf/embed.rs +++ b/crates/typst-library/src/pdf/embed.rs @@ -1,12 +1,8 @@ use ecow::EcoString; -use typst_library::foundations::Target; use typst_syntax::Spanned; -use crate::diag::{warning, At, SourceResult}; -use crate::engine::Engine; -use crate::foundations::{ - elem, Bytes, Cast, Content, Derived, Packed, Show, StyleChain, TargetElem, -}; +use crate::diag::At; +use crate::foundations::{elem, Bytes, Cast, Derived}; use crate::introspection::Locatable; use crate::World; @@ -33,7 +29,7 @@ use crate::World; /// - This element is ignored if exporting to a format other than PDF. /// - File embeddings are not currently supported for PDF/A-2, even if the /// embedded file conforms to PDF/A-1 or PDF/A-2. -#[elem(Show, Locatable)] +#[elem(Locatable)] pub struct EmbedElem { /// The [path]($syntax/#paths) of the file to be embedded. /// @@ -77,17 +73,6 @@ pub struct EmbedElem { pub description: Option, } -impl Show for Packed { - fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { - if styles.get(TargetElem::target) == Target::Html { - engine - .sink - .warn(warning!(self.span(), "embed was ignored during HTML export")); - } - Ok(Content::empty()) - } -} - /// The relationship of an embedded file with the document. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum EmbeddedFileRelationship { diff --git a/crates/typst-library/src/routines.rs b/crates/typst-library/src/routines.rs index 59ce83282..4bf8d60c8 100644 --- a/crates/typst-library/src/routines.rs +++ b/crates/typst-library/src/routines.rs @@ -1,7 +1,4 @@ -#![allow(unused)] - use std::hash::{Hash, Hasher}; -use std::num::NonZeroUsize; use comemo::{Tracked, TrackedMut}; use typst_syntax::{Span, SyntaxMode}; @@ -10,20 +7,12 @@ use typst_utils::LazyHash; use crate::diag::SourceResult; use crate::engine::{Engine, Route, Sink, Traced}; use crate::foundations::{ - Args, Cast, Closure, Content, Context, Func, Packed, Scope, StyleChain, Styles, Value, + Args, Closure, Content, Context, Func, NativeRuleMap, Scope, StyleChain, Styles, + Value, }; use crate::introspection::{Introspector, Locator, SplitLocator}; -use crate::layout::{ - Abs, BoxElem, ColumnsElem, Fragment, Frame, GridElem, InlineItem, MoveElem, PadElem, - PagedDocument, Region, Regions, Rel, RepeatElem, RotateElem, ScaleElem, Size, - SkewElem, StackElem, -}; -use crate::math::EquationElem; -use crate::model::{DocumentInfo, EnumElem, ListElem, TableElem}; -use crate::visualize::{ - CircleElem, CurveElem, EllipseElem, ImageElem, LineElem, PathElem, PolygonElem, - RectElem, SquareElem, -}; +use crate::layout::{Frame, Region}; +use crate::model::DocumentInfo; use crate::World; /// Defines the `Routines` struct. @@ -38,6 +27,8 @@ macro_rules! routines { /// This is essentially dynamic linking and done to allow for crate /// splitting. pub struct Routines { + /// Native show rules. + pub rules: NativeRuleMap, $( $(#[$attr])* pub $name: $(for<$($time),*>)? fn ($($args)*) -> $ret @@ -86,15 +77,6 @@ routines! { styles: StyleChain<'a>, ) -> SourceResult>> - /// Lays out content into multiple regions. - fn layout_fragment( - engine: &mut Engine, - content: &Content, - locator: Locator, - styles: StyleChain, - regions: Regions, - ) -> SourceResult - /// Lays out content into a single region, producing a single frame. fn layout_frame( engine: &mut Engine, @@ -103,213 +85,6 @@ routines! { styles: StyleChain, region: Region, ) -> SourceResult - - /// Lays out a [`ListElem`]. - fn layout_list( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - regions: Regions, - ) -> SourceResult - - /// Lays out an [`EnumElem`]. - fn layout_enum( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - regions: Regions, - ) -> SourceResult - - /// Lays out a [`GridElem`]. - fn layout_grid( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - regions: Regions, - ) -> SourceResult - - /// Lays out a [`TableElem`]. - fn layout_table( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - regions: Regions, - ) -> SourceResult - - /// Lays out a [`StackElem`]. - fn layout_stack( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - regions: Regions, - ) -> SourceResult - - /// Lays out a [`ColumnsElem`]. - fn layout_columns( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - regions: Regions, - ) -> SourceResult - - /// Lays out a [`MoveElem`]. - fn layout_move( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - region: Region, - ) -> SourceResult - - /// Lays out a [`RotateElem`]. - fn layout_rotate( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - region: Region, - ) -> SourceResult - - /// Lays out a [`ScaleElem`]. - fn layout_scale( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - region: Region, - ) -> SourceResult - - /// Lays out a [`SkewElem`]. - fn layout_skew( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - region: Region, - ) -> SourceResult - - /// Lays out a [`RepeatElem`]. - fn layout_repeat( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - region: Region, - ) -> SourceResult - - /// Lays out a [`PadElem`]. - fn layout_pad( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - regions: Regions, - ) -> SourceResult - - /// Lays out a [`LineElem`]. - fn layout_line( - elem: &Packed, - _: &mut Engine, - _: Locator, - styles: StyleChain, - region: Region, - ) -> SourceResult - - /// Lays out a [`CurveElem`]. - fn layout_curve( - elem: &Packed, - _: &mut Engine, - _: Locator, - styles: StyleChain, - region: Region, - ) -> SourceResult - - /// Lays out a [`PathElem`]. - fn layout_path( - elem: &Packed, - _: &mut Engine, - _: Locator, - styles: StyleChain, - region: Region, - ) -> SourceResult - - /// Lays out a [`PolygonElem`]. - fn layout_polygon( - elem: &Packed, - _: &mut Engine, - _: Locator, - styles: StyleChain, - region: Region, - ) -> SourceResult - - /// Lays out a [`RectElem`]. - fn layout_rect( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - region: Region, - ) -> SourceResult - - /// Lays out a [`SquareElem`]. - fn layout_square( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - region: Region, - ) -> SourceResult - - /// Lays out a [`EllipseElem`]. - fn layout_ellipse( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - region: Region, - ) -> SourceResult - - /// Lays out a [`CircleElem`]. - fn layout_circle( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - region: Region, - ) -> SourceResult - - /// Lays out an [`ImageElem`]. - fn layout_image( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - region: Region, - ) -> SourceResult - - /// Lays out an [`EquationElem`] in a paragraph. - fn layout_equation_inline( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - region: Size, - ) -> SourceResult> - - /// Lays out an [`EquationElem`] in a flow. - fn layout_equation_block( - elem: &Packed, - engine: &mut Engine, - locator: Locator, - styles: StyleChain, - regions: Regions, - ) -> SourceResult } /// Defines what kind of realization we are performing. diff --git a/crates/typst-library/src/text/deco.rs b/crates/typst-library/src/text/deco.rs index 8c1d56345..f7d5c33be 100644 --- a/crates/typst-library/src/text/deco.rs +++ b/crates/typst-library/src/text/deco.rs @@ -1,13 +1,6 @@ -use smallvec::smallvec; - -use crate::diag::SourceResult; -use crate::engine::Engine; -use crate::foundations::{ - elem, Content, NativeElement, Packed, Show, Smart, StyleChain, TargetElem, -}; -use crate::html::{attr, tag, HtmlElem}; +use crate::foundations::{elem, Content, Smart}; use crate::layout::{Abs, Corners, Length, Rel, Sides}; -use crate::text::{BottomEdge, BottomEdgeMetric, TextElem, TopEdge, TopEdgeMetric}; +use crate::text::{BottomEdge, BottomEdgeMetric, TopEdge, TopEdgeMetric}; use crate::visualize::{Color, FixedStroke, Paint, Stroke}; /// Underlines text. @@ -16,7 +9,7 @@ use crate::visualize::{Color, FixedStroke, Paint, Stroke}; /// ```example /// This is #underline[important]. /// ``` -#[elem(Show)] +#[elem] pub struct UnderlineElem { /// How to [stroke] the line. /// @@ -78,41 +71,13 @@ pub struct UnderlineElem { pub body: Content, } -impl Show for Packed { - #[typst_macros::time(name = "underline", span = self.span())] - fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - if styles.get(TargetElem::target).is_html() { - // 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. - return Ok(HtmlElem::new(tag::span) - .with_attr(attr::style, "text-decoration: underline") - .with_body(Some(self.body.clone())) - .pack()); - } - - Ok(self.body.clone().set( - TextElem::deco, - smallvec![Decoration { - line: DecoLine::Underline { - stroke: self.stroke.resolve(styles).unwrap_or_default(), - offset: self.offset.resolve(styles), - evade: self.evade.get(styles), - background: self.background.get(styles), - }, - extent: self.extent.resolve(styles), - }], - )) - } -} - /// Adds a line over text. /// /// # Example /// ```example /// #overline[A line over text.] /// ``` -#[elem(Show)] +#[elem] pub struct OverlineElem { /// How to [stroke] the line. /// @@ -180,38 +145,13 @@ pub struct OverlineElem { pub body: Content, } -impl Show for Packed { - #[typst_macros::time(name = "overline", span = self.span())] - fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - if styles.get(TargetElem::target).is_html() { - return Ok(HtmlElem::new(tag::span) - .with_attr(attr::style, "text-decoration: overline") - .with_body(Some(self.body.clone())) - .pack()); - } - - Ok(self.body.clone().set( - TextElem::deco, - smallvec![Decoration { - line: DecoLine::Overline { - stroke: self.stroke.resolve(styles).unwrap_or_default(), - offset: self.offset.resolve(styles), - evade: self.evade.get(styles), - background: self.background.get(styles), - }, - extent: self.extent.resolve(styles), - }], - )) - } -} - /// Strikes through text. /// /// # Example /// ```example /// This is #strike[not] relevant. /// ``` -#[elem(title = "Strikethrough", Show)] +#[elem(title = "Strikethrough")] pub struct StrikeElem { /// How to [stroke] the line. /// @@ -264,35 +204,13 @@ pub struct StrikeElem { pub body: Content, } -impl Show for Packed { - #[typst_macros::time(name = "strike", span = self.span())] - fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - if styles.get(TargetElem::target).is_html() { - return Ok(HtmlElem::new(tag::s).with_body(Some(self.body.clone())).pack()); - } - - Ok(self.body.clone().set( - TextElem::deco, - smallvec![Decoration { - // Note that we do not support evade option for strikethrough. - line: DecoLine::Strikethrough { - stroke: self.stroke.resolve(styles).unwrap_or_default(), - offset: self.offset.resolve(styles), - background: self.background.get(styles), - }, - extent: self.extent.resolve(styles), - }], - )) - } -} - /// Highlights text with a background color. /// /// # Example /// ```example /// This is #highlight[important]. /// ``` -#[elem(Show)] +#[elem] pub struct HighlightElem { /// The color to highlight the text with. /// @@ -363,35 +281,6 @@ pub struct HighlightElem { pub body: Content, } -impl Show for Packed { - #[typst_macros::time(name = "highlight", span = self.span())] - fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - if styles.get(TargetElem::target).is_html() { - return Ok(HtmlElem::new(tag::mark) - .with_body(Some(self.body.clone())) - .pack()); - } - - Ok(self.body.clone().set( - TextElem::deco, - smallvec![Decoration { - line: DecoLine::Highlight { - fill: self.fill.get_cloned(styles), - stroke: self - .stroke - .resolve(styles) - .unwrap_or_default() - .map(|stroke| stroke.map(Stroke::unwrap_or_default)), - top_edge: self.top_edge.get(styles), - bottom_edge: self.bottom_edge.get(styles), - radius: self.radius.resolve(styles).unwrap_or_default(), - }, - extent: self.extent.resolve(styles), - }], - )) - } -} - /// A text decoration. /// /// Can be positioned over, under, or on top of text, or highlight the text with diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs index 67038163d..8cddfbfb5 100644 --- a/crates/typst-library/src/text/raw.rs +++ b/crates/typst-library/src/text/raw.rs @@ -16,14 +16,13 @@ use crate::diag::{ }; use crate::engine::Engine; use crate::foundations::{ - cast, elem, scope, Bytes, Content, Derived, NativeElement, OneOrMultiple, Packed, - PlainText, Show, ShowSet, Smart, StyleChain, Styles, Synthesize, TargetElem, + cast, elem, scope, Bytes, Content, Derived, OneOrMultiple, Packed, PlainText, + ShowSet, Smart, StyleChain, Styles, Synthesize, }; -use crate::html::{tag, HtmlElem}; -use crate::layout::{BlockBody, BlockElem, Em, HAlignment}; +use crate::layout::{Em, HAlignment}; use crate::loading::{DataSource, Load}; use crate::model::{Figurable, ParElem}; -use crate::text::{FontFamily, FontList, LinebreakElem, LocalName, TextElem, TextSize}; +use crate::text::{FontFamily, FontList, LocalName, TextElem, TextSize}; use crate::visualize::Color; use crate::World; @@ -78,7 +77,6 @@ use crate::World; scope, title = "Raw Text / Code", Synthesize, - Show, ShowSet, LocalName, Figurable, @@ -429,46 +427,6 @@ impl Packed { } } -impl Show for Packed { - #[typst_macros::time(name = "raw", span = self.span())] - fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - let lines = self.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 styles.get(TargetElem::target).is_html() { - return Ok(HtmlElem::new(if self.block.get(styles) { - tag::pre - } else { - tag::code - }) - .with_body(Some(realized)) - .pack() - .spanned(self.span())); - } - - if self.block.get(styles) { - // Align the text before inserting it into the block. - realized = realized.aligned(self.align.get(styles).into()); - realized = BlockElem::new() - .with_body(Some(BlockBody::Content(realized))) - .pack() - .spanned(self.span()); - } - - Ok(realized) - } -} - impl ShowSet for Packed { fn show_set(&self, styles: StyleChain) -> Styles { let mut out = Styles::new(); @@ -634,7 +592,7 @@ fn format_theme_error(error: syntect::LoadingError) -> LoadError { /// It allows you to access various properties of the line, such as the line /// number, the raw non-highlighted text, the highlighted text, and whether it /// is the first or last line of the raw block. -#[elem(name = "line", title = "Raw Text / Code Line", Show, PlainText)] +#[elem(name = "line", title = "Raw Text / Code Line", PlainText)] pub struct RawLine { /// The line number of the raw line inside of the raw block, starts at 1. #[required] @@ -653,13 +611,6 @@ pub struct RawLine { pub body: Content, } -impl Show for Packed { - #[typst_macros::time(name = "raw.line", span = self.span())] - fn show(&self, _: &mut Engine, _styles: StyleChain) -> SourceResult { - Ok(self.body.clone()) - } -} - impl PlainText for Packed { fn plain_text(&self, text: &mut EcoString) { text.push_str(&self.text); diff --git a/crates/typst-library/src/text/shift.rs b/crates/typst-library/src/text/shift.rs index 1a05d8f9c..87ccae635 100644 --- a/crates/typst-library/src/text/shift.rs +++ b/crates/typst-library/src/text/shift.rs @@ -1,13 +1,8 @@ -use crate::diag::SourceResult; -use crate::engine::Engine; -use crate::foundations::{ - elem, Content, NativeElement, Packed, Show, Smart, StyleChain, TargetElem, -}; -use crate::html::{tag, HtmlElem}; -use crate::layout::{Em, Length}; -use crate::text::{FontMetrics, TextElem, TextSize}; use ttf_parser::Tag; -use typst_library::text::ScriptMetrics; + +use crate::foundations::{elem, Content, Smart}; +use crate::layout::{Em, Length}; +use crate::text::{FontMetrics, ScriptMetrics, TextSize}; /// Renders text in subscript. /// @@ -17,7 +12,7 @@ use typst_library::text::ScriptMetrics; /// ```example /// Revenue#sub[yearly] /// ``` -#[elem(title = "Subscript", Show)] +#[elem(title = "Subscript")] pub struct SubElem { /// Whether to create artificial subscripts by lowering and scaling down /// regular glyphs. @@ -64,29 +59,6 @@ pub struct SubElem { pub body: Content, } -impl Show for Packed { - #[typst_macros::time(name = "sub", span = self.span())] - fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - let body = self.body.clone(); - - if styles.get(TargetElem::target).is_html() { - return Ok(HtmlElem::new(tag::sub) - .with_body(Some(body)) - .pack() - .spanned(self.span())); - } - - show_script( - styles, - body, - self.typographic.get(styles), - self.baseline.get(styles), - self.size.get(styles), - ScriptKind::Sub, - ) - } -} - /// Renders text in superscript. /// /// The text is rendered smaller and its baseline is raised. @@ -95,7 +67,7 @@ impl Show for Packed { /// ```example /// 1#super[st] try! /// ``` -#[elem(title = "Superscript", Show)] +#[elem(title = "Superscript")] pub struct SuperElem { /// Whether to create artificial superscripts by raising and scaling down /// regular glyphs. @@ -146,49 +118,6 @@ pub struct SuperElem { pub body: Content, } -impl Show for Packed { - #[typst_macros::time(name = "super", span = self.span())] - fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - let body = self.body.clone(); - - if styles.get(TargetElem::target).is_html() { - return Ok(HtmlElem::new(tag::sup) - .with_body(Some(body)) - .pack() - .spanned(self.span())); - } - - show_script( - styles, - body, - self.typographic.get(styles), - self.baseline.get(styles), - self.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, - }), - )) -} - /// Configuration values for sub- or superscript text. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct ShiftSettings { diff --git a/crates/typst-library/src/text/smallcaps.rs b/crates/typst-library/src/text/smallcaps.rs index 1c2838933..199222fed 100644 --- a/crates/typst-library/src/text/smallcaps.rs +++ b/crates/typst-library/src/text/smallcaps.rs @@ -1,7 +1,4 @@ -use crate::diag::SourceResult; -use crate::engine::Engine; -use crate::foundations::{elem, Content, Packed, Show, StyleChain}; -use crate::text::TextElem; +use crate::foundations::{elem, Content}; /// Displays text in small capitals. /// @@ -43,7 +40,7 @@ use crate::text::TextElem; /// = Introduction /// #lorem(40) /// ``` -#[elem(title = "Small Capitals", Show)] +#[elem(title = "Small Capitals")] pub struct SmallcapsElem { /// Whether to turn uppercase letters into small capitals as well. /// @@ -61,15 +58,6 @@ pub struct SmallcapsElem { pub body: Content, } -impl Show for Packed { - #[typst_macros::time(name = "smallcaps", span = self.span())] - fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult { - let sc = - if self.all.get(styles) { Smallcaps::All } else { Smallcaps::Minuscules }; - Ok(self.body.clone().set(TextElem::smallcaps, Some(sc))) - } -} - /// What becomes small capitals. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Smallcaps { diff --git a/crates/typst-library/src/text/smartquote.rs b/crates/typst-library/src/text/smartquote.rs index 24787d062..375b1cf09 100644 --- a/crates/typst-library/src/text/smartquote.rs +++ b/crates/typst-library/src/text/smartquote.rs @@ -5,9 +5,10 @@ use unicode_segmentation::UnicodeSegmentation; use crate::diag::{bail, HintedStrResult, StrResult}; use crate::foundations::{ array, cast, dict, elem, Array, Dict, FromValue, Packed, PlainText, Smart, Str, + StyleChain, }; use crate::layout::Dir; -use crate::text::{Lang, Region}; +use crate::text::{Lang, Region, TextElem}; /// A language-aware quote that reacts to its context. /// @@ -200,6 +201,16 @@ pub struct SmartQuotes<'s> { } impl<'s> SmartQuotes<'s> { + /// Retrieve the smart quotes as configured by the current styles. + pub fn get_in(styles: StyleChain<'s>) -> Self { + Self::get( + styles.get_ref(SmartQuoteElem::quotes), + styles.get(TextElem::lang), + styles.get(TextElem::region), + styles.get(SmartQuoteElem::alternative), + ) + } + /// Create a new `Quotes` struct with the given quotes, optionally falling /// back to the defaults for a language and region. /// diff --git a/crates/typst-library/src/visualize/curve.rs b/crates/typst-library/src/visualize/curve.rs index 587f0d4a2..15ae48c61 100644 --- a/crates/typst-library/src/visualize/curve.rs +++ b/crates/typst-library/src/visualize/curve.rs @@ -2,12 +2,9 @@ use kurbo::ParamCurveExtrema; use typst_macros::{scope, Cast}; use typst_utils::Numeric; -use crate::diag::{bail, HintedStrResult, HintedString, SourceResult}; -use crate::engine::Engine; -use crate::foundations::{ - cast, elem, Content, NativeElement, Packed, Show, Smart, StyleChain, -}; -use crate::layout::{Abs, Axes, BlockElem, Length, Point, Rel, Size}; +use crate::diag::{bail, HintedStrResult, HintedString}; +use crate::foundations::{cast, elem, Content, Packed, Smart}; +use crate::layout::{Abs, Axes, Length, Point, Rel, Size}; use crate::visualize::{FillRule, Paint, Stroke}; use super::FixedStroke; @@ -42,7 +39,7 @@ use super::FixedStroke; /// curve.close(), /// ) /// ``` -#[elem(scope, Show)] +#[elem(scope)] pub struct CurveElem { /// How to fill the curve. /// @@ -95,14 +92,6 @@ pub struct CurveElem { pub components: Vec, } -impl Show for Packed { - fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult { - Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_curve) - .pack() - .spanned(self.span())) - } -} - #[scope] impl CurveElem { #[elem] diff --git a/crates/typst-library/src/visualize/image/mod.rs b/crates/typst-library/src/visualize/image/mod.rs index 48a14f0ed..95021b818 100644 --- a/crates/typst-library/src/visualize/image/mod.rs +++ b/crates/typst-library/src/visualize/image/mod.rs @@ -15,13 +15,11 @@ use ecow::EcoString; use typst_syntax::{Span, Spanned}; use typst_utils::LazyHash; -use crate::diag::{SourceResult, StrResult}; -use crate::engine::Engine; +use crate::diag::StrResult; use crate::foundations::{ - cast, elem, func, scope, Bytes, Cast, Content, Derived, NativeElement, Packed, Show, - Smart, StyleChain, + cast, elem, func, scope, Bytes, Cast, Content, Derived, NativeElement, Packed, Smart, }; -use crate::layout::{BlockElem, Length, Rel, Sizing}; +use crate::layout::{Length, Rel, Sizing}; use crate::loading::{DataSource, Load, LoadSource, Loaded, Readable}; use crate::model::Figurable; use crate::text::LocalName; @@ -44,7 +42,7 @@ use crate::text::LocalName; /// ], /// ) /// ``` -#[elem(scope, Show, LocalName, Figurable)] +#[elem(scope, LocalName, Figurable)] pub struct ImageElem { /// A [path]($syntax/#paths) to an image file or raw bytes making up an /// image in one of the supported [formats]($image.format). @@ -219,16 +217,6 @@ impl ImageElem { } } -impl Show for Packed { - fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { - Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_image) - .with_width(self.width.get(styles)) - .with_height(self.height.get(styles)) - .pack() - .spanned(self.span())) - } -} - impl LocalName for Packed { const KEY: &'static str = "figure"; } diff --git a/crates/typst-library/src/visualize/line.rs b/crates/typst-library/src/visualize/line.rs index d058b926a..7eecfc915 100644 --- a/crates/typst-library/src/visualize/line.rs +++ b/crates/typst-library/src/visualize/line.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::{Abs, Angle, Axes, BlockElem, Length, Rel}; +use crate::foundations::elem; +use crate::layout::{Abs, Angle, Axes, Length, Rel}; use crate::visualize::Stroke; /// A line from one point to another. @@ -17,7 +15,7 @@ use crate::visualize::Stroke; /// stroke: 2pt + maroon, /// ) /// ``` -#[elem(Show)] +#[elem] pub struct LineElem { /// The start point of the line. /// @@ -50,11 +48,3 @@ pub struct LineElem { #[fold] pub stroke: Stroke, } - -impl Show for Packed { - fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult { - Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_line) - .pack() - .spanned(self.span())) - } -} diff --git a/crates/typst-library/src/visualize/path.rs b/crates/typst-library/src/visualize/path.rs index e19e091df..bd8aea02d 100644 --- a/crates/typst-library/src/visualize/path.rs +++ b/crates/typst-library/src/visualize/path.rs @@ -1,11 +1,7 @@ use self::PathVertex::{AllControlPoints, MirroredControlPoint, Vertex}; -use crate::diag::{bail, SourceResult}; -use crate::engine::Engine; -use crate::foundations::{ - array, cast, elem, Array, Content, NativeElement, Packed, Reflect, Show, Smart, - StyleChain, -}; -use crate::layout::{Axes, BlockElem, Length, Rel}; +use crate::diag::bail; +use crate::foundations::{array, cast, elem, Array, Reflect, Smart}; +use crate::layout::{Axes, Length, Rel}; use crate::visualize::{FillRule, Paint, Stroke}; /// A path through a list of points, connected by Bézier curves. @@ -21,7 +17,7 @@ use crate::visualize::{FillRule, Paint, Stroke}; /// ((50%, 0pt), (40pt, 0pt)), /// ) /// ``` -#[elem(Show)] +#[elem] pub struct PathElem { /// How to fill the path. /// @@ -83,14 +79,6 @@ pub struct PathElem { pub vertices: Vec, } -impl Show for Packed { - fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult { - Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_path) - .pack() - .spanned(self.span())) - } -} - /// A component used for path creation. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum PathVertex { diff --git a/crates/typst-library/src/visualize/polygon.rs b/crates/typst-library/src/visualize/polygon.rs index d75e1a657..db75a2670 100644 --- a/crates/typst-library/src/visualize/polygon.rs +++ b/crates/typst-library/src/visualize/polygon.rs @@ -2,12 +2,8 @@ use std::f64::consts::PI; use typst_syntax::Span; -use crate::diag::SourceResult; -use crate::engine::Engine; -use crate::foundations::{ - elem, func, scope, Content, NativeElement, Packed, Show, Smart, StyleChain, -}; -use crate::layout::{Axes, BlockElem, Em, Length, Rel}; +use crate::foundations::{elem, func, scope, Content, NativeElement, Smart}; +use crate::layout::{Axes, Em, Length, Rel}; use crate::visualize::{FillRule, Paint, Stroke}; /// A closed polygon. @@ -25,7 +21,7 @@ use crate::visualize::{FillRule, Paint, Stroke}; /// (0%, 2cm), /// ) /// ``` -#[elem(scope, Show)] +#[elem(scope)] pub struct PolygonElem { /// How to fill the polygon. /// @@ -124,11 +120,3 @@ impl PolygonElem { elem.pack().spanned(span) } } - -impl Show for Packed { - fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult { - Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_polygon) - .pack() - .spanned(self.span())) - } -} diff --git a/crates/typst-library/src/visualize/shape.rs b/crates/typst-library/src/visualize/shape.rs index f21bf93e9..fc7b8748e 100644 --- a/crates/typst-library/src/visualize/shape.rs +++ b/crates/typst-library/src/visualize/shape.rs @@ -1,9 +1,5 @@ -use crate::diag::SourceResult; -use crate::engine::Engine; -use crate::foundations::{ - elem, Cast, Content, NativeElement, Packed, Show, Smart, StyleChain, -}; -use crate::layout::{Abs, BlockElem, Corners, Length, Point, Rel, Sides, Size, Sizing}; +use crate::foundations::{elem, Cast, Content, Smart}; +use crate::layout::{Abs, Corners, Length, Point, Rel, Sides, Size, Sizing}; use crate::visualize::{Curve, FixedStroke, Paint, Stroke}; /// A rectangle with optional content. @@ -19,7 +15,7 @@ use crate::visualize::{Curve, FixedStroke, Paint, Stroke}; /// to fit the content. /// ] /// ``` -#[elem(title = "Rectangle", Show)] +#[elem(title = "Rectangle")] pub struct RectElem { /// The rectangle's width, relative to its parent container. pub width: Smart>, @@ -122,16 +118,6 @@ pub struct RectElem { pub body: Option, } -impl Show for Packed { - fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { - Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_rect) - .with_width(self.width.get(styles)) - .with_height(self.height.get(styles)) - .pack() - .spanned(self.span())) - } -} - /// A square with optional content. /// /// # Example @@ -145,7 +131,7 @@ impl Show for Packed { /// sized to fit. /// ] /// ``` -#[elem(Show)] +#[elem] pub struct SquareElem { /// The square's side length. This is mutually exclusive with `width` and /// `height`. @@ -209,16 +195,6 @@ pub struct SquareElem { pub body: Option, } -impl Show for Packed { - fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { - Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_square) - .with_width(self.width.get(styles)) - .with_height(self.height.get(styles)) - .pack() - .spanned(self.span())) - } -} - /// An ellipse with optional content. /// /// # Example @@ -233,7 +209,7 @@ impl Show for Packed { /// to fit the content. /// ] /// ``` -#[elem(Show)] +#[elem] pub struct EllipseElem { /// The ellipse's width, relative to its parent container. pub width: Smart>, @@ -269,16 +245,6 @@ pub struct EllipseElem { pub body: Option, } -impl Show for Packed { - fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { - Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_ellipse) - .with_width(self.width.get(styles)) - .with_height(self.height.get(styles)) - .pack() - .spanned(self.span())) - } -} - /// A circle with optional content. /// /// # Example @@ -293,7 +259,7 @@ impl Show for Packed { /// sized to fit. /// ] /// ``` -#[elem(Show)] +#[elem] pub struct CircleElem { /// The circle's radius. This is mutually exclusive with `width` and /// `height`. @@ -354,16 +320,6 @@ pub struct CircleElem { pub body: Option, } -impl Show for Packed { - fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { - Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_circle) - .with_width(self.width.get(styles)) - .with_height(self.height.get(styles)) - .pack() - .spanned(self.span())) - } -} - /// A geometric shape with optional fill and stroke. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Shape { diff --git a/crates/typst-realize/src/lib.rs b/crates/typst-realize/src/lib.rs index fcfb40667..6af249cc3 100644 --- a/crates/typst-realize/src/lib.rs +++ b/crates/typst-realize/src/lib.rs @@ -14,9 +14,9 @@ use ecow::EcoString; use typst_library::diag::{bail, At, SourceResult}; use typst_library::engine::Engine; use typst_library::foundations::{ - Content, Context, ContextElem, Element, NativeElement, Recipe, RecipeIndex, Selector, - SequenceElem, Show, ShowSet, Style, StyleChain, StyledElem, Styles, SymbolElem, - Synthesize, Transformation, + Content, Context, ContextElem, Element, NativeElement, NativeShowRule, Recipe, + RecipeIndex, Selector, SequenceElem, ShowSet, Style, StyleChain, StyledElem, Styles, + SymbolElem, Synthesize, TargetElem, Transformation, }; use typst_library::html::{tag, FrameElem, HtmlElem}; use typst_library::introspection::{Locatable, SplitLocator, Tag, TagElem}; @@ -160,7 +160,7 @@ enum ShowStep<'a> { /// A user-defined transformational show rule. Recipe(&'a Recipe, RecipeIndex), /// The built-in show rule. - Builtin, + Builtin(NativeShowRule), } /// A match of a regex show rule. @@ -382,9 +382,7 @@ fn visit_show_rules<'a>( } // Apply a built-in show rule. - ShowStep::Builtin => { - output.with::().unwrap().show(s.engine, chained) - } + ShowStep::Builtin(rule) => rule.apply(&output, s.engine, chained), }; // Errors in show rules don't terminate compilation immediately. We just @@ -426,14 +424,14 @@ fn visit_show_rules<'a>( Ok(true) } -/// Inspects a target element and the current styles and determines how to -/// proceed with the styling. +/// Inspects an element and the current styles and determines how to proceed +/// with the styling. fn verdict<'a>( engine: &mut Engine, - target: &'a Content, + elem: &'a Content, styles: StyleChain<'a>, ) -> Option> { - let prepared = target.is_prepared(); + let prepared = elem.is_prepared(); let mut map = Styles::new(); let mut step = None; @@ -441,20 +439,20 @@ fn verdict<'a>( // fields before real synthesis runs (during preparation). It's really // unfortunate that we have to do this, but otherwise // `show figure.where(kind: table)` won't work :( - let mut target = target; + let mut elem = elem; let mut slot; - if !prepared && target.can::() { - slot = target.clone(); + if !prepared && elem.can::() { + slot = elem.clone(); slot.with_mut::() .unwrap() .synthesize(engine, styles) .ok(); - target = &slot; + elem = &slot; } // Lazily computes the total number of recipes in the style chain. We need // it to determine whether a particular show rule was already applied to the - // `target` previously. For this purpose, show rules are indexed from the + // `elem` previously. For this purpose, show rules are indexed from the // top of the chain as the chain might grow to the bottom. let depth = LazyCell::new(|| styles.recipes().count()); @@ -462,7 +460,7 @@ fn verdict<'a>( // We're not interested in recipes that don't match. if !recipe .selector() - .is_some_and(|selector| selector.matches(target, Some(styles))) + .is_some_and(|selector| selector.matches(elem, Some(styles))) { continue; } @@ -480,9 +478,9 @@ fn verdict<'a>( continue; } - // Check whether this show rule was already applied to the target. + // Check whether this show rule was already applied to the element. let index = RecipeIndex(*depth - r); - if target.is_guarded(index) { + if elem.is_guarded(index) { continue; } @@ -498,19 +496,22 @@ fn verdict<'a>( } // If we found no user-defined rule, also consider the built-in show rule. - if step.is_none() && target.can::() { - step = Some(ShowStep::Builtin); + if step.is_none() { + let target = styles.get(TargetElem::target); + if let Some(rule) = engine.routines.rules.get(target, elem) { + step = Some(ShowStep::Builtin(rule)); + } } // If there's no nothing to do, there is also no verdict. if step.is_none() && map.is_empty() && (prepared || { - target.label().is_none() - && target.location().is_none() - && !target.can::() - && !target.can::() - && !target.can::() + elem.label().is_none() + && elem.location().is_none() + && !elem.can::() + && !elem.can::() + && !elem.can::() }) { return None; @@ -523,7 +524,7 @@ fn verdict<'a>( fn prepare( engine: &mut Engine, locator: &mut SplitLocator, - target: &mut Content, + elem: &mut Content, map: &mut Styles, styles: StyleChain, ) -> SourceResult> { @@ -533,43 +534,43 @@ fn prepare( // // The element could already have a location even if it is not prepared // when it stems from a query. - let key = typst_utils::hash128(&target); - if target.location().is_none() - && (target.can::() || target.label().is_some()) + let key = typst_utils::hash128(&elem); + if elem.location().is_none() + && (elem.can::() || elem.label().is_some()) { let loc = locator.next_location(engine.introspector, key); - target.set_location(loc); + elem.set_location(loc); } // Apply built-in show-set rules. User-defined show-set rules are already // considered in the map built while determining the verdict. - if let Some(show_settable) = target.with::() { + if let Some(show_settable) = elem.with::() { map.apply(show_settable.show_set(styles)); } // If necessary, generated "synthesized" fields (which are derived from // other fields or queries). Do this after show-set so that show-set styles // are respected. - if let Some(synthesizable) = target.with_mut::() { + if let Some(synthesizable) = elem.with_mut::() { synthesizable.synthesize(engine, styles.chain(map))?; } // Copy style chain fields into the element itself, so that they are // available in rules. - target.materialize(styles.chain(map)); + elem.materialize(styles.chain(map)); // If the element is locatable, create start and end tags to be able to find // the element in the frames after layout. Do this after synthesis and // materialization, so that it includes the synthesized fields. Do it before // marking as prepared so that show-set rules will apply to this element // when queried. - let tags = target + let tags = elem .location() - .map(|loc| (Tag::Start(target.clone()), Tag::End(loc, key))); + .map(|loc| (Tag::Start(elem.clone()), Tag::End(loc, key))); // Ensure that this preparation only runs once by marking the element as // prepared. - target.mark_prepared(); + elem.mark_prepared(); Ok(tags) } diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs index eee7966a7..0673c3259 100644 --- a/crates/typst/src/lib.rs +++ b/crates/typst/src/lib.rs @@ -39,6 +39,7 @@ pub use typst_syntax as syntax; pub use typst_utils as utils; use std::collections::HashSet; +use std::sync::LazyLock; use comemo::{Track, Tracked, Validate}; use ecow::{eco_format, eco_vec, EcoString, EcoVec}; @@ -46,7 +47,7 @@ use typst_library::diag::{ bail, warning, FileError, SourceDiagnostic, SourceResult, Warned, }; use typst_library::engine::{Engine, Route, Sink, Traced}; -use typst_library::foundations::{StyleChain, Styles, Value}; +use typst_library::foundations::{NativeRuleMap, StyleChain, Styles, Value}; use typst_library::html::HtmlDocument; use typst_library::introspection::Introspector; use typst_library::layout::PagedDocument; @@ -326,33 +327,15 @@ mod sealed { /// function pointers. /// /// This is essentially dynamic linking and done to allow for crate splitting. -pub static ROUTINES: Routines = Routines { +pub static ROUTINES: LazyLock = LazyLock::new(|| Routines { + rules: { + let mut rules = NativeRuleMap::new(); + typst_layout::register(&mut rules); + typst_html::register(&mut rules); + rules + }, eval_string: typst_eval::eval_string, eval_closure: typst_eval::eval_closure, realize: typst_realize::realize, - layout_fragment: typst_layout::layout_fragment, layout_frame: typst_layout::layout_frame, - layout_list: typst_layout::layout_list, - layout_enum: typst_layout::layout_enum, - layout_grid: typst_layout::layout_grid, - layout_table: typst_layout::layout_table, - layout_stack: typst_layout::layout_stack, - layout_columns: typst_layout::layout_columns, - layout_move: typst_layout::layout_move, - layout_rotate: typst_layout::layout_rotate, - layout_scale: typst_layout::layout_scale, - layout_skew: typst_layout::layout_skew, - layout_repeat: typst_layout::layout_repeat, - layout_pad: typst_layout::layout_pad, - layout_line: typst_layout::layout_line, - layout_curve: typst_layout::layout_curve, - layout_path: typst_layout::layout_path, - layout_polygon: typst_layout::layout_polygon, - layout_rect: typst_layout::layout_rect, - layout_square: typst_layout::layout_square, - layout_ellipse: typst_layout::layout_ellipse, - layout_circle: typst_layout::layout_circle, - layout_image: typst_layout::layout_image, - layout_equation_block: typst_layout::layout_equation_block, - layout_equation_inline: typst_layout::layout_equation_inline, -}; +});