diff options
Diffstat (limited to 'crates')
112 files changed, 3068 insertions, 2250 deletions
diff --git a/crates/typst-eval/src/markup.rs b/crates/typst-eval/src/markup.rs index 9118ded5..cc960626 100644 --- a/crates/typst-eval/src/markup.rs +++ b/crates/typst-eval/src/markup.rs @@ -186,7 +186,7 @@ impl Eval for ast::Raw<'_> { let lines = self.lines().map(|line| (line.get().clone(), line.span())).collect(); let mut elem = RawElem::new(RawContent::Lines(lines)).with_block(self.block()); if let Some(lang) = self.lang() { - elem.push_lang(Some(lang.get().clone())); + elem.lang.set(Some(lang.get().clone())); } Ok(elem.pack()) } @@ -219,9 +219,8 @@ impl Eval for ast::Ref<'_> { .expect("unexpected empty reference"); let mut elem = RefElem::new(target); if let Some(supplement) = self.supplement() { - elem.push_supplement(Smart::Custom(Some(Supplement::Content( - supplement.eval(vm)?, - )))); + elem.supplement + .set(Smart::Custom(Some(Supplement::Content(supplement.eval(vm)?)))); } Ok(elem.pack()) } @@ -252,7 +251,7 @@ impl Eval for ast::EnumItem<'_> { let body = self.body().eval(vm)?; let mut elem = EnumItem::new(body); if let Some(number) = self.number() { - elem.push_number(Some(number)); + elem.number.set(Some(number)); } Ok(elem.pack()) } diff --git a/crates/typst-eval/src/math.rs b/crates/typst-eval/src/math.rs index 0e271a08..c2325a8c 100644 --- a/crates/typst-eval/src/math.rs +++ b/crates/typst-eval/src/math.rs @@ -80,17 +80,17 @@ impl Eval for ast::MathAttach<'_> { let mut elem = AttachElem::new(base); if let Some(expr) = self.top() { - elem.push_t(Some(expr.eval_display(vm)?)); + elem.t.set(Some(expr.eval_display(vm)?)); } // Always attach primes in scripts style (not limits style), // i.e. at the top-right corner. if let Some(primes) = self.primes() { - elem.push_tr(Some(primes.eval(vm)?)); + elem.tr.set(Some(primes.eval(vm)?)); } if let Some(expr) = self.bottom() { - elem.push_b(Some(expr.eval_display(vm)?)); + elem.b.set(Some(expr.eval_display(vm)?)); } Ok(elem.pack()) diff --git a/crates/typst-eval/src/rules.rs b/crates/typst-eval/src/rules.rs index f4c1563f..eb6a1e6d 100644 --- a/crates/typst-eval/src/rules.rs +++ b/crates/typst-eval/src/rules.rs @@ -1,6 +1,6 @@ use typst_library::diag::{warning, At, SourceResult}; use typst_library::foundations::{ - Element, Fields, Func, Recipe, Selector, ShowableSelector, Styles, Transformation, + Element, Func, Recipe, Selector, ShowableSelector, Styles, Transformation, }; use typst_library::layout::BlockElem; use typst_library::model::ParElem; @@ -62,8 +62,7 @@ fn check_show_par_set_block(vm: &mut Vm, recipe: &Recipe) { if let Some(Selector::Elem(elem, _)) = recipe.selector(); if *elem == Element::of::<ParElem>(); if let Transformation::Style(styles) = recipe.transform(); - if styles.has::<BlockElem>(<BlockElem as Fields>::Enum::Above as _) || - styles.has::<BlockElem>(<BlockElem as Fields>::Enum::Below as _); + if styles.has(BlockElem::above) || styles.has(BlockElem::below); then { vm.engine.sink.warn(warning!( recipe.span(), diff --git a/crates/typst-html/src/lib.rs b/crates/typst-html/src/lib.rs index 49518716..60ffa78e 100644 --- a/crates/typst-html/src/lib.rs +++ b/crates/typst-html/src/lib.rs @@ -177,12 +177,12 @@ fn handle( output.push(HtmlNode::Tag(elem.tag.clone())); } else if let Some(elem) = child.to_packed::<HtmlElem>() { let mut children = vec![]; - if let Some(body) = elem.body(styles) { + if let Some(body) = elem.body.get_ref(styles) { children = html_fragment(engine, body, locator.next(&elem.span()), styles)?; } let element = HtmlElement { tag: elem.tag, - attrs: elem.attrs(styles).clone(), + attrs: elem.attrs.get_cloned(styles), children, span: elem.span(), }; @@ -198,7 +198,7 @@ fn handle( ); } else if let Some(elem) = child.to_packed::<BoxElem>() { // TODO: This is rather incomplete. - if let Some(body) = elem.body(styles) { + if let Some(body) = elem.body.get_ref(styles) { let children = html_fragment(engine, body, locator.next(&elem.span()), styles)?; output.push( @@ -212,7 +212,7 @@ fn handle( } else if let Some((elem, body)) = child .to_packed::<BlockElem>() - .and_then(|elem| match elem.body(styles) { + .and_then(|elem| match elem.body.get_ref(styles) { Some(BlockBody::Content(body)) => Some((elem, body)), _ => None, }) @@ -233,12 +233,12 @@ fn handle( output.push(HtmlElement::new(tag::br).spanned(elem.span()).into()); } else if let Some(elem) = child.to_packed::<SmartQuoteElem>() { output.push(HtmlNode::text( - if elem.double(styles) { '"' } else { '\'' }, + if elem.double.get(styles) { '"' } else { '\'' }, child.span(), )); } else if let Some(elem) = child.to_packed::<FrameElem>() { let locator = locator.next(&elem.span()); - let style = TargetElem::set_target(Target::Paged).wrap(); + let style = TargetElem::target.set(Target::Paged).wrap(); let frame = (engine.routines.layout_frame)( engine, &elem.body, @@ -248,7 +248,7 @@ fn handle( )?; output.push(HtmlNode::Frame(HtmlFrame { inner: frame, - text_size: TextElem::size_in(styles), + text_size: styles.resolve(TextElem::size), })); } else { engine.sink.warn(warning!( diff --git a/crates/typst-ide/src/definition.rs b/crates/typst-ide/src/definition.rs index ae1ba287..4c2b80cd 100644 --- a/crates/typst-ide/src/definition.rs +++ b/crates/typst-ide/src/definition.rs @@ -187,6 +187,6 @@ mod tests { #[test] fn test_definition_std() { - test("#table", 1, Side::After).must_be_value(typst::model::TableElem::elem()); + test("#table", 1, Side::After).must_be_value(typst::model::TableElem::ELEM); } } diff --git a/crates/typst-ide/src/tests.rs b/crates/typst-ide/src/tests.rs index dd5c230a..b3f368f2 100644 --- a/crates/typst-ide/src/tests.rs +++ b/crates/typst-ide/src/tests.rs @@ -171,13 +171,11 @@ fn library() -> Library { let mut lib = typst::Library::builder() .with_features([Feature::Html].into_iter().collect()) .build(); + lib.styles.set(PageElem::width, Smart::Custom(Abs::pt(120.0).into())); + lib.styles.set(PageElem::height, Smart::Auto); lib.styles - .set(PageElem::set_width(Smart::Custom(Abs::pt(120.0).into()))); - lib.styles.set(PageElem::set_height(Smart::Auto)); - lib.styles.set(PageElem::set_margin(Margin::splat(Some(Smart::Custom( - Abs::pt(10.0).into(), - ))))); - lib.styles.set(TextElem::set_size(TextSize(Abs::pt(10.0).into()))); + .set(PageElem::margin, Margin::splat(Some(Smart::Custom(Abs::pt(10.0).into())))); + lib.styles.set(TextElem::size, TextSize(Abs::pt(10.0).into())); lib } diff --git a/crates/typst-layout/src/flow/block.rs b/crates/typst-layout/src/flow/block.rs index 6c2c3923..d6cfe3a9 100644 --- a/crates/typst-layout/src/flow/block.rs +++ b/crates/typst-layout/src/flow/block.rs @@ -24,15 +24,15 @@ pub fn layout_single_block( region: Region, ) -> SourceResult<Frame> { // Fetch sizing properties. - let width = elem.width(styles); - let height = elem.height(styles); - let inset = elem.inset(styles).unwrap_or_default(); + let width = elem.width.get(styles); + let height = elem.height.get(styles); + let inset = elem.inset.resolve(styles).unwrap_or_default(); // Build the pod regions. let pod = unbreakable_pod(&width.into(), &height, &inset, styles, region.size); // Layout the body. - let body = elem.body(styles); + let body = elem.body.get_ref(styles); let mut frame = match body { // If we have no body, just create one frame. Its size will be // adjusted below. @@ -73,18 +73,19 @@ pub fn layout_single_block( } // Prepare fill and stroke. - let fill = elem.fill(styles); + let fill = elem.fill.get_cloned(styles); let stroke = elem - .stroke(styles) + .stroke + .resolve(styles) .unwrap_or_default() .map(|s| s.map(Stroke::unwrap_or_default)); // Only fetch these if necessary (for clipping or filling/stroking). - let outset = LazyCell::new(|| elem.outset(styles).unwrap_or_default()); - let radius = LazyCell::new(|| elem.radius(styles).unwrap_or_default()); + let outset = LazyCell::new(|| elem.outset.resolve(styles).unwrap_or_default()); + let radius = LazyCell::new(|| elem.radius.resolve(styles).unwrap_or_default()); // Clip the contents, if requested. - if elem.clip(styles) { + if elem.clip.get(styles) { frame.clip(clip_rect(frame.size(), &radius, &stroke, &outset)); } @@ -111,9 +112,9 @@ pub fn layout_multi_block( regions: Regions, ) -> SourceResult<Fragment> { // Fetch sizing properties. - let width = elem.width(styles); - let height = elem.height(styles); - let inset = elem.inset(styles).unwrap_or_default(); + let width = elem.width.get(styles); + let height = elem.height.get(styles); + let inset = elem.inset.resolve(styles).unwrap_or_default(); // Allocate a small vector for backlogs. let mut buf = SmallVec::<[Abs; 2]>::new(); @@ -122,7 +123,7 @@ pub fn layout_multi_block( let pod = breakable_pod(&width.into(), &height, &inset, styles, regions, &mut buf); // Layout the body. - let body = elem.body(styles); + let body = elem.body.get_ref(styles); let mut fragment = match body { // If we have no body, just create one frame plus one per backlog // region. We create them zero-sized; if necessary, their size will @@ -188,18 +189,19 @@ pub fn layout_multi_block( }; // Prepare fill and stroke. - let fill = elem.fill(styles); + let fill = elem.fill.get_ref(styles); let stroke = elem - .stroke(styles) + .stroke + .resolve(styles) .unwrap_or_default() .map(|s| s.map(Stroke::unwrap_or_default)); // Only fetch these if necessary (for clipping or filling/stroking). - let outset = LazyCell::new(|| elem.outset(styles).unwrap_or_default()); - let radius = LazyCell::new(|| elem.radius(styles).unwrap_or_default()); + let outset = LazyCell::new(|| elem.outset.resolve(styles).unwrap_or_default()); + let radius = LazyCell::new(|| elem.radius.resolve(styles).unwrap_or_default()); // Fetch/compute these outside of the loop. - let clip = elem.clip(styles); + let clip = elem.clip.get(styles); let has_fill_or_stroke = fill.is_some() || stroke.iter().any(Option::is_some); let has_inset = !inset.is_zero(); let is_explicit = matches!(body, None | Some(BlockBody::Content(_))); diff --git a/crates/typst-layout/src/flow/collect.rs b/crates/typst-layout/src/flow/collect.rs index 2c14f7a3..76268b59 100644 --- a/crates/typst-layout/src/flow/collect.rs +++ b/crates/typst-layout/src/flow/collect.rs @@ -89,7 +89,7 @@ impl<'a> Collector<'a, '_, '_> { } else if child.is::<FlushElem>() { self.output.push(Child::Flush); } else if let Some(elem) = child.to_packed::<ColbreakElem>() { - self.output.push(Child::Break(elem.weak(styles))); + self.output.push(Child::Break(elem.weak.get(styles))); } else if child.is::<PagebreakElem>() { bail!( child.span(), "pagebreaks are not allowed inside of containers"; @@ -132,7 +132,7 @@ impl<'a> Collector<'a, '_, '_> { self.output.push(Child::Tag(&elem.tag)); } - let leading = ParElem::leading_in(styles); + let leading = styles.resolve(ParElem::leading); self.lines(lines, leading, styles); for (c, _) in &self.children[end..] { @@ -146,7 +146,9 @@ impl<'a> Collector<'a, '_, '_> { /// Collect vertical spacing into a relative or fractional child. fn v(&mut self, elem: &'a Packed<VElem>, styles: StyleChain<'a>) { self.output.push(match elem.amount { - Spacing::Rel(rel) => Child::Rel(rel.resolve(styles), elem.weak(styles) as u8), + Spacing::Rel(rel) => { + Child::Rel(rel.resolve(styles), elem.weak.get(styles) as u8) + } Spacing::Fr(fr) => Child::Fr(fr), }); } @@ -169,8 +171,8 @@ impl<'a> Collector<'a, '_, '_> { )? .into_frames(); - let spacing = elem.spacing(styles); - let leading = elem.leading(styles); + let spacing = elem.spacing.resolve(styles); + let leading = elem.leading.resolve(styles); self.output.push(Child::Rel(spacing.into(), 4)); @@ -184,8 +186,8 @@ impl<'a> Collector<'a, '_, '_> { /// Collect laid-out lines. fn lines(&mut self, lines: Vec<Frame>, leading: Abs, styles: StyleChain<'a>) { - let align = AlignElem::alignment_in(styles).resolve(styles); - let costs = TextElem::costs_in(styles); + let align = styles.resolve(AlignElem::alignment); + let costs = styles.get(TextElem::costs); // Determine whether to prevent widow and orphans. let len = lines.len(); @@ -231,23 +233,23 @@ impl<'a> Collector<'a, '_, '_> { /// whether it is breakable. fn block(&mut self, elem: &'a Packed<BlockElem>, styles: StyleChain<'a>) { let locator = self.locator.next(&elem.span()); - let align = AlignElem::alignment_in(styles).resolve(styles); + let align = styles.resolve(AlignElem::alignment); let alone = self.children.len() == 1; - let sticky = elem.sticky(styles); - let breakable = elem.breakable(styles); - let fr = match elem.height(styles) { + let sticky = elem.sticky.get(styles); + let breakable = elem.breakable.get(styles); + let fr = match elem.height.get(styles) { Sizing::Fr(fr) => Some(fr), _ => None, }; - let fallback = LazyCell::new(|| ParElem::spacing_in(styles)); + let fallback = LazyCell::new(|| styles.resolve(ParElem::spacing)); let spacing = |amount| match amount { Smart::Auto => Child::Rel((*fallback).into(), 4), Smart::Custom(Spacing::Rel(rel)) => Child::Rel(rel.resolve(styles), 3), Smart::Custom(Spacing::Fr(fr)) => Child::Fr(fr), }; - self.output.push(spacing(elem.above(styles))); + self.output.push(spacing(elem.above.get(styles))); if !breakable || fr.is_some() { self.output.push(Child::Single(self.boxed(SingleChild { @@ -272,7 +274,7 @@ impl<'a> Collector<'a, '_, '_> { }))); }; - self.output.push(spacing(elem.below(styles))); + self.output.push(spacing(elem.below.get(styles))); self.par_situation = ParSituation::Other; } @@ -282,13 +284,13 @@ impl<'a> Collector<'a, '_, '_> { elem: &'a Packed<PlaceElem>, styles: StyleChain<'a>, ) -> SourceResult<()> { - let alignment = elem.alignment(styles); + let alignment = elem.alignment.get(styles); let align_x = alignment.map_or(FixedAlignment::Center, |align| { align.x().unwrap_or_default().resolve(styles) }); let align_y = alignment.map(|align| align.y().map(|y| y.resolve(styles))); - let scope = elem.scope(styles); - let float = elem.float(styles); + let scope = elem.scope.get(styles); + let float = elem.float.get(styles); match (float, align_y) { (true, Smart::Custom(None | Some(FixedAlignment::Center))) => bail!( @@ -312,8 +314,8 @@ impl<'a> Collector<'a, '_, '_> { } let locator = self.locator.next(&elem.span()); - let clearance = elem.clearance(styles); - let delta = Axes::new(elem.dx(styles), elem.dy(styles)).resolve(styles); + let clearance = elem.clearance.resolve(styles); + let delta = Axes::new(elem.dx.get(styles), elem.dy.get(styles)).resolve(styles); self.output.push(Child::Placed(self.boxed(PlacedChild { align_x, align_y, @@ -631,7 +633,7 @@ impl PlacedChild<'_> { pub fn layout(&self, engine: &mut Engine, base: Size) -> SourceResult<Frame> { self.cell.get_or_init(base, |base| { let align = self.alignment.unwrap_or_else(|| Alignment::CENTER); - let aligned = AlignElem::set_alignment(align).wrap(); + let aligned = AlignElem::alignment.set(align).wrap(); let styles = self.styles.chain(&aligned); let mut frame = layout_and_modify(styles, |styles| { diff --git a/crates/typst-layout/src/flow/compose.rs b/crates/typst-layout/src/flow/compose.rs index 54dc487a..ed514a24 100644 --- a/crates/typst-layout/src/flow/compose.rs +++ b/crates/typst-layout/src/flow/compose.rs @@ -851,7 +851,7 @@ fn layout_line_number_reset( config: &Config, locator: &mut SplitLocator, ) -> SourceResult<Frame> { - let counter = Counter::of(ParLineMarker::elem()); + let counter = Counter::of(ParLineMarker::ELEM); let update = CounterUpdate::Set(CounterState::init(false)); let content = counter.update(Span::detached(), update); crate::layout_frame( @@ -879,7 +879,7 @@ fn layout_line_number( locator: &mut SplitLocator, numbering: &Numbering, ) -> SourceResult<Frame> { - let counter = Counter::of(ParLineMarker::elem()); + let counter = Counter::of(ParLineMarker::ELEM); let update = CounterUpdate::Step(NonZeroUsize::ONE); let numbering = Smart::Custom(numbering.clone()); diff --git a/crates/typst-layout/src/flow/mod.rs b/crates/typst-layout/src/flow/mod.rs index cba228bc..f4f1c091 100644 --- a/crates/typst-layout/src/flow/mod.rs +++ b/crates/typst-layout/src/flow/mod.rs @@ -98,8 +98,8 @@ pub fn layout_columns( locator.track(), styles, regions, - elem.count(styles), - elem.gutter(styles), + elem.count.get(styles), + elem.gutter.resolve(styles), ) } @@ -251,22 +251,22 @@ fn configuration<'x>( let gutter = column_gutter.relative_to(regions.base().x); let width = (regions.size.x - gutter * (count - 1) as f64) / count as f64; - let dir = TextElem::dir_in(shared); + let dir = shared.resolve(TextElem::dir); ColumnConfig { count, width, gutter, dir } }, footnote: FootnoteConfig { - separator: FootnoteEntry::separator_in(shared), - clearance: FootnoteEntry::clearance_in(shared), - gap: FootnoteEntry::gap_in(shared), + separator: shared.get_cloned(FootnoteEntry::separator), + clearance: shared.resolve(FootnoteEntry::clearance), + gap: shared.resolve(FootnoteEntry::gap), expand: regions.expand.x, }, line_numbers: (mode == FlowMode::Root).then(|| LineNumberConfig { - scope: ParLine::numbering_scope_in(shared), + scope: shared.get(ParLine::numbering_scope), default_clearance: { - let width = if PageElem::flipped_in(shared) { - PageElem::height_in(shared) + let width = if shared.get(PageElem::flipped) { + shared.resolve(PageElem::height) } else { - PageElem::width_in(shared) + shared.resolve(PageElem::width) }; // Clamp below is safe (min <= max): if the font size is diff --git a/crates/typst-layout/src/grid/layouter.rs b/crates/typst-layout/src/grid/layouter.rs index 42fe38db..d4f11f47 100644 --- a/crates/typst-layout/src/grid/layouter.rs +++ b/crates/typst-layout/src/grid/layouter.rs @@ -249,7 +249,7 @@ impl<'a> GridLayouter<'a> { rowspans: vec![], finished: vec![], finished_header_rows: vec![], - is_rtl: TextElem::dir_in(styles) == Dir::RTL, + is_rtl: styles.resolve(TextElem::dir) == Dir::RTL, repeating_headers: vec![], upcoming_headers: &grid.headers, pending_headers: Default::default(), diff --git a/crates/typst-layout/src/image.rs b/crates/typst-layout/src/image.rs index a8f4a0c8..261a58fa 100644 --- a/crates/typst-layout/src/image.rs +++ b/crates/typst-layout/src/image.rs @@ -28,7 +28,7 @@ pub fn layout_image( // Take the format that was explicitly defined, or parse the extension, // or try to detect the format. let Derived { source, derived: loaded } = &elem.source; - let format = match elem.format(styles) { + let format = match elem.format.get(styles) { Smart::Custom(v) => v, Smart::Auto => determine_format(source, &loaded.data).at(span)?, }; @@ -55,7 +55,7 @@ pub fn layout_image( RasterImage::new( loaded.data.clone(), format, - elem.icc(styles).as_ref().map(|icc| icc.derived.clone()), + elem.icc.get_ref(styles).as_ref().map(|icc| icc.derived.clone()), ) .at(span)?, ), @@ -69,7 +69,7 @@ pub fn layout_image( ), }; - let image = Image::new(kind, elem.alt(styles), elem.scaling(styles)); + let image = Image::new(kind, elem.alt.get_cloned(styles), elem.scaling.get(styles)); // Determine the image's pixel aspect ratio. let pxw = image.width(); @@ -106,7 +106,7 @@ pub fn layout_image( }; // Compute the actual size of the fitted image. - let fit = elem.fit(styles); + let fit = elem.fit.get(styles); let fitted = match fit { ImageFit::Cover | ImageFit::Contain => { if wide == (fit == ImageFit::Contain) { diff --git a/crates/typst-layout/src/inline/box.rs b/crates/typst-layout/src/inline/box.rs index e21928d3..65b02533 100644 --- a/crates/typst-layout/src/inline/box.rs +++ b/crates/typst-layout/src/inline/box.rs @@ -21,15 +21,15 @@ pub fn layout_box( region: Size, ) -> SourceResult<Frame> { // Fetch sizing properties. - let width = elem.width(styles); - let height = elem.height(styles); - let inset = elem.inset(styles).unwrap_or_default(); + let width = elem.width.get(styles); + let height = elem.height.get(styles); + let inset = elem.inset.resolve(styles).unwrap_or_default(); // Build the pod region. let pod = unbreakable_pod(&width, &height.into(), &inset, styles, region); // Layout the body. - let mut frame = match elem.body(styles) { + let mut frame = match elem.body.get_ref(styles) { // If we have no body, just create an empty frame. If necessary, // its size will be adjusted below. None => Frame::hard(Size::zero()), @@ -50,18 +50,19 @@ pub fn layout_box( } // Prepare fill and stroke. - let fill = elem.fill(styles); + let fill = elem.fill.get_cloned(styles); let stroke = elem - .stroke(styles) + .stroke + .resolve(styles) .unwrap_or_default() .map(|s| s.map(Stroke::unwrap_or_default)); // Only fetch these if necessary (for clipping or filling/stroking). - let outset = LazyCell::new(|| elem.outset(styles).unwrap_or_default()); - let radius = LazyCell::new(|| elem.radius(styles).unwrap_or_default()); + let outset = LazyCell::new(|| elem.outset.resolve(styles).unwrap_or_default()); + let radius = LazyCell::new(|| elem.radius.resolve(styles).unwrap_or_default()); // Clip the contents, if requested. - if elem.clip(styles) { + if elem.clip.get(styles) { frame.clip(clip_rect(frame.size(), &radius, &stroke, &outset)); } @@ -78,7 +79,7 @@ pub fn layout_box( // Apply baseline shift. Do this after setting the size and applying the // inset, so that a relative shift is resolved relative to the final // height. - let shift = elem.baseline(styles).relative_to(frame.height()); + let shift = elem.baseline.resolve(styles).relative_to(frame.height()); if !shift.is_zero() { frame.set_baseline(frame.baseline() - shift); } diff --git a/crates/typst-layout/src/inline/collect.rs b/crates/typst-layout/src/inline/collect.rs index 829f64b6..2744b31e 100644 --- a/crates/typst-layout/src/inline/collect.rs +++ b/crates/typst-layout/src/inline/collect.rs @@ -144,7 +144,7 @@ pub fn collect<'a>( collector.push_text(" ", styles); } else if let Some(elem) = child.to_packed::<TextElem>() { collector.build_text(styles, |full| { - let dir = TextElem::dir_in(styles); + let dir = styles.resolve(TextElem::dir); if dir != config.dir { // Insert "Explicit Directional Embedding". match dir { @@ -154,7 +154,7 @@ pub fn collect<'a>( } } - if let Some(case) = TextElem::case_in(styles) { + if let Some(case) = styles.get(TextElem::case) { full.push_str(&case.apply(&elem.text)); } else { full.push_str(&elem.text); @@ -174,20 +174,22 @@ pub fn collect<'a>( Spacing::Fr(fr) => Item::Fractional(fr, None), Spacing::Rel(rel) => Item::Absolute( rel.resolve(styles).relative_to(region.x), - elem.weak(styles), + elem.weak.get(styles), ), }); } else if let Some(elem) = child.to_packed::<LinebreakElem>() { - collector - .push_text(if elem.justify(styles) { "\u{2028}" } else { "\n" }, styles); + collector.push_text( + if elem.justify.get(styles) { "\u{2028}" } else { "\n" }, + styles, + ); } else if let Some(elem) = child.to_packed::<SmartQuoteElem>() { - let double = elem.double(styles); - if elem.enabled(styles) { + let double = elem.double.get(styles); + if elem.enabled.get(styles) { let quotes = SmartQuotes::get( - elem.quotes(styles), - TextElem::lang_in(styles), - TextElem::region_in(styles), - elem.alternative(styles), + elem.quotes.get_ref(styles), + styles.get(TextElem::lang), + styles.get(TextElem::region), + elem.alternative.get(styles), ); let before = collector.full.chars().rev().find(|&c| !is_default_ignorable(c)); @@ -215,7 +217,7 @@ pub fn collect<'a>( collector.push_item(Item::Skip(POP_ISOLATE)); } else if let Some(elem) = child.to_packed::<BoxElem>() { let loc = locator.next(&elem.span()); - if let Sizing::Fr(v) = elem.width(styles) { + if let Sizing::Fr(v) = elem.width.get(styles) { collector.push_item(Item::Fractional(v, Some((elem, loc, styles)))); } else { let mut frame = layout_and_modify(styles, |styles| { diff --git a/crates/typst-layout/src/inline/line.rs b/crates/typst-layout/src/inline/line.rs index b850e50e..58162d12 100644 --- a/crates/typst-layout/src/inline/line.rs +++ b/crates/typst-layout/src/inline/line.rs @@ -2,6 +2,7 @@ use std::fmt::{self, Debug, Formatter}; use std::ops::{Deref, DerefMut}; use typst_library::engine::Engine; +use typst_library::foundations::Resolve; use typst_library::introspection::{SplitLocator, Tag}; use typst_library::layout::{Abs, Dir, Em, Fr, Frame, FrameItem, Point}; use typst_library::model::ParLineMarker; @@ -418,10 +419,11 @@ pub fn apply_shift<'a>( frame: &mut Frame, styles: StyleChain, ) { - let mut baseline = TextElem::baseline_in(styles); + let mut baseline = styles.resolve(TextElem::baseline); let mut compensation = Abs::zero(); - if let Some(scripts) = TextElem::shift_settings_in(styles) { - let font_metrics = TextElem::font_in(styles) + if let Some(scripts) = styles.get_ref(TextElem::shift_settings) { + let font_metrics = styles + .get_ref(TextElem::font) .into_iter() .find_map(|family| { world @@ -462,7 +464,7 @@ pub fn commit( if let Some(Item::Text(text)) = line.items.first() { if let Some(glyph) = text.glyphs.first() { if !text.dir.is_positive() - && TextElem::overhang_in(text.styles) + && text.styles.get(TextElem::overhang) && (line.items.len() > 1 || text.glyphs.len() > 1) { let amount = overhang(glyph.c) * glyph.x_advance.at(glyph.size); @@ -476,7 +478,7 @@ pub fn commit( if let Some(Item::Text(text)) = line.items.last() { if let Some(glyph) = text.glyphs.last() { if text.dir.is_positive() - && TextElem::overhang_in(text.styles) + && text.styles.get(TextElem::overhang) && (line.items.len() > 1 || text.glyphs.len() > 1) { let amount = overhang(glyph.c) * glyph.x_advance.at(glyph.size); diff --git a/crates/typst-layout/src/inline/linebreak.rs b/crates/typst-layout/src/inline/linebreak.rs index 709745ed..955360df 100644 --- a/crates/typst-layout/src/inline/linebreak.rs +++ b/crates/typst-layout/src/inline/linebreak.rs @@ -846,7 +846,9 @@ fn hyphenate_at(p: &Preparation, offset: usize) -> bool { p.config.hyphenate.unwrap_or_else(|| { let (_, item) = p.get(offset); match item.text() { - Some(text) => TextElem::hyphenate_in(text.styles).unwrap_or(p.config.justify), + Some(text) => { + text.styles.get(TextElem::hyphenate).unwrap_or(p.config.justify) + } None => false, } }) @@ -857,7 +859,7 @@ fn lang_at(p: &Preparation, offset: usize) -> Option<hypher::Lang> { let lang = p.config.lang.or_else(|| { let (_, item) = p.get(offset); let styles = item.text()?.styles; - Some(TextElem::lang_in(styles)) + Some(styles.get(TextElem::lang)) })?; let bytes = lang.as_str().as_bytes().try_into().ok()?; diff --git a/crates/typst-layout/src/inline/mod.rs b/crates/typst-layout/src/inline/mod.rs index 506fa5ea..06223ceb 100644 --- a/crates/typst-layout/src/inline/mod.rs +++ b/crates/typst-layout/src/inline/mod.rs @@ -14,7 +14,7 @@ pub use self::shaping::create_shape_plan; use comemo::{Track, Tracked, TrackedMut}; use typst_library::diag::SourceResult; use typst_library::engine::{Engine, Route, Sink, Traced}; -use typst_library::foundations::{Packed, Resolve, Smart, StyleChain}; +use typst_library::foundations::{Packed, Smart, StyleChain}; use typst_library::introspection::{Introspector, Locator, LocatorLink, SplitLocator}; use typst_library::layout::{Abs, AlignElem, Dir, FixedAlignment, Fragment, Size}; use typst_library::model::{ @@ -113,10 +113,10 @@ fn layout_par_impl( expand, Some(situation), &ConfigBase { - justify: elem.justify(styles), - linebreaks: elem.linebreaks(styles), - first_line_indent: elem.first_line_indent(styles), - hanging_indent: elem.hanging_indent(styles), + justify: elem.justify.get(styles), + linebreaks: elem.linebreaks.get(styles), + first_line_indent: elem.first_line_indent.get(styles), + hanging_indent: elem.hanging_indent.resolve(styles), }, ) } @@ -139,10 +139,10 @@ pub fn layout_inline<'a>( expand, None, &ConfigBase { - justify: ParElem::justify_in(shared), - linebreaks: ParElem::linebreaks_in(shared), - first_line_indent: ParElem::first_line_indent_in(shared), - hanging_indent: ParElem::hanging_indent_in(shared), + justify: shared.get(ParElem::justify), + linebreaks: shared.get(ParElem::linebreaks), + first_line_indent: shared.get(ParElem::first_line_indent), + hanging_indent: shared.resolve(ParElem::hanging_indent), }, ) } @@ -184,8 +184,8 @@ fn configuration( situation: Option<ParSituation>, ) -> Config { let justify = base.justify; - let font_size = TextElem::size_in(shared); - let dir = TextElem::dir_in(shared); + let font_size = shared.resolve(TextElem::size); + let dir = shared.resolve(TextElem::dir); Config { justify, @@ -207,7 +207,7 @@ fn configuration( Some(ParSituation::Other) => all, None => false, } - && AlignElem::alignment_in(shared).resolve(shared).x == dir.start().into() + && shared.resolve(AlignElem::alignment).x == dir.start().into() { amount.at(font_size) } else { @@ -219,26 +219,26 @@ fn configuration( } else { Abs::zero() }, - numbering_marker: ParLine::numbering_in(shared).map(|numbering| { + numbering_marker: shared.get_cloned(ParLine::numbering).map(|numbering| { Packed::new(ParLineMarker::new( numbering, - ParLine::number_align_in(shared), - ParLine::number_margin_in(shared), + shared.get(ParLine::number_align), + shared.get(ParLine::number_margin), // Delay resolving the number clearance until line numbers are // laid out to avoid inconsistent spacing depending on varying // font size. - ParLine::number_clearance_in(shared), + shared.get(ParLine::number_clearance), )) }), - align: AlignElem::alignment_in(shared).fix(dir).x, + align: shared.get(AlignElem::alignment).fix(dir).x, font_size, dir, - hyphenate: shared_get(children, shared, TextElem::hyphenate_in) + hyphenate: shared_get(children, shared, |s| s.get(TextElem::hyphenate)) .map(|uniform| uniform.unwrap_or(justify)), - lang: shared_get(children, shared, TextElem::lang_in), - fallback: TextElem::fallback_in(shared), - cjk_latin_spacing: TextElem::cjk_latin_spacing_in(shared).is_auto(), - costs: TextElem::costs_in(shared), + lang: shared_get(children, shared, |s| s.get(TextElem::lang)), + fallback: shared.get(TextElem::fallback), + cjk_latin_spacing: shared.get(TextElem::cjk_latin_spacing).is_auto(), + costs: shared.get(TextElem::costs), } } @@ -314,7 +314,7 @@ fn shared_get<T: PartialEq>( /// When we support some kind of more general ancestry mechanism, this can /// become more elegant. fn in_list(styles: StyleChain) -> bool { - ListElem::depth_in(styles).0 > 0 - || !EnumElem::parents_in(styles).is_empty() - || TermsElem::within_in(styles) + styles.get(ListElem::depth).0 > 0 + || !styles.get_cloned(EnumElem::parents).is_empty() + || styles.get(TermsElem::within) } diff --git a/crates/typst-layout/src/inline/shaping.rs b/crates/typst-layout/src/inline/shaping.rs index 48747cd5..d1e748da 100644 --- a/crates/typst-layout/src/inline/shaping.rs +++ b/crates/typst-layout/src/inline/shaping.rs @@ -223,12 +223,12 @@ impl<'a> ShapedText<'a> { let mut frame = Frame::soft(size); frame.set_baseline(top); - let size = TextElem::size_in(self.styles); - let shift = TextElem::baseline_in(self.styles); - let decos = TextElem::deco_in(self.styles); - let fill = TextElem::fill_in(self.styles); - let stroke = TextElem::stroke_in(self.styles); - let span_offset = TextElem::span_offset_in(self.styles); + let size = self.styles.resolve(TextElem::size); + let shift = self.styles.resolve(TextElem::baseline); + let decos = self.styles.get_cloned(TextElem::deco); + let fill = self.styles.get_ref(TextElem::fill); + let stroke = self.styles.resolve(TextElem::stroke); + let span_offset = self.styles.get(TextElem::span_offset); for ((font, y_offset, glyph_size), group) in self .glyphs @@ -340,9 +340,9 @@ impl<'a> ShapedText<'a> { let mut top = Abs::zero(); let mut bottom = Abs::zero(); - let size = TextElem::size_in(self.styles); - let top_edge = TextElem::top_edge_in(self.styles); - let bottom_edge = TextElem::bottom_edge_in(self.styles); + let size = self.styles.resolve(TextElem::size); + let top_edge = self.styles.get(TextElem::top_edge); + let bottom_edge = self.styles.get(TextElem::bottom_edge); // Expand top and bottom by reading the font's vertical metrics. let mut expand = |font: &Font, bounds: TextEdgeBounds| { @@ -486,7 +486,7 @@ impl<'a> ShapedText<'a> { // that subtracting either of the endpoints by self.base doesn't // underflow. See <https://github.com/typst/typst/issues/2283>. .unwrap_or_else(|| self.base..self.base); - let size = TextElem::size_in(self.styles); + let size = self.styles.resolve(TextElem::size); self.width += x_advance.at(size); let glyph = ShapedGlyph { font, @@ -603,9 +603,9 @@ pub fn shape_range<'a>( range: Range, styles: StyleChain<'a>, ) { - let script = TextElem::script_in(styles); - let lang = TextElem::lang_in(styles); - let region = TextElem::region_in(styles); + let script = styles.get(TextElem::script); + let lang = styles.get(TextElem::lang); + let region = styles.get(TextElem::region); let mut process = |range: Range, level: BidiLevel| { let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL }; let shaped = @@ -669,8 +669,8 @@ fn shape<'a>( lang: Lang, region: Option<Region>, ) -> ShapedText<'a> { - let size = TextElem::size_in(styles); - let shift_settings = TextElem::shift_settings_in(styles); + let size = styles.resolve(TextElem::size); + let shift_settings = styles.get(TextElem::shift_settings); let mut ctx = ShapingContext { engine, size, @@ -679,7 +679,7 @@ fn shape<'a>( styles, variant: variant(styles), features: features(styles), - fallback: TextElem::fallback_in(styles), + fallback: styles.get(TextElem::fallback), dir, shift_settings, }; @@ -783,7 +783,7 @@ fn shape_segment<'a>( let mut buffer = UnicodeBuffer::new(); buffer.push_str(text); buffer.set_language(language(ctx.styles)); - if let Some(script) = TextElem::script_in(ctx.styles).custom().and_then(|script| { + if let Some(script) = ctx.styles.get(TextElem::script).custom().and_then(|script| { rustybuzz::Script::from_iso15924_tag(Tag::from_bytes(script.as_bytes())) }) { buffer.set_script(script) @@ -1073,8 +1073,11 @@ fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, font: Font) { /// Apply tracking and spacing to the shaped glyphs. fn track_and_space(ctx: &mut ShapingContext) { - let tracking = Em::from_abs(TextElem::tracking_in(ctx.styles), ctx.size); - let spacing = TextElem::spacing_in(ctx.styles).map(|abs| Em::from_abs(abs, ctx.size)); + let tracking = Em::from_abs(ctx.styles.resolve(TextElem::tracking), ctx.size); + let spacing = ctx + .styles + .resolve(TextElem::spacing) + .map(|abs| Em::from_abs(abs, ctx.size)); let mut glyphs = ctx.glyphs.iter_mut().peekable(); while let Some(glyph) = glyphs.next() { diff --git a/crates/typst-layout/src/lists.rs b/crates/typst-layout/src/lists.rs index 974788a7..adb793fb 100644 --- a/crates/typst-layout/src/lists.rs +++ b/crates/typst-layout/src/lists.rs @@ -20,20 +20,21 @@ pub fn layout_list( styles: StyleChain, regions: Regions, ) -> SourceResult<Fragment> { - let indent = elem.indent(styles); - let body_indent = elem.body_indent(styles); - let tight = elem.tight(styles); - let gutter = elem.spacing(styles).unwrap_or_else(|| { + let indent = elem.indent.get(styles); + let body_indent = elem.body_indent.get(styles); + let tight = elem.tight.get(styles); + let gutter = elem.spacing.get(styles).unwrap_or_else(|| { if tight { - ParElem::leading_in(styles).into() + styles.get(ParElem::leading) } else { - ParElem::spacing_in(styles).into() + styles.get(ParElem::spacing) } }); - let Depth(depth) = ListElem::depth_in(styles); + let Depth(depth) = styles.get(ListElem::depth); let marker = elem - .marker(styles) + .marker + .get_ref(styles) .resolve(engine, styles, depth)? // avoid '#set align' interference with the list .aligned(HAlignment::Start + VAlignment::Top); @@ -52,7 +53,7 @@ pub fn layout_list( cells.push(Cell::new(marker.clone(), locator.next(&marker.span()))); cells.push(Cell::new(Content::empty(), locator.next(&()))); cells.push(Cell::new( - body.styled(ListElem::set_depth(Depth(1))), + body.set(ListElem::depth, Depth(1)), locator.next(&item.body.span()), )); } @@ -81,40 +82,40 @@ pub fn layout_enum( styles: StyleChain, regions: Regions, ) -> SourceResult<Fragment> { - let numbering = elem.numbering(styles); - let reversed = elem.reversed(styles); - let indent = elem.indent(styles); - let body_indent = elem.body_indent(styles); - let tight = elem.tight(styles); - let gutter = elem.spacing(styles).unwrap_or_else(|| { + let numbering = elem.numbering.get_ref(styles); + let reversed = elem.reversed.get(styles); + let indent = elem.indent.get(styles); + let body_indent = elem.body_indent.get(styles); + let tight = elem.tight.get(styles); + let gutter = elem.spacing.get(styles).unwrap_or_else(|| { if tight { - ParElem::leading_in(styles).into() + styles.get(ParElem::leading) } else { - ParElem::spacing_in(styles).into() + styles.get(ParElem::spacing) } }); let mut cells = vec![]; let mut locator = locator.split(); - let mut number = elem.start(styles).unwrap_or_else(|| { + let mut number = elem.start.get(styles).unwrap_or_else(|| { if reversed { elem.children.len() as u64 } else { 1 } }); - let mut parents = EnumElem::parents_in(styles); + let mut parents = styles.get_cloned(EnumElem::parents); - let full = elem.full(styles); + let full = elem.full.get(styles); // Horizontally align based on the given respective parameter. // Vertically align to the top to avoid inheriting `horizon` or `bottom` // alignment from the context and having the number be displaced in // relation to the item it refers to. - let number_align = elem.number_align(styles); + let number_align = elem.number_align.get(styles); for item in &elem.children { - number = item.number(styles).unwrap_or(number); + number = item.number.get(styles).unwrap_or(number); let context = Context::new(None, Some(styles)); let resolved = if full { @@ -133,8 +134,7 @@ pub fn layout_enum( // Disable overhang as a workaround to end-aligned dots glitching // and decreasing spacing between numbers and items. - let resolved = - resolved.aligned(number_align).styled(TextElem::set_overhang(false)); + let resolved = resolved.aligned(number_align).set(TextElem::overhang, false); // Text in wide enums shall always turn into paragraphs. let mut body = item.body.clone(); @@ -146,7 +146,7 @@ pub fn layout_enum( cells.push(Cell::new(resolved, locator.next(&()))); cells.push(Cell::new(Content::empty(), locator.next(&()))); cells.push(Cell::new( - body.styled(EnumElem::set_parents(smallvec![number])), + body.set(EnumElem::parents, smallvec![number]), locator.next(&item.body.span()), )); number = diff --git a/crates/typst-layout/src/math/accent.rs b/crates/typst-layout/src/math/accent.rs index 159703b8..e7f051ac 100644 --- a/crates/typst-layout/src/math/accent.rs +++ b/crates/typst-layout/src/math/accent.rs @@ -24,7 +24,7 @@ pub fn layout_accent( // Try to replace the base glyph with its dotless variant. let dtls = style_dtls(); let base_styles = - if top_accent && elem.dotless(styles) { styles.chain(&dtls) } else { styles }; + if top_accent && elem.dotless.get(styles) { styles.chain(&dtls) } else { styles }; let cramped = style_cramped(); let base = ctx.layout_into_fragment(&elem.base, base_styles.chain(&cramped))?; @@ -47,7 +47,7 @@ pub fn layout_accent( // Forcing the accent to be at least as large as the base makes it too wide // in many cases. - let width = elem.size(styles).relative_to(base.width()); + let width = elem.size.resolve(styles).relative_to(base.width()); let short_fall = ACCENT_SHORT_FALL.at(glyph.item.size); glyph.stretch_horizontal(ctx, width - short_fall); let accent_attach = glyph.accent_attach.0; diff --git a/crates/typst-layout/src/math/attach.rs b/crates/typst-layout/src/math/attach.rs index a7f3cad5..78b6f551 100644 --- a/crates/typst-layout/src/math/attach.rs +++ b/crates/typst-layout/src/math/attach.rs @@ -31,16 +31,16 @@ pub fn layout_attach( let mut base = ctx.layout_into_fragment(&elem.base, styles)?; let sup_style = style_for_superscript(styles); let sup_style_chain = styles.chain(&sup_style); - let tl = elem.tl(sup_style_chain); - let tr = elem.tr(sup_style_chain); + let tl = elem.tl.get_cloned(sup_style_chain); + let tr = elem.tr.get_cloned(sup_style_chain); let primed = tr.as_ref().is_some_and(|content| content.is::<PrimesElem>()); - let t = elem.t(sup_style_chain); + let t = elem.t.get_cloned(sup_style_chain); let sub_style = style_for_subscript(styles); let sub_style_chain = styles.chain(&sub_style); - let bl = elem.bl(sub_style_chain); - let br = elem.br(sub_style_chain); - let b = elem.b(sub_style_chain); + let bl = elem.bl.get_cloned(sub_style_chain); + let br = elem.br.get_cloned(sub_style_chain); + let b = elem.b.get_cloned(sub_style_chain); let limits = base.limits().active(styles); let (t, tr) = match (t, tr) { @@ -146,7 +146,7 @@ pub fn layout_limits( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - let limits = if elem.inline(styles) { Limits::Always } else { Limits::Display }; + let limits = if elem.inline.get(styles) { Limits::Always } else { Limits::Display }; let mut fragment = ctx.layout_into_fragment(&elem.body, styles)?; fragment.set_limits(limits); ctx.push(fragment); @@ -161,7 +161,8 @@ fn stretch_size(styles: StyleChain, elem: &Packed<AttachElem>) -> Option<Rel<Abs base = &equation.body; } - base.to_packed::<StretchElem>().map(|stretch| stretch.size(styles)) + base.to_packed::<StretchElem>() + .map(|stretch| stretch.size.resolve(styles)) } /// Lay out the attachments. @@ -397,7 +398,7 @@ fn compute_script_shifts( base: &MathFragment, [tl, tr, bl, br]: [&Option<MathFragment>; 4], ) -> (Abs, Abs) { - let sup_shift_up = if EquationElem::cramped_in(styles) { + let sup_shift_up = if styles.get(EquationElem::cramped) { scaled!(ctx, styles, superscript_shift_up_cramped) } else { scaled!(ctx, styles, superscript_shift_up) diff --git a/crates/typst-layout/src/math/cancel.rs b/crates/typst-layout/src/math/cancel.rs index 9826397f..57a32ca2 100644 --- a/crates/typst-layout/src/math/cancel.rs +++ b/crates/typst-layout/src/math/cancel.rs @@ -27,16 +27,16 @@ pub fn layout_cancel( let mut body = body.into_frame(); let body_size = body.size(); let span = elem.span(); - let length = elem.length(styles); + let length = elem.length.resolve(styles); - let stroke = elem.stroke(styles).unwrap_or(FixedStroke { - paint: TextElem::fill_in(styles).as_decoration(), + let stroke = elem.stroke.resolve(styles).unwrap_or(FixedStroke { + paint: styles.get_ref(TextElem::fill).as_decoration(), ..Default::default() }); - let invert = elem.inverted(styles); - let cross = elem.cross(styles); - let angle = elem.angle(styles); + let invert = elem.inverted.get(styles); + let cross = elem.cross.get(styles); + let angle = elem.angle.get_ref(styles); let invert_first_line = !cross && invert; let first_line = draw_cancel_line( @@ -44,7 +44,7 @@ pub fn layout_cancel( length, stroke.clone(), invert_first_line, - &angle, + angle, body_size, styles, span, @@ -57,7 +57,7 @@ pub fn layout_cancel( if cross { // Draw the second line. let second_line = - draw_cancel_line(ctx, length, stroke, true, &angle, body_size, styles, span)?; + draw_cancel_line(ctx, length, stroke, true, angle, body_size, styles, span)?; body.push_frame(center, second_line); } diff --git a/crates/typst-layout/src/math/frac.rs b/crates/typst-layout/src/math/frac.rs index 091f328f..12a2c6fd 100644 --- a/crates/typst-layout/src/math/frac.rs +++ b/crates/typst-layout/src/math/frac.rs @@ -124,7 +124,7 @@ fn layout_frac_like( FrameItem::Shape( Geometry::Line(Point::with_x(line_width)).stroked( FixedStroke::from_pair( - TextElem::fill_in(styles).as_decoration(), + styles.get_ref(TextElem::fill).as_decoration(), thickness, ), ), diff --git a/crates/typst-layout/src/math/fragment.rs b/crates/typst-layout/src/math/fragment.rs index c5891f7d..758dd401 100644 --- a/crates/typst-layout/src/math/fragment.rs +++ b/crates/typst-layout/src/math/fragment.rs @@ -315,7 +315,8 @@ impl GlyphFragment { let cluster = info.cluster as usize; let c = text[cluster..].chars().next().unwrap(); let limits = Limits::for_char(c); - let class = EquationElem::class_in(styles) + let class = styles + .get(EquationElem::class) .or_else(|| default_math_class(c)) .unwrap_or(MathClass::Normal); @@ -331,11 +332,11 @@ impl GlyphFragment { let item = TextItem { font: font.clone(), - size: TextElem::size_in(styles), - fill: TextElem::fill_in(styles).as_decoration(), - stroke: TextElem::stroke_in(styles).map(|s| s.unwrap_or_default()), - lang: TextElem::lang_in(styles), - region: TextElem::region_in(styles), + size: styles.resolve(TextElem::size), + fill: styles.get_ref(TextElem::fill).as_decoration(), + stroke: styles.resolve(TextElem::stroke).map(|s| s.unwrap_or_default()), + lang: styles.get(TextElem::lang), + region: styles.get(TextElem::region), text: text.into(), glyphs: vec![glyph.clone()], }; @@ -344,7 +345,7 @@ impl GlyphFragment { item, base_glyph: glyph, // Math - math_size: EquationElem::size_in(styles), + math_size: styles.get(EquationElem::size), class, limits, mid_stretched: None, @@ -356,7 +357,7 @@ impl GlyphFragment { baseline: None, // Misc align: Abs::zero(), - shift: TextElem::baseline_in(styles), + shift: styles.resolve(TextElem::baseline), modifiers: FrameModifiers::get_in(styles), }; fragment.update_glyph(); @@ -541,9 +542,9 @@ impl FrameFragment { let accent_attach = frame.width() / 2.0; Self { frame: frame.modified(&FrameModifiers::get_in(styles)), - font_size: TextElem::size_in(styles), - class: EquationElem::class_in(styles).unwrap_or(MathClass::Normal), - math_size: EquationElem::size_in(styles), + font_size: styles.resolve(TextElem::size), + class: styles.get(EquationElem::class).unwrap_or(MathClass::Normal), + math_size: styles.get(EquationElem::size), limits: Limits::Never, spaced: false, base_ascent, @@ -864,7 +865,7 @@ impl Limits { pub fn active(&self, styles: StyleChain) -> bool { match self { Self::Always => true, - Self::Display => EquationElem::size_in(styles) == MathSize::Display, + Self::Display => styles.get(EquationElem::size) == MathSize::Display, Self::Never => false, } } diff --git a/crates/typst-layout/src/math/lr.rs b/crates/typst-layout/src/math/lr.rs index a3b5cb05..2348025e 100644 --- a/crates/typst-layout/src/math/lr.rs +++ b/crates/typst-layout/src/math/lr.rs @@ -22,7 +22,7 @@ pub fn layout_lr( // Extract implicit LrElem. if let Some(lr) = body.to_packed::<LrElem>() { - if lr.size(styles).is_one() { + if lr.size.get(styles).is_one() { body = &lr.body; } } @@ -41,7 +41,7 @@ pub fn layout_lr( .unwrap_or_default(); let relative_to = 2.0 * max_extent; - let height = elem.size(styles); + let height = elem.size.resolve(styles); // Scale up fragments at both ends. match inner_fragments { diff --git a/crates/typst-layout/src/math/mat.rs b/crates/typst-layout/src/math/mat.rs index 278b1343..4a897a03 100644 --- a/crates/typst-layout/src/math/mat.rs +++ b/crates/typst-layout/src/math/mat.rs @@ -30,15 +30,15 @@ pub fn layout_vec( ctx, styles, &[column], - elem.align(styles), + elem.align.resolve(styles), LeftRightAlternator::Right, None, - Axes::with_y(elem.gap(styles)), + Axes::with_y(elem.gap.resolve(styles)), span, "elements", )?; - let delim = elem.delim(styles); + let delim = elem.delim.get(styles); layout_delimiters(ctx, styles, frame, delim.open(), delim.close(), span) } @@ -59,14 +59,17 @@ pub fn layout_cases( FixedAlignment::Start, LeftRightAlternator::None, None, - Axes::with_y(elem.gap(styles)), + Axes::with_y(elem.gap.resolve(styles)), span, "branches", )?; - let delim = elem.delim(styles); - let (open, close) = - if elem.reverse(styles) { (None, delim.close()) } else { (delim.open(), None) }; + let delim = elem.delim.get(styles); + let (open, close) = if elem.reverse.get(styles) { + (None, delim.close()) + } else { + (delim.open(), None) + }; layout_delimiters(ctx, styles, frame, open, close, span) } @@ -81,7 +84,7 @@ pub fn layout_mat( let rows = &elem.rows; let ncols = rows.first().map_or(0, |row| row.len()); - let augment = elem.augment(styles); + let augment = elem.augment.resolve(styles); if let Some(aug) = &augment { for &offset in &aug.hline.0 { if offset == 0 || offset.unsigned_abs() >= rows.len() { @@ -116,15 +119,15 @@ pub fn layout_mat( ctx, styles, &columns, - elem.align(styles), + elem.align.resolve(styles), LeftRightAlternator::Right, augment, - Axes::new(elem.column_gap(styles), elem.row_gap(styles)), + Axes::new(elem.column_gap.resolve(styles), elem.row_gap.resolve(styles)), span, "cells", )?; - let delim = elem.delim(styles); + let delim = elem.delim.get(styles); layout_delimiters(ctx, styles, frame, delim.open(), delim.close(), span) } @@ -157,7 +160,7 @@ fn layout_body( let default_stroke_thickness = DEFAULT_STROKE_THICKNESS.resolve(styles); let default_stroke = FixedStroke { thickness: default_stroke_thickness, - paint: TextElem::fill_in(styles).as_decoration(), + paint: styles.get_ref(TextElem::fill).as_decoration(), cap: LineCap::Square, ..Default::default() }; diff --git a/crates/typst-layout/src/math/mod.rs b/crates/typst-layout/src/math/mod.rs index 5fd22e57..39083506 100644 --- a/crates/typst-layout/src/math/mod.rs +++ b/crates/typst-layout/src/math/mod.rs @@ -51,7 +51,7 @@ pub fn layout_equation_inline( styles: StyleChain, region: Size, ) -> SourceResult<Vec<InlineItem>> { - assert!(!elem.block(styles)); + assert!(!elem.block.get(styles)); let font = find_math_font(engine, styles, elem.span())?; @@ -78,12 +78,12 @@ pub fn layout_equation_inline( for item in &mut items { let InlineItem::Frame(frame) = item else { continue }; - let slack = ParElem::leading_in(styles) * 0.7; + let slack = styles.resolve(ParElem::leading) * 0.7; let (t, b) = font.edges( - TextElem::top_edge_in(styles), - TextElem::bottom_edge_in(styles), - TextElem::size_in(styles), + styles.get(TextElem::top_edge), + styles.get(TextElem::bottom_edge), + styles.resolve(TextElem::size), TextEdgeBounds::Frame(frame), ); @@ -105,7 +105,7 @@ pub fn layout_equation_block( styles: StyleChain, regions: Regions, ) -> SourceResult<Fragment> { - assert!(elem.block(styles)); + assert!(elem.block.get(styles)); let span = elem.span(); let font = find_math_font(engine, styles, span)?; @@ -121,7 +121,7 @@ pub fn layout_equation_block( .multiline_frame_builder(styles); let width = full_equation_builder.size.x; - let equation_builders = if BlockElem::breakable_in(styles) { + let equation_builders = if styles.get(BlockElem::breakable) { let mut rows = full_equation_builder.frames.into_iter().peekable(); let mut equation_builders = vec![]; let mut last_first_pos = Point::zero(); @@ -188,7 +188,7 @@ pub fn layout_equation_block( vec![full_equation_builder] }; - let Some(numbering) = (**elem).numbering(styles) else { + let Some(numbering) = elem.numbering.get_ref(styles) else { let frames = equation_builders .into_iter() .map(MathRunFrameBuilder::build) @@ -197,7 +197,7 @@ pub fn layout_equation_block( }; let pod = Region::new(regions.base(), Axes::splat(false)); - let counter = Counter::of(EquationElem::elem()) + let counter = Counter::of(EquationElem::ELEM) .display_at_loc(engine, elem.location().unwrap(), styles, numbering)? .spanned(span); let number = crate::layout_frame(engine, &counter, locator.next(&()), styles, pod)?; @@ -205,7 +205,7 @@ pub fn layout_equation_block( static NUMBER_GUTTER: Em = Em::new(0.5); let full_number_width = number.width() + NUMBER_GUTTER.resolve(styles); - let number_align = match elem.number_align(styles) { + let number_align = match elem.number_align.get(styles) { SpecificAlignment::H(h) => SpecificAlignment::Both(h, VAlignment::Horizon), SpecificAlignment::V(v) => SpecificAlignment::Both(OuterHAlignment::End, v), SpecificAlignment::Both(h, v) => SpecificAlignment::Both(h, v), @@ -224,7 +224,7 @@ pub fn layout_equation_block( builder, number.clone(), number_align.resolve(styles), - AlignElem::alignment_in(styles).resolve(styles).x, + styles.get(AlignElem::alignment).resolve(styles).x, regions.size.x, full_number_width, ) @@ -472,7 +472,9 @@ impl<'a, 'v, 'e> MathContext<'a, 'v, 'e> { let outer = styles; for (elem, styles) in pairs { // Hack because the font is fixed in math. - if styles != outer && TextElem::font_in(styles) != TextElem::font_in(outer) { + if styles != outer + && styles.get_ref(TextElem::font) != outer.get_ref(TextElem::font) + { let frame = layout_external(elem, self, styles)?; self.push(FrameFragment::new(styles, frame).with_spaced(true)); continue; @@ -603,7 +605,10 @@ fn layout_h( ) -> SourceResult<()> { if let Spacing::Rel(rel) = elem.amount { if rel.rel.is_zero() { - ctx.push(MathFragment::Spacing(rel.abs.resolve(styles), elem.weak(styles))); + ctx.push(MathFragment::Spacing( + rel.abs.resolve(styles), + elem.weak.get(styles), + )); } } Ok(()) @@ -616,7 +621,7 @@ fn layout_class( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - let style = EquationElem::set_class(Some(elem.class)).wrap(); + let style = EquationElem::class.set(Some(elem.class)).wrap(); let mut fragment = ctx.layout_into_fragment(&elem.body, styles.chain(&style))?; fragment.set_class(elem.class); fragment.set_limits(Limits::for_class(elem.class)); @@ -642,7 +647,7 @@ fn layout_op( .with_italics_correction(italics) .with_accent_attach(accent_attach) .with_text_like(text_like) - .with_limits(if elem.limits(styles) { + .with_limits(if elem.limits.get(styles) { Limits::Display } else { Limits::Never diff --git a/crates/typst-layout/src/math/root.rs b/crates/typst-layout/src/math/root.rs index 91b9b16a..30948e08 100644 --- a/crates/typst-layout/src/math/root.rs +++ b/crates/typst-layout/src/math/root.rs @@ -17,7 +17,7 @@ pub fn layout_root( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - let index = elem.index(styles); + let index = elem.index.get_ref(styles); let span = elem.span(); let gap = scaled!( @@ -54,7 +54,7 @@ pub fn layout_root( let sqrt = sqrt.into_frame(); // Layout the index. - let sscript = EquationElem::set_size(MathSize::ScriptScript).wrap(); + let sscript = EquationElem::size.set(MathSize::ScriptScript).wrap(); let index = index .as_ref() .map(|elem| ctx.layout_into_frame(elem, styles.chain(&sscript))) @@ -112,7 +112,7 @@ pub fn layout_root( FrameItem::Shape( Geometry::Line(Point::with_x(radicand.width())).stroked( FixedStroke::from_pair( - TextElem::fill_in(styles).as_decoration(), + styles.get_ref(TextElem::fill).as_decoration(), thickness, ), ), diff --git a/crates/typst-layout/src/math/run.rs b/crates/typst-layout/src/math/run.rs index 4ec76c25..161fa106 100644 --- a/crates/typst-layout/src/math/run.rs +++ b/crates/typst-layout/src/math/run.rs @@ -194,13 +194,13 @@ impl MathRun { let row_count = rows.len(); let alignments = alignments(&rows); - let leading = if EquationElem::size_in(styles) >= MathSize::Text { - ParElem::leading_in(styles) + let leading = if styles.get(EquationElem::size) >= MathSize::Text { + styles.resolve(ParElem::leading) } else { TIGHT_LEADING.resolve(styles) }; - let align = AlignElem::alignment_in(styles).resolve(styles).x; + let align = styles.resolve(AlignElem::alignment).x; let mut frames: Vec<(Frame, Point)> = vec![]; let mut size = Size::zero(); for (i, row) in rows.into_iter().enumerate() { diff --git a/crates/typst-layout/src/math/shared.rs b/crates/typst-layout/src/math/shared.rs index 1f88d2dd..c9d20aa6 100644 --- a/crates/typst-layout/src/math/shared.rs +++ b/crates/typst-layout/src/math/shared.rs @@ -10,7 +10,7 @@ use super::{LeftRightAlternator, MathContext, MathFragment, MathRun}; macro_rules! scaled { ($ctx:expr, $styles:expr, text: $text:ident, display: $display:ident $(,)?) => { - match typst_library::math::EquationElem::size_in($styles) { + match $styles.get(typst_library::math::EquationElem::size) { typst_library::math::MathSize::Display => scaled!($ctx, $styles, $display), _ => scaled!($ctx, $styles, $text), } @@ -19,7 +19,7 @@ macro_rules! scaled { $crate::math::Scaled::scaled( $ctx.constants.$name(), $ctx, - typst_library::text::TextElem::size_in($styles), + $styles.resolve(typst_library::text::TextElem::size), ) }; } @@ -58,55 +58,62 @@ impl Scaled for MathValue<'_> { /// Styles something as cramped. pub fn style_cramped() -> LazyHash<Style> { - EquationElem::set_cramped(true).wrap() + EquationElem::cramped.set(true).wrap() } /// Sets flac OpenType feature. pub fn style_flac() -> LazyHash<Style> { - TextElem::set_features(FontFeatures(vec![(Tag::from_bytes(b"flac"), 1)])).wrap() + TextElem::features + .set(FontFeatures(vec![(Tag::from_bytes(b"flac"), 1)])) + .wrap() } /// Sets dtls OpenType feature. pub fn style_dtls() -> LazyHash<Style> { - TextElem::set_features(FontFeatures(vec![(Tag::from_bytes(b"dtls"), 1)])).wrap() + TextElem::features + .set(FontFeatures(vec![(Tag::from_bytes(b"dtls"), 1)])) + .wrap() } /// The style for subscripts in the current style. pub fn style_for_subscript(styles: StyleChain) -> [LazyHash<Style>; 2] { - [style_for_superscript(styles), EquationElem::set_cramped(true).wrap()] + [style_for_superscript(styles), EquationElem::cramped.set(true).wrap()] } /// The style for superscripts in the current style. pub fn style_for_superscript(styles: StyleChain) -> LazyHash<Style> { - EquationElem::set_size(match EquationElem::size_in(styles) { - MathSize::Display | MathSize::Text => MathSize::Script, - MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript, - }) - .wrap() + EquationElem::size + .set(match styles.get(EquationElem::size) { + MathSize::Display | MathSize::Text => MathSize::Script, + MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript, + }) + .wrap() } /// The style for numerators in the current style. pub fn style_for_numerator(styles: StyleChain) -> LazyHash<Style> { - EquationElem::set_size(match EquationElem::size_in(styles) { - MathSize::Display => MathSize::Text, - MathSize::Text => MathSize::Script, - MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript, - }) - .wrap() + EquationElem::size + .set(match styles.get(EquationElem::size) { + MathSize::Display => MathSize::Text, + MathSize::Text => MathSize::Script, + MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript, + }) + .wrap() } /// The style for denominators in the current style. pub fn style_for_denominator(styles: StyleChain) -> [LazyHash<Style>; 2] { - [style_for_numerator(styles), EquationElem::set_cramped(true).wrap()] + [style_for_numerator(styles), EquationElem::cramped.set(true).wrap()] } /// Styles to add font constants to the style chain. pub fn style_for_script_scale(ctx: &MathContext) -> LazyHash<Style> { - EquationElem::set_script_scale(( - ctx.constants.script_percent_scale_down(), - ctx.constants.script_script_percent_scale_down(), - )) - .wrap() + EquationElem::script_scale + .set(( + ctx.constants.script_percent_scale_down(), + ctx.constants.script_script_percent_scale_down(), + )) + .wrap() } /// Stack rows on top of each other. diff --git a/crates/typst-layout/src/math/stretch.rs b/crates/typst-layout/src/math/stretch.rs index d4370e7b..f1a22a81 100644 --- a/crates/typst-layout/src/math/stretch.rs +++ b/crates/typst-layout/src/math/stretch.rs @@ -14,7 +14,14 @@ pub fn layout_stretch( styles: StyleChain, ) -> SourceResult<()> { let mut fragment = ctx.layout_into_fragment(&elem.body, styles)?; - stretch_fragment(ctx, &mut fragment, None, None, elem.size(styles), Abs::zero()); + stretch_fragment( + ctx, + &mut fragment, + None, + None, + elem.size.resolve(styles), + Abs::zero(), + ); ctx.push(fragment); Ok(()) } diff --git a/crates/typst-layout/src/math/text.rs b/crates/typst-layout/src/math/text.rs index 67dc0a2c..53f88f2b 100644 --- a/crates/typst-layout/src/math/text.rs +++ b/crates/typst-layout/src/math/text.rs @@ -77,8 +77,8 @@ fn layout_inline_text( Ok(FrameFragment::new(styles, frame).with_text_like(true)) } else { let local = [ - TextElem::set_top_edge(TopEdge::Metric(TopEdgeMetric::Bounds)), - TextElem::set_bottom_edge(BottomEdge::Metric(BottomEdgeMetric::Bounds)), + TextElem::top_edge.set(TopEdge::Metric(TopEdgeMetric::Bounds)), + TextElem::bottom_edge.set(BottomEdge::Metric(BottomEdgeMetric::Bounds)), ] .map(|p| p.wrap()); @@ -150,7 +150,7 @@ fn adjust_glyph_layout( styles: StyleChain, ) { if glyph.class == MathClass::Large { - if EquationElem::size_in(styles) == MathSize::Display { + if styles.get(EquationElem::size) == MathSize::Display { let height = scaled!(ctx, styles, display_operator_min_height) .max(SQRT_2 * glyph.size.y); glyph.stretch_vertical(ctx, height); @@ -169,9 +169,9 @@ fn adjust_glyph_layout( fn styled_char(styles: StyleChain, c: char, auto_italic: bool) -> char { use MathVariant::*; - let variant = EquationElem::variant_in(styles); - let bold = EquationElem::bold_in(styles); - let italic = EquationElem::italic_in(styles).unwrap_or( + let variant = styles.get(EquationElem::variant); + let bold = styles.get(EquationElem::bold); + let italic = styles.get(EquationElem::italic).unwrap_or( auto_italic && matches!( c, diff --git a/crates/typst-layout/src/math/underover.rs b/crates/typst-layout/src/math/underover.rs index c29d9947..1e1aeb41 100644 --- a/crates/typst-layout/src/math/underover.rs +++ b/crates/typst-layout/src/math/underover.rs @@ -56,7 +56,7 @@ pub fn layout_underbrace( ctx, styles, &elem.body, - &elem.annotation(styles), + elem.annotation.get_ref(styles), '⏟', BRACE_GAP, Position::Under, @@ -75,7 +75,7 @@ pub fn layout_overbrace( ctx, styles, &elem.body, - &elem.annotation(styles), + elem.annotation.get_ref(styles), '⏞', BRACE_GAP, Position::Over, @@ -94,7 +94,7 @@ pub fn layout_underbracket( ctx, styles, &elem.body, - &elem.annotation(styles), + elem.annotation.get_ref(styles), '⎵', BRACKET_GAP, Position::Under, @@ -113,7 +113,7 @@ pub fn layout_overbracket( ctx, styles, &elem.body, - &elem.annotation(styles), + elem.annotation.get_ref(styles), '⎴', BRACKET_GAP, Position::Over, @@ -132,7 +132,7 @@ pub fn layout_underparen( ctx, styles, &elem.body, - &elem.annotation(styles), + elem.annotation.get_ref(styles), '⏝', PAREN_GAP, Position::Under, @@ -151,7 +151,7 @@ pub fn layout_overparen( ctx, styles, &elem.body, - &elem.annotation(styles), + elem.annotation.get_ref(styles), '⏜', PAREN_GAP, Position::Over, @@ -170,7 +170,7 @@ pub fn layout_undershell( ctx, styles, &elem.body, - &elem.annotation(styles), + elem.annotation.get_ref(styles), '⏡', SHELL_GAP, Position::Under, @@ -189,7 +189,7 @@ pub fn layout_overshell( ctx, styles, &elem.body, - &elem.annotation(styles), + elem.annotation.get_ref(styles), '⏠', SHELL_GAP, Position::Over, @@ -251,7 +251,7 @@ fn layout_underoverline( line_pos, FrameItem::Shape( Geometry::Line(Point::with_x(line_width)).stroked(FixedStroke { - paint: TextElem::fill_in(styles).as_decoration(), + paint: styles.get_ref(TextElem::fill).as_decoration(), thickness: bar_height, ..FixedStroke::default() }), diff --git a/crates/typst-layout/src/modifiers.rs b/crates/typst-layout/src/modifiers.rs index b0371d63..57d70302 100644 --- a/crates/typst-layout/src/modifiers.rs +++ b/crates/typst-layout/src/modifiers.rs @@ -29,8 +29,8 @@ impl FrameModifiers { /// Retrieve all modifications that should be applied per-frame. pub fn get_in(styles: StyleChain) -> Self { Self { - dest: LinkElem::current_in(styles), - hidden: HideElem::hidden_in(styles), + dest: styles.get_cloned(LinkElem::current), + hidden: styles.get(HideElem::hidden), } } } @@ -83,7 +83,7 @@ pub trait FrameModifyText { impl FrameModifyText for Frame { fn modify_text(&mut self, styles: StyleChain) { let modifiers = FrameModifiers::get_in(styles); - let expand_y = 0.5 * ParElem::leading_in(styles); + let expand_y = 0.5 * styles.resolve(ParElem::leading); let outset = Sides::new(Abs::zero(), expand_y, Abs::zero(), expand_y); modify_frame(self, &modifiers, Some(outset)); } @@ -130,7 +130,7 @@ where let outer = styles; let mut styles = styles; if modifiers.dest.is_some() { - reset = LinkElem::set_current(None).wrap(); + reset = LinkElem::current.set(None).wrap(); styles = outer.chain(&reset); } diff --git a/crates/typst-layout/src/pad.rs b/crates/typst-layout/src/pad.rs index 00badcdb..b69e0de4 100644 --- a/crates/typst-layout/src/pad.rs +++ b/crates/typst-layout/src/pad.rs @@ -1,6 +1,6 @@ use typst_library::diag::SourceResult; use typst_library::engine::Engine; -use typst_library::foundations::{Packed, Resolve, StyleChain}; +use typst_library::foundations::{Packed, StyleChain}; use typst_library::introspection::Locator; use typst_library::layout::{ Abs, Fragment, Frame, PadElem, Point, Regions, Rel, Sides, Size, @@ -16,10 +16,10 @@ pub fn layout_pad( regions: Regions, ) -> SourceResult<Fragment> { let padding = Sides::new( - elem.left(styles).resolve(styles), - elem.top(styles).resolve(styles), - elem.right(styles).resolve(styles), - elem.bottom(styles).resolve(styles), + elem.left.resolve(styles), + elem.top.resolve(styles), + elem.right.resolve(styles), + elem.bottom.resolve(styles), ); let mut backlog = vec![]; diff --git a/crates/typst-layout/src/pages/collect.rs b/crates/typst-layout/src/pages/collect.rs index 8eab18a6..64167d08 100644 --- a/crates/typst-layout/src/pages/collect.rs +++ b/crates/typst-layout/src/pages/collect.rs @@ -39,14 +39,14 @@ pub fn collect<'a>( if let Some(pagebreak) = elem.to_packed::<PagebreakElem>() { // Add a blank page if we encounter a strong pagebreak and there was // a staged empty page. - let strong = !pagebreak.weak(styles); + let strong = !pagebreak.weak.get(styles); if strong && staged_empty_page { let locator = locator.next(&elem.span()); items.push(Item::Run(&[], initial, locator)); } // Add an instruction to adjust the page parity if requested. - if let Some(parity) = pagebreak.to(styles) { + if let Some(parity) = pagebreak.to.get(styles) { let locator = locator.next(&elem.span()); items.push(Item::Parity(parity, styles, locator)); } @@ -56,7 +56,7 @@ pub fn collect<'a>( // the scope of a page set rule to ensure a page boundary. Its // styles correspond to the styles _before_ the page set rule, so we // don't want to apply it to a potential empty page. - if !pagebreak.boundary(styles) { + if !pagebreak.boundary.get(styles) { initial = styles; } @@ -94,7 +94,7 @@ pub fn collect<'a>( if group.iter().all(|(c, _)| c.is::<TagElem>()) && !(staged_empty_page && children.iter().all(|&(c, s)| { - c.to_packed::<PagebreakElem>().is_some_and(|c| c.boundary(s)) + c.to_packed::<PagebreakElem>().is_some_and(|c| c.boundary.get(s)) })) { items.push(Item::Tags(group)); diff --git a/crates/typst-layout/src/pages/run.rs b/crates/typst-layout/src/pages/run.rs index 6d2d29da..8c1a34ed 100644 --- a/crates/typst-layout/src/pages/run.rs +++ b/crates/typst-layout/src/pages/run.rs @@ -101,10 +101,10 @@ fn layout_page_run_impl( // When one of the lengths is infinite the page fits its content along // that axis. - let width = PageElem::width_in(styles).unwrap_or(Abs::inf()); - let height = PageElem::height_in(styles).unwrap_or(Abs::inf()); + let width = styles.resolve(PageElem::width).unwrap_or(Abs::inf()); + let height = styles.resolve(PageElem::height).unwrap_or(Abs::inf()); let mut size = Size::new(width, height); - if PageElem::flipped_in(styles) { + if styles.get(PageElem::flipped) { std::mem::swap(&mut size.x, &mut size.y); } @@ -115,7 +115,7 @@ fn layout_page_run_impl( // Determine the margins. let default = Rel::<Length>::from((2.5 / 21.0) * min); - let margin = PageElem::margin_in(styles); + let margin = styles.get(PageElem::margin); let two_sided = margin.two_sided.unwrap_or(false); let margin = margin .sides @@ -123,22 +123,24 @@ fn layout_page_run_impl( .resolve(styles) .relative_to(size); - let fill = PageElem::fill_in(styles); - let foreground = PageElem::foreground_in(styles); - let background = PageElem::background_in(styles); - let header_ascent = PageElem::header_ascent_in(styles).relative_to(margin.top); - let footer_descent = PageElem::footer_descent_in(styles).relative_to(margin.bottom); - let numbering = PageElem::numbering_in(styles); - let supplement = match PageElem::supplement_in(styles) { + let fill = styles.get_cloned(PageElem::fill); + let foreground = styles.get_ref(PageElem::foreground); + let background = styles.get_ref(PageElem::background); + let header_ascent = styles.resolve(PageElem::header_ascent).relative_to(margin.top); + let footer_descent = + styles.resolve(PageElem::footer_descent).relative_to(margin.bottom); + let numbering = styles.get_ref(PageElem::numbering); + let supplement = match styles.get_cloned(PageElem::supplement) { Smart::Auto => TextElem::packed(PageElem::local_name_in(styles)), Smart::Custom(content) => content.unwrap_or_default(), }; - let number_align = PageElem::number_align_in(styles); - let binding = - PageElem::binding_in(styles).unwrap_or_else(|| match TextElem::dir_in(styles) { + let number_align = styles.get(PageElem::number_align); + let binding = styles.get(PageElem::binding).unwrap_or_else(|| { + match styles.resolve(TextElem::dir) { Dir::LTR => Binding::Left, _ => Binding::Right, - }); + } + }); // Construct the numbering (for header or footer). let numbering_marginal = numbering.as_ref().map(|numbering| { @@ -163,8 +165,8 @@ fn layout_page_run_impl( counter }); - let header = PageElem::header_in(styles); - let footer = PageElem::footer_in(styles); + let header = styles.get_ref(PageElem::header); + let footer = styles.get_ref(PageElem::footer); let (header, footer) = if matches!(number_align.y(), Some(OuterVAlignment::Top)) { (header.as_ref().unwrap_or(&numbering_marginal), footer.as_ref().unwrap_or(&None)) } else { @@ -179,15 +181,15 @@ fn layout_page_run_impl( &mut locator, styles, Regions::repeat(area, area.map(Abs::is_finite)), - PageElem::columns_in(styles), - ColumnsElem::gutter_in(styles), + styles.get(PageElem::columns), + styles.get(ColumnsElem::gutter).resolve(styles), FlowMode::Root, )?; // Layouts a single marginal. let mut layout_marginal = |content: &Option<Content>, area, align| { let Some(content) = content else { return Ok(None) }; - let aligned = content.clone().styled(AlignElem::set_alignment(align)); + let aligned = content.clone().set(AlignElem::alignment, align); crate::layout_frame( &mut engine, &aligned, diff --git a/crates/typst-layout/src/repeat.rs b/crates/typst-layout/src/repeat.rs index bfc7b32c..40179c34 100644 --- a/crates/typst-layout/src/repeat.rs +++ b/crates/typst-layout/src/repeat.rs @@ -29,7 +29,7 @@ pub fn layout_repeat( frame.set_baseline(piece.baseline()); } - let mut gap = elem.gap(styles).resolve(styles); + let mut gap = elem.gap.resolve(styles); let fill = region.size.x; let width = piece.width(); @@ -47,12 +47,12 @@ pub fn layout_repeat( let count = ((fill + gap) / (width + gap)).floor(); let remaining = (fill + gap) % (width + gap); - let justify = elem.justify(styles); + let justify = elem.justify.get(styles); if justify { gap += remaining / (count - 1.0); } - let align = AlignElem::alignment_in(styles).resolve(styles); + let align = styles.get(AlignElem::alignment).resolve(styles); let mut offset = Abs::zero(); if count == 1.0 || !justify { offset += align.x.position(remaining); diff --git a/crates/typst-layout/src/shapes.rs b/crates/typst-layout/src/shapes.rs index 0616b4ce..1a9b38f2 100644 --- a/crates/typst-layout/src/shapes.rs +++ b/crates/typst-layout/src/shapes.rs @@ -27,16 +27,20 @@ pub fn layout_line( region: Region, ) -> SourceResult<Frame> { let resolve = |axes: Axes<Rel<Abs>>| axes.zip_map(region.size, Rel::relative_to); - let start = resolve(elem.start(styles)); - let delta = elem.end(styles).map(|end| resolve(end) - start).unwrap_or_else(|| { - let length = elem.length(styles); - let angle = elem.angle(styles); - let x = angle.cos() * length; - let y = angle.sin() * length; - resolve(Axes::new(x, y)) - }); - - let stroke = elem.stroke(styles).unwrap_or_default(); + let start = resolve(elem.start.resolve(styles)); + let delta = elem + .end + .resolve(styles) + .map(|end| resolve(end) - start) + .unwrap_or_else(|| { + let length = elem.length.resolve(styles); + let angle = elem.angle.get(styles); + let x = angle.cos() * length; + let y = angle.sin() * length; + resolve(Axes::new(x, y)) + }); + + let stroke = elem.stroke.resolve(styles).unwrap_or_default(); let size = start.max(start + delta).max(Size::zero()); if !size.is_finite() { @@ -105,7 +109,7 @@ pub fn layout_path( add_cubic(from_point, to_point, from, to); } - if elem.closed(styles) { + if elem.closed.get(styles) { let from = *vertices.last().unwrap(); // We checked that we have at least one element. let to = vertices[0]; let from_point = *points.last().unwrap(); @@ -120,9 +124,9 @@ pub fn layout_path( } // Prepare fill and stroke. - let fill = elem.fill(styles); - let fill_rule = elem.fill_rule(styles); - let stroke = match elem.stroke(styles) { + let fill = elem.fill.get_cloned(styles); + let fill_rule = elem.fill_rule.get(styles); + let stroke = match elem.stroke.resolve(styles) { Smart::Auto if fill.is_none() => Some(FixedStroke::default()), Smart::Auto => None, Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default), @@ -153,19 +157,19 @@ pub fn layout_curve( for item in &elem.components { match item { CurveComponent::Move(element) => { - let relative = element.relative(styles); + let relative = element.relative.get(styles); let point = builder.resolve_point(element.start, relative); builder.move_(point); } CurveComponent::Line(element) => { - let relative = element.relative(styles); + let relative = element.relative.get(styles); let point = builder.resolve_point(element.end, relative); builder.line(point); } CurveComponent::Quad(element) => { - let relative = element.relative(styles); + let relative = element.relative.get(styles); let end = builder.resolve_point(element.end, relative); let control = match element.control { Smart::Auto => { @@ -178,7 +182,7 @@ pub fn layout_curve( } CurveComponent::Cubic(element) => { - let relative = element.relative(styles); + let relative = element.relative.get(styles); let end = builder.resolve_point(element.end, relative); let c1 = match element.control_start { Some(Smart::Custom(p)) => builder.resolve_point(p, relative), @@ -193,7 +197,7 @@ pub fn layout_curve( } CurveComponent::Close(element) => { - builder.close(element.mode(styles)); + builder.close(element.mode.get(styles)); } } } @@ -208,9 +212,9 @@ pub fn layout_curve( } // Prepare fill and stroke. - let fill = elem.fill(styles); - let fill_rule = elem.fill_rule(styles); - let stroke = match elem.stroke(styles) { + let fill = elem.fill.get_cloned(styles); + let fill_rule = elem.fill_rule.get(styles); + let stroke = match elem.stroke.resolve(styles) { Smart::Auto if fill.is_none() => Some(FixedStroke::default()), Smart::Auto => None, Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default), @@ -418,9 +422,9 @@ pub fn layout_polygon( } // Prepare fill and stroke. - let fill = elem.fill(styles); - let fill_rule = elem.fill_rule(styles); - let stroke = match elem.stroke(styles) { + let fill = elem.fill.get_cloned(styles); + let fill_rule = elem.fill_rule.get(styles); + let stroke = match elem.stroke.resolve(styles) { Smart::Auto if fill.is_none() => Some(FixedStroke::default()), Smart::Auto => None, Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default), @@ -459,12 +463,12 @@ pub fn layout_rect( styles, region, ShapeKind::Rect, - elem.body(styles), - elem.fill(styles), - elem.stroke(styles), - elem.inset(styles), - elem.outset(styles), - elem.radius(styles), + elem.body.get_ref(styles), + elem.fill.get_cloned(styles), + elem.stroke.resolve(styles), + elem.inset.resolve(styles), + elem.outset.resolve(styles), + elem.radius.resolve(styles), elem.span(), ) } @@ -484,12 +488,12 @@ pub fn layout_square( styles, region, ShapeKind::Square, - elem.body(styles), - elem.fill(styles), - elem.stroke(styles), - elem.inset(styles), - elem.outset(styles), - elem.radius(styles), + elem.body.get_ref(styles), + elem.fill.get_cloned(styles), + elem.stroke.resolve(styles), + elem.inset.resolve(styles), + elem.outset.resolve(styles), + elem.radius.resolve(styles), elem.span(), ) } @@ -509,11 +513,11 @@ pub fn layout_ellipse( styles, region, ShapeKind::Ellipse, - elem.body(styles), - elem.fill(styles), - elem.stroke(styles).map(|s| Sides::splat(Some(s))), - elem.inset(styles), - elem.outset(styles), + elem.body.get_ref(styles), + elem.fill.get_cloned(styles), + elem.stroke.resolve(styles).map(|s| Sides::splat(Some(s))), + elem.inset.resolve(styles), + elem.outset.resolve(styles), Corners::splat(None), elem.span(), ) @@ -534,11 +538,11 @@ pub fn layout_circle( styles, region, ShapeKind::Circle, - elem.body(styles), - elem.fill(styles), - elem.stroke(styles).map(|s| Sides::splat(Some(s))), - elem.inset(styles), - elem.outset(styles), + elem.body.get_ref(styles), + elem.fill.get_cloned(styles), + elem.stroke.resolve(styles).map(|s| Sides::splat(Some(s))), + elem.inset.resolve(styles), + elem.outset.resolve(styles), Corners::splat(None), elem.span(), ) diff --git a/crates/typst-layout/src/stack.rs b/crates/typst-layout/src/stack.rs index c468945e..5df626fd 100644 --- a/crates/typst-layout/src/stack.rs +++ b/crates/typst-layout/src/stack.rs @@ -19,12 +19,12 @@ pub fn layout_stack( regions: Regions, ) -> SourceResult<Fragment> { let mut layouter = - StackLayouter::new(elem.span(), elem.dir(styles), locator, styles, regions); + StackLayouter::new(elem.span(), elem.dir.get(styles), locator, styles, regions); let axis = layouter.dir.axis(); // Spacing to insert before the next block. - let spacing = elem.spacing(styles); + let spacing = elem.spacing.get(styles); let mut deferred = None; for child in &elem.children { @@ -167,11 +167,11 @@ impl<'a> StackLayouter<'a> { // Block-axis alignment of the `AlignElem` is respected by stacks. let align = if let Some(align) = block.to_packed::<AlignElem>() { - align.alignment(styles) + align.alignment.get(styles) } else if let Some(styled) = block.to_packed::<StyledElem>() { - AlignElem::alignment_in(styles.chain(&styled.styles)) + styles.chain(&styled.styles).get(AlignElem::alignment) } else { - AlignElem::alignment_in(styles) + styles.get(AlignElem::alignment) } .resolve(styles); diff --git a/crates/typst-layout/src/transforms.rs b/crates/typst-layout/src/transforms.rs index f4526dd0..e545dd51 100644 --- a/crates/typst-layout/src/transforms.rs +++ b/crates/typst-layout/src/transforms.rs @@ -20,7 +20,7 @@ pub fn layout_move( region: Region, ) -> SourceResult<Frame> { let mut frame = crate::layout_frame(engine, &elem.body, locator, styles, region)?; - let delta = Axes::new(elem.dx(styles), elem.dy(styles)).resolve(styles); + let delta = Axes::new(elem.dx.resolve(styles), elem.dy.resolve(styles)); let delta = delta.zip_map(region.size, Rel::relative_to); frame.translate(delta.to_point()); Ok(frame) @@ -35,8 +35,8 @@ pub fn layout_rotate( styles: StyleChain, region: Region, ) -> SourceResult<Frame> { - let angle = elem.angle(styles); - let align = elem.origin(styles).resolve(styles); + let angle = elem.angle.get(styles); + let align = elem.origin.resolve(styles); // Compute the new region's approximate size. let is_finite = region.size.is_finite(); @@ -55,7 +55,7 @@ pub fn layout_rotate( &elem.body, Transform::rotate(angle), align, - elem.reflow(styles), + elem.reflow.get(styles), ) } @@ -83,8 +83,8 @@ pub fn layout_scale( styles, &elem.body, Transform::scale(scale.x, scale.y), - elem.origin(styles).resolve(styles), - elem.reflow(styles), + elem.origin.resolve(styles), + elem.reflow.get(styles), ) } @@ -121,13 +121,13 @@ fn resolve_scale( }); let x = resolve_axis( - elem.x(styles), + elem.x.get(styles), || size.as_ref().map(|size| size.x).map_err(Clone::clone), styles, )?; let y = resolve_axis( - elem.y(styles), + elem.y.get(styles), || size.as_ref().map(|size| size.y).map_err(Clone::clone), styles, )?; @@ -152,9 +152,9 @@ pub fn layout_skew( styles: StyleChain, region: Region, ) -> SourceResult<Frame> { - let ax = elem.ax(styles); - let ay = elem.ay(styles); - let align = elem.origin(styles).resolve(styles); + let ax = elem.ax.get(styles); + let ay = elem.ay.get(styles); + let align = elem.origin.resolve(styles); // Compute the new region's approximate size. let size = if region.size.is_finite() { @@ -172,7 +172,7 @@ pub fn layout_skew( &elem.body, Transform::skew(ax, ay), align, - elem.reflow(styles), + elem.reflow.get(styles), ) } diff --git a/crates/typst-library/src/foundations/element.rs b/crates/typst-library/src/foundations/content/element.rs index 7ff00b9d..49b0b0f9 100644 --- a/crates/typst-library/src/foundations/element.rs +++ b/crates/typst-library/src/foundations/content/element.rs @@ -2,52 +2,54 @@ use std::any::TypeId; use std::cmp::Ordering; use std::fmt::{self, Debug}; use std::hash::Hash; -use std::ptr::NonNull; -use std::sync::LazyLock; +use std::sync::OnceLock; use ecow::EcoString; use smallvec::SmallVec; -#[doc(inline)] -pub use typst_macros::elem; use typst_utils::Static; use crate::diag::SourceResult; use crate::engine::Engine; use crate::foundations::{ - cast, Args, Content, Dict, FieldAccessError, Func, ParamInfo, Repr, Scope, Selector, - StyleChain, Styles, Value, + cast, Args, Content, ContentVtable, FieldAccessError, Func, ParamInfo, Repr, Scope, + Selector, StyleChain, Styles, Value, }; use crate::text::{Lang, Region}; /// A document element. #[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct Element(Static<NativeElementData>); +pub struct Element(Static<ContentVtable>); impl Element { /// Get the element for `T`. - pub fn of<T: NativeElement>() -> Self { - T::elem() + pub const fn of<T: NativeElement>() -> Self { + T::ELEM + } + + /// Get the element for `T`. + pub const fn from_vtable(vtable: &'static ContentVtable) -> Self { + Self(Static(vtable)) } /// The element's normal name (e.g. `enum`). pub fn name(self) -> &'static str { - self.0.name + self.vtable().name } /// The element's title case name, for use in documentation /// (e.g. `Numbered List`). pub fn title(&self) -> &'static str { - self.0.title + self.vtable().title } /// Documentation for the element (as Markdown). pub fn docs(&self) -> &'static str { - self.0.docs + self.vtable().docs } /// Search keywords for the element. pub fn keywords(&self) -> &'static [&'static str] { - self.0.keywords + self.vtable().keywords } /// Construct an instance of this element. @@ -56,12 +58,12 @@ impl Element { engine: &mut Engine, args: &mut Args, ) -> SourceResult<Content> { - (self.0.construct)(engine, args) + (self.vtable().construct)(engine, args) } /// Execute the set rule for the element and return the resulting style map. pub fn set(self, engine: &mut Engine, mut args: Args) -> SourceResult<Styles> { - let styles = (self.0.set)(engine, &mut args)?; + let styles = (self.vtable().set)(engine, &mut args)?; args.finish()?; Ok(styles) } @@ -77,12 +79,7 @@ impl Element { /// Whether the element has the given capability where the capability is /// given by a `TypeId`. pub fn can_type_id(self, type_id: TypeId) -> bool { - (self.0.vtable)(type_id).is_some() - } - - /// The VTable for capabilities dispatch. - pub fn vtable(self) -> fn(of: TypeId) -> Option<NonNull<()>> { - self.0.vtable + (self.vtable().capability)(type_id).is_some() } /// Create a selector for this element. @@ -98,12 +95,29 @@ impl Element { /// The element's associated scope of sub-definition. pub fn scope(&self) -> &'static Scope { - &(self.0).0.scope + (self.vtable().store)().scope.get_or_init(|| (self.vtable().scope)()) } /// Details about the element's fields. pub fn params(&self) -> &'static [ParamInfo] { - &(self.0).0.params + (self.vtable().store)().params.get_or_init(|| { + self.vtable() + .fields + .iter() + .filter(|field| !field.synthesized) + .map(|field| ParamInfo { + name: field.name, + docs: field.docs, + input: (field.input)(), + default: field.default, + positional: field.positional, + named: !field.positional, + variadic: field.variadic, + required: field.required, + settable: field.settable, + }) + .collect() + }) } /// Extract the field ID for the given field name. @@ -111,7 +125,7 @@ impl Element { if name == "label" { return Some(255); } - (self.0.field_id)(name) + (self.vtable().field_id)(name) } /// Extract the field name for the given field ID. @@ -119,7 +133,7 @@ impl Element { if id == 255 { return Some("label"); } - (self.0.field_name)(id) + self.vtable().field(id).map(|data| data.name) } /// Extract the value of the field for the given field ID and style chain. @@ -128,12 +142,20 @@ impl Element { id: u8, styles: StyleChain, ) -> Result<Value, FieldAccessError> { - (self.0.field_from_styles)(id, styles) + self.vtable() + .field(id) + .and_then(|field| (field.get_from_styles)(styles)) + .ok_or(FieldAccessError::Unknown) } /// 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)) + self.vtable().local_name.map(|f| f(lang, region)) + } + + /// Retrieves the element's vtable for dynamic dispatch. + pub(super) fn vtable(&self) -> &'static ContentVtable { + (self.0).0 } } @@ -167,84 +189,34 @@ cast! { v: Func => v.element().ok_or("expected element")?, } -/// A Typst element that is defined by a native Rust type. -pub trait NativeElement: - Debug - + Clone - + PartialEq - + Hash - + Construct - + Set - + Capable - + Fields - + Repr - + Send - + Sync - + 'static -{ - /// Get the element for the native Rust element. - fn elem() -> Element - where - Self: Sized, - { - Element::from(Self::data()) - } +/// Lazily initialized data for an element. +#[derive(Default)] +pub struct LazyElementStore { + pub scope: OnceLock<Scope>, + pub params: OnceLock<Vec<ParamInfo>>, +} - /// Pack the element into type-erased content. - fn pack(self) -> Content - where - Self: Sized, - { - Content::new(self) +impl LazyElementStore { + /// Create an empty store. + pub const fn new() -> Self { + Self { scope: OnceLock::new(), params: OnceLock::new() } } - - /// Get the element data for the native Rust element. - fn data() -> &'static NativeElementData - where - Self: Sized; } -/// Used to cast an element to a trait object for a trait it implements. +/// A Typst element that is defined by a native Rust type. /// /// # Safety -/// If the `vtable` function returns `Some(p)`, then `p` must be a valid pointer -/// to a vtable of `Packed<Self>` w.r.t to the trait `C` where `capability` is -/// `TypeId::of::<dyn C>()`. -pub unsafe trait Capable { - /// Get the pointer to the vtable for the given capability / trait. - fn vtable(capability: TypeId) -> Option<NonNull<()>>; -} - -/// Defines how fields of an element are accessed. -pub trait Fields { - /// An enum with the fields of the element. - type Enum - where - Self: Sized; - - /// Whether the element has the given field set. - fn has(&self, id: u8) -> bool; - - /// Get the field with the given field ID. - fn field(&self, id: u8) -> Result<Value, FieldAccessError>; - - /// Get the field with the given ID in the presence of styles. - fn field_with_styles( - &self, - id: u8, - styles: StyleChain, - ) -> Result<Value, FieldAccessError>; - - /// Get the field with the given ID from the styles. - fn field_from_styles(id: u8, styles: StyleChain) -> Result<Value, FieldAccessError> - where - Self: Sized; - - /// Resolve all fields with the styles and save them in-place. - fn materialize(&mut self, styles: StyleChain); +/// `ELEM` must hold the correct `Element` for `Self`. +pub unsafe trait NativeElement: + Debug + Clone + Hash + Construct + Set + Send + Sync + 'static +{ + /// The associated element. + const ELEM: Element; - /// Get the fields of the element. - fn fields(&self) -> Dict; + /// Pack the element into type-erased content. + fn pack(self) -> Content { + Content::new(self) + } } /// An element's constructor function. @@ -266,48 +238,6 @@ pub trait Set { Self: Sized; } -/// Defines a native element. -#[derive(Debug)] -pub struct NativeElementData { - /// The element's normal name (e.g. `align`), as exposed to Typst. - pub name: &'static str, - /// The element's title case name (e.g. `Align`). - pub title: &'static str, - /// The documentation for this element as a string. - pub docs: &'static str, - /// A list of alternate search terms for this element. - pub keywords: &'static [&'static str], - /// The constructor for this element (see [`Construct`]). - pub construct: fn(&mut Engine, &mut Args) -> SourceResult<Content>, - /// Executes this element's set rule (see [`Set`]). - pub set: fn(&mut Engine, &mut Args) -> SourceResult<Styles>, - /// Gets the vtable for one of this element's capabilities - /// (see [`Capable`]). - pub vtable: fn(capability: TypeId) -> Option<NonNull<()>>, - /// Gets the numeric index of this field by its name. - pub field_id: fn(name: &str) -> Option<u8>, - /// Gets the name of a field by its numeric index. - pub field_name: fn(u8) -> Option<&'static str>, - /// Get the field with the given ID in the presence of styles (see [`Fields`]). - pub field_from_styles: fn(u8, StyleChain) -> Result<Value, FieldAccessError>, - /// Gets the localized name for this element (see [`LocalName`][crate::text::LocalName]). - pub local_name: Option<fn(Lang, Option<Region>) -> &'static str>, - pub scope: LazyLock<Scope>, - /// A list of parameter information for each field. - pub params: LazyLock<Vec<ParamInfo>>, -} - -impl From<&'static NativeElementData> for Element { - fn from(data: &'static NativeElementData) -> Self { - Self(Static(data)) - } -} - -cast! { - &'static NativeElementData, - self => Element::from(self).into_value(), -} - /// Synthesize fields on an element. This happens before execution of any show /// rule. pub trait Synthesize { @@ -331,3 +261,9 @@ pub trait ShowSet { /// that should work even in the face of a user-defined show rule. fn show_set(&self, styles: StyleChain) -> Styles; } + +/// Tries to extract the plain-text representation of the element. +pub trait PlainText { + /// Write this element's plain text into the given buffer. + fn plain_text(&self, text: &mut EcoString); +} diff --git a/crates/typst-library/src/foundations/content/field.rs b/crates/typst-library/src/foundations/content/field.rs new file mode 100644 index 00000000..8d0fe529 --- /dev/null +++ b/crates/typst-library/src/foundations/content/field.rs @@ -0,0 +1,564 @@ +use std::fmt::{self, Debug}; +use std::hash::Hash; +use std::marker::PhantomData; +use std::sync::OnceLock; + +use ecow::{eco_format, EcoString}; + +use crate::foundations::{ + Container, Content, FieldVtable, Fold, FoldFn, IntoValue, NativeElement, Packed, + Property, Reflect, Repr, Resolve, StyleChain, +}; + +/// An accessor for the `I`-th field of the element `E`. Values of this type are +/// generated for each field of an element can be used to interact with this +/// field programmatically, for example to access the style chain, as in +/// `styles.get(TextElem::size)`. +#[derive(Copy, Clone)] +pub struct Field<E: NativeElement, const I: u8>(pub PhantomData<E>); + +impl<E: NativeElement, const I: u8> Field<E, I> { + /// Creates a new zero-sized accessor. + pub const fn new() -> Self { + Self(PhantomData) + } + + /// The index of the projected field. + pub const fn index(self) -> u8 { + I + } + + /// Creates a dynamic property instance for this field. + /// + /// Prefer [`Content::set`] or + /// [`Styles::set`](crate::foundations::Styles::set) when working with + /// existing content or style value. + pub fn set(self, value: E::Type) -> Property + where + E: SettableProperty<I>, + E::Type: Debug + Clone + Hash + Send + Sync + 'static, + { + Property::new(self, value) + } +} + +impl<E: NativeElement, const I: u8> Default for Field<E, I> { + fn default() -> Self { + Self::new() + } +} + +/// A field that is present on every instance of the element. +pub trait RequiredField<const I: u8>: NativeElement { + type Type: Clone; + + const FIELD: RequiredFieldData<Self, I>; +} + +/// Metadata and routines for a [`RequiredField`]. +pub struct RequiredFieldData<E: RequiredField<I>, const I: u8> { + name: &'static str, + docs: &'static str, + get: fn(&E) -> &E::Type, +} + +impl<E: RequiredField<I>, const I: u8> RequiredFieldData<E, I> { + /// Creates the data from its parts. This is called in the `#[elem]` macro. + pub const fn new( + name: &'static str, + docs: &'static str, + get: fn(&E) -> &E::Type, + ) -> Self { + Self { name, docs, get } + } + + /// Creates the vtable for a `#[required]` field. + pub const fn vtable() -> FieldVtable<Packed<E>> + where + E: RequiredField<I>, + E::Type: Reflect + IntoValue + PartialEq, + { + FieldVtable { + name: E::FIELD.name, + docs: E::FIELD.docs, + positional: true, + required: true, + variadic: false, + settable: false, + synthesized: false, + input: || <E::Type as Reflect>::input(), + default: None, + has: |_| true, + get: |elem| Some((E::FIELD.get)(elem).clone().into_value()), + get_with_styles: |elem, _| Some((E::FIELD.get)(elem).clone().into_value()), + get_from_styles: |_| None, + materialize: |_, _| {}, + eq: |a, b| (E::FIELD.get)(a) == (E::FIELD.get)(b), + } + } + + /// Creates the vtable for a `#[variadic]` field. + pub const fn vtable_variadic() -> FieldVtable<Packed<E>> + where + E: RequiredField<I>, + E::Type: Container + IntoValue + PartialEq, + <E::Type as Container>::Inner: Reflect, + { + FieldVtable { + name: E::FIELD.name, + docs: E::FIELD.docs, + positional: true, + required: true, + variadic: true, + settable: false, + synthesized: false, + input: || <<E::Type as Container>::Inner as Reflect>::input(), + default: None, + has: |_| true, + get: |elem| Some((E::FIELD.get)(elem).clone().into_value()), + get_with_styles: |elem, _| Some((E::FIELD.get)(elem).clone().into_value()), + get_from_styles: |_| None, + materialize: |_, _| {}, + eq: |a, b| (E::FIELD.get)(a) == (E::FIELD.get)(b), + } + } +} + +/// A field that is initially unset, but may be set through a +/// [`Synthesize`](crate::foundations::Synthesize) implementation. +pub trait SynthesizedField<const I: u8>: NativeElement { + type Type: Clone; + + const FIELD: SynthesizedFieldData<Self, I>; +} + +/// Metadata and routines for a [`SynthesizedField`]. +pub struct SynthesizedFieldData<E: SynthesizedField<I>, const I: u8> { + name: &'static str, + docs: &'static str, + get: fn(&E) -> &Option<E::Type>, +} + +impl<E: SynthesizedField<I>, const I: u8> SynthesizedFieldData<E, I> { + /// Creates the data from its parts. This is called in the `#[elem]` macro. + pub const fn new( + name: &'static str, + docs: &'static str, + get: fn(&E) -> &Option<E::Type>, + ) -> Self { + Self { name, docs, get } + } + + /// Creates type-erased metadata and routines for a `#[synthesized]` field. + pub const fn vtable() -> FieldVtable<Packed<E>> + where + E: SynthesizedField<I>, + E::Type: Reflect + IntoValue + PartialEq, + { + FieldVtable { + name: E::FIELD.name, + docs: E::FIELD.docs, + positional: false, + required: false, + variadic: false, + settable: false, + synthesized: true, + input: || <E::Type as Reflect>::input(), + default: None, + has: |elem| (E::FIELD.get)(elem).is_some(), + get: |elem| (E::FIELD.get)(elem).clone().map(|v| v.into_value()), + get_with_styles: |elem, _| { + (E::FIELD.get)(elem).clone().map(|v| v.into_value()) + }, + get_from_styles: |_| None, + materialize: |_, _| {}, + // Synthesized fields don't affect equality. + eq: |_, _| true, + } + } +} + +/// A field that is not actually there. It's only visible in the docs. +pub trait ExternalField<const I: u8>: NativeElement { + type Type; + + const FIELD: ExternalFieldData<Self, I>; +} + +/// Metadata for an [`ExternalField`]. +pub struct ExternalFieldData<E: ExternalField<I>, const I: u8> { + name: &'static str, + docs: &'static str, + default: fn() -> E::Type, +} + +impl<E: ExternalField<I>, const I: u8> ExternalFieldData<E, I> { + /// Creates the data from its parts. This is called in the `#[elem]` macro. + pub const fn new( + name: &'static str, + docs: &'static str, + default: fn() -> E::Type, + ) -> Self { + Self { name, docs, default } + } + + /// Creates type-erased metadata and routines for an `#[external]` field. + pub const fn vtable() -> FieldVtable<Packed<E>> + where + E: ExternalField<I>, + E::Type: Reflect + IntoValue, + { + FieldVtable { + name: E::FIELD.name, + docs: E::FIELD.docs, + positional: false, + required: false, + variadic: false, + settable: false, + synthesized: false, + input: || <E::Type as Reflect>::input(), + default: Some(|| (E::FIELD.default)().into_value()), + has: |_| false, + get: |_| None, + get_with_styles: |_, _| None, + get_from_styles: |_| None, + materialize: |_, _| {}, + eq: |_, _| true, + } + } +} + +/// A field that has a default value and can be configured via a set rule, but +/// can also present on elements and be present in the constructor. +pub trait SettableField<const I: u8>: NativeElement { + type Type: Clone; + + const FIELD: SettableFieldData<Self, I>; +} + +/// Metadata and routines for a [`SettableField`]. +pub struct SettableFieldData<E: SettableField<I>, const I: u8> { + get: fn(&E) -> &Settable<E, I>, + get_mut: fn(&mut E) -> &mut Settable<E, I>, + property: SettablePropertyData<E, I>, +} + +impl<E: SettableField<I>, const I: u8> SettableFieldData<E, I> { + /// Creates the data from its parts. This is called in the `#[elem]` macro. + pub const fn new( + name: &'static str, + docs: &'static str, + positional: bool, + get: fn(&E) -> &Settable<E, I>, + get_mut: fn(&mut E) -> &mut Settable<E, I>, + default: fn() -> E::Type, + slot: fn() -> &'static OnceLock<E::Type>, + ) -> Self { + Self { + get, + get_mut, + property: SettablePropertyData::new(name, docs, positional, default, slot), + } + } + + /// Ensures that the property is folded on every access. See the + /// documentation of the [`Fold`] trait for more details. + pub const fn with_fold(mut self) -> Self + where + E::Type: Fold, + { + self.property.fold = Some(E::Type::fold); + self + } + + /// Creates type-erased metadata and routines for a normal settable field. + pub const fn vtable() -> FieldVtable<Packed<E>> + where + E: SettableField<I>, + E::Type: Reflect + IntoValue + PartialEq, + { + FieldVtable { + name: E::FIELD.property.name, + docs: E::FIELD.property.docs, + positional: E::FIELD.property.positional, + required: false, + variadic: false, + settable: true, + synthesized: false, + input: || <E::Type as Reflect>::input(), + default: Some(|| E::default().into_value()), + has: |elem| (E::FIELD.get)(elem).is_set(), + get: |elem| (E::FIELD.get)(elem).as_option().clone().map(|v| v.into_value()), + get_with_styles: |elem, styles| { + Some((E::FIELD.get)(elem).get_cloned(styles).into_value()) + }, + get_from_styles: |styles| { + Some(styles.get_cloned::<E, I>(Field::new()).into_value()) + }, + materialize: |elem, styles| { + if !(E::FIELD.get)(elem).is_set() { + (E::FIELD.get_mut)(elem).set(styles.get_cloned::<E, I>(Field::new())); + } + }, + eq: |a, b| (E::FIELD.get)(a).as_option() == (E::FIELD.get)(b).as_option(), + } + } +} + +/// A field that has a default value and can be configured via a set rule, but +/// is never present on elements. +/// +/// This is provided for all `SettableField` impls through a blanket impl. In +/// the case of `#[ghost]` fields, which only live in the style chain and not in +/// elements, it is also implemented manually. +pub trait SettableProperty<const I: u8>: NativeElement { + type Type: Clone; + + const FIELD: SettablePropertyData<Self, I>; + const FOLD: Option<FoldFn<Self::Type>> = Self::FIELD.fold; + + /// Produces an instance of the property's default value. + fn default() -> Self::Type { + // Avoid recreating an expensive instance over and over, but also + // avoid unnecessary lazy initialization for cheap types. + if std::mem::needs_drop::<Self::Type>() { + Self::default_ref().clone() + } else { + (Self::FIELD.default)() + } + } + + /// Produces a static reference to this property's default value. + fn default_ref() -> &'static Self::Type { + (Self::FIELD.slot)().get_or_init(Self::FIELD.default) + } +} + +impl<T, const I: u8> SettableProperty<I> for T +where + T: SettableField<I>, +{ + type Type = <Self as SettableField<I>>::Type; + + const FIELD: SettablePropertyData<Self, I> = + <Self as SettableField<I>>::FIELD.property; +} + +/// Metadata and routines for a [`SettableProperty`]. +pub struct SettablePropertyData<E: SettableProperty<I>, const I: u8> { + name: &'static str, + docs: &'static str, + positional: bool, + default: fn() -> E::Type, + slot: fn() -> &'static OnceLock<E::Type>, + fold: Option<FoldFn<E::Type>>, +} + +impl<E: SettableProperty<I>, const I: u8> SettablePropertyData<E, I> { + /// Creates the data from its parts. This is called in the `#[elem]` macro. + pub const fn new( + name: &'static str, + docs: &'static str, + positional: bool, + default: fn() -> E::Type, + slot: fn() -> &'static OnceLock<E::Type>, + ) -> Self { + Self { name, docs, positional, default, slot, fold: None } + } + + /// Ensures that the property is folded on every access. See the + /// documentation of the [`Fold`] trait for more details. + pub const fn with_fold(self) -> Self + where + E::Type: Fold, + { + Self { fold: Some(E::Type::fold), ..self } + } + + /// Creates type-erased metadata and routines for a `#[ghost]` field. + pub const fn vtable() -> FieldVtable<Packed<E>> + where + E: SettableProperty<I>, + E::Type: Reflect + IntoValue + PartialEq, + { + FieldVtable { + name: E::FIELD.name, + docs: E::FIELD.docs, + positional: E::FIELD.positional, + required: false, + variadic: false, + settable: true, + synthesized: false, + input: || <E::Type as Reflect>::input(), + default: Some(|| E::default().into_value()), + has: |_| false, + get: |_| None, + get_with_styles: |_, styles| { + Some(styles.get_cloned::<E, I>(Field::new()).into_value()) + }, + get_from_styles: |styles| { + Some(styles.get_cloned::<E, I>(Field::new()).into_value()) + }, + materialize: |_, _| {}, + eq: |_, _| true, + } + } +} + +/// A settable property that can be accessed by reference (because it is not +/// folded). +pub trait RefableProperty<const I: u8>: SettableProperty<I> {} + +/// A settable field of an element. +/// +/// The field can be in two states: Unset or present. +/// +/// See [`StyleChain`] for more details about the available accessor methods. +#[derive(Copy, Clone, Hash)] +pub struct Settable<E: NativeElement, const I: u8>(Option<E::Type>) +where + E: SettableProperty<I>; + +impl<E: NativeElement, const I: u8> Settable<E, I> +where + E: SettableProperty<I>, +{ + /// Creates a new unset instance. + pub fn new() -> Self { + Self(None) + } + + /// Sets the instance to a value. + pub fn set(&mut self, value: E::Type) { + self.0 = Some(value); + } + + /// Clears the value from the instance. + pub fn unset(&mut self) { + self.0 = None; + } + + /// Views the type as an [`Option`] which is `Some` if the type is set + /// and `None` if it is unset. + pub fn as_option(&self) -> &Option<E::Type> { + &self.0 + } + + /// Views the type as a mutable [`Option`]. + pub fn as_option_mut(&mut self) -> &mut Option<E::Type> { + &mut self.0 + } + + /// Whether the field is set. + pub fn is_set(&self) -> bool { + self.0.is_some() + } + + /// Retrieves the value given styles. The styles are used if the value is + /// unset. + pub fn get<'a>(&'a self, styles: StyleChain<'a>) -> E::Type + where + E::Type: Copy, + { + self.get_cloned(styles) + } + + /// Retrieves and clones the value given styles. The styles are used if the + /// value is unset or if it needs folding. + pub fn get_cloned<'a>(&'a self, styles: StyleChain<'a>) -> E::Type { + if let Some(fold) = E::FOLD { + let mut res = styles.get_cloned::<E, I>(Field::new()); + if let Some(value) = &self.0 { + res = fold(value.clone(), res); + } + res + } else if let Some(value) = &self.0 { + value.clone() + } else { + styles.get_cloned::<E, I>(Field::new()) + } + } + + /// Retrieves a reference to the value given styles. The styles are used if + /// the value is unset. + pub fn get_ref<'a>(&'a self, styles: StyleChain<'a>) -> &'a E::Type + where + E: RefableProperty<I>, + { + if let Some(value) = &self.0 { + value + } else { + styles.get_ref::<E, I>(Field::new()) + } + } + + /// Retrieves the value and then immediately [resolves](Resolve) it. + pub fn resolve<'a>(&'a self, styles: StyleChain<'a>) -> <E::Type as Resolve>::Output + where + E::Type: Resolve, + { + self.get_cloned(styles).resolve(styles) + } +} + +impl<E: NativeElement, const I: u8> Debug for Settable<E, I> +where + E: SettableProperty<I>, + E::Type: Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<E: NativeElement, const I: u8> Default for Settable<E, I> +where + E: SettableProperty<I>, +{ + fn default() -> Self { + Self(None) + } +} + +impl<E: NativeElement, const I: u8> From<Option<E::Type>> for Settable<E, I> +where + E: SettableProperty<I>, +{ + fn from(value: Option<E::Type>) -> Self { + Self(value) + } +} + +/// An error arising when trying to access a field of content. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum FieldAccessError { + Unknown, + Unset, +} + +impl FieldAccessError { + /// Formats the error message given the content and the field name. + #[cold] + pub fn message(self, content: &Content, field: &str) -> EcoString { + let elem_name = content.elem().name(); + match self { + FieldAccessError::Unknown => { + eco_format!("{elem_name} does not have field {}", field.repr()) + } + FieldAccessError::Unset => { + eco_format!( + "field {} in {elem_name} is not known at this point", + field.repr() + ) + } + } + } + + /// Formats the error message for an `at` calls without a default value. + #[cold] + pub fn message_no_default(self, content: &Content, field: &str) -> EcoString { + let mut msg = self.message(content, field); + msg.push_str(" and no default was specified"); + msg + } +} diff --git a/crates/typst-library/src/foundations/content.rs b/crates/typst-library/src/foundations/content/mod.rs index 1855bb70..7ba790d8 100644 --- a/crates/typst-library/src/foundations/content.rs +++ b/crates/typst-library/src/foundations/content/mod.rs @@ -1,23 +1,33 @@ -use std::any::TypeId; +mod element; +mod field; +mod packed; +mod raw; +mod vtable; + +pub use self::element::*; +pub use self::field::*; +pub use self::packed::Packed; +pub use self::vtable::{ContentVtable, FieldVtable}; +#[doc(inline)] +pub use typst_macros::elem; + use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; +use std::hash::Hash; use std::iter::{self, Sum}; -use std::marker::PhantomData; -use std::ops::{Add, AddAssign, ControlFlow, Deref, DerefMut}; -use std::sync::Arc; +use std::ops::{Add, AddAssign, ControlFlow}; use comemo::Tracked; use ecow::{eco_format, EcoString}; use serde::{Serialize, Serializer}; + use typst_syntax::Span; -use typst_utils::{fat, singleton, LazyHash, SmallBitSet}; +use typst_utils::singleton; use crate::diag::{SourceResult, StrResult}; use crate::engine::Engine; use crate::foundations::{ - elem, func, scope, ty, Context, Dict, Element, Fields, IntoValue, Label, - NativeElement, Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles, - Value, + func, repr, scope, ty, Context, Dict, IntoValue, Label, Property, Recipe, + RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles, Value, }; use crate::introspection::Location; use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides}; @@ -68,43 +78,14 @@ use crate::text::UnderlineElem; /// elements the content is composed of and what fields they have. /// Alternatively, you can inspect the output of the [`repr`] function. #[ty(scope, cast)] -#[derive(Clone, Hash)] -#[allow(clippy::derived_hash_with_manual_eq)] -pub struct Content { - /// The partially element-dependent inner data. - inner: Arc<Inner<dyn Bounds>>, - /// The element's source code location. - span: Span, -} - -/// The inner representation behind the `Arc`. -#[derive(Hash)] -struct Inner<T: ?Sized + 'static> { - /// An optional label attached to the element. - label: Option<Label>, - /// The element's location which identifies it in the layouted output. - location: Option<Location>, - /// Manages the element during realization. - /// - If bit 0 is set, the element is prepared. - /// - If bit n is set, the element is guarded against the n-th show rule - /// recipe from the top of the style chain (counting from 1). - lifecycle: SmallBitSet, - /// The element's raw data. - elem: LazyHash<T>, -} +#[derive(Clone, PartialEq, Hash)] +#[repr(transparent)] +pub struct Content(raw::RawContent); impl Content { /// Creates a new content from an element. pub fn new<T: NativeElement>(elem: T) -> Self { - Self { - inner: Arc::new(Inner { - label: None, - location: None, - lifecycle: SmallBitSet::new(), - elem: elem.into(), - }), - span: Span::detached(), - } + Self(raw::RawContent::new(elem)) } /// Creates a empty sequence content. @@ -114,25 +95,25 @@ impl Content { /// Get the element of this content. pub fn elem(&self) -> Element { - self.inner.elem.dyn_elem() + self.0.elem() } /// Get the span of the content. pub fn span(&self) -> Span { - self.span + self.0.span() } /// Set the span of the content. pub fn spanned(mut self, span: Span) -> Self { - if self.span.is_detached() { - self.span = span; + if self.0.span().is_detached() { + *self.0.span_mut() = span; } self } /// Get the label of the content. pub fn label(&self) -> Option<Label> { - self.inner.label + self.0.meta().label } /// Attach a label to the content. @@ -143,7 +124,7 @@ impl Content { /// Set the label of the content. pub fn set_label(&mut self, label: Label) { - self.make_mut().label = Some(label); + self.0.meta_mut().label = Some(label); } /// Assigns a location to the content. @@ -159,28 +140,28 @@ impl Content { /// Set the location of the content. pub fn set_location(&mut self, location: Location) { - self.make_mut().location = Some(location); + self.0.meta_mut().location = Some(location); } /// Check whether a show rule recipe is disabled. pub fn is_guarded(&self, index: RecipeIndex) -> bool { - self.inner.lifecycle.contains(index.0) + self.0.meta().lifecycle.contains(index.0) } /// Disable a show rule recipe. pub fn guarded(mut self, index: RecipeIndex) -> Self { - self.make_mut().lifecycle.insert(index.0); + self.0.meta_mut().lifecycle.insert(index.0); self } /// Whether this content has already been prepared. pub fn is_prepared(&self) -> bool { - self.inner.lifecycle.contains(0) + self.0.meta().lifecycle.contains(0) } /// Mark this content as prepared. pub fn mark_prepared(&mut self) { - self.make_mut().lifecycle.insert(0); + self.0.meta_mut().lifecycle.insert(0); } /// Get a field by ID. @@ -198,9 +179,14 @@ impl Content { return Ok(label.into_value()); } } - match styles { - Some(styles) => self.inner.elem.field_with_styles(id, styles), - None => self.inner.elem.field(id), + + match self.0.handle().field(id) { + Some(handle) => match styles { + Some(styles) => handle.get_with_styles(styles), + None => handle.get(), + } + .ok_or(FieldAccessError::Unset), + None => Err(FieldAccessError::Unknown), } } @@ -215,8 +201,11 @@ impl Content { .map(|label| label.into_value()) .ok_or(FieldAccessError::Unknown); } - let id = self.elem().field_id(name).ok_or(FieldAccessError::Unknown)?; - self.get(id, None) + + match self.elem().field_id(name).and_then(|id| self.0.handle().field(id)) { + Some(handle) => handle.get().ok_or(FieldAccessError::Unset), + None => Err(FieldAccessError::Unknown), + } } /// Get a field by ID, returning a missing field error if it does not exist. @@ -240,7 +229,9 @@ impl Content { /// Resolve all fields with the styles and save them in-place. pub fn materialize(&mut self, styles: StyleChain) { - self.make_mut().elem.materialize(styles); + for id in 0..self.elem().vtable().fields.len() as u8 { + self.0.handle_mut().field(id).unwrap().materialize(styles); + } } /// Create a new sequence element from multiples elements. @@ -257,7 +248,7 @@ impl Content { /// Whether the contained element is of type `T`. pub fn is<T: NativeElement>(&self) -> bool { - self.inner.elem.dyn_type_id() == TypeId::of::<T>() + self.0.is::<T>() } /// Downcasts the element to a packed value. @@ -280,16 +271,6 @@ impl Content { self.into_packed::<T>().map(Packed::unpack) } - /// Makes sure the content is not shared and returns a mutable reference to - /// the inner data. - fn make_mut(&mut self) -> &mut Inner<dyn Bounds> { - let arc = &mut self.inner; - if Arc::strong_count(arc) > 1 || Arc::weak_count(arc) > 0 { - *self = arc.elem.dyn_clone(arc, self.span); - } - Arc::get_mut(&mut self.inner).unwrap() - } - /// Whether the contained element has the given capability. pub fn can<C>(&self) -> bool where @@ -304,13 +285,7 @@ impl Content { where C: ?Sized + 'static, { - // Safety: The vtable comes from the `Capable` implementation which - // guarantees to return a matching vtable for `Packed<T>` and `C`. - // Since any `Packed<T>` is a repr(transparent) `Content`, we can also - // use a `*const Content` pointer. - let vtable = self.elem().vtable()(TypeId::of::<C>())?; - let data = self as *const Content as *const (); - Some(unsafe { &*fat::from_raw_parts(data, vtable.as_ptr()) }) + self.0.with::<C>() } /// Cast to a mutable trait object if the contained element has the given @@ -319,18 +294,7 @@ impl Content { where C: ?Sized + 'static, { - // Safety: The vtable comes from the `Capable` implementation which - // guarantees to return a matching vtable for `Packed<T>` and `C`. - // Since any `Packed<T>` is a repr(transparent) `Content`, we can also - // use a `*const Content` pointer. - // - // The resulting trait object contains an `&mut Packed<T>`. We do _not_ - // need to ensure that we hold the only reference to the `Arc` here - // because `Packed<T>`'s DerefMut impl will take care of that if - // mutable access is required. - let vtable = self.elem().vtable()(TypeId::of::<C>())?; - let data = self as *mut Content as *mut (); - Some(unsafe { &mut *fat::from_raw_parts_mut(data, vtable.as_ptr()) }) + self.0.with_mut::<C>() } /// Whether the content is an empty sequence. @@ -372,6 +336,15 @@ impl Content { Self::sequence(std::iter::repeat_with(|| self.clone()).take(count)) } + /// Sets a style property on the content. + pub fn set<E, const I: u8>(self, field: Field<E, I>, value: E::Type) -> Self + where + E: SettableProperty<I>, + E::Type: Debug + Clone + Hash + Send + Sync + 'static, + { + self.styled(Property::new(field, value)) + } + /// Style this content with a style entry. pub fn styled(mut self, style: impl Into<Style>) -> Self { if let Some(style_elem) = self.to_packed_mut::<StyledElem>() { @@ -476,7 +449,7 @@ impl Content { // Call f on the element itself before recursively iterating its fields. f(self.clone())?; - for (_, value) in self.inner.elem.fields() { + for (_, value) in self.fields() { walk_value(value, f)?; } ControlFlow::Continue(()) @@ -504,12 +477,12 @@ impl Content { /// Link the content somewhere. pub fn linked(self, dest: Destination) -> Self { - self.styled(LinkElem::set_current(Some(dest))) + self.set(LinkElem::current, Some(dest)) } /// Set alignments for this content. pub fn aligned(self, align: Alignment) -> Self { - self.styled(AlignElem::set_alignment(align)) + self.set(AlignElem::alignment, align) } /// Pad this content at the sides. @@ -562,7 +535,10 @@ impl Content { return false; }; - self.inner.elem.has(id) + match self.0.handle().field(id) { + Some(field) => field.has(), + None => false, + } } /// Access the specified field on the content. Returns the default value if @@ -592,7 +568,12 @@ impl Content { /// ``` #[func] pub fn fields(&self) -> Dict { - let mut dict = self.inner.elem.fields(); + let mut dict = Dict::new(); + for field in self.0.handle().fields() { + if let Some(value) = field.get() { + dict.insert(field.name.into(), value); + } + } if let Some(label) = self.label() { dict.insert("label".into(), label.into_value()); } @@ -605,7 +586,7 @@ impl Content { /// used with [counters]($counter), [state] and [queries]($query). #[func] pub fn location(&self) -> Option<Location> { - self.inner.location + self.0.meta().location } } @@ -617,7 +598,7 @@ impl Default for Content { impl Debug for Content { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.inner.elem.fmt(f) + self.0.fmt(f) } } @@ -627,16 +608,22 @@ impl<T: NativeElement> From<T> for Content { } } -impl PartialEq for Content { - fn eq(&self, other: &Self) -> bool { - // Additional short circuit for different elements. - self.elem() == other.elem() && self.inner.elem.dyn_eq(other) - } -} - impl Repr for Content { fn repr(&self) -> EcoString { - self.inner.elem.repr() + self.0.handle().repr().unwrap_or_else(|| { + let fields = self + .0 + .handle() + .fields() + .filter_map(|field| field.get().map(|v| (field.name, v.repr()))) + .map(|(name, value)| eco_format!("{name}: {value}")) + .collect::<Vec<_>>(); + eco_format!( + "{}{}", + self.elem().name(), + repr::pretty_array_like(&fields, false), + ) + }) } } @@ -717,190 +704,8 @@ impl Serialize for Content { } } -/// The trait that combines all the other traits into a trait object. -trait Bounds: Debug + Repr + Fields + Send + Sync + 'static { - fn dyn_type_id(&self) -> TypeId; - fn dyn_elem(&self) -> Element; - fn dyn_clone(&self, inner: &Inner<dyn Bounds>, span: Span) -> Content; - fn dyn_hash(&self, hasher: &mut dyn Hasher); - fn dyn_eq(&self, other: &Content) -> bool; -} - -impl<T: NativeElement> Bounds for T { - fn dyn_type_id(&self) -> TypeId { - TypeId::of::<Self>() - } - - fn dyn_elem(&self) -> Element { - Self::elem() - } - - fn dyn_clone(&self, inner: &Inner<dyn Bounds>, span: Span) -> Content { - Content { - inner: Arc::new(Inner { - label: inner.label, - location: inner.location, - lifecycle: inner.lifecycle.clone(), - elem: LazyHash::reuse(self.clone(), &inner.elem), - }), - span, - } - } - - fn dyn_hash(&self, mut state: &mut dyn Hasher) { - TypeId::of::<Self>().hash(&mut state); - self.hash(&mut state); - } - - fn dyn_eq(&self, other: &Content) -> bool { - let Some(other) = other.to_packed::<Self>() else { - return false; - }; - *self == **other - } -} - -impl Hash for dyn Bounds { - fn hash<H: Hasher>(&self, state: &mut H) { - self.dyn_hash(state); - } -} - -/// A packed element of a static type. -#[derive(Clone, PartialEq, Hash)] -#[repr(transparent)] -pub struct Packed<T: NativeElement>( - /// Invariant: Must be of type `T`. - Content, - PhantomData<T>, -); - -impl<T: NativeElement> Packed<T> { - /// Pack element while retaining its static type. - pub fn new(element: T) -> Self { - // Safety: The element is known to be of type `T`. - Packed(element.pack(), PhantomData) - } - - /// Try to cast type-erased content into a statically known packed element. - pub fn from_ref(content: &Content) -> Option<&Self> { - if content.is::<T>() { - // Safety: - // - We have checked the type. - // - Packed<T> is repr(transparent). - return Some(unsafe { std::mem::transmute::<&Content, &Packed<T>>(content) }); - } - None - } - - /// Try to cast type-erased content into a statically known packed element. - pub fn from_mut(content: &mut Content) -> Option<&mut Self> { - if content.is::<T>() { - // Safety: - // - We have checked the type. - // - Packed<T> is repr(transparent). - return Some(unsafe { - std::mem::transmute::<&mut Content, &mut Packed<T>>(content) - }); - } - None - } - - /// Try to cast type-erased content into a statically known packed element. - pub fn from_owned(content: Content) -> Result<Self, Content> { - if content.is::<T>() { - // Safety: - // - We have checked the type. - // - Packed<T> is repr(transparent). - return Ok(unsafe { std::mem::transmute::<Content, Packed<T>>(content) }); - } - Err(content) - } - - /// Pack back into content. - pub fn pack(self) -> Content { - self.0 - } - - /// Extract the raw underlying element. - pub fn unpack(self) -> T { - // This function doesn't yet need owned self, but might in the future. - (*self).clone() - } - - /// The element's span. - pub fn span(&self) -> Span { - self.0.span() - } - - /// Set the span of the element. - pub fn spanned(self, span: Span) -> Self { - Self(self.0.spanned(span), PhantomData) - } - - /// Accesses the label of the element. - pub fn label(&self) -> Option<Label> { - self.0.label() - } - - /// Accesses the location of the element. - pub fn location(&self) -> Option<Location> { - self.0.location() - } - - /// Sets the location of the element. - pub fn set_location(&mut self, location: Location) { - self.0.set_location(location); - } -} - -impl<T: NativeElement> AsRef<T> for Packed<T> { - fn as_ref(&self) -> &T { - self - } -} - -impl<T: NativeElement> AsMut<T> for Packed<T> { - fn as_mut(&mut self) -> &mut T { - self - } -} - -impl<T: NativeElement> Deref for Packed<T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - // Safety: - // - Packed<T> guarantees that the content trait object wraps - // an element of type `T`. - // - This downcast works the same way as dyn Any's does. We can't reuse - // that one because we don't want to pay the cost for every deref. - let elem = &*self.0.inner.elem; - unsafe { &*(elem as *const dyn Bounds as *const T) } - } -} - -impl<T: NativeElement> DerefMut for Packed<T> { - fn deref_mut(&mut self) -> &mut Self::Target { - // Safety: - // - Packed<T> guarantees that the content trait object wraps - // an element of type `T`. - // - We have guaranteed unique access thanks to `make_mut`. - // - This downcast works the same way as dyn Any's does. We can't reuse - // that one because we don't want to pay the cost for every deref. - let elem = &mut *self.0.make_mut().elem; - unsafe { &mut *(elem as *mut dyn Bounds as *mut T) } - } -} - -impl<T: NativeElement + Debug> Debug for Packed<T> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - /// A sequence of content. -#[elem(Debug, Repr, PartialEq)] +#[elem(Debug, Repr)] pub struct SequenceElem { /// The elements. #[required] @@ -922,19 +727,13 @@ impl Default for SequenceElem { } } -impl PartialEq for SequenceElem { - fn eq(&self, other: &Self) -> bool { - self.children.iter().eq(other.children.iter()) - } -} - impl Repr for SequenceElem { fn repr(&self) -> EcoString { if self.children.is_empty() { "[]".into() } else { let elements = crate::foundations::repr::pretty_array_like( - &self.children.iter().map(|c| c.inner.elem.repr()).collect::<Vec<_>>(), + &self.children.iter().map(|c| c.repr()).collect::<Vec<_>>(), false, ); eco_format!("sequence{}", elements) @@ -974,49 +773,8 @@ impl Repr for StyledElem { } } -/// Tries to extract the plain-text representation of the element. -pub trait PlainText { - /// Write this element's plain text into the given buffer. - fn plain_text(&self, text: &mut EcoString); -} - -/// An error arising when trying to access a field of content. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum FieldAccessError { - Unknown, - Unset, - Internal, -} - -impl FieldAccessError { - /// Formats the error message given the content and the field name. - #[cold] - pub fn message(self, content: &Content, field: &str) -> EcoString { - let elem_name = content.elem().name(); - match self { - FieldAccessError::Unknown => { - eco_format!("{elem_name} does not have field {}", field.repr()) - } - FieldAccessError::Unset => { - eco_format!( - "field {} in {elem_name} is not known at this point", - field.repr() - ) - } - FieldAccessError::Internal => { - eco_format!( - "internal error when accessing field {} in {elem_name} – this is a bug", - field.repr() - ) - } - } - } - - /// Formats the error message for an `at` calls without a default value. - #[cold] - pub fn message_no_default(self, content: &Content, field: &str) -> EcoString { - let mut msg = self.message(content, field); - msg.push_str(" and no default was specified"); - msg +impl<T: NativeElement> IntoValue for T { + fn into_value(self) -> Value { + Value::Content(self.pack()) } } diff --git a/crates/typst-library/src/foundations/content/packed.rs b/crates/typst-library/src/foundations/content/packed.rs new file mode 100644 index 00000000..71bb66a9 --- /dev/null +++ b/crates/typst-library/src/foundations/content/packed.rs @@ -0,0 +1,147 @@ +use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; + +use typst_syntax::Span; + +use crate::foundations::{Content, Label, NativeElement}; +use crate::introspection::Location; + +/// A packed element of a static type. +#[derive(Clone)] +#[repr(transparent)] +pub struct Packed<T: NativeElement>( + /// Invariant: Must be of type `T`. + Content, + PhantomData<T>, +); + +impl<T: NativeElement> Packed<T> { + /// Pack element while retaining its static type. + pub fn new(element: T) -> Self { + // Safety: The element is known to be of type `T`. + Packed(element.pack(), PhantomData) + } + + /// Try to cast type-erased content into a statically known packed element. + pub fn from_ref(content: &Content) -> Option<&Self> { + if content.is::<T>() { + // Safety: + // - We have checked the type. + // - Packed<T> is repr(transparent). + return Some(unsafe { std::mem::transmute::<&Content, &Packed<T>>(content) }); + } + None + } + + /// Try to cast type-erased content into a statically known packed element. + pub fn from_mut(content: &mut Content) -> Option<&mut Self> { + if content.is::<T>() { + // Safety: + // - We have checked the type. + // - Packed<T> is repr(transparent). + return Some(unsafe { + std::mem::transmute::<&mut Content, &mut Packed<T>>(content) + }); + } + None + } + + /// Try to cast type-erased content into a statically known packed element. + pub fn from_owned(content: Content) -> Result<Self, Content> { + if content.is::<T>() { + // Safety: + // - We have checked the type. + // - Packed<T> is repr(transparent). + return Ok(unsafe { std::mem::transmute::<Content, Packed<T>>(content) }); + } + Err(content) + } + + /// Pack back into content. + pub fn pack(self) -> Content { + self.0 + } + + /// Extract the raw underlying element. + pub fn unpack(self) -> T { + // This function doesn't yet need owned self, but might in the future. + (*self).clone() + } + + /// The element's span. + pub fn span(&self) -> Span { + self.0.span() + } + + /// Set the span of the element. + pub fn spanned(self, span: Span) -> Self { + Self(self.0.spanned(span), PhantomData) + } + + /// Accesses the label of the element. + pub fn label(&self) -> Option<Label> { + self.0.label() + } + + /// Accesses the location of the element. + pub fn location(&self) -> Option<Location> { + self.0.location() + } + + /// Sets the location of the element. + pub fn set_location(&mut self, location: Location) { + self.0.set_location(location); + } + + pub fn as_content(&self) -> &Content { + &self.0 + } +} + +impl<T: NativeElement> AsRef<T> for Packed<T> { + fn as_ref(&self) -> &T { + self + } +} + +impl<T: NativeElement> AsMut<T> for Packed<T> { + fn as_mut(&mut self) -> &mut T { + self + } +} + +impl<T: NativeElement> Deref for Packed<T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // Safety: Packed<T> guarantees that the content is of element type `T`. + unsafe { (self.0).0.data::<T>() } + } +} + +impl<T: NativeElement> DerefMut for Packed<T> { + fn deref_mut(&mut self) -> &mut Self::Target { + // Safety: Packed<T> guarantees that the content is of element type `T`. + unsafe { (self.0).0.data_mut::<T>() } + } +} + +impl<T: NativeElement + Debug> Debug for Packed<T> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<T: NativeElement> PartialEq for Packed<T> { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl<T: NativeElement> Hash for Packed<T> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.0.hash(state); + } +} diff --git a/crates/typst-library/src/foundations/content/raw.rs b/crates/typst-library/src/foundations/content/raw.rs new file mode 100644 index 00000000..f5dfffd7 --- /dev/null +++ b/crates/typst-library/src/foundations/content/raw.rs @@ -0,0 +1,426 @@ +use std::any::TypeId; +use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; +use std::ptr::NonNull; +use std::sync::atomic::{self, AtomicUsize, Ordering}; + +use typst_syntax::Span; +use typst_utils::{fat, HashLock, SmallBitSet}; + +use super::vtable; +use crate::foundations::{Element, Label, NativeElement, Packed}; +use crate::introspection::Location; + +/// The raw, low-level implementation of content. +/// +/// The `ptr` + `elem` fields implement a fat pointer setup similar to an +/// `Arc<Inner<dyn Trait>>`, but in a manual way, allowing us to have a custom +/// [vtable]. +pub struct RawContent { + /// A type-erased pointer to an allocation containing two things: + /// - A header that is the same for all elements + /// - Element-specific `data` that holds the specific element + /// + /// This pointer is valid for both a `Header` and an `Inner<E>` where + /// `E::ELEM == self.elem` and can be freely cast between both. This is + /// possible because + /// - `Inner<E>` is `repr(C)` + /// - The first field of `Inner<E>` is `Header` + /// - ISO/IEC 9899:TC2 C standard § 6.7.2.1 - 13 states that a pointer to a + /// structure "points to its initial member" with no padding at the start + ptr: NonNull<Header>, + /// Describes which kind of element this content holds. This is used for + /// + /// - Direct comparisons, e.g. `is::<HeadingElem>()` + /// - Behavior: An `Element` is just a pointer to a `ContentVtable` + /// containing not just data, but also function pointers for various + /// element-specific operations that can be performed + /// + /// It is absolutely crucial that `elem == <E as NativeElement>::ELEM` for + /// `Inner<E>` pointed to by `ptr`. Otherwise, things will go very wrong + /// since we'd be using the wrong vtable. + elem: Element, + /// The content's span. + span: Span, +} + +/// The allocated part of an element's representation. +/// +/// This is `repr(C)` to ensure that a pointer to the whole structure may be +/// cast to a pointer to its first field. +#[repr(C)] +struct Inner<E> { + /// It is crucial that this is the first field because we cast between + /// pointers to `Inner<E>` and pointers to `Header`. See the documentation + /// of `RawContent::ptr` for more details. + header: Header, + /// The element struct. E.g. `E = HeadingElem`. + data: E, +} + +/// The header that is shared by all elements. +struct Header { + /// The element's reference count. This works just like for `Arc`. + /// Unfortunately, we have to reimplement reference counting because we + /// have a custom fat pointer and `Arc` wouldn't know how to drop its + /// contents. Something with `ManuallyDrop<Arc<_>>` might also work, but at + /// that point we're not gaining much and with the way it's implemented now + /// we can also skip the unnecessary weak reference count. + refs: AtomicUsize, + /// Metadata for the element. + meta: Meta, + /// A cell for memoizing the hash of just the `data` part of the content. + hash: HashLock, +} + +/// Metadata that elements can hold. +#[derive(Clone, Hash)] +pub(super) struct Meta { + /// An optional label attached to the element. + pub label: Option<Label>, + /// The element's location which identifies it in the laid-out output. + pub location: Option<Location>, + /// Manages the element during realization. + /// - If bit 0 is set, the element is prepared. + /// - If bit n is set, the element is guarded against the n-th show rule + /// recipe from the top of the style chain (counting from 1). + pub lifecycle: SmallBitSet, +} + +impl RawContent { + /// Creates raw content wrapping an element, with all metadata set to + /// default (including a detached span). + pub(super) fn new<E: NativeElement>(data: E) -> Self { + Self::create( + data, + Meta { + label: None, + location: None, + lifecycle: SmallBitSet::new(), + }, + HashLock::new(), + Span::detached(), + ) + } + + /// Creates and allocates raw content. + fn create<E: NativeElement>(data: E, meta: Meta, hash: HashLock, span: Span) -> Self { + let raw = Box::into_raw(Box::<Inner<E>>::new(Inner { + header: Header { refs: AtomicUsize::new(1), meta, hash }, + data, + })); + + // Safety: `Box` always holds a non-null pointer. See also + // `Box::into_non_null` (which is unstable). + let non_null = unsafe { NonNull::new_unchecked(raw) }; + + // Safety: See `RawContent::ptr`. + let ptr = non_null.cast::<Header>(); + + Self { ptr, elem: E::ELEM, span } + } + + /// Destroys raw content and deallocates. + /// + /// # Safety + /// - The reference count must be zero. + /// - The raw content must be be of type `E`. + pub(super) unsafe fn drop_impl<E: NativeElement>(&mut self) { + debug_assert_eq!(self.header().refs.load(Ordering::Relaxed), 0); + + // Safety: + // - The caller guarantees that the content is of type `E`. + // - Thus, `ptr` must have been created from `Box<Inner<E>>` (see + // `RawContent::ptr`). + // - And to clean it up, we can just reproduce our box. + unsafe { + let ptr = self.ptr.cast::<Inner<E>>(); + drop(Box::<Inner<E>>::from_raw(ptr.as_ptr())); + } + } + + /// Clones a packed element into new raw content. + pub(super) fn clone_impl<E: NativeElement>(elem: &Packed<E>) -> Self { + let raw = &elem.as_content().0; + let header = raw.header(); + RawContent::create( + elem.as_ref().clone(), + header.meta.clone(), + header.hash.clone(), + raw.span, + ) + } + + /// Accesses the header part of the raw content. + fn header(&self) -> &Header { + // Safety: `self.ptr` is a valid pointer to a header structure. + unsafe { self.ptr.as_ref() } + } + + /// Mutably accesses the header part of the raw content. + fn header_mut(&mut self) -> &mut Header { + self.make_unique(); + + // Safety: + // - `self.ptr` is a valid pointer to a header structure. + // - We have unique access to the backing allocation (just ensured). + unsafe { self.ptr.as_mut() } + } + + /// Retrieves the contained element **without checking that the content is + /// of the correct type.** + /// + /// # Safety + /// This must be preceded by a check to [`is`]. The safe API for this is + /// [`Content::to_packed`] and the [`Packed`] struct. + pub(super) unsafe fn data<E: NativeElement>(&self) -> &E { + debug_assert!(self.is::<E>()); + + // Safety: + // - The caller guarantees that the content is of type `E`. + // - `self.ptr` is a valid pointer to an `Inner<E>` (see + // `RawContent::ptr`). + unsafe { &self.ptr.cast::<Inner<E>>().as_ref().data } + } + + /// Retrieves the contained element mutably **without checking that the + /// content is of the correct type.** + /// + /// Ensures that the element's allocation is unique. + /// + /// # Safety + /// This must be preceded by a check to [`is`]. The safe API for this is + /// [`Content::to_packed_mut`] and the [`Packed`] struct. + pub(super) unsafe fn data_mut<E: NativeElement>(&mut self) -> &mut E { + debug_assert!(self.is::<E>()); + + // Ensure that the memoized hash is reset because we may mutate the + // element. + self.header_mut().hash.reset(); + + // Safety: + // - The caller guarantees that the content is of type `E`. + // - `self.ptr` is a valid pointer to an `Inner<E>` (see + // `RawContent::ptr`). + // - We have unique access to the backing allocation (due to header_mut). + unsafe { &mut self.ptr.cast::<Inner<E>>().as_mut().data } + } + + /// Ensures that we have unique access to the backing allocation by cloning + /// if the reference count exceeds 1. This is used before performing + /// mutable operations, implementing a clone-on-write scheme. + fn make_unique(&mut self) { + if self.header().refs.load(Ordering::Relaxed) > 1 { + *self = self.handle().clone(); + } + } + + /// Retrieves the element this content is for. + pub(super) fn elem(&self) -> Element { + self.elem + } + + /// Whether this content holds an element of type `E`. + pub(super) fn is<E: NativeElement>(&self) -> bool { + self.elem == E::ELEM + } + + /// Retrieves the content's span. + pub(super) fn span(&self) -> Span { + self.span + } + + /// Retrieves the content's span mutably. + pub(super) fn span_mut(&mut self) -> &mut Span { + &mut self.span + } + + /// Retrieves the content's metadata. + pub(super) fn meta(&self) -> &Meta { + &self.header().meta + } + + /// Retrieves the content's metadata mutably. + pub(super) fn meta_mut(&mut self) -> &mut Meta { + &mut self.header_mut().meta + } + + /// Casts into a trait object for a given trait if the packed element + /// implements said trait. + pub(super) fn with<C>(&self) -> Option<&C> + where + C: ?Sized + 'static, + { + // Safety: The vtable comes from the `Capable` implementation which + // guarantees to return a matching vtable for `Packed<T>` and `C`. Since + // any `Packed<T>` is repr(transparent) with `Content` and `RawContent`, + // we can also use a `*const RawContent` pointer. + let vtable = (self.elem.vtable().capability)(TypeId::of::<C>())?; + let data = self as *const Self as *const (); + Some(unsafe { &*fat::from_raw_parts(data, vtable.as_ptr()) }) + } + + /// Casts into a mutable trait object for a given trait if the packed + /// element implements said trait. + pub(super) fn with_mut<C>(&mut self) -> Option<&mut C> + where + C: ?Sized + 'static, + { + // Safety: The vtable comes from the `Capable` implementation which + // guarantees to return a matching vtable for `Packed<T>` and `C`. Since + // any `Packed<T>` is repr(transparent) with `Content` and `RawContent`, + // we can also use a `*const Content` pointer. + // + // The resulting trait object contains an `&mut Packed<T>`. We do _not_ + // need to ensure that we hold the only reference to the `Arc` here + // because `Packed<T>`'s DerefMut impl will take care of that if mutable + // access is required. + let vtable = (self.elem.vtable().capability)(TypeId::of::<C>())?; + let data = self as *mut Self as *mut (); + Some(unsafe { &mut *fat::from_raw_parts_mut(data, vtable.as_ptr()) }) + } +} + +impl RawContent { + /// Retrieves the element's vtable. + pub(super) fn handle(&self) -> vtable::ContentHandle<&RawContent> { + // Safety `self.elem.vtable()` is a matching vtable for `self`. + unsafe { vtable::Handle::new(self, self.elem.vtable()) } + } + + /// Retrieves the element's vtable. + pub(super) fn handle_mut(&mut self) -> vtable::ContentHandle<&mut RawContent> { + // Safety `self.elem.vtable()` is a matching vtable for `self`. + unsafe { vtable::Handle::new(self, self.elem.vtable()) } + } + + /// Retrieves the element's vtable. + pub(super) fn handle_pair<'a, 'b>( + &'a self, + other: &'b RawContent, + ) -> Option<vtable::ContentHandle<(&'a RawContent, &'b RawContent)>> { + (self.elem == other.elem).then(|| { + // Safety: + // - `self.elem.vtable()` is a matching vtable for `self`. + // - It's also matching for `other` because `self.elem == other.elem`. + unsafe { vtable::Handle::new((self, other), self.elem.vtable()) } + }) + } +} + +impl Debug for RawContent { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.handle().debug(f) + } +} + +impl Clone for RawContent { + fn clone(&self) -> Self { + // See Arc's clone impl for details about memory ordering. + let prev = self.header().refs.fetch_add(1, Ordering::Relaxed); + + // See Arc's clone impl details about guarding against incredibly + // degenerate programs. + if prev > isize::MAX as usize { + ref_count_overflow(self.ptr, self.elem, self.span); + } + + Self { ptr: self.ptr, elem: self.elem, span: self.span } + } +} + +impl Drop for RawContent { + fn drop(&mut self) { + // Drop our ref-count. If there was more than one content before + // (including this one), we shouldn't deallocate. See Arc's drop impl + // for details about memory ordering. + if self.header().refs.fetch_sub(1, Ordering::Release) != 1 { + return; + } + + // See Arc's drop impl for details. + atomic::fence(Ordering::Acquire); + + // Safety: + // No other content references the backing allocation (just checked) + unsafe { + self.handle_mut().drop(); + } + } +} + +impl PartialEq for RawContent { + fn eq(&self, other: &Self) -> bool { + let Some(handle) = self.handle_pair(other) else { return false }; + handle + .eq() + .unwrap_or_else(|| handle.fields().all(|handle| handle.eq())) + } +} + +impl Hash for RawContent { + fn hash<H: Hasher>(&self, state: &mut H) { + self.elem.hash(state); + let header = self.header(); + header.meta.hash(state); + header.hash.get_or_insert_with(|| self.handle().hash()).hash(state); + self.span.hash(state); + } +} + +// Safety: +// - Works like `Arc`. +// - `NativeElement` implies `Send` and `Sync`, see below. +unsafe impl Sync for RawContent {} +unsafe impl Send for RawContent {} + +fn _ensure_send_sync<T: NativeElement>() { + fn needs_send_sync<T: Send + Sync>() {} + needs_send_sync::<T>(); +} + +#[cold] +fn ref_count_overflow(ptr: NonNull<Header>, elem: Element, span: Span) -> ! { + // Drop to decrement the ref count to counter the increment in `clone()` + drop(RawContent { ptr, elem, span }); + panic!("reference count overflow"); +} + +#[cfg(test)] +mod tests { + use crate::foundations::{NativeElement, Repr, StyleChain, Value}; + use crate::introspection::Location; + use crate::model::HeadingElem; + use crate::text::TextElem; + + #[test] + fn test_miri() { + let styles = StyleChain::default(); + + let mut first = HeadingElem::new(TextElem::packed("Hi!")).with_offset(2).pack(); + let hash1 = typst_utils::hash128(&first); + first.set_location(Location::new(10)); + let _ = format!("{first:?}"); + let _ = first.repr(); + + assert!(first.is::<HeadingElem>()); + assert!(!first.is::<TextElem>()); + assert_eq!(first.to_packed::<TextElem>(), None); + assert_eq!(first.location(), Some(Location::new(10))); + assert_eq!(first.field_by_name("offset"), Ok(Value::Int(2))); + assert!(!first.has("depth".into())); + + let second = first.clone(); + first.materialize(styles); + + let first_packed = first.to_packed::<HeadingElem>().unwrap(); + let second_packed = second.to_packed::<HeadingElem>().unwrap(); + + assert!(first.has("depth".into())); + assert!(!second.has("depth".into())); + assert!(first_packed.depth.is_set()); + assert!(!second_packed.depth.is_set()); + assert_ne!(first, second); + assert_ne!(hash1, typst_utils::hash128(&first)); + } +} diff --git a/crates/typst-library/src/foundations/content/vtable.rs b/crates/typst-library/src/foundations/content/vtable.rs new file mode 100644 index 00000000..fdc9d423 --- /dev/null +++ b/crates/typst-library/src/foundations/content/vtable.rs @@ -0,0 +1,383 @@ +//! A custom [vtable] implementation for content. +//! +//! This is similar to what is generated by the Rust compiler under the hood +//! when using trait objects. However, ours has two key advantages: +//! +//! - It can store a _slice_ of sub-vtables for field-specific operations. +//! - It can store not only methods, but also plain data, allowing us to access +//! that data without going through dynamic dispatch. +//! +//! Because our vtable pointers are backed by `static` variables, we can also +//! perform checks for element types by comparing raw vtable pointers giving us +//! `RawContent::is` without dynamic dispatch. +//! +//! Overall, the custom vtable gives us just a little more flexibility and +//! optimizability than using built-in trait objects. +//! +//! Note that all vtable methods receive elements of type `Packed<E>`, but some +//! only perform actions on the `E` itself, with the shared part kept outside of +//! the vtable (e.g. `hash`), while some perform the full action (e.g. `clone` +//! as it needs to return new, fully populated raw content). Which one it is, is +//! documented for each. +//! +//! # Safety +//! This module contains a lot of `unsafe` keywords, but almost all of it is the +//! same and quite straightfoward. All function pointers that operate on a +//! specific element type are marked as unsafe. In combination with `repr(C)`, +//! this grants us the ability to safely transmute a `ContentVtable<Packed<E>>` +//! into a `ContentVtable<RawContent>` (or just short `ContentVtable`). Callers +//! of functions marked as unsafe have to guarantee that the `ContentVtable` was +//! transmuted from the same `E` as the RawContent was constructed from. The +//! `Handle` struct provides a safe access layer, moving the guarantee that the +//! vtable is matching into a single spot. +//! +//! [vtable]: https://en.wikipedia.org/wiki/Virtual_method_table + +use std::any::TypeId; +use std::fmt::{self, Debug, Formatter}; +use std::ops::Deref; +use std::ptr::NonNull; + +use ecow::EcoString; + +use super::raw::RawContent; +use crate::diag::SourceResult; +use crate::engine::Engine; +use crate::foundations::{ + Args, CastInfo, Construct, Content, LazyElementStore, NativeElement, NativeScope, + Packed, Repr, Scope, Set, StyleChain, Styles, Value, +}; +use crate::text::{Lang, LocalName, Region}; + +/// Encapsulates content and a vtable, granting safe access to vtable operations. +pub(super) struct Handle<T, V: 'static>(T, &'static V); + +impl<T, V> Handle<T, V> { + /// Produces a new handle from content and a vtable. + /// + /// # Safety + /// The content and vtable must be matching, i.e. `vtable` must be derived + /// from the content's vtable. + pub(super) unsafe fn new(content: T, vtable: &'static V) -> Self { + Self(content, vtable) + } +} + +impl<T, V> Deref for Handle<T, V> { + type Target = V; + + fn deref(&self) -> &Self::Target { + self.1 + } +} + +pub(super) type ContentHandle<T> = Handle<T, ContentVtable>; +pub(super) type FieldHandle<T> = Handle<T, FieldVtable>; + +/// A vtable for performing element-specific actions on type-erased content. +/// Also contains general metadata for the specific element. +#[repr(C)] +pub struct ContentVtable<T: 'static = RawContent> { + /// The element's normal name, as in code. + pub(super) name: &'static str, + /// The element's title-cased name. + pub(super) title: &'static str, + /// The element's documentation (as Markdown). + pub(super) docs: &'static str, + /// Search keywords for the documentation. + pub(super) keywords: &'static [&'static str], + + /// Subvtables for all fields of the element. + pub(super) fields: &'static [FieldVtable<T>], + /// Determines the ID for a field name. This is a separate function instead + /// of searching through `fields` so that Rust can generate optimized code + /// for the string matching. + pub(super) field_id: fn(name: &str) -> Option<u8>, + + /// The constructor of the element. + pub(super) construct: fn(&mut Engine, &mut Args) -> SourceResult<Content>, + /// The set rule of the element. + pub(super) set: fn(&mut Engine, &mut Args) -> SourceResult<Styles>, + /// The element's local name in a specific lang-region pairing. + pub(super) local_name: Option<fn(Lang, Option<Region>) -> &'static str>, + /// Produces the associated [`Scope`] of the element. + pub(super) scope: fn() -> Scope, + /// If the `capability` function returns `Some(p)`, then `p` must be a valid + /// pointer to a native Rust vtable of `Packed<Self>` w.r.t to the trait `C` + /// where `capability` is `TypeId::of::<dyn C>()`. + pub(super) capability: fn(capability: TypeId) -> Option<NonNull<()>>, + + /// The `Drop` impl (for the whole raw content). The content must have a + /// reference count of zero and may not be used anymore after `drop` was + /// called. + pub(super) drop: unsafe fn(&mut RawContent), + /// The `Clone` impl (for the whole raw content). + pub(super) clone: unsafe fn(&T) -> RawContent, + /// The `Hash` impl (for just the element). + pub(super) hash: unsafe fn(&T) -> u128, + /// The `Debug` impl (for just the element). + pub(super) debug: unsafe fn(&T, &mut Formatter) -> fmt::Result, + /// The `PartialEq` impl (for just the element). If this is `None`, + /// field-wise equality checks (via `FieldVtable`) should be performed. + pub(super) eq: Option<unsafe fn(&T, &T) -> bool>, + /// The `Repr` impl (for just the element). If this is `None`, a generic + /// name + fields representation should be produced. + pub(super) repr: Option<unsafe fn(&T) -> EcoString>, + + /// Produces a reference to a `static` variable holding a `LazyElementStore` + /// that is unique for this element and can be populated with data that is + /// somewhat costly to initialize at runtime and shouldn't be initialized + /// over and over again. Must be a function rather than a direct reference + /// so that we can store the vtable in a `const` without Rust complaining + /// about the presence of interior mutability. + pub(super) store: fn() -> &'static LazyElementStore, +} + +impl ContentVtable { + /// Creates the vtable for an element. + pub const fn new<E: NativeElement>( + name: &'static str, + title: &'static str, + docs: &'static str, + fields: &'static [FieldVtable<Packed<E>>], + field_id: fn(name: &str) -> Option<u8>, + capability: fn(TypeId) -> Option<NonNull<()>>, + store: fn() -> &'static LazyElementStore, + ) -> ContentVtable<Packed<E>> { + ContentVtable { + name, + title, + docs, + keywords: &[], + fields, + field_id, + construct: <E as Construct>::construct, + set: <E as Set>::set, + local_name: None, + scope: || Scope::new(), + capability, + drop: RawContent::drop_impl::<E>, + clone: RawContent::clone_impl::<E>, + hash: |elem| typst_utils::hash128(elem.as_ref()), + debug: |elem, f| Debug::fmt(elem.as_ref(), f), + eq: None, + repr: None, + store, + } + } + + /// Retrieves the vtable of the element with the given ID. + pub fn field(&self, id: u8) -> Option<&'static FieldVtable> { + self.fields.get(usize::from(id)) + } +} + +impl<E: NativeElement> ContentVtable<Packed<E>> { + /// Attaches search keywords for the documentation. + pub const fn with_keywords(mut self, keywords: &'static [&'static str]) -> Self { + self.keywords = keywords; + self + } + + /// Takes a [`Repr`] impl into account. + pub const fn with_repr(mut self) -> Self + where + E: Repr, + { + self.repr = Some(|e| E::repr(&**e)); + self + } + + /// Takes a [`PartialEq`] impl into account. + pub const fn with_partial_eq(mut self) -> Self + where + E: PartialEq, + { + self.eq = Some(|a, b| E::eq(&**a, &**b)); + self + } + + /// Takes a [`LocalName`] impl into account. + pub const fn with_local_name(mut self) -> Self + where + Packed<E>: LocalName, + { + self.local_name = Some(<Packed<E> as LocalName>::local_name); + self + } + + /// Takes a [`NativeScope`] impl into account. + pub const fn with_scope(mut self) -> Self + where + E: NativeScope, + { + self.scope = || E::scope(); + self + } + + /// Type-erases the data. + pub const fn erase(self) -> ContentVtable { + // Safety: + // - `ContentVtable` is `repr(C)`. + // - `ContentVtable` does not hold any `E`-specific data except for + // function pointers. + // - All functions pointers have the same memory layout. + // - All functions containing `E` are marked as unsafe and callers need + // to uphold the guarantee that they only call them with raw content + // that is of type `E`. + // - `Packed<E>` and `RawContent` have the exact same memory layout + // because of `repr(transparent)`. + unsafe { + std::mem::transmute::<ContentVtable<Packed<E>>, ContentVtable<RawContent>>( + self, + ) + } + } +} + +impl<T> ContentHandle<T> { + /// Provides safe access to operations for the field with the given `id`. + pub(super) fn field(self, id: u8) -> Option<FieldHandle<T>> { + self.fields.get(usize::from(id)).map(|vtable| { + // Safety: Field vtables are of same type as the content vtable. + unsafe { Handle::new(self.0, vtable) } + }) + } + + /// Provides safe access to all field operations. + pub(super) fn fields(self) -> impl Iterator<Item = FieldHandle<T>> + where + T: Copy, + { + self.fields.iter().map(move |vtable| { + // Safety: Field vtables are of same type as the content vtable. + unsafe { Handle::new(self.0, vtable) } + }) + } +} + +impl ContentHandle<&RawContent> { + /// See [`ContentVtable::debug`]. + pub fn debug(&self, f: &mut Formatter) -> fmt::Result { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { (self.1.debug)(self.0, f) } + } + + /// See [`ContentVtable::repr`]. + pub fn repr(&self) -> Option<EcoString> { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { self.1.repr.map(|f| f(self.0)) } + } + + /// See [`ContentVtable::clone`]. + pub fn clone(&self) -> RawContent { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { (self.1.clone)(self.0) } + } + + /// See [`ContentVtable::hash`]. + pub fn hash(&self) -> u128 { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { (self.1.hash)(self.0) } + } +} + +impl ContentHandle<&mut RawContent> { + /// See [`ContentVtable::drop`]. + pub unsafe fn drop(&mut self) { + // Safety: + // - `Handle` has the invariant that the vtable is matching. + // - The caller satifies the requirements of `drop` + unsafe { (self.1.drop)(self.0) } + } +} + +impl ContentHandle<(&RawContent, &RawContent)> { + /// See [`ContentVtable::eq`]. + pub fn eq(&self) -> Option<bool> { + // Safety: `Handle` has the invariant that the vtable is matching. + let (a, b) = self.0; + unsafe { self.1.eq.map(|f| f(a, b)) } + } +} + +/// A vtable for performing field-specific actions on type-erased +/// content. Also contains general metadata for the specific field. +#[repr(C)] +pub struct FieldVtable<T: 'static = RawContent> { + /// The field's name, as in code. + pub(super) name: &'static str, + /// The fields's documentation (as Markdown). + pub(super) docs: &'static str, + + /// Whether the field's parameter is positional. + pub(super) positional: bool, + /// Whether the field's parameter is variadic. + pub(super) variadic: bool, + /// Whether the field's parameter is required. + pub(super) required: bool, + /// Whether the field can be set via a set rule. + pub(super) settable: bool, + /// Whether the field is synthesized (i.e. initially not present). + pub(super) synthesized: bool, + /// Reflects what types the field's parameter accepts. + pub(super) input: fn() -> CastInfo, + /// Produces the default value of the field, if any. This would e.g. be + /// `None` for a required parameter. + pub(super) default: Option<fn() -> Value>, + + /// Whether the field is set on the given element. Always true for required + /// fields, but can be false for settable or synthesized fields. + pub(super) has: unsafe fn(elem: &T) -> bool, + /// Retrieves the field and [turns it into a + /// value](crate::foundations::IntoValue). + pub(super) get: unsafe fn(elem: &T) -> Option<Value>, + /// Retrieves the field given styles. The resulting value may come from the + /// element, the style chain, or a mix (if it's a + /// [`Fold`](crate::foundations::Fold) field). + pub(super) get_with_styles: unsafe fn(elem: &T, StyleChain) -> Option<Value>, + /// Retrieves the field just from the styles. + pub(super) get_from_styles: fn(StyleChain) -> Option<Value>, + /// Sets the field from the styles if it is currently unset. (Or merges + /// with the style data in case of a `Fold` field). + pub(super) materialize: unsafe fn(elem: &mut T, styles: StyleChain), + /// Compares the field for equality. + pub(super) eq: unsafe fn(a: &T, b: &T) -> bool, +} + +impl FieldHandle<&RawContent> { + /// See [`FieldVtable::has`]. + pub fn has(&self) -> bool { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { (self.1.has)(self.0) } + } + + /// See [`FieldVtable::get`]. + pub fn get(&self) -> Option<Value> { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { (self.1.get)(self.0) } + } + + /// See [`FieldVtable::get_with_styles`]. + pub fn get_with_styles(&self, styles: StyleChain) -> Option<Value> { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { (self.1.get_with_styles)(self.0, styles) } + } +} + +impl FieldHandle<&mut RawContent> { + /// See [`FieldVtable::materialize`]. + pub fn materialize(&mut self, styles: StyleChain) { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { (self.1.materialize)(self.0, styles) } + } +} + +impl FieldHandle<(&RawContent, &RawContent)> { + /// See [`FieldVtable::eq`]. + pub fn eq(&self) -> bool { + // Safety: `Handle` has the invariant that the vtable is matching. + let (a, b) = self.0; + unsafe { (self.1.eq)(a, b) } + } +} diff --git a/crates/typst-library/src/foundations/mod.rs b/crates/typst-library/src/foundations/mod.rs index 6840f855..382beb2c 100644 --- a/crates/typst-library/src/foundations/mod.rs +++ b/crates/typst-library/src/foundations/mod.rs @@ -17,7 +17,6 @@ mod datetime; mod decimal; mod dict; mod duration; -mod element; mod fields; mod float; mod func; @@ -49,7 +48,6 @@ pub use self::datetime::*; pub use self::decimal::*; pub use self::dict::*; pub use self::duration::*; -pub use self::element::*; pub use self::fields::*; pub use self::float::*; pub use self::func::*; diff --git a/crates/typst-library/src/foundations/scope.rs b/crates/typst-library/src/foundations/scope.rs index e1ce61b8..838584cc 100644 --- a/crates/typst-library/src/foundations/scope.rs +++ b/crates/typst-library/src/foundations/scope.rs @@ -8,8 +8,7 @@ use typst_syntax::Span; use crate::diag::{bail, DeprecationSink, HintedStrResult, HintedString, StrResult}; use crate::foundations::{ - Element, Func, IntoValue, NativeElement, NativeFunc, NativeFuncData, NativeType, - Type, Value, + Func, IntoValue, NativeElement, NativeFunc, NativeFuncData, NativeType, Value, }; use crate::{Category, Library}; @@ -149,15 +148,15 @@ impl Scope { /// Define a native type. #[track_caller] pub fn define_type<T: NativeType>(&mut self) -> &mut Binding { - let data = T::data(); - self.define(data.name, Type::from(data)) + let ty = T::ty(); + self.define(ty.short_name(), ty) } /// Define a native element. #[track_caller] pub fn define_elem<T: NativeElement>(&mut self) -> &mut Binding { - let data = T::data(); - self.define(data.name, Element::from(data)) + let elem = T::ELEM; + self.define(elem.name(), elem) } /// Define a built-in with compile-time known name and returns a mutable diff --git a/crates/typst-library/src/foundations/selector.rs b/crates/typst-library/src/foundations/selector.rs index bf5449d9..8f264a81 100644 --- a/crates/typst-library/src/foundations/selector.rs +++ b/crates/typst-library/src/foundations/selector.rs @@ -21,12 +21,12 @@ macro_rules! __select_where { let mut fields = ::smallvec::SmallVec::new(); $( fields.push(( - <$ty as $crate::foundations::Fields>::Enum::$field as u8, + <$ty>::$field.index(), $crate::foundations::IntoValue::into_value($value), )); )* $crate::foundations::Selector::Elem( - <$ty as $crate::foundations::NativeElement>::elem(), + <$ty as $crate::foundations::NativeElement>::ELEM, Some(fields), ) }}; diff --git a/crates/typst-library/src/foundations/styles.rs b/crates/typst-library/src/foundations/styles.rs index d124f2c8..978b47d5 100644 --- a/crates/typst-library/src/foundations/styles.rs +++ b/crates/typst-library/src/foundations/styles.rs @@ -12,8 +12,8 @@ use typst_utils::LazyHash; use crate::diag::{SourceResult, Trace, Tracepoint}; use crate::engine::Engine; use crate::foundations::{ - cast, ty, Content, Context, Element, Func, NativeElement, OneOrMultiple, Repr, - Selector, + cast, ty, Content, Context, Element, Field, Func, NativeElement, OneOrMultiple, + RefableProperty, Repr, Selector, SettableProperty, }; use crate::text::{FontFamily, FontList, TextElem}; @@ -48,7 +48,16 @@ impl Styles { /// If the property needs folding and the value is already contained in the /// style map, `self` contributes the outer values and `value` is the inner /// one. - pub fn set(&mut self, style: impl Into<Style>) { + pub fn set<E, const I: u8>(&mut self, field: Field<E, I>, value: E::Type) + where + E: SettableProperty<I>, + E::Type: Debug + Clone + Hash + Send + Sync + 'static, + { + self.push(Property::new(field, value)); + } + + /// Add a new style to the list. + pub fn push(&mut self, style: impl Into<Style>) { self.0.push(LazyHash::new(style.into())); } @@ -101,22 +110,25 @@ impl Styles { } /// Whether there is a style for the given field of the given element. - pub fn has<T: NativeElement>(&self, field: u8) -> bool { - let elem = T::elem(); + pub fn has<E: NativeElement, const I: u8>(&self, _: Field<E, I>) -> bool { + let elem = E::ELEM; self.0 .iter() .filter_map(|style| style.property()) - .any(|property| property.is_of(elem) && property.id == field) + .any(|property| property.is_of(elem) && property.id == I) } /// Set a font family composed of a preferred family and existing families /// from a style chain. pub fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) { - self.set(TextElem::set_font(FontList( - std::iter::once(preferred) - .chain(TextElem::font_in(existing).into_iter().cloned()) - .collect(), - ))); + self.set( + TextElem::font, + FontList( + std::iter::once(preferred) + .chain(existing.get_ref(TextElem::font).into_iter().cloned()) + .collect(), + ), + ); } } @@ -281,14 +293,14 @@ pub struct Property { impl Property { /// Create a new property from a key-value pair. - pub fn new<E, T>(id: u8, value: T) -> Self + pub fn new<E, const I: u8>(_: Field<E, I>, value: E::Type) -> Self where - E: NativeElement, - T: Debug + Clone + Hash + Send + Sync + 'static, + E: SettableProperty<I>, + E::Type: Debug + Clone + Hash + Send + Sync + 'static, { Self { - elem: E::elem(), - id, + elem: E::ELEM, + id: I, value: Block::new(value), span: Span::detached(), liftable: false, @@ -340,8 +352,11 @@ impl Block { } /// Downcasts the block to the specified type. - fn downcast<T: 'static>(&self) -> Option<&T> { - self.0.as_any().downcast_ref() + fn downcast<T: 'static>(&self, func: Element, id: u8) -> &T { + self.0 + .as_any() + .downcast_ref() + .unwrap_or_else(|| block_wrong_type(func, id, self)) } } @@ -528,90 +543,113 @@ impl<'a> StyleChain<'a> { Self { head: &root.0, tail: None } } - /// Make the given chainable the first link of this chain. + /// Retrieves the value of the given field from the style chain. /// - /// The resulting style chain contains styles from `local` as well as - /// `self`. The ones from `local` take precedence over the ones from - /// `self`. For folded properties `local` contributes the inner value. - pub fn chain<'b, C>(&'b self, local: &'b C) -> StyleChain<'b> + /// A `Field` value is a zero-sized value that specifies which field of an + /// element you want to retrieve on the type-system level. It also ensures + /// that Rust can infer the correct return type. + /// + /// Should be preferred over [`get_cloned`](Self::get_cloned) or + /// [`get_ref`](Self::get_ref), but is only available for [`Copy`] types. + /// For other types an explicit decision needs to be made whether cloning is + /// necessary. + pub fn get<E, const I: u8>(self, field: Field<E, I>) -> E::Type where - C: Chainable + ?Sized, + E: SettableProperty<I>, + E::Type: Copy, { - Chainable::chain(local, self) + self.get_cloned(field) } - /// Cast the first value for the given property in the chain. - pub fn get<T: Clone + 'static>( - self, - func: Element, - id: u8, - inherent: Option<&T>, - default: impl Fn() -> T, - ) -> T { - self.properties::<T>(func, id, inherent) - .next() - .cloned() - .unwrap_or_else(default) + /// Retrieves and clones the value from the style chain. + /// + /// Prefer [`get`](Self::get) if the type is `Copy` and + /// [`get_ref`](Self::get_ref) if a reference suffices. + pub fn get_cloned<E, const I: u8>(self, _: Field<E, I>) -> E::Type + where + E: SettableProperty<I>, + { + if let Some(fold) = E::FOLD { + self.get_folded::<E::Type>(E::ELEM, I, fold, E::default()) + } else { + self.get_unfolded::<E::Type>(E::ELEM, I) + .cloned() + .unwrap_or_else(E::default) + } } - /// Cast the first value for the given property in the chain, - /// returning a borrowed value. - pub fn get_ref<T: 'static>( + /// Retrieves a reference to the value of the given field from the style + /// chain. + /// + /// Not possible if the value needs folding. + pub fn get_ref<E, const I: u8>(self, _: Field<E, I>) -> &'a E::Type + where + E: RefableProperty<I>, + { + self.get_unfolded(E::ELEM, I).unwrap_or_else(|| E::default_ref()) + } + + /// Retrieves the value and then immediately [resolves](Resolve) it. + pub fn resolve<E, const I: u8>( self, - func: Element, - id: u8, - inherent: Option<&'a T>, - default: impl Fn() -> &'a T, - ) -> &'a T { - self.properties::<T>(func, id, inherent) - .next() - .unwrap_or_else(default) + field: Field<E, I>, + ) -> <E::Type as Resolve>::Output + where + E: SettableProperty<I>, + E::Type: Resolve, + { + self.get_cloned(field).resolve(self) + } + + /// Retrieves a reference to a field, also taking into account the + /// instance's value if any. + fn get_unfolded<T: 'static>(self, func: Element, id: u8) -> Option<&'a T> { + self.find(func, id).map(|block| block.downcast(func, id)) } - /// Cast the first value for the given property in the chain, taking - /// `Fold` implementations into account. - pub fn get_folded<T: Fold + Clone + 'static>( + /// Retrieves a reference to a field, also taking into account the + /// instance's value if any. + fn get_folded<T: 'static + Clone>( self, func: Element, id: u8, - inherent: Option<&T>, - default: impl Fn() -> T, + fold: fn(T, T) -> T, + default: T, ) -> T { - fn next<T: Fold>( - mut values: impl Iterator<Item = T>, - default: &impl Fn() -> T, - ) -> T { - values - .next() - .map(|value| value.fold(next(values, default))) - .unwrap_or_else(default) + let iter = self + .properties(func, id) + .map(|block| block.downcast::<T>(func, id).clone()); + + if let Some(folded) = iter.reduce(fold) { + fold(folded, default) + } else { + default } - next(self.properties::<T>(func, id, inherent).cloned(), &default) } /// Iterate over all values for the given property in the chain. - fn properties<T: 'static>( - self, - func: Element, - id: u8, - inherent: Option<&'a T>, - ) -> impl Iterator<Item = &'a T> { - inherent.into_iter().chain( - self.entries() - .filter_map(|style| 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 - ) - }) - }), - ) + fn find(self, func: Element, id: u8) -> Option<&'a Block> { + self.properties(func, id).next() + } + + /// Iterate over all values for the given property in the chain. + fn properties(self, func: Element, id: u8) -> impl Iterator<Item = &'a Block> { + self.entries() + .filter_map(|style| style.property()) + .filter(move |property| property.is(func, id)) + .map(|property| &property.value) + } + + /// Make the given chainable the first link of this chain. + /// + /// The resulting style chain contains styles from `local` as well as + /// `self`. The ones from `local` take precedence over the ones from + /// `self`. For folded properties `local` contributes the inner value. + pub fn chain<'b, C>(&'b self, local: &'b C) -> StyleChain<'b> + where + C: Chainable + ?Sized, + { + Chainable::chain(local, self) } /// Iterate over the entries of the chain. @@ -804,6 +842,9 @@ impl<T: Resolve> Resolve for Option<T> { /// #set rect(stroke: 4pt) /// #rect() /// ``` +/// +/// Note: Folding must be associative, i.e. any implementation must satisfy +/// `fold(fold(a, b), c) == fold(a, fold(b, c))`. pub trait Fold { /// Fold this inner value with an outer folded value. fn fold(self, outer: Self) -> Self; @@ -847,6 +888,9 @@ impl<T> Fold for OneOrMultiple<T> { } } +/// A [folding](Fold) function. +pub type FoldFn<T> = fn(T, T) -> T; + /// A variant of fold for foldable optional (`Option<T>`) values where an inner /// `None` value isn't respected (contrary to `Option`'s usual `Fold` /// implementation, with which folding with an inner `None` always returns @@ -884,3 +928,13 @@ impl Fold for Depth { Self(outer.0 + self.0) } } + +#[cold] +fn block_wrong_type(func: Element, id: u8, value: &Block) -> ! { + panic!( + "attempted to read a value of a different type than was written {}.{}: {:?}", + func.name(), + func.field_name(id).unwrap(), + value + ) +} diff --git a/crates/typst-library/src/foundations/target.rs b/crates/typst-library/src/foundations/target.rs index 2a21fd42..71e7554e 100644 --- a/crates/typst-library/src/foundations/target.rs +++ b/crates/typst-library/src/foundations/target.rs @@ -73,5 +73,5 @@ pub struct TargetElem { /// ``` #[func(contextual)] pub fn target(context: Tracked<Context>) -> HintedStrResult<Target> { - Ok(TargetElem::target_in(context.styles()?)) + Ok(context.styles()?.get(TargetElem::target)) } diff --git a/crates/typst-library/src/html/mod.rs b/crates/typst-library/src/html/mod.rs index 7fc8adec..f9835206 100644 --- a/crates/typst-library/src/html/mod.rs +++ b/crates/typst-library/src/html/mod.rs @@ -47,21 +47,22 @@ pub struct HtmlElem { pub tag: HtmlTag, /// The element's HTML attributes. - #[borrowed] pub attrs: HtmlAttrs, /// The contents of the HTML element. /// /// The body can be arbitrary Typst content. #[positional] - #[borrowed] pub body: Option<Content>, } impl HtmlElem { /// Add an attribute to the element. pub fn with_attr(mut self, attr: HtmlAttr, value: impl Into<EcoString>) -> Self { - self.attrs.get_or_insert_with(Default::default).push(attr, value); + self.attrs + .as_option_mut() + .get_or_insert_with(Default::default) + .push(attr, value); self } } diff --git a/crates/typst-library/src/html/typed.rs b/crates/typst-library/src/html/typed.rs index 1e7c1ad6..8240b296 100644 --- a/crates/typst-library/src/html/typed.rs +++ b/crates/typst-library/src/html/typed.rs @@ -127,12 +127,12 @@ fn construct(element: &'static data::ElemInfo, args: &mut Args) -> SourceResult< let tag = HtmlTag::constant(element.name); let mut elem = HtmlElem::new(tag); if !attrs.0.is_empty() { - elem.push_attrs(attrs); + elem.attrs.set(attrs); } if !tag::is_void(tag) { let body = args.eat::<Content>()?; - elem.push_body(body); + elem.body.set(body); } Ok(elem.into_value()) diff --git a/crates/typst-library/src/introspection/counter.rs b/crates/typst-library/src/introspection/counter.rs index 772bea96..a7925e13 100644 --- a/crates/typst-library/src/introspection/counter.rs +++ b/crates/typst-library/src/introspection/counter.rs @@ -338,7 +338,7 @@ impl Counter { /// The selector relevant for this counter's updates. fn selector(&self) -> Selector { - let mut selector = select_where!(CounterUpdateElem, Key => self.0.clone()); + let mut selector = select_where!(CounterUpdateElem, key => self.0.clone()); if let CounterKey::Selector(key) = &self.0 { selector = Selector::Or(eco_vec![selector, key.clone()]); @@ -367,16 +367,16 @@ impl Counter { .or_else(|| { let styles = styles?; match self.0 { - CounterKey::Page => PageElem::numbering_in(styles).clone(), + CounterKey::Page => styles.get_cloned(PageElem::numbering), CounterKey::Selector(Selector::Elem(func, _)) => { - if func == HeadingElem::elem() { - HeadingElem::numbering_in(styles).clone() - } else if func == FigureElem::elem() { - FigureElem::numbering_in(styles).clone() - } else if func == EquationElem::elem() { - EquationElem::numbering_in(styles).clone() - } else if func == FootnoteElem::elem() { - Some(FootnoteElem::numbering_in(styles).clone()) + if func == HeadingElem::ELEM { + styles.get_cloned(HeadingElem::numbering) + } else if func == FigureElem::ELEM { + styles.get_cloned(FigureElem::numbering) + } else if func == EquationElem::ELEM { + styles.get_cloned(EquationElem::numbering) + } else if func == FootnoteElem::ELEM { + Some(styles.get_cloned(FootnoteElem::numbering)) } else { None } @@ -398,7 +398,7 @@ impl Counter { /// Selects all state updates. pub fn select_any() -> Selector { - CounterUpdateElem::elem().select() + CounterUpdateElem::ELEM.select() } } @@ -565,14 +565,14 @@ pub enum CounterKey { cast! { CounterKey, self => match self { - Self::Page => PageElem::elem().into_value(), + Self::Page => PageElem::ELEM.into_value(), Self::Selector(v) => v.into_value(), Self::Str(v) => v.into_value(), }, v: Str => Self::Str(v), v: Label => Self::Selector(Selector::Label(v)), v: Element => { - if v == PageElem::elem() { + if v == PageElem::ELEM { Self::Page } else { Self::Selector(LocatableSelector::from_value(v.into_value())?.0) diff --git a/crates/typst-library/src/introspection/state.rs b/crates/typst-library/src/introspection/state.rs index cc3f566b..784f2acb 100644 --- a/crates/typst-library/src/introspection/state.rs +++ b/crates/typst-library/src/introspection/state.rs @@ -259,12 +259,12 @@ impl State { /// The selector for this state's updates. fn selector(&self) -> Selector { - select_where!(StateUpdateElem, Key => self.key.clone()) + select_where!(StateUpdateElem, key => self.key.clone()) } /// Selects all state updates. pub fn select_any() -> Selector { - StateUpdateElem::elem().select() + StateUpdateElem::ELEM.select() } } diff --git a/crates/typst-library/src/layout/align.rs b/crates/typst-library/src/layout/align.rs index 0a978dba..e5ceddf6 100644 --- a/crates/typst-library/src/layout/align.rs +++ b/crates/typst-library/src/layout/align.rs @@ -100,7 +100,7 @@ pub struct AlignElem { impl Show for Packed<AlignElem> { #[typst_macros::time(name = "align", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - Ok(self.body.clone().aligned(self.alignment(styles))) + Ok(self.body.clone().aligned(self.alignment.get(styles))) } } @@ -277,7 +277,7 @@ impl Resolve for Alignment { type Output = Axes<FixedAlignment>; fn resolve(self, styles: StyleChain) -> Self::Output { - self.fix(TextElem::dir_in(styles)) + self.fix(styles.resolve(TextElem::dir)) } } @@ -378,7 +378,7 @@ impl Resolve for HAlignment { type Output = FixedAlignment; fn resolve(self, styles: StyleChain) -> Self::Output { - self.fix(TextElem::dir_in(styles)) + self.fix(styles.resolve(TextElem::dir)) } } @@ -414,7 +414,7 @@ impl Resolve for OuterHAlignment { type Output = FixedAlignment; fn resolve(self, styles: StyleChain) -> Self::Output { - self.fix(TextElem::dir_in(styles)) + self.fix(styles.resolve(TextElem::dir)) } } @@ -636,7 +636,7 @@ where type Output = Axes<FixedAlignment>; fn resolve(self, styles: StyleChain) -> Self::Output { - self.fix(TextElem::dir_in(styles)) + self.fix(styles.resolve(TextElem::dir)) } } diff --git a/crates/typst-library/src/layout/columns.rs b/crates/typst-library/src/layout/columns.rs index f2f36441..1cea5275 100644 --- a/crates/typst-library/src/layout/columns.rs +++ b/crates/typst-library/src/layout/columns.rs @@ -49,7 +49,6 @@ pub struct ColumnsElem { pub count: NonZeroUsize, /// The size of the gutter space between each column. - #[resolve] #[default(Ratio::new(0.04).into())] pub gutter: Rel<Length>, diff --git a/crates/typst-library/src/layout/container.rs b/crates/typst-library/src/layout/container.rs index fc27644e..cff66fe3 100644 --- a/crates/typst-library/src/layout/container.rs +++ b/crates/typst-library/src/layout/container.rs @@ -51,7 +51,6 @@ pub struct BoxElem { /// ```example /// Image: #box(baseline: 40%, image("tiger.jpg", width: 2cm)). /// ``` - #[resolve] pub baseline: Rel<Length>, /// The box's background color. See the @@ -60,13 +59,11 @@ pub struct BoxElem { /// The box's border color. See the /// [rectangle's documentation]($rect.stroke) for more details. - #[resolve] #[fold] pub stroke: Sides<Option<Option<Stroke>>>, /// How much to round the box's corners. See the /// [rectangle's documentation]($rect.radius) for more details. - #[resolve] #[fold] pub radius: Corners<Option<Rel<Length>>>, @@ -78,7 +75,6 @@ pub struct BoxElem { /// ```example /// #rect(inset: 0pt)[Tight] /// ``` - #[resolve] #[fold] pub inset: Sides<Option<Rel<Length>>>, @@ -97,7 +93,6 @@ pub struct BoxElem { /// radius: 2pt, /// )[rectangle]. /// ``` - #[resolve] #[fold] pub outset: Sides<Option<Rel<Length>>>, @@ -119,7 +114,6 @@ pub struct BoxElem { /// The contents of the box. #[positional] - #[borrowed] pub body: Option<Content>, } @@ -262,25 +256,21 @@ pub struct BlockElem { /// The block's border color. See the /// [rectangle's documentation]($rect.stroke) for more details. - #[resolve] #[fold] pub stroke: Sides<Option<Option<Stroke>>>, /// How much to round the block's corners. See the /// [rectangle's documentation]($rect.radius) for more details. - #[resolve] #[fold] pub radius: Corners<Option<Rel<Length>>>, /// How much to pad the block's content. See the /// [box's documentation]($box.inset) for more details. - #[resolve] #[fold] pub inset: Sides<Option<Rel<Length>>>, /// How much to expand the block's size without affecting the layout. See /// the [box's documentation]($box.outset) for more details. - #[resolve] #[fold] pub outset: Sides<Option<Rel<Length>>>, @@ -358,7 +348,6 @@ pub struct BlockElem { /// The contents of the block. #[positional] - #[borrowed] pub body: Option<BlockBody>, } diff --git a/crates/typst-library/src/layout/em.rs b/crates/typst-library/src/layout/em.rs index 746e1769..460bf5fa 100644 --- a/crates/typst-library/src/layout/em.rs +++ b/crates/typst-library/src/layout/em.rs @@ -167,7 +167,7 @@ impl Resolve for Em { if self.is_zero() { Abs::zero() } else { - self.at(TextElem::size_in(styles)) + self.at(styles.resolve(TextElem::size)) } } } diff --git a/crates/typst-library/src/layout/grid/mod.rs b/crates/typst-library/src/layout/grid/mod.rs index 52621c64..64e7464b 100644 --- a/crates/typst-library/src/layout/grid/mod.rs +++ b/crates/typst-library/src/layout/grid/mod.rs @@ -144,14 +144,12 @@ 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 and columns. @@ -169,12 +167,10 @@ 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. #[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))] - #[borrowed] pub row_gutter: TrackSizings, /// How to fill the cells. @@ -197,7 +193,6 @@ pub struct GridElem { /// [O], [X], [O], [X], /// ) /// ``` - #[borrowed] pub fill: Celled<Option<Paint>>, /// How to align the cells' content. @@ -209,7 +204,6 @@ pub struct GridElem { /// /// You can find an example for this argument at the /// [`table.align`]($table.align) parameter. - #[borrowed] pub align: Celled<Smart<Alignment>>, /// How to [stroke]($stroke) the cells. @@ -289,7 +283,6 @@ pub struct GridElem { /// ), /// ) /// ``` - #[resolve] #[fold] pub stroke: Celled<Sides<Option<Option<Arc<Stroke>>>>>, @@ -541,7 +534,6 @@ pub struct GridHLine { /// /// Specifying `{none}` removes any lines previously placed across this /// line's range, including hlines or per-cell stroke below it. - #[resolve] #[fold] #[default(Some(Arc::new(Stroke::default())))] pub stroke: Option<Arc<Stroke>>, @@ -596,7 +588,6 @@ pub struct GridVLine { /// /// Specifying `{none}` removes any lines previously placed across this /// line's range, including vlines or per-cell stroke below it. - #[resolve] #[fold] #[default(Some(Arc::new(Stroke::default())))] pub stroke: Option<Arc<Stroke>>, @@ -742,7 +733,6 @@ pub struct GridCell { pub inset: Smart<Sides<Option<Rel<Length>>>>, /// The cell's [stroke]($grid.stroke) override. - #[resolve] #[fold] pub stroke: Sides<Option<Option<Arc<Stroke>>>>, @@ -760,7 +750,7 @@ cast! { impl Show for Packed<GridCell> { fn show(&self, _engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - show_grid_cell(self.body.clone(), self.inset(styles), self.align(styles)) + show_grid_cell(self.body.clone(), self.inset.get(styles), self.align.get(styles)) } } diff --git a/crates/typst-library/src/layout/grid/resolve.rs b/crates/typst-library/src/layout/grid/resolve.rs index baf6b738..f3004088 100644 --- a/crates/typst-library/src/layout/grid/resolve.rs +++ b/crates/typst-library/src/layout/grid/resolve.rs @@ -31,14 +31,14 @@ pub fn grid_to_cellgrid<'a>( locator: Locator<'a>, styles: StyleChain, ) -> SourceResult<CellGrid<'a>> { - let inset = elem.inset(styles); - let align = elem.align(styles); - let columns = elem.columns(styles); - let rows = elem.rows(styles); - let column_gutter = elem.column_gutter(styles); - let row_gutter = elem.row_gutter(styles); - let fill = elem.fill(styles); - let stroke = elem.stroke(styles); + let inset = elem.inset.get_cloned(styles); + let align = elem.align.get_ref(styles); + let columns = elem.columns.get_ref(styles); + let rows = elem.rows.get_ref(styles); + let column_gutter = elem.column_gutter.get_ref(styles); + let row_gutter = elem.row_gutter.get_ref(styles); + let fill = elem.fill.get_ref(styles); + let stroke = elem.stroke.resolve(styles); 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()); @@ -47,13 +47,13 @@ pub fn grid_to_cellgrid<'a>( let resolve_item = |item: &GridItem| grid_item_to_resolvable(item, styles); let children = elem.children.iter().map(|child| match child { GridChild::Header(header) => ResolvableGridChild::Header { - repeat: header.repeat(styles), - level: header.level(styles), + repeat: header.repeat.get(styles), + level: header.level.get(styles), span: header.span(), items: header.children.iter().map(resolve_item), }, GridChild::Footer(footer) => ResolvableGridChild::Footer { - repeat: footer.repeat(styles), + repeat: footer.repeat.get(styles), span: footer.span(), items: footer.children.iter().map(resolve_item), }, @@ -85,14 +85,14 @@ pub fn table_to_cellgrid<'a>( locator: Locator<'a>, styles: StyleChain, ) -> SourceResult<CellGrid<'a>> { - let inset = elem.inset(styles); - let align = elem.align(styles); - let columns = elem.columns(styles); - let rows = elem.rows(styles); - let column_gutter = elem.column_gutter(styles); - let row_gutter = elem.row_gutter(styles); - let fill = elem.fill(styles); - let stroke = elem.stroke(styles); + let inset = elem.inset.get_cloned(styles); + let align = elem.align.get_ref(styles); + let columns = elem.columns.get_ref(styles); + let rows = elem.rows.get_ref(styles); + let column_gutter = elem.column_gutter.get_ref(styles); + let row_gutter = elem.row_gutter.get_ref(styles); + let fill = elem.fill.get_ref(styles); + let stroke = elem.stroke.resolve(styles); 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()); @@ -101,13 +101,13 @@ pub fn table_to_cellgrid<'a>( let resolve_item = |item: &TableItem| table_item_to_resolvable(item, styles); let children = elem.children.iter().map(|child| match child { TableChild::Header(header) => ResolvableGridChild::Header { - repeat: header.repeat(styles), - level: header.level(styles), + repeat: header.repeat.get(styles), + level: header.level.get(styles), span: header.span(), items: header.children.iter().map(resolve_item), }, TableChild::Footer(footer) => ResolvableGridChild::Footer { - repeat: footer.repeat(styles), + repeat: footer.repeat.get(styles), span: footer.span(), items: footer.children.iter().map(resolve_item), }, @@ -137,27 +137,27 @@ fn grid_item_to_resolvable( ) -> ResolvableGridItem<Packed<GridCell>> { match item { GridItem::HLine(hline) => ResolvableGridItem::HLine { - y: hline.y(styles), - start: hline.start(styles), - end: hline.end(styles), - stroke: hline.stroke(styles), + y: hline.y.get(styles), + start: hline.start.get(styles), + end: hline.end.get(styles), + stroke: hline.stroke.resolve(styles), span: hline.span(), - position: match hline.position(styles) { + position: match hline.position.get(styles) { OuterVAlignment::Top => LinePosition::Before, OuterVAlignment::Bottom => LinePosition::After, }, }, GridItem::VLine(vline) => ResolvableGridItem::VLine { - x: vline.x(styles), - start: vline.start(styles), - end: vline.end(styles), - stroke: vline.stroke(styles), + x: vline.x.get(styles), + start: vline.start.get(styles), + end: vline.end.get(styles), + stroke: vline.stroke.resolve(styles), span: vline.span(), - position: match vline.position(styles) { - OuterHAlignment::Left if TextElem::dir_in(styles) == Dir::RTL => { + position: match vline.position.get(styles) { + OuterHAlignment::Left if styles.resolve(TextElem::dir) == Dir::RTL => { LinePosition::After } - OuterHAlignment::Right if TextElem::dir_in(styles) == Dir::RTL => { + OuterHAlignment::Right if styles.resolve(TextElem::dir) == Dir::RTL => { LinePosition::Before } OuterHAlignment::Start | OuterHAlignment::Left => LinePosition::Before, @@ -174,27 +174,27 @@ fn table_item_to_resolvable( ) -> ResolvableGridItem<Packed<TableCell>> { match item { TableItem::HLine(hline) => ResolvableGridItem::HLine { - y: hline.y(styles), - start: hline.start(styles), - end: hline.end(styles), - stroke: hline.stroke(styles), + y: hline.y.get(styles), + start: hline.start.get(styles), + end: hline.end.get(styles), + stroke: hline.stroke.resolve(styles), span: hline.span(), - position: match hline.position(styles) { + position: match hline.position.get(styles) { OuterVAlignment::Top => LinePosition::Before, OuterVAlignment::Bottom => LinePosition::After, }, }, TableItem::VLine(vline) => ResolvableGridItem::VLine { - x: vline.x(styles), - start: vline.start(styles), - end: vline.end(styles), - stroke: vline.stroke(styles), + x: vline.x.get(styles), + start: vline.start.get(styles), + end: vline.end.get(styles), + stroke: vline.stroke.resolve(styles), span: vline.span(), - position: match vline.position(styles) { - OuterHAlignment::Left if TextElem::dir_in(styles) == Dir::RTL => { + position: match vline.position.get(styles) { + OuterHAlignment::Left if styles.resolve(TextElem::dir) == Dir::RTL => { LinePosition::After } - OuterHAlignment::Right if TextElem::dir_in(styles) == Dir::RTL => { + OuterHAlignment::Right if styles.resolve(TextElem::dir) == Dir::RTL => { LinePosition::Before } OuterHAlignment::Start | OuterHAlignment::Left => LinePosition::Before, @@ -219,12 +219,12 @@ impl ResolvableCell for Packed<TableCell> { styles: StyleChain, ) -> Cell<'a> { let cell = &mut *self; - let colspan = cell.colspan(styles); - let rowspan = cell.rowspan(styles); - let breakable = cell.breakable(styles).unwrap_or(breakable); - let fill = cell.fill(styles).unwrap_or_else(|| fill.clone()); + let colspan = cell.colspan.get(styles); + let rowspan = cell.rowspan.get(styles); + let breakable = cell.breakable.get(styles).unwrap_or(breakable); + let fill = cell.fill.get_cloned(styles).unwrap_or_else(|| fill.clone()); - let cell_stroke = cell.stroke(styles); + let cell_stroke = cell.stroke.resolve(styles); let stroke_overridden = cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_)))); @@ -238,22 +238,22 @@ impl ResolvableCell for Packed<TableCell> { // cell stroke is the same as specifying 'none', so we equate the two // concepts. let stroke = cell_stroke.fold(stroke).map(Option::flatten); - cell.push_x(Smart::Custom(x)); - cell.push_y(Smart::Custom(y)); - cell.push_fill(Smart::Custom(fill.clone())); - cell.push_align(match align { - Smart::Custom(align) => { - Smart::Custom(cell.align(styles).map_or(align, |inner| inner.fold(align))) - } + cell.x.set(Smart::Custom(x)); + cell.y.set(Smart::Custom(y)); + cell.fill.set(Smart::Custom(fill.clone())); + cell.align.set(match align { + Smart::Custom(align) => Smart::Custom( + cell.align.get(styles).map_or(align, |inner| inner.fold(align)), + ), // Don't fold if the table is using outer alignment. Use the // cell's alignment instead (which, in the end, will fold with // the outer alignment when it is effectively displayed). - Smart::Auto => cell.align(styles), + Smart::Auto => cell.align.get(styles), }); - cell.push_inset(Smart::Custom( - cell.inset(styles).map_or(inset, |inner| inner.fold(inset)), + cell.inset.set(Smart::Custom( + cell.inset.get(styles).map_or(inset, |inner| inner.fold(inset)), )); - cell.push_stroke( + cell.stroke.set( // Here we convert the resolved stroke to a regular stroke, however // with resolved units (that is, 'em' converted to absolute units). // We also convert any stroke unspecified by both the cell and the @@ -266,7 +266,7 @@ impl ResolvableCell for Packed<TableCell> { })) }), ); - cell.push_breakable(Smart::Custom(breakable)); + cell.breakable.set(Smart::Custom(breakable)); Cell { body: self.pack(), locator, @@ -280,19 +280,19 @@ impl ResolvableCell for Packed<TableCell> { } fn x(&self, styles: StyleChain) -> Smart<usize> { - (**self).x(styles) + self.x.get(styles) } fn y(&self, styles: StyleChain) -> Smart<usize> { - (**self).y(styles) + self.y.get(styles) } fn colspan(&self, styles: StyleChain) -> NonZeroUsize { - (**self).colspan(styles) + self.colspan.get(styles) } fn rowspan(&self, styles: StyleChain) -> NonZeroUsize { - (**self).rowspan(styles) + self.rowspan.get(styles) } fn span(&self) -> Span { @@ -314,12 +314,12 @@ impl ResolvableCell for Packed<GridCell> { styles: StyleChain, ) -> Cell<'a> { let cell = &mut *self; - let colspan = cell.colspan(styles); - let rowspan = cell.rowspan(styles); - let breakable = cell.breakable(styles).unwrap_or(breakable); - let fill = cell.fill(styles).unwrap_or_else(|| fill.clone()); + let colspan = cell.colspan.get(styles); + let rowspan = cell.rowspan.get(styles); + let breakable = cell.breakable.get(styles).unwrap_or(breakable); + let fill = cell.fill.get_cloned(styles).unwrap_or_else(|| fill.clone()); - let cell_stroke = cell.stroke(styles); + let cell_stroke = cell.stroke.resolve(styles); let stroke_overridden = cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_)))); @@ -333,22 +333,22 @@ impl ResolvableCell for Packed<GridCell> { // cell stroke is the same as specifying 'none', so we equate the two // concepts. let stroke = cell_stroke.fold(stroke).map(Option::flatten); - cell.push_x(Smart::Custom(x)); - cell.push_y(Smart::Custom(y)); - cell.push_fill(Smart::Custom(fill.clone())); - cell.push_align(match align { - Smart::Custom(align) => { - Smart::Custom(cell.align(styles).map_or(align, |inner| inner.fold(align))) - } + cell.x.set(Smart::Custom(x)); + cell.y.set(Smart::Custom(y)); + cell.fill.set(Smart::Custom(fill.clone())); + cell.align.set(match align { + Smart::Custom(align) => Smart::Custom( + cell.align.get(styles).map_or(align, |inner| inner.fold(align)), + ), // Don't fold if the grid is using outer alignment. Use the // cell's alignment instead (which, in the end, will fold with // the outer alignment when it is effectively displayed). - Smart::Auto => cell.align(styles), + Smart::Auto => cell.align.get(styles), }); - cell.push_inset(Smart::Custom( - cell.inset(styles).map_or(inset, |inner| inner.fold(inset)), + cell.inset.set(Smart::Custom( + cell.inset.get(styles).map_or(inset, |inner| inner.fold(inset)), )); - cell.push_stroke( + cell.stroke.set( // Here we convert the resolved stroke to a regular stroke, however // with resolved units (that is, 'em' converted to absolute units). // We also convert any stroke unspecified by both the cell and the @@ -361,7 +361,7 @@ impl ResolvableCell for Packed<GridCell> { })) }), ); - cell.push_breakable(Smart::Custom(breakable)); + cell.breakable.set(Smart::Custom(breakable)); Cell { body: self.pack(), locator, @@ -375,19 +375,19 @@ impl ResolvableCell for Packed<GridCell> { } fn x(&self, styles: StyleChain) -> Smart<usize> { - (**self).x(styles) + self.x.get(styles) } fn y(&self, styles: StyleChain) -> Smart<usize> { - (**self).y(styles) + self.y.get(styles) } fn colspan(&self, styles: StyleChain) -> NonZeroUsize { - (**self).colspan(styles) + self.colspan.get(styles) } fn rowspan(&self, styles: StyleChain) -> NonZeroUsize { - (**self).rowspan(styles) + self.rowspan.get(styles) } fn span(&self) -> Span { diff --git a/crates/typst-library/src/layout/hide.rs b/crates/typst-library/src/layout/hide.rs index eca33471..5f3a5a2d 100644 --- a/crates/typst-library/src/layout/hide.rs +++ b/crates/typst-library/src/layout/hide.rs @@ -29,6 +29,6 @@ pub struct HideElem { impl Show for Packed<HideElem> { #[typst_macros::time(name = "hide", span = self.span())] fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> { - Ok(self.body.clone().styled(HideElem::set_hidden(true))) + Ok(self.body.clone().set(HideElem::hidden, true)) } } diff --git a/crates/typst-library/src/layout/page.rs b/crates/typst-library/src/layout/page.rs index 98afbd06..a0b1c6fe 100644 --- a/crates/typst-library/src/layout/page.rs +++ b/crates/typst-library/src/layout/page.rs @@ -60,7 +60,6 @@ pub struct PageElem { /// box(square(width: 1cm)) /// } /// ``` - #[resolve] #[parse( let paper = args.named_or_find::<Paper>("paper")?; args.named("width")? @@ -77,7 +76,6 @@ pub struct PageElem { /// page set rule. Most examples throughout this documentation use `{auto}` /// for the height of the page to dynamically grow and shrink to fit their /// content. - #[resolve] #[parse( args.named("height")? .or_else(|| paper.map(|paper| Smart::Custom(paper.height().into()))) @@ -201,7 +199,6 @@ pub struct PageElem { /// #set text(fill: rgb("fdfdfd")) /// *Dark mode enabled.* /// ``` - #[borrowed] #[ghost] pub fill: Smart<Option<Paint>>, @@ -219,7 +216,6 @@ pub struct PageElem { /// /// #lorem(48) /// ``` - #[borrowed] #[ghost] pub numbering: Option<Numbering>, @@ -276,12 +272,10 @@ pub struct PageElem { /// /// #lorem(19) /// ``` - #[borrowed] #[ghost] pub header: Smart<Option<Content>>, /// The amount the header is raised into the top margin. - #[resolve] #[default(Ratio::new(0.3).into())] #[ghost] pub header_ascent: Rel<Length>, @@ -314,12 +308,10 @@ pub struct PageElem { /// /// #lorem(48) /// ``` - #[borrowed] #[ghost] pub footer: Smart<Option<Content>>, /// The amount the footer is lowered into the bottom margin. - #[resolve] #[default(Ratio::new(0.3).into())] #[ghost] pub footer_descent: Rel<Length>, @@ -340,7 +332,6 @@ pub struct PageElem { /// In the year 2023, we plan to take /// over the world (of typesetting). /// ``` - #[borrowed] #[ghost] pub background: Option<Content>, @@ -355,7 +346,6 @@ pub struct PageElem { /// "Weak Reject" because they did /// not understand our approach... /// ``` - #[borrowed] #[ghost] pub foreground: Option<Content>, diff --git a/crates/typst-library/src/layout/place.rs b/crates/typst-library/src/layout/place.rs index bedeb507..b6c4a0cc 100644 --- a/crates/typst-library/src/layout/place.rs +++ b/crates/typst-library/src/layout/place.rs @@ -134,7 +134,6 @@ pub struct PlaceElem { /// /// Has no effect if `float` is `{false}`. #[default(Em::new(1.5).into())] - #[resolve] pub clearance: Length, /// The horizontal displacement of the placed content. diff --git a/crates/typst-library/src/math/accent.rs b/crates/typst-library/src/math/accent.rs index c8569ea2..5a9c47ae 100644 --- a/crates/typst-library/src/math/accent.rs +++ b/crates/typst-library/src/math/accent.rs @@ -62,7 +62,6 @@ pub struct AccentElem { /// ```example /// $dash(A, size: #150%)$ /// ``` - #[resolve] #[default(Rel::one())] pub size: Rel<Length>, diff --git a/crates/typst-library/src/math/attach.rs b/crates/typst-library/src/math/attach.rs index d526aba5..0dda1d33 100644 --- a/crates/typst-library/src/math/attach.rs +++ b/crates/typst-library/src/math/attach.rs @@ -59,9 +59,9 @@ impl Packed<AttachElem> { macro_rules! merge { ($content:ident) => { - if base.$content.is_none() && elem.$content.is_some() { + if !base.$content.is_set() && elem.$content.is_set() { base.$content = elem.$content.clone(); - elem.$content = None; + elem.$content.unset(); } }; } @@ -152,7 +152,6 @@ pub struct StretchElem { /// The size to stretch to, relative to the maximum size of the glyph and /// its attachments. - #[resolve] #[default(Rel::one())] pub size: Rel<Length>, } diff --git a/crates/typst-library/src/math/cancel.rs b/crates/typst-library/src/math/cancel.rs index 17f4dcfb..88637095 100644 --- a/crates/typst-library/src/math/cancel.rs +++ b/crates/typst-library/src/math/cancel.rs @@ -29,7 +29,6 @@ pub struct CancelElem { /// $ a + cancel(x, length: #200%) /// - cancel(x, length: #200%) $ /// ``` - #[resolve] #[default(Rel::new(Ratio::one(), Abs::pt(3.0).into()))] pub length: Rel<Length>, @@ -89,7 +88,6 @@ pub struct CancelElem { /// ), /// ) $ /// ``` - #[resolve] #[fold] #[default(Stroke { // Default stroke has 0.5pt for better visuals. diff --git a/crates/typst-library/src/math/equation.rs b/crates/typst-library/src/math/equation.rs index 32be216a..b97bb18d 100644 --- a/crates/typst-library/src/math/equation.rs +++ b/crates/typst-library/src/math/equation.rs @@ -63,7 +63,6 @@ pub struct EquationElem { /// With @ratio, we get: /// $ F_n = floor(1 / sqrt(5) phi.alt^n) $ /// ``` - #[borrowed] pub numbering: Option<Numbering>, /// The alignment of the equation numbering. @@ -152,7 +151,7 @@ impl Synthesize for Packed<EquationElem> { engine: &mut Engine, styles: StyleChain, ) -> SourceResult<()> { - let supplement = match self.as_ref().supplement(styles) { + let supplement = match self.as_ref().supplement.get_ref(styles) { Smart::Auto => TextElem::packed(Self::local_name_in(styles)), Smart::Custom(None) => Content::empty(), Smart::Custom(Some(supplement)) => { @@ -160,14 +159,15 @@ impl Synthesize for Packed<EquationElem> { } }; - self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement)))); + self.supplement + .set(Smart::Custom(Some(Supplement::Content(supplement)))); Ok(()) } } impl Show for Packed<EquationElem> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - if self.block(styles) { + if self.block.get(styles) { Ok(BlockElem::multi_layouter( self.clone(), engine.routines.layout_equation_block, @@ -185,25 +185,27 @@ impl Show for Packed<EquationElem> { impl ShowSet for Packed<EquationElem> { fn show_set(&self, styles: StyleChain) -> Styles { let mut out = Styles::new(); - if self.block(styles) { - out.set(AlignElem::set_alignment(Alignment::CENTER)); - out.set(BlockElem::set_breakable(false)); - out.set(ParLine::set_numbering(None)); - out.set(EquationElem::set_size(MathSize::Display)); + if self.block.get(styles) { + out.set(AlignElem::alignment, Alignment::CENTER); + out.set(AlignElem::alignment, Alignment::CENTER); + out.set(BlockElem::breakable, false); + out.set(ParLine::numbering, None); + out.set(EquationElem::size, MathSize::Display); } else { - out.set(EquationElem::set_size(MathSize::Text)); + out.set(EquationElem::size, MathSize::Text); } - out.set(TextElem::set_weight(FontWeight::from_number(450))); - out.set(TextElem::set_font(FontList(vec![FontFamily::new( - "New Computer Modern Math", - )]))); + out.set(TextElem::weight, FontWeight::from_number(450)); + out.set( + TextElem::font, + FontList(vec![FontFamily::new("New Computer Modern Math")]), + ); out } } impl Count for Packed<EquationElem> { fn update(&self) -> Option<CounterUpdate> { - (self.block(StyleChain::default()) && self.numbering().is_some()) + (self.block.get(StyleChain::default()) && self.numbering().is_some()) .then(|| CounterUpdate::Step(NonZeroUsize::ONE)) } } @@ -215,24 +217,24 @@ impl LocalName for Packed<EquationElem> { impl Refable for Packed<EquationElem> { fn supplement(&self) -> Content { // After synthesis, this should always be custom content. - match (**self).supplement(StyleChain::default()) { + match self.supplement.get_cloned(StyleChain::default()) { Smart::Custom(Some(Supplement::Content(content))) => content, _ => Content::empty(), } } fn counter(&self) -> Counter { - Counter::of(EquationElem::elem()) + Counter::of(EquationElem::ELEM) } fn numbering(&self) -> Option<&Numbering> { - (**self).numbering(StyleChain::default()).as_ref() + self.numbering.get_ref(StyleChain::default()).as_ref() } } impl Outlinable for Packed<EquationElem> { fn outlined(&self) -> bool { - self.block(StyleChain::default()) && self.numbering().is_some() + self.block.get(StyleChain::default()) && self.numbering().is_some() } fn prefix(&self, numbers: Content) -> Content { diff --git a/crates/typst-library/src/math/lr.rs b/crates/typst-library/src/math/lr.rs index 7558717a..5f5cb861 100644 --- a/crates/typst-library/src/math/lr.rs +++ b/crates/typst-library/src/math/lr.rs @@ -9,7 +9,6 @@ use crate::math::Mathy; #[elem(title = "Left/Right", Mathy)] pub struct LrElem { /// The size of the brackets, relative to the height of the wrapped content. - #[resolve] #[default(Rel::one())] pub size: Rel<Length>, @@ -130,7 +129,7 @@ fn delimited( ])); // Push size only if size is provided if let Some(size) = size { - elem.push_size(size); + elem.size.set(size); } elem.pack().spanned(span) } diff --git a/crates/typst-library/src/math/matrix.rs b/crates/typst-library/src/math/matrix.rs index b6c4654e..2a51caef 100644 --- a/crates/typst-library/src/math/matrix.rs +++ b/crates/typst-library/src/math/matrix.rs @@ -46,7 +46,6 @@ pub struct VecElem { /// #set math.vec(align: right) /// $ vec(-1, 1, -1) $ /// ``` - #[resolve] #[default(HAlignment::Center)] pub align: HAlignment, @@ -56,7 +55,6 @@ pub struct VecElem { /// #set math.vec(gap: 1em) /// $ vec(1, 2) $ /// ``` - #[resolve] #[default(DEFAULT_ROW_GAP.into())] pub gap: Rel<Length>, @@ -107,7 +105,6 @@ pub struct MatElem { /// #set math.mat(align: right) /// $ mat(-1, 1, 1; 1, -1, 1; 1, 1, -1) $ /// ``` - #[resolve] #[default(HAlignment::Center)] pub align: HAlignment, @@ -141,7 +138,6 @@ pub struct MatElem { /// ```example /// $ mat(0, 0, 0; 1, 1, 1; augment: #(hline: 1, stroke: 2pt + green)) $ /// ``` - #[resolve] #[fold] pub augment: Option<Augment>, @@ -162,7 +158,6 @@ pub struct MatElem { /// #set math.mat(row-gap: 1em) /// $ mat(1, 2; 3, 4) $ /// ``` - #[resolve] #[parse( let gap = args.named("gap")?; args.named("row-gap")?.or(gap) @@ -176,7 +171,6 @@ pub struct MatElem { /// #set math.mat(column-gap: 1em) /// $ mat(1, 2; 3, 4) $ /// ``` - #[resolve] #[parse(args.named("column-gap")?.or(gap))] #[default(DEFAULT_COL_GAP.into())] pub column_gap: Rel<Length>, @@ -259,7 +253,6 @@ pub struct CasesElem { /// #set math.cases(gap: 1em) /// $ x = cases(1, 2) $ /// ``` - #[resolve] #[default(DEFAULT_ROW_GAP.into())] pub gap: Rel<Length>, diff --git a/crates/typst-library/src/math/style.rs b/crates/typst-library/src/math/style.rs index f3d28f2a..53242e6e 100644 --- a/crates/typst-library/src/math/style.rs +++ b/crates/typst-library/src/math/style.rs @@ -11,7 +11,7 @@ pub fn bold( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_bold(true)) + body.set(EquationElem::bold, true) } /// Upright (non-italic) font style in math. @@ -24,7 +24,7 @@ pub fn upright( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_italic(Smart::Custom(false))) + body.set(EquationElem::italic, Smart::Custom(false)) } /// Italic font style in math. @@ -35,7 +35,7 @@ pub fn italic( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_italic(Smart::Custom(true))) + body.set(EquationElem::italic, Smart::Custom(true)) } /// Serif (roman) font style in math. @@ -46,7 +46,7 @@ pub fn serif( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_variant(MathVariant::Serif)) + body.set(EquationElem::variant, MathVariant::Serif) } /// Sans-serif font style in math. @@ -59,7 +59,7 @@ pub fn sans( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_variant(MathVariant::Sans)) + body.set(EquationElem::variant, MathVariant::Sans) } /// Calligraphic font style in math. @@ -93,7 +93,7 @@ pub fn cal( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_variant(MathVariant::Cal)) + body.set(EquationElem::variant, MathVariant::Cal) } /// Fraktur font style in math. @@ -106,7 +106,7 @@ pub fn frak( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_variant(MathVariant::Frak)) + body.set(EquationElem::variant, MathVariant::Frak) } /// Monospace font style in math. @@ -119,7 +119,7 @@ pub fn mono( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_variant(MathVariant::Mono)) + body.set(EquationElem::variant, MathVariant::Mono) } /// Blackboard bold (double-struck) font style in math. @@ -137,7 +137,7 @@ pub fn bb( /// The content to style. body: Content, ) -> Content { - body.styled(EquationElem::set_variant(MathVariant::Bb)) + body.set(EquationElem::variant, MathVariant::Bb) } /// Forced display style in math. @@ -157,8 +157,8 @@ pub fn display( #[default(false)] cramped: bool, ) -> Content { - body.styled(EquationElem::set_size(MathSize::Display)) - .styled(EquationElem::set_cramped(cramped)) + body.set(EquationElem::size, MathSize::Display) + .set(EquationElem::cramped, cramped) } /// Forced inline (text) style in math. @@ -179,8 +179,8 @@ pub fn inline( #[default(false)] cramped: bool, ) -> Content { - body.styled(EquationElem::set_size(MathSize::Text)) - .styled(EquationElem::set_cramped(cramped)) + body.set(EquationElem::size, MathSize::Text) + .set(EquationElem::cramped, cramped) } /// Forced script style in math. @@ -200,8 +200,8 @@ pub fn script( #[default(true)] cramped: bool, ) -> Content { - body.styled(EquationElem::set_size(MathSize::Script)) - .styled(EquationElem::set_cramped(cramped)) + body.set(EquationElem::size, MathSize::Script) + .set(EquationElem::cramped, cramped) } /// Forced second script style in math. @@ -222,8 +222,8 @@ pub fn sscript( #[default(true)] cramped: bool, ) -> Content { - body.styled(EquationElem::set_size(MathSize::ScriptScript)) - .styled(EquationElem::set_cramped(cramped)) + body.set(EquationElem::size, MathSize::ScriptScript) + .set(EquationElem::cramped, cramped) } /// The size of elements in an equation. diff --git a/crates/typst-library/src/model/bibliography.rs b/crates/typst-library/src/model/bibliography.rs index f56f5813..c44748a9 100644 --- a/crates/typst-library/src/model/bibliography.rs +++ b/crates/typst-library/src/model/bibliography.rs @@ -158,7 +158,7 @@ pub struct BibliographyElem { impl BibliographyElem { /// Find the document's bibliography. pub fn find(introspector: Tracked<Introspector>) -> StrResult<Packed<Self>> { - let query = introspector.query(&Self::elem().select()); + let query = introspector.query(&Self::ELEM.select()); let mut iter = query.iter(); let Some(elem) = iter.next() else { bail!("the document does not contain a bibliography"); @@ -175,7 +175,7 @@ impl BibliographyElem { pub fn has(engine: &Engine, key: Label) -> bool { engine .introspector - .query(&Self::elem().select()) + .query(&Self::ELEM.select()) .iter() .any(|elem| elem.to_packed::<Self>().unwrap().sources.derived.has(key)) } @@ -183,7 +183,7 @@ impl BibliographyElem { /// Find all bibliography keys. pub fn keys(introspector: Tracked<Introspector>) -> Vec<(Label, Option<EcoString>)> { let mut vec = vec![]; - for elem in introspector.query(&Self::elem().select()).iter() { + for elem in introspector.query(&Self::ELEM.select()).iter() { let this = elem.to_packed::<Self>().unwrap(); for (key, entry) in this.sources.derived.iter() { let detail = entry.title().map(|title| title.value.to_str().into()); @@ -197,8 +197,8 @@ impl BibliographyElem { impl Synthesize for Packed<BibliographyElem> { fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> { let elem = self.as_mut(); - elem.push_lang(TextElem::lang_in(styles)); - elem.push_region(TextElem::region_in(styles)); + elem.lang = Some(styles.get(TextElem::lang)); + elem.region = Some(styles.get(TextElem::region)); Ok(()) } } @@ -212,7 +212,7 @@ impl Show for Packed<BibliographyElem> { let span = self.span(); let mut seq = vec![]; - if let Some(title) = self.title(styles).unwrap_or_else(|| { + if let Some(title) = self.title.get_ref(styles).clone().unwrap_or_else(|| { Some(TextElem::packed(Self::local_name_in(styles)).spanned(span)) }) { seq.push( @@ -227,7 +227,7 @@ impl Show for Packed<BibliographyElem> { let references = works .references .as_ref() - .ok_or_else(|| match self.style(styles).source { + .ok_or_else(|| match self.style.get_ref(styles).source { CslSource::Named(style) => eco_format!( "CSL style \"{}\" is not suitable for bibliographies", style.display_name() @@ -239,7 +239,7 @@ impl Show for Packed<BibliographyElem> { .at(span)?; if references.iter().any(|(prefix, _)| prefix.is_some()) { - let row_gutter = ParElem::spacing_in(styles); + let row_gutter = styles.get(ParElem::spacing); let mut cells = vec![]; for (prefix, reference) in references { @@ -265,7 +265,7 @@ impl Show for Packed<BibliographyElem> { let block = if works.hanging_indent { let body = HElem::new((-INDENT).into()).pack() + realized; let inset = Sides::default() - .with(TextElem::dir_in(styles).start(), Some(INDENT.into())); + .with(styles.resolve(TextElem::dir).start(), Some(INDENT.into())); BlockElem::new() .with_body(Some(BlockBody::Content(body))) .with_inset(inset) @@ -285,8 +285,8 @@ impl ShowSet for Packed<BibliographyElem> { fn show_set(&self, _: StyleChain) -> Styles { const INDENT: Em = Em::new(1.0); let mut out = Styles::new(); - out.set(HeadingElem::set_numbering(None)); - out.set(PadElem::set_left(INDENT.into())); + out.set(HeadingElem::numbering, None); + out.set(PadElem::left, INDENT.into()); out } } @@ -643,7 +643,7 @@ impl<'a> Generator<'a> { introspector: Tracked<Introspector>, ) -> StrResult<Self> { let bibliography = BibliographyElem::find(introspector)?; - let groups = introspector.query(&CiteGroup::elem().select()); + let groups = introspector.query(&CiteGroup::ELEM.select()); let infos = Vec::with_capacity(groups.len()); Ok(Self { routines, @@ -661,7 +661,8 @@ impl<'a> Generator<'a> { LazyLock::new(hayagriva::archive::locales); let database = &self.bibliography.sources.derived; - let bibliography_style = &self.bibliography.style(StyleChain::default()).derived; + let bibliography_style = + &self.bibliography.style.get_ref(StyleChain::default()).derived; // Process all citation groups. let mut driver = BibliographyDriver::new(); @@ -689,7 +690,7 @@ impl<'a> Generator<'a> { continue; }; - let supplement = child.supplement(StyleChain::default()); + let supplement = child.supplement.get_cloned(StyleChain::default()); let locator = supplement.as_ref().map(|_| { SpecificLocator( citationberg::taxonomy::Locator::Custom, @@ -698,7 +699,7 @@ impl<'a> Generator<'a> { }); let mut hidden = false; - let special_form = match child.form(StyleChain::default()) { + let special_form = match child.form.get(StyleChain::default()) { None => { hidden = true; None @@ -720,7 +721,7 @@ impl<'a> Generator<'a> { continue; } - let style = match first.style(StyleChain::default()) { + let style = match first.style.get_ref(StyleChain::default()) { Smart::Auto => bibliography_style.get(), Smart::Custom(style) => style.derived.get(), }; @@ -736,23 +737,20 @@ impl<'a> Generator<'a> { driver.citation(CitationRequest::new( items, style, - Some(locale( - first.lang().copied().unwrap_or(Lang::ENGLISH), - first.region().copied().flatten(), - )), + Some(locale(first.lang.unwrap_or(Lang::ENGLISH), first.region.flatten())), &LOCALES, None, )); } let locale = locale( - self.bibliography.lang().copied().unwrap_or(Lang::ENGLISH), - self.bibliography.region().copied().flatten(), + self.bibliography.lang.unwrap_or(Lang::ENGLISH), + self.bibliography.region.flatten(), ); // Add hidden items for everything if we should print the whole // bibliography. - if self.bibliography.full(StyleChain::default()) { + if self.bibliography.full.get(StyleChain::default()) { for (_, entry) in database.iter() { driver.citation(CitationRequest::new( vec![CitationItem::new(entry, None, None, true, None)], @@ -1071,25 +1069,24 @@ fn apply_formatting(mut content: Content, format: &hayagriva::Formatting) -> Con match format.font_style { citationberg::FontStyle::Normal => {} citationberg::FontStyle::Italic => { - content = content.styled(TextElem::set_style(FontStyle::Italic)); + content = content.set(TextElem::style, FontStyle::Italic); } } match format.font_variant { citationberg::FontVariant::Normal => {} citationberg::FontVariant::SmallCaps => { - content = - content.styled(TextElem::set_smallcaps(Some(Smallcaps::Minuscules))); + content = content.set(TextElem::smallcaps, Some(Smallcaps::Minuscules)); } } match format.font_weight { citationberg::FontWeight::Normal => {} citationberg::FontWeight::Bold => { - content = content.styled(TextElem::set_delta(WeightDelta(300))); + content = content.set(TextElem::delta, WeightDelta(300)); } citationberg::FontWeight::Light => { - content = content.styled(TextElem::set_delta(WeightDelta(-100))); + content = content.set(TextElem::delta, WeightDelta(-100)); } } diff --git a/crates/typst-library/src/model/cite.rs b/crates/typst-library/src/model/cite.rs index 29497993..19513990 100644 --- a/crates/typst-library/src/model/cite.rs +++ b/crates/typst-library/src/model/cite.rs @@ -106,7 +106,6 @@ pub struct CiteElem { Some(Spanned { v: Smart::Auto, .. }) => Some(Smart::Auto), None => None, })] - #[borrowed] pub style: Smart<Derived<CslSource, CslStyle>>, /// The text language setting where the citation is. @@ -123,8 +122,8 @@ pub struct CiteElem { impl Synthesize for Packed<CiteElem> { fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> { let elem = self.as_mut(); - elem.push_lang(TextElem::lang_in(styles)); - elem.push_region(TextElem::region_in(styles)); + elem.lang = Some(styles.get(TextElem::lang)); + elem.region = Some(styles.get(TextElem::region)); Ok(()) } } diff --git a/crates/typst-library/src/model/document.rs b/crates/typst-library/src/model/document.rs index 1bce6b35..47e99973 100644 --- a/crates/typst-library/src/model/document.rs +++ b/crates/typst-library/src/model/document.rs @@ -3,7 +3,7 @@ use ecow::EcoString; use crate::diag::{bail, HintedStrResult, SourceResult}; use crate::engine::Engine; use crate::foundations::{ - cast, elem, Args, Array, Construct, Content, Datetime, Fields, OneOrMultiple, Smart, + cast, elem, Args, Array, Construct, Content, Datetime, OneOrMultiple, Smart, StyleChain, Styles, Value, }; @@ -109,23 +109,26 @@ impl DocumentInfo { /// Document set rules are a bit special, so we need to do this manually. pub fn populate(&mut self, styles: &Styles) { let chain = StyleChain::new(styles); - let has = |field| styles.has::<DocumentElem>(field as _); - if has(<DocumentElem as Fields>::Enum::Title) { - self.title = - DocumentElem::title_in(chain).map(|content| content.plain_text()); + if styles.has(DocumentElem::title) { + self.title = chain + .get_ref(DocumentElem::title) + .as_ref() + .map(|content| content.plain_text()); } - if has(<DocumentElem as Fields>::Enum::Author) { - self.author = DocumentElem::author_in(chain).0; + if styles.has(DocumentElem::author) { + self.author = chain.get_cloned(DocumentElem::author).0; } - if has(<DocumentElem as Fields>::Enum::Description) { - self.description = - DocumentElem::description_in(chain).map(|content| content.plain_text()); + if styles.has(DocumentElem::description) { + self.description = chain + .get_ref(DocumentElem::description) + .as_ref() + .map(|content| content.plain_text()); } - if has(<DocumentElem as Fields>::Enum::Keywords) { - self.keywords = DocumentElem::keywords_in(chain).0; + if styles.has(DocumentElem::keywords) { + self.keywords = chain.get_cloned(DocumentElem::keywords).0; } - if has(<DocumentElem as Fields>::Enum::Date) { - self.date = DocumentElem::date_in(chain); + if styles.has(DocumentElem::date) { + self.date = chain.get(DocumentElem::date); } } } diff --git a/crates/typst-library/src/model/emph.rs b/crates/typst-library/src/model/emph.rs index 45097b34..2d9cbec1 100644 --- a/crates/typst-library/src/model/emph.rs +++ b/crates/typst-library/src/model/emph.rs @@ -40,13 +40,13 @@ impl Show for Packed<EmphElem> { #[typst_macros::time(name = "emph", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { let body = self.body.clone(); - Ok(if TargetElem::target_in(styles).is_html() { + Ok(if styles.get(TargetElem::target).is_html() { HtmlElem::new(tag::em) .with_body(Some(body)) .pack() .spanned(self.span()) } else { - body.styled(TextElem::set_emph(ItalicToggle(true))) + body.set(TextElem::emph, ItalicToggle(true)) }) } } diff --git a/crates/typst-library/src/model/enum.rs b/crates/typst-library/src/model/enum.rs index f1f93702..8c191658 100644 --- a/crates/typst-library/src/model/enum.rs +++ b/crates/typst-library/src/model/enum.rs @@ -117,7 +117,6 @@ pub struct EnumElem { /// + Numbering! /// ``` #[default(Numbering::Pattern(NumberingPattern::from_str("1.").unwrap()))] - #[borrowed] pub numbering: Numbering, /// Which number to start the enumeration with. @@ -157,11 +156,9 @@ pub struct EnumElem { pub reversed: bool, /// The indentation of each item. - #[resolve] pub indent: Length, /// The space between the numbering and the body of each item. - #[resolve] #[default(Em::new(0.5).into())] pub body_indent: Length, @@ -228,19 +225,19 @@ impl EnumElem { impl Show for Packed<EnumElem> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - let tight = self.tight(styles); + let tight = self.tight.get(styles); - if TargetElem::target_in(styles).is_html() { + if styles.get(TargetElem::target).is_html() { let mut elem = HtmlElem::new(tag::ol); - if self.reversed(styles) { + if self.reversed.get(styles) { elem = elem.with_attr(attr::reversed, "reversed"); } - if let Some(n) = self.start(styles).custom() { + if let Some(n) = self.start.get(styles).custom() { elem = elem.with_attr(attr::start, eco_format!("{n}")); } let body = Content::sequence(self.children.iter().map(|item| { let mut li = HtmlElem::new(tag::li); - if let Some(nr) = item.number(styles) { + if let Some(nr) = item.number.get(styles) { li = li.with_attr(attr::value, eco_format!("{nr}")); } // Text in wide enums shall always turn into paragraphs. @@ -260,8 +257,9 @@ impl Show for Packed<EnumElem> { if tight { let spacing = self - .spacing(styles) - .unwrap_or_else(|| ParElem::leading_in(styles).into()); + .spacing + .get(styles) + .unwrap_or_else(|| styles.get(ParElem::leading)); let v = VElem::new(spacing.into()).with_weak(true).with_attach(true).pack(); realized = v + realized; } diff --git a/crates/typst-library/src/model/figure.rs b/crates/typst-library/src/model/figure.rs index bec667d6..7ac659a9 100644 --- a/crates/typst-library/src/model/figure.rs +++ b/crates/typst-library/src/model/figure.rs @@ -161,7 +161,6 @@ pub struct FigureElem { pub scope: PlacementScope, /// The figure's caption. - #[borrowed] pub caption: Option<Packed<FigureCaption>>, /// The kind of figure this is. @@ -214,13 +213,11 @@ pub struct FigureElem { /// kind: "foo", /// ) /// ``` - #[borrowed] pub supplement: Smart<Option<Supplement>>, /// How to number the figure. Accepts a /// [numbering pattern or function]($numbering). #[default(Some(NumberingPattern::from_str("1").unwrap().into()))] - #[borrowed] pub numbering: Option<Numbering>, /// The vertical gap between the body and caption. @@ -259,25 +256,25 @@ impl Synthesize for Packed<FigureElem> { let span = self.span(); let location = self.location(); let elem = self.as_mut(); - let numbering = elem.numbering(styles); + let numbering = elem.numbering.get_ref(styles); // Determine the figure's kind. - let kind = elem.kind(styles).unwrap_or_else(|| { + let kind = elem.kind.get_cloned(styles).unwrap_or_else(|| { elem.body .query_first(&Selector::can::<dyn Figurable>()) .map(|elem| FigureKind::Elem(elem.func())) - .unwrap_or_else(|| FigureKind::Elem(ImageElem::elem())) + .unwrap_or_else(|| FigureKind::Elem(ImageElem::ELEM)) }); // Resolve the supplement. - let supplement = match elem.supplement(styles).as_ref() { + let supplement = match elem.supplement.get_ref(styles).as_ref() { Smart::Auto => { // Default to the local name for the kind, if available. let name = match &kind { FigureKind::Elem(func) => func .local_name( - TextElem::lang_in(styles), - TextElem::region_in(styles), + styles.get(TextElem::lang), + styles.get(TextElem::region), ) .map(TextElem::packed), FigureKind::Name(_) => None, @@ -307,24 +304,25 @@ impl Synthesize for Packed<FigureElem> { // Construct the figure's counter. let counter = Counter::new(CounterKey::Selector( - select_where!(FigureElem, Kind => kind.clone()), + select_where!(FigureElem, kind => kind.clone()), )); // Fill the figure's caption. - let mut caption = elem.caption(styles).clone(); + let mut caption = elem.caption.get_cloned(styles); if let Some(caption) = &mut caption { caption.synthesize(engine, styles)?; - caption.push_kind(kind.clone()); - caption.push_supplement(supplement.clone()); - caption.push_numbering(numbering.clone()); - caption.push_counter(Some(counter.clone())); - caption.push_figure_location(location); + caption.kind = Some(kind.clone()); + caption.supplement = Some(supplement.clone()); + caption.numbering = Some(numbering.clone()); + caption.counter = Some(Some(counter.clone())); + caption.figure_location = Some(location); } - elem.push_kind(Smart::Custom(kind)); - elem.push_supplement(Smart::Custom(supplement.map(Supplement::Content))); - elem.push_counter(Some(counter)); - elem.push_caption(caption); + elem.kind.set(Smart::Custom(kind)); + elem.supplement + .set(Smart::Custom(supplement.map(Supplement::Content))); + elem.counter = Some(Some(counter)); + elem.caption.set(caption); Ok(()) } @@ -334,19 +332,19 @@ impl Show for Packed<FigureElem> { #[typst_macros::time(name = "figure", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { let span = self.span(); - let target = TargetElem::target_in(styles); + let target = styles.get(TargetElem::target); let mut realized = self.body.clone(); // Build the caption, if any. - if let Some(caption) = self.caption(styles).clone() { - let (first, second) = match caption.position(styles) { + if let Some(caption) = self.caption.get_cloned(styles) { + let (first, second) = match caption.position.get(styles) { OuterVAlignment::Top => (caption.pack(), realized), OuterVAlignment::Bottom => (realized, caption.pack()), }; let mut seq = Vec::with_capacity(3); seq.push(first); if !target.is_html() { - let v = VElem::new(self.gap(styles).into()).with_weak(true); + let v = VElem::new(self.gap.get(styles).into()).with_weak(true); seq.push(v.pack().spanned(span)) } seq.push(second); @@ -370,14 +368,14 @@ impl Show for Packed<FigureElem> { .spanned(span); // Wrap in a float. - if let Some(align) = self.placement(styles) { + if let Some(align) = self.placement.get(styles) { realized = PlaceElem::new(realized) .with_alignment(align.map(|align| HAlignment::Center + align)) - .with_scope(self.scope(styles)) + .with_scope(self.scope.get(styles)) .with_float(true) .pack() .spanned(span); - } else if self.scope(styles) == PlacementScope::Parent { + } else if self.scope.get(styles) == PlacementScope::Parent { bail!( span, "parent-scoped placement is only available for floating figures"; @@ -394,8 +392,8 @@ impl ShowSet for Packed<FigureElem> { // Still allows breakable figures with // `show figure: set block(breakable: true)`. let mut map = Styles::new(); - map.set(BlockElem::set_breakable(false)); - map.set(AlignElem::set_alignment(Alignment::CENTER)); + map.set(BlockElem::breakable, false); + map.set(AlignElem::alignment, Alignment::CENTER); map } } @@ -413,29 +411,28 @@ impl Count for Packed<FigureElem> { impl Refable for Packed<FigureElem> { fn supplement(&self) -> Content { // After synthesis, this should always be custom content. - match (**self).supplement(StyleChain::default()).as_ref() { - Smart::Custom(Some(Supplement::Content(content))) => content.clone(), + match self.supplement.get_cloned(StyleChain::default()) { + Smart::Custom(Some(Supplement::Content(content))) => content, _ => Content::empty(), } } fn counter(&self) -> Counter { - (**self) - .counter() - .cloned() + self.counter + .clone() .flatten() - .unwrap_or_else(|| Counter::of(FigureElem::elem())) + .unwrap_or_else(|| Counter::of(FigureElem::ELEM)) } fn numbering(&self) -> Option<&Numbering> { - (**self).numbering(StyleChain::default()).as_ref() + self.numbering.get_ref(StyleChain::default()).as_ref() } } impl Outlinable for Packed<FigureElem> { fn outlined(&self) -> bool { - (**self).outlined(StyleChain::default()) - && (self.caption(StyleChain::default()).is_some() + self.outlined.get(StyleChain::default()) + && (self.caption.get_ref(StyleChain::default()).is_some() || self.numbering().is_some()) } @@ -449,7 +446,8 @@ impl Outlinable for Packed<FigureElem> { } fn body(&self) -> Content { - self.caption(StyleChain::default()) + self.caption + .get_ref(StyleChain::default()) .as_ref() .map(|caption| caption.body.clone()) .unwrap_or_default() @@ -573,10 +571,10 @@ impl FigureCaption { } fn get_separator(&self, styles: StyleChain) -> Content { - self.separator(styles).unwrap_or_else(|| { + self.separator.get_cloned(styles).unwrap_or_else(|| { TextElem::packed(Self::local_separator( - TextElem::lang_in(styles), - TextElem::region_in(styles), + styles.get(TextElem::lang), + styles.get(TextElem::region), )) }) } @@ -585,7 +583,7 @@ impl FigureCaption { impl Synthesize for Packed<FigureCaption> { fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> { let elem = self.as_mut(); - elem.push_separator(Smart::Custom(elem.get_separator(styles))); + elem.separator.set(Smart::Custom(elem.get_separator(styles))); Ok(()) } } @@ -601,10 +599,10 @@ impl Show for Packed<FigureCaption> { Some(Some(counter)), Some(Some(location)), ) = ( - self.supplement().cloned(), - self.numbering(), - self.counter(), - self.figure_location(), + self.supplement.clone(), + &self.numbering, + &self.counter, + &self.figure_location, ) { let numbers = counter.display_at_loc(engine, *location, styles, numbering)?; if !supplement.is_empty() { @@ -613,7 +611,7 @@ impl Show for Packed<FigureCaption> { realized = supplement + numbers + self.get_separator(styles) + realized; } - Ok(if TargetElem::target_in(styles).is_html() { + Ok(if styles.get(TargetElem::target).is_html() { HtmlElem::new(tag::figcaption) .with_body(Some(realized)) .pack() diff --git a/crates/typst-library/src/model/footnote.rs b/crates/typst-library/src/model/footnote.rs index dfa3933b..63a461bd 100644 --- a/crates/typst-library/src/model/footnote.rs +++ b/crates/typst-library/src/model/footnote.rs @@ -67,7 +67,6 @@ pub struct FootnoteElem { /// #footnote[Star], /// #footnote[Dagger] /// ``` - #[borrowed] #[default(Numbering::Pattern(NumberingPattern::from_str("1").unwrap()))] pub numbering: Numbering, @@ -141,8 +140,8 @@ impl Show for Packed<FootnoteElem> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { let span = self.span(); let loc = self.declaration_location(engine).at(span)?; - let numbering = self.numbering(styles); - let counter = Counter::of(FootnoteElem::elem()); + let numbering = self.numbering.get_ref(styles); + let counter = Counter::of(FootnoteElem::ELEM); let num = counter.display_at_loc(engine, loc, styles, numbering)?; let sup = SuperElem::new(num).pack().spanned(span); let loc = loc.variant(1); @@ -248,7 +247,6 @@ pub struct FootnoteEntry { /// ] /// ``` #[default(Em::new(1.0).into())] - #[resolve] pub clearance: Length, /// The gap between footnote entries. @@ -261,7 +259,6 @@ pub struct FootnoteEntry { /// #footnote[Apart] /// ``` #[default(Em::new(0.5).into())] - #[resolve] pub gap: Length, /// The indent of each footnote entry. @@ -283,8 +280,8 @@ impl Show for Packed<FootnoteEntry> { let span = self.span(); let number_gap = Em::new(0.05); let default = StyleChain::default(); - let numbering = self.note.numbering(default); - let counter = Counter::of(FootnoteElem::elem()); + let numbering = self.note.numbering.get_ref(default); + let counter = Counter::of(FootnoteElem::ELEM); let Some(loc) = self.note.location() else { bail!( span, "footnote entry must have a location"; @@ -300,7 +297,7 @@ impl Show for Packed<FootnoteEntry> { .located(loc.variant(1)); Ok(Content::sequence([ - HElem::new(self.indent(styles).into()).pack(), + HElem::new(self.indent.get(styles).into()).pack(), sup, HElem::new(number_gap.into()).with_weak(true).pack(), self.note.body_content().unwrap().clone(), @@ -311,8 +308,8 @@ impl Show for Packed<FootnoteEntry> { impl ShowSet for Packed<FootnoteEntry> { fn show_set(&self, _: StyleChain) -> Styles { let mut out = Styles::new(); - out.set(ParElem::set_leading(Em::new(0.5).into())); - out.set(TextElem::set_size(TextSize(Em::new(0.85).into()))); + out.set(ParElem::leading, Em::new(0.5).into()); + out.set(TextElem::size, TextSize(Em::new(0.85).into())); out } } diff --git a/crates/typst-library/src/model/heading.rs b/crates/typst-library/src/model/heading.rs index 00931c81..d6f6d01f 100644 --- a/crates/typst-library/src/model/heading.rs +++ b/crates/typst-library/src/model/heading.rs @@ -106,7 +106,6 @@ pub struct HeadingElem { /// == A subsection /// === A sub-subsection /// ``` - #[borrowed] pub numbering: Option<Numbering>, /// A supplement for the heading. @@ -187,8 +186,8 @@ pub struct HeadingElem { impl HeadingElem { pub fn resolve_level(&self, styles: StyleChain) -> NonZeroUsize { - self.level(styles).unwrap_or_else(|| { - NonZeroUsize::new(self.offset(styles) + self.depth(styles).get()) + self.level.get(styles).unwrap_or_else(|| { + NonZeroUsize::new(self.offset.get(styles) + self.depth.get(styles).get()) .expect("overflow to 0 on NoneZeroUsize + usize") }) } @@ -200,7 +199,7 @@ impl Synthesize for Packed<HeadingElem> { engine: &mut Engine, styles: StyleChain, ) -> SourceResult<()> { - let supplement = match (**self).supplement(styles) { + let supplement = match self.supplement.get_ref(styles) { Smart::Auto => TextElem::packed(Self::local_name_in(styles)), Smart::Custom(None) => Content::empty(), Smart::Custom(Some(supplement)) => { @@ -209,8 +208,9 @@ impl Synthesize for Packed<HeadingElem> { }; let elem = self.as_mut(); - elem.push_level(Smart::Custom(elem.resolve_level(styles))); - elem.push_supplement(Smart::Custom(Some(Supplement::Content(supplement)))); + elem.level.set(Smart::Custom(elem.resolve_level(styles))); + elem.supplement + .set(Smart::Custom(Some(Supplement::Content(supplement)))); Ok(()) } } @@ -218,22 +218,22 @@ impl Synthesize for Packed<HeadingElem> { impl Show for Packed<HeadingElem> { #[typst_macros::time(name = "heading", span = self.span())] fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - let html = TargetElem::target_in(styles).is_html(); + let html = styles.get(TargetElem::target).is_html(); const SPACING_TO_NUMBERING: Em = Em::new(0.3); let span = self.span(); let mut realized = self.body.clone(); - let hanging_indent = self.hanging_indent(styles); + let hanging_indent = self.hanging_indent.get(styles); let mut indent = match hanging_indent { Smart::Custom(length) => length.resolve(styles), Smart::Auto => Abs::zero(), }; - if let Some(numbering) = (**self).numbering(styles).as_ref() { + if let Some(numbering) = self.numbering.get_ref(styles).as_ref() { let location = self.location().unwrap(); - let numbering = Counter::of(HeadingElem::elem()) + let numbering = Counter::of(HeadingElem::ELEM) .display_at_loc(engine, location, styles, numbering)? .spanned(span); @@ -293,7 +293,7 @@ impl Show for Packed<HeadingElem> { let block = if indent != Abs::zero() { let body = HElem::new((-indent).into()).pack() + realized; let inset = Sides::default() - .with(TextElem::dir_in(styles).start(), Some(indent.into())); + .with(styles.resolve(TextElem::dir).start(), Some(indent.into())); BlockElem::new() .with_body(Some(BlockBody::Content(body))) .with_inset(inset) @@ -307,7 +307,7 @@ impl Show for Packed<HeadingElem> { impl ShowSet for Packed<HeadingElem> { fn show_set(&self, styles: StyleChain) -> Styles { - let level = (**self).resolve_level(styles).get(); + let level = self.resolve_level(styles).get(); let scale = match level { 1 => 1.4, 2 => 1.2, @@ -319,49 +319,49 @@ impl ShowSet for Packed<HeadingElem> { let below = Em::new(0.75) / scale; let mut out = Styles::new(); - out.set(TextElem::set_size(TextSize(size.into()))); - out.set(TextElem::set_weight(FontWeight::BOLD)); - out.set(BlockElem::set_above(Smart::Custom(above.into()))); - out.set(BlockElem::set_below(Smart::Custom(below.into()))); - out.set(BlockElem::set_sticky(true)); + out.set(TextElem::size, TextSize(size.into())); + out.set(TextElem::weight, FontWeight::BOLD); + out.set(BlockElem::above, Smart::Custom(above.into())); + out.set(BlockElem::below, Smart::Custom(below.into())); + out.set(BlockElem::sticky, true); out } } impl Count for Packed<HeadingElem> { fn update(&self) -> Option<CounterUpdate> { - (**self) - .numbering(StyleChain::default()) + self.numbering + .get_ref(StyleChain::default()) .is_some() - .then(|| CounterUpdate::Step((**self).resolve_level(StyleChain::default()))) + .then(|| CounterUpdate::Step(self.resolve_level(StyleChain::default()))) } } impl Refable for Packed<HeadingElem> { fn supplement(&self) -> Content { // After synthesis, this should always be custom content. - match (**self).supplement(StyleChain::default()) { + match self.supplement.get_cloned(StyleChain::default()) { Smart::Custom(Some(Supplement::Content(content))) => content, _ => Content::empty(), } } fn counter(&self) -> Counter { - Counter::of(HeadingElem::elem()) + Counter::of(HeadingElem::ELEM) } fn numbering(&self) -> Option<&Numbering> { - (**self).numbering(StyleChain::default()).as_ref() + self.numbering.get_ref(StyleChain::default()).as_ref() } } impl Outlinable for Packed<HeadingElem> { fn outlined(&self) -> bool { - (**self).outlined(StyleChain::default()) + self.outlined.get(StyleChain::default()) } fn level(&self) -> NonZeroUsize { - (**self).resolve_level(StyleChain::default()) + self.resolve_level(StyleChain::default()) } fn prefix(&self, numbers: Content) -> Content { diff --git a/crates/typst-library/src/model/link.rs b/crates/typst-library/src/model/link.rs index ea85aa94..1e2c708e 100644 --- a/crates/typst-library/src/model/link.rs +++ b/crates/typst-library/src/model/link.rs @@ -108,7 +108,7 @@ impl Show for Packed<LinkElem> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { let body = self.body.clone(); - Ok(if TargetElem::target_in(styles).is_html() { + Ok(if styles.get(TargetElem::target).is_html() { if let LinkTarget::Dest(Destination::Url(url)) = &self.dest { HtmlElem::new(tag::a) .with_attr(attr::href, url.clone().into_inner()) @@ -138,7 +138,7 @@ impl Show for Packed<LinkElem> { impl ShowSet for Packed<LinkElem> { fn show_set(&self, _: StyleChain) -> Styles { let mut out = Styles::new(); - out.set(TextElem::set_hyphenate(Smart::Custom(false))); + out.set(TextElem::hyphenate, Smart::Custom(false)); out } } diff --git a/crates/typst-library/src/model/list.rs b/crates/typst-library/src/model/list.rs index 3c3afd33..5e6db1fa 100644 --- a/crates/typst-library/src/model/list.rs +++ b/crates/typst-library/src/model/list.rs @@ -86,7 +86,6 @@ pub struct ListElem { /// - Items /// - Items /// ``` - #[borrowed] #[default(ListMarker::Content(vec![ // These are all available in the default font, vertically centered, and // roughly of the same size (with the last one having slightly lower @@ -98,11 +97,9 @@ pub struct ListElem { pub marker: ListMarker, /// The indent of each item. - #[resolve] pub indent: Length, /// The spacing between the marker and the body of each item. - #[resolve] #[default(Em::new(0.5).into())] pub body_indent: Length, @@ -141,9 +138,9 @@ impl ListElem { impl Show for Packed<ListElem> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - let tight = self.tight(styles); + let tight = self.tight.get(styles); - if TargetElem::target_in(styles).is_html() { + if styles.get(TargetElem::target).is_html() { return Ok(HtmlElem::new(tag::ul) .with_body(Some(Content::sequence(self.children.iter().map(|item| { // Text in wide lists shall always turn into paragraphs. @@ -167,8 +164,9 @@ impl Show for Packed<ListElem> { if tight { let spacing = self - .spacing(styles) - .unwrap_or_else(|| ParElem::leading_in(styles).into()); + .spacing + .get(styles) + .unwrap_or_else(|| styles.get(ParElem::leading)); let v = VElem::new(spacing.into()).with_weak(true).with_attach(true).pack(); realized = v + realized; } diff --git a/crates/typst-library/src/model/outline.rs b/crates/typst-library/src/model/outline.rs index 16a11614..bb061fb7 100644 --- a/crates/typst-library/src/model/outline.rs +++ b/crates/typst-library/src/model/outline.rs @@ -183,8 +183,7 @@ pub struct OutlineElem { /// caption: [Experiment results], /// ) /// ``` - #[default(LocatableSelector(HeadingElem::elem().select()))] - #[borrowed] + #[default(LocatableSelector(HeadingElem::ELEM.select()))] pub target: LocatableSelector, /// The maximum level up to which elements are included in the outline. When @@ -257,7 +256,7 @@ impl Show for Packed<OutlineElem> { // Build the outline title. let mut seq = vec![]; - if let Some(title) = self.title(styles).unwrap_or_else(|| { + if let Some(title) = self.title.get_cloned(styles).unwrap_or_else(|| { Some(TextElem::packed(Self::local_name_in(styles)).spanned(span)) }) { seq.push( @@ -268,8 +267,8 @@ impl Show for Packed<OutlineElem> { ); } - let elems = engine.introspector.query(&self.target(styles).0); - let depth = self.depth(styles).unwrap_or(NonZeroUsize::MAX); + let elems = engine.introspector.query(&self.target.get_ref(styles).0); + let depth = self.depth.get(styles).unwrap_or(NonZeroUsize::MAX); // Build the outline entries. for elem in elems { @@ -291,13 +290,13 @@ impl Show for Packed<OutlineElem> { impl ShowSet for Packed<OutlineElem> { fn show_set(&self, styles: StyleChain) -> Styles { let mut out = Styles::new(); - out.set(HeadingElem::set_outlined(false)); - out.set(HeadingElem::set_numbering(None)); - out.set(ParElem::set_justify(false)); - out.set(BlockElem::set_above(Smart::Custom(ParElem::leading_in(styles).into()))); + out.set(HeadingElem::outlined, false); + out.set(HeadingElem::numbering, None); + out.set(ParElem::justify, false); + out.set(BlockElem::above, Smart::Custom(styles.get(ParElem::leading).into())); // Makes the outline itself available to its entries. Should be // superseded by a proper ancestry mechanism in the future. - out.set(OutlineEntry::set_parent(Some(self.clone()))); + out.set(OutlineEntry::parent, Some(self.clone())); out } } @@ -395,7 +394,6 @@ pub struct OutlineEntry { /// /// = A New Beginning /// ``` - #[borrowed] #[default(Some( RepeatElem::new(TextElem::packed(".")) .with_gap(Em::new(0.15).into()) @@ -472,7 +470,9 @@ impl OutlineEntry { gap: Length, ) -> SourceResult<Content> { let styles = context.styles().at(span)?; - let outline = Self::parent_in(styles) + let outline = styles + .get_ref(Self::parent) + .as_ref() .ok_or("must be called within the context of an outline") .at(span)?; let outline_loc = outline.location().unwrap(); @@ -483,7 +483,7 @@ impl OutlineEntry { .transpose()?; let prefix_inset = prefix_width.map(|w| w + gap.resolve(styles)); - let indent = outline.indent(styles); + let indent = outline.indent.get_ref(styles); let (base_indent, hanging_indent) = match &indent { Smart::Auto => compute_auto_indents( engine.introspector, @@ -527,7 +527,7 @@ impl OutlineEntry { }; let inset = Sides::default().with( - TextElem::dir_in(styles).start(), + styles.resolve(TextElem::dir).start(), Some(base_indent + Rel::from(hanging_indent.unwrap_or_default())), ); @@ -582,7 +582,7 @@ impl OutlineEntry { // See also: // - https://github.com/typst/typst/issues/4476 // - https://github.com/typst/typst/issues/5176 - let rtl = TextElem::dir_in(styles) == Dir::RTL; + let rtl = styles.resolve(TextElem::dir) == Dir::RTL; if rtl { // "Right-to-Left Embedding" seq.push(TextElem::packed("\u{202B}")); @@ -596,11 +596,11 @@ impl OutlineEntry { } // Add the filler between the section name and page number. - if let Some(filler) = self.fill(styles) { + if let Some(filler) = self.fill.get_cloned(styles) { seq.push(SpaceElem::shared().clone()); seq.push( BoxElem::new() - .with_body(Some(filler.clone())) + .with_body(Some(filler)) .with_width(Fr::one().into()) .pack() .spanned(span), @@ -717,7 +717,7 @@ fn query_prefix_widths( outline_loc: Location, ) -> SmallVec<[Option<Abs>; 4]> { let mut widths = SmallVec::<[Option<Abs>; 4]>::new(); - let elems = introspector.query(&select_where!(PrefixInfo, Key => outline_loc)); + let elems = introspector.query(&select_where!(PrefixInfo, key => outline_loc)); for elem in &elems { let info = elem.to_packed::<PrefixInfo>().unwrap(); let level = info.level.get(); diff --git a/crates/typst-library/src/model/par.rs b/crates/typst-library/src/model/par.rs index cf31b519..8aceec2c 100644 --- a/crates/typst-library/src/model/par.rs +++ b/crates/typst-library/src/model/par.rs @@ -108,7 +108,6 @@ pub struct ParElem { /// to `{-0.2em}` to get a baseline gap of exactly `{2em}`. The exact /// distribution of the top- and bottom-edge values affects the bounds of /// the first and last line. - #[resolve] #[default(Em::new(0.65).into())] pub leading: Length, @@ -122,7 +121,6 @@ pub struct ParElem { /// that block's [`above`]($block.above) or [`below`]($block.below) property /// takes precedence over the paragraph spacing. Headings, for instance, /// reduce the spacing below them by default for a better look. - #[resolve] #[default(Em::new(1.2).into())] pub spacing: Length, @@ -213,7 +211,6 @@ pub struct ParElem { /// /// #lorem(15) /// ``` - #[resolve] pub hanging_indent: Length, /// The contents of the paragraph. diff --git a/crates/typst-library/src/model/quote.rs b/crates/typst-library/src/model/quote.rs index cd45eec8..a8cf3eae 100644 --- a/crates/typst-library/src/model/quote.rs +++ b/crates/typst-library/src/model/quote.rs @@ -123,7 +123,6 @@ pub struct QuoteElem { /// /// #bibliography("works.bib", style: "apa") /// ``` - #[borrowed] attribution: Option<Attribution>, /// The quote. @@ -158,19 +157,19 @@ impl Show for Packed<QuoteElem> { #[typst_macros::time(name = "quote", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { let mut realized = self.body.clone(); - let block = self.block(styles); - let html = TargetElem::target_in(styles).is_html(); + let block = self.block.get(styles); + let html = styles.get(TargetElem::target).is_html(); - if self.quotes(styles).unwrap_or(!block) { + if self.quotes.get(styles).unwrap_or(!block) { let quotes = SmartQuotes::get( - SmartQuoteElem::quotes_in(styles), - TextElem::lang_in(styles), - TextElem::region_in(styles), - SmartQuoteElem::alternative_in(styles), + styles.get_ref(SmartQuoteElem::quotes), + styles.get(TextElem::lang), + styles.get(TextElem::region), + styles.get(SmartQuoteElem::alternative), ); // Alternate between single and double quotes. - let Depth(depth) = QuoteElem::depth_in(styles); + let Depth(depth) = styles.get(QuoteElem::depth); let double = depth % 2 == 0; if !html { @@ -183,10 +182,10 @@ impl Show for Packed<QuoteElem> { realized, TextElem::packed(quotes.close(double)), ]) - .styled(QuoteElem::set_depth(Depth(1))); + .set(QuoteElem::depth, Depth(1)); } - let attribution = self.attribution(styles); + let attribution = self.attribution.get_ref(styles); if block { realized = if html { @@ -204,7 +203,7 @@ impl Show for Packed<QuoteElem> { } .spanned(self.span()); - if let Some(attribution) = attribution.as_ref() { + if let Some(attribution) = attribution { let attribution = match attribution { Attribution::Content(content) => content.clone(), Attribution::Label(label) => CiteElem::new(*label) @@ -247,11 +246,11 @@ impl Show for Packed<QuoteElem> { impl ShowSet for Packed<QuoteElem> { fn show_set(&self, styles: StyleChain) -> Styles { let mut out = Styles::new(); - if self.block(styles) { - out.set(PadElem::set_left(Em::new(1.0).into())); - out.set(PadElem::set_right(Em::new(1.0).into())); - out.set(BlockElem::set_above(Smart::Custom(Em::new(2.4).into()))); - out.set(BlockElem::set_below(Smart::Custom(Em::new(1.8).into()))); + if self.block.get(styles) { + out.set(PadElem::left, Em::new(1.0).into()); + out.set(PadElem::right, Em::new(1.0).into()); + out.set(BlockElem::above, Smart::Custom(Em::new(2.4).into())); + out.set(BlockElem::below, Smart::Custom(Em::new(1.8).into())); } out } diff --git a/crates/typst-library/src/model/reference.rs b/crates/typst-library/src/model/reference.rs index 17f93b7c..2d04a97a 100644 --- a/crates/typst-library/src/model/reference.rs +++ b/crates/typst-library/src/model/reference.rs @@ -175,7 +175,6 @@ pub struct RefElem { /// in @intro[Part], it is done /// manually. /// ``` - #[borrowed] pub supplement: Smart<Option<Supplement>>, /// The kind of reference to produce. @@ -207,12 +206,12 @@ impl Synthesize for Packed<RefElem> { let citation = to_citation(self, engine, styles)?; let elem = self.as_mut(); - elem.push_citation(Some(citation)); - elem.push_element(None); + elem.citation = Some(Some(citation)); + elem.element = Some(None); if !BibliographyElem::has(engine, elem.target) { if let Ok(found) = engine.introspector.query_label(elem.target).cloned() { - elem.push_element(Some(found)); + elem.element = Some(Some(found)); return Ok(()); } } @@ -227,7 +226,7 @@ impl Show for Packed<RefElem> { let elem = engine.introspector.query_label(self.target); let span = self.span(); - let form = self.form(styles); + let form = self.form.get(styles); if form == RefForm::Page { let elem = elem.at(span)?; let elem = elem.clone(); @@ -299,7 +298,7 @@ impl Show for Packed<RefElem> { .hint(eco_format!( "you can enable {} numbering with `#set {}(numbering: \"1.\")`", elem.func().name(), - if elem.func() == EquationElem::elem() { + if elem.func() == EquationElem::ELEM { "math.equation" } else { elem.func().name() @@ -332,7 +331,7 @@ fn show_reference( let loc = elem.location().unwrap(); let numbers = counter.display_at_loc(engine, loc, styles, &numbering.trimmed())?; - let supplement = match reference.supplement(styles).as_ref() { + let supplement = match reference.supplement.get_ref(styles) { Smart::Auto => supplement, Smart::Custom(None) => Content::empty(), Smart::Custom(Some(supplement)) => supplement.resolve(engine, styles, [elem])?, @@ -353,7 +352,7 @@ fn to_citation( styles: StyleChain, ) -> SourceResult<Packed<CiteElem>> { let mut elem = Packed::new(CiteElem::new(reference.target).with_supplement( - match reference.supplement(styles).clone() { + match reference.supplement.get_cloned(styles) { Smart::Custom(Some(Supplement::Content(content))) => Some(content), _ => None, }, diff --git a/crates/typst-library/src/model/strong.rs b/crates/typst-library/src/model/strong.rs index 16d04ba9..08cf4839 100644 --- a/crates/typst-library/src/model/strong.rs +++ b/crates/typst-library/src/model/strong.rs @@ -44,13 +44,13 @@ impl Show for Packed<StrongElem> { #[typst_macros::time(name = "strong", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { let body = self.body.clone(); - Ok(if TargetElem::target_in(styles).is_html() { + Ok(if styles.get(TargetElem::target).is_html() { HtmlElem::new(tag::strong) .with_body(Some(body)) .pack() .spanned(self.span()) } else { - body.styled(TextElem::set_delta(WeightDelta(self.delta(styles)))) + body.set(TextElem::delta, WeightDelta(self.delta.get(styles))) }) } } diff --git a/crates/typst-library/src/model/table.rs b/crates/typst-library/src/model/table.rs index dcc77b0d..72c5acc5 100644 --- a/crates/typst-library/src/model/table.rs +++ b/crates/typst-library/src/model/table.rs @@ -125,12 +125,10 @@ use crate::visualize::{Paint, Stroke}; 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 and columns. This is a shorthand for setting @@ -141,7 +139,6 @@ 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()) @@ -151,7 +148,6 @@ 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. @@ -176,7 +172,6 @@ pub struct TableElem { /// [Profit:], [500 €], [1000 €], [1500 €], /// ) /// ``` - #[borrowed] pub fill: Celled<Option<Paint>>, /// How to align the cells' content. @@ -194,7 +189,6 @@ pub struct TableElem { /// [A], [B], [C], /// ) /// ``` - #[borrowed] pub align: Celled<Smart<Alignment>>, /// How to [stroke] the cells. @@ -209,7 +203,6 @@ pub struct TableElem { /// /// See the [grid documentation]($grid.stroke) for more information on /// strokes. - #[resolve] #[fold] #[default(Celled::Value(Sides::splat(Some(Some(Arc::new(Stroke::default()))))))] pub stroke: Celled<Sides<Option<Option<Arc<Stroke>>>>>, @@ -267,10 +260,10 @@ fn show_cell_html(tag: HtmlTag, cell: &Cell, styles: StyleChain) -> Content { let Some(cell) = cell.to_packed::<TableCell>() else { return cell }; let mut attrs = HtmlAttrs::default(); let span = |n: NonZeroUsize| (n != NonZeroUsize::MIN).then(|| n.to_string()); - if let Some(colspan) = span(cell.colspan(styles)) { + if let Some(colspan) = span(cell.colspan.get(styles)) { attrs.push(attr::colspan, colspan); } - if let Some(rowspan) = span(cell.rowspan(styles)) { + if let Some(rowspan) = span(cell.rowspan.get(styles)) { attrs.push(attr::rowspan, rowspan); } HtmlElem::new(tag) @@ -357,7 +350,7 @@ fn show_cellgrid_html(grid: CellGrid, styles: StyleChain) -> Content { impl Show for Packed<TableElem> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - Ok(if TargetElem::target_in(styles).is_html() { + Ok(if styles.get(TargetElem::target).is_html() { // TODO: This is a hack, it is not clear whether the locator is actually used by HTML. // How can we find out whether locator is actually used? let locator = Locator::root(); @@ -621,7 +614,6 @@ pub struct TableHLine { /// /// Specifying `{none}` removes any lines previously placed across this /// line's range, including hlines or per-cell stroke below it. - #[resolve] #[fold] #[default(Some(Arc::new(Stroke::default())))] pub stroke: Option<Arc<Stroke>>, @@ -666,7 +658,6 @@ pub struct TableVLine { /// /// Specifying `{none}` removes any lines previously placed across this /// line's range, including vlines or per-cell stroke below it. - #[resolve] #[fold] #[default(Some(Arc::new(Stroke::default())))] pub stroke: Option<Arc<Stroke>>, @@ -802,7 +793,6 @@ pub struct TableCell { pub inset: Smart<Sides<Option<Rel<Length>>>>, /// The cell's [stroke]($table.stroke) override. - #[resolve] #[fold] pub stroke: Sides<Option<Option<Arc<Stroke>>>>, @@ -820,7 +810,7 @@ cast! { impl Show for Packed<TableCell> { fn show(&self, _engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - show_grid_cell(self.body.clone(), self.inset(styles), self.align(styles)) + show_grid_cell(self.body.clone(), self.inset.get(styles), self.align.get(styles)) } } diff --git a/crates/typst-library/src/model/terms.rs b/crates/typst-library/src/model/terms.rs index 3df74cd9..280c2d67 100644 --- a/crates/typst-library/src/model/terms.rs +++ b/crates/typst-library/src/model/terms.rs @@ -66,7 +66,6 @@ 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. @@ -121,9 +120,9 @@ impl TermsElem { impl Show for Packed<TermsElem> { fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { let span = self.span(); - let tight = self.tight(styles); + let tight = self.tight.get(styles); - if TargetElem::target_in(styles).is_html() { + if styles.get(TargetElem::target).is_html() { return Ok(HtmlElem::new(tag::dl) .with_body(Some(Content::sequence(self.children.iter().flat_map( |item| { @@ -148,14 +147,14 @@ impl Show for Packed<TermsElem> { .pack()); } - let separator = self.separator(styles); - let indent = self.indent(styles); - let hanging_indent = self.hanging_indent(styles); - let gutter = self.spacing(styles).unwrap_or_else(|| { + let separator = self.separator.get_ref(styles); + let indent = self.indent.get(styles); + let hanging_indent = self.hanging_indent.get(styles); + let gutter = self.spacing.get(styles).unwrap_or_else(|| { if tight { - ParElem::leading_in(styles).into() + styles.get(ParElem::leading) } else { - ParElem::spacing_in(styles).into() + styles.get(ParElem::spacing) } }); @@ -179,19 +178,21 @@ impl Show for Packed<TermsElem> { children.push(StackChild::Block(Content::sequence(seq))); } - let padding = Sides::default().with(TextElem::dir_in(styles).start(), pad.into()); + let padding = + Sides::default().with(styles.resolve(TextElem::dir).start(), pad.into()); let mut realized = StackElem::new(children) .with_spacing(Some(gutter.into())) .pack() .spanned(span) .padded(padding) - .styled(TermsElem::set_within(true)); + .set(TermsElem::within, true); if tight { let spacing = self - .spacing(styles) - .unwrap_or_else(|| ParElem::leading_in(styles).into()); + .spacing + .get(styles) + .unwrap_or_else(|| styles.get(ParElem::leading)); let v = VElem::new(spacing.into()) .with_weak(true) .with_attach(true) diff --git a/crates/typst-library/src/pdf/embed.rs b/crates/typst-library/src/pdf/embed.rs index 4c01cd65..0f93f95a 100644 --- a/crates/typst-library/src/pdf/embed.rs +++ b/crates/typst-library/src/pdf/embed.rs @@ -48,7 +48,6 @@ pub struct EmbedElem { let resolved = id.vpath().as_rootless_path().to_string_lossy().replace("\\", "/").into(); Derived::new(path.clone(), resolved) )] - #[borrowed] pub path: Derived<EcoString, EcoString>, /// Raw file data, optionally. @@ -72,17 +71,15 @@ pub struct EmbedElem { pub relationship: Option<EmbeddedFileRelationship>, /// The MIME type of the embedded file. - #[borrowed] pub mime_type: Option<EcoString>, /// A description for the embedded file. - #[borrowed] pub description: Option<EcoString>, } impl Show for Packed<EmbedElem> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - if TargetElem::target_in(styles) == Target::Html { + if styles.get(TargetElem::target) == Target::Html { engine .sink .warn(warning!(self.span(), "embed was ignored during HTML export")); diff --git a/crates/typst-library/src/text/case.rs b/crates/typst-library/src/text/case.rs index 69dbf5e1..3b2ae450 100644 --- a/crates/typst-library/src/text/case.rs +++ b/crates/typst-library/src/text/case.rs @@ -37,9 +37,7 @@ pub fn upper( fn case(text: Caseable, case: Case) -> Caseable { match text { Caseable::Str(v) => Caseable::Str(case.apply(&v).into()), - Caseable::Content(v) => { - Caseable::Content(v.styled(TextElem::set_case(Some(case)))) - } + Caseable::Content(v) => Caseable::Content(v.set(TextElem::case, Some(case))), } } diff --git a/crates/typst-library/src/text/deco.rs b/crates/typst-library/src/text/deco.rs index d745a48f..8c1d5634 100644 --- a/crates/typst-library/src/text/deco.rs +++ b/crates/typst-library/src/text/deco.rs @@ -30,7 +30,6 @@ pub struct UnderlineElem { /// [care], /// ) /// ``` - #[resolve] #[fold] pub stroke: Smart<Stroke>, @@ -42,7 +41,6 @@ pub struct UnderlineElem { /// The Tale Of A Faraway Line I /// ] /// ``` - #[resolve] pub offset: Smart<Length>, /// The amount by which to extend the line beyond (or within if negative) @@ -53,7 +51,6 @@ pub struct UnderlineElem { /// underline(extent: 2pt)[Chapter 1] /// ) /// ``` - #[resolve] pub extent: Length, /// Whether the line skips sections in which it would collide with the @@ -84,7 +81,7 @@ pub struct UnderlineElem { impl Show for Packed<UnderlineElem> { #[typst_macros::time(name = "underline", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - if TargetElem::target_in(styles).is_html() { + if styles.get(TargetElem::target).is_html() { // Note: In modern HTML, `<u>` is not the underline element, but // rather an "Unarticulated Annotation" element (see HTML spec // 4.5.22). Using `text-decoration` instead is recommended by MDN. @@ -94,15 +91,18 @@ impl Show for Packed<UnderlineElem> { .pack()); } - Ok(self.body.clone().styled(TextElem::set_deco(smallvec![Decoration { - line: DecoLine::Underline { - stroke: self.stroke(styles).unwrap_or_default(), - offset: self.offset(styles), - evade: self.evade(styles), - background: self.background(styles), - }, - extent: self.extent(styles), - }]))) + Ok(self.body.clone().set( + TextElem::deco, + smallvec![Decoration { + line: DecoLine::Underline { + stroke: self.stroke.resolve(styles).unwrap_or_default(), + offset: self.offset.resolve(styles), + evade: self.evade.get(styles), + background: self.background.get(styles), + }, + extent: self.extent.resolve(styles), + }], + )) } } @@ -127,7 +127,6 @@ pub struct OverlineElem { /// [The Forest Theme], /// ) /// ``` - #[resolve] #[fold] pub stroke: Smart<Stroke>, @@ -139,7 +138,6 @@ pub struct OverlineElem { /// The Tale Of A Faraway Line II /// ] /// ``` - #[resolve] pub offset: Smart<Length>, /// The amount by which to extend the line beyond (or within if negative) @@ -150,7 +148,6 @@ pub struct OverlineElem { /// #set underline(extent: 4pt) /// #overline(underline[Typography Today]) /// ``` - #[resolve] pub extent: Length, /// Whether the line skips sections in which it would collide with the @@ -186,22 +183,25 @@ pub struct OverlineElem { impl Show for Packed<OverlineElem> { #[typst_macros::time(name = "overline", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - if TargetElem::target_in(styles).is_html() { + if styles.get(TargetElem::target).is_html() { return Ok(HtmlElem::new(tag::span) .with_attr(attr::style, "text-decoration: overline") .with_body(Some(self.body.clone())) .pack()); } - Ok(self.body.clone().styled(TextElem::set_deco(smallvec![Decoration { - line: DecoLine::Overline { - stroke: self.stroke(styles).unwrap_or_default(), - offset: self.offset(styles), - evade: self.evade(styles), - background: self.background(styles), - }, - extent: self.extent(styles), - }]))) + Ok(self.body.clone().set( + TextElem::deco, + smallvec![Decoration { + line: DecoLine::Overline { + stroke: self.stroke.resolve(styles).unwrap_or_default(), + offset: self.offset.resolve(styles), + evade: self.evade.get(styles), + background: self.background.get(styles), + }, + extent: self.extent.resolve(styles), + }], + )) } } @@ -225,7 +225,6 @@ pub struct StrikeElem { /// This is #strike(stroke: 1.5pt + red)[very stricken through]. \ /// This is #strike(stroke: 10pt)[redacted]. /// ``` - #[resolve] #[fold] pub stroke: Smart<Stroke>, @@ -239,7 +238,6 @@ pub struct StrikeElem { /// This is #strike(offset: auto)[low-ish]. \ /// This is #strike(offset: -3.5pt)[on-top]. /// ``` - #[resolve] pub offset: Smart<Length>, /// The amount by which to extend the line beyond (or within if negative) @@ -249,7 +247,6 @@ pub struct StrikeElem { /// This #strike(extent: -2pt)[skips] parts of the word. /// This #strike(extent: 2pt)[extends] beyond the word. /// ``` - #[resolve] pub extent: Length, /// Whether the line is placed behind the content. @@ -270,19 +267,22 @@ pub struct StrikeElem { impl Show for Packed<StrikeElem> { #[typst_macros::time(name = "strike", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - if TargetElem::target_in(styles).is_html() { + if styles.get(TargetElem::target).is_html() { return Ok(HtmlElem::new(tag::s).with_body(Some(self.body.clone())).pack()); } - Ok(self.body.clone().styled(TextElem::set_deco(smallvec![Decoration { - // Note that we do not support evade option for strikethrough. - line: DecoLine::Strikethrough { - stroke: self.stroke(styles).unwrap_or_default(), - offset: self.offset(styles), - background: self.background(styles), - }, - extent: self.extent(styles), - }]))) + Ok(self.body.clone().set( + TextElem::deco, + smallvec![Decoration { + // Note that we do not support evade option for strikethrough. + line: DecoLine::Strikethrough { + stroke: self.stroke.resolve(styles).unwrap_or_default(), + offset: self.offset.resolve(styles), + background: self.background.get(styles), + }, + extent: self.extent.resolve(styles), + }], + )) } } @@ -312,7 +312,6 @@ pub struct HighlightElem { /// stroke: fuchsia /// )[stroked highlighting]. /// ``` - #[resolve] #[fold] pub stroke: Sides<Option<Option<Stroke>>>, @@ -346,7 +345,6 @@ pub struct HighlightElem { /// ```example /// A long #highlight(extent: 4pt)[background]. /// ``` - #[resolve] pub extent: Length, /// How much to round the highlight's corners. See the @@ -357,7 +355,6 @@ pub struct HighlightElem { /// radius: 5pt, extent: 2pt /// )[carefully], it will be on the test. /// ``` - #[resolve] #[fold] pub radius: Corners<Option<Rel<Length>>>, @@ -369,25 +366,29 @@ pub struct HighlightElem { impl Show for Packed<HighlightElem> { #[typst_macros::time(name = "highlight", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - if TargetElem::target_in(styles).is_html() { + if styles.get(TargetElem::target).is_html() { return Ok(HtmlElem::new(tag::mark) .with_body(Some(self.body.clone())) .pack()); } - Ok(self.body.clone().styled(TextElem::set_deco(smallvec![Decoration { - line: DecoLine::Highlight { - fill: self.fill(styles), - stroke: self - .stroke(styles) - .unwrap_or_default() - .map(|stroke| stroke.map(Stroke::unwrap_or_default)), - top_edge: self.top_edge(styles), - bottom_edge: self.bottom_edge(styles), - radius: self.radius(styles).unwrap_or_default(), - }, - extent: self.extent(styles), - }]))) + Ok(self.body.clone().set( + TextElem::deco, + smallvec![Decoration { + line: DecoLine::Highlight { + fill: self.fill.get_cloned(styles), + stroke: self + .stroke + .resolve(styles) + .unwrap_or_default() + .map(|stroke| stroke.map(Stroke::unwrap_or_default)), + top_edge: self.top_edge.get(styles), + bottom_edge: self.bottom_edge.get(styles), + radius: self.radius.resolve(styles).unwrap_or_default(), + }, + extent: self.extent.resolve(styles), + }], + )) } } diff --git a/crates/typst-library/src/text/lang.rs b/crates/typst-library/src/text/lang.rs index a170714b..530b1f00 100644 --- a/crates/typst-library/src/text/lang.rs +++ b/crates/typst-library/src/text/lang.rs @@ -250,7 +250,7 @@ pub trait LocalName { where Self: Sized, { - Self::local_name(TextElem::lang_in(styles), TextElem::region_in(styles)) + Self::local_name(styles.get(TextElem::lang), styles.get(TextElem::region)) } } diff --git a/crates/typst-library/src/text/mod.rs b/crates/typst-library/src/text/mod.rs index 230f8e50..51663633 100644 --- a/crates/typst-library/src/text/mod.rs +++ b/crates/typst-library/src/text/mod.rs @@ -165,7 +165,6 @@ pub struct TextElem { font_list.map(|font_list| font_list.v) })] #[default(FontList(vec![FontFamily::new("Libertinus Serif")]))] - #[borrowed] #[ghost] pub font: FontList, @@ -260,7 +259,6 @@ pub struct TextElem { #[parse(args.named_or_find("size")?)] #[fold] #[default(TextSize(Abs::pt(11.0).into()))] - #[resolve] #[ghost] pub size: TextSize, @@ -292,7 +290,6 @@ pub struct TextElem { /// ```example /// #text(stroke: 0.5pt + red)[Stroked] /// ``` - #[resolve] #[ghost] pub stroke: Option<Stroke>, @@ -302,7 +299,6 @@ pub struct TextElem { /// #set text(tracking: 1.5pt) /// Distant text. /// ``` - #[resolve] #[ghost] pub tracking: Length, @@ -318,7 +314,6 @@ pub struct TextElem { /// #set text(spacing: 200%) /// Text with distant words. /// ``` - #[resolve] #[default(Rel::one())] #[ghost] pub spacing: Rel<Length>, @@ -341,7 +336,6 @@ pub struct TextElem { /// A #text(baseline: 3pt)[lowered] /// word. /// ``` - #[resolve] #[ghost] pub baseline: Length, @@ -483,7 +477,6 @@ pub struct TextElem { /// #set text(dir: rtl) /// هذا عربي. /// ``` - #[resolve] #[ghost] pub dir: TextDir, @@ -950,24 +943,24 @@ pub fn families(styles: StyleChain<'_>) -> impl Iterator<Item = &'_ FontFamily> .collect() }); - let tail = if TextElem::fallback_in(styles) { fallbacks.as_slice() } else { &[] }; - TextElem::font_in(styles).into_iter().chain(tail.iter()) + let tail = if styles.get(TextElem::fallback) { fallbacks.as_slice() } else { &[] }; + styles.get_ref(TextElem::font).into_iter().chain(tail.iter()) } /// Resolve the font variant. pub fn variant(styles: StyleChain) -> FontVariant { let mut variant = FontVariant::new( - TextElem::style_in(styles), - TextElem::weight_in(styles), - TextElem::stretch_in(styles), + styles.get(TextElem::style), + styles.get(TextElem::weight), + styles.get(TextElem::stretch), ); - let WeightDelta(delta) = TextElem::delta_in(styles); + let WeightDelta(delta) = styles.get(TextElem::delta); variant.weight = variant .weight .thicken(delta.clamp(i16::MIN as i64, i16::MAX as i64) as i16); - if TextElem::emph_in(styles).0 { + if styles.get(TextElem::emph).0 { variant.style = match variant.style { FontStyle::Normal => FontStyle::Italic, FontStyle::Italic => FontStyle::Normal, @@ -996,11 +989,11 @@ impl Resolve for TextSize { type Output = Abs; fn resolve(self, styles: StyleChain) -> Self::Output { - let factor = match EquationElem::size_in(styles) { + let factor = match styles.get(EquationElem::size) { MathSize::Display | MathSize::Text => 1.0, - MathSize::Script => EquationElem::script_scale_in(styles).0 as f64 / 100.0, + MathSize::Script => styles.get(EquationElem::script_scale).0 as f64 / 100.0, MathSize::ScriptScript => { - EquationElem::script_scale_in(styles).1 as f64 / 100.0 + styles.get(EquationElem::script_scale).1 as f64 / 100.0 } }; factor * self.0.resolve(styles) @@ -1123,7 +1116,7 @@ impl Resolve for TextDir { fn resolve(self, styles: StyleChain) -> Self::Output { match self.0 { - Smart::Auto => TextElem::lang_in(styles).dir(), + Smart::Auto => styles.get(TextElem::lang).dir(), Smart::Custom(dir) => dir, } } @@ -1236,67 +1229,67 @@ pub fn features(styles: StyleChain) -> Vec<Feature> { }; // Features that are on by default in Harfbuzz are only added if disabled. - if !TextElem::kerning_in(styles) { + if !styles.get(TextElem::kerning) { feat(b"kern", 0); } // Features that are off by default in Harfbuzz are only added if enabled. - if let Some(sc) = TextElem::smallcaps_in(styles) { + if let Some(sc) = styles.get(TextElem::smallcaps) { feat(b"smcp", 1); if sc == Smallcaps::All { feat(b"c2sc", 1); } } - if TextElem::alternates_in(styles) { + if styles.get(TextElem::alternates) { feat(b"salt", 1); } - for set in TextElem::stylistic_set_in(styles).sets() { + for set in styles.get(TextElem::stylistic_set).sets() { let storage = [b's', b's', b'0' + set / 10, b'0' + set % 10]; feat(&storage, 1); } - if !TextElem::ligatures_in(styles) { + if !styles.get(TextElem::ligatures) { feat(b"liga", 0); feat(b"clig", 0); } - if TextElem::discretionary_ligatures_in(styles) { + if styles.get(TextElem::discretionary_ligatures) { feat(b"dlig", 1); } - if TextElem::historical_ligatures_in(styles) { + if styles.get(TextElem::historical_ligatures) { feat(b"hlig", 1); } - match TextElem::number_type_in(styles) { + match styles.get(TextElem::number_type) { Smart::Auto => {} Smart::Custom(NumberType::Lining) => feat(b"lnum", 1), Smart::Custom(NumberType::OldStyle) => feat(b"onum", 1), } - match TextElem::number_width_in(styles) { + match styles.get(TextElem::number_width) { Smart::Auto => {} Smart::Custom(NumberWidth::Proportional) => feat(b"pnum", 1), Smart::Custom(NumberWidth::Tabular) => feat(b"tnum", 1), } - if TextElem::slashed_zero_in(styles) { + if styles.get(TextElem::slashed_zero) { feat(b"zero", 1); } - if TextElem::fractions_in(styles) { + if styles.get(TextElem::fractions) { feat(b"frac", 1); } - match EquationElem::size_in(styles) { + match styles.get(EquationElem::size) { MathSize::Script => feat(b"ssty", 1), MathSize::ScriptScript => feat(b"ssty", 2), _ => {} } - for (tag, value) in TextElem::features_in(styles).0 { + for (tag, value) in styles.get_cloned(TextElem::features).0 { tags.push(Feature::new(tag, value, ..)) } @@ -1306,8 +1299,8 @@ pub fn features(styles: StyleChain) -> Vec<Feature> { /// Process the language and region of a style chain into a /// rustybuzz-compatible BCP 47 language. pub fn language(styles: StyleChain) -> rustybuzz::Language { - let mut bcp: EcoString = TextElem::lang_in(styles).as_str().into(); - if let Some(region) = TextElem::region_in(styles) { + let mut bcp: EcoString = styles.get(TextElem::lang).as_str().into(); + if let Some(region) = styles.get(TextElem::region) { bcp.push('-'); bcp.push_str(region.as_str()); } diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs index e1f4cf13..67038163 100644 --- a/crates/typst-library/src/text/raw.rs +++ b/crates/typst-library/src/text/raw.rs @@ -158,7 +158,6 @@ 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. @@ -250,7 +249,6 @@ pub struct RawElem { Some(Spanned { v: Smart::Auto, .. }) => Some(Smart::Auto), None => None, })] - #[borrowed] pub theme: Smart<Option<Derived<DataSource, RawTheme>>>, /// The size for a tab stop in spaces. A tab is replaced with enough spaces to @@ -306,7 +304,7 @@ impl RawElem { impl Synthesize for Packed<RawElem> { fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> { let seq = self.highlight(styles); - self.push_lines(seq); + self.lines = Some(seq); Ok(()) } } @@ -319,8 +317,8 @@ impl Packed<RawElem> { let count = lines.len() as i64; let lang = elem - .lang(styles) - .as_ref() + .lang + .get_ref(styles) .as_ref() .map(|s| s.to_lowercase()) .or(Some("txt".into())); @@ -337,8 +335,8 @@ impl Packed<RawElem> { }) }; - let syntaxes = LazyCell::new(|| elem.syntaxes(styles)); - let theme: &synt::Theme = match elem.theme(styles) { + let syntaxes = LazyCell::new(|| elem.syntaxes.get_cloned(styles)); + let theme: &synt::Theme = match elem.theme.get_ref(styles) { Smart::Auto => &RAW_THEME, Smart::Custom(Some(theme)) => theme.derived.get(), Smart::Custom(None) => return non_highlighted_result(lines).collect(), @@ -434,7 +432,7 @@ impl Packed<RawElem> { impl Show for Packed<RawElem> { #[typst_macros::time(name = "raw", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - let lines = self.lines().map(|v| v.as_slice()).unwrap_or_default(); + let lines = self.lines.as_deref().unwrap_or_default(); let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1)); for (i, line) in lines.iter().enumerate() { @@ -447,8 +445,8 @@ impl Show for Packed<RawElem> { let mut realized = Content::sequence(seq); - if TargetElem::target_in(styles).is_html() { - return Ok(HtmlElem::new(if self.block(styles) { + if styles.get(TargetElem::target).is_html() { + return Ok(HtmlElem::new(if self.block.get(styles) { tag::pre } else { tag::code @@ -458,9 +456,9 @@ impl Show for Packed<RawElem> { .spanned(self.span())); } - if self.block(styles) { + if self.block.get(styles) { // Align the text before inserting it into the block. - realized = realized.aligned(self.align(styles).into()); + realized = realized.aligned(self.align.get(styles).into()); realized = BlockElem::new() .with_body(Some(BlockBody::Content(realized))) .pack() @@ -474,14 +472,14 @@ impl Show for Packed<RawElem> { impl ShowSet for Packed<RawElem> { fn show_set(&self, styles: StyleChain) -> Styles { let mut out = Styles::new(); - out.set(TextElem::set_overhang(false)); - out.set(TextElem::set_lang(Lang::ENGLISH)); - out.set(TextElem::set_hyphenate(Smart::Custom(false))); - out.set(TextElem::set_size(TextSize(Em::new(0.8).into()))); - out.set(TextElem::set_font(FontList(vec![FontFamily::new("DejaVu Sans Mono")]))); - out.set(TextElem::set_cjk_latin_spacing(Smart::Custom(None))); - if self.block(styles) { - out.set(ParElem::set_justify(false)); + out.set(TextElem::overhang, false); + out.set(TextElem::lang, Lang::ENGLISH); + out.set(TextElem::hyphenate, Smart::Custom(false)); + out.set(TextElem::size, TextSize(Em::new(0.8).into())); + out.set(TextElem::font, FontList(vec![FontFamily::new("DejaVu Sans Mono")])); + out.set(TextElem::cjk_latin_spacing, Smart::Custom(None)); + if self.block.get(styles) { + out.set(ParElem::justify, false); } out } @@ -789,7 +787,7 @@ fn preprocess( let mut text = text.get(); if text.contains('\t') { - let tab_size = RawElem::tab_size_in(styles); + let tab_size = styles.get(RawElem::tab_size); text = align_tabs(&text, tab_size); } split_newlines(&text) @@ -809,11 +807,11 @@ fn styled( let mut body = TextElem::packed(piece).spanned(span); if span_offset > 0 { - body = body.styled(TextElem::set_span_offset(span_offset)); + body = body.set(TextElem::span_offset, span_offset); } if style.foreground != foreground { - body = body.styled(TextElem::set_fill(to_typst(style.foreground).into())); + body = body.set(TextElem::fill, to_typst(style.foreground).into()); } if style.font_style.contains(synt::FontStyle::BOLD) { diff --git a/crates/typst-library/src/text/shift.rs b/crates/typst-library/src/text/shift.rs index b7f3ed92..1a05d8f9 100644 --- a/crates/typst-library/src/text/shift.rs +++ b/crates/typst-library/src/text/shift.rs @@ -69,7 +69,7 @@ impl Show for Packed<SubElem> { fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { let body = self.body.clone(); - if TargetElem::target_in(styles).is_html() { + if styles.get(TargetElem::target).is_html() { return Ok(HtmlElem::new(tag::sub) .with_body(Some(body)) .pack() @@ -79,9 +79,9 @@ impl Show for Packed<SubElem> { show_script( styles, body, - self.typographic(styles), - self.baseline(styles), - self.size(styles), + self.typographic.get(styles), + self.baseline.get(styles), + self.size.get(styles), ScriptKind::Sub, ) } @@ -151,7 +151,7 @@ impl Show for Packed<SuperElem> { fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { let body = self.body.clone(); - if TargetElem::target_in(styles).is_html() { + if styles.get(TargetElem::target).is_html() { return Ok(HtmlElem::new(tag::sup) .with_body(Some(body)) .pack() @@ -161,9 +161,9 @@ impl Show for Packed<SuperElem> { show_script( styles, body, - self.typographic(styles), - self.baseline(styles), - self.size(styles), + self.typographic.get(styles), + self.baseline.get(styles), + self.size.get(styles), ScriptKind::Super, ) } @@ -177,13 +177,16 @@ fn show_script( size: Smart<TextSize>, kind: ScriptKind, ) -> SourceResult<Content> { - let font_size = TextElem::size_in(styles); - Ok(body.styled(TextElem::set_shift_settings(Some(ShiftSettings { - typographic, - shift: baseline.map(|l| -Em::from_length(l, font_size)), - size: size.map(|t| Em::from_length(t.0, font_size)), - kind, - })))) + let font_size = styles.resolve(TextElem::size); + Ok(body.set( + TextElem::shift_settings, + Some(ShiftSettings { + typographic, + shift: baseline.map(|l| -Em::from_length(l, font_size)), + size: size.map(|t| Em::from_length(t.0, font_size)), + kind, + }), + )) } /// Configuration values for sub- or superscript text. diff --git a/crates/typst-library/src/text/smallcaps.rs b/crates/typst-library/src/text/smallcaps.rs index 924a45e8..1c283893 100644 --- a/crates/typst-library/src/text/smallcaps.rs +++ b/crates/typst-library/src/text/smallcaps.rs @@ -64,8 +64,9 @@ pub struct SmallcapsElem { impl Show for Packed<SmallcapsElem> { #[typst_macros::time(name = "smallcaps", span = self.span())] fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { - let sc = if self.all(styles) { Smallcaps::All } else { Smallcaps::Minuscules }; - Ok(self.body.clone().styled(TextElem::set_smallcaps(Some(sc)))) + let sc = + if self.all.get(styles) { Smallcaps::All } else { Smallcaps::Minuscules }; + Ok(self.body.clone().set(TextElem::smallcaps, Some(sc))) } } diff --git a/crates/typst-library/src/text/smartquote.rs b/crates/typst-library/src/text/smartquote.rs index 09cefd01..24787d06 100644 --- a/crates/typst-library/src/text/smartquote.rs +++ b/crates/typst-library/src/text/smartquote.rs @@ -83,13 +83,12 @@ pub struct SmartQuoteElem { /// #set smartquote(quotes: (single: ("[[", "]]"), double: auto)) /// 'Das sind eigene Anführungszeichen.' /// ``` - #[borrowed] pub quotes: Smart<SmartQuoteDict>, } impl PlainText for Packed<SmartQuoteElem> { fn plain_text(&self, text: &mut EcoString) { - if self.double.unwrap_or(true) { + if self.double.as_option().unwrap_or(true) { text.push_str("\""); } else { text.push_str("'"); diff --git a/crates/typst-library/src/visualize/curve.rs b/crates/typst-library/src/visualize/curve.rs index 50944a51..587f0d4a 100644 --- a/crates/typst-library/src/visualize/curve.rs +++ b/crates/typst-library/src/visualize/curve.rs @@ -86,7 +86,6 @@ pub struct CurveElem { /// down, up, down, up, down, /// ) /// ``` - #[resolve] #[fold] pub stroke: Smart<Option<Stroke>>, diff --git a/crates/typst-library/src/visualize/image/mod.rs b/crates/typst-library/src/visualize/image/mod.rs index f5109798..48a14f0e 100644 --- a/crates/typst-library/src/visualize/image/mod.rs +++ b/crates/typst-library/src/visualize/image/mod.rs @@ -160,7 +160,6 @@ pub struct ImageElem { Some(Spanned { v: Smart::Auto, .. }) => Some(Smart::Auto), None => None, })] - #[borrowed] pub icc: Smart<Derived<DataSource, Bytes>>, } @@ -199,22 +198,22 @@ impl ImageElem { let source = Derived::new(DataSource::Bytes(bytes), loaded); let mut elem = ImageElem::new(source); if let Some(format) = format { - elem.push_format(format); + elem.format.set(format); } if let Some(width) = width { - elem.push_width(width); + elem.width.set(width); } if let Some(height) = height { - elem.push_height(height); + elem.height.set(height); } if let Some(alt) = alt { - elem.push_alt(alt); + elem.alt.set(alt); } if let Some(fit) = fit { - elem.push_fit(fit); + elem.fit.set(fit); } if let Some(scaling) = scaling { - elem.push_scaling(scaling); + elem.scaling.set(scaling); } Ok(elem.pack().spanned(span)) } @@ -223,8 +222,8 @@ impl ImageElem { impl Show for Packed<ImageElem> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_image) - .with_width(self.width(styles)) - .with_height(self.height(styles)) + .with_width(self.width.get(styles)) + .with_height(self.height.get(styles)) .pack() .spanned(self.span())) } diff --git a/crates/typst-library/src/visualize/line.rs b/crates/typst-library/src/visualize/line.rs index 689321f1..d058b926 100644 --- a/crates/typst-library/src/visualize/line.rs +++ b/crates/typst-library/src/visualize/line.rs @@ -22,15 +22,12 @@ pub struct LineElem { /// The start point of the line. /// /// Must be an array of exactly two relative lengths. - #[resolve] pub start: Axes<Rel<Length>>, /// The point where the line ends. - #[resolve] pub end: Option<Axes<Rel<Length>>>, /// The line's length. This is only respected if `end` is `{none}`. - #[resolve] #[default(Abs::pt(30.0).into())] pub length: Rel<Length>, @@ -50,7 +47,6 @@ pub struct LineElem { /// line(stroke: (paint: blue, thickness: 1pt, dash: ("dot", 2pt, 4pt, 2pt))), /// ) /// ``` - #[resolve] #[fold] pub stroke: Stroke, } diff --git a/crates/typst-library/src/visualize/path.rs b/crates/typst-library/src/visualize/path.rs index 968146cd..e19e091d 100644 --- a/crates/typst-library/src/visualize/path.rs +++ b/crates/typst-library/src/visualize/path.rs @@ -55,7 +55,6 @@ pub struct PathElem { /// /// Can be set to `{none}` to disable the stroke or to `{auto}` for a /// stroke of `{1pt}` black if and if only if no fill is given. - #[resolve] #[fold] pub stroke: Smart<Option<Stroke>>, diff --git a/crates/typst-library/src/visualize/polygon.rs b/crates/typst-library/src/visualize/polygon.rs index 42b08343..d75e1a65 100644 --- a/crates/typst-library/src/visualize/polygon.rs +++ b/crates/typst-library/src/visualize/polygon.rs @@ -43,7 +43,6 @@ pub struct PolygonElem { /// /// Can be set to `{none}` to disable the stroke or to `{auto}` for a /// stroke of `{1pt}` black if and if only if no fill is given. - #[resolve] #[fold] pub stroke: Smart<Option<Stroke>>, @@ -117,10 +116,10 @@ impl PolygonElem { let mut elem = PolygonElem::new(vertices); if let Some(fill) = fill { - elem.push_fill(fill); + elem.fill.set(fill); } if let Some(stroke) = stroke { - elem.push_stroke(stroke); + elem.stroke.set(stroke); } elem.pack().spanned(span) } diff --git a/crates/typst-library/src/visualize/shape.rs b/crates/typst-library/src/visualize/shape.rs index ff05be2b..f21bf93e 100644 --- a/crates/typst-library/src/visualize/shape.rs +++ b/crates/typst-library/src/visualize/shape.rs @@ -63,7 +63,6 @@ pub struct RectElem { /// rect(stroke: 2pt + red), /// ) /// ``` - #[resolve] #[fold] pub stroke: Smart<Sides<Option<Option<Stroke>>>>, @@ -101,20 +100,17 @@ pub struct RectElem { /// ), /// ) /// ``` - #[resolve] #[fold] pub radius: Corners<Option<Rel<Length>>>, /// How much to pad the rectangle's content. /// See the [box's documentation]($box.inset) for more details. - #[resolve] #[fold] #[default(Sides::splat(Some(Abs::pt(5.0).into())))] pub inset: Sides<Option<Rel<Length>>>, /// How much to expand the rectangle's size without affecting the layout. /// See the [box's documentation]($box.outset) for more details. - #[resolve] #[fold] pub outset: Sides<Option<Rel<Length>>>, @@ -123,15 +119,14 @@ pub struct RectElem { /// When this is omitted, the rectangle takes on a default size of at most /// `{45pt}` by `{30pt}`. #[positional] - #[borrowed] pub body: Option<Content>, } impl Show for Packed<RectElem> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_rect) - .with_width(self.width(styles)) - .with_height(self.height(styles)) + .with_width(self.width.get(styles)) + .with_height(self.height.get(styles)) .pack() .spanned(self.span())) } @@ -186,26 +181,22 @@ pub struct SquareElem { /// How to stroke the square. See the /// [rectangle's documentation]($rect.stroke) for more details. - #[resolve] #[fold] pub stroke: Smart<Sides<Option<Option<Stroke>>>>, /// How much to round the square's corners. See the /// [rectangle's documentation]($rect.radius) for more details. - #[resolve] #[fold] pub radius: Corners<Option<Rel<Length>>>, /// How much to pad the square's content. See the /// [box's documentation]($box.inset) for more details. - #[resolve] #[fold] #[default(Sides::splat(Some(Abs::pt(5.0).into())))] pub inset: Sides<Option<Rel<Length>>>, /// How much to expand the square's size without affecting the layout. See /// the [box's documentation]($box.outset) for more details. - #[resolve] #[fold] pub outset: Sides<Option<Rel<Length>>>, @@ -215,15 +206,14 @@ pub struct SquareElem { /// When this is omitted, the square takes on a default size of at most /// `{30pt}`. #[positional] - #[borrowed] pub body: Option<Content>, } impl Show for Packed<SquareElem> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_square) - .with_width(self.width(styles)) - .with_height(self.height(styles)) + .with_width(self.width.get(styles)) + .with_height(self.height.get(styles)) .pack() .spanned(self.span())) } @@ -257,20 +247,17 @@ pub struct EllipseElem { /// How to stroke the ellipse. See the /// [rectangle's documentation]($rect.stroke) for more details. - #[resolve] #[fold] pub stroke: Smart<Option<Stroke>>, /// How much to pad the ellipse's content. See the /// [box's documentation]($box.inset) for more details. - #[resolve] #[fold] #[default(Sides::splat(Some(Abs::pt(5.0).into())))] pub inset: Sides<Option<Rel<Length>>>, /// How much to expand the ellipse's size without affecting the layout. See /// the [box's documentation]($box.outset) for more details. - #[resolve] #[fold] pub outset: Sides<Option<Rel<Length>>>, @@ -279,15 +266,14 @@ pub struct EllipseElem { /// When this is omitted, the ellipse takes on a default size of at most /// `{45pt}` by `{30pt}`. #[positional] - #[borrowed] pub body: Option<Content>, } impl Show for Packed<EllipseElem> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_ellipse) - .with_width(self.width(styles)) - .with_height(self.height(styles)) + .with_width(self.width.get(styles)) + .with_height(self.height.get(styles)) .pack() .spanned(self.span())) } @@ -347,36 +333,32 @@ pub struct CircleElem { /// How to stroke the circle. See the /// [rectangle's documentation]($rect.stroke) for more details. - #[resolve] #[fold] #[default(Smart::Auto)] pub stroke: Smart<Option<Stroke>>, /// How much to pad the circle's content. See the /// [box's documentation]($box.inset) for more details. - #[resolve] #[fold] #[default(Sides::splat(Some(Abs::pt(5.0).into())))] pub inset: Sides<Option<Rel<Length>>>, /// How much to expand the circle's size without affecting the layout. See /// the [box's documentation]($box.outset) for more details. - #[resolve] #[fold] pub outset: Sides<Option<Rel<Length>>>, /// The content to place into the circle. The circle expands to fit this /// content, keeping the 1-1 aspect ratio. #[positional] - #[borrowed] pub body: Option<Content>, } impl Show for Packed<CircleElem> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_circle) - .with_width(self.width(styles)) - .with_height(self.height(styles)) + .with_width(self.width.get(styles)) + .with_height(self.height.get(styles)) .pack() .spanned(self.span())) } diff --git a/crates/typst-macros/src/elem.rs b/crates/typst-macros/src/elem.rs index 67fe7ed6..e1c901d8 100644 --- a/crates/typst-macros/src/elem.rs +++ b/crates/typst-macros/src/elem.rs @@ -1,9 +1,9 @@ -use heck::{ToKebabCase, ToShoutySnakeCase, ToUpperCamelCase}; +use heck::ToKebabCase; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -use syn::{parse_quote, Ident, Result, Token}; +use syn::{Ident, Result, Token}; use crate::util::{ determine_name_and_title, documentation, foundations, has_attr, kw, parse_attr, @@ -39,14 +39,13 @@ struct Elem { } impl Elem { - /// Calls the closure to produce a token stream if the - /// element has the given capability. + /// Whether the element has the given trait listed as a capability. fn can(&self, name: &str) -> bool { self.capabilities.iter().any(|capability| capability == name) } - /// Calls the closure to produce a token stream if the - /// element does not have the given capability. + /// Whether the element does not have the given trait listed as a + /// capability. fn cannot(&self, name: &str) -> bool { !self.can(name) } @@ -68,21 +67,6 @@ impl Elem { self.struct_fields().filter(|field| !field.required) } - /// Fields that are relevant for equality. - /// - /// Synthesized fields are excluded to ensure equality before and after - /// synthesis. - fn eq_fields(&self) -> impl Iterator<Item = &Field> + Clone { - self.struct_fields().filter(|field| !field.synthesized) - } - - /// Fields that show up in the documentation. - fn doc_fields(&self) -> impl Iterator<Item = &Field> + Clone { - self.fields - .iter() - .filter(|field| !field.internal && !field.synthesized) - } - /// Fields that are relevant for `Construct` impl. /// /// The reason why fields that are `parse` and internal are allowed is @@ -98,44 +82,20 @@ impl Elem { fn set_fields(&self) -> impl Iterator<Item = &Field> + Clone { self.construct_fields().filter(|field| !field.required) } - - /// Fields that can be accessed from the style chain. - fn style_fields(&self) -> impl Iterator<Item = &Field> + Clone { - self.real_fields() - .filter(|field| !field.required && !field.synthesized) - } - - /// Fields that are visible to the user. - fn visible_fields(&self) -> impl Iterator<Item = &Field> + Clone { - self.real_fields().filter(|field| !field.internal) - } } /// A field of an [element definition][`Elem`]. struct Field { + /// The index of the field among all. + i: u8, /// The name of this field. ident: Ident, - /// The identifier `{ident}_in`. - ident_in: Ident, /// The identifier `with_{ident}`. with_ident: Ident, - /// The identifier `push_{ident}`. - push_ident: Ident, - /// The identifier `set_{ident}`. - set_ident: Ident, - /// The upper camel-case version of `ident`, used for the enum variant name. - enum_ident: Ident, - /// The all-caps snake-case version of `ident`, used for the constant name. - const_ident: Ident, /// The visibility of this field. vis: syn::Visibility, /// The type of this field. ty: syn::Type, - /// The type returned by accessor methods for this field. - /// - /// Usually, this is the same as `ty`, but this might be different - /// if this field has a `#[resolve]` attribute. - output: syn::Type, /// The field's identifier as exposed to Typst. name: String, /// The documentation for this field as a string. @@ -147,16 +107,12 @@ struct Field { /// Whether this field is variadic; that is, has its values /// taken from a variable number of arguments. variadic: bool, - /// Whether this field has a `#[resolve]` attribute. - resolve: bool, /// Whether this field has a `#[fold]` attribute. fold: bool, /// Whether this field is excluded from documentation. internal: bool, /// Whether this field exists only in documentation. external: bool, - /// Whether this field has a `#[borrowed]` attribute. - borrowed: bool, /// Whether this field has a `#[ghost]` attribute. ghost: bool, /// Whether this field has a `#[synthesized]` attribute. @@ -206,7 +162,12 @@ fn parse(stream: TokenStream, body: &syn::ItemStruct) -> Result<Elem> { bail!(body, "expected named fields"); }; - let fields = named.named.iter().map(parse_field).collect::<Result<Vec<_>>>()?; + let mut fields = named.named.iter().map(parse_field).collect::<Result<Vec<_>>>()?; + fields.sort_by_key(|field| field.internal); + for (i, field) in fields.iter_mut().enumerate() { + field.i = i as u8; + } + if fields.iter().any(|field| field.ghost && !field.internal) && meta.capabilities.iter().all(|capability| capability != "Construct") { @@ -243,27 +204,20 @@ fn parse_field(field: &syn::Field) -> Result<Field> { let required = has_attr(&mut attrs, "required") || variadic; let positional = has_attr(&mut attrs, "positional") || required; - let mut field = Field { + let field = Field { + i: 0, ident: ident.clone(), - ident_in: format_ident!("{ident}_in"), with_ident: format_ident!("with_{ident}"), - push_ident: format_ident!("push_{ident}"), - set_ident: format_ident!("set_{ident}"), - 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()), vis: field.vis.clone(), ty: field.ty.clone(), - output: field.ty.clone(), name: ident.to_string().to_kebab_case(), docs: documentation(&attrs), positional, required, variadic, - resolve: has_attr(&mut attrs, "resolve"), fold: has_attr(&mut attrs, "fold"), internal: has_attr(&mut attrs, "internal"), external: has_attr(&mut attrs, "external"), - borrowed: has_attr(&mut attrs, "borrowed"), ghost: has_attr(&mut attrs, "ghost"), synthesized: has_attr(&mut attrs, "synthesized"), parse: parse_attr(&mut attrs, "parse")?.flatten(), @@ -275,17 +229,9 @@ fn parse_field(field: &syn::Field) -> Result<Field> { } if (field.required || field.synthesized) - && (field.default.is_some() || field.fold || field.resolve || field.ghost) + && (field.default.is_some() || field.fold || field.ghost) { - bail!( - ident, - "required and synthesized fields cannot be default, fold, resolve, or ghost" - ); - } - - if field.resolve { - let ty = &field.ty; - field.output = parse_quote! { <#ty as #foundations::Resolve>::Output }; + bail!(ident, "required and synthesized fields cannot be default, fold, or ghost"); } validate_attrs(&attrs)?; @@ -297,30 +243,17 @@ fn parse_field(field: &syn::Field) -> Result<Field> { fn create(element: &Elem) -> Result<TokenStream> { // The struct itself. let struct_ = create_struct(element); - let inherent_impl = create_inherent_impl(element); - // The enum with the struct's fields. - let fields_enum = create_fields_enum(element); - - // The statics with borrowed fields' default values. - let default_statics = element - .style_fields() - .filter(|field| field.borrowed) - .map(create_default_static); - - // Trait implementations. + // Implementations. + let inherent_impl = create_inherent_impl(element); let native_element_impl = create_native_elem_impl(element); - let partial_eq_impl = - element.cannot("PartialEq").then(|| create_partial_eq_impl(element)); + let field_impls = + element.fields.iter().map(|field| create_field_impl(element, field)); let construct_impl = element.cannot("Construct").then(|| create_construct_impl(element)); let set_impl = element.cannot("Set").then(|| create_set_impl(element)); - let capable_impl = create_capable_impl(element); - let fields_impl = create_fields_impl(element); - let repr_impl = element.cannot("Repr").then(|| create_repr_impl(element)); let locatable_impl = element.can("Locatable").then(|| create_locatable_impl(element)); let mathy_impl = element.can("Mathy").then(|| create_mathy_impl(element)); - let into_value_impl = create_into_value_impl(element); // We use a const block to create an anonymous scope, as to not leak any // local definitions. @@ -328,19 +261,13 @@ fn create(element: &Elem) -> Result<TokenStream> { #struct_ const _: () = { - #fields_enum - #(#default_statics)* #inherent_impl #native_element_impl - #fields_impl - #capable_impl + #(#field_impls)* #construct_impl #set_impl - #partial_eq_impl - #repr_impl #locatable_impl #mathy_impl - #into_value_impl }; }) } @@ -365,80 +292,13 @@ fn create_struct(element: &Elem) -> TokenStream { /// Create a field declaration for the struct. fn create_field(field: &Field) -> TokenStream { - let Field { vis, ident, ty, .. } = field; + let Field { i, vis, ident, ty, .. } = field; if field.required { quote! { #vis #ident: #ty } + } else if field.synthesized { + quote! { #vis #ident: ::std::option::Option<#ty> } } else { - quote! { #ident: ::std::option::Option<#ty> } - } -} - -/// Creates the element's enum for field identifiers. -fn create_fields_enum(element: &Elem) -> TokenStream { - let variants: Vec<_> = element.real_fields().map(|field| &field.enum_ident).collect(); - let names: Vec<_> = element.real_fields().map(|field| &field.name).collect(); - let consts: Vec<_> = element.real_fields().map(|field| &field.const_ident).collect(); - let repr = (!variants.is_empty()).then(|| quote! { #[repr(u8)] }); - - quote! { - #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] - #repr - pub enum Fields { - #(#variants,)* - } - - impl Fields { - /// Converts the field identifier to the field name. - pub fn to_str(self) -> &'static str { - match self { - #(Self::#variants => #names,)* - } - } - } - - impl ::std::convert::TryFrom<u8> for Fields { - type Error = #foundations::FieldAccessError; - - fn try_from(value: u8) -> Result<Self, Self::Error> { - #(const #consts: u8 = Fields::#variants as u8;)* - match value { - #(#consts => Ok(Self::#variants),)* - _ => Err(#foundations::FieldAccessError::Internal), - } - } - } - - impl ::std::str::FromStr for Fields { - type Err = (); - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s { - #(#names => Ok(Self::#variants),)* - _ => Err(()), - } - } - } - - impl ::std::fmt::Display for Fields { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - f.pad(self.to_str()) - } - } - } -} - -/// Creates a static with a borrowed field's default value. -fn create_default_static(field: &Field) -> TokenStream { - let Field { const_ident, default, ty, .. } = field; - - let init = match default { - Some(default) => quote! { || #default }, - None => quote! { ::std::default::Default::default }, - }; - - quote! { - static #const_ident: ::std::sync::LazyLock<#ty> = - ::std::sync::LazyLock::new(#init); + quote! { #vis #ident: #foundations::Settable<Self, #i> } } } @@ -448,19 +308,23 @@ fn create_inherent_impl(element: &Elem) -> TokenStream { let new_func = create_new_func(element); let with_field_methods = element.accessor_fields().map(create_with_field_method); - let push_field_methods = element.accessor_fields().map(create_push_field_method); - let field_methods = element.accessor_fields().map(create_field_method); - let field_in_methods = element.style_fields().map(create_field_in_method); - let set_field_methods = element.style_fields().map(create_set_field_method); + + let style_consts = element.real_fields().map(|field| { + let Field { i, vis, ident, .. } = field; + quote! { + #vis const #ident: #foundations::Field<Self, #i> + = #foundations::Field::new(); + } + }); quote! { impl #ident { #new_func #(#with_field_methods)* - #(#push_field_methods)* - #(#field_methods)* - #(#field_in_methods)* - #(#set_field_methods)* + } + #[allow(non_upper_case_globals)] + impl #ident { + #(#style_consts)* } } } @@ -476,8 +340,10 @@ fn create_new_func(element: &Elem) -> TokenStream { let ident = &field.ident; if field.required { quote! { #ident } - } else { + } else if field.synthesized { quote! { #ident: None } + } else { + quote! { #ident: #foundations::Settable::new() } } }); @@ -491,254 +357,189 @@ fn create_new_func(element: &Elem) -> TokenStream { /// Create a builder-style setter method for a field. fn create_with_field_method(field: &Field) -> TokenStream { - let Field { vis, ident, with_ident, push_ident, name, ty, .. } = field; + let Field { vis, ident, with_ident, name, ty, .. } = field; let doc = format!("Builder-style setter for the [`{name}`](Self::{ident}) field."); - quote! { - #[doc = #doc] - #vis fn #with_ident(mut self, #ident: #ty) -> Self { - self.#push_ident(#ident); - self - } - } -} - -/// Create a setter method for a field. -fn create_push_field_method(field: &Field) -> TokenStream { - let Field { vis, ident, push_ident, name, ty, .. } = field; - let doc = format!("Setter for the [`{name}`](Self::{ident}) field."); let expr = if field.required { - quote! { #ident } + quote! { self.#ident = #ident } + } else if field.synthesized { + quote! { self.#ident = Some(#ident) } } else { - quote! { Some(#ident) } + quote! { self.#ident.set(#ident) } }; quote! { #[doc = #doc] - #vis fn #push_ident(&mut self, #ident: #ty) { - self.#ident = #expr; + #vis fn #with_ident(mut self, #ident: #ty) -> Self { + #expr; + self } } } -/// Create an accessor method for a field. -fn create_field_method(field: &Field) -> TokenStream { - let Field { vis, docs, ident, output, .. } = field; +/// Creates the element's `NativeElement` implementation. +fn create_native_elem_impl(element: &Elem) -> TokenStream { + let Elem { name, ident, title, scope, keywords, docs, .. } = element; - if field.required { - quote! { - #[doc = #docs] - #vis fn #ident(&self) -> &#output { - &self.#ident - } - } - } else if field.synthesized { - quote! { - #[doc = #docs] - #[track_caller] - #vis fn #ident(&self) -> ::std::option::Option<&#output> { - self.#ident.as_ref() - } - } - } else { - let sig = if field.borrowed { - quote! { <'a>(&'a self, styles: #foundations::StyleChain<'a>) -> &'a #output } + let fields = element.fields.iter().filter(|field| !field.internal).map(|field| { + let i = field.i; + if field.external { + quote! { #foundations::ExternalFieldData::<#ident, #i>::vtable() } + } else if field.variadic { + quote! { #foundations::RequiredFieldData::<#ident, #i>::vtable_variadic() } + } else if field.required { + quote! { #foundations::RequiredFieldData::<#ident, #i>::vtable() } + } else if field.synthesized { + quote! { #foundations::SynthesizedFieldData::<#ident, #i>::vtable() } + } else if field.ghost { + quote! { #foundations::SettablePropertyData::<#ident, #i>::vtable() } } else { - quote! { (&self, styles: #foundations::StyleChain) -> #output } - }; - - let mut value = create_style_chain_access( - field, - field.borrowed, - quote! { self.#ident.as_ref() }, - ); - if field.resolve { - value = quote! { #foundations::Resolve::resolve(#value, styles) }; - } - - quote! { - #[doc = #docs] - #vis fn #ident #sig { - #value - } - } - } -} - -/// Create a style accessor 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 `{name}` field in the given style chain."); - - let ref_ = field.borrowed.then(|| quote! { & }); - - let mut value = create_style_chain_access(field, field.borrowed, quote! { None }); - if field.resolve { - value = quote! { #foundations::Resolve::resolve(#value, styles) }; - } - - quote! { - #[doc = #doc] - #vis fn #ident_in(styles: #foundations::StyleChain) -> #ref_ #output { - #value - } - } -} - -/// Create a style setter method for a field. -fn create_set_field_method(field: &Field) -> TokenStream { - let Field { vis, ident, set_ident, enum_ident, ty, name, .. } = field; - let doc = format!("Create a style property for the `{name}` field."); - - quote! { - #[doc = #doc] - #vis fn #set_ident(#ident: #ty) -> #foundations::Property { - #foundations::Property::new::<Self, _>( - Fields::#enum_ident as u8, - #ident, - ) + quote! { #foundations::SettableFieldData::<#ident, #i>::vtable() } } - } -} - -/// Create a style chain access method for a field. -fn create_style_chain_access( - field: &Field, - borrowed: bool, - inherent: TokenStream, -) -> TokenStream { - let Field { ty, default, enum_ident, const_ident, .. } = field; - - let getter = match (field.fold, borrowed) { - (false, false) => quote! { get }, - (false, true) => quote! { get_ref }, - (true, _) => quote! { get_folded }, - }; + }); - let default = if borrowed { - quote! { || &#const_ident } - } else { - match default { - Some(default) => quote! { || #default }, - None => quote! { ::std::default::Default::default }, + let field_arms = element + .fields + .iter() + .filter(|field| !field.internal && !field.external) + .map(|field| { + let Field { name, i, .. } = field; + quote! { #name => Some(#i) } + }); + let field_id = quote! { + |name| match name { + #(#field_arms,)* + _ => None, } }; - quote! { - styles.#getter::<#ty>( - <Self as #foundations::NativeElement>::elem(), - Fields::#enum_ident as u8, - #inherent, - #default, - ) - } -} - -/// Creates the element's `NativeElement` implementation. -fn create_native_elem_impl(element: &Elem) -> TokenStream { - let Elem { name, ident, title, scope, keywords, docs, .. } = element; + let capable_func = create_capable_func(element); - let local_name = if element.can("LocalName") { - quote! { Some(<#foundations::Packed<#ident> as ::typst_library::text::LocalName>::local_name) } - } else { - quote! { None } - }; + let with_keywords = + (!keywords.is_empty()).then(|| quote! { .with_keywords(&[#(#keywords),*]) }); + let with_repr = element.can("Repr").then(|| quote! { .with_repr() }); + let with_partial_eq = element.can("PartialEq").then(|| quote! { .with_partial_eq() }); + let with_local_name = element.can("LocalName").then(|| quote! { .with_local_name() }); + let with_scope = scope.then(|| quote! { .with_scope() }); - let scope = if *scope { - quote! { <#ident as #foundations::NativeScope>::scope() } - } else { - quote! { #foundations::Scope::new() } + quote! { + unsafe impl #foundations::NativeElement for #ident { + const ELEM: #foundations::Element = #foundations::Element::from_vtable({ + static STORE: #foundations::LazyElementStore + = #foundations::LazyElementStore::new(); + static VTABLE: #foundations::ContentVtable = + #foundations::ContentVtable::new::<#ident>( + #name, + #title, + #docs, + &[#(#fields),*], + #field_id, + #capable_func, + || &STORE, + ) #with_keywords + #with_repr + #with_partial_eq + #with_local_name + #with_scope + .erase(); + &VTABLE + }); + } + } +} + +/// Creates the appropriate trait implementation for a field. +fn create_field_impl(element: &Elem, field: &Field) -> TokenStream { + let elem_ident = &element.ident; + let Field { i, ty, ident, default, positional, name, docs, .. } = field; + + let default = match default { + Some(default) => quote! { || #default }, + None => quote! { std::default::Default::default }, }; - let params = element.doc_fields().map(create_param_info); - - let data = quote! { - #foundations::NativeElementData { - name: #name, - title: #title, - docs: #docs, - keywords: &[#(#keywords),*], - construct: <#ident as #foundations::Construct>::construct, - set: <#ident as #foundations::Set>::set, - vtable: <#ident as #foundations::Capable>::vtable, - field_id: |name| name.parse().ok().map(|id: Fields| id as u8), - field_name: |id| id.try_into().ok().map(Fields::to_str), - field_from_styles: <#ident as #foundations::Fields>::field_from_styles, - local_name: #local_name, - scope: ::std::sync::LazyLock::new(|| #scope), - params: ::std::sync::LazyLock::new(|| ::std::vec![#(#params),*]) + if field.external { + quote! { + impl #foundations::ExternalField<#i> for #elem_ident { + type Type = #ty; + const FIELD: #foundations::ExternalFieldData<Self, #i> = + #foundations::ExternalFieldData::<Self, #i>::new( + #name, + #docs, + #default, + ); + } } - }; - - quote! { - impl #foundations::NativeElement for #ident { - fn data() -> &'static #foundations::NativeElementData { - static DATA: #foundations::NativeElementData = #data; - &DATA + } else if field.required { + quote! { + impl #foundations::RequiredField<#i> for #elem_ident { + type Type = #ty; + const FIELD: #foundations::RequiredFieldData<Self, #i> = + #foundations::RequiredFieldData::<Self, #i>::new( + #name, + #docs, + |elem| &elem.#ident, + ); } } - } -} - -/// Creates a parameter info for a field. -fn create_param_info(field: &Field) -> TokenStream { - let Field { - name, - docs, - positional, - variadic, - required, - default, - ty, - .. - } = field; - - let named = !positional; - let settable = !field.required; - - let default = if settable { - let default = default - .clone() - .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }); + } else if field.synthesized { quote! { - Some(|| <#ty as #foundations::IntoValue>::into_value(#default)) + impl #foundations::SynthesizedField<#i> for #elem_ident { + type Type = #ty; + const FIELD: #foundations::SynthesizedFieldData<Self, #i> = + #foundations::SynthesizedFieldData::<Self, #i>::new( + #name, + #docs, + |elem| &elem.#ident, + ); + } } } else { - quote! { None } - }; - - let ty = if *variadic { - quote! { <#ty as #foundations::Container>::Inner } - } else { - quote! { #ty } - }; - - quote! { - #foundations::ParamInfo { - name: #name, - docs: #docs, - input: <#ty as #foundations::Reflect>::input(), - default: #default, - positional: #positional, - named: #named, - variadic: #variadic, - required: #required, - settable: #settable, - } - } -} + let slot = quote! { + || { + static LOCK: ::std::sync::OnceLock<#ty> = ::std::sync::OnceLock::new(); + &LOCK + } + }; -/// Creates the element's `PartialEq` implementation. -fn create_partial_eq_impl(element: &Elem) -> TokenStream { - let ident = &element.ident; - let empty = element.eq_fields().next().is_none().then(|| quote! { true }); - let fields = element.eq_fields().map(|field| &field.ident); + let with_fold = field.fold.then(|| quote! { .with_fold() }); + let refable = (!field.fold).then(|| { + quote! { + impl #foundations::RefableProperty<#i> for #elem_ident {} + } + }); - quote! { - impl PartialEq for #ident { - fn eq(&self, other: &Self) -> bool { - #empty - #(self.#fields == other.#fields)&&* + if field.ghost { + quote! { + impl #foundations::SettableProperty<#i> for #elem_ident { + type Type = #ty; + const FIELD: #foundations::SettablePropertyData<Self, #i> = + #foundations::SettablePropertyData::<Self, #i>::new( + #name, + #docs, + #positional, + #default, + #slot, + ) #with_fold; + } + #refable + } + } else { + quote! { + impl #foundations::SettableField<#i> for #elem_ident { + type Type = #ty; + const FIELD: #foundations::SettableFieldData<Self, #i> = + #foundations::SettableFieldData::<Self, #i>::new( + #name, + #docs, + #positional, + |elem| &elem.#ident, + |elem| &mut elem.#ident, + #default, + #slot, + ) #with_fold; + } + #refable } } } @@ -758,10 +559,12 @@ fn create_construct_impl(element: &Elem) -> TokenStream { let fields = element.struct_fields().map(|field| { let ident = &field.ident; - if field.synthesized { + if field.required { + quote! { #ident } + } else if field.synthesized { quote! { #ident: None } } else { - quote! { #ident } + quote! { #ident: #foundations::Settable::from(#ident) } } }); @@ -782,12 +585,12 @@ fn create_construct_impl(element: &Elem) -> TokenStream { fn create_set_impl(element: &Elem) -> TokenStream { let ident = &element.ident; let handlers = element.set_fields().map(|field| { - let set_ident = &field.set_ident; + let field_ident = &field.ident; let (prefix, value) = create_field_parser(field); quote! { #prefix if let Some(value) = #value { - styles.set(Self::#set_ident(value)); + styles.set(Self::#field_ident, value); } } }); @@ -827,7 +630,7 @@ fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) { } /// Creates the element's casting vtable. -fn create_capable_impl(element: &Elem) -> TokenStream { +fn create_capable_func(element: &Elem) -> TokenStream { // Forbidden capabilities (i.e capabilities that are not object safe). const FORBIDDEN: &[&str] = &["Debug", "PartialEq", "Hash", "Construct", "Set", "Repr", "LocalName"]; @@ -851,202 +654,10 @@ fn create_capable_impl(element: &Elem) -> TokenStream { }); quote! { - unsafe impl #foundations::Capable for #ident { - fn vtable(capability: ::std::any::TypeId) -> ::std::option::Option<::std::ptr::NonNull<()>> { - let dangling = ::std::ptr::NonNull::<#foundations::Packed<#ident>>::dangling().as_ptr(); - #(#checks)* - None - } - } - } -} - -/// Creates the element's `Fields` implementation. -fn create_fields_impl(element: &Elem) -> TokenStream { - let into_value = quote! { #foundations::IntoValue::into_value }; - let visible_non_ghost = || element.visible_fields().filter(|field| !field.ghost); - - // Fields that can be checked using the `has` method. - let has_arms = visible_non_ghost().map(|field| { - let Field { enum_ident, ident, .. } = field; - - let expr = if field.required { - quote! { true } - } else { - quote! { self.#ident.is_some() } - }; - - quote! { Fields::#enum_ident => #expr } - }); - - // Fields that can be accessed using the `field` method. - let field_arms = visible_non_ghost().map(|field| { - let Field { enum_ident, ident, .. } = field; - - let expr = if field.required { - quote! { Ok(#into_value(self.#ident.clone())) } - } else { - quote! { self.#ident.clone().map(#into_value).ok_or(#foundations::FieldAccessError::Unset) } - }; - - quote! { Fields::#enum_ident => #expr } - }); - - // Fields that can be accessed using the `field_with_styles` method. - let field_with_styles_arms = element.visible_fields().map(|field| { - let Field { enum_ident, ident, .. } = field; - - let expr = if field.required { - quote! { Ok(#into_value(self.#ident.clone())) } - } else if field.synthesized { - quote! { self.#ident.clone().map(#into_value).ok_or(#foundations::FieldAccessError::Unset) } - } else { - let value = create_style_chain_access( - field, - false, - if field.ghost { quote!(None) } else { quote!(self.#ident.as_ref()) }, - ); - - quote! { Ok(#into_value(#value)) } - }; - - quote! { Fields::#enum_ident => #expr } - }); - - // Fields that can be accessed using the `field_from_styles` method. - let field_from_styles_arms = element.visible_fields().map(|field| { - let Field { enum_ident, .. } = field; - - let expr = if field.required || field.synthesized { - quote! { Err(#foundations::FieldAccessError::Unknown) } - } else { - let value = create_style_chain_access(field, false, quote!(None)); - quote! { Ok(#into_value(#value)) } - }; - - quote! { Fields::#enum_ident => #expr } - }); - - // Sets fields from the style chain. - let materializes = visible_non_ghost() - .filter(|field| !field.required && !field.synthesized) - .map(|field| { - let Field { ident, .. } = field; - let value = create_style_chain_access( - field, - false, - if field.ghost { quote!(None) } else { quote!(self.#ident.as_ref()) }, - ); - - if field.fold { - quote! { self.#ident = Some(#value); } - } else { - quote! { - if self.#ident.is_none() { - self.#ident = Some(#value); - } - } - } - }); - - // Creation of the `fields` dictionary for inherent fields. - let field_inserts = visible_non_ghost().map(|field| { - let Field { ident, name, .. } = field; - let string = quote! { #name.into() }; - - if field.required { - quote! { - fields.insert(#string, #into_value(self.#ident.clone())); - } - } else { - quote! { - if let Some(value) = &self.#ident { - fields.insert(#string, #into_value(value.clone())); - } - } - } - }); - - let Elem { ident, .. } = element; - - let result = quote! { - Result<#foundations::Value, #foundations::FieldAccessError> - }; - - quote! { - impl #foundations::Fields for #ident { - type Enum = Fields; - - fn has(&self, id: u8) -> bool { - let Ok(id) = Fields::try_from(id) else { - return false; - }; - - match id { - #(#has_arms,)* - _ => false, - } - } - - fn field(&self, id: u8) -> #result { - let id = Fields::try_from(id)?; - match id { - #(#field_arms,)* - // This arm might be reached if someone tries to access an - // internal field. - _ => Err(#foundations::FieldAccessError::Unknown), - } - } - - fn field_with_styles(&self, id: u8, styles: #foundations::StyleChain) -> #result { - let id = Fields::try_from(id)?; - match id { - #(#field_with_styles_arms,)* - // This arm might be reached if someone tries to access an - // internal field. - _ => Err(#foundations::FieldAccessError::Unknown), - } - } - - fn field_from_styles(id: u8, styles: #foundations::StyleChain) -> #result { - let id = Fields::try_from(id)?; - match id { - #(#field_from_styles_arms,)* - // This arm might be reached if someone tries to access an - // internal field. - _ => Err(#foundations::FieldAccessError::Unknown), - } - } - - fn materialize(&mut self, styles: #foundations::StyleChain) { - #(#materializes)* - } - - fn fields(&self) -> #foundations::Dict { - let mut fields = #foundations::Dict::new(); - #(#field_inserts)* - fields - } - } - } -} - -/// 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 #foundations::Repr for #ident { - fn repr(&self) -> ::ecow::EcoString { - let fields = #foundations::Fields::fields(self) - .into_iter() - .map(|(name, value)| ::ecow::eco_format!("{}: {}", name, value.repr())) - .collect::<Vec<_>>(); - ::ecow::eco_format!( - #repr_format, - #foundations::repr::pretty_array_like(&fields, false), - ) - } + |capability| { + let dangling = ::std::ptr::NonNull::<#foundations::Packed<#ident>>::dangling().as_ptr(); + #(#checks)* + None } } } @@ -1062,15 +673,3 @@ fn create_mathy_impl(element: &Elem) -> TokenStream { let ident = &element.ident; quote! { impl ::typst_library::math::Mathy for #foundations::Packed<#ident> {} } } - -/// Creates the element's `IntoValue` implementation. -fn create_into_value_impl(element: &Elem) -> TokenStream { - let Elem { ident, .. } = element; - quote! { - impl #foundations::IntoValue for #ident { - fn into_value(self) -> #foundations::Value { - #foundations::Value::Content(#foundations::Content::new(self)) - } - } - } -} diff --git a/crates/typst-pdf/src/convert.rs b/crates/typst-pdf/src/convert.rs index 645d56f1..9e2aa87b 100644 --- a/crates/typst-pdf/src/convert.rs +++ b/crates/typst-pdf/src/convert.rs @@ -597,7 +597,7 @@ fn collect_named_destinations( let mut seen = HashSet::new(); document .introspector - .query(&HeadingElem::elem().select()) + .query(&HeadingElem::ELEM.select()) .iter() .filter_map(|elem| elem.location().zip(elem.label())) .filter(|&(_, label)| seen.insert(label)) diff --git a/crates/typst-pdf/src/embed.rs b/crates/typst-pdf/src/embed.rs index 36330c44..c62178cf 100644 --- a/crates/typst-pdf/src/embed.rs +++ b/crates/typst-pdf/src/embed.rs @@ -11,20 +11,24 @@ pub(crate) fn embed_files( typst_doc: &PagedDocument, document: &mut Document, ) -> SourceResult<()> { - let elements = typst_doc.introspector.query(&EmbedElem::elem().select()); + let elements = typst_doc.introspector.query(&EmbedElem::ELEM.select()); for elem in &elements { let embed = elem.to_packed::<EmbedElem>().unwrap(); let span = embed.span(); let derived_path = &embed.path.derived; let path = derived_path.to_string(); - let mime_type = - embed.mime_type(StyleChain::default()).clone().map(|s| s.to_string()); + let mime_type = embed + .mime_type + .get_ref(StyleChain::default()) + .as_ref() + .map(|s| s.to_string()); let description = embed - .description(StyleChain::default()) - .clone() + .description + .get_ref(StyleChain::default()) + .as_ref() .map(|s| s.to_string()); - let association_kind = match embed.relationship(StyleChain::default()) { + let association_kind = match embed.relationship.get(StyleChain::default()) { None => AssociationKind::Unspecified, Some(e) => match e { EmbeddedFileRelationship::Source => AssociationKind::Source, diff --git a/crates/typst-pdf/src/outline.rs b/crates/typst-pdf/src/outline.rs index e6324309..f73822c4 100644 --- a/crates/typst-pdf/src/outline.rs +++ b/crates/typst-pdf/src/outline.rs @@ -18,7 +18,7 @@ pub(crate) fn build_outline(gc: &GlobalContext) -> Outline { // Therefore, its next descendant must be added at its level, which is // enforced in the manner shown below. let mut last_skipped_level = None; - let elements = &gc.document.introspector.query(&HeadingElem::elem().select()); + let elements = &gc.document.introspector.query(&HeadingElem::ELEM.select()); for elem in elements.iter() { if let Some(page_ranges) = &gc.options.page_ranges { @@ -115,8 +115,9 @@ impl<'a> HeadingNode<'a> { level: element.resolve_level(StyleChain::default()), // 'bookmarked' set to 'auto' falls back to the value of 'outlined'. bookmarked: element - .bookmarked(StyleChain::default()) - .unwrap_or_else(|| element.outlined(StyleChain::default())), + .bookmarked + .get(StyleChain::default()) + .unwrap_or_else(|| element.outlined.get(StyleChain::default())), element, children: Vec::new(), } diff --git a/crates/typst-realize/src/lib.rs b/crates/typst-realize/src/lib.rs index 526f4631..fcfb4066 100644 --- a/crates/typst-realize/src/lib.rs +++ b/crates/typst-realize/src/lib.rs @@ -590,7 +590,7 @@ fn visit_styled<'a>( let mut pagebreak = false; for style in local.iter() { let Some(elem) = style.element() else { continue }; - if elem == DocumentElem::elem() { + if elem == DocumentElem::ELEM { if let Some(info) = s.kind.as_document_mut() { info.populate(&local) } else { @@ -599,7 +599,7 @@ fn visit_styled<'a>( "document set rules are not allowed inside of containers" ); } - } else if elem == PageElem::elem() { + } else if elem == PageElem::ELEM { if !matches!(s.kind, RealizationKind::LayoutDocument(_)) { bail!( style.span(), @@ -633,7 +633,7 @@ fn visit_styled<'a>( if pagebreak { let relevant = local .as_slice() - .trim_end_matches(|style| style.element() != Some(PageElem::elem())); + .trim_end_matches(|style| style.element() != Some(PageElem::ELEM)); visit(s, PagebreakElem::shared_weak(), outer.chain(relevant))?; } @@ -722,7 +722,9 @@ fn visit_filter_rules<'a>( s.saw_parbreak = true; return Ok(true); } else if !s.may_attach - && content.to_packed::<VElem>().is_some_and(|elem| elem.attach(styles)) + && content + .to_packed::<VElem>() + .is_some_and(|elem| elem.attach.get(styles)) { // Attach spacing collapses if not immediately following a paragraph. return Ok(true); @@ -867,11 +869,11 @@ static TEXTUAL: GroupingRule = GroupingRule { // Note that `SymbolElem` converts into `TextElem` before textual show // rules run, and we apply textual rules to elements manually during // math realization, so we don't check for it here. - elem == TextElem::elem() - || elem == LinebreakElem::elem() - || elem == SmartQuoteElem::elem() + elem == TextElem::ELEM + || elem == LinebreakElem::ELEM + || elem == SmartQuoteElem::ELEM }, - inner: |content| content.elem() == SpaceElem::elem(), + inner: |content| content.elem() == SpaceElem::ELEM, // Any kind of style interrupts this kind of grouping since regex show // rules cannot match over style changes anyway. interrupt: |_| true, @@ -884,19 +886,19 @@ static PAR: GroupingRule = GroupingRule { tags: true, trigger: |content, kind| { let elem = content.elem(); - elem == TextElem::elem() - || elem == HElem::elem() - || elem == LinebreakElem::elem() - || elem == SmartQuoteElem::elem() - || elem == InlineElem::elem() - || elem == BoxElem::elem() + elem == TextElem::ELEM + || elem == HElem::ELEM + || elem == LinebreakElem::ELEM + || elem == SmartQuoteElem::ELEM + || elem == InlineElem::ELEM + || elem == BoxElem::ELEM || (kind.is_html() && content .to_packed::<HtmlElem>() .is_some_and(|elem| tag::is_inline_by_default(elem.tag))) }, - inner: |content| content.elem() == SpaceElem::elem(), - interrupt: |elem| elem == ParElem::elem() || elem == AlignElem::elem(), + inner: |content| content.elem() == SpaceElem::ELEM, + interrupt: |elem| elem == ParElem::ELEM || elem == AlignElem::ELEM, finish: finish_par, }; @@ -904,10 +906,10 @@ static PAR: GroupingRule = GroupingRule { static CITES: GroupingRule = GroupingRule { priority: 2, tags: false, - trigger: |content, _| content.elem() == CiteElem::elem(), - inner: |content| content.elem() == SpaceElem::elem(), + trigger: |content, _| content.elem() == CiteElem::ELEM, + inner: |content| content.elem() == SpaceElem::ELEM, interrupt: |elem| { - elem == CiteGroup::elem() || elem == ParElem::elem() || elem == AlignElem::elem() + elem == CiteGroup::ELEM || elem == ParElem::ELEM || elem == AlignElem::ELEM }, finish: finish_cites, }; @@ -926,12 +928,12 @@ const fn list_like_grouping<T: ListLike>() -> GroupingRule { GroupingRule { priority: 2, tags: false, - trigger: |content, _| content.elem() == T::Item::elem(), + trigger: |content, _| content.elem() == T::Item::ELEM, inner: |content| { let elem = content.elem(); - elem == SpaceElem::elem() || elem == ParbreakElem::elem() + elem == SpaceElem::ELEM || elem == ParbreakElem::ELEM }, - interrupt: |elem| elem == T::elem() || elem == AlignElem::elem(), + interrupt: |elem| elem == T::ELEM || elem == AlignElem::ELEM, finish: finish_list_like::<T>, } } @@ -1118,7 +1120,7 @@ fn find_regex_match_in_elems<'a>( buf.push('\n'); SpaceState::Destructive } else if let Some(elem) = content.to_packed::<SmartQuoteElem>() { - buf.push(if elem.double(styles) { '"' } else { '\'' }); + buf.push(if elem.double.get(styles) { '"' } else { '\'' }); SpaceState::Supportive } else if let Some(elem) = content.to_packed::<TextElem>() { buf.push_str(&elem.text); @@ -1310,7 +1312,7 @@ fn collapse_spaces(buf: &mut Vec<Pair>, start: usize) { } else if content.is::<LinebreakElem>() { destruct_space(buf, &mut k, &mut state); } else if let Some(elem) = content.to_packed::<HElem>() { - if elem.amount.is_fractional() || elem.weak(styles) { + if elem.amount.is_fractional() || elem.weak.get(styles) { destruct_space(buf, &mut k, &mut state); } } else { diff --git a/crates/typst-utils/src/hash.rs b/crates/typst-utils/src/hash.rs index 9687da20..eaf8a672 100644 --- a/crates/typst-utils/src/hash.rs +++ b/crates/typst-utils/src/hash.rs @@ -30,9 +30,10 @@ use siphasher::sip128::{Hasher128, SipHasher13}; /// # Usage /// If the value is expected to be cloned, it is best used inside of an `Arc` /// or `Rc` to best re-use the hash once it has been computed. +#[derive(Clone)] pub struct LazyHash<T: ?Sized> { /// The hash for the value. - hash: AtomicU128, + hash: HashLock, /// The underlying value. value: T, } @@ -48,16 +49,7 @@ impl<T> LazyHash<T> { /// Wraps an item without pre-computed hash. #[inline] pub fn new(value: T) -> Self { - Self { hash: AtomicU128::new(0), value } - } - - /// Wrap an item with a pre-computed hash. - /// - /// **Important:** The hash must be correct for the value. This cannot be - /// enforced at compile time, so use with caution. - #[inline] - pub fn reuse<U: ?Sized>(value: T, existing: &LazyHash<U>) -> Self { - LazyHash { hash: AtomicU128::new(existing.load_hash()), value } + Self { hash: HashLock::new(), value } } /// Returns the wrapped value. @@ -67,33 +59,11 @@ impl<T> LazyHash<T> { } } -impl<T: ?Sized> LazyHash<T> { - /// Get the hash, returns zero if not computed yet. - #[inline] - fn load_hash(&self) -> u128 { - // We only need atomicity and no synchronization of other operations, so - // `Relaxed` is fine. - self.hash.load(Ordering::Relaxed) - } -} - impl<T: Hash + ?Sized + 'static> LazyHash<T> { /// Get the hash or compute it if not set yet. #[inline] fn load_or_compute_hash(&self) -> u128 { - let mut hash = self.load_hash(); - if hash == 0 { - hash = hash_item(&self.value); - self.hash.store(hash, Ordering::Relaxed); - } - hash - } - - /// Reset the hash to zero. - #[inline] - fn reset_hash(&mut self) { - // Because we have a mutable reference, we can skip the atomic. - *self.hash.get_mut() = 0; + self.hash.get_or_insert_with(|| hash_item(&self.value)) } } @@ -140,23 +110,14 @@ impl<T: ?Sized> Deref for LazyHash<T> { } } -impl<T: Hash + ?Sized + 'static> DerefMut for LazyHash<T> { +impl<T: ?Sized + 'static> DerefMut for LazyHash<T> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { - self.reset_hash(); + self.hash.reset(); &mut self.value } } -impl<T: Hash + Clone + 'static> Clone for LazyHash<T> { - fn clone(&self) -> Self { - Self { - hash: AtomicU128::new(self.load_hash()), - value: self.value.clone(), - } - } -} - impl<T: Debug> Debug for LazyHash<T> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.value.fmt(f) @@ -233,3 +194,51 @@ impl<T: Debug> Debug for ManuallyHash<T> { self.value.fmt(f) } } + +/// Storage for lazy hash computation. +pub struct HashLock(AtomicU128); + +impl HashLock { + /// Create a new unset hash cell. + pub const fn new() -> Self { + Self(AtomicU128::new(0)) + } + + /// Get the hash or compute it if not set yet. + #[inline] + pub fn get_or_insert_with(&self, f: impl FnOnce() -> u128) -> u128 { + let mut hash = self.get(); + if hash == 0 { + hash = f(); + self.0.store(hash, Ordering::Relaxed); + } + hash + } + + /// Reset the hash to unset. + #[inline] + pub fn reset(&mut self) { + // Because we have a mutable reference, we can skip the atomic. + *self.0.get_mut() = 0; + } + + /// Get the hash, returns zero if not computed yet. + #[inline] + fn get(&self) -> u128 { + // We only need atomicity and no synchronization of other operations, so + // `Relaxed` is fine. + self.0.load(Ordering::Relaxed) + } +} + +impl Default for HashLock { + fn default() -> Self { + Self::new() + } +} + +impl Clone for HashLock { + fn clone(&self) -> Self { + Self(AtomicU128::new(self.get())) + } +} diff --git a/crates/typst-utils/src/lib.rs b/crates/typst-utils/src/lib.rs index 8d0cdc15..6a833b17 100644 --- a/crates/typst-utils/src/lib.rs +++ b/crates/typst-utils/src/lib.rs @@ -15,7 +15,7 @@ mod scalar; pub use self::bitset::{BitSet, SmallBitSet}; pub use self::deferred::Deferred; pub use self::duration::format_duration; -pub use self::hash::{LazyHash, ManuallyHash}; +pub use self::hash::{HashLock, LazyHash, ManuallyHash}; pub use self::pico::{PicoStr, ResolvedPicoStr}; pub use self::round::{round_int_with_precision, round_with_precision}; pub use self::scalar::Scalar; diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs index a6bb4fe3..eee7966a 100644 --- a/crates/typst/src/lib.rs +++ b/crates/typst/src/lib.rs @@ -98,7 +98,7 @@ fn compile_impl<D: Document>( let library = world.library(); let base = StyleChain::new(&library.styles); - let target = TargetElem::set_target(D::TARGET).wrap(); + let target = TargetElem::target.set(D::TARGET).wrap(); let styles = base.chain(&target); let empty_introspector = Introspector::default(); |
