diff options
84 files changed, 2290 insertions, 1064 deletions
@@ -932,6 +932,15 @@ dependencies = [ [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" @@ -1399,6 +1408,17 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -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 0db6eb54..cc9cfc23 100644 --- a/crates/typst-cli/src/query.rs +++ b/crates/typst-cli/src/query.rs @@ -89,7 +89,7 @@ fn format(elements: Vec<Content>, command: &QueryCommand) -> StrResult<String> { 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 32f3df0d..4d12e1c5 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 8da50e5a..32505585 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 d358f851..f90f2aea 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 b348596b..f4fce351 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 dadf0bed..e4767ebf 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 f080f677..9c18266d 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<Content> { - 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 436aacb9..8c491dca 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::<Self>().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 1feee4b8..5c795c81 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<Content>, + pub children: Vec<Prehashed<Content>>, } 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<FootnoteElem>, 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::<FootnoteElem>() 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 8d015782..b29adeb9 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<Fragment> { + 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<Sizing>); +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::<StrResult<_>>()?), } diff --git a/crates/typst-library/src/layout/hide.rs b/crates/typst-library/src/layout/hide.rs index 7f17a7d7..af3d0631 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<Content> { - 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 34e0e6fb..18c89a24 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<Content>), 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 1c7c2610..d31f6841 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::<dyn LayoutRoot>() && !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::<dyn Layout>() @@ -277,14 +278,14 @@ fn realize_block<'a>( && !content.is::<PlaceElem>() && !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::<PagebreakElem>() { 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::<MetaElem>() || content.is::<PlaceElem>() { - 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::<BlockElem>() { @@ -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::<MetaElem>() { 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::<SpaceElem>() @@ -627,7 +628,7 @@ impl<'a> ParBuilder<'a> { || content.to::<EquationElem>().map_or(false, |elem| !elem.block(styles)) || content.is::<BoxElem>() { - 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::<ParbreakElem>()); return true; } @@ -685,7 +686,8 @@ impl<'a> ListBuilder<'a> { .iter() .map(|(item, local)| { let item = item.to::<ListItem>().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::<Vec<_>>(), ) @@ -697,7 +699,8 @@ impl<'a> ListBuilder<'a> { .iter() .map(|(item, local)| { let item = item.to::<EnumItem>().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::<Vec<_>>(), ) @@ -710,9 +713,9 @@ impl<'a> ListBuilder<'a> { .map(|(item, local)| { let item = item.to::<TermItem>().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::<Vec<_>>(), diff --git a/crates/typst-library/src/layout/page.rs b/crates/typst-library/src/layout/page.rs index 495b390d..5f8f90c1 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<Paint>, /// How to [number]($numbering) the pages. @@ -188,6 +190,7 @@ pub struct PageElem { /// /// #lorem(48) /// ``` + #[borrowed] pub numbering: Option<Numbering>, /// The alignment of the page numbering. @@ -233,6 +236,7 @@ pub struct PageElem { /// /// #lorem(19) /// ``` + #[borrowed] pub header: Option<Content>, /// The amount the header is raised into the top margin. @@ -263,6 +267,7 @@ pub struct PageElem { /// /// #lorem(48) /// ``` + #[borrowed] pub footer: Option<Content>, /// 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>, /// 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<Content>, /// 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<Content> { + pub fn resolve(&self, vt: &mut Vt, page: usize) -> SourceResult<Cow<'_, Content>> { 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 10f78ef2..b3d5fb3e 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<Content>, + pub children: Vec<Prehashed<Content>>, } 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<Item<'a>>, /// 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<bool>, /// 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<Linebreaks>, + /// 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<Content>], 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::<TextElem>() { 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::<HElem>() { @@ -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::<LinebreakElem>() { 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<Content>], 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<T: PartialEq>( styles: StyleChain<'_>, - children: &[Content], + children: &[Prehashed<Content>], getter: fn(StyleChain) -> T, ) -> Option<T> { let value = getter(styles); @@ -865,8 +875,8 @@ fn shared_get<T: PartialEq>( /// Find suitable linebreaks. fn linebreak<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> { - 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<Line<'a>> { /// 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<Line<'a>> { - 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<L line: line(vt, p, 0..0, Breakpoint::Mandatory), }]; - let em = TextElem::size_in(p.styles); - + let em = p.size; + let mut lines = Vec::with_capacity(16); breakpoints(p, |end, breakpoint| { let k = table.len(); let eof = end == p.bidi.text.len(); @@ -1071,7 +1081,6 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<L }); // Retrace the best path. - let mut lines = vec![]; let mut idx = table.len() - 1; while idx != 0 { table.truncate(idx + 1); @@ -1151,7 +1160,7 @@ fn line<'a>( 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::<SourceResult<_>>()?; // 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 64cbd9a8..c8e83383 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 37f1ed35..88b6e2cd 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<Content>, Behaviour, StyleChain), styles: StyleChain, ) -> bool { let Some(other) = prev.0.to::<Self>() else { return false }; @@ -173,7 +175,7 @@ impl Behave for VElem { fn larger( &self, - prev: &(Content, Behaviour, StyleChain), + prev: &(Cow<Content>, Behaviour, StyleChain), styles: StyleChain, ) -> bool { let Some(other) = prev.0.to::<Self>() else { return false }; diff --git a/crates/typst-library/src/layout/stack.rs b/crates/typst-library/src/layout/stack.rs index 398341b2..50d1c862 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 056b63a2..9e7da071 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<Option<Paint>>, /// How to align the cells' content. @@ -99,6 +104,7 @@ pub struct TableElem { /// [A], [B], [C], /// ) /// ``` + #[borrowed] pub align: Celled<Smart<Align>>, /// How to [stroke]($stroke) the cells. @@ -151,16 +157,20 @@ impl Layout for TableElem { ) -> SourceResult<Fragment> { 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<T: FromValue> FromValue for Celled<T> { } impl LocalName for TableElem { - fn local_name(&self, lang: Lang, _: Option<Region>) -> &'static str { + fn local_name(lang: Lang, _: Option<Region>) -> &'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 07f17bb0..d4262118 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 a542111b..bedc5635 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::<TextElem>() { - 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 5d1e477e..3e6b69f2 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 d521149e..16e4067a 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 fc8a6c79..d2c5192d 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 59ba057f..2637921c 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 ee582775..bd8d86bf 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 71d8d237..a40ed2bf 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<MathClass>, pub span: Span, - pub meta: Vec<Meta>, + 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 16696f2e..39143620 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 333a5706..b54da5d6 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<T: Numeric = Length> { pub hline: Offsets, pub vline: Offsets, @@ -573,7 +573,7 @@ impl Augment<Abs> { 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<Abs> { type Output = Augment<Abs>; 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<Abs> { 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<isize>); +/// 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::<StrResult<_>>()?), } diff --git a/crates/typst-library/src/math/mod.rs b/crates/typst-library/src/math/mod.rs index 26ab10c6..a4d44d00 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<Region>) -> &'static str { + fn local_name(lang: Lang, region: Option<Region>) -> &'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 152efdf5..b4c36002 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 49ceb75f..13c5c147 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 4d80a235..774fadac 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 5d010c28..aeb83061 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 d429d343..342135ec 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<EcoString>); cast! { @@ -153,11 +153,12 @@ impl BibliographyElem { bail!("multiple bibliographies are not yet supported"); } - Ok(elem.to::<Self>().unwrap().clone()) + Ok(elem.to::<Self>().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<PicoStr>) -> 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<Region>) -> &'static str { + fn local_name(lang: Lang, region: Option<Region>) -> &'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<IndexMap<EcoString, hayagriva::Entry>>, + map: Arc<IndexMap<PicoStr, hayagriva::Entry>>, 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<PicoStr>) -> bool { + self.map.contains_key(&key.into()) } fn entries(&self) -> impl Iterator<Item = &hayagriva::Entry> { @@ -645,7 +641,7 @@ impl<'a> Generator<'a> { let mut driver = BibliographyDriver::new(); for elem in &self.groups { let group = elem.to::<CiteGroup>().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 2e3c4155..a61523d8 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<Content> { 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 3a82a925..59b35577 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<Content> { 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 1ad0bff1..45041a38 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::<UpdateElem>() { - Some(elem) => Some(elem.update()), + Some(elem) => Some(elem.update().clone()), None => match elem.with::<dyn Count>() { - 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<Content> { 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::<UpdateElem>() 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 55c365ac..f6ff6812 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<Option<Datetime>>, /// The page runs. - #[internal] #[variadic] - pub children: Vec<Content>, + pub children: Vec<Prehashed<Content>>, } 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<EcoString>); 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<EcoString>); cast! { diff --git a/crates/typst-library/src/meta/figure.rs b/crates/typst-library/src/meta/figure.rs index 990c9860..bfcc9b44 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<Option<Supplement>>, /// 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::<dyn Figurable>()) - .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::<dyn LocalName>().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<Content> { - 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<Numbering> { @@ -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<Counter>, /// The figure's location. + #[internal] #[synthesized] - pub location: Option<Location>, + pub figure_location: Option<Location>, } 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<Content> { - 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 a8f0b4dd..4306f833 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<Content> { + 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<Location> { match self.body() { FootnoteBody::Reference(label) => { - let element: Prehashed<Content> = vt.introspector.query_label(&label)?; + let element: Prehashed<Content> = vt.introspector.query_label(*label)?; let footnote = element .to::<FootnoteElem>() .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<Content> { 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 34af8238..1a2f4dd0 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<Numbering>, /// 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<Content> { - 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<Numbering> { - 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<Region>) -> &'static str { + fn local_name(lang: Lang, region: Option<Region>) -> &'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 7b68b186..74857f1b 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<Content> { - 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 1019864f..ffe861ab 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::<query>(); } -/// 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<Region>) -> &'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<T: LocalName> LocalNameIn for T {} diff --git a/crates/typst-library/src/meta/outline.rs b/crates/typst-library/src/meta/outline.rs index 9266ba2b..e7332ab3 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<Smart<OutlineIndent>>, /// 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::<dyn Outlinable>()) - .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<Region>) -> &'static str { + fn local_name(lang: Lang, region: Option<Region>) -> &'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<Length>), @@ -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 12e13929..3a463c80 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<Option<Supplement>>, /// 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<Content> { 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<CiteElem> { - 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 137b754e..4f04628f 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::<UpdateElem>().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<Content> { 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 329cbf61..e3163e0f 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 f97e3fbc..e152fbb5 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::<dyn Behave>() .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<Item = &Content> { + pub fn elems(&self) -> impl DoubleEndedIterator<Item = &Cow<'a, Content>> { 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<Content>, StyleChain<'a>) { + pub fn finish(mut self) -> (StyleVec<Cow<'a, Content>>, 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 e6431423..60614820 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 136dfad8..d8167788 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<Content> { - 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<Content> { - 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<Content> { - 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<Content> { - 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 73657345..9f768f11 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<Content> { - 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<Content> { - 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 27dd4a3f..84a68d6f 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<Content> { // 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<FontFamily>); -impl IntoIterator for FontList { - type IntoIter = std::vec::IntoIter<FontFamily>; - 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 86be2416..c555df2d 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<Attribution>, /// 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<Content> { - 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 37e664fd..035ba422 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<QuoteDict>, } diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs index d16659be..4f672be9 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<EcoString>, /// 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<EcoString>, /// The raw file buffer of syntax theme file. #[internal] #[parse(theme_data.map(Some))] + #[borrowed] pub theme_data: Option<Bytes>, /// 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<Content>, + pub lines: Vec<RawLine>, } #[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<Content> { 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<Region>) -> &'static str { + fn local_name(lang: Lang, region: Option<Region>) -> &'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<Content> { - 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<EcoString>); cast! { @@ -681,7 +688,7 @@ fn parse_syntaxes( } #[comemo::memoize] -fn load_theme(path: EcoString, bytes: Bytes) -> StrResult<Arc<synt::Theme>> { +fn load_theme(path: &str, bytes: &Bytes) -> StrResult<Arc<synt::Theme>> { 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 0cfffce0..f7cf90bd 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<Item = FontFamily> + Clone, + mut families: impl Iterator<Item = &'a str> + 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<Item = FontFamily> + Clone { +pub fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone { const FALLBACKS: &[&str] = &[ "linux libertine", "twitter color emoji", @@ -883,7 +883,8 @@ pub fn families(styles: StyleChain) -> impl Iterator<Item = FontFamily> + 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 6cb4d895..903982ef 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<Content> { - 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<Content> { - 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<EcoString> { if content.is::<SpaceElem>() { Some(' '.into()) } else if let Some(elem) = content.to::<TextElem>() { - 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 931e38c6..6e166bc0 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::<Vec<_>>(), + &families(styles).map(|s| s.into()).collect::<Vec<_>>(), ) .at(self.span())?; @@ -223,7 +224,7 @@ impl Layout for ImageElem { } impl LocalName for ImageElem { - fn local_name(&self, lang: Lang, region: Option<Region>) -> &'static str { + fn local_name(lang: Lang, region: Option<Region>) -> &'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 79364b07..43b11526 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<PathVertex> = self.vertices(); + let vertices = self.vertices(); let points: Vec<Point> = 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 5584bdb6..a5e14b5a 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<Ident>, fields: Vec<Field>, } -/// 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<TokenStream> { + 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<TokenStream> { + 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<Item = &Field> + Clone { + self.fields.iter().filter(|field| !field.external) + } + + /// Fields that are inherent to the element. + fn inherent_fields(&self) -> impl Iterator<Item = &Field> + 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<Item = &Field> + 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<Item = &Field> + 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<Item = &Field> + 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<Item = &Field> + 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<usize>, parse: Option<BlockWithReturn>, default: syn::Expr, } @@ -107,6 +194,7 @@ fn parse(stream: TokenStream, body: &syn::ItemStruct) -> Result<Elem> { 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<Field> { docs: documentation(&attrs), internal: has_attr(&mut attrs, "internal"), external: has_attr(&mut attrs, "external"), + forced_variant: parse_attr::<syn::LitInt>(&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::<syn::Expr>(&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<Field> { 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<Field> { /// 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::<Vec<_>>(); + fields.sort_by_key(|field| field.forced_variant.unwrap_or(usize::MAX)); + + let field_names = fields.iter().map(|Field { name, .. }| name).collect::<Vec<_>>(); + let field_consts = fields + .iter() + .map(|Field { const_ident, .. }| const_ident) + .collect::<Vec<_>>(); + + let field_variants = fields + .iter() + .map(|Field { enum_ident, .. }| enum_ident) + .collect::<Vec<_>>(); + + 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<u8> for #enum_ident { + type Error = (); + + fn try_from(value: u8) -> Result<Self, Self::Error> { + #(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<Self, Self::Err> { + 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,83 +451,54 @@ 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 } }); - quote! { - /// Create a new element. - pub fn new(#(#params),*) -> Self { - Self(::typst::model::Content::new( - <Self as ::typst::model::NativeElement>::elem() - )) - #(#builder_calls)* - } - } -} + let defaults = element + .fields + .iter() + .filter(|field| !field.external && (field.synthesized || !field.inherent())) + .map(|Field { ident, .. }| { + quote! { #ident: None } + }); -/// 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 { - quote! { - #[doc = #docs] - #[track_caller] - #vis fn #ident(&self) -> #output { - self.0.expect_field(#name) - } - } - } else { - let access = create_style_chain_access(field, quote! { self.0.field(#name) }); + let label_and_location = element.unless_capability("Unlabellable", || { quote! { - #[doc = #docs] - #vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output { - #access - } + location: None, + label: None, + prepared: false, } - } -} + }); -/// 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 + /// Create a new element. + pub fn new(#(#params),*) -> Self { + Self { + span: ::typst::syntax::Span::detached(), + #label_and_location + guards: ::std::vec::Vec::with_capacity(0), + #(#required,)* + #(#defaults,)* + } } } } -/// 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 }, - }; - - quote! { - styles.#getter::<#ty>( - <Self as ::typst::model::NativeElement>::elem(), - #name, - #inherent, - || #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); + + 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 { - Self(self.0.with_field(#name, #ident)) + #set + self } } } @@ -332,36 +507,142 @@ fn create_with_field_method(field: &Field) -> TokenStream { 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) { - self.0.push_field(#name, #ident); + #set } } } /// Create a setter method for a field. -fn create_set_field_method(field: &Field) -> TokenStream { - let Field { vis, ident, set_ident, name, ty, .. } = 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( <Self as ::typst::model::NativeElement>::elem(), - #name, + <#elem as #model::ElementFields>::Fields::#enum_ident as u8, #ident, )) } } } -/// Create the element's `Pack` implementation. -fn create_pack_impl(element: &Elem) -> TokenStream { +/// 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(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.#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(element, field, quote! { self.#ident.as_ref() }); + + quote! { + #[doc = #docs] + #vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output { + #access + } + } + } +} + +/// Create a style chain access method for a field. +fn create_style_chain_access( + element: &Elem, + field: &Field, + inherent: TokenStream, +) -> TokenStream { + let model = quote! { ::typst::model }; + let elem = &element.ident; + + 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>( + <Self as ::typst::model::NativeElement>::elem(), + <#elem as #model::ElementFields>::Fields::#enum_ident as u8, + #inherent, + #default, + ) + } +} + +/// 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<u8> + >::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::<Self>() + } + + fn dyn_hash(&self, mut hasher: &mut dyn ::std::hash::Hasher) { + <Self as ::std::hash::Hash>::hash(self, &mut hasher); + } + + fn dyn_eq(&self, other: &#model::Content) -> bool { + if let Some(other) = other.to::<Self>() { + <Self as ::std::cmp::PartialEq>::eq(self, other) + } else { + false + } + } + + fn dyn_clone(&self) -> ::std::sync::Arc<dyn #model::NativeElement> { + ::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<Self>) -> ::std::sync::Arc<dyn ::std::any::Any + Send + Sync> { + 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); + } + } + } + } + } +} + +/// Creates the element's `Construct` implementation. +fn create_construct_impl(element: &Elem) -> TokenStream { + let ident = &element.ident; + 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)); } + } + }); - fn unpack(content: &#model::Content) -> ::std::option::Option<&Self> { - // Safety: Elements are #[repr(transparent)]. - content.is::<Self>().then(|| unsafe { - ::std::mem::transmute(content) - }) + 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 the element's casting vtable. +/// 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::<Vec<_>>(); + + 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::<Vec<_>>(); + ::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| ident != "Construct"); + 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::<dyn #capability>() { - 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( - <Self as ::typst::model::NativeElement>::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 52f3e237..cbeb4ba1 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 c6c97398..1e05a2d7 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 1c23fd75..742c6245 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 d5757d04..772db535 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<T> Reflect for Vec<T> { } } +impl<T: Reflect, const N: usize> Reflect for SmallVec<[T; N]> { + fn input() -> CastInfo { + Array::input() + } + + fn output() -> CastInfo { + Array::output() + } + + fn castable(value: &Value) -> bool { + Array::castable(value) + } +} + impl<T: IntoValue> IntoValue for Vec<T> { fn into_value(self) -> Value { Value::Array(self.into_iter().map(IntoValue::into_value).collect()) } } +impl<T: IntoValue, const N: usize> IntoValue for SmallVec<[T; N]> { + fn into_value(self) -> Value { + Value::Array(self.into_iter().map(IntoValue::into_value).collect()) + } +} + impl<T: FromValue> FromValue for Vec<T> { fn from_value(value: Value) -> StrResult<Self> { value.cast::<Array>()?.into_iter().map(Value::cast).collect() } } +impl<T: FromValue, const N: usize> FromValue for SmallVec<[T; N]> { + fn from_value(value: Value) -> StrResult<Self> { + value.cast::<Array>()?.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 0965c8fa..eeb632f9 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<T: Reflect> Reflect for Spanned<T> { } } +impl<T: Reflect> Reflect for Prehashed<T> { + fn input() -> CastInfo { + T::input() + } + + fn output() -> CastInfo { + T::output() + } + + fn castable(value: &Value) -> bool { + T::castable(value) + } +} + impl<T: Reflect> Reflect for StrResult<T> { fn input() -> CastInfo { T::input() @@ -150,12 +168,24 @@ impl IntoValue for Value { } } +impl<T: IntoValue + Clone> IntoValue for Cow<'_, T> { + fn into_value(self) -> Value { + self.into_owned().into_value() + } +} + impl<T: IntoValue> IntoValue for Spanned<T> { fn into_value(self) -> Value { self.v.into_value() } } +impl<T: IntoValue + Hash + 'static> IntoValue for Prehashed<T> { + fn into_value(self) -> Value { + self.into_inner().into_value() + } +} + /// Cast a Rust type or result into a [`SourceResult<Value>`]. /// /// Converts `T`, [`StrResult<T>`], or [`SourceResult<T>`] into @@ -203,6 +233,12 @@ impl FromValue for Value { } } +impl<T: FromValue + Hash + 'static> FromValue for Prehashed<T> { + fn from_value(value: Value) -> StrResult<Self> { + Ok(Self::new(T::from_value(value)?)) + } +} + impl<T: FromValue> FromValue<Spanned<Value>> for T { fn from_value(value: Spanned<Value>) -> StrResult<Self> { T::from_value(value.v) @@ -217,7 +253,7 @@ impl<T: FromValue> FromValue<Spanned<Value>> for Spanned<T> { } /// 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<T> Container for Vec<T> { type Inner = T; } +impl<T, const N: usize> 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 f24b714b..8660abac 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<Selector> { 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::<StrResult<smallvec::SmallVec<_>>>()?; + + Ok(element.where_(fields)) } } diff --git a/crates/typst/src/eval/library.rs b/crates/typst/src/eval/library.rs index 5d65e193..c712adae 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<EcoString>, + 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 ede21c52..8ac2c40b 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<Self::Output> { - 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<Self::Output> { - 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<Self::Output> { - 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 3bbfcc0b..97d15870 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 19a5610a..b87c8592 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 d29852fd..f6f9f09d 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::<NonZeroUsize>("level"), + level: element.expect_field_by_name::<NonZeroUsize>("level"), // 'bookmarked' set to 'auto' falls back to the value of 'outlined'. bookmarked: element - .expect_field::<Smart<bool>>("bookmarked") - .unwrap_or_else(|| element.expect_field::<bool>("outlined")), + .expect_field_by_name::<Smart<bool>>("bookmarked") + .unwrap_or_else(|| element.expect_field_by_name::<bool>("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::<Content>("body"); + let body = node.element.expect_field_by_name::<Content>("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 00000000..92e2a18e --- /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<dyn Blockable>); + +impl Block { + /// Creates a new block. + pub fn new<T: Blockable>(value: T) -> Self { + Self(Box::new(value)) + } + + /// Downcasts the block to the specified type. + pub fn downcast<T: Blockable>(&self) -> Option<&T> { + self.0.as_any().downcast_ref() + } + + /// Downcasts mutably the block to the specified type. + pub fn downcast_mut<T: Blockable>(&mut self) -> Option<&mut T> { + self.0.as_any_mut().downcast_mut() + } +} + +impl Hash for Block { + fn hash<H: Hasher>(&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<T: Clone + Hash + Debug + Send + Sync + 'static> 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 c5aa4e58..f853fc51 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,95 +64,174 @@ 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<Attr>, -} - -/// Attributes that can be attached to content. -#[derive(Debug, Clone, PartialEq, Hash)] -enum Attr { - Span(Span), - Field(EcoString), - Value(Prehashed<Value>), - Child(Prehashed<Content>), - Styles(Styles), - Prepared, - Guard(Guard), - Location(Location), -} +#[derive(Debug, Clone)] +pub struct Content(Arc<dyn NativeElement>); 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<E: NativeElement>(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()) } - /// Create a new sequence element from multiples elements. - pub fn sequence(iter: impl IntoIterator<Item = Self>) -> Self { - let mut iter = iter.into_iter(); - let Some(first) = iter.next() else { return Self::empty() }; - let Some(second) = iter.next() else { return first }; - let mut content = Content::empty(); - content.attrs.push(Attr::Child(Prehashed::new(first))); - content.attrs.push(Attr::Child(Prehashed::new(second))); - content - .attrs - .extend(iter.map(|child| Attr::Child(Prehashed::new(child)))); - content + /// Get the element of this content. + #[inline] + pub fn elem(&self) -> Element { + self.0.dyn_elem() } - /// Whether the content is an empty sequence. - pub fn is_empty(&self) -> bool { - self.is::<SequenceElem>() && self.attrs.is_empty() + /// Get the span of the content. + #[inline] + pub fn span(&self) -> Span { + self.0.span() } - /// Whether the contained element is of type `T`. - pub fn is<T: NativeElement>(&self) -> bool { - self.elem == T::elem() + /// Set the span of the content. + pub fn spanned(mut self, span: Span) -> Self { + self.make_mut().set_span(span); + self } - /// Cast to `T` if the contained element is of type `T`. - pub fn to<T: NativeElement>(&self) -> Option<&T> { - T::unpack(self) + /// Get the label of the content. + #[inline] + pub fn label(&self) -> Option<Label> { + self.0.label() } - /// Access the children if this is a sequence. - pub fn to_sequence(&self) -> Option<impl Iterator<Item = &Self>> { - if !self.is_sequence() { - return None; - } - Some(self.attrs.iter().filter_map(Attr::child)) + /// Set the label of the content. + pub fn labelled(mut self, label: Label) -> Self { + self.make_mut().set_label(label); + self } - pub fn is_sequence(&self) -> bool { - self.is::<SequenceElem>() + /// Set the location of the content. + pub fn set_location(&mut self, location: Location) { + self.make_mut().set_location(location); } - /// Also auto expands sequence of sequences into flat sequence - pub fn sequence_recursive_for_each(&self, f: &mut impl FnMut(&Self)) { - if let Some(children) = self.to_sequence() { - children.for_each(|c| c.sequence_recursive_for_each(f)); - } else { - f(self); - } + /// Disable a show rule recipe. + pub fn guarded(mut self, guard: Guard) -> Self { + self.make_mut().push_guard(guard); + self.0.into() } - /// Access the child and styles. - pub fn to_styled(&self) -> Option<(&Content, &Styles)> { - if !self.is::<StyledElem>() { + /// Whether the content needs to be realized specially. + pub fn needs_preparation(&self) -> bool { + self.0.needs_preparation() + } + + /// Check whether a show rule recipe is disabled. + pub fn is_guarded(&self, guard: Guard) -> bool { + self.0.is_guarded(guard) + } + + /// Whether no show rule was executed for this content so far. + pub fn is_pristine(&self) -> bool { + self.0.is_pristine() + } + + /// Whether this content has already been prepared. + pub fn is_prepared(&self) -> bool { + self.0.is_prepared() + } + + /// Mark this content as prepared. + pub fn mark_prepared(&mut self) { + self.make_mut().mark_prepared(); + } + + /// Get a field by ID. + /// + /// This is the preferred way to access fields. However, you can only use it + /// if you have set the field IDs yourself or are using the field IDs + /// generated by the `#[elem]` macro. + #[inline] + pub fn get(&self, id: u8) -> Option<Value> { + self.0.field(id) + } + + /// Get a field by name. + /// + /// If you have access to the field IDs of the element, use [`Self::get`] + /// instead. + #[inline] + pub fn get_by_name(&self, name: &str) -> Option<Value> { + let id = self.elem().field_id(name)?; + self.get(id) + } + + /// Get a field by ID, returning a missing field error if it does not exist. + /// + /// This is the preferred way to access fields. However, you can only use it + /// if you have set the field IDs yourself or are using the field IDs + /// generated by the `#[elem]` macro. + #[inline] + pub fn field(&self, id: u8) -> StrResult<Value> { + self.get(id) + .ok_or_else(|| missing_field(self.elem().field_name(id).unwrap())) + } + + /// Get a field by name, returning a missing field error if it does not + /// exist. + /// + /// If you have access to the field IDs of the element, use [`Self::field`] + /// instead. + #[inline] + pub fn field_by_name(&self, name: &str) -> StrResult<Value> { + let id = self.elem().field_id(name).ok_or_else(|| missing_field(name))?; + self.field(id) + } + + /// Expect a field on the content to exist as a specified type. + #[track_caller] + pub fn expect_field<T: FromValue>(&self, id: u8) -> T { + self.field(id).unwrap().cast().unwrap() + } + + /// Expect a field on the content to exist as a specified type. + #[track_caller] + pub fn expect_field_by_name<T: FromValue>(&self, name: &str) -> T { + self.field_by_name(name).unwrap().cast().unwrap() + } + + /// Set a field to the content. + pub fn with_field(mut self, id: u8, value: impl IntoValue) -> Self { + self.make_mut().set_field(id, value.into_value()).unwrap(); + self + } + + /// Create a new sequence element from multiples elements. + pub fn sequence(iter: impl IntoIterator<Item = Self>) -> Self { + let mut iter = iter.into_iter(); + let Some(first) = iter.next() else { return Self::empty() }; + let Some(second) = iter.next() else { return first }; + SequenceElem::new( + std::iter::once(Prehashed::new(first)) + .chain(std::iter::once(Prehashed::new(second))) + .chain(iter.map(Prehashed::new)) + .collect(), + ) + .into() + } + + /// Access the children if this is a sequence. + pub fn to_sequence(&self) -> Option<impl Iterator<Item = &Prehashed<Content>>> { + let Some(sequence) = self.to::<SequenceElem>() else { return None; - } - let child = self.attrs.iter().find_map(Attr::child)?; - let styles = self.attrs.iter().find_map(Attr::styles)?; - Some((child, styles)) + }; + + Some(sequence.children.iter()) + } + + /// Whether the contained element is of type `T`. + pub fn is<T: NativeElement>(&self) -> bool { + self.elem() == T::elem() } /// Whether the contained element has the given capability. @@ -157,13 +239,13 @@ impl Content { where C: ?Sized + 'static, { - self.elem.can::<C>() + self.elem().can::<C>() } /// Whether the contained element has the given capability where the /// capability is given by a `TypeId`. pub fn can_type_id(&self, type_id: TypeId) -> bool { - self.elem.can_type_id(type_id) + self.elem().can_type_id(type_id) } /// Cast to a trait object if the contained element has the given @@ -172,8 +254,8 @@ impl Content { where C: ?Sized + 'static, { - let vtable = self.elem.vtable()(TypeId::of::<C>())?; - let data = self as *const Self as *const (); + let vtable = self.elem().vtable()(TypeId::of::<C>())?; + let data = Arc::as_ptr(&self.0) as *const (); Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) }) } @@ -183,119 +265,62 @@ impl Content { where C: ?Sized + 'static, { - let vtable = self.elem.vtable()(TypeId::of::<C>())?; - let data = self as *mut Self as *mut (); + // Safety: We ensure the element is not shared. + let vtable = self.elem().vtable()(TypeId::of::<C>())?; + let data = self.make_mut() as *mut dyn NativeElement as *mut (); Some(unsafe { &mut *crate::util::fat::from_raw_parts_mut(data, vtable) }) } - /// The content's span. - pub fn span(&self) -> Span { - self.attrs.iter().find_map(Attr::span).unwrap_or(Span::detached()) - } - - /// Attach a span to the content if it doesn't already have one. - pub fn spanned(mut self, span: Span) -> Self { - if self.span().is_detached() { - self.attrs.push(Attr::Span(span)); - } - self + /// Whether the content is a sequence. + pub fn is_sequence(&self) -> bool { + self.is::<SequenceElem>() } - /// Attach a field to the content. - pub fn with_field( - mut self, - name: impl Into<EcoString>, - value: impl IntoValue, - ) -> Self { - self.push_field(name, value); - self - } + /// Whether the content is an empty sequence. + pub fn is_empty(&self) -> bool { + let Some(sequence) = self.to::<SequenceElem>() else { + return false; + }; - /// Attach a field to the content. - pub fn push_field(&mut self, name: impl Into<EcoString>, value: impl IntoValue) { - let name = name.into(); - if let Some(i) = self.attrs.iter().position(|attr| match attr { - Attr::Field(field) => *field == name, - _ => false, - }) { - self.attrs.make_mut()[i + 1] = - Attr::Value(Prehashed::new(value.into_value())); - } else { - self.attrs.push(Attr::Field(name)); - self.attrs.push(Attr::Value(Prehashed::new(value.into_value()))); - } + sequence.children.is_empty() } - /// Access a field on the content. - pub fn field(&self, name: &str) -> Option<Value> { - if let (Some(iter), "children") = (self.to_sequence(), name) { - Some(Value::Array(iter.cloned().map(Value::Content).collect())) - } else if let (Some((child, _)), "child") = (self.to_styled(), name) { - Some(Value::Content(child.clone())) + /// Also auto expands sequence of sequences into flat sequence + pub fn sequence_recursive_for_each(&self, f: &mut impl FnMut(&Self)) { + if let Some(children) = self.to_sequence() { + children.for_each(|c| c.sequence_recursive_for_each(f)); } else { - self.field_ref(name).cloned() + f(self); } } - /// Access a field on the content by reference. - /// - /// Does not include synthesized fields for sequence and styled elements. - pub fn field_ref(&self, name: &str) -> Option<&Value> { - self.fields_ref() - .find(|&(field, _)| field == name) - .map(|(_, value)| value) - } - - /// Iter over all fields on the content. - /// - /// Does not include synthesized fields for sequence and styled elements. - pub fn fields_ref(&self) -> impl Iterator<Item = (&EcoString, &Value)> { - let mut iter = self.attrs.iter(); - std::iter::from_fn(move || { - let field = iter.find_map(Attr::field)?; - let value = iter.next()?.value()?; - Some((field, value)) - }) - } - - /// Borrow the value of the given field. - pub fn get(&self, key: &str) -> StrResult<Value> { - self.field(key).ok_or_else(|| missing_field(key)) - } - - /// Try to access a field on the content as a specified type. - pub fn cast_field<T: FromValue>(&self, name: &str) -> Option<T> { - match self.field(name) { - Some(value) => value.cast().ok(), - None => None, - } - } + /// Access the child and styles. + pub fn to_styled(&self) -> Option<(&Content, &Styles)> { + let styled = self.to::<StyledElem>()?; - /// Expect a field on the content to exist as a specified type. - #[track_caller] - pub fn expect_field<T: FromValue>(&self, name: &str) -> T { - self.field(name).unwrap().cast().unwrap() + let child = styled.child(); + let styles = styled.styles(); + Some((child, styles)) } - /// The content's label. - pub fn label(&self) -> Option<&Label> { - match self.field_ref("label")? { - Value::Label(label) => Some(label), - _ => None, + /// Style this content with a recipe, eagerly applying it if possible. + pub fn styled_with_recipe(self, vm: &mut Vm, recipe: Recipe) -> SourceResult<Self> { + if recipe.selector.is_none() { + recipe.apply_vm(vm, self) + } else { + Ok(self.styled(recipe)) } } - /// Attach a label to the content. - pub fn labelled(self, label: Label) -> Self { - self.with_field("label", label) + /// Repeat this content `count` times. + pub fn repeat(&self, count: usize) -> Self { + Self::sequence(std::iter::repeat_with(|| self.clone()).take(count)) } /// Style this content with a style entry. pub fn styled(mut self, style: impl Into<Style>) -> Self { - if self.is::<StyledElem>() { - let prev = - self.attrs.make_mut().iter_mut().find_map(Attr::styles_mut).unwrap(); - prev.apply_one(style.into()); + if let Some(style_elem) = self.to_mut::<StyledElem>() { + style_elem.styles.apply_one(style.into()); self } else { self.styled_with_map(style.into().into()) @@ -308,80 +333,22 @@ impl Content { return self; } - if self.is::<StyledElem>() { - let prev = - self.attrs.make_mut().iter_mut().find_map(Attr::styles_mut).unwrap(); - prev.apply(styles); + if let Some(style_elem) = self.to_mut::<StyledElem>() { + style_elem.styles.apply(styles); self } else { - let mut content = Content::new(StyledElem::elem()); - content.attrs.push(Attr::Child(Prehashed::new(self))); - content.attrs.push(Attr::Styles(styles)); - content - } - } - - /// Style this content with a recipe, eagerly applying it if possible. - pub fn styled_with_recipe(self, vm: &mut Vm, recipe: Recipe) -> SourceResult<Self> { - if recipe.selector.is_none() { - recipe.apply_vm(vm, self) - } else { - Ok(self.styled(recipe)) + StyledElem::new(Prehashed::new(self), styles).into() } } - /// Repeat this content `count` times. - pub fn repeat(&self, count: usize) -> Self { - Self::sequence(vec![self.clone(); count]) - } - - /// Disable a show rule recipe. - pub fn guarded(mut self, guard: Guard) -> Self { - self.attrs.push(Attr::Guard(guard)); - self - } - - /// Check whether a show rule recipe is disabled. - pub fn is_guarded(&self, guard: Guard) -> bool { - self.attrs.contains(&Attr::Guard(guard)) - } - - /// Whether no show rule was executed for this content so far. - pub fn is_pristine(&self) -> bool { - !self.attrs.iter().any(|modifier| matches!(modifier, Attr::Guard(_))) - } - - /// Whether this content has already been prepared. - pub fn is_prepared(&self) -> bool { - self.attrs.contains(&Attr::Prepared) - } - - /// Mark this content as prepared. - pub fn mark_prepared(&mut self) { - self.attrs.push(Attr::Prepared); - } - - /// Whether the content needs to be realized specially. - pub fn needs_preparation(&self) -> bool { - (self.can::<dyn Locatable>() - || self.can::<dyn Synthesize>() - || self.label().is_some()) - && !self.is_prepared() - } - - /// Attach a location to this content. - pub fn set_location(&mut self, location: Location) { - self.attrs.push(Attr::Location(location)); - } - /// Queries the content tree for all elements that match the given selector. /// /// Elements produced in `show` rules will not be included in the results. #[tracing::instrument(skip_all)] - pub fn query(&self, selector: Selector) -> Vec<&Content> { + pub fn query(&self, selector: Selector) -> Vec<Content> { let mut results = Vec::new(); self.traverse(&mut |element| { - if selector.matches(element) { + if selector.matches(&element) { results.push(element); } }); @@ -393,10 +360,10 @@ impl Content { /// /// Elements produced in `show` rules will not be included in the results. #[tracing::instrument(skip_all)] - pub fn query_first(&self, selector: Selector) -> Option<&Content> { + pub fn query_first(&self, selector: Selector) -> Option<Content> { let mut result = None; self.traverse(&mut |element| { - if result.is_none() && selector.matches(element) { + if result.is_none() && selector.matches(&element) { result = Some(element); } }); @@ -415,24 +382,21 @@ impl Content { } /// Traverse this content. - fn traverse<'a, F>(&'a self, f: &mut F) + fn traverse<F>(&self, f: &mut F) where - F: FnMut(&'a Content), + F: FnMut(Content), { - f(self); + f(self.clone()); - for attr in &self.attrs { - match attr { - Attr::Child(child) => child.traverse(f), - Attr::Value(value) => walk_value(value, f), - _ => {} - } - } + self.0 + .fields() + .into_iter() + .for_each(|(_, value)| walk_value(value, f)); /// Walks a given value to find any content that matches the selector. - fn walk_value<'a, F>(value: &'a Value, f: &mut F) + fn walk_value<F>(value: Value, f: &mut F) where - F: FnMut(&'a Content), + F: FnMut(Content), { match value { Value::Content(content) => content.traverse(f), @@ -445,6 +409,51 @@ impl Content { } } } + + /// Downcasts the element to the specified type. + #[inline] + pub fn to<T: NativeElement>(&self) -> Option<&T> { + // Early check for performance. + if !self.is::<T>() { + return None; + } + + self.0.as_any().downcast_ref() + } + + /// Downcasts mutably the element to the specified type. + #[inline] + pub fn to_mut<T: NativeElement>(&mut self) -> Option<&mut T> { + // Early check for performance. + if !self.is::<T>() { + return None; + } + + self.make_mut().as_any_mut().downcast_mut() + } + + /// Downcast the element into an owned value. + #[inline] + pub fn unpack<T: NativeElement>(self) -> Option<Arc<T>> { + // Early check for performance. + if !self.is::<T>() { + return None; + } + + Arc::downcast(self.0.into_any()).ok() + } + + /// Makes sure the content is not shared and returns a mutable reference to + /// the inner element. + #[inline] + fn make_mut(&mut self) -> &mut dyn NativeElement { + let arc = &mut self.0; + if Arc::strong_count(arc) > 1 || Arc::weak_count(arc) > 0 { + *arc = arc.dyn_clone(); + } + + Arc::get_mut(arc).unwrap() + } } #[scope] @@ -456,7 +465,7 @@ impl Content { /// kind of element. #[func] pub fn func(&self) -> Element { - self.elem + self.elem() } /// Whether the content has the specified field. @@ -466,7 +475,11 @@ impl Content { /// The field to look for. field: Str, ) -> bool { - self.field(&field).is_some() + let Some(id) = self.elem().field_id(&field) else { + return false; + }; + + self.get(id).is_some() } /// Access the specified field on the content. Returns the default value if @@ -481,7 +494,11 @@ impl Content { #[named] default: Option<Value>, ) -> StrResult<Value> { - self.field(&field) + let Some(id) = self.elem().field_id(&field) else { + return default.ok_or_else(|| missing_field_no_default(&field)); + }; + + self.get(id) .or(default) .ok_or_else(|| missing_field_no_default(&field)) } @@ -496,22 +513,7 @@ impl Content { /// ``` #[func] pub fn fields(&self) -> Dict { - static CHILD: EcoString = EcoString::inline("child"); - static CHILDREN: EcoString = EcoString::inline("children"); - - let option = if let Some(iter) = self.to_sequence() { - Some((&CHILDREN, Value::Array(iter.cloned().map(Value::Content).collect()))) - } else if let Some((child, _)) = self.to_styled() { - Some((&CHILD, Value::Content(child.clone()))) - } else { - None - }; - - self.fields_ref() - .map(|(name, value)| (name, value.clone())) - .chain(option) - .map(|(key, value)| (key.to_owned().into(), value)) - .collect() + self.0.fields() } /// The location of the content. This is only available on content returned @@ -521,51 +523,44 @@ impl Content { /// [counters]($counter), [state]($state) and [queries]($query). #[func] pub fn location(&self) -> Option<Location> { - self.attrs.iter().find_map(|modifier| match modifier { - Attr::Location(location) => Some(*location), - _ => None, - }) + self.0.location() } } -impl Repr for Content { - fn repr(&self) -> EcoString { - let name = self.elem.name(); - if let Some(text) = item!(text_str)(self) { - return eco_format!("[{}]", text); - } else if name == "space" { - return ("[ ]").into(); - } - - let mut pieces: Vec<_> = self - .fields() - .into_iter() - .map(|(name, value)| eco_format!("{}: {}", name, value.repr())) - .collect(); +impl Default for Content { + fn default() -> Self { + Self::empty() + } +} - if self.is::<StyledElem>() { - pieces.push(EcoString::from("..")); - } +impl<T: NativeElement> From<T> for Content { + fn from(value: T) -> Self { + Self::new(value) + } +} - eco_format!("{}{}", name, pretty_array_like(&pieces, false)) +impl From<Arc<dyn NativeElement>> for Content { + fn from(value: Arc<dyn NativeElement>) -> Self { + Self(value) } } -impl Default for Content { - fn default() -> Self { - Self::empty() +impl std::hash::Hash for Content { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.0.dyn_hash(state); } } impl PartialEq for Content { fn eq(&self, other: &Self) -> bool { - if let (Some(left), Some(right)) = (self.to_sequence(), other.to_sequence()) { - left.eq(right) - } else if let (Some(left), Some(right)) = (self.to_styled(), other.to_styled()) { - left == right - } else { - self.elem == other.elem && self.fields_ref().eq(other.fields_ref()) - } + // Additional short circuit for different elements. + self.elem() == other.elem() && self.0.dyn_eq(other) + } +} + +impl Repr for Content { + fn repr(&self) -> EcoString { + self.0.repr() } } @@ -574,20 +569,47 @@ impl Add for Content { fn add(self, mut rhs: Self) -> Self::Output { let mut lhs = self; - match (lhs.is::<SequenceElem>(), rhs.is::<SequenceElem>()) { - (true, true) => { - lhs.attrs.extend(rhs.attrs); + match (lhs.to_mut::<SequenceElem>(), rhs.to_mut::<SequenceElem>()) { + (Some(seq_lhs), Some(rhs)) => { + seq_lhs.children.extend(rhs.children.iter().cloned()); + lhs + } + (Some(seq_lhs), None) => { + seq_lhs.children.push(Prehashed::new(rhs)); + lhs + } + (None, Some(rhs_seq)) => { + rhs_seq.children.insert(0, Prehashed::new(lhs)); + rhs + } + (None, None) => Self::sequence([lhs, rhs]), + } + } +} + +impl<'a> Add<&'a Self> for Content { + type Output = Self; + + fn add(self, rhs: &'a Self) -> Self::Output { + let mut lhs = self; + match (lhs.to_mut::<SequenceElem>(), rhs.to::<SequenceElem>()) { + (Some(seq_lhs), Some(rhs)) => { + seq_lhs.children.extend(rhs.children.iter().cloned()); lhs } - (true, false) => { - lhs.attrs.push(Attr::Child(Prehashed::new(rhs))); + (Some(seq_lhs), None) => { + seq_lhs.children.push(Prehashed::new(rhs.clone())); lhs } - (false, true) => { - rhs.attrs.insert(0, Attr::Child(Prehashed::new(lhs))); + (None, Some(_)) => { + let mut rhs = rhs.clone(); + rhs.to_mut::<SequenceElem>() + .unwrap() + .children + .insert(0, Prehashed::new(lhs)); rhs } - (false, false) => Self::sequence([lhs, rhs]), + (None, None) => Self::sequence([lhs, rhs.clone()]), } } } @@ -598,6 +620,12 @@ impl AddAssign for Content { } } +impl AddAssign<&Self> for Content { + fn add_assign(&mut self, rhs: &Self) { + *self = std::mem::take(self) + rhs; + } +} + impl Sum for Content { fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { Self::sequence(iter) @@ -610,63 +638,80 @@ impl Serialize for Content { S: Serializer, { serializer.collect_map( - iter::once((&"func".into(), &self.func().name().into_value())) - .chain(self.fields_ref()), + iter::once(( + Str::from(EcoString::inline("func")), + self.func().name().into_value(), + )) + .chain(self.fields()), ) } } -impl Attr { - fn child(&self) -> Option<&Content> { - match self { - Self::Child(child) => Some(child), - _ => None, - } - } +/// Defines the `ElemFunc` for sequences. +#[elem(Repr, PartialEq)] +struct SequenceElem { + #[required] + children: Vec<Prehashed<Content>>, +} - fn styles(&self) -> Option<&Styles> { - match self { - Self::Styles(styles) => Some(styles), - _ => None, +impl Default for SequenceElem { + fn default() -> Self { + Self { + span: Span::detached(), + location: Default::default(), + label: Default::default(), + prepared: Default::default(), + guards: Default::default(), + children: Default::default(), } } +} - fn styles_mut(&mut self) -> Option<&mut Styles> { - match self { - Self::Styles(styles) => Some(styles), - _ => None, - } +impl PartialEq for SequenceElem { + fn eq(&self, other: &Self) -> bool { + self.children + .iter() + .map(|c| &**c) + .eq(other.children.iter().map(|c| &**c)) } +} - fn field(&self) -> Option<&EcoString> { - match self { - Self::Field(field) => Some(field), - _ => None, +impl Repr for SequenceElem { + fn repr(&self) -> EcoString { + if self.children.is_empty() { + EcoString::inline("[]") + } else { + eco_format!( + "[{}]", + pretty_array_like( + &self.children.iter().map(|c| c.0.repr()).collect::<Vec<_>>(), + false + ) + ) } } +} - fn value(&self) -> Option<&Value> { - match self { - Self::Value(value) => Some(value), - _ => None, - } - } +/// Defines the `ElemFunc` for styled elements. +#[elem(Repr, PartialEq)] +struct StyledElem { + #[required] + child: Prehashed<Content>, + #[required] + styles: Styles, +} - fn span(&self) -> Option<Span> { - match self { - Self::Span(span) => Some(*span), - _ => None, - } +impl PartialEq for StyledElem { + fn eq(&self, other: &Self) -> bool { + *self.child == *other.child } } -/// Defines the `ElemFunc` for sequences. -#[elem] -struct SequenceElem {} - -/// Defines the `ElemFunc` for styled elements. -#[elem] -struct StyledElem {} +impl Repr for StyledElem { + fn repr(&self) -> EcoString { + eco_format!("styled(child: {}, ..)", self.child.0.repr()) + } +} /// Hosts metadata and ensures metadata is produced even for empty elements. #[elem(Behave)] @@ -674,7 +719,7 @@ pub struct MetaElem { /// Metadata that should be attached to all elements affected by this style /// property. #[fold] - pub data: Vec<Meta>, + pub data: SmallVec<[Meta; 1]>, } impl Behave for MetaElem { @@ -691,7 +736,7 @@ pub trait PlainText { /// The missing field access error message. #[cold] -fn missing_field(field: &str) -> EcoString { +pub fn missing_field(field: &str) -> EcoString { eco_format!("content does not contain field {}", field.repr()) } diff --git a/crates/typst/src/model/element.rs b/crates/typst/src/model/element.rs index d3b6bf1e..4426ff31 100644 --- a/crates/typst/src/model/element.rs +++ b/crates/typst/src/model/element.rs @@ -1,13 +1,19 @@ -use ecow::EcoString; -use std::any::TypeId; +use std::any::{Any, TypeId}; use std::cmp::Ordering; -use std::fmt::{self, Debug, Formatter}; +use std::fmt::{self, Debug}; +use std::hash::Hasher; +use std::sync::Arc; +use ecow::EcoString; use once_cell::sync::Lazy; +use smallvec::SmallVec; use super::{Content, Selector, Styles}; -use crate::diag::SourceResult; +use crate::diag::{SourceResult, StrResult}; +use crate::doc::{Lang, Region}; use crate::eval::{cast, Args, Dict, Func, ParamInfo, Repr, Scope, Value, Vm}; +use crate::model::{Guard, Label, Location}; +use crate::syntax::Span; use crate::util::Static; /// A document element. @@ -20,6 +26,16 @@ impl Element { T::elem() } + /// Extract the field ID for the given field name. + pub fn field_id(&self, name: &str) -> Option<u8> { + (self.0.field_id)(name) + } + + /// Extract the field name for the given field ID. + pub fn field_name(&self, id: u8) -> Option<&'static str> { + (self.0.field_name)(id) + } + /// The element's normal name (e.g. `enum`). pub fn name(self) -> &'static str { self.0.name @@ -79,7 +95,7 @@ impl Element { /// Create a selector for this element, filtering for those /// that [fields](super::Content::field) match the given argument. - pub fn where_(self, fields: Dict) -> Selector { + pub fn where_(self, fields: SmallVec<[(u8, Value); 1]>) -> Selector { Selector::Elem(self, Some(fields)) } @@ -92,10 +108,15 @@ impl Element { pub fn params(&self) -> &'static [ParamInfo] { &(self.0).0.params } + + /// The element's local name, if any. + pub fn local_name(&self, lang: Lang, region: Option<Region>) -> Option<&'static str> { + (self.0).0.local_name.map(|f| f(lang, region)) + } } impl Debug for Element { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.pad(self.name()) } } @@ -124,21 +145,120 @@ cast! { v: Func => v.element().ok_or("expected element")?, } +/// Fields of an element. +pub trait ElementFields { + /// The fields of the element. + type Fields; +} + /// A Typst element that is defined by a native Rust type. -pub trait NativeElement: Construct + Set + Sized + 'static { +pub trait NativeElement: Debug + Repr + Construct + Set + Send + Sync + 'static { /// Get the element for the native Rust element. - fn elem() -> Element { + fn elem() -> Element + where + Self: Sized, + { Element::from(Self::data()) } + /// Pack the element into type-erased content. + fn pack(self) -> Content + where + Self: Sized, + { + Content::new(self) + } + /// Get the element data for the native Rust element. - fn data() -> &'static NativeElementData; + fn data() -> &'static NativeElementData + where + Self: Sized; - /// Pack the element into type-erased content. - fn pack(self) -> Content; + /// Get the element data for the native Rust element. + fn dyn_elem(&self) -> Element; + + /// Dynamically hash the element. + fn dyn_hash(&self, hasher: &mut dyn Hasher); - /// Extract this element from type-erased content. - fn unpack(content: &Content) -> Option<&Self>; + /// Dynamically compare the element. + fn dyn_eq(&self, other: &Content) -> bool; + + /// Dynamically clone the element. + fn dyn_clone(&self) -> Arc<dyn NativeElement>; + + /// Get the element as a dynamic value. + fn as_any(&self) -> &dyn Any; + + /// Get the element as a mutable dynamic value. + fn as_any_mut(&mut self) -> &mut dyn Any; + + /// Get the element as a dynamic value. + fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>; + + /// Get the element's span. + /// + /// May be detached if it has not been set. + fn span(&self) -> Span; + + /// Sets the span of this element. + fn set_span(&mut self, span: Span); + + /// Set the element's span. + fn spanned(mut self, span: Span) -> Self + where + Self: Sized, + { + self.set_span(span); + self + } + + /// Get the element's label. + fn label(&self) -> Option<Label>; + + /// Sets the label of this element. + fn set_label(&mut self, label: Label); + + /// Set the element's label. + fn labelled(mut self, label: ::typst::model::Label) -> Self + where + Self: Sized, + { + self.set_label(label); + self + } + + /// Get the element's location. + fn location(&self) -> Option<Location>; + + /// Sets the location of this element. + fn set_location(&mut self, location: Location); + + /// Checks whether the element is guarded by the given guard. + fn is_guarded(&self, guard: Guard) -> bool; + + /// Pushes a guard onto the element. + fn push_guard(&mut self, guard: Guard); + + /// Whether the element is pristine. + fn is_pristine(&self) -> bool; + + /// Mark the element as having been prepared. + fn mark_prepared(&mut self); + + /// Whether this element needs preparations. + fn needs_preparation(&self) -> bool; + + /// Whether this element has been prepared. + fn is_prepared(&self) -> bool; + + /// Get the field with the given field ID. + fn field(&self, id: u8) -> Option<Value>; + + /// Set the field with the given ID. + fn set_field(&mut self, id: u8, value: Value) -> StrResult<()>; + + /// Get the fields of the element. + fn fields(&self) -> Dict; } /// An element's constructor function. @@ -147,13 +267,17 @@ pub trait Construct { /// /// This is passed only the arguments that remain after execution of the /// element's set rule. - fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content>; + fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content> + where + Self: Sized; } /// An element's set rule. pub trait Set { /// Parse relevant arguments into style properties for this element. - fn set(vm: &mut Vm, args: &mut Args) -> SourceResult<Styles>; + fn set(vm: &mut Vm, args: &mut Args) -> SourceResult<Styles> + where + Self: Sized; } /// Defines a native element. @@ -166,6 +290,9 @@ pub struct NativeElementData { pub construct: fn(&mut Vm, &mut Args) -> SourceResult<Content>, pub set: fn(&mut Vm, &mut Args) -> SourceResult<Styles>, pub vtable: fn(of: TypeId) -> Option<*const ()>, + pub field_id: fn(name: &str) -> Option<u8>, + pub field_name: fn(u8) -> Option<&'static str>, + pub local_name: Option<fn(Lang, Option<Region>) -> &'static str>, pub scope: Lazy<Scope>, pub params: Lazy<Vec<ParamInfo>>, } @@ -180,3 +307,9 @@ cast! { &'static NativeElementData, self => Element::from(self).into_value(), } + +/// The named with which an element is referenced. +pub trait LocalName { + /// Get the name in the given language and (optionally) region. + fn local_name(lang: Lang, region: Option<Region>) -> &'static str; +} diff --git a/crates/typst/src/model/introspect.rs b/crates/typst/src/model/introspect.rs index bd79e5ed..ad7995ce 100644 --- a/crates/typst/src/model/introspect.rs +++ b/crates/typst/src/model/introspect.rs @@ -384,7 +384,7 @@ impl Introspector { } /// Query for a unique element with the label. - pub fn query_label(&self, label: &Label) -> StrResult<Prehashed<Content>> { + pub fn query_label(&self, label: Label) -> StrResult<Prehashed<Content>> { let mut found = None; for elem in self.all().filter(|elem| elem.label() == Some(label)) { if found.is_some() { diff --git a/crates/typst/src/model/label.rs b/crates/typst/src/model/label.rs index 117a682a..8886b370 100644 --- a/crates/typst/src/model/label.rs +++ b/crates/typst/src/model/label.rs @@ -3,6 +3,7 @@ use std::fmt::Debug; use ecow::{eco_format, EcoString}; use crate::eval::{func, scope, ty, Repr}; +use crate::util::PicoStr; /// A label for an element. /// @@ -29,8 +30,27 @@ use crate::eval::{func, scope, ty, Repr}; /// Currently, labels can only be attached to elements in markup mode, not in /// code mode. This might change in the future. #[ty(scope)] -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Label(pub EcoString); +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Label(PicoStr); + +impl Label { + /// Creates a label from a string, interning it. + pub fn new(name: impl Into<PicoStr>) -> Self { + Self(name.into()) + } + + /// Resolves the label to a string. + #[inline] + pub fn as_str(&self) -> &'static str { + self.0.resolve() + } + + /// Turns this label into its inner interned string. + #[inline] + pub fn into_inner(self) -> PicoStr { + self.0 + } +} #[scope] impl Label { @@ -38,7 +58,7 @@ impl Label { #[func(constructor)] pub fn construct( /// The name of the label. - name: EcoString, + name: PicoStr, ) -> Label { Self(name) } @@ -46,7 +66,13 @@ impl Label { impl Repr for Label { fn repr(&self) -> EcoString { - eco_format!("<{}>", self.0) + eco_format!("<{}>", self.0.resolve()) + } +} + +impl From<Label> for PicoStr { + fn from(value: Label) -> Self { + value.into_inner() } } diff --git a/crates/typst/src/model/mod.rs b/crates/typst/src/model/mod.rs index dcad8741..0c91f386 100644 --- a/crates/typst/src/model/mod.rs +++ b/crates/typst/src/model/mod.rs @@ -1,5 +1,6 @@ //! The document model. +mod block; mod content; mod element; mod introspect; @@ -12,14 +13,17 @@ use ecow::EcoVec; #[doc(inline)] pub use typst_macros::elem; +pub use self::block::{Block, Blockable}; pub use self::content::{Content, MetaElem, PlainText}; -pub use self::element::{Construct, Element, NativeElement, NativeElementData, Set}; +pub use self::element::{ + Construct, Element, ElementFields, LocalName, NativeElement, NativeElementData, Set, +}; pub use self::introspect::{Introspector, Location, Locator}; pub use self::label::{Label, Unlabellable}; pub use self::realize::{ applicable, realize, Behave, Behaviour, Finalize, Guard, Locatable, Show, Synthesize, }; -pub use self::selector::{LocatableSelector, Selector, ShowableSelector}; +pub use self::selector::{select_where, LocatableSelector, Selector, ShowableSelector}; pub use self::styles::{ Fold, Property, Recipe, Resolve, Style, StyleChain, StyleVec, StyleVecBuilder, Styles, Transform, diff --git a/crates/typst/src/model/realize.rs b/crates/typst/src/model/realize.rs index 8edeb0e7..7e926b0f 100644 --- a/crates/typst/src/model/realize.rs +++ b/crates/typst/src/model/realize.rs @@ -1,3 +1,7 @@ +use std::borrow::Cow; + +use smallvec::smallvec; + use super::{ Content, Element, MetaElem, NativeElement, Recipe, Selector, StyleChain, Vt, }; @@ -54,7 +58,7 @@ pub fn realize( let meta = Meta::Elem(elem.clone()); return Ok(Some( (elem + MetaElem::new().pack().spanned(span)) - .styled(MetaElem::set_data(vec![meta])), + .styled(MetaElem::set_data(smallvec![meta])), )); } @@ -114,7 +118,7 @@ fn try_apply( } Some(Selector::Label(label)) => { - if target.label() != Some(label) { + if target.label() != Some(*label) { return Ok(None); } @@ -126,11 +130,12 @@ fn try_apply( return Ok(None); }; - let make = |s: &str| target.clone().with_field("text", s); + // We know we are on a `TextElem` and the `text` is always at ID = 0. + let make = |s: &str| target.clone().with_field(0, s); let mut result = vec![]; let mut cursor = 0; - for m in regex.find_iter(&text) { + for m in regex.find_iter(text) { let start = m.start(); if cursor < start { result.push(make(&text[cursor..start])); @@ -200,7 +205,7 @@ pub trait Behave { #[allow(unused_variables)] fn larger( &self, - prev: &(Content, Behaviour, StyleChain), + prev: &(Cow<Content>, Behaviour, StyleChain), styles: StyleChain, ) -> bool { false diff --git a/crates/typst/src/model/selector.rs b/crates/typst/src/model/selector.rs index ce0e62cc..6d117acd 100644 --- a/crates/typst/src/model/selector.rs +++ b/crates/typst/src/model/selector.rs @@ -3,6 +3,7 @@ use std::fmt::Debug; use std::sync::Arc; use ecow::{eco_format, EcoString, EcoVec}; +use smallvec::SmallVec; use super::{Content, Element, Label, Locatable, Location}; use crate::diag::{bail, StrResult}; @@ -12,6 +13,33 @@ use crate::eval::{ }; use crate::util::pretty_array_like; +/// A helper macro to create a field selector used in [`Selector::Elem`] +/// +/// ```ignore +/// select_where!(SequenceElem, Children => vec![]); +/// ``` +#[macro_export] +#[doc(hidden)] +macro_rules! __select_where { + ($ty:ty $(, $field:ident => $value:expr)* $(,)?) => {{ + #[allow(unused_mut)] + let mut fields = ::smallvec::SmallVec::new(); + $( + fields.push(( + <$ty as ::typst::model::ElementFields>::Fields::$field as u8, + $crate::eval::IntoValue::into_value($value), + )); + )* + ::typst::model::Selector::Elem( + <$ty as ::typst::model::NativeElement>::elem(), + Some(fields), + ) + }}; +} + +#[doc(inline)] +pub use crate::__select_where as select_where; + /// A filter for selecting elements within the document. /// /// You can construct a selector in the following ways: @@ -55,7 +83,7 @@ pub enum Selector { /// /// If there is a dictionary, only elements with the fields from the /// dictionary match. - Elem(Element, Option<Dict>), + Elem(Element, Option<SmallVec<[(u8, Value); 1]>>), /// Matches the element at the specified location. Location(Location), /// Matches elements with a specific label. @@ -101,20 +129,21 @@ impl Selector { /// Whether the selector matches for the target. pub fn matches(&self, target: &Content) -> bool { + // TODO: optimize field access to not clone. match self { Self::Elem(element, dict) => { target.func() == *element && dict .iter() .flat_map(|dict| dict.iter()) - .all(|(name, value)| target.field_ref(name) == Some(value)) + .all(|(id, value)| target.get(*id).as_ref() == Some(value)) } - Self::Label(label) => target.label() == Some(label), + Self::Label(label) => target.label() == Some(*label), Self::Regex(regex) => { target.func() == item!(text_elem) - && item!(text_str)(target).map_or(false, |text| regex.is_match(&text)) + && item!(text_str)(target).map_or(false, |text| regex.is_match(text)) } - Self::Can(cap) => target.can_type_id(*cap), + Self::Can(cap) => target.func().can_type_id(*cap), Self::Or(selectors) => selectors.iter().any(move |sel| sel.matches(target)), Self::And(selectors) => selectors.iter().all(move |sel| sel.matches(target)), Self::Location(location) => target.location() == Some(*location), @@ -214,6 +243,11 @@ impl Repr for Selector { match self { Self::Elem(elem, dict) => { if let Some(dict) = dict { + let dict = dict + .iter() + .map(|(id, value)| (elem.field_name(*id).unwrap(), value.clone())) + .map(|(name, value)| (EcoString::from(name).into(), value)) + .collect::<Dict>(); eco_format!("{}.where{}", elem.name(), dict.repr()) } else { elem.name().into() @@ -261,7 +295,7 @@ cast! { /// /// Hopefully, this is made obsolete by a more powerful query mechanism in the /// future. -#[derive(Clone, PartialEq, Hash)] +#[derive(Debug, Clone, PartialEq, Hash)] pub struct LocatableSelector(pub Selector); impl Reflect for LocatableSelector { diff --git a/crates/typst/src/model/styles.rs b/crates/typst/src/model/styles.rs index 8429fecf..394b96fe 100644 --- a/crates/typst/src/model/styles.rs +++ b/crates/typst/src/model/styles.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::fmt::{self, Debug, Formatter, Write}; use std::iter; use std::mem; @@ -5,10 +6,12 @@ use std::ptr; use comemo::Prehashed; use ecow::{eco_vec, EcoString, EcoVec}; +use once_cell::sync::Lazy; +use smallvec::SmallVec; -use super::{Content, Element, NativeElement, Selector, Vt}; +use super::{Block, Blockable, Content, Element, NativeElement, Selector, Vt}; use crate::diag::{SourceResult, Trace, Tracepoint}; -use crate::eval::{cast, ty, Args, FromValue, Func, IntoValue, Repr, Value, Vm}; +use crate::eval::{cast, ty, Args, Func, Repr, Value, Vm}; use crate::syntax::Span; /// A list of style properties. @@ -93,7 +96,7 @@ impl Repr for Styles { } /// A single style property or recipe. -#[derive(Clone, PartialEq, Hash)] +#[derive(Clone, Hash)] pub enum Style { /// A style property originating from a set rule or constructor. Property(Property), @@ -141,32 +144,27 @@ impl From<Recipe> for Style { } /// A style property originating from a set rule or constructor. -#[derive(Clone, PartialEq, Hash)] +#[derive(Clone, Hash)] pub struct Property { /// The element the property belongs to. elem: Element, - /// The property's name. - name: EcoString, + /// The property's ID. + id: u8, /// The property's value. - value: Value, + value: Block, /// The span of the set rule the property stems from. span: Option<Span>, } impl Property { /// Create a new property from a key-value pair. - pub fn new(elem: Element, name: impl Into<EcoString>, value: impl IntoValue) -> Self { - Self { - elem, - name: name.into(), - value: value.into_value(), - span: None, - } + pub fn new<T: Blockable>(elem: Element, id: u8, value: T) -> Self { + Self { elem, id, value: Block::new(value), span: None } } /// Whether this property is the given one. - pub fn is(&self, elem: Element, name: &str) -> bool { - self.elem == elem && self.name == name + pub fn is(&self, elem: Element, id: u8) -> bool { + self.elem == elem && self.id == id } /// Whether this property belongs to the given element. @@ -177,7 +175,7 @@ impl Property { impl Debug for Property { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "set {}({}: {:?})", self.elem.name(), self.name, self.value)?; + write!(f, "set {}({}: {:?})", self.elem.name(), self.id, self.value)?; Ok(()) } } @@ -317,61 +315,72 @@ impl<'a> StyleChain<'a> { } } + /// Cast the first value for the given property in the chain, + /// returning a borrowed value if possible. + pub fn get_borrowed<T: Blockable + Clone>( + self, + func: Element, + id: u8, + inherent: Option<&'a T>, + default: &'static Lazy<T>, + ) -> &'a T { + self.properties::<T>(func, id, inherent) + .next() + .unwrap_or_else(|| default) + } + /// Cast the first value for the given property in the chain. - pub fn get<T: FromValue>( + pub fn get<T: Blockable + Clone>( self, func: Element, - name: &'a str, - inherent: Option<Value>, - default: impl Fn() -> T, + id: u8, + inherent: Option<&T>, + default: &'static Lazy<T>, ) -> T { - self.properties::<T>(func, name, inherent) - .next() - .unwrap_or_else(default) + self.get_borrowed(func, id, inherent, default).clone() } /// Cast the first value for the given property in the chain. - pub fn get_resolve<T: FromValue + Resolve>( + pub fn get_resolve<T: Blockable + Clone + Resolve>( self, func: Element, - name: &'a str, - inherent: Option<Value>, - default: impl Fn() -> T, + id: u8, + inherent: Option<&T>, + default: &'static Lazy<T>, ) -> T::Output { - self.get(func, name, inherent, default).resolve(self) + self.get(func, id, inherent, default).resolve(self) } /// Cast the first value for the given property in the chain. - pub fn get_fold<T: FromValue + Fold>( + pub fn get_fold<T: Blockable + Clone + Fold>( self, func: Element, - name: &'a str, - inherent: Option<Value>, + id: u8, + inherent: Option<&T>, default: impl Fn() -> T::Output, ) -> T::Output { fn next<T: Fold>( mut values: impl Iterator<Item = T>, - _styles: StyleChain, default: &impl Fn() -> T::Output, ) -> T::Output { values .next() - .map(|value| value.fold(next(values, _styles, default))) + .map(|value| value.fold(next(values, default))) .unwrap_or_else(default) } - next(self.properties::<T>(func, name, inherent), self, &default) + next(self.properties::<T>(func, id, inherent).cloned(), &default) } /// Cast the first value for the given property in the chain. pub fn get_resolve_fold<T>( self, func: Element, - name: &'a str, - inherent: Option<Value>, + id: u8, + inherent: Option<&T>, default: impl Fn() -> <T::Output as Fold>::Output, ) -> <T::Output as Fold>::Output where - T: FromValue + Resolve, + T: Blockable + Clone + Resolve, T::Output: Fold, { fn next<T>( @@ -380,7 +389,7 @@ impl<'a> StyleChain<'a> { default: &impl Fn() -> <T::Output as Fold>::Output, ) -> <T::Output as Fold>::Output where - T: Resolve, + T: Blockable + Resolve, T::Output: Fold, { values @@ -388,7 +397,8 @@ impl<'a> StyleChain<'a> { .map(|value| value.resolve(styles).fold(next(values, styles, default))) .unwrap_or_else(default) } - next(self.properties::<T>(func, name, inherent), self, &default) + + next(self.properties::<T>(func, id, inherent).cloned(), self, &default) } /// Iterate over all style recipes in the chain. @@ -397,25 +407,28 @@ impl<'a> StyleChain<'a> { } /// Iterate over all values for the given property in the chain. - pub fn properties<T: FromValue + 'a>( + pub fn properties<T: Blockable>( self, func: Element, - name: &'a str, - inherent: Option<Value>, - ) -> impl Iterator<Item = T> + '_ { - inherent - .into_iter() - .chain( - self.entries() - .filter_map(Style::property) - .filter(move |property| property.is(func, name)) - .map(|property| property.value.clone()), - ) - .map(move |value| { - value.cast().unwrap_or_else(|err| { - panic!("{} (for {}.{})", err, func.name(), name) - }) - }) + id: u8, + inherent: Option<&'a T>, + ) -> impl Iterator<Item = &'a T> { + inherent.into_iter().chain( + self.entries() + .filter_map(Style::property) + .filter(move |property| property.is(func, id)) + .map(|property| &property.value) + .map(move |value| { + value.downcast().unwrap_or_else(|| { + panic!( + "attempted to read a value of a different type than was written {}.{}: {:?}", + func.name(), + func.field_name(id).unwrap(), + value + ) + }) + }), + ) } /// Convert to a style map. @@ -579,8 +592,8 @@ impl<T> StyleVec<T> { } } -impl StyleVec<Content> { - pub fn to_vec(self) -> Vec<Content> { +impl<'a> StyleVec<Cow<'a, Content>> { + pub fn to_vec(self) -> Vec<Prehashed<Content>> { self.items .into_iter() .zip( @@ -588,7 +601,8 @@ impl StyleVec<Content> { .iter() .flat_map(|(map, count)| iter::repeat(map).take(*count)), ) - .map(|(content, styles)| content.styled_with_map(styles.clone())) + .map(|(content, styles)| content.into_owned().styled_with_map(styles.clone())) + .map(Prehashed::new) .collect() } } @@ -759,3 +773,12 @@ impl<T> Fold for Vec<T> { self } } + +impl<T, const N: usize> Fold for SmallVec<[T; N]> { + type Output = SmallVec<[T; N]>; + + fn fold(mut self, outer: Self::Output) -> Self::Output { + self.extend(outer); + self + } +} diff --git a/crates/typst/src/util/mod.rs b/crates/typst/src/util/mod.rs index f5c20520..573d6b1c 100644 --- a/crates/typst/src/util/mod.rs +++ b/crates/typst/src/util/mod.rs @@ -2,8 +2,10 @@ pub mod fat; pub mod fmt; +mod str; pub use self::fmt::{pretty_array_like, pretty_comma_list, separated_list}; +pub use self::str::PicoStr; use std::fmt::{Debug, Formatter}; use std::hash::Hash; diff --git a/crates/typst/src/util/str.rs b/crates/typst/src/util/str.rs new file mode 100644 index 00000000..398392f8 --- /dev/null +++ b/crates/typst/src/util/str.rs @@ -0,0 +1,65 @@ +use std::fmt::{self, Debug, Formatter}; + +use ecow::EcoString; +use lasso::{Spur, ThreadedRodeo}; +use once_cell::sync::Lazy; +use typst_macros::cast; + +/// The global string interner. +static INTERNER: Lazy<ThreadedRodeo> = Lazy::new(ThreadedRodeo::new); + +/// An interned string. +/// +/// The API is purposefully kept small. This is because it might be relatively +/// slow to look up a string in the interner, so we want to avoid doing it +/// unnecessarily. For this reason, the user should use the [`PicoStr::resolve`] +/// method to get the underlying string, such that the lookup is done only once. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct PicoStr(Spur); + +impl PicoStr { + /// Creates a new interned string. + pub fn new(s: impl AsRef<str>) -> Self { + Self(INTERNER.get_or_intern(s.as_ref())) + } + + /// Creates a new interned string from a static string. + pub fn static_(s: &'static str) -> Self { + Self(INTERNER.get_or_intern_static(s)) + } + + /// Resolves the interned string. + pub fn resolve(&self) -> &'static str { + INTERNER.resolve(&self.0) + } +} + +cast! { + PicoStr, + self => self.resolve().into_value(), + v: EcoString => Self::new(&v), +} + +impl Debug for PicoStr { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.resolve().fmt(f) + } +} + +impl AsRef<str> for PicoStr { + fn as_ref(&self) -> &str { + self.resolve() + } +} + +impl From<&str> for PicoStr { + fn from(value: &str) -> Self { + Self::new(value) + } +} + +impl From<&EcoString> for PicoStr { + fn from(value: &EcoString) -> Self { + Self::new(value) + } +} |
