summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml12
-rw-r--r--crates/typst-eval/src/markup.rs9
-rw-r--r--crates/typst-eval/src/math.rs6
-rw-r--r--crates/typst-eval/src/rules.rs5
-rw-r--r--crates/typst-html/src/lib.rs14
-rw-r--r--crates/typst-ide/src/definition.rs2
-rw-r--r--crates/typst-ide/src/tests.rs10
-rw-r--r--crates/typst-layout/src/flow/block.rs38
-rw-r--r--crates/typst-layout/src/flow/collect.rs42
-rw-r--r--crates/typst-layout/src/flow/compose.rs4
-rw-r--r--crates/typst-layout/src/flow/mod.rs20
-rw-r--r--crates/typst-layout/src/grid/layouter.rs2
-rw-r--r--crates/typst-layout/src/image.rs8
-rw-r--r--crates/typst-layout/src/inline/box.rs21
-rw-r--r--crates/typst-layout/src/inline/collect.rs26
-rw-r--r--crates/typst-layout/src/inline/line.rs12
-rw-r--r--crates/typst-layout/src/inline/linebreak.rs6
-rw-r--r--crates/typst-layout/src/inline/mod.rs50
-rw-r--r--crates/typst-layout/src/inline/shaping.rs41
-rw-r--r--crates/typst-layout/src/lists.rs50
-rw-r--r--crates/typst-layout/src/math/accent.rs4
-rw-r--r--crates/typst-layout/src/math/attach.rs19
-rw-r--r--crates/typst-layout/src/math/cancel.rs16
-rw-r--r--crates/typst-layout/src/math/frac.rs2
-rw-r--r--crates/typst-layout/src/math/fragment.rs25
-rw-r--r--crates/typst-layout/src/math/lr.rs4
-rw-r--r--crates/typst-layout/src/math/mat.rs27
-rw-r--r--crates/typst-layout/src/math/mod.rs35
-rw-r--r--crates/typst-layout/src/math/root.rs6
-rw-r--r--crates/typst-layout/src/math/run.rs6
-rw-r--r--crates/typst-layout/src/math/shared.rs53
-rw-r--r--crates/typst-layout/src/math/stretch.rs9
-rw-r--r--crates/typst-layout/src/math/text.rs12
-rw-r--r--crates/typst-layout/src/math/underover.rs18
-rw-r--r--crates/typst-layout/src/modifiers.rs8
-rw-r--r--crates/typst-layout/src/pad.rs10
-rw-r--r--crates/typst-layout/src/pages/collect.rs8
-rw-r--r--crates/typst-layout/src/pages/run.rs42
-rw-r--r--crates/typst-layout/src/repeat.rs6
-rw-r--r--crates/typst-layout/src/shapes.rs98
-rw-r--r--crates/typst-layout/src/stack.rs10
-rw-r--r--crates/typst-layout/src/transforms.rs24
-rw-r--r--crates/typst-library/src/foundations/content/element.rs (renamed from crates/typst-library/src/foundations/element.rs)216
-rw-r--r--crates/typst-library/src/foundations/content/field.rs564
-rw-r--r--crates/typst-library/src/foundations/content/mod.rs (renamed from crates/typst-library/src/foundations/content.rs)434
-rw-r--r--crates/typst-library/src/foundations/content/packed.rs147
-rw-r--r--crates/typst-library/src/foundations/content/raw.rs426
-rw-r--r--crates/typst-library/src/foundations/content/vtable.rs383
-rw-r--r--crates/typst-library/src/foundations/mod.rs2
-rw-r--r--crates/typst-library/src/foundations/scope.rs11
-rw-r--r--crates/typst-library/src/foundations/selector.rs4
-rw-r--r--crates/typst-library/src/foundations/styles.rs222
-rw-r--r--crates/typst-library/src/foundations/target.rs2
-rw-r--r--crates/typst-library/src/html/mod.rs7
-rw-r--r--crates/typst-library/src/html/typed.rs4
-rw-r--r--crates/typst-library/src/introspection/counter.rs26
-rw-r--r--crates/typst-library/src/introspection/state.rs4
-rw-r--r--crates/typst-library/src/layout/align.rs10
-rw-r--r--crates/typst-library/src/layout/columns.rs1
-rw-r--r--crates/typst-library/src/layout/container.rs11
-rw-r--r--crates/typst-library/src/layout/em.rs2
-rw-r--r--crates/typst-library/src/layout/grid/mod.rs12
-rw-r--r--crates/typst-library/src/layout/grid/resolve.rs176
-rw-r--r--crates/typst-library/src/layout/hide.rs2
-rw-r--r--crates/typst-library/src/layout/page.rs10
-rw-r--r--crates/typst-library/src/layout/place.rs1
-rw-r--r--crates/typst-library/src/math/accent.rs1
-rw-r--r--crates/typst-library/src/math/attach.rs5
-rw-r--r--crates/typst-library/src/math/cancel.rs2
-rw-r--r--crates/typst-library/src/math/equation.rs40
-rw-r--r--crates/typst-library/src/math/lr.rs3
-rw-r--r--crates/typst-library/src/math/matrix.rs7
-rw-r--r--crates/typst-library/src/math/style.rs34
-rw-r--r--crates/typst-library/src/model/bibliography.rs53
-rw-r--r--crates/typst-library/src/model/cite.rs5
-rw-r--r--crates/typst-library/src/model/document.rs31
-rw-r--r--crates/typst-library/src/model/emph.rs4
-rw-r--r--crates/typst-library/src/model/enum.rs18
-rw-r--r--crates/typst-library/src/model/figure.rs94
-rw-r--r--crates/typst-library/src/model/footnote.rs17
-rw-r--r--crates/typst-library/src/model/heading.rs50
-rw-r--r--crates/typst-library/src/model/link.rs4
-rw-r--r--crates/typst-library/src/model/list.rs12
-rw-r--r--crates/typst-library/src/model/outline.rs36
-rw-r--r--crates/typst-library/src/model/par.rs3
-rw-r--r--crates/typst-library/src/model/quote.rs33
-rw-r--r--crates/typst-library/src/model/reference.rs15
-rw-r--r--crates/typst-library/src/model/strong.rs4
-rw-r--r--crates/typst-library/src/model/table.rs18
-rw-r--r--crates/typst-library/src/model/terms.rs27
-rw-r--r--crates/typst-library/src/pdf/embed.rs5
-rw-r--r--crates/typst-library/src/text/case.rs4
-rw-r--r--crates/typst-library/src/text/deco.rs113
-rw-r--r--crates/typst-library/src/text/lang.rs2
-rw-r--r--crates/typst-library/src/text/mod.rs59
-rw-r--r--crates/typst-library/src/text/raw.rs44
-rw-r--r--crates/typst-library/src/text/shift.rs33
-rw-r--r--crates/typst-library/src/text/smallcaps.rs5
-rw-r--r--crates/typst-library/src/text/smartquote.rs3
-rw-r--r--crates/typst-library/src/visualize/curve.rs1
-rw-r--r--crates/typst-library/src/visualize/image/mod.rs17
-rw-r--r--crates/typst-library/src/visualize/line.rs4
-rw-r--r--crates/typst-library/src/visualize/path.rs1
-rw-r--r--crates/typst-library/src/visualize/polygon.rs5
-rw-r--r--crates/typst-library/src/visualize/shape.rs34
-rw-r--r--crates/typst-macros/src/elem.rs809
-rw-r--r--crates/typst-pdf/src/convert.rs2
-rw-r--r--crates/typst-pdf/src/embed.rs16
-rw-r--r--crates/typst-pdf/src/outline.rs7
-rw-r--r--crates/typst-realize/src/lib.rs50
-rw-r--r--crates/typst-utils/src/hash.rs99
-rw-r--r--crates/typst-utils/src/lib.rs2
-rw-r--r--crates/typst/src/lib.rs2
-rw-r--r--docs/src/lib.rs8
-rw-r--r--tests/src/world.rs10
115 files changed, 3087 insertions, 2261 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2354de58..70518860 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -103,3 +103,15 @@ jobs:
- uses: Swatinem/rust-cache@v2
- run: cargo install --locked cargo-fuzz@0.12.0
- run: cd tests/fuzz && cargo fuzz build --dev
+
+ miri:
+ name: Check unsafe code
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ components: miri
+ toolchain: nightly-2024-10-29
+ - uses: Swatinem/rust-cache@v2
+ - run: cargo miri test -p typst-library test_miri
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();
diff --git a/docs/src/lib.rs b/docs/src/lib.rs
index dc6b62c7..ddc956e6 100644
--- a/docs/src/lib.rs
+++ b/docs/src/lib.rs
@@ -63,12 +63,10 @@ static LIBRARY: LazyLock<LazyHash<Library>> = LazyLock::new(|| {
scope.reset_category();
// Adjust the default look.
+ lib.styles.set(PageElem::width, Smart::Custom(Abs::pt(240.0).into()));
+ lib.styles.set(PageElem::height, Smart::Auto);
lib.styles
- .set(PageElem::set_width(Smart::Custom(Abs::pt(240.0).into())));
- lib.styles.set(PageElem::set_height(Smart::Auto));
- lib.styles.set(PageElem::set_margin(Margin::splat(Some(Smart::Custom(
- Abs::pt(15.0).into(),
- )))));
+ .set(PageElem::margin, Margin::splat(Some(Smart::Custom(Abs::pt(15.0).into()))));
LazyHash::new(lib)
});
diff --git a/tests/src/world.rs b/tests/src/world.rs
index 6fa1cf60..9b16d612 100644
--- a/tests/src/world.rs
+++ b/tests/src/world.rs
@@ -214,13 +214,11 @@ fn library() -> Library {
.define("forest", Color::from_u8(0x43, 0xA1, 0x27, 0xFF));
// Hook up default styles.
+ 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
}