diff --git a/Cargo.lock b/Cargo.lock index 62345eaa6..d7d3384db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -930,6 +930,15 @@ dependencies = [ "ahash 0.7.7", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.6", +] + [[package]] name = "hashbrown" version = "0.14.2" @@ -1398,6 +1407,17 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "lasso" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4644821e1c3d7a560fe13d842d13f587c07348a1a05d3a797152d41c90c56df2" +dependencies = [ + "ahash 0.8.6", + "dashmap", + "hashbrown 0.13.2", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -2881,6 +2901,7 @@ dependencies = [ "image", "indexmap 2.0.2", "kurbo", + "lasso", "log", "miniz_oxide", "once_cell", @@ -2893,6 +2914,7 @@ dependencies = [ "rustybuzz", "serde", "siphasher", + "smallvec", "stacker", "subsetter", "svg2pdf", diff --git a/crates/typst-cli/src/query.rs b/crates/typst-cli/src/query.rs index 0db6eb540..cc9cfc230 100644 --- a/crates/typst-cli/src/query.rs +++ b/crates/typst-cli/src/query.rs @@ -89,7 +89,7 @@ fn format(elements: Vec, command: &QueryCommand) -> StrResult { let mapped: Vec<_> = elements .into_iter() .filter_map(|c| match &command.field { - Some(field) => c.field(field), + Some(field) => c.get_by_name(field), _ => Some(c.into_value()), }) .collect(); diff --git a/crates/typst-ide/src/analyze.rs b/crates/typst-ide/src/analyze.rs index 32f3df0d3..4d12e1c5d 100644 --- a/crates/typst-ide/src/analyze.rs +++ b/crates/typst-ide/src/analyze.rs @@ -85,10 +85,10 @@ pub fn analyze_labels( // Labels in the document. for elem in introspector.all() { - let Some(label) = elem.label().cloned() else { continue }; + let Some(label) = elem.label() else { continue }; let details = elem - .field("caption") - .or_else(|| elem.field("body")) + .get_by_name("caption") + .or_else(|| elem.get_by_name("body")) .and_then(|field| match field { Value::Content(content) => Some(content), _ => None, @@ -103,7 +103,7 @@ pub fn analyze_labels( // Bibliography keys. for (key, detail) in (items.bibliography_keys)(introspector.track()) { - output.push((Label(key), detail)); + output.push((Label::new(&key), detail)); } (output, split) diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index 8da50e5a4..325055859 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -1119,11 +1119,11 @@ impl<'a> CompletionContext<'a> { eco_format!( "{}{}{}", if open { "<" } else { "" }, - label.0, + label.as_str(), if close { ">" } else { "" } ) }), - label: label.0, + label: label.as_str().into(), detail, }); } diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs index d358f851b..f90f2aea6 100644 --- a/crates/typst-ide/src/tooltip.rs +++ b/crates/typst-ide/src/tooltip.rs @@ -158,7 +158,7 @@ fn label_tooltip( }; for (label, detail) in analyze_labels(world, frames).0 { - if label.0 == target { + if label.as_str() == target { return Some(Tooltip::Text(detail?)); } } diff --git a/crates/typst-library/Cargo.toml b/crates/typst-library/Cargo.toml index b348596b2..f4fce3512 100644 --- a/crates/typst-library/Cargo.toml +++ b/crates/typst-library/Cargo.toml @@ -40,7 +40,7 @@ roxmltree = "0.18" rustybuzz = "0.10" serde_json = "1" serde_yaml = "0.9" -smallvec = "1.10" +smallvec = { version = "1.11.1", features = ["union", "const_generics", "const_new"] } syntect = { version = "5", default-features = false, features = ["parsing", "regex-fancy", "plist-load", "yaml-load"] } time = { version = "0.3.20", features = ["formatting"] } toml = { version = "0.8" } diff --git a/crates/typst-library/src/compute/data.rs b/crates/typst-library/src/compute/data.rs index dadf0bed3..e4767ebf3 100644 --- a/crates/typst-library/src/compute/data.rs +++ b/crates/typst-library/src/compute/data.rs @@ -66,6 +66,7 @@ pub enum Encoding { } /// A value that can be read from a file. +#[derive(Debug, Clone, PartialEq, Hash)] pub enum Readable { /// A decoded string. Str(Str), diff --git a/crates/typst-library/src/layout/align.rs b/crates/typst-library/src/layout/align.rs index f080f677b..9c18266db 100644 --- a/crates/typst-library/src/layout/align.rs +++ b/crates/typst-library/src/layout/align.rs @@ -38,6 +38,9 @@ pub struct AlignElem { impl Show for AlignElem { #[tracing::instrument(name = "AlignElem::show", skip_all)] fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(self.body().styled(Self::set_alignment(self.alignment(styles)))) + Ok(self + .body() + .clone() + .styled(Self::set_alignment(self.alignment(styles)))) } } diff --git a/crates/typst-library/src/layout/enum.rs b/crates/typst-library/src/layout/enum.rs index 436aacb91..8c491dcab 100644 --- a/crates/typst-library/src/layout/enum.rs +++ b/crates/typst-library/src/layout/enum.rs @@ -107,6 +107,7 @@ pub struct EnumElem { /// + Numbering! /// ``` #[default(Numbering::Pattern(NumberingPattern::from_str("1.").unwrap()))] + #[borrowed] pub numbering: Numbering, /// Which number to start the enumeration with. @@ -215,7 +216,7 @@ impl Layout for EnumElem { ParElem::leading_in(styles).into() } else { self.spacing(styles) - .unwrap_or_else(|| BlockElem::below_in(styles).amount()) + .unwrap_or_else(|| *BlockElem::below_in(styles).amount()) }; let mut cells = vec![]; @@ -238,7 +239,7 @@ impl Layout for EnumElem { parents.pop(); content } else { - match &numbering { + match numbering { Numbering::Pattern(pattern) => { TextElem::packed(pattern.apply_kth(parents.len(), number)) } @@ -254,7 +255,7 @@ impl Layout for EnumElem { cells.push(Content::empty()); cells.push(resolved); cells.push(Content::empty()); - cells.push(item.body().styled(Self::set_parents(Parent(number)))); + cells.push(item.body().clone().styled(Self::set_parents(Parent(number)))); number = number.saturating_add(1); } @@ -301,6 +302,7 @@ cast! { v: Content => v.to::().cloned().unwrap_or_else(|| Self::new(v.clone())), } +#[derive(Debug, Clone, Copy, PartialEq, Hash)] struct Parent(usize); cast! { diff --git a/crates/typst-library/src/layout/flow.rs b/crates/typst-library/src/layout/flow.rs index 1feee4b85..5c795c81c 100644 --- a/crates/typst-library/src/layout/flow.rs +++ b/crates/typst-library/src/layout/flow.rs @@ -1,5 +1,7 @@ use std::mem; +use comemo::Prehashed; + use super::{ AlignElem, BlockElem, ColbreakElem, ColumnsElem, ParElem, PlaceElem, Spacing, VElem, }; @@ -18,7 +20,7 @@ use crate::visualize::{ pub struct FlowElem { /// The children that will be arranges into a flow. #[variadic] - pub children: Vec, + pub children: Vec>, } impl Layout for FlowElem { @@ -37,7 +39,7 @@ impl Layout for FlowElem { } let mut layouter = FlowLayouter::new(regions, styles); - for mut child in &self.children() { + for mut child in self.children().iter().map(|c| &**c) { let outer = styles; let mut styles = styles; if let Some((elem, map)) = child.to_styled() { @@ -199,7 +201,7 @@ impl<'a> FlowLayouter<'a> { rel.resolve(styles).relative_to(self.initial.y), v.weakness(styles) > 0, ), - Spacing::Fr(fr) => FlowItem::Fractional(fr), + Spacing::Fr(fr) => FlowItem::Fractional(*fr), }, ) } @@ -701,7 +703,7 @@ fn find_footnotes(notes: &mut Vec, frame: &Frame) { match item { FrameItem::Group(group) => find_footnotes(notes, &group.frame), FrameItem::Meta(Meta::Elem(content), _) - if !notes.iter().any(|note| note.0.location() == content.location()) => + if !notes.iter().any(|note| note.location() == content.location()) => { let Some(footnote) = content.to::() else { continue }; notes.push(footnote.clone()); diff --git a/crates/typst-library/src/layout/grid.rs b/crates/typst-library/src/layout/grid.rs index 8d0157823..b29adeb9e 100644 --- a/crates/typst-library/src/layout/grid.rs +++ b/crates/typst-library/src/layout/grid.rs @@ -1,3 +1,5 @@ +use smallvec::{smallvec, SmallVec}; + use crate::prelude::*; use crate::text::TextElem; @@ -66,12 +68,14 @@ pub struct GridElem { /// with that many `{auto}`-sized columns. Note that opposed to rows and /// gutters, providing a single track size will only ever create a single /// column. + #[borrowed] pub columns: TrackSizings, /// The row sizes. /// /// If there are more cells than fit the defined rows, the last row is /// repeated until there are no more cells. + #[borrowed] pub rows: TrackSizings, /// The gaps between rows & columns. @@ -85,10 +89,12 @@ pub struct GridElem { let gutter = args.named("gutter")?; args.named("column-gutter")?.or_else(|| gutter.clone()) )] + #[borrowed] pub column_gutter: TrackSizings, /// The gaps between rows. Takes precedence over `gutter`. #[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))] + #[borrowed] pub row_gutter: TrackSizings, /// The contents of the grid cells. @@ -106,12 +112,16 @@ impl Layout for GridElem { styles: StyleChain, regions: Regions, ) -> SourceResult { + let columns = self.columns(styles); + let rows = self.rows(styles); + let column_gutter = self.column_gutter(styles); + let row_gutter = self.row_gutter(styles); + // Prepare grid layout by unifying content and gutter tracks. - let cells = self.children(); let layouter = GridLayouter::new( - Axes::new(&self.columns(styles).0, &self.rows(styles).0), - Axes::new(&self.column_gutter(styles).0, &self.row_gutter(styles).0), - &cells, + Axes::new(&columns.0, &rows.0), + Axes::new(&column_gutter.0, &row_gutter.0), + &self.children, regions, styles, self.span(), @@ -124,13 +134,13 @@ impl Layout for GridElem { /// Track sizing definitions. #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] -pub struct TrackSizings(pub Vec); +pub struct TrackSizings(pub SmallVec<[Sizing; 4]>); cast! { TrackSizings, self => self.0.into_value(), - sizing: Sizing => Self(vec![sizing]), - count: NonZeroUsize => Self(vec![Sizing::Auto; count.get()]), + sizing: Sizing => Self(smallvec![sizing]), + count: NonZeroUsize => Self(smallvec![Sizing::Auto; count.get()]), values: Array => Self(values.into_iter().map(Value::cast).collect::>()?), } diff --git a/crates/typst-library/src/layout/hide.rs b/crates/typst-library/src/layout/hide.rs index 7f17a7d73..af3d06311 100644 --- a/crates/typst-library/src/layout/hide.rs +++ b/crates/typst-library/src/layout/hide.rs @@ -1,3 +1,5 @@ +use smallvec::smallvec; + use crate::prelude::*; /// Hides content without affecting layout. @@ -22,6 +24,6 @@ pub struct HideElem { impl Show for HideElem { #[tracing::instrument(name = "HideElem::show", skip(self))] fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult { - Ok(self.body().styled(MetaElem::set_data(vec![Meta::Hide]))) + Ok(self.body().clone().styled(MetaElem::set_data(smallvec![Meta::Hide]))) } } diff --git a/crates/typst-library/src/layout/list.rs b/crates/typst-library/src/layout/list.rs index 34e0e6fbe..18c89a24b 100644 --- a/crates/typst-library/src/layout/list.rs +++ b/crates/typst-library/src/layout/list.rs @@ -77,6 +77,7 @@ pub struct ListElem { /// - Items /// - Items /// ``` + #[borrowed] #[default(ListMarker::Content(vec![TextElem::packed('•')]))] pub marker: ListMarker, @@ -133,7 +134,7 @@ impl Layout for ListElem { ParElem::leading_in(styles).into() } else { self.spacing(styles) - .unwrap_or_else(|| BlockElem::below_in(styles).amount()) + .unwrap_or_else(|| *BlockElem::below_in(styles).amount()) }; let depth = self.depth(styles); @@ -148,7 +149,7 @@ impl Layout for ListElem { cells.push(Content::empty()); cells.push(marker.clone()); cells.push(Content::empty()); - cells.push(item.body().styled(Self::set_depth(Depth))); + cells.push(item.body().clone().styled(Self::set_depth(Depth))); } let layouter = GridLayouter::new( @@ -183,7 +184,7 @@ cast! { } /// A list's marker. -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Clone, PartialEq, Hash)] pub enum ListMarker { Content(Vec), Func(Func), @@ -221,6 +222,7 @@ cast! { v: Func => Self::Func(v), } +#[derive(Debug, Clone, Copy, PartialEq, Hash)] struct Depth; cast! { diff --git a/crates/typst-library/src/layout/mod.rs b/crates/typst-library/src/layout/mod.rs index 1c7c26103..d31f68416 100644 --- a/crates/typst-library/src/layout/mod.rs +++ b/crates/typst-library/src/layout/mod.rs @@ -46,6 +46,7 @@ pub use self::table::*; pub use self::terms::*; pub use self::transform::*; +use std::borrow::Cow; use std::mem; use typed_arena::Arena; @@ -242,16 +243,16 @@ fn realize_root<'a>( scratch: &'a Scratch<'a>, content: &'a Content, styles: StyleChain<'a>, -) -> SourceResult<(Content, StyleChain<'a>)> { +) -> SourceResult<(Cow<'a, Content>, StyleChain<'a>)> { if content.can::() && !applicable(content, styles) { - return Ok((content.clone(), styles)); + return Ok((Cow::Borrowed(content), styles)); } let mut builder = Builder::new(vt, scratch, true); builder.accept(content, styles)?; builder.interrupt_page(Some(styles), true)?; let (pages, shared) = builder.doc.unwrap().pages.finish(); - Ok((DocumentElem::new(pages.to_vec()).pack(), shared)) + Ok((Cow::Owned(DocumentElem::new(pages.to_vec()).pack()), shared)) } /// Realize into an element that is capable of block-level layout. @@ -261,7 +262,7 @@ fn realize_block<'a>( scratch: &'a Scratch<'a>, content: &'a Content, styles: StyleChain<'a>, -) -> SourceResult<(Content, StyleChain<'a>)> { +) -> SourceResult<(Cow<'a, Content>, StyleChain<'a>)> { // These elements implement `Layout` but still require a flow for // proper layout. if content.can::() @@ -277,14 +278,14 @@ fn realize_block<'a>( && !content.is::() && !applicable(content, styles) { - return Ok((content.clone(), styles)); + return Ok((Cow::Borrowed(content), styles)); } let mut builder = Builder::new(vt, scratch, false); builder.accept(content, styles)?; builder.interrupt_par()?; let (children, shared) = builder.flow.0.finish(); - Ok((FlowElem::new(children.to_vec()).pack(), shared)) + Ok((Cow::Owned(FlowElem::new(children.to_vec()).pack()), shared)) } /// Builds a document or a flow element from content. @@ -509,7 +510,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { /// Accepts pagebreaks and pages. struct DocBuilder<'a> { /// The page runs built so far. - pages: StyleVecBuilder<'a, Content>, + pages: StyleVecBuilder<'a, Cow<'a, Content>>, /// Whether to keep a following page even if it is empty. keep_next: bool, /// Whether the next page should be cleared to an even or odd number. @@ -517,7 +518,7 @@ struct DocBuilder<'a> { } impl<'a> DocBuilder<'a> { - fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool { + fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { if let Some(pagebreak) = content.to::() { self.keep_next = !pagebreak.weak(styles); self.clear_next = pagebreak.to(styles); @@ -528,9 +529,9 @@ impl<'a> DocBuilder<'a> { let elem = if let Some(clear_to) = self.clear_next.take() { let mut page = page.clone(); page.push_clear_to(Some(clear_to)); - page.pack() + Cow::Owned(page.pack()) } else { - content.clone() + Cow::Borrowed(content) }; self.pages.push(elem, styles); @@ -571,7 +572,7 @@ impl<'a> FlowBuilder<'a> { || content.is::() || content.is::() { - self.0.push(content.clone(), styles); + self.0.push(Cow::Borrowed(content), styles); return true; } @@ -589,7 +590,7 @@ impl<'a> FlowBuilder<'a> { if !last_was_parbreak && is_tight_list { let leading = ParElem::leading_in(styles); let spacing = VElem::list_attach(leading.into()); - self.0.push(spacing.pack(), styles); + self.0.push(Cow::Owned(spacing.pack()), styles); } let (above, below) = if let Some(block) = content.to::() { @@ -598,9 +599,9 @@ impl<'a> FlowBuilder<'a> { (BlockElem::above_in(styles), BlockElem::below_in(styles)) }; - self.0.push(above.pack(), styles); - self.0.push(content.clone(), styles); - self.0.push(below.pack(), styles); + self.0.push(Cow::Owned(above.pack()), styles); + self.0.push(Cow::Borrowed(content), styles); + self.0.push(Cow::Owned(below.pack()), styles); return true; } @@ -616,7 +617,7 @@ impl<'a> ParBuilder<'a> { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { if content.is::() { if self.0.has_strong_elements(false) { - self.0.push(content.clone(), styles); + self.0.push(Cow::Borrowed(content), styles); return true; } } else if content.is::() @@ -627,7 +628,7 @@ impl<'a> ParBuilder<'a> { || content.to::().map_or(false, |elem| !elem.block(styles)) || content.is::() { - self.0.push(content.clone(), styles); + self.0.push(Cow::Borrowed(content), styles); return true; } @@ -643,7 +644,7 @@ impl<'a> ParBuilder<'a> { /// Accepts list / enum items, spaces, paragraph breaks. struct ListBuilder<'a> { /// The list items collected so far. - items: StyleVecBuilder<'a, Content>, + items: StyleVecBuilder<'a, Cow<'a, Content>>, /// Whether the list contains no paragraph breaks. tight: bool, /// Trailing content for which it is unclear whether it is part of the list. @@ -668,7 +669,7 @@ impl<'a> ListBuilder<'a> { .next() .map_or(true, |first| first.func() == content.func()) { - self.items.push(content.clone(), styles); + self.items.push(Cow::Borrowed(content), styles); self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::()); return true; } @@ -685,7 +686,8 @@ impl<'a> ListBuilder<'a> { .iter() .map(|(item, local)| { let item = item.to::().unwrap(); - item.clone().with_body(item.body().styled_with_map(local.clone())) + item.clone() + .with_body(item.body().clone().styled_with_map(local.clone())) }) .collect::>(), ) @@ -697,7 +699,8 @@ impl<'a> ListBuilder<'a> { .iter() .map(|(item, local)| { let item = item.to::().unwrap(); - item.clone().with_body(item.body().styled_with_map(local.clone())) + item.clone() + .with_body(item.body().clone().styled_with_map(local.clone())) }) .collect::>(), ) @@ -710,9 +713,9 @@ impl<'a> ListBuilder<'a> { .map(|(item, local)| { let item = item.to::().unwrap(); item.clone() - .with_term(item.term().styled_with_map(local.clone())) + .with_term(item.term().clone().styled_with_map(local.clone())) .with_description( - item.description().styled_with_map(local.clone()), + item.description().clone().styled_with_map(local.clone()), ) }) .collect::>(), diff --git a/crates/typst-library/src/layout/page.rs b/crates/typst-library/src/layout/page.rs index 495b390dc..5f8f90c14 100644 --- a/crates/typst-library/src/layout/page.rs +++ b/crates/typst-library/src/layout/page.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::ptr; use std::str::FromStr; @@ -172,6 +173,7 @@ pub struct PageElem { /// #set text(fill: rgb("fdfdfd")) /// *Dark mode enabled.* /// ``` + #[borrowed] pub fill: Option, /// How to [number]($numbering) the pages. @@ -188,6 +190,7 @@ pub struct PageElem { /// /// #lorem(48) /// ``` + #[borrowed] pub numbering: Option, /// The alignment of the page numbering. @@ -233,6 +236,7 @@ pub struct PageElem { /// /// #lorem(19) /// ``` + #[borrowed] pub header: Option, /// The amount the header is raised into the top margin. @@ -263,6 +267,7 @@ pub struct PageElem { /// /// #lorem(48) /// ``` + #[borrowed] pub footer: Option, /// The amount the footer is lowered into the bottom margin. @@ -286,6 +291,7 @@ pub struct PageElem { /// In the year 2023, we plan to take /// over the world (of typesetting). /// ``` + #[borrowed] pub background: Option, /// Content in the page's foreground. @@ -299,6 +305,7 @@ pub struct PageElem { /// "Weak Reject" because they did /// not understand our approach... /// ``` + #[borrowed] pub foreground: Option, /// The contents of the page(s). @@ -364,7 +371,7 @@ impl PageElem { }); // Realize columns. - let mut child = self.body(); + let mut child = self.body().clone(); let columns = self.columns(styles); if columns.get() > 1 { child = ColumnsElem::new(child).with_count(columns).pack(); @@ -388,25 +395,25 @@ impl PageElem { } let fill = self.fill(styles); - let foreground = self.foreground(styles); - let background = self.background(styles); + let foreground = Cow::Borrowed(self.foreground(styles)); + let background = Cow::Borrowed(self.background(styles)); let header_ascent = self.header_ascent(styles); let footer_descent = self.footer_descent(styles); let numbering = self.numbering(styles); let numbering_meta = Meta::PageNumbering(numbering.clone().into_value()); let number_align = self.number_align(styles); - let mut header = self.header(styles); - let mut footer = self.footer(styles); + let mut header = Cow::Borrowed(self.header(styles)); + let mut footer = Cow::Borrowed(self.footer(styles)); // Construct the numbering (for header or footer). - let numbering_marginal = numbering.clone().map(|numbering| { - let both = match &numbering { + let numbering_marginal = Cow::Owned(numbering.as_ref().map(|numbering| { + let both = match numbering { Numbering::Pattern(pattern) => pattern.pieces() >= 2, Numbering::Func(_) => true, }; let mut counter = - Counter::new(CounterKey::Page).display(Some(numbering), both); + Counter::new(CounterKey::Page).display(Some(numbering.clone()), both); // We interpret the Y alignment as selecting header or footer // and then ignore it for aligning the actual number. @@ -415,12 +422,12 @@ impl PageElem { } counter - }); + })); if matches!(number_align.y(), Some(VAlign::Top)) { - header = header.or(numbering_marginal); + header = if header.is_some() { header } else { numbering_marginal }; } else { - footer = footer.or(numbering_marginal); + footer = if footer.is_some() { footer } else { numbering_marginal }; } // Post-process pages. @@ -455,7 +462,7 @@ impl PageElem { ] { tracing::info!("Layouting {name}"); - let Some(content) = marginal else { continue }; + let Some(content) = &**marginal else { continue }; let (pos, area, align); if ptr::eq(marginal, &header) { @@ -488,14 +495,14 @@ impl PageElem { } } - if let Some(fill) = &fill { + if let Some(fill) = fill { frame.fill(fill.clone()); } page_counter.visit(vt, frame)?; // Add a PDF page label if there is a numbering. - if let Some(num) = &numbering { + if let Some(num) = numbering { if let Some(page_label) = num.apply_pdf(page_counter.logical()) { frame.push_positionless_meta(Meta::PdfPageLabel(page_label)); } @@ -657,10 +664,10 @@ pub enum Marginal { impl Marginal { /// Resolve the marginal based on the page number. - pub fn resolve(&self, vt: &mut Vt, page: usize) -> SourceResult { + pub fn resolve(&self, vt: &mut Vt, page: usize) -> SourceResult> { Ok(match self { - Self::Content(content) => content.clone(), - Self::Func(func) => func.call_vt(vt, [page])?.display(), + Self::Content(content) => Cow::Borrowed(content), + Self::Func(func) => Cow::Owned(func.call_vt(vt, [page])?.display()), }) } } diff --git a/crates/typst-library/src/layout/par.rs b/crates/typst-library/src/layout/par.rs index 10f78ef2a..b3d5fb3ed 100644 --- a/crates/typst-library/src/layout/par.rs +++ b/crates/typst-library/src/layout/par.rs @@ -1,3 +1,4 @@ +use comemo::Prehashed; use typst::eval::Tracer; use typst::model::DelayedErrors; use unicode_bidi::{BidiInfo, Level as BidiLevel}; @@ -104,7 +105,7 @@ pub struct ParElem { /// The paragraph's children. #[internal] #[variadic] - pub children: Vec, + pub children: Vec>, } impl Construct for ParElem { @@ -158,12 +159,12 @@ impl ParElem { let children = par.children(); // Collect all text into one string for BiDi analysis. - let (text, segments, spans) = collect(&children, &styles, consecutive)?; + let (text, segments, spans) = collect(children, &styles, consecutive)?; // Perform BiDi analysis and then prepare paragraph layout by building a // representation on which we can do line breaking without layouting // each and every line from scratch. - let p = prepare(&mut vt, &children, &text, segments, spans, styles, region)?; + let p = prepare(&mut vt, children, &text, segments, spans, styles, region)?; // Break the paragraph into lines. let lines = linebreak(&vt, &p, region.x - p.hang); @@ -246,8 +247,6 @@ pub(crate) struct Preparation<'a> { pub items: Vec>, /// The span mapper. pub spans: SpanMapper, - /// The styles shared by all children. - pub styles: StyleChain<'a>, /// Whether to hyphenate if it's the same for all children. pub hyphenate: Option, /// The text language if it's the same for all children. @@ -258,8 +257,16 @@ pub(crate) struct Preparation<'a> { pub justify: bool, /// The paragraph's hanging indent. pub hang: Abs, - /// The CJK-latin spacing. + /// Whether to add spacing between CJK and Latin characters. pub cjk_latin_spacing: bool, + /// Whether font fallback is enabled for this paragraph. + pub fallback: bool, + /// The leading of the paragraph. + pub leading: Abs, + /// How to determine line breaks. + pub linebreaks: Smart, + /// The text size. + pub size: Abs, } impl<'a> Preparation<'a> { @@ -525,15 +532,15 @@ impl<'a> Line<'a> { /// string-level preprocessing like case transformations. #[allow(clippy::type_complexity)] fn collect<'a>( - children: &'a [Content], + children: &'a [Prehashed], styles: &'a StyleChain<'a>, consecutive: bool, ) -> SourceResult<(String, Vec<(Segment<'a>, StyleChain<'a>)>, SpanMapper)> { let mut full = String::new(); let mut quoter = Quoter::new(); - let mut segments = vec![]; + let mut segments = Vec::with_capacity(2 + children.len()); let mut spans = SpanMapper::new(); - let mut iter = children.iter().peekable(); + let mut iter = children.iter().map(|c| &**c).peekable(); let first_line_indent = ParElem::first_line_indent_in(*styles); if !first_line_indent.is_zero() @@ -565,9 +572,9 @@ fn collect<'a>( } else if let Some(elem) = child.to::() { let prev = full.len(); if let Some(case) = TextElem::case_in(styles) { - full.push_str(&case.apply(&elem.text())); + full.push_str(&case.apply(elem.text())); } else { - full.push_str(&elem.text()); + full.push_str(elem.text()); } Segment::Text(full.len() - prev) } else if let Some(elem) = child.to::() { @@ -576,7 +583,7 @@ fn collect<'a>( } full.push(SPACING_REPLACE); - Segment::Spacing(elem.amount()) + Segment::Spacing(*elem.amount()) } else if let Some(elem) = child.to::() { let c = if elem.justify(styles) { '\u{2028}' } else { '\n' }; full.push(c); @@ -588,7 +595,7 @@ fn collect<'a>( let lang = TextElem::lang_in(styles); let region = TextElem::region_in(styles); let quotes = Quotes::new( - "es, + quotes, lang, region, SmartquoteElem::alternative_in(styles), @@ -656,7 +663,7 @@ fn collect<'a>( /// contained inline-level content. fn prepare<'a>( vt: &mut Vt, - children: &'a [Content], + children: &'a [Prehashed], text: &'a str, segments: Vec<(Segment<'a>, StyleChain<'a>)>, spans: SpanMapper, @@ -674,7 +681,7 @@ fn prepare<'a>( ); let mut cursor = 0; - let mut items = vec![]; + let mut items = Vec::with_capacity(segments.len()); // Shape / layout the children and collect them into items. for (segment, styles) in segments { @@ -727,13 +734,16 @@ fn prepare<'a>( bidi, items, spans, - styles, hyphenate: shared_get(styles, children, TextElem::hyphenate_in), lang: shared_get(styles, children, TextElem::lang_in), align: AlignElem::alignment_in(styles).resolve(styles).x, justify: ParElem::justify_in(styles), hang: ParElem::hanging_indent_in(styles), cjk_latin_spacing, + fallback: TextElem::fallback_in(styles), + leading: ParElem::leading_in(styles), + linebreaks: ParElem::linebreaks_in(styles), + size: TextElem::size_in(styles), }) } @@ -852,7 +862,7 @@ fn is_compatible(a: Script, b: Script) -> bool { /// paragraph. fn shared_get( styles: StyleChain<'_>, - children: &[Content], + children: &[Prehashed], getter: fn(StyleChain) -> T, ) -> Option { let value = getter(styles); @@ -865,8 +875,8 @@ fn shared_get( /// Find suitable linebreaks. fn linebreak<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec> { - let linebreaks = ParElem::linebreaks_in(p.styles).unwrap_or_else(|| { - if ParElem::justify_in(p.styles) { + let linebreaks = p.linebreaks.unwrap_or_else(|| { + if p.justify { Linebreaks::Optimized } else { Linebreaks::Simple @@ -883,7 +893,7 @@ fn linebreak<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec> { /// lines greedily, always taking the longest possible line. This may lead to /// very unbalanced line, but is fast and simple. fn linebreak_simple<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec> { - let mut lines = vec![]; + let mut lines = Vec::with_capacity(16); let mut start = 0; let mut last = None; @@ -964,8 +974,8 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec( if hyphen || start < range.end || before.is_empty() { let mut reshaped = shaped.reshape(vt, &p.spans, start..range.end); if hyphen || shy { - reshaped.push_hyphen(vt, TextElem::fallback_in(p.styles)); + reshaped.push_hyphen(vt, p.fallback); } if let Some(last_glyph) = reshaped.glyphs.last() { @@ -1287,11 +1296,10 @@ fn finalize( .collect::>()?; // Prevent orphans. - let leading = ParElem::leading_in(p.styles); if frames.len() >= 2 && !frames[1].is_empty() { let second = frames.remove(1); let first = &mut frames[0]; - merge(first, second, leading); + merge(first, second, p.leading); } // Prevent widows. @@ -1299,7 +1307,7 @@ fn finalize( if len >= 2 && !frames[len - 2].is_empty() { let second = frames.pop().unwrap(); let first = frames.last_mut().unwrap(); - merge(first, second, leading); + merge(first, second, p.leading); } Ok(Fragment::frames(frames)) diff --git a/crates/typst-library/src/layout/place.rs b/crates/typst-library/src/layout/place.rs index 64cbd9a88..c8e83383d 100644 --- a/crates/typst-library/src/layout/place.rs +++ b/crates/typst-library/src/layout/place.rs @@ -106,7 +106,10 @@ impl Layout for PlaceElem { .at(self.span()); } - let child = self.body().aligned(alignment.unwrap_or_else(|| Align::CENTER)); + let child = self + .body() + .clone() + .aligned(alignment.unwrap_or_else(|| Align::CENTER)); let pod = Regions::one(base, Axes::splat(false)); let frame = child.layout(vt, styles, pod)?.into_frame(); diff --git a/crates/typst-library/src/layout/spacing.rs b/crates/typst-library/src/layout/spacing.rs index 37f1ed355..88b6e2cdc 100644 --- a/crates/typst-library/src/layout/spacing.rs +++ b/crates/typst-library/src/layout/spacing.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use crate::prelude::*; /// Inserts horizontal spacing into a paragraph. @@ -71,7 +73,7 @@ impl Behave for HElem { fn larger( &self, - prev: &(Content, Behaviour, StyleChain), + prev: &(Cow, Behaviour, StyleChain), styles: StyleChain, ) -> bool { let Some(other) = prev.0.to::() else { return false }; @@ -173,7 +175,7 @@ impl Behave for VElem { fn larger( &self, - prev: &(Content, Behaviour, StyleChain), + prev: &(Cow, Behaviour, StyleChain), styles: StyleChain, ) -> bool { let Some(other) = prev.0.to::() else { return false }; diff --git a/crates/typst-library/src/layout/stack.rs b/crates/typst-library/src/layout/stack.rs index 398341b2f..50d1c862b 100644 --- a/crates/typst-library/src/layout/stack.rs +++ b/crates/typst-library/src/layout/stack.rs @@ -60,7 +60,7 @@ impl Layout for StackElem { for child in self.children() { match child { StackChild::Spacing(kind) => { - layouter.layout_spacing(kind); + layouter.layout_spacing(*kind); deferred = None; } StackChild::Block(block) => { @@ -68,7 +68,7 @@ impl Layout for StackElem { layouter.layout_spacing(kind); } - layouter.layout_block(vt, &block, styles)?; + layouter.layout_block(vt, block, styles)?; deferred = spacing; } } @@ -79,7 +79,7 @@ impl Layout for StackElem { } /// A child of a stack element. -#[derive(Hash)] +#[derive(Clone, PartialEq, Hash)] pub enum StackChild { /// Spacing between other children. Spacing(Spacing), diff --git a/crates/typst-library/src/layout/table.rs b/crates/typst-library/src/layout/table.rs index 056b63a21..9e7da071f 100644 --- a/crates/typst-library/src/layout/table.rs +++ b/crates/typst-library/src/layout/table.rs @@ -1,7 +1,7 @@ use typst::eval::{CastInfo, Reflect}; use crate::layout::{AlignElem, GridLayouter, TrackSizings}; -use crate::meta::{Figurable, LocalName}; +use crate::meta::Figurable; use crate::prelude::*; /// A table of items. @@ -38,10 +38,12 @@ use crate::prelude::*; pub struct TableElem { /// The column sizes. See the [grid documentation]($grid) for more /// information on track sizing. + #[borrowed] pub columns: TrackSizings, /// The row sizes. See the [grid documentation]($grid) for more information /// on track sizing. + #[borrowed] pub rows: TrackSizings, /// The gaps between rows & columns. See the [grid documentation]($grid) for @@ -51,6 +53,7 @@ pub struct TableElem { /// The gaps between columns. Takes precedence over `gutter`. See the /// [grid documentation]($grid) for more information on gutters. + #[borrowed] #[parse( let gutter = args.named("gutter")?; args.named("column-gutter")?.or_else(|| gutter.clone()) @@ -60,6 +63,7 @@ pub struct TableElem { /// The gaps between rows. Takes precedence over `gutter`. See the /// [grid documentation]($grid) for more information on gutters. #[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))] + #[borrowed] pub row_gutter: TrackSizings, /// How to fill the cells. @@ -82,6 +86,7 @@ pub struct TableElem { /// [Profit:], [500 €], [1000 €], [1500 €], /// ) /// ``` + #[borrowed] pub fill: Celled>, /// How to align the cells' content. @@ -99,6 +104,7 @@ pub struct TableElem { /// [A], [B], [C], /// ) /// ``` + #[borrowed] pub align: Celled>, /// How to [stroke]($stroke) the cells. @@ -151,16 +157,20 @@ impl Layout for TableElem { ) -> SourceResult { let inset = self.inset(styles); let align = self.align(styles); + let columns = self.columns(styles); + let rows = self.rows(styles); + let column_gutter = self.column_gutter(styles); + let row_gutter = self.row_gutter(styles); - let tracks = Axes::new(self.columns(styles).0, self.rows(styles).0); - let gutter = Axes::new(self.column_gutter(styles).0, self.row_gutter(styles).0); + let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice()); + let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice()); let cols = tracks.x.len().max(1); let cells: Vec<_> = self .children() - .into_iter() + .iter() .enumerate() .map(|(i, child)| { - let mut child = child.padded(inset); + let mut child = child.clone().padded(inset); let x = i % cols; let y = i / cols; @@ -176,14 +186,8 @@ impl Layout for TableElem { let stroke = self.stroke(styles).map(Stroke::unwrap_or_default); // Prepare grid layout by unifying content and gutter tracks. - let layouter = GridLayouter::new( - tracks.as_deref(), - gutter.as_deref(), - &cells, - regions, - styles, - self.span(), - ); + let layouter = + GridLayouter::new(tracks, gutter, &cells, regions, styles, self.span()); // Measure the columns and layout the grid row-by-row. let mut layout = layouter.layout(vt)?; @@ -321,7 +325,7 @@ impl FromValue for Celled { } impl LocalName for TableElem { - fn local_name(&self, lang: Lang, _: Option) -> &'static str { + fn local_name(lang: Lang, _: Option) -> &'static str { match lang { Lang::ALBANIAN => "Tabel", Lang::ARABIC => "جدول", diff --git a/crates/typst-library/src/layout/terms.rs b/crates/typst-library/src/layout/terms.rs index 07f17bb0f..d42621187 100644 --- a/crates/typst-library/src/layout/terms.rs +++ b/crates/typst-library/src/layout/terms.rs @@ -55,6 +55,7 @@ pub struct TermsElem { /// / Colon: A nice separator symbol. /// ``` #[default(HElem::new(Em::new(0.6).into()).with_weak(true).pack())] + #[borrowed] pub separator: Content, /// The indentation of each item. @@ -114,20 +115,20 @@ impl Layout for TermsElem { ParElem::leading_in(styles).into() } else { self.spacing(styles) - .unwrap_or_else(|| BlockElem::below_in(styles).amount()) + .unwrap_or_else(|| *BlockElem::below_in(styles).amount()) }; let mut seq = vec![]; - for (i, child) in self.children().into_iter().enumerate() { + for (i, child) in self.children().iter().enumerate() { if i > 0 { seq.push(VElem::new(gutter).with_weakness(1).pack()); } if !indent.is_zero() { seq.push(HElem::new(indent.into()).pack()); } - seq.push(child.term().strong()); - seq.push(separator.clone()); - seq.push(child.description()); + seq.push(child.term().clone().strong()); + seq.push((*separator).clone()); + seq.push(child.description().clone()); } Content::sequence(seq) diff --git a/crates/typst-library/src/math/accent.rs b/crates/typst-library/src/math/accent.rs index a542111b2..bedc56359 100644 --- a/crates/typst-library/src/math/accent.rs +++ b/crates/typst-library/src/math/accent.rs @@ -52,7 +52,7 @@ impl LayoutMath for AccentElem { #[tracing::instrument(skip(ctx))] fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { ctx.style(ctx.style.with_cramped(true)); - let base = ctx.layout_fragment(&self.base())?; + let base = ctx.layout_fragment(self.base())?; ctx.unstyle(); // Preserve class to preserve automatic spacing. @@ -67,7 +67,7 @@ impl LayoutMath for AccentElem { // Forcing the accent to be at least as large as the base makes it too // wide in many case. let Accent(c) = self.accent(); - let glyph = GlyphFragment::new(ctx, c, self.span()); + let glyph = GlyphFragment::new(ctx, *c, self.span()); let short_fall = ACCENT_SHORT_FALL.scaled(ctx); let variant = glyph.stretch_horizontal(ctx, base.width(), short_fall); let accent = variant.frame; @@ -116,6 +116,7 @@ fn attachment(ctx: &MathContext, id: GlyphId, italics_correction: Abs) -> Abs { } /// An accent character. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct Accent(char); impl Accent { @@ -130,7 +131,7 @@ cast! { self => self.0.into_value(), v: char => Self::new(v), v: Content => match v.to::() { - Some(elem) => Value::Str(elem.text().into()).cast()?, + Some(elem) => Value::Str(elem.text().clone().into()).cast()?, None => bail!("expected text"), }, } diff --git a/crates/typst-library/src/math/attach.rs b/crates/typst-library/src/math/attach.rs index 5d1e477eb..3e6b69f22 100644 --- a/crates/typst-library/src/math/attach.rs +++ b/crates/typst-library/src/math/attach.rs @@ -50,7 +50,7 @@ impl LayoutMath for AttachElem { .transpose() }; - let base = ctx.layout_fragment(&self.base())?; + let base = ctx.layout_fragment(self.base())?; ctx.style(ctx.style.for_superscript()); let tl = layout_attachment(ctx, Self::tl)?; @@ -91,7 +91,7 @@ pub struct PrimesElem { impl LayoutMath for PrimesElem { #[tracing::instrument(skip(ctx))] fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - match self.count() { + match *self.count() { count @ 1..=4 => { let f = ctx.layout_fragment(&TextElem::packed(match count { 1 => '′', @@ -137,7 +137,7 @@ pub struct ScriptsElem { impl LayoutMath for ScriptsElem { #[tracing::instrument(skip(ctx))] fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let mut fragment = ctx.layout_fragment(&self.body())?; + let mut fragment = ctx.layout_fragment(self.body())?; fragment.set_limits(Limits::Never); ctx.push(fragment); Ok(()) @@ -166,7 +166,7 @@ pub struct LimitsElem { impl LayoutMath for LimitsElem { #[tracing::instrument(skip(ctx))] fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let mut fragment = ctx.layout_fragment(&self.body())?; + let mut fragment = ctx.layout_fragment(self.body())?; fragment.set_limits(if self.inline(ctx.styles()) { Limits::Always } else { diff --git a/crates/typst-library/src/math/cancel.rs b/crates/typst-library/src/math/cancel.rs index d521149ed..16e4067a7 100644 --- a/crates/typst-library/src/math/cancel.rs +++ b/crates/typst-library/src/math/cancel.rs @@ -97,7 +97,7 @@ pub struct CancelElem { impl LayoutMath for CancelElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let body = ctx.layout_fragment(&self.body())?; + let body = ctx.layout_fragment(self.body())?; // Use the same math class as the body, in order to preserve automatic spacing around it. let body_class = body.class().unwrap_or(MathClass::Special); let mut body = body.into_frame(); @@ -146,6 +146,7 @@ impl LayoutMath for CancelElem { } /// Defines the cancel line. +#[derive(Debug, Clone, PartialEq, Hash)] pub enum CancelAngle { Angle(Angle), Func(Func), diff --git a/crates/typst-library/src/math/class.rs b/crates/typst-library/src/math/class.rs index fc8a6c798..d2c5192dd 100644 --- a/crates/typst-library/src/math/class.rs +++ b/crates/typst-library/src/math/class.rs @@ -27,11 +27,11 @@ pub struct ClassElem { impl LayoutMath for ClassElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.style(ctx.style.with_class(self.class())); - let mut fragment = ctx.layout_fragment(&self.body())?; + ctx.style(ctx.style.with_class(*self.class())); + let mut fragment = ctx.layout_fragment(self.body())?; ctx.unstyle(); - fragment.set_class(self.class()); + fragment.set_class(*self.class()); ctx.push(fragment); Ok(()) } diff --git a/crates/typst-library/src/math/ctx.rs b/crates/typst-library/src/math/ctx.rs index 59ba057fa..2637921ce 100644 --- a/crates/typst-library/src/math/ctx.rs +++ b/crates/typst-library/src/math/ctx.rs @@ -1,3 +1,4 @@ +use comemo::Prehashed; use ttf_parser::gsub::SubstitutionSubtable; use ttf_parser::math::MathValue; use typst::font::{FontStyle, FontWeight}; @@ -251,7 +252,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { // because it will be placed somewhere probably not at the left margin // it will overflow. So emulate an `hbox` instead and allow the paragraph // to extend as far as needed. - let frame = ParElem::new(vec![elem]) + let frame = ParElem::new(vec![Prehashed::new(elem)]) .layout( self.vt, self.outer.chain(&self.local), diff --git a/crates/typst-library/src/math/frac.rs b/crates/typst-library/src/math/frac.rs index ee5827751..bd8d86bf0 100644 --- a/crates/typst-library/src/math/frac.rs +++ b/crates/typst-library/src/math/frac.rs @@ -29,7 +29,7 @@ pub struct FracElem { impl LayoutMath for FracElem { #[tracing::instrument(skip(ctx))] fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - layout(ctx, &self.num(), &[self.denom()], false, self.span()) + layout(ctx, self.num(), std::slice::from_ref(self.denom()), false, self.span()) } } @@ -62,7 +62,7 @@ pub struct BinomElem { impl LayoutMath for BinomElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - layout(ctx, &self.upper(), &self.lower(), true, self.span()) + layout(ctx, self.upper(), self.lower(), true, self.span()) } } diff --git a/crates/typst-library/src/math/fragment.rs b/crates/typst-library/src/math/fragment.rs index 71d8d237b..a40ed2bfe 100644 --- a/crates/typst-library/src/math/fragment.rs +++ b/crates/typst-library/src/math/fragment.rs @@ -196,7 +196,7 @@ pub struct GlyphFragment { pub font_size: Abs, pub class: Option, pub span: Span, - pub meta: Vec, + pub meta: SmallVec<[Meta; 1]>, pub limits: Limits, } diff --git a/crates/typst-library/src/math/lr.rs b/crates/typst-library/src/math/lr.rs index 16696f2ef..391436206 100644 --- a/crates/typst-library/src/math/lr.rs +++ b/crates/typst-library/src/math/lr.rs @@ -37,7 +37,7 @@ impl LayoutMath for LrElem { } } - let mut fragments = ctx.layout_fragments(&body)?; + let mut fragments = ctx.layout_fragments(body)?; let axis = scaled!(ctx, axis_height); let max_extent = fragments .iter() diff --git a/crates/typst-library/src/math/matrix.rs b/crates/typst-library/src/math/matrix.rs index 333a57066..b54da5d60 100644 --- a/crates/typst-library/src/math/matrix.rs +++ b/crates/typst-library/src/math/matrix.rs @@ -49,7 +49,7 @@ impl LayoutMath for VecElem { let delim = self.delim(ctx.styles()); let frame = layout_vec_body( ctx, - &self.children(), + self.children(), FixedAlign::Center, self.gap(ctx.styles()), )?; @@ -233,7 +233,7 @@ impl LayoutMath for MatElem { let delim = self.delim(ctx.styles()); let frame = layout_mat_body( ctx, - &rows, + rows, augment, Axes::new(self.column_gap(ctx.styles()), self.row_gap(ctx.styles())), self.span(), @@ -303,7 +303,7 @@ impl LayoutMath for CasesElem { let delim = self.delim(ctx.styles()); let frame = layout_vec_body( ctx, - &self.children(), + self.children(), FixedAlign::Start, self.gap(ctx.styles()), )?; @@ -562,7 +562,7 @@ fn layout_delimiters( /// Parameters specifying how augmentation lines /// should be drawn on a matrix. -#[derive(Default, Clone, Hash)] +#[derive(Debug, Default, Clone, PartialEq, Hash)] pub struct Augment { pub hline: Offsets, pub vline: Offsets, @@ -573,7 +573,7 @@ impl Augment { fn stroke_or(&self, fallback: FixedStroke) -> FixedStroke { match &self.stroke { Smart::Custom(v) => v.clone().unwrap_or(fallback), - _ => fallback, + Smart::Auto => fallback, } } } @@ -594,7 +594,13 @@ impl Fold for Augment { type Output = Augment; fn fold(mut self, outer: Self::Output) -> Self::Output { - self.stroke = self.stroke.fold(outer.stroke); + // Special case for handling `auto` strokes in subsequent `Augment`. + if self.stroke.is_auto() && outer.stroke.is_custom() { + self.stroke = outer.stroke; + } else { + self.stroke = self.stroke.fold(outer.stroke); + } + self } } @@ -602,19 +608,22 @@ impl Fold for Augment { cast! { Augment, self => { - let stroke = self.stroke.unwrap_or_default(); + // if the stroke is auto and there is only one vertical line, + if self.stroke.is_auto() && self.hline.0.is_empty() && self.vline.0.len() == 1 { + return self.vline.0[0].into_value(); + } let d = dict! { "hline" => self.hline.into_value(), "vline" => self.vline.into_value(), - "stroke" => stroke.into_value() + "stroke" => self.stroke.into_value() }; d.into_value() }, v: isize => Augment { hline: Offsets::default(), - vline: Offsets(vec![v]), + vline: Offsets(smallvec![v]), stroke: Smart::Auto, }, mut dict: Dict => { @@ -636,14 +645,13 @@ cast! { self => self.into_value(), } -/// The offsets at which augmentation lines -/// should be drawn on a matrix. -#[derive(Debug, Default, Clone, Hash)] -pub struct Offsets(Vec); +/// The offsets at which augmentation lines should be drawn on a matrix. +#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] +pub struct Offsets(SmallVec<[isize; 1]>); cast! { Offsets, self => self.0.into_value(), - v: isize => Self(vec![v]), + v: isize => Self(smallvec![v]), v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), } diff --git a/crates/typst-library/src/math/mod.rs b/crates/typst-library/src/math/mod.rs index 26ab10c6e..a4d44d000 100644 --- a/crates/typst-library/src/math/mod.rs +++ b/crates/typst-library/src/math/mod.rs @@ -32,6 +32,8 @@ pub use self::root::*; pub use self::style::*; pub use self::underover::*; +use std::borrow::Cow; + use ttf_parser::{GlyphId, Rect}; use typst::eval::{Module, Scope}; use typst::font::{Font, FontWeight}; @@ -46,7 +48,7 @@ use self::spacing::*; use crate::layout::{AlignElem, BoxElem, HElem, ParElem, Spacing}; use crate::meta::Supplement; use crate::meta::{ - Count, Counter, CounterUpdate, LocalName, Numbering, Outlinable, Refable, + Count, Counter, CounterUpdate, LocalNameIn, Numbering, Outlinable, Refable, }; use crate::prelude::*; use crate::shared::BehavedBuilder; @@ -182,7 +184,7 @@ impl Synthesize for EquationElem { fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> { // Resolve the supplement. let supplement = match self.supplement(styles) { - Smart::Auto => TextElem::packed(self.local_name_in(styles)), + Smart::Auto => TextElem::packed(Self::local_name_in(styles)), Smart::Custom(None) => Content::empty(), Smart::Custom(Some(supplement)) => supplement.resolve(vt, [self.clone()])?, }; @@ -236,7 +238,7 @@ impl Layout for EquationElem { let variant = variant(styles); let world = vt.world; let Some(font) = families(styles).find_map(|family| { - let id = world.book().select(family.as_str(), variant)?; + let id = world.book().select(family, variant)?; let font = world.font(id)?; let _ = font.ttf().tables().math?.constants?; Some(font) @@ -312,7 +314,7 @@ impl Count for EquationElem { } impl LocalName for EquationElem { - fn local_name(&self, lang: Lang, region: Option) -> &'static str { + fn local_name(lang: Lang, region: Option) -> &'static str { match lang { Lang::ALBANIAN => "Ekuacion", Lang::ARABIC => "معادلة", @@ -381,7 +383,7 @@ impl Outlinable for EquationElem { let numbers = self .counter() - .at(vt, self.0.location().unwrap())? + .at(vt, self.location().unwrap())? .display(vt, &numbering)?; Ok(Some(supplement + numbers)) @@ -419,7 +421,7 @@ impl LayoutMath for Content { if self.is_sequence() { let mut bb = BehavedBuilder::new(); self.sequence_recursive_for_each(&mut |child: &Content| { - bb.push(child.clone(), StyleChain::default()) + bb.push(Cow::Owned(child.clone()), StyleChain::default()) }); for (child, _) in bb.finish().0.iter() { diff --git a/crates/typst-library/src/math/op.rs b/crates/typst-library/src/math/op.rs index 152efdf51..b4c360023 100644 --- a/crates/typst-library/src/math/op.rs +++ b/crates/typst-library/src/math/op.rs @@ -32,7 +32,7 @@ impl LayoutMath for OpElem { #[tracing::instrument(skip(ctx))] fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { let fragment = - ctx.layout_text(&TextElem::new(self.text()).spanned(self.span()))?; + ctx.layout_text(&TextElem::new(self.text().clone()).spanned(self.span()))?; ctx.push( FrameFragment::new(ctx, fragment.into_frame()) .with_class(MathClass::Large) diff --git a/crates/typst-library/src/math/root.rs b/crates/typst-library/src/math/root.rs index 49ceb75f0..13c5c1478 100644 --- a/crates/typst-library/src/math/root.rs +++ b/crates/typst-library/src/math/root.rs @@ -32,7 +32,7 @@ pub struct RootElem { impl LayoutMath for RootElem { #[tracing::instrument(skip(ctx))] fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - layout(ctx, self.index(ctx.styles()).as_ref(), &self.radicand(), self.span()) + layout(ctx, self.index(ctx.styles()).as_ref(), self.radicand(), self.span()) } } diff --git a/crates/typst-library/src/math/style.rs b/crates/typst-library/src/math/style.rs index 4d80a235d..774fadaca 100644 --- a/crates/typst-library/src/math/style.rs +++ b/crates/typst-library/src/math/style.rs @@ -344,7 +344,7 @@ impl MathStyle { /// The size of elements in an equation. /// /// See the TeXbook p. 141. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Cast)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Cast, Hash)] pub enum MathSize { /// Second-level sub- and superscripts. ScriptScript, @@ -367,7 +367,7 @@ impl MathSize { } /// A mathematical style variant, as defined by Unicode. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Cast)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Cast, Hash)] pub enum MathVariant { Serif, Sans, diff --git a/crates/typst-library/src/math/underover.rs b/crates/typst-library/src/math/underover.rs index 5d010c288..aeb83061f 100644 --- a/crates/typst-library/src/math/underover.rs +++ b/crates/typst-library/src/math/underover.rs @@ -24,7 +24,7 @@ pub struct UnderlineElem { impl LayoutMath for UnderlineElem { #[tracing::instrument(skip(ctx))] fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - layout_underoverline(ctx, &self.body(), self.span(), LineKind::Under) + layout_underoverline(ctx, self.body(), self.span(), LineKind::Under) } } @@ -43,7 +43,7 @@ pub struct OverlineElem { impl LayoutMath for OverlineElem { #[tracing::instrument(skip(ctx))] fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - layout_underoverline(ctx, &self.body(), self.span(), LineKind::Over) + layout_underoverline(ctx, self.body(), self.span(), LineKind::Over) } } @@ -130,7 +130,7 @@ impl LayoutMath for UnderbraceElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { layout_underoverspreader( ctx, - &self.body(), + self.body(), &self.annotation(ctx.styles()), '⏟', BRACE_GAP, @@ -161,7 +161,7 @@ impl LayoutMath for OverbraceElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { layout_underoverspreader( ctx, - &self.body(), + self.body(), &self.annotation(ctx.styles()), '⏞', BRACE_GAP, @@ -192,7 +192,7 @@ impl LayoutMath for UnderbracketElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { layout_underoverspreader( ctx, - &self.body(), + self.body(), &self.annotation(ctx.styles()), '⎵', BRACKET_GAP, @@ -223,7 +223,7 @@ impl LayoutMath for OverbracketElem { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { layout_underoverspreader( ctx, - &self.body(), + self.body(), &self.annotation(ctx.styles()), '⎴', BRACKET_GAP, diff --git a/crates/typst-library/src/meta/bibliography.rs b/crates/typst-library/src/meta/bibliography.rs index d429d3436..342135ecd 100644 --- a/crates/typst-library/src/meta/bibliography.rs +++ b/crates/typst-library/src/meta/bibliography.rs @@ -19,13 +19,13 @@ use typed_arena::Arena; use typst::diag::FileError; use typst::eval::{eval_string, Bytes, CastInfo, EvalMode, Reflect}; use typst::font::FontStyle; -use typst::util::option_eq; +use typst::util::{option_eq, PicoStr}; use super::{CitationForm, CiteGroup, LocalName}; use crate::layout::{ BlockElem, GridElem, HElem, PadElem, ParElem, Sizing, TrackSizings, VElem, }; -use crate::meta::{FootnoteElem, HeadingElem}; +use crate::meta::{FootnoteElem, HeadingElem, LocalNameIn}; use crate::prelude::*; use crate::text::{Delta, SubElem, SuperElem, TextElem}; @@ -130,7 +130,7 @@ pub struct BibliographyElem { } /// A list of bibliography file paths. -#[derive(Debug, Default, Clone, Hash)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] pub struct BibPaths(Vec); cast! { @@ -153,11 +153,12 @@ impl BibliographyElem { bail!("multiple bibliographies are not yet supported"); } - Ok(elem.to::().unwrap().clone()) + Ok(elem.to::().cloned().unwrap()) } /// Whether the bibliography contains the given key. - pub fn has(vt: &Vt, key: &str) -> bool { + pub fn has(vt: &Vt, key: impl Into) -> bool { + let key = key.into(); vt.introspector .query(&Self::elem().select()) .iter() @@ -199,14 +200,9 @@ impl Show for BibliographyElem { let mut seq = vec![]; if let Some(title) = self.title(styles) { - let title = - title.unwrap_or_else(|| { - TextElem::packed(self.local_name( - TextElem::lang_in(styles), - TextElem::region_in(styles), - )) - .spanned(self.span()) - }); + let title = title.unwrap_or_else(|| { + TextElem::packed(Self::local_name_in(styles)).spanned(self.span()) + }); seq.push(HeadingElem::new(title).with_level(NonZeroUsize::ONE).pack()); } @@ -220,7 +216,7 @@ impl Show for BibliographyElem { .ok_or("CSL style is not suitable for bibliographies") .at(span)?; - let row_gutter = BlockElem::below_in(styles).amount(); + let row_gutter = *BlockElem::below_in(styles).amount(); if references.iter().any(|(prefix, _)| prefix.is_some()) { let mut cells = vec![]; for (prefix, reference) in references { @@ -231,9 +227,9 @@ impl Show for BibliographyElem { seq.push(VElem::new(row_gutter).with_weakness(3).pack()); seq.push( GridElem::new(cells) - .with_columns(TrackSizings(vec![Sizing::Auto; 2])) - .with_column_gutter(TrackSizings(vec![COLUMN_GUTTER.into()])) - .with_row_gutter(TrackSizings(vec![row_gutter.into()])) + .with_columns(TrackSizings(smallvec![Sizing::Auto; 2])) + .with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()])) + .with_row_gutter(TrackSizings(smallvec![(row_gutter).into()])) .pack(), ); } else { @@ -263,7 +259,7 @@ impl Finalize for BibliographyElem { } impl LocalName for BibliographyElem { - fn local_name(&self, lang: Lang, region: Option) -> &'static str { + fn local_name(lang: Lang, region: Option) -> &'static str { match lang { Lang::ALBANIAN => "Bibliografi", Lang::ARABIC => "المراجع", @@ -300,7 +296,7 @@ impl LocalName for BibliographyElem { #[ty] #[derive(Debug, Clone, PartialEq)] pub struct Bibliography { - map: Arc>, + map: Arc>, hash: u128, } @@ -371,8 +367,8 @@ impl Bibliography { }) } - fn has(&self, key: &str) -> bool { - self.map.contains_key(key) + fn has(&self, key: impl Into) -> bool { + self.map.contains_key(&key.into()) } fn entries(&self) -> impl Iterator { @@ -645,7 +641,7 @@ impl<'a> Generator<'a> { let mut driver = BibliographyDriver::new(); for elem in &self.groups { let group = elem.to::().unwrap(); - let location = group.0.location().unwrap(); + let location = group.location().unwrap(); let children = group.children(); // Groups should never be empty. @@ -657,12 +653,13 @@ impl<'a> Generator<'a> { let mut normal = true; // Create infos and items for each child in the group. - for child in &children { - let key = child.key(); - let Some(entry) = database.map.get(&key.0) else { + for child in children { + let key = *child.key(); + let Some(entry) = database.map.get(&key.into_inner()) else { errors.push(error!( child.span(), - "key `{}` does not exist in the bibliography", key.0 + "key `{}` does not exist in the bibliography", + key.as_str() )); continue; }; @@ -714,13 +711,13 @@ impl<'a> Generator<'a> { driver.citation(CitationRequest::new( items, style, - Some(locale(first.lang(), first.region())), + Some(locale(*first.lang(), *first.region())), &LOCALES, None, )); } - let locale = locale(self.bibliography.lang(), self.bibliography.region()); + let locale = locale(*self.bibliography.lang(), *self.bibliography.region()); // Add hidden items for everything if we should print the whole // bibliography. @@ -761,7 +758,7 @@ impl<'a> Generator<'a> { // so that we can link there. let mut links = HashMap::new(); if let Some(bibliography) = &rendered.bibliography { - let location = self.bibliography.0.location().unwrap(); + let location = self.bibliography.location().unwrap(); for (k, item) in bibliography.items.iter().enumerate() { links.insert(item.key.as_str(), location.variant(k + 1)); } @@ -770,8 +767,7 @@ impl<'a> Generator<'a> { let mut output = std::mem::take(&mut self.failures); for (info, citation) in self.infos.iter().zip(&rendered.citations) { let supplement = |i: usize| info.subinfos.get(i)?.supplement.clone(); - let link = - |i: usize| links.get(info.subinfos.get(i)?.key.0.as_str()).copied(); + let link = |i: usize| links.get(info.subinfos.get(i)?.key.as_str()).copied(); let renderer = ElemRenderer { world: self.world, @@ -811,13 +807,13 @@ impl<'a> Generator<'a> { let mut first_occurances = HashMap::new(); for info in &self.infos { for subinfo in &info.subinfos { - let key = subinfo.key.0.as_str(); + let key = subinfo.key.as_str(); first_occurances.entry(key).or_insert(info.location); } } // The location of the bibliography. - let location = self.bibliography.0.location().unwrap(); + let location = self.bibliography.location().unwrap(); let mut output = vec![]; for (k, item) in rendered.items.iter().enumerate() { @@ -918,8 +914,8 @@ impl ElemRenderer<'_> { if let Some(prefix) = suf_prefix { const COLUMN_GUTTER: Em = Em::new(0.65); content = GridElem::new(vec![prefix, content]) - .with_columns(TrackSizings(vec![Sizing::Auto; 2])) - .with_column_gutter(TrackSizings(vec![COLUMN_GUTTER.into()])) + .with_columns(TrackSizings(smallvec![Sizing::Auto; 2])) + .with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()])) .pack(); } diff --git a/crates/typst-library/src/meta/cite.rs b/crates/typst-library/src/meta/cite.rs index 2e3c41552..a61523d81 100644 --- a/crates/typst-library/src/meta/cite.rs +++ b/crates/typst-library/src/meta/cite.rs @@ -141,7 +141,7 @@ impl Show for CiteGroup { #[tracing::instrument(name = "CiteGroup::show", skip(self, vt))] fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult { Ok(vt.delayed(|vt| { - let location = self.0.location().unwrap(); + let location = self.location().unwrap(); let span = self.span(); Works::generate(vt.world, vt.introspector) .at(span)? diff --git a/crates/typst-library/src/meta/context.rs b/crates/typst-library/src/meta/context.rs index 3a82a9253..59b35577e 100644 --- a/crates/typst-library/src/meta/context.rs +++ b/crates/typst-library/src/meta/context.rs @@ -37,7 +37,7 @@ impl Show for LocateElem { #[tracing::instrument(name = "LocateElem::show", skip(self, vt))] fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult { Ok(vt.delayed(|vt| { - let location = self.0.location().unwrap(); + let location = self.location().unwrap(); Ok(self.func().call_vt(vt, [location])?.display()) })) } diff --git a/crates/typst-library/src/meta/counter.rs b/crates/typst-library/src/meta/counter.rs index 1ad0bff1c..45041a38b 100644 --- a/crates/typst-library/src/meta/counter.rs +++ b/crates/typst-library/src/meta/counter.rs @@ -284,9 +284,9 @@ impl Counter { } if let Some(update) = match elem.to::() { - Some(elem) => Some(elem.update()), + Some(elem) => Some(elem.update().clone()), None => match elem.with::() { - Some(countable) => countable.update(), + Some(countable) => countable.update().clone(), None => Some(CounterUpdate::Step(NonZeroUsize::ONE)), }, } { @@ -301,8 +301,7 @@ impl Counter { /// The selector relevant for this counter's updates. fn selector(&self) -> Selector { - let mut selector = - Selector::Elem(UpdateElem::elem(), Some(dict! { "key" => self.0.clone() })); + let mut selector = select_where!(UpdateElem, Key => self.0.clone()); if let CounterKey::Selector(key) = &self.0 { selector = Selector::Or(eco_vec![selector, key.clone()]); @@ -613,17 +612,18 @@ impl Show for DisplayElem { #[tracing::instrument(name = "DisplayElem::show", skip_all)] fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { Ok(vt.delayed(|vt| { - let location = self.0.location().unwrap(); + let location = self.location().unwrap(); let counter = self.counter(); let numbering = self .numbering() + .clone() .or_else(|| { let CounterKey::Selector(Selector::Elem(func, _)) = counter.0 else { return None; }; if func == HeadingElem::elem() { - HeadingElem::numbering_in(styles) + HeadingElem::numbering_in(styles).clone() } else if func == FigureElem::elem() { FigureElem::numbering_in(styles) } else if func == EquationElem::elem() { @@ -634,7 +634,7 @@ impl Show for DisplayElem { }) .unwrap_or_else(|| NumberingPattern::from_str("1.1").unwrap().into()); - let state = if self.both() { + let state = if *self.both() { counter.both(vt, location)? } else { counter.at(vt, location)? @@ -695,9 +695,9 @@ impl ManualPageCounter { FrameItem::Group(group) => self.visit(vt, &group.frame)?, FrameItem::Meta(Meta::Elem(elem), _) => { let Some(elem) = elem.to::() else { continue }; - if elem.key() == CounterKey::Page { + if *elem.key() == CounterKey::Page { let mut state = CounterState(smallvec![self.logical]); - state.update(vt, elem.update())?; + state.update(vt, elem.update().clone())?; self.logical = state.first(); } } diff --git a/crates/typst-library/src/meta/document.rs b/crates/typst-library/src/meta/document.rs index 55c365ac7..f6ff6812e 100644 --- a/crates/typst-library/src/meta/document.rs +++ b/crates/typst-library/src/meta/document.rs @@ -1,3 +1,4 @@ +use comemo::Prehashed; use typst::eval::Datetime; use crate::layout::{LayoutRoot, PageElem}; @@ -43,9 +44,8 @@ pub struct DocumentElem { pub date: Smart>, /// The page runs. - #[internal] #[variadic] - pub children: Vec, + pub children: Vec>, } impl Construct for DocumentElem { @@ -64,7 +64,7 @@ impl LayoutRoot for DocumentElem { let mut page_counter = ManualPageCounter::new(); let children = self.children(); - let mut iter = children.iter().peekable(); + let mut iter = children.iter().map(|c| &**c).peekable(); while let Some(mut child) = iter.next() { let outer = styles; @@ -99,7 +99,7 @@ impl LayoutRoot for DocumentElem { } /// A list of authors. -#[derive(Debug, Default, Clone, Hash)] +#[derive(Debug, Default, Clone, PartialEq, Hash)] pub struct Author(Vec); cast! { @@ -110,7 +110,7 @@ cast! { } /// A list of keywords. -#[derive(Debug, Default, Clone, Hash)] +#[derive(Debug, Default, Clone, PartialEq, Hash)] pub struct Keywords(Vec); cast! { diff --git a/crates/typst-library/src/meta/figure.rs b/crates/typst-library/src/meta/figure.rs index 990c9860e..bfcc9b449 100644 --- a/crates/typst-library/src/meta/figure.rs +++ b/crates/typst-library/src/meta/figure.rs @@ -1,9 +1,8 @@ +use std::borrow::Cow; use std::str::FromStr; use typst::util::option_eq; -use super::{ - Count, Counter, CounterKey, CounterUpdate, LocalName, Numbering, NumberingPattern, -}; +use super::{Count, Counter, CounterKey, CounterUpdate, Numbering, NumberingPattern}; use crate::layout::{BlockElem, PlaceElem, VElem}; use crate::meta::{Outlinable, Refable, Supplement}; use crate::prelude::*; @@ -170,6 +169,7 @@ pub struct FigureElem { /// kind: "foo", /// ) /// ``` + #[borrowed] pub supplement: Smart>, /// How to number the figure. Accepts a @@ -212,25 +212,21 @@ impl Synthesize for FigureElem { let kind = self.kind(styles).unwrap_or_else(|| { self.body() .query_first(Selector::can::()) - .cloned() .map(|elem| FigureKind::Elem(elem.func())) .unwrap_or_else(|| FigureKind::Elem(ImageElem::elem())) }); // Resolve the supplement. - let supplement = match self.supplement(styles) { + let supplement = match self.supplement(styles).as_ref() { Smart::Auto => { // Default to the local name for the kind, if available. let name = match &kind { - FigureKind::Elem(func) => { - let empty = Content::new(*func); - empty.with::().map(|c| { - TextElem::packed(c.local_name( - TextElem::lang_in(styles), - TextElem::region_in(styles), - )) - }) - } + FigureKind::Elem(func) => func + .local_name( + TextElem::lang_in(styles), + TextElem::region_in(styles), + ) + .map(TextElem::packed), FigureKind::Name(_) => None, }; @@ -245,24 +241,21 @@ impl Synthesize for FigureElem { // Resolve the supplement with the first descendant of the kind or // just the body, if none was found. let descendant = match kind { - FigureKind::Elem(func) => { - self.body().query_first(Selector::Elem(func, None)).cloned() - } + FigureKind::Elem(func) => self + .body() + .query_first(Selector::Elem(func, None)) + .map(Cow::Owned), FigureKind::Name(_) => None, }; - let target = descendant.unwrap_or_else(|| self.body()); + let target = descendant.unwrap_or_else(|| Cow::Borrowed(self.body())); Some(supplement.resolve(vt, [target])?) } }; // Construct the figure's counter. - let counter = Counter::new(CounterKey::Selector(Selector::Elem( - Self::elem(), - Some(dict! { - "kind" => kind.clone(), - }), - ))); + let counter = + Counter::new(CounterKey::Selector(select_where!(Self, Kind => kind.clone()))); // Fill the figure's caption. let mut caption = self.caption(styles); @@ -271,7 +264,7 @@ impl Synthesize for FigureElem { caption.push_supplement(supplement.clone()); caption.push_numbering(numbering.clone()); caption.push_counter(Some(counter.clone())); - caption.push_location(self.0.location()); + caption.push_figure_location(self.location()); } self.push_placement(self.placement(styles)); @@ -289,7 +282,7 @@ impl Synthesize for FigureElem { impl Show for FigureElem { #[tracing::instrument(name = "FigureElem::show", skip_all)] fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - let mut realized = self.body(); + let mut realized = self.body().clone(); // Build the caption, if any. if let Some(caption) = self.caption(styles) { @@ -339,14 +332,15 @@ impl Count for FigureElem { impl Refable for FigureElem { fn supplement(&self) -> Content { // After synthesis, this should always be custom content. - match self.supplement(StyleChain::default()) { - Smart::Custom(Some(Supplement::Content(content))) => content, + let default = StyleChain::default(); + match self.supplement(default).as_ref() { + Smart::Custom(Some(Supplement::Content(content))) => content.clone(), _ => Content::empty(), } } fn counter(&self) -> Counter { - self.counter().unwrap_or_else(|| Counter::of(Self::elem())) + self.counter().clone().unwrap_or_else(|| Counter::of(Self::elem())) } fn numbering(&self) -> Option { @@ -364,17 +358,17 @@ impl Outlinable for FigureElem { return Ok(None); }; - let mut realized = caption.body(); + let mut realized = caption.body().clone(); if let ( Smart::Custom(Some(Supplement::Content(mut supplement))), Some(counter), Some(numbering), ) = ( - self.supplement(StyleChain::default()), + self.supplement(StyleChain::default()).clone(), self.counter(), self.numbering(StyleChain::default()), ) { - let location = self.0.location().unwrap(); + let location = self.location().unwrap(); let numbers = counter.at(vt, location)?.display(vt, &numbering)?; if !supplement.is_empty() { @@ -497,8 +491,9 @@ pub struct FigureCaption { pub counter: Option, /// The figure's location. + #[internal] #[synthesized] - pub location: Option, + pub figure_location: Option, } impl FigureCaption { @@ -542,12 +537,15 @@ impl Synthesize for FigureCaption { impl Show for FigureCaption { #[tracing::instrument(name = "FigureCaption::show", skip_all)] fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { - let mut realized = self.body(); + let mut realized = self.body().clone(); - if let (Some(mut supplement), Some(numbering), Some(counter), Some(location)) = - (self.supplement(), self.numbering(), self.counter(), self.location()) - { - let numbers = counter.at(vt, location)?.display(vt, &numbering)?; + if let (Some(mut supplement), Some(numbering), Some(counter), Some(location)) = ( + self.supplement().clone(), + self.numbering(), + self.counter(), + self.figure_location(), + ) { + let numbers = counter.at(vt, *location)?.display(vt, numbering)?; if !supplement.is_empty() { supplement += TextElem::packed('\u{a0}'); } @@ -564,7 +562,7 @@ cast! { } /// The `kind` parameter of a [`FigureElem`]. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Hash)] pub enum FigureKind { /// The kind is an element function. Elem(Element), @@ -585,4 +583,4 @@ cast! { /// An element that can be auto-detected in a figure. /// /// This trait is used to determine the type of a figure. -pub trait Figurable: LocalName {} +pub trait Figurable {} diff --git a/crates/typst-library/src/meta/footnote.rs b/crates/typst-library/src/meta/footnote.rs index a8f0b4dd0..4306f8333 100644 --- a/crates/typst-library/src/meta/footnote.rs +++ b/crates/typst-library/src/meta/footnote.rs @@ -59,6 +59,7 @@ pub struct FootnoteElem { /// #footnote[Star], /// #footnote[Dagger] /// ``` + #[borrowed] #[default(Numbering::Pattern(NumberingPattern::from_str("1").unwrap()))] pub numbering: Numbering, @@ -91,7 +92,7 @@ impl FootnoteElem { } /// Returns the content of the body of this footnote if it is not a ref. - pub fn body_content(&self) -> Option { + pub fn body_content(&self) -> Option<&Content> { match self.body() { FootnoteBody::Content(content) => Some(content), _ => None, @@ -102,20 +103,20 @@ impl FootnoteElem { pub fn declaration_location(&self, vt: &Vt) -> StrResult { match self.body() { FootnoteBody::Reference(label) => { - let element: Prehashed = vt.introspector.query_label(&label)?; + let element: Prehashed = vt.introspector.query_label(*label)?; let footnote = element .to::() .ok_or("referenced element should be a footnote")?; footnote.declaration_location(vt) } - _ => Ok(self.0.location().unwrap()), + _ => Ok(self.location().unwrap()), } } } impl Synthesize for FootnoteElem { fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> { - self.push_numbering(self.numbering(styles)); + self.push_numbering(self.numbering(styles).clone()); Ok(()) } } @@ -127,7 +128,7 @@ impl Show for FootnoteElem { let loc = self.declaration_location(vt).at(self.span())?; let numbering = self.numbering(styles); let counter = Counter::of(Self::elem()); - let num = counter.at(vt, loc)?.display(vt, &numbering)?; + let num = counter.at(vt, loc)?.display(vt, numbering)?; let sup = SuperElem::new(num).pack(); let loc = loc.variant(1); // Add zero-width weak spacing to make the footnote "sticky". @@ -144,7 +145,7 @@ impl Count for FootnoteElem { /// The body of a footnote can be either some content or a label referencing /// another footnote. -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Hash)] pub enum FootnoteBody { Content(Content), Reference(Label), @@ -266,10 +267,11 @@ impl Show for FootnoteEntry { fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { let note = self.note(); let number_gap = Em::new(0.05); - let numbering = note.numbering(StyleChain::default()); + let default = StyleChain::default(); + let numbering = note.numbering(default); let counter = Counter::of(FootnoteElem::elem()); - let loc = note.0.location().unwrap(); - let num = counter.at(vt, loc)?.display(vt, &numbering)?; + let loc = note.location().unwrap(); + let num = counter.at(vt, loc)?.display(vt, numbering)?; let sup = SuperElem::new(num) .pack() .linked(Destination::Location(loc)) @@ -278,7 +280,7 @@ impl Show for FootnoteEntry { HElem::new(self.indent(styles).into()).pack(), sup, HElem::new(number_gap.into()).with_weak(true).pack(), - note.body_content().unwrap(), + note.body_content().unwrap().clone(), ])) } } diff --git a/crates/typst-library/src/meta/heading.rs b/crates/typst-library/src/meta/heading.rs index 34af82385..1a2f4dd07 100644 --- a/crates/typst-library/src/meta/heading.rs +++ b/crates/typst-library/src/meta/heading.rs @@ -3,7 +3,7 @@ use typst::util::option_eq; use super::{Counter, CounterUpdate, LocalName, Numbering, Outlinable, Refable}; use crate::layout::{BlockElem, HElem, VElem}; -use crate::meta::{Count, Supplement}; +use crate::meta::{Count, LocalNameIn, Supplement}; use crate::prelude::*; use crate::text::{SpaceElem, TextElem, TextSize}; @@ -54,6 +54,7 @@ pub struct HeadingElem { /// == A subsection /// === A sub-subsection /// ``` + #[borrowed] pub numbering: Option, /// A supplement for the heading. @@ -124,13 +125,13 @@ impl Synthesize for HeadingElem { fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> { // Resolve the supplement. let supplement = match self.supplement(styles) { - Smart::Auto => TextElem::packed(self.local_name_in(styles)), + Smart::Auto => TextElem::packed(Self::local_name_in(styles)), Smart::Custom(None) => Content::empty(), Smart::Custom(Some(supplement)) => supplement.resolve(vt, [self.clone()])?, }; self.push_level(self.level(styles)); - self.push_numbering(self.numbering(styles)); + self.push_numbering(self.numbering(styles).clone()); self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement)))); self.push_outlined(self.outlined(styles)); self.push_bookmarked(self.bookmarked(styles)); @@ -142,10 +143,10 @@ impl Synthesize for HeadingElem { impl Show for HeadingElem { #[tracing::instrument(name = "HeadingElem::show", skip_all)] fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - let mut realized = self.body(); - if let Some(numbering) = self.numbering(styles) { + let mut realized = self.body().clone(); + if let Some(numbering) = self.numbering(styles).as_ref() { realized = Counter::of(Self::elem()) - .display(Some(numbering), false) + .display(Some(numbering.clone()), false) .spanned(self.span()) + HElem::new(Em::new(0.3).into()).with_weak(true).pack() + realized; @@ -204,7 +205,7 @@ impl Refable for HeadingElem { } fn numbering(&self) -> Option { - self.numbering(StyleChain::default()) + self.numbering(StyleChain::default()).clone() } } @@ -214,11 +215,12 @@ impl Outlinable for HeadingElem { return Ok(None); } - let mut content = self.body(); - if let Some(numbering) = self.numbering(StyleChain::default()) { + let mut content = self.body().clone(); + let default = StyleChain::default(); + if let Some(numbering) = self.numbering(default).as_ref() { let numbers = Counter::of(Self::elem()) - .at(vt, self.0.location().unwrap())? - .display(vt, &numbering)?; + .at(vt, self.location().unwrap())? + .display(vt, numbering)?; content = numbers + SpaceElem::new().pack() + content; }; @@ -231,7 +233,7 @@ impl Outlinable for HeadingElem { } impl LocalName for HeadingElem { - fn local_name(&self, lang: Lang, region: Option) -> &'static str { + fn local_name(lang: Lang, region: Option) -> &'static str { match lang { Lang::ALBANIAN => "Kapitull", Lang::ARABIC => "الفصل", diff --git a/crates/typst-library/src/meta/link.rs b/crates/typst-library/src/meta/link.rs index 7b68b1860..74857f1bb 100644 --- a/crates/typst-library/src/meta/link.rs +++ b/crates/typst-library/src/meta/link.rs @@ -84,12 +84,12 @@ impl LinkElem { impl Show for LinkElem { #[tracing::instrument(name = "LinkElem::show", skip(self, vt))] fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult { - let body = self.body(); + let body = self.body().clone(); let linked = match self.dest() { - LinkTarget::Dest(dest) => body.linked(dest), + LinkTarget::Dest(dest) => body.linked(dest.clone()), LinkTarget::Label(label) => vt .delayed(|vt| { - let elem = vt.introspector.query_label(&label).at(self.span())?; + let elem = vt.introspector.query_label(*label).at(self.span())?; let dest = Destination::Location(elem.location().unwrap()); Ok(Some(body.clone().linked(dest))) }) @@ -110,7 +110,7 @@ fn body_from_url(url: &EcoString) -> Content { } /// A target where a link can go. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Hash)] pub enum LinkTarget { Dest(Destination), Label(Label), diff --git a/crates/typst-library/src/meta/mod.rs b/crates/typst-library/src/meta/mod.rs index 1019864f0..ffe861abf 100644 --- a/crates/typst-library/src/meta/mod.rs +++ b/crates/typst-library/src/meta/mod.rs @@ -62,13 +62,15 @@ pub(super) fn define(global: &mut Scope) { global.define_func::(); } -/// The named with which an element is referenced. -pub trait LocalName { - /// Get the name in the given language and (optionally) region. - fn local_name(&self, lang: Lang, region: Option) -> &'static str; - - /// Resolve the local name with a style chain. - fn local_name_in(&self, styles: StyleChain) -> &'static str { - self.local_name(TextElem::lang_in(styles), TextElem::region_in(styles)) +/// An element that has a local name. +pub trait LocalNameIn: LocalName { + /// Gets the local name from the style chain. + fn local_name_in(styles: StyleChain) -> &'static str + where + Self: Sized, + { + Self::local_name(TextElem::lang_in(styles), TextElem::region_in(styles)) } } + +impl LocalNameIn for T {} diff --git a/crates/typst-library/src/meta/outline.rs b/crates/typst-library/src/meta/outline.rs index 9266ba2b1..e7332ab34 100644 --- a/crates/typst-library/src/meta/outline.rs +++ b/crates/typst-library/src/meta/outline.rs @@ -6,6 +6,7 @@ use super::{ Counter, CounterKey, HeadingElem, LocalName, Numbering, NumberingPattern, Refable, }; use crate::layout::{BoxElem, HElem, HideElem, ParbreakElem, RepeatElem, Spacing}; +use crate::meta::LocalNameIn; use crate::prelude::*; use crate::text::{LinebreakElem, SpaceElem, TextElem}; @@ -88,10 +89,8 @@ pub struct OutlineElem { /// caption: [Experiment results], /// ) /// ``` - #[default(LocatableSelector(Selector::Elem( - HeadingElem::elem(), - Some(dict! { "outlined" => true }) - )))] + #[default(LocatableSelector(select_where!(HeadingElem, Outlined => true)))] + #[borrowed] pub target: LocatableSelector, /// The maximum level up to which elements are included in the outline. When @@ -161,6 +160,7 @@ pub struct OutlineElem { /// #lorem(10) /// ``` #[default(None)] + #[borrowed] pub indent: Option>, /// Content to fill the space between the title and the page number. Can be @@ -187,14 +187,9 @@ impl Show for OutlineElem { let mut seq = vec![ParbreakElem::new().pack()]; // Build the outline title. if let Some(title) = self.title(styles) { - let title = - title.unwrap_or_else(|| { - TextElem::packed(self.local_name( - TextElem::lang_in(styles), - TextElem::region_in(styles), - )) - .spanned(self.span()) - }); + let title = title.unwrap_or_else(|| { + TextElem::packed(Self::local_name_in(styles)).spanned(self.span()) + }); seq.push(HeadingElem::new(title).with_level(NonZeroUsize::ONE).pack()); } @@ -217,7 +212,7 @@ impl Show for OutlineElem { }; let level = entry.level(); - if depth < level { + if depth < *level { continue; } @@ -226,12 +221,12 @@ impl Show for OutlineElem { while ancestors .last() .and_then(|ancestor| ancestor.with::()) - .map_or(false, |last| last.level() >= level) + .map_or(false, |last| last.level() >= *level) { ancestors.pop(); } - OutlineIndent::apply(&indent, vt, &ancestors, &mut seq, self.span())?; + OutlineIndent::apply(indent, vt, &ancestors, &mut seq, self.span())?; // Add the overridable outline entry, followed by a line break. seq.push(entry.pack()); @@ -255,7 +250,7 @@ impl Finalize for OutlineElem { } impl LocalName for OutlineElem { - fn local_name(&self, lang: Lang, region: Option) -> &'static str { + fn local_name(lang: Lang, region: Option) -> &'static str { match lang { Lang::ALBANIAN => "Përmbajtja", Lang::ARABIC => "المحتويات", @@ -301,7 +296,7 @@ pub trait Outlinable: Refable { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Hash)] pub enum OutlineIndent { Bool(bool), Rel(Rel), @@ -497,14 +492,14 @@ impl Show for OutlineEntry { }; // The body text remains overridable. - seq.push(self.body().linked(Destination::Location(location))); + seq.push(self.body().clone().linked(Destination::Location(location))); // Add filler symbols between the section name and page number. if let Some(filler) = self.fill() { seq.push(SpaceElem::new().pack()); seq.push( BoxElem::new() - .with_body(Some(filler)) + .with_body(Some(filler.clone())) .with_width(Fr::one().into()) .pack(), ); @@ -514,7 +509,7 @@ impl Show for OutlineEntry { } // Add the page number. - let page = self.page().linked(Destination::Location(location)); + let page = self.page().clone().linked(Destination::Location(location)); seq.push(page); Ok(Content::sequence(seq)) diff --git a/crates/typst-library/src/meta/reference.rs b/crates/typst-library/src/meta/reference.rs index 12e13929a..3a463c803 100644 --- a/crates/typst-library/src/meta/reference.rs +++ b/crates/typst-library/src/meta/reference.rs @@ -115,6 +115,7 @@ pub struct RefElem { /// in @intro[Part], it is done /// manually. /// ``` + #[borrowed] pub supplement: Smart>, /// A synthesized citation. @@ -132,9 +133,9 @@ impl Synthesize for RefElem { self.push_citation(Some(citation)); self.push_element(None); - let target = self.target(); - if !BibliographyElem::has(vt, &target.0) { - if let Ok(elem) = vt.introspector.query_label(&target) { + let target = *self.target(); + if !BibliographyElem::has(vt, target) { + if let Ok(elem) = vt.introspector.query_label(target) { self.push_element(Some(elem.into_inner())); return Ok(()); } @@ -148,22 +149,22 @@ impl Show for RefElem { #[tracing::instrument(name = "RefElem::show", skip_all)] fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { Ok(vt.delayed(|vt| { - let target = self.target(); - let elem = vt.introspector.query_label(&self.target()); + let target = *self.target(); + let elem = vt.introspector.query_label(target); let span = self.span(); - if BibliographyElem::has(vt, &target.0) { + if BibliographyElem::has(vt, target) { if elem.is_ok() { bail!(span, "label occurs in the document and its bibliography"); } - return Ok(self.to_citation(vt, styles)?.pack().spanned(span)); + return Ok(self.to_citation(vt, styles)?.spanned(span).pack()); } let elem = elem.at(span)?; if elem.func() == FootnoteElem::elem() { - return Ok(FootnoteElem::with_label(target).pack().spanned(span)); + return Ok(FootnoteElem::with_label(target).spanned(span).pack()); } let refable = elem @@ -204,7 +205,7 @@ impl Show for RefElem { .at(vt, elem.location().unwrap())? .display(vt, &numbering.trimmed())?; - let supplement = match self.supplement(styles) { + let supplement = match self.supplement(styles).as_ref() { Smart::Auto => refable.supplement(), Smart::Custom(None) => Content::empty(), Smart::Custom(Some(supplement)) => { @@ -225,10 +226,10 @@ impl Show for RefElem { impl RefElem { /// Turn the reference into a citation. pub fn to_citation(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { - let mut elem = CiteElem::new(self.target()); - elem.0.set_location(self.0.location().unwrap()); + let mut elem = CiteElem::new(*self.target()); + elem.set_location(self.location().unwrap()); elem.synthesize(vt, styles)?; - elem.push_supplement(match self.supplement(styles) { + elem.push_supplement(match self.supplement(styles).clone() { Smart::Custom(Some(Supplement::Content(content))) => Some(content), _ => None, }); @@ -238,6 +239,7 @@ impl RefElem { } /// Additional content for a reference. +#[derive(Debug, Clone, PartialEq, Hash)] pub enum Supplement { Content(Content), Func(Func), diff --git a/crates/typst-library/src/meta/state.rs b/crates/typst-library/src/meta/state.rs index 137b754e4..4f04628f5 100644 --- a/crates/typst-library/src/meta/state.rs +++ b/crates/typst-library/src/meta/state.rs @@ -233,7 +233,7 @@ impl State { for elem in introspector.query(&self.selector()) { let elem = elem.to::().unwrap(); match elem.update() { - StateUpdate::Set(value) => state = value, + StateUpdate::Set(value) => state = value.clone(), StateUpdate::Func(func) => state = func.call_vt(&mut vt, [state])?, } stops.push(state.clone()); @@ -244,7 +244,7 @@ impl State { /// The selector for this state's updates. fn selector(&self) -> Selector { - Selector::Elem(UpdateElem::elem(), Some(dict! { "key" => self.key.clone() })) + select_where!(UpdateElem, Key => self.key.clone()) } } @@ -383,7 +383,7 @@ impl Show for DisplayElem { #[tracing::instrument(name = "DisplayElem::show", skip(self, vt))] fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult { Ok(vt.delayed(|vt| { - let location = self.0.location().unwrap(); + let location = self.location().unwrap(); let value = self.state().at(vt, location)?; Ok(match self.func() { Some(func) => func.call_vt(vt, [value])?.display(), diff --git a/crates/typst-library/src/prelude.rs b/crates/typst-library/src/prelude.rs index 329cbf61b..e3163e0fc 100644 --- a/crates/typst-library/src/prelude.rs +++ b/crates/typst-library/src/prelude.rs @@ -10,6 +10,8 @@ pub use comemo::{Track, Tracked, TrackedMut}; #[doc(no_inline)] pub use ecow::{eco_format, EcoString}; #[doc(no_inline)] +pub use smallvec::{smallvec, SmallVec}; +#[doc(no_inline)] pub use typst::diag::{bail, error, At, Hint, SourceResult, StrResult}; #[doc(no_inline)] pub use typst::doc::*; @@ -22,10 +24,10 @@ pub use typst::eval::{ pub use typst::geom::*; #[doc(no_inline)] pub use typst::model::{ - elem, Behave, Behaviour, Construct, Content, Element, Finalize, Fold, Introspector, - Label, Locatable, LocatableSelector, Location, Locator, MetaElem, NativeElement, - PlainText, Resolve, Selector, Set, Show, StyleChain, StyleVec, Styles, Synthesize, - Unlabellable, Vt, + elem, select_where, Behave, Behaviour, Construct, Content, Element, ElementFields, + Finalize, Fold, Introspector, Label, LocalName, Locatable, LocatableSelector, + Location, Locator, MetaElem, NativeElement, PlainText, Resolve, Selector, Set, Show, + StyleChain, StyleVec, Styles, Synthesize, Unlabellable, Vt, }; #[doc(no_inline)] pub use typst::syntax::{FileId, Span, Spanned}; diff --git a/crates/typst-library/src/shared/behave.rs b/crates/typst-library/src/shared/behave.rs index f97e3fbcc..e152fbb50 100644 --- a/crates/typst-library/src/shared/behave.rs +++ b/crates/typst-library/src/shared/behave.rs @@ -1,16 +1,18 @@ //! Element interaction. +use std::borrow::Cow; + use typst::model::{Behave, Behaviour, Content, StyleChain, StyleVec, StyleVecBuilder}; /// A wrapper around a [`StyleVecBuilder`] that allows elements to interact. #[derive(Debug)] pub struct BehavedBuilder<'a> { /// The internal builder. - builder: StyleVecBuilder<'a, Content>, + builder: StyleVecBuilder<'a, Cow<'a, Content>>, /// Staged weak and ignorant elements that we can't yet commit to the /// builder. The option is `Some(_)` for weak elements and `None` for /// ignorant elements. - staged: Vec<(Content, Behaviour, StyleChain<'a>)>, + staged: Vec<(Cow<'a, Content>, Behaviour, StyleChain<'a>)>, /// What the last non-ignorant item was. last: Behaviour, } @@ -41,7 +43,7 @@ impl<'a> BehavedBuilder<'a> { } /// Push an item into the sequence. - pub fn push(&mut self, elem: Content, styles: StyleChain<'a>) { + pub fn push(&mut self, elem: Cow<'a, Content>, styles: StyleChain<'a>) { let interaction = elem .with::() .map_or(Behaviour::Supportive, Behave::behaviour); @@ -81,12 +83,12 @@ impl<'a> BehavedBuilder<'a> { } /// Iterate over the contained elements. - pub fn elems(&self) -> impl DoubleEndedIterator { + pub fn elems(&self) -> impl DoubleEndedIterator> { self.builder.elems().chain(self.staged.iter().map(|(item, ..)| item)) } /// Return the finish style vec and the common prefix chain. - pub fn finish(mut self) -> (StyleVec, StyleChain<'a>) { + pub fn finish(mut self) -> (StyleVec>, StyleChain<'a>) { self.flush(false); self.builder.finish() } diff --git a/crates/typst-library/src/shared/ext.rs b/crates/typst-library/src/shared/ext.rs index e6431423e..606148204 100644 --- a/crates/typst-library/src/shared/ext.rs +++ b/crates/typst-library/src/shared/ext.rs @@ -47,13 +47,13 @@ impl ContentExt for Content { } fn linked(self, dest: Destination) -> Self { - self.styled(MetaElem::set_data(vec![Meta::Link(dest)])) + self.styled(MetaElem::set_data(smallvec![Meta::Link(dest)])) } fn backlinked(self, loc: Location) -> Self { let mut backlink = Content::empty(); backlink.set_location(loc); - self.styled(MetaElem::set_data(vec![Meta::Elem(backlink)])) + self.styled(MetaElem::set_data(smallvec![Meta::Elem(backlink)])) } fn aligned(self, align: Align) -> Self { @@ -85,7 +85,7 @@ impl StylesExt for Styles { fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) { self.set(TextElem::set_font(FontList( std::iter::once(preferred) - .chain(TextElem::font_in(existing)) + .chain(TextElem::font_in(existing).into_iter().cloned()) .collect(), ))); } diff --git a/crates/typst-library/src/text/deco.rs b/crates/typst-library/src/text/deco.rs index 136dfad87..d8167788d 100644 --- a/crates/typst-library/src/text/deco.rs +++ b/crates/typst-library/src/text/deco.rs @@ -78,7 +78,7 @@ pub struct UnderlineElem { impl Show for UnderlineElem { #[tracing::instrument(name = "UnderlineElem::show", skip_all)] fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(self.body().styled(TextElem::set_deco(Decoration { + Ok(self.body().clone().styled(TextElem::set_deco(Decoration { line: DecoLine::Underline { stroke: self.stroke(styles).unwrap_or_default(), offset: self.offset(styles), @@ -170,7 +170,7 @@ pub struct OverlineElem { impl Show for OverlineElem { #[tracing::instrument(name = "OverlineElem::show", skip_all)] fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(self.body().styled(TextElem::set_deco(Decoration { + Ok(self.body().clone().styled(TextElem::set_deco(Decoration { line: DecoLine::Overline { stroke: self.stroke(styles).unwrap_or_default(), offset: self.offset(styles), @@ -247,7 +247,7 @@ pub struct StrikeElem { impl Show for StrikeElem { #[tracing::instrument(name = "StrikeElem::show", skip_all)] fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(self.body().styled(TextElem::set_deco(Decoration { + Ok(self.body().clone().styled(TextElem::set_deco(Decoration { // Note that we do not support evade option for strikethrough. line: DecoLine::Strikethrough { stroke: self.stroke(styles).unwrap_or_default(), @@ -317,7 +317,7 @@ pub struct HighlightElem { impl Show for HighlightElem { #[tracing::instrument(name = "HighlightElem::show", skip_all)] fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(self.body().styled(TextElem::set_deco(Decoration { + Ok(self.body().clone().styled(TextElem::set_deco(Decoration { line: DecoLine::Highlight { fill: self.fill(styles), top_edge: self.top_edge(styles), diff --git a/crates/typst-library/src/text/misc.rs b/crates/typst-library/src/text/misc.rs index 736573458..9f768f11f 100644 --- a/crates/typst-library/src/text/misc.rs +++ b/crates/typst-library/src/text/misc.rs @@ -2,9 +2,15 @@ use super::TextElem; use crate::prelude::*; /// A text space. -#[elem(Behave, Unlabellable, PlainText)] +#[elem(Behave, Unlabellable, PlainText, Repr)] pub struct SpaceElem {} +impl Repr for SpaceElem { + fn repr(&self) -> EcoString { + EcoString::inline("[ ]") + } +} + impl Behave for SpaceElem { fn behaviour(&self) -> Behaviour { Behaviour::Weak(2) @@ -98,7 +104,10 @@ pub struct StrongElem { impl Show for StrongElem { #[tracing::instrument(name = "StrongElem::show", skip_all)] fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - Ok(self.body().styled(TextElem::set_delta(Delta(self.delta(styles))))) + Ok(self + .body() + .clone() + .styled(TextElem::set_delta(Delta(self.delta(styles))))) } } @@ -153,7 +162,7 @@ pub struct EmphElem { impl Show for EmphElem { #[tracing::instrument(name = "EmphElem::show", skip(self))] fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult { - Ok(self.body().styled(TextElem::set_emph(Toggle))) + Ok(self.body().clone().styled(TextElem::set_emph(Toggle))) } } diff --git a/crates/typst-library/src/text/mod.rs b/crates/typst-library/src/text/mod.rs index 27dd4a3f6..84a68d6f5 100644 --- a/crates/typst-library/src/text/mod.rs +++ b/crates/typst-library/src/text/mod.rs @@ -64,7 +64,7 @@ pub(super) fn define(global: &mut Scope) { /// With a function call. /// ]) /// ``` -#[elem(Construct, PlainText)] +#[elem(Construct, PlainText, Repr)] pub struct TextElem { /// A font family name or priority list of font family names. /// @@ -97,6 +97,7 @@ pub struct TextElem { /// هذا عربي. /// ``` #[default(FontList(vec![FontFamily::new("Linux Libertine")]))] + #[borrowed] pub font: FontList, /// Whether to allow last resort font fallback when the primary font list @@ -558,8 +559,8 @@ pub struct TextElem { pub body: Content, /// The text. - #[internal] #[required] + #[variant(0)] pub text: EcoString, /// A delta to apply on the font weight. @@ -595,6 +596,12 @@ impl TextElem { } } +impl Repr for TextElem { + fn repr(&self) -> EcoString { + eco_format!("[{}]", self.text) + } +} + impl Construct for TextElem { fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult { // The text constructor is special: It doesn't create a text element. @@ -608,7 +615,7 @@ impl Construct for TextElem { impl PlainText for TextElem { fn plain_text(&self, text: &mut EcoString) { - text.push_str(&self.text()); + text.push_str(self.text()); } } @@ -644,12 +651,12 @@ cast! { #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] pub struct FontList(pub Vec); -impl IntoIterator for FontList { - type IntoIter = std::vec::IntoIter; - type Item = FontFamily; +impl<'a> IntoIterator for &'a FontList { + type IntoIter = std::slice::Iter<'a, FontFamily>; + type Item = &'a FontFamily; fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() + self.0.iter() } } diff --git a/crates/typst-library/src/text/quote.rs b/crates/typst-library/src/text/quote.rs index 86be24166..c555df2d1 100644 --- a/crates/typst-library/src/text/quote.rs +++ b/crates/typst-library/src/text/quote.rs @@ -101,6 +101,7 @@ pub struct QuoteElem { /// /// #bibliography("works.bib") /// ``` + #[borrowed] attribution: Option, /// The quote. @@ -108,7 +109,7 @@ pub struct QuoteElem { body: Content, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Hash)] pub enum Attribution { Content(Content), Label(Label), @@ -126,7 +127,7 @@ cast! { impl Show for QuoteElem { fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - let mut realized = self.body(); + let mut realized = self.body().clone(); let block = self.block(styles); if self.quotes(styles) == Smart::Custom(true) || !block { @@ -140,16 +141,16 @@ impl Show for QuoteElem { if block { realized = BlockElem::new().with_body(Some(realized)).pack(); - if let Some(attribution) = self.attribution(styles) { + if let Some(attribution) = self.attribution(styles).as_ref() { let mut seq = vec![TextElem::packed('—'), SpaceElem::new().pack()]; match attribution { Attribution::Content(content) => { - seq.push(content); + seq.push(content.clone()); } Attribution::Label(label) => { seq.push( - CiteElem::new(label) + CiteElem::new(*label) .with_form(Some(CitationForm::Prose)) .pack(), ); @@ -164,7 +165,7 @@ impl Show for QuoteElem { realized = PadElem::new(realized).pack(); } else if let Some(Attribution::Label(label)) = self.attribution(styles) { - realized += SpaceElem::new().pack() + CiteElem::new(label).pack(); + realized += SpaceElem::new().pack() + CiteElem::new(*label).pack(); } Ok(realized) diff --git a/crates/typst-library/src/text/quotes.rs b/crates/typst-library/src/text/quotes.rs index 37e664fd9..035ba4223 100644 --- a/crates/typst-library/src/text/quotes.rs +++ b/crates/typst-library/src/text/quotes.rs @@ -78,6 +78,7 @@ pub struct SmartquoteElem { /// #set smartquote(quotes: (single: ("[[", "]]"), double: auto)) /// 'Das sind eigene Anführungszeichen.' /// ``` + #[borrowed] pub quotes: Smart, } diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs index d16659beb..4f672be91 100644 --- a/crates/typst-library/src/text/raw.rs +++ b/crates/typst-library/src/text/raw.rs @@ -17,7 +17,7 @@ use super::{ FontFamily, FontList, Hyphenate, LinebreakElem, SmartquoteElem, TextElem, TextSize, }; use crate::layout::BlockElem; -use crate::meta::{Figurable, LocalName}; +use crate::meta::Figurable; use crate::prelude::*; // Shorthand for highlighter closures. @@ -145,6 +145,7 @@ pub struct RawElem { /// /// This is ```typ also *Typst*```, but inline! /// ```` + #[borrowed] pub lang: Option, /// The horizontal alignment that each line in a raw block should have. @@ -225,11 +226,13 @@ pub struct RawElem { let (theme_path, theme_data) = parse_theme(vm, args)?; theme_path.map(Some) )] + #[borrowed] pub theme: Option, /// The raw file buffer of syntax theme file. #[internal] #[parse(theme_data.map(Some))] + #[borrowed] pub theme_data: Option, /// The size for a tab stop in spaces. A tab is replaced with enough spaces to @@ -252,7 +255,7 @@ pub struct RawElem { /// Made accessible for the [`raw.line` element]($raw.line). /// Allows more styling control in `show` rules. #[synthesized] - pub lines: Vec, + pub lines: Vec, } #[scope] @@ -280,17 +283,20 @@ impl RawElem { impl Synthesize for RawElem { fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> { - self.push_lang(self.lang(styles)); + self.push_lang(self.lang(styles).clone()); - let mut text = self.text(); + let mut text = self.text().clone(); if text.contains('\t') { let tab_size = RawElem::tab_size_in(styles); text = align_tabs(&text, tab_size); } + let count = text.lines().count() as i64; + let lang = self .lang(styles) .as_ref() + .as_ref() .map(|s| s.to_lowercase()) .or(Some("txt".into())); @@ -298,12 +304,12 @@ impl Synthesize for RawElem { load_syntaxes(&self.syntaxes(styles), &self.syntaxes_data(styles)).unwrap() }); - let theme = self.theme(styles).map(|theme_path| { - load_theme(theme_path, self.theme_data(styles).unwrap()).unwrap() + let theme = self.theme(styles).as_ref().as_ref().map(|theme_path| { + load_theme(theme_path, self.theme_data(styles).as_ref().as_ref().unwrap()) + .unwrap() }); let theme = theme.as_deref().unwrap_or(&THEME); - let foreground = theme.settings.foreground.unwrap_or(synt::Color::BLACK); let mut seq = vec![]; @@ -319,15 +325,12 @@ impl Synthesize for RawElem { synt::Highlighter::new(theme), &mut |_, range, style| styled(&text[range], foreground, style), &mut |i, range, line| { - seq.push( - RawLine::new( - i + 1, - text.split(is_newline).count() as i64, - EcoString::from(&text[range]), - Content::sequence(line.drain(..)), - ) - .pack(), - ); + seq.push(RawLine::new( + i + 1, + count, + EcoString::from(&text[range]), + Content::sequence(line.drain(..)), + )); }, ) .highlight(); @@ -342,7 +345,6 @@ impl Synthesize for RawElem { }) }) { let mut highlighter = syntect::easy::HighlightLines::new(syntax, theme); - let len = text.lines().count(); for (i, line) in text.lines().enumerate() { let mut line_content = vec![]; for (style, piece) in @@ -351,18 +353,23 @@ impl Synthesize for RawElem { line_content.push(styled(piece, foreground, style)); } - seq.push( - RawLine::new( - i as i64 + 1, - len as i64, - EcoString::from(line), - Content::sequence(line_content), - ) - .pack(), - ); + seq.push(RawLine::new( + i as i64 + 1, + count, + EcoString::from(line), + Content::sequence(line_content), + )); } } else { - seq.extend(text.lines().map(TextElem::packed)); + let lines = text.lines(); + seq.extend(lines.enumerate().map(|(i, line)| { + RawLine::new( + i as i64 + 1, + count, + EcoString::from(line), + TextElem::packed(line), + ) + })); }; self.push_lines(seq); @@ -375,12 +382,12 @@ impl Show for RawElem { #[tracing::instrument(name = "RawElem::show", skip_all)] fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { let mut lines = EcoVec::with_capacity((2 * self.lines().len()).saturating_sub(1)); - for (i, line) in self.lines().into_iter().enumerate() { + for (i, line) in self.lines().iter().enumerate() { if i != 0 { lines.push(LinebreakElem::new().pack()); } - lines.push(line); + lines.push(line.clone().pack()); } let mut realized = Content::sequence(lines); @@ -408,7 +415,7 @@ impl Finalize for RawElem { } impl LocalName for RawElem { - fn local_name(&self, lang: Lang, region: Option) -> &'static str { + fn local_name(lang: Lang, region: Option) -> &'static str { match lang { Lang::ALBANIAN => "List", Lang::ARABIC => "قائمة", @@ -443,7 +450,7 @@ impl Figurable for RawElem {} impl PlainText for RawElem { fn plain_text(&self, text: &mut EcoString) { - text.push_str(&self.text()); + text.push_str(self.text()); } } @@ -475,13 +482,13 @@ pub struct RawLine { impl Show for RawLine { fn show(&self, _vt: &mut Vt, _styles: StyleChain) -> SourceResult { - Ok(self.body()) + Ok(self.body().clone()) } } impl PlainText for RawLine { fn plain_text(&self, text: &mut EcoString) { - text.push_str(&self.text()); + text.push_str(self.text()); } } @@ -617,7 +624,7 @@ fn to_syn(color: Color) -> synt::Color { } /// A list of bibliography file paths. -#[derive(Debug, Default, Clone, Hash)] +#[derive(Debug, Default, Clone, PartialEq, Hash)] pub struct SyntaxPaths(Vec); cast! { @@ -681,7 +688,7 @@ fn parse_syntaxes( } #[comemo::memoize] -fn load_theme(path: EcoString, bytes: Bytes) -> StrResult> { +fn load_theme(path: &str, bytes: &Bytes) -> StrResult> { let mut cursor = std::io::Cursor::new(bytes.as_slice()); synt::ThemeSet::load_from_reader(&mut cursor) @@ -705,7 +712,7 @@ fn parse_theme( let data = vm.world().file(id).at(span)?; // Check that parsing works. - let _ = load_theme(path.clone(), data.clone()).at(span)?; + let _ = load_theme(&path, &data).at(span)?; Ok((Some(path), Some(data))) } diff --git a/crates/typst-library/src/text/shaping.rs b/crates/typst-library/src/text/shaping.rs index 0cfffce07..f7cf90bd6 100644 --- a/crates/typst-library/src/text/shaping.rs +++ b/crates/typst-library/src/text/shaping.rs @@ -8,7 +8,7 @@ use typst::font::{Font, FontStyle, FontVariant}; use typst::util::SliceExt; use unicode_script::{Script, UnicodeScript}; -use super::{decorate, FontFamily, NumberType, NumberWidth, TextElem}; +use super::{decorate, NumberType, NumberWidth, TextElem}; use crate::layout::SpanMapper; use crate::prelude::*; @@ -320,7 +320,7 @@ impl<'a> ShapedText<'a> { for family in families(self.styles) { if let Some(font) = world .book() - .select(family.as_str(), self.variant) + .select(family, self.variant) .and_then(|id| world.font(id)) { expand(&font, None); @@ -424,7 +424,7 @@ impl<'a> ShapedText<'a> { None }; let mut chain = families(self.styles) - .map(|family| book.select(family.as_str(), self.variant)) + .map(|family| book.select(family, self.variant)) .chain(fallback_func.iter().map(|f| f())) .flatten(); @@ -593,11 +593,11 @@ pub fn shape<'a>( } /// Shape text with font fallback using the `families` iterator. -fn shape_segment( +fn shape_segment<'a>( ctx: &mut ShapingContext, base: usize, text: &str, - mut families: impl Iterator + Clone, + mut families: impl Iterator + Clone, ) { // Fonts dont have newlines and tabs. if text.chars().all(|c| c == '\n' || c == '\t') { @@ -608,7 +608,7 @@ fn shape_segment( let world = ctx.vt.world; let book = world.book(); let mut selection = families.find_map(|family| { - book.select(family.as_str(), ctx.variant) + book.select(family, ctx.variant) .and_then(|id| world.font(id)) .filter(|font| !ctx.used.contains(font)) }); @@ -871,7 +871,7 @@ pub fn variant(styles: StyleChain) -> FontVariant { } /// Resolve a prioritized iterator over the font families. -pub fn families(styles: StyleChain) -> impl Iterator + Clone { +pub fn families(styles: StyleChain) -> impl Iterator + Clone { const FALLBACKS: &[&str] = &[ "linux libertine", "twitter color emoji", @@ -883,7 +883,8 @@ pub fn families(styles: StyleChain) -> impl Iterator + Clone let tail = if TextElem::fallback_in(styles) { FALLBACKS } else { &[] }; TextElem::font_in(styles) .into_iter() - .chain(tail.iter().copied().map(FontFamily::new)) + .map(|family| family.as_str()) + .chain(tail.iter().copied()) } /// Collect the tags of the OpenType features to apply. diff --git a/crates/typst-library/src/text/shift.rs b/crates/typst-library/src/text/shift.rs index 6cb4d8951..903982ef1 100644 --- a/crates/typst-library/src/text/shift.rs +++ b/crates/typst-library/src/text/shift.rs @@ -44,7 +44,7 @@ pub struct SubElem { impl Show for SubElem { #[tracing::instrument(name = "SubElem::show", skip_all)] fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { - let body = self.body(); + let body = self.body().clone(); let mut transformed = None; if self.typographic(styles) { if let Some(text) = search_text(&body, true) { @@ -104,7 +104,7 @@ pub struct SuperElem { impl Show for SuperElem { #[tracing::instrument(name = "SuperElem::show", skip_all)] fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { - let body = self.body(); + let body = self.body().clone(); let mut transformed = None; if self.typographic(styles) { if let Some(text) = search_text(&body, false) { @@ -127,7 +127,7 @@ fn search_text(content: &Content, sub: bool) -> Option { if content.is::() { Some(' '.into()) } else if let Some(elem) = content.to::() { - convert_script(&elem.text(), sub) + convert_script(elem.text(), sub) } else if let Some(children) = content.to_sequence() { let mut full = EcoString::new(); for item in children { diff --git a/crates/typst-library/src/visualize/image.rs b/crates/typst-library/src/visualize/image.rs index 931e38c63..6e166bc08 100644 --- a/crates/typst-library/src/visualize/image.rs +++ b/crates/typst-library/src/visualize/image.rs @@ -6,7 +6,7 @@ use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat}; use typst::util::option_eq; use crate::compute::Readable; -use crate::meta::{Figurable, LocalName}; +use crate::meta::Figurable; use crate::prelude::*; use crate::text::families; @@ -41,6 +41,7 @@ pub struct ImageElem { let data = vm.world().file(id).at(span)?; path )] + #[borrowed] pub path: EcoString, /// The raw file data. @@ -157,11 +158,11 @@ impl Layout for ImageElem { }; let image = Image::with_fonts( - data.into(), + data.clone().into(), format, self.alt(styles), vt.world, - &families(styles).map(|s| s.as_str().into()).collect::>(), + &families(styles).map(|s| s.into()).collect::>(), ) .at(self.span())?; @@ -223,7 +224,7 @@ impl Layout for ImageElem { } impl LocalName for ImageElem { - fn local_name(&self, lang: Lang, region: Option) -> &'static str { + fn local_name(lang: Lang, region: Option) -> &'static str { match lang { Lang::ALBANIAN => "Figurë", Lang::ARABIC => "شكل", diff --git a/crates/typst-library/src/visualize/path.rs b/crates/typst-library/src/visualize/path.rs index 79364b070..43b115266 100644 --- a/crates/typst-library/src/visualize/path.rs +++ b/crates/typst-library/src/visualize/path.rs @@ -77,7 +77,7 @@ impl Layout for PathElem { .to_point() }; - let vertices: Vec = self.vertices(); + let vertices = self.vertices(); let points: Vec = vertices.iter().map(|c| resolve(c.vertex())).collect(); let mut size = Size::zero(); diff --git a/crates/typst-macros/src/elem.rs b/crates/typst-macros/src/elem.rs index 5584bdb68..a5e14b5a2 100644 --- a/crates/typst-macros/src/elem.rs +++ b/crates/typst-macros/src/elem.rs @@ -1,4 +1,4 @@ -use heck::ToKebabCase; +use heck::{ToKebabCase, ToShoutySnakeCase, ToUpperCamelCase}; use super::*; @@ -17,17 +17,102 @@ struct Elem { docs: String, vis: syn::Visibility, ident: Ident, + enum_ident: Ident, capabilities: Vec, fields: Vec, } -/// Details about an element field. +impl Elem { + /// Calls the closure to produce a token stream if the + /// element does not have the given capability. + fn unless_capability( + &self, + name: &str, + closure: impl FnOnce() -> TokenStream, + ) -> Option { + self.capabilities + .iter() + .all(|capability| capability != name) + .then(closure) + } + + /// Calls the closure to produce a token stream if the + /// element has the given capability. + fn if_capability( + &self, + name: &str, + closure: impl FnOnce() -> TokenStream, + ) -> Option { + self.capabilities + .iter() + .any(|capability| capability == name) + .then(closure) + } + + /// All fields. + /// + /// This includes: + /// - Fields that are not external and therefore present in the struct. + fn real_fields(&self) -> impl Iterator + Clone { + self.fields.iter().filter(|field| !field.external) + } + + /// Fields that are inherent to the element. + fn inherent_fields(&self) -> impl Iterator + Clone { + self.real_fields().filter(|field| field.inherent()) + } + + /// Fields that can be set with style rules. + /// + /// The reason why fields that are `parse` and internal are allowed + /// is because it's a pattern used a lot for parsing data from the + /// input and then storing it in a field. + /// + /// This includes: + /// - Fields that are not synthesized. + /// - Fields that are not inherent and therefore present at all times. + /// - Fields that are not internal. + fn settable_fields(&self) -> impl Iterator + Clone { + self.real_fields().filter(|field| { + !field.synthesized + && field.settable() + && (!field.internal || field.parse.is_some()) + }) + } + + /// Fields that are visible to the user. + /// + /// This includes: + /// - Fields that are not internal. + fn visible_fields(&self) -> impl Iterator + Clone { + self.real_fields().filter(|field| !field.internal) + } + + /// Fields that are relevant for equality. + /// + /// This includes: + /// - Fields that are not synthesized (guarantees equality before and after synthesis). + fn eq_fields(&self) -> impl Iterator + Clone { + self.real_fields().filter(|field| !field.synthesized) + } + + /// Fields that are relevant for `Construct` impl. + /// + /// This includes: + /// - Fields that are not synthesized. + fn construct_fields(&self) -> impl Iterator + Clone { + self.real_fields().filter(|field| !field.synthesized) + } +} + struct Field { ident: Ident, ident_in: Ident, with_ident: Ident, push_ident: Ident, set_ident: Ident, + enum_ident: Ident, + const_ident: Ident, vis: syn::Visibility, ty: syn::Type, output: syn::Type, @@ -41,6 +126,8 @@ struct Field { internal: bool, external: bool, synthesized: bool, + borrowed: bool, + forced_variant: Option, parse: Option, default: syn::Expr, } @@ -107,6 +194,7 @@ fn parse(stream: TokenStream, body: &syn::ItemStruct) -> Result { ident: body.ident.clone(), capabilities: meta.capabilities, fields, + enum_ident: Ident::new(&format!("{}Fields", body.ident), body.ident.span()), }) } @@ -129,14 +217,19 @@ fn parse_field(field: &syn::Field) -> Result { docs: documentation(&attrs), internal: has_attr(&mut attrs, "internal"), external: has_attr(&mut attrs, "external"), + forced_variant: parse_attr::(&mut attrs, "variant")? + .flatten() + .map(|lit| lit.base10_parse()) + .transpose()?, positional, required, variadic, + borrowed: has_attr(&mut attrs, "borrowed"), synthesized: has_attr(&mut attrs, "synthesized"), fold: has_attr(&mut attrs, "fold"), resolve: has_attr(&mut attrs, "resolve"), parse: parse_attr(&mut attrs, "parse")?.flatten(), - default: parse_attr(&mut attrs, "default")? + default: parse_attr::(&mut attrs, "default")? .flatten() .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }), vis: field.vis.clone(), @@ -145,6 +238,8 @@ fn parse_field(field: &syn::Field) -> Result { with_ident: Ident::new(&format!("with_{}", ident), ident.span()), push_ident: Ident::new(&format!("push_{}", ident), ident.span()), set_ident: Ident::new(&format!("set_{}", ident), ident.span()), + enum_ident: Ident::new(&ident.to_string().to_upper_camel_case(), ident.span()), + const_ident: Ident::new(&ident.to_string().to_shouty_snake_case(), ident.span()), ty: field.ty.clone(), output: field.ty.clone(), }; @@ -175,36 +270,53 @@ fn parse_field(field: &syn::Field) -> Result { /// Produce the element's definition. fn create(element: &Elem) -> TokenStream { let Elem { vis, ident, docs, .. } = element; - let all = element.fields.iter().filter(|field| !field.external); + let all = element.real_fields(); let settable = all.clone().filter(|field| !field.synthesized && field.settable()); - // Inherent methods and functions. + let fields = all.clone().map(create_field); + let fields_enum = create_fields_enum(element); + let new = create_new_func(element); - let field_methods = all.clone().map(create_field_method); - let field_in_methods = settable.clone().map(create_field_in_method); + let field_methods = all.clone().map(|field| create_field_method(element, field)); + let field_in_methods = + settable.clone().map(|field| create_field_in_method(element, field)); let with_field_methods = all.clone().map(create_with_field_method); - let push_field_methods = all.map(create_push_field_method); - let field_style_methods = settable.map(create_set_field_method); + let push_field_methods = all.clone().map(create_push_field_method); + let field_style_methods = + settable.clone().map(|field| create_set_field_method(element, field)); // Trait implementations. - let element_impl = create_pack_impl(element); - let construct_impl = element - .capabilities - .iter() - .all(|capability| capability != "Construct") - .then(|| create_construct_impl(element)); - let set_impl = create_set_impl(element); - let locatable_impl = element - .capabilities - .iter() - .any(|capability| capability == "Locatable") - .then(|| quote! { impl ::typst::model::Locatable for #ident {} }); + let native_element_impl = create_native_elem_impl(element); + let construct_impl = + element.unless_capability("Construct", || create_construct_impl(element)); + let set_impl = element.unless_capability("Set", || create_set_impl(element)); + let locatable_impl = + element.if_capability("Locatable", || create_locatable_impl(element)); + let partial_eq_impl = + element.unless_capability("PartialEq", || create_partial_eq_impl(element)); + let repr_impl = element.unless_capability("Repr", || create_repr_impl(element)); + + let label_and_location = element.unless_capability("Unlabellable", || { + quote! { + location: Option<::typst::model::Location>, + label: Option<::typst::model::Label>, + prepared: bool, + } + }); quote! { #[doc = #docs] #[derive(Debug, Clone, Hash)] - #[repr(transparent)] - #vis struct #ident(pub ::typst::model::Content); + #[allow(clippy::derived_hash_with_manual_eq)] + #vis struct #ident { + span: ::typst::syntax::Span, + #label_and_location + guards: ::std::vec::Vec<::typst::model::Guard>, + + #(#fields,)* + } + + #fields_enum impl #ident { #new @@ -213,31 +325,123 @@ fn create(element: &Elem) -> TokenStream { #(#with_field_methods)* #(#push_field_methods)* #(#field_style_methods)* - - /// The element's span. - pub fn span(&self) -> ::typst::syntax::Span { - self.0.span() - } - - /// Set the element's span. - pub fn spanned(self, span: ::typst::syntax::Span) -> Self { - Self(self.0.spanned(span)) - } } - #element_impl + #native_element_impl #construct_impl #set_impl #locatable_impl + #partial_eq_impl + #repr_impl impl ::typst::eval::IntoValue for #ident { fn into_value(self) -> ::typst::eval::Value { - ::typst::eval::Value::Content(self.0) + ::typst::eval::Value::Content(::typst::model::Content::new(self)) } } } } +/// Create a field declaration. +fn create_field(field: &Field) -> TokenStream { + let Field { ident, ty, docs, required, .. } = field; + + let ty = required + .then(|| quote! { #ty }) + .unwrap_or_else(|| quote! { Option<#ty> }); + quote! { + #[doc = #docs] + #ident: #ty + } +} + +/// Creates the element's enum for field identifiers. +fn create_fields_enum(element: &Elem) -> TokenStream { + let model = quote! { ::typst::model }; + let Elem { ident, enum_ident, .. } = element; + + let mut fields = element.real_fields().collect::>(); + fields.sort_by_key(|field| field.forced_variant.unwrap_or(usize::MAX)); + + let field_names = fields.iter().map(|Field { name, .. }| name).collect::>(); + let field_consts = fields + .iter() + .map(|Field { const_ident, .. }| const_ident) + .collect::>(); + + let field_variants = fields + .iter() + .map(|Field { enum_ident, .. }| enum_ident) + .collect::>(); + + let definitions = + fields.iter().map(|Field { forced_variant, enum_ident, .. }| { + if let Some(variant) = forced_variant { + let variant = proc_macro2::Literal::u8_unsuffixed(*variant as _); + quote! { #enum_ident = #variant } + } else { + quote! { #enum_ident } + } + }); + + quote! { + // To hide the private type + const _: () = { + impl #model::ElementFields for #ident { + type Fields = #enum_ident; + } + + #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] + #[repr(u8)] + pub enum #enum_ident { + #(#definitions,)* + Label = 255, + } + + impl #enum_ident { + /// Converts this field identifier to the field name. + pub fn to_str(self) -> &'static str { + match self { + #(Self::#field_variants => #field_names,)* + Self::Label => "label", + } + } + } + + impl ::std::convert::TryFrom for #enum_ident { + type Error = (); + + fn try_from(value: u8) -> Result { + #(const #field_consts: u8 = #enum_ident::#field_variants as u8;)* + match value { + #(#field_consts => Ok(Self::#field_variants),)* + 255 => Ok(Self::Label), + _ => Err(()), + } + } + } + + impl ::std::fmt::Display for #enum_ident { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.pad(self.to_str()) + } + } + + impl ::std::str::FromStr for #enum_ident { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + #(#field_names => Ok(Self::#field_variants),)* + "label" => Ok(Self::Label), + _ => Err(()), + } + } + } + }; + } +} + /// Create the `new` function for the element. fn create_new_func(element: &Elem) -> TokenStream { let relevant = element @@ -247,33 +451,146 @@ fn create_new_func(element: &Elem) -> TokenStream { let params = relevant.clone().map(|Field { ident, ty, .. }| { quote! { #ident: #ty } }); - let builder_calls = relevant.map(|Field { ident, with_ident, .. }| { - quote! { .#with_ident(#ident) } + let required = relevant.map(|Field { ident, .. }| { + quote! { #ident } }); + let defaults = element + .fields + .iter() + .filter(|field| !field.external && (field.synthesized || !field.inherent())) + .map(|Field { ident, .. }| { + quote! { #ident: None } + }); + + let label_and_location = element.unless_capability("Unlabellable", || { + quote! { + location: None, + label: None, + prepared: false, + } + }); + quote! { /// Create a new element. pub fn new(#(#params),*) -> Self { - Self(::typst::model::Content::new( - ::elem() + Self { + span: ::typst::syntax::Span::detached(), + #label_and_location + guards: ::std::vec::Vec::with_capacity(0), + #(#required,)* + #(#defaults,)* + } + } + } +} + +/// Create a builder pattern method for a field. +fn create_with_field_method(field: &Field) -> TokenStream { + let Field { vis, ident, with_ident, name, ty, .. } = field; + let doc = format!("Set the [`{}`](Self::{}) field.", name, ident); + + let set = if field.inherent() { + quote! { self.#ident = #ident; } + } else { + quote! { self.#ident = Some(#ident); } + }; + quote! { + #[doc = #doc] + #vis fn #with_ident(mut self, #ident: #ty) -> Self { + #set + self + } + } +} + +/// Create a set-style method for a field. +fn create_push_field_method(field: &Field) -> TokenStream { + let Field { vis, ident, push_ident, name, ty, .. } = field; + let doc = format!("Push the [`{}`](Self::{}) field.", name, ident); + let set = if field.inherent() && !field.synthesized { + quote! { self.#ident = #ident; } + } else { + quote! { self.#ident = Some(#ident); } + }; + quote! { + #[doc = #doc] + #vis fn #push_ident(&mut self, #ident: #ty) { + #set + } + } +} + +/// Create a setter method for a field. +fn create_set_field_method(element: &Elem, field: &Field) -> TokenStream { + let model = quote! { ::typst::model }; + let elem = &element.ident; + let Field { vis, ident, set_ident, enum_ident, ty, name, .. } = field; + let doc = format!("Create a style property for the `{}` field.", name); + quote! { + #[doc = #doc] + #vis fn #set_ident(#ident: #ty) -> ::typst::model::Style { + ::typst::model::Style::Property(::typst::model::Property::new( + ::elem(), + <#elem as #model::ElementFields>::Fields::#enum_ident as u8, + #ident, )) - #(#builder_calls)* + } + } +} + +/// Create a style chain access method for a field. +fn create_field_in_method(element: &Elem, field: &Field) -> TokenStream { + let Field { vis, ident_in, name, output, .. } = field; + let doc = format!("Access the `{}` field in the given style chain.", name); + let access = create_style_chain_access(element, field, quote! { None }); + + let output = if field.borrowed { + quote! { &#output } + } else { + quote! { #output } + }; + + quote! { + #[doc = #doc] + #vis fn #ident_in(styles: ::typst::model::StyleChain) -> #output { + #access } } } /// Create an accessor methods for a field. -fn create_field_method(field: &Field) -> TokenStream { - let Field { vis, docs, ident, name, output, .. } = field; - if field.inherent() || field.synthesized { +fn create_field_method(element: &Elem, field: &Field) -> TokenStream { + let Field { vis, docs, ident, output, .. } = field; + if field.inherent() && !field.synthesized { quote! { #[doc = #docs] #[track_caller] - #vis fn #ident(&self) -> #output { - self.0.expect_field(#name) + #vis fn #ident(&self) -> &#output { + &self.#ident + } + } + } else if field.synthesized { + quote! { + #[doc = #docs] + #[track_caller] + #vis fn #ident(&self) -> &#output { + self.#ident.as_ref().unwrap() + } + } + } else if field.borrowed { + let access = + create_style_chain_access(element, field, quote! { self.#ident.as_ref() }); + + quote! { + #[doc = #docs] + #vis fn #ident<'a>(&'a self, styles: ::typst::model::StyleChain<'a>) -> &'a #output { + #access } } } else { - let access = create_style_chain_access(field, quote! { self.0.field(#name) }); + let access = + create_style_chain_access(element, field, quote! { self.#ident.as_ref() }); + quote! { #[doc = #docs] #vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output { @@ -284,84 +601,48 @@ fn create_field_method(field: &Field) -> TokenStream { } /// Create a style chain access method for a field. -fn create_field_in_method(field: &Field) -> TokenStream { - let Field { vis, ident_in, name, output, .. } = field; - let doc = format!("Access the `{}` field in the given style chain.", name); - let access = create_style_chain_access(field, quote! { None }); - quote! { - #[doc = #doc] - #vis fn #ident_in(styles: ::typst::model::StyleChain) -> #output { - #access - } - } -} +fn create_style_chain_access( + element: &Elem, + field: &Field, + inherent: TokenStream, +) -> TokenStream { + let model = quote! { ::typst::model }; + let elem = &element.ident; -/// Create a style chain access method for a field. -fn create_style_chain_access(field: &Field, inherent: TokenStream) -> TokenStream { - let Field { name, ty, default, .. } = field; - let getter = match (field.fold, field.resolve) { - (false, false) => quote! { get }, - (false, true) => quote! { get_resolve }, - (true, false) => quote! { get_fold }, - (true, true) => quote! { get_resolve_fold }, + let Field { ty, default, enum_ident, .. } = field; + let getter = match (field.fold, field.resolve, field.borrowed) { + (false, false, false) => quote! { get }, + (false, false, true) => quote! { get_borrowed }, + (false, true, _) => quote! { get_resolve }, + (true, false, _) => quote! { get_fold }, + (true, true, _) => quote! { get_resolve_fold }, }; + let (init, default) = field.fold.then(|| (None, quote! { || #default })).unwrap_or_else(|| ( + Some(quote! { + static DEFAULT: ::once_cell::sync::Lazy<#ty> = ::once_cell::sync::Lazy::new(|| #default); + }), + quote! { &DEFAULT }, + )); + quote! { + #init styles.#getter::<#ty>( ::elem(), - #name, + <#elem as #model::ElementFields>::Fields::#enum_ident as u8, #inherent, - || #default, + #default, ) } } -/// Create a builder pattern method for a field. -fn create_with_field_method(field: &Field) -> TokenStream { - let Field { vis, ident, with_ident, name, ty, .. } = field; - let doc = format!("Set the [`{}`](Self::{}) field.", name, ident); - quote! { - #[doc = #doc] - #vis fn #with_ident(mut self, #ident: #ty) -> Self { - Self(self.0.with_field(#name, #ident)) - } - } -} - -/// Create a set-style method for a field. -fn create_push_field_method(field: &Field) -> TokenStream { - let Field { vis, ident, push_ident, name, ty, .. } = field; - let doc = format!("Push the [`{}`](Self::{}) field.", name, ident); - quote! { - #[doc = #doc] - #vis fn #push_ident(&mut self, #ident: #ty) { - self.0.push_field(#name, #ident); - } - } -} - -/// Create a setter method for a field. -fn create_set_field_method(field: &Field) -> TokenStream { - let Field { vis, ident, set_ident, name, ty, .. } = field; - let doc = format!("Create a style property for the `{}` field.", name); - quote! { - #[doc = #doc] - #vis fn #set_ident(#ident: #ty) -> ::typst::model::Style { - ::typst::model::Style::Property(::typst::model::Property::new( - ::elem(), - #name, - #ident, - )) - } - } -} - -/// Create the element's `Pack` implementation. -fn create_pack_impl(element: &Elem) -> TokenStream { +/// Creates the element's `Pack` implementation. +fn create_native_elem_impl(element: &Elem) -> TokenStream { let eval = quote! { ::typst::eval }; let model = quote! { ::typst::model }; let Elem { name, ident, title, scope, keywords, docs, .. } = element; + let vtable_func = create_vtable_func(element); let params = element .fields @@ -375,6 +656,184 @@ fn create_pack_impl(element: &Elem) -> TokenStream { quote! { #eval::Scope::new() } }; + // Fields that can be accessed using the `field` method. + let field_matches = element.visible_fields().map(|field| { + let elem = &element.ident; + let name = &field.enum_ident; + let field_ident = &field.ident; + + quote! { + <#elem as #model::ElementFields>::Fields::#name => Some( + ::typst::eval::IntoValue::into_value(self.#field_ident.clone()) + ), + } + }); + + // Fields that can be set using the `set_field` method. + let field_set_matches = element.visible_fields() + .filter(|field| field.settable() && !field.synthesized).map(|field| { + let elem = &element.ident; + let name = &field.enum_ident; + let field_ident = &field.ident; + + quote! { + <#elem as #model::ElementFields>::Fields::#name => { + self.#field_ident = Some(::typst::eval::FromValue::from_value(value)?); + return Ok(()); + } + } + }); + + // Fields that are inherent. + let field_inherent_matches = element + .visible_fields() + .filter(|field| field.inherent()) + .map(|field| { + let elem = &element.ident; + let name = &field.enum_ident; + let field_ident = &field.ident; + + quote! { + <#elem as #model::ElementFields>::Fields::#name => { + self.#field_ident = ::typst::eval::FromValue::from_value(value)?; + return Ok(()); + } + } + }); + + // Fields that cannot be set or are internal create an error. + let field_not_set_matches = element + .real_fields() + .filter(|field| field.internal || field.synthesized) + .map(|field| { + let elem = &element.ident; + let ident = &field.enum_ident; + let field_name = &field.name; + if field.internal { + // Internal fields create an error that they are unknown. + let unknown_field = format!("unknown field `{field_name}` on `{name}`"); + quote! { + <#elem as #model::ElementFields>::Fields::#ident => ::typst::diag::bail!(#unknown_field), + } + } else { + // Fields that cannot be set create an error that they are not settable. + let not_settable = format!("cannot set `{field_name}` on `{name}`"); + quote! { + <#elem as #model::ElementFields>::Fields::#ident => ::typst::diag::bail!(#not_settable), + } + } + }); + + // Statistically compute whether we need preparation or not. + let needs_preparation = element + .unless_capability("Unlabellable", || { + element + .capabilities + .iter() + .any(|capability| capability == "Locatable" || capability == "Synthesize") + .then(|| quote! { !self.prepared }) + .unwrap_or_else(|| quote! { self.label().is_some() && !self.prepared }) + }) + .unwrap_or_else(|| { + assert!(element.capabilities.iter().all(|capability| capability + != "Locatable" + && capability != "Synthesize")); + quote! { false } + }); + + // Creation of the fields dictionary for inherent fields. + let field_dict = element.inherent_fields().clone().map(|field| { + let name = &field.name; + let field_ident = &field.ident; + + let field_call = if name.len() > 15 { + quote! { EcoString::from(#name).into() } + } else { + quote! { EcoString::inline(#name).into() } + }; + + quote! { + fields.insert( + #field_call, + ::typst::eval::IntoValue::into_value(self.#field_ident.clone()) + ); + } + }); + + // Creation of the fields dictionary for optional fields. + let field_opt_dict = element + .visible_fields() + .filter(|field| !field.inherent()) + .clone() + .map(|field| { + let name = &field.name; + let field_ident = &field.ident; + + let field_call = if name.len() > 15 { + quote! { EcoString::from(#name).into() } + } else { + quote! { EcoString::inline(#name).into() } + }; + + quote! { + if let Some(value) = &self.#field_ident { + fields.insert( + #field_call, + ::typst::eval::IntoValue::into_value(value.clone()) + ); + } + } + }); + + let location = element + .unless_capability("Unlabellable", || quote! { self.location }) + .unwrap_or_else(|| quote! { None }); + + let set_location = element + .unless_capability("Unlabellable", || { + quote! { + self.location = Some(location); + } + }) + .unwrap_or_else(|| quote! { drop(location) }); + + let label = element + .unless_capability("Unlabellable", || quote! { self.label }) + .unwrap_or_else(|| quote! { None }); + + let set_label = element + .unless_capability("Unlabellable", || { + quote! { + self.label = Some(label); + } + }) + .unwrap_or_else(|| quote! { drop(label) }); + + let label_field = element + .unless_capability("Unlabellable", || { + quote! { + self.label().map(::typst::eval::Value::Label) + } + }) + .unwrap_or_else(|| quote! { None }); + + let mark_prepared = element + .unless_capability("Unlabellable", || quote! { self.prepared = true; }) + .unwrap_or_else(|| quote! {}); + + let prepared = element + .unless_capability("Unlabellable", || quote! { self.prepared }) + .unwrap_or_else(|| quote! { true }); + + let local_name = element + .if_capability( + "LocalName", + || quote! { Some(<#ident as ::typst::model::LocalName>::local_name) }, + ) + .unwrap_or_else(|| quote! { None }); + + let unknown_field = format!("unknown field {{}} on {}", name); + let label_error = format!("cannot set label on {}", name); let data = quote! { #model::NativeElementData { name: #name, @@ -384,6 +843,15 @@ fn create_pack_impl(element: &Elem) -> TokenStream { construct: <#ident as #model::Construct>::construct, set: <#ident as #model::Set>::set, vtable: #vtable_func, + field_id: |name| + < + <#ident as #model::ElementFields>::Fields as ::std::str::FromStr + >::from_str(name).ok().map(|id| id as u8), + field_name: |id| + < + <#ident as #model::ElementFields>::Fields as ::std::convert::TryFrom + >::try_from(id).ok().map(<#ident as #model::ElementFields>::Fields::to_str), + local_name: #local_name, scope: #eval::Lazy::new(|| #scope), params: #eval::Lazy::new(|| ::std::vec![#(#params),*]) } @@ -396,46 +864,266 @@ fn create_pack_impl(element: &Elem) -> TokenStream { &DATA } - fn pack(self) -> #model::Content { - self.0 + fn dyn_elem(&self) -> #model::Element { + #model::Element::of::() } - fn unpack(content: &#model::Content) -> ::std::option::Option<&Self> { - // Safety: Elements are #[repr(transparent)]. - content.is::().then(|| unsafe { - ::std::mem::transmute(content) - }) + fn dyn_hash(&self, mut hasher: &mut dyn ::std::hash::Hasher) { + ::hash(self, &mut hasher); + } + + fn dyn_eq(&self, other: &#model::Content) -> bool { + if let Some(other) = other.to::() { + ::eq(self, other) + } else { + false + } + } + + fn dyn_clone(&self) -> ::std::sync::Arc { + ::std::sync::Arc::new(Clone::clone(self)) + } + + fn as_any(&self) -> &dyn ::std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any { + self + } + + fn into_any(self: ::std::sync::Arc) -> ::std::sync::Arc { + self + } + + fn span(&self) -> ::typst::syntax::Span { + self.span + } + + fn set_span(&mut self, span: ::typst::syntax::Span) { + if self.span().is_detached() { + self.span = span; + } + } + + fn label(&self) -> Option<#model::Label> { + #label + } + + fn set_label(&mut self, label: #model::Label) { + #set_label + } + + fn location(&self) -> Option<#model::Location> { + #location + } + + fn set_location(&mut self, location: #model::Location) { + #set_location + } + + fn push_guard(&mut self, guard: #model::Guard) { + self.guards.push(guard); + } + + fn is_guarded(&self, guard: #model::Guard) -> bool { + self.guards.contains(&guard) + } + + fn is_pristine(&self) -> bool { + self.guards.is_empty() + } + + fn mark_prepared(&mut self) { + #mark_prepared + } + + fn needs_preparation(&self) -> bool { + #needs_preparation + } + + fn is_prepared(&self) -> bool { + #prepared + } + + fn field(&self, id: u8) -> Option<::typst::eval::Value> { + let id = <#ident as #model::ElementFields>::Fields::try_from(id).ok()?; + match id { + <#ident as #model::ElementFields>::Fields::Label => #label_field, + #(#field_matches)* + _ => None, + } + } + + fn fields(&self) -> Dict { + let mut fields = Dict::new(); + #(#field_dict)* + #(#field_opt_dict)* + fields + } + + fn set_field(&mut self, id: u8, value: Value) -> ::typst::diag::StrResult<()> { + let id = <#ident as #model::ElementFields>::Fields::try_from(id) + .map_err(|_| ::ecow::eco_format!(#unknown_field, id))?; + match id { + #(#field_set_matches)* + #(#field_inherent_matches)* + #(#field_not_set_matches)* + <#ident as #model::ElementFields>::Fields::Label => { + ::typst::diag::bail!(#label_error); + } + } } } } } -/// Create the element's casting vtable. -fn create_vtable_func(element: &Elem) -> TokenStream { +/// Creates the element's `Construct` implementation. +fn create_construct_impl(element: &Elem) -> TokenStream { let ident = &element.ident; - let relevant = element.capabilities.iter().filter(|&ident| ident != "Construct"); + let pre = element.construct_fields().map(|field| { + let (prefix, value) = create_field_parser(field); + let ident = &field.ident; + quote! { + #prefix + let #ident = #value; + } + }); + + let handlers = + element + .construct_fields() + .filter(|field| field.settable()) + .map(|field| { + let push_ident = &field.push_ident; + let ident = &field.ident; + quote! { + if let Some(value) = #ident { + element.#push_ident(value); + } + } + }); + + let defaults = element + .construct_fields() + .filter(|field| !field.settable()) + .map(|field| &field.ident); + + quote! { + impl ::typst::model::Construct for #ident { + fn construct( + vm: &mut ::typst::eval::Vm, + args: &mut ::typst::eval::Args, + ) -> ::typst::diag::SourceResult<::typst::model::Content> { + #(#pre)* + + let mut element = Self::new(#(#defaults),*); + + #(#handlers)* + + Ok(::typst::model::Content::new(element)) + } + } + } +} + +/// Creates the element's `Set` implementation. +fn create_set_impl(element: &Elem) -> TokenStream { + let ident = &element.ident; + let handlers = element.settable_fields().map(|field| { + let set_ident = &field.set_ident; + let (prefix, value) = create_field_parser(field); + quote! { + #prefix + if let Some(value) = #value { + styles.set(Self::#set_ident(value)); + } + } + }); + + quote! { + impl ::typst::model::Set for #ident { + fn set( + vm: &mut Vm, + args: &mut ::typst::eval::Args, + ) -> ::typst::diag::SourceResult<::typst::model::Styles> { + let mut styles = ::typst::model::Styles::new(); + #(#handlers)* + Ok(styles) + } + } + } +} + +/// Creates the element's `Locatable` implementation. +fn create_locatable_impl(element: &Elem) -> TokenStream { + let ident = &element.ident; + quote! { impl ::typst::model::Locatable for #ident {} } +} + +/// Creates the element's `PartialEq` implementation. +fn create_partial_eq_impl(element: &Elem) -> TokenStream { + let ident = &element.ident; + let all = element.eq_fields().map(|field| &field.ident).collect::>(); + + let empty = all.is_empty().then(|| quote! { true }); + quote! { + impl PartialEq for #ident { + fn eq(&self, other: &Self) -> bool { + #empty + #(self.#all == other.#all)&&* + } + } + } +} + +/// Creates the element's `Repr` implementation. +fn create_repr_impl(element: &Elem) -> TokenStream { + let ident = &element.ident; + let repr_format = format!("{}{{}}", element.name); + quote! { + impl ::typst::eval::Repr for #ident { + fn repr(&self) -> ::ecow::EcoString { + let fields = self.fields().into_iter() + .map(|(name, value)| eco_format!("{}: {}", name, value.repr())) + .collect::>(); + ::ecow::eco_format!(#repr_format, ::typst::util::pretty_array_like(&fields, false)) + } + } + } +} + +/// Creates the element's casting vtable. +fn create_vtable_func(element: &Elem) -> TokenStream { + // Forbidden capabilities (i.e capabilities that are not object safe). + const FORBIDDEN: &[&str] = &["Construct", "PartialEq", "Hash", "LocalName"]; + + let ident = &element.ident; + let relevant = element + .capabilities + .iter() + .filter(|&ident| !FORBIDDEN.contains(&(&ident.to_string() as &str))); let checks = relevant.map(|capability| { quote! { if id == ::std::any::TypeId::of::() { - return Some(unsafe { - ::typst::util::fat::vtable(&null as &dyn #capability) - }); + let vtable = unsafe { + let dangling = ::std::ptr::NonNull::<#ident>::dangling().as_ptr() as *const dyn #capability; + ::typst::util::fat::vtable(dangling) + }; + return Some(vtable); } } }); quote! { |id| { - let null = Self(::typst::model::Content::new( - <#ident as ::typst::model::NativeElement>::elem() - )); #(#checks)* None } } } -/// Create a parameter info for a field. +/// Creates a parameter info for a field. fn create_param_info(field: &Field) -> TokenStream { let Field { name, @@ -480,88 +1168,6 @@ fn create_param_info(field: &Field) -> TokenStream { } } -/// Create the element's `Construct` implementation. -fn create_construct_impl(element: &Elem) -> TokenStream { - let ident = &element.ident; - let handlers = element - .fields - .iter() - .filter(|field| { - !field.external - && !field.synthesized - && (!field.internal || field.parse.is_some()) - }) - .map(|field| { - let push_ident = &field.push_ident; - let (prefix, value) = create_field_parser(field); - if field.settable() { - quote! { - #prefix - if let Some(value) = #value { - element.#push_ident(value); - } - } - } else { - quote! { - #prefix - element.#push_ident(#value); - } - } - }); - - quote! { - impl ::typst::model::Construct for #ident { - fn construct( - vm: &mut ::typst::eval::Vm, - args: &mut ::typst::eval::Args, - ) -> ::typst::diag::SourceResult<::typst::model::Content> { - let mut element = Self(::typst::model::Content::new( - ::elem() - )); - #(#handlers)* - Ok(element.0) - } - } - } -} - -/// Create the element's `Set` implementation. -fn create_set_impl(element: &Elem) -> TokenStream { - let ident = &element.ident; - let handlers = element - .fields - .iter() - .filter(|field| { - !field.external - && !field.synthesized - && field.settable() - && (!field.internal || field.parse.is_some()) - }) - .map(|field| { - let set_ident = &field.set_ident; - let (prefix, value) = create_field_parser(field); - quote! { - #prefix - if let Some(value) = #value { - styles.set(Self::#set_ident(value)); - } - } - }); - - quote! { - impl ::typst::model::Set for #ident { - fn set( - vm: &mut Vm, - args: &mut ::typst::eval::Args, - ) -> ::typst::diag::SourceResult<::typst::model::Styles> { - let mut styles = ::typst::model::Styles::new(); - #(#handlers)* - Ok(styles) - } - } - } -} - /// Create argument parsing code for a field. fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) { if let Some(BlockWithReturn { prefix, expr }) = &field.parse { diff --git a/crates/typst-macros/src/lib.rs b/crates/typst-macros/src/lib.rs index 52f3e237e..cbeb4ba14 100644 --- a/crates/typst-macros/src/lib.rs +++ b/crates/typst-macros/src/lib.rs @@ -189,6 +189,9 @@ pub fn ty(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { /// - `#[synthesized]`: The field cannot be specified in a constructor or set /// rule. Instead, it is added to an element before its show rule runs /// through the `Synthesize` trait. +/// - `#[variant]`: Allows setting the ID of a field's variant. This is used +/// for fields that are accessed in `typst` and not `typst-library`. It gives +/// the field a stable ID that can be used to access it. #[proc_macro_attribute] pub fn elem(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { let item = syn::parse_macro_input!(item as syn::ItemStruct); diff --git a/crates/typst/Cargo.toml b/crates/typst/Cargo.toml index c6c973982..1e05a2d7f 100644 --- a/crates/typst/Cargo.toml +++ b/crates/typst/Cargo.toml @@ -28,6 +28,7 @@ fontdb = { version = "0.15", default-features = false } image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] } indexmap = { version = "2", features = ["serde"] } kurbo = "0.9" +lasso = { version = "0.7.2", features = ["ahasher", "multi-threaded"] } log = "0.4" miniz_oxide = "0.7" once_cell = "1" @@ -57,6 +58,7 @@ xmp-writer = "0.2" time = { version = "0.3.20", features = ["std", "formatting", "macros", "parsing"] } wasmi = "0.31.0" xmlparser = "0.13.5" +smallvec = { version = "1.11.1", features = ["union", "const_generics", "const_new"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] stacker = "0.1.15" diff --git a/crates/typst/src/doc.rs b/crates/typst/src/doc.rs index 1c23fd756..742c6245a 100644 --- a/crates/typst/src/doc.rs +++ b/crates/typst/src/doc.rs @@ -190,13 +190,13 @@ impl Frame { /// /// This panics if the layer is greater than the number of layers present. #[track_caller] - pub fn insert(&mut self, layer: usize, pos: Point, items: FrameItem) { - Arc::make_mut(&mut self.items).insert(layer, (pos, items)); + pub fn insert(&mut self, layer: usize, pos: Point, item: FrameItem) { + Arc::make_mut(&mut self.items).insert(layer, (pos, item)); } /// Add an item at a position in the background. pub fn prepend(&mut self, pos: Point, item: FrameItem) { - Arc::make_mut(&mut self.items).insert(0, (pos, item)); + self.insert(0, pos, item); } /// Add multiple items at a position in the background. diff --git a/crates/typst/src/eval/array.rs b/crates/typst/src/eval/array.rs index d5757d04b..772db535a 100644 --- a/crates/typst/src/eval/array.rs +++ b/crates/typst/src/eval/array.rs @@ -5,6 +5,7 @@ use std::ops::{Add, AddAssign}; use ecow::{eco_format, EcoString, EcoVec}; use serde::{Deserialize, Serialize}; +use smallvec::SmallVec; use super::{ cast, func, ops, scope, ty, Args, Bytes, CastInfo, FromValue, Func, IntoValue, @@ -907,18 +908,44 @@ impl Reflect for Vec { } } +impl Reflect for SmallVec<[T; N]> { + fn input() -> CastInfo { + Array::input() + } + + fn output() -> CastInfo { + Array::output() + } + + fn castable(value: &Value) -> bool { + Array::castable(value) + } +} + impl IntoValue for Vec { fn into_value(self) -> Value { Value::Array(self.into_iter().map(IntoValue::into_value).collect()) } } +impl IntoValue for SmallVec<[T; N]> { + fn into_value(self) -> Value { + Value::Array(self.into_iter().map(IntoValue::into_value).collect()) + } +} + impl FromValue for Vec { fn from_value(value: Value) -> StrResult { value.cast::()?.into_iter().map(Value::cast).collect() } } +impl FromValue for SmallVec<[T; N]> { + fn from_value(value: Value) -> StrResult { + value.cast::()?.into_iter().map(Value::cast).collect() + } +} + /// The error message when the array is empty. #[cold] fn array_is_empty() -> EcoString { diff --git a/crates/typst/src/eval/cast.rs b/crates/typst/src/eval/cast.rs index 0965c8fae..eeb632f9f 100644 --- a/crates/typst/src/eval/cast.rs +++ b/crates/typst/src/eval/cast.rs @@ -1,10 +1,14 @@ pub use typst_macros::{cast, Cast}; -use unicode_math_class::MathClass; +use std::borrow::Cow; use std::fmt::Write; +use std::hash::Hash; use std::ops::Add; +use comemo::Prehashed; use ecow::{eco_format, EcoString}; +use smallvec::SmallVec; +use unicode_math_class::MathClass; use super::{Repr, Type, Value}; use crate::diag::{At, SourceResult, StrResult}; @@ -80,6 +84,20 @@ impl Reflect for Spanned { } } +impl Reflect for Prehashed { + fn input() -> CastInfo { + T::input() + } + + fn output() -> CastInfo { + T::output() + } + + fn castable(value: &Value) -> bool { + T::castable(value) + } +} + impl Reflect for StrResult { fn input() -> CastInfo { T::input() @@ -150,12 +168,24 @@ impl IntoValue for Value { } } +impl IntoValue for Cow<'_, T> { + fn into_value(self) -> Value { + self.into_owned().into_value() + } +} + impl IntoValue for Spanned { fn into_value(self) -> Value { self.v.into_value() } } +impl IntoValue for Prehashed { + fn into_value(self) -> Value { + self.into_inner().into_value() + } +} + /// Cast a Rust type or result into a [`SourceResult`]. /// /// Converts `T`, [`StrResult`], or [`SourceResult`] into @@ -203,6 +233,12 @@ impl FromValue for Value { } } +impl FromValue for Prehashed { + fn from_value(value: Value) -> StrResult { + Ok(Self::new(T::from_value(value)?)) + } +} + impl FromValue> for T { fn from_value(value: Spanned) -> StrResult { T::from_value(value.v) @@ -217,7 +253,7 @@ impl FromValue> for Spanned { } /// Describes a possible value for a cast. -#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] +#[derive(Debug, Clone, PartialEq, Hash, PartialOrd)] pub enum CastInfo { /// Any value is okay. Any, @@ -335,7 +371,12 @@ impl Container for Vec { type Inner = T; } +impl Container for SmallVec<[T; N]> { + type Inner = T; +} + /// An uninhabitable type. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum Never {} impl Reflect for Never { diff --git a/crates/typst/src/eval/func.rs b/crates/typst/src/eval/func.rs index f24b714b2..8660abacb 100644 --- a/crates/typst/src/eval/func.rs +++ b/crates/typst/src/eval/func.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use std::sync::Arc; use comemo::{Prehashed, Tracked, TrackedMut}; -use ecow::EcoString; +use ecow::{eco_format, EcoString}; use once_cell::sync::Lazy; use super::{ @@ -368,10 +368,25 @@ impl Func { ) -> StrResult { let fields = args.to_named(); args.items.retain(|arg| arg.name.is_none()); - Ok(self + + let element = self .element() - .ok_or("`where()` can only be called on element functions")? - .where_(fields)) + .ok_or("`where()` can only be called on element functions")?; + + let fields = fields + .into_iter() + .map(|(key, value)| { + element.field_id(&key).map(|id| (id, value)).ok_or_else(|| { + eco_format!( + "element `{}` does not have field `{}`", + element.name(), + key + ) + }) + }) + .collect::>>()?; + + Ok(element.where_(fields)) } } diff --git a/crates/typst/src/eval/library.rs b/crates/typst/src/eval/library.rs index 5d65e1937..c712adaee 100644 --- a/crates/typst/src/eval/library.rs +++ b/crates/typst/src/eval/library.rs @@ -45,7 +45,7 @@ pub struct LangItems { /// The text element. pub text_elem: Element, /// Get the string if this is a text element. - pub text_str: fn(&Content) -> Option, + pub text_str: fn(&Content) -> Option<&EcoString>, /// A smart quote: `'` or `"`. pub smart_quote: fn(double: bool) -> Content, /// A paragraph break. diff --git a/crates/typst/src/eval/mod.rs b/crates/typst/src/eval/mod.rs index ede21c528..8ac2c40bc 100644 --- a/crates/typst/src/eval/mod.rs +++ b/crates/typst/src/eval/mod.rs @@ -663,7 +663,7 @@ impl Eval for ast::Label<'_> { #[tracing::instrument(name = "Label::eval", skip_all)] fn eval(self, _: &mut Vm) -> SourceResult { - Ok(Value::Label(Label(self.get().into()))) + Ok(Value::Label(Label::new(self.get()))) } } @@ -672,7 +672,7 @@ impl Eval for ast::Ref<'_> { #[tracing::instrument(name = "Ref::eval", skip_all)] fn eval(self, vm: &mut Vm) -> SourceResult { - let label = Label(self.target().into()); + let label = Label::new(self.target()); let supplement = self.supplement().map(|block| block.eval(vm)).transpose()?; Ok((vm.items.reference)(label, supplement)) } @@ -1295,7 +1295,7 @@ impl Eval for ast::Args<'_> { type Output = Args; fn eval(self, vm: &mut Vm) -> SourceResult { - let mut items = EcoVec::new(); + let mut items = EcoVec::with_capacity(self.items().count()); for arg in self.items() { let span = arg.span(); diff --git a/crates/typst/src/eval/str.rs b/crates/typst/src/eval/str.rs index 3bbfcc0ba..97d158704 100644 --- a/crates/typst/src/eval/str.rs +++ b/crates/typst/src/eval/str.rs @@ -617,7 +617,7 @@ cast! { .map_err(|_| "bytes are not valid utf-8")? .into() ), - v: Label => Self::Str(v.0.into()), + v: Label => Self::Str(v.as_str().into()), v: Type => Self::Str(v.long_name().into()), v: Str => Self::Str(v), } diff --git a/crates/typst/src/eval/value.rs b/crates/typst/src/eval/value.rs index 19a5610a5..b87c85924 100644 --- a/crates/typst/src/eval/value.rs +++ b/crates/typst/src/eval/value.rs @@ -158,7 +158,7 @@ impl Value { Self::Symbol(symbol) => symbol.clone().modified(field).map(Self::Symbol), Self::Version(version) => version.component(field).map(Self::Int), Self::Dict(dict) => dict.get(field).cloned(), - Self::Content(content) => content.get(field), + Self::Content(content) => content.field_by_name(field), Self::Type(ty) => ty.field(field).cloned(), Self::Func(func) => func.field(field).cloned(), Self::Module(module) => module.field(field).cloned(), diff --git a/crates/typst/src/export/pdf/outline.rs b/crates/typst/src/export/pdf/outline.rs index d29852fd9..f6f9f09d8 100644 --- a/crates/typst/src/export/pdf/outline.rs +++ b/crates/typst/src/export/pdf/outline.rs @@ -113,11 +113,11 @@ struct HeadingNode { impl HeadingNode { fn leaf(element: Content) -> Self { HeadingNode { - level: element.expect_field::("level"), + level: element.expect_field_by_name::("level"), // 'bookmarked' set to 'auto' falls back to the value of 'outlined'. bookmarked: element - .expect_field::>("bookmarked") - .unwrap_or_else(|| element.expect_field::("outlined")), + .expect_field_by_name::>("bookmarked") + .unwrap_or_else(|| element.expect_field_by_name::("outlined")), element, children: Vec::new(), } @@ -158,7 +158,7 @@ fn write_outline_item( outline.count(-(node.children.len() as i32)); } - let body = node.element.expect_field::("body"); + let body = node.element.expect_field_by_name::("body"); outline.title(TextStr(body.plain_text().trim())); let loc = node.element.location().unwrap(); diff --git a/crates/typst/src/model/block.rs b/crates/typst/src/model/block.rs new file mode 100644 index 000000000..92e2a18e4 --- /dev/null +++ b/crates/typst/src/model/block.rs @@ -0,0 +1,82 @@ +use std::any::Any; +use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; + +/// A block storage for storing stylechain values either on the stack (if they +/// fit) or on the heap. +/// +/// We're using a `Box` since values will either be contained in an `Arc` and +/// therefore already on the heap or they will be small enough that we can just +/// clone them. +pub struct Block(Box); + +impl Block { + /// Creates a new block. + pub fn new(value: T) -> Self { + Self(Box::new(value)) + } + + /// Downcasts the block to the specified type. + pub fn downcast(&self) -> Option<&T> { + self.0.as_any().downcast_ref() + } + + /// Downcasts mutably the block to the specified type. + pub fn downcast_mut(&mut self) -> Option<&mut T> { + self.0.as_any_mut().downcast_mut() + } +} + +impl Hash for Block { + fn hash(&self, state: &mut H) { + self.0.dyn_hash(state); + } +} + +impl Clone for Block { + fn clone(&self) -> Self { + self.0.dyn_clone() + } +} + +impl Debug for Block { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +/// A value that can be stored in a block. +/// +/// Auto derived for all types that implement [`Any`], [`Clone`], [`Hash`], +/// [`Debug`], [`Send`] and [`Sync`]. +pub trait Blockable: Debug + Send + Sync + 'static { + /// Equivalent to `downcast_ref` for the block. + fn as_any(&self) -> &dyn Any; + + /// Equivalent to `downcast_mut` for the block. + fn as_any_mut(&mut self) -> &mut dyn Any; + + /// Equivalent to [`Hash`] for the block. + fn dyn_hash(&self, state: &mut dyn Hasher); + + /// Equivalent to [`Clone`] for the block. + fn dyn_clone(&self) -> Block; +} + +impl Blockable for T { + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn dyn_hash(&self, mut state: &mut dyn Hasher) { + self.hash(&mut state); + } + + fn dyn_clone(&self) -> Block { + Block(Box::new(self.clone())) + } +} diff --git a/crates/typst/src/model/content.rs b/crates/typst/src/model/content.rs index c5aa4e58a..f853fc51d 100644 --- a/crates/typst/src/model/content.rs +++ b/crates/typst/src/model/content.rs @@ -2,14 +2,17 @@ use std::any::TypeId; use std::fmt::Debug; use std::iter::{self, Sum}; use std::ops::{Add, AddAssign}; +use std::sync::Arc; use comemo::Prehashed; -use ecow::{eco_format, EcoString, EcoVec}; +use ecow::{eco_format, EcoString}; use serde::{Serialize, Serializer}; +use smallvec::SmallVec; +use typst_macros::elem; use super::{ - elem, Behave, Behaviour, Element, Guard, Label, Locatable, Location, NativeElement, - Recipe, Selector, Style, Styles, Synthesize, + Behave, Behaviour, Element, Guard, Label, Location, NativeElement, Recipe, Selector, + Style, Styles, }; use crate::diag::{SourceResult, StrResult}; use crate::doc::Meta; @@ -61,35 +64,146 @@ use crate::util::pretty_array_like; /// elements the content is composed of and what fields they have. /// Alternatively, you can inspect the output of the [`repr`]($repr) function. #[ty(scope)] -#[derive(Debug, Clone, Hash)] -#[allow(clippy::derived_hash_with_manual_eq)] -pub struct Content { - elem: Element, - attrs: EcoVec, -} - -/// Attributes that can be attached to content. -#[derive(Debug, Clone, PartialEq, Hash)] -enum Attr { - Span(Span), - Field(EcoString), - Value(Prehashed), - Child(Prehashed), - Styles(Styles), - Prepared, - Guard(Guard), - Location(Location), -} +#[derive(Debug, Clone)] +pub struct Content(Arc); impl Content { - /// Create an empty element. - pub fn new(elem: Element) -> Self { - Self { elem, attrs: EcoVec::new() } + /// Creates a new content from an element. + #[inline] + pub fn new(elem: E) -> Self { + Self(Arc::new(elem)) } - /// Create empty content. + /// Creates a new empty sequence content. + #[inline] pub fn empty() -> Self { - Self::new(SequenceElem::elem()) + Self::new(SequenceElem::default()) + } + + /// Get the element of this content. + #[inline] + pub fn elem(&self) -> Element { + self.0.dyn_elem() + } + + /// Get the span of the content. + #[inline] + pub fn span(&self) -> Span { + self.0.span() + } + + /// Set the span of the content. + pub fn spanned(mut self, span: Span) -> Self { + self.make_mut().set_span(span); + self + } + + /// Get the label of the content. + #[inline] + pub fn label(&self) -> Option