diff --git a/docs/src/html.rs b/docs/src/html.rs index 33003349c..fc9d7d9e3 100644 --- a/docs/src/html.rs +++ b/docs/src/html.rs @@ -311,7 +311,7 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html { Ok(doc) => doc.pages, Err(err) => { let msg = &err[0].message; - panic!("while trying to compile {text}:\n\nerror: {msg}"); + panic!("while trying to compile:\n{text}:\n\nerror: {msg}"); } }; diff --git a/library/src/layout/align.rs b/library/src/layout/align.rs index fdd4d0ea9..88815dc9f 100644 --- a/library/src/layout/align.rs +++ b/library/src/layout/align.rs @@ -15,9 +15,6 @@ use crate::prelude::*; /// Display: Align /// Category: layout #[node(Show)] -#[set({ - styles.set(Self::set_alignment(args.find()?.unwrap_or_default())); -})] pub struct AlignNode { /// The alignment along both axes. /// @@ -50,10 +47,8 @@ pub struct AlignNode { /// rect(inset: 12pt)[ركن] /// ) /// ``` - #[settable] #[positional] #[fold] - #[skip] #[default(Axes::new(GenAlign::Start, GenAlign::Specific(Align::Top)))] pub alignment: Axes>, @@ -64,7 +59,9 @@ pub struct AlignNode { } impl Show for AlignNode { - fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult { - Ok(self.body()) + fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult { + Ok(self + .body() + .styled(Self::set_alignment(self.alignment(styles).map(Some)))) } } diff --git a/library/src/layout/columns.rs b/library/src/layout/columns.rs index 27339628f..58b369c61 100644 --- a/library/src/layout/columns.rs +++ b/library/src/layout/columns.rs @@ -36,19 +36,18 @@ use crate::text::TextNode; pub struct ColumnsNode { /// The number of columns. #[positional] - #[required] + #[default(NonZeroUsize::new(2).unwrap())] pub count: NonZeroUsize, + /// The size of the gutter space between each column. + #[resolve] + #[default(Ratio::new(0.04).into())] + pub gutter: Rel, + /// The content that should be layouted into the columns. #[positional] #[required] pub body: Content, - - /// The size of the gutter space between each column. - #[settable] - #[resolve] - #[default(Ratio::new(0.04).into())] - pub gutter: Rel, } impl Layout for ColumnsNode { @@ -67,8 +66,8 @@ impl Layout for ColumnsNode { } // Determine the width of the gutter and each column. - let columns = self.count().get(); - let gutter = Self::gutter_in(styles).relative_to(regions.base().x); + let columns = self.count(styles).get(); + let gutter = self.gutter(styles).relative_to(regions.base().x); let width = (regions.size.x - gutter * (columns - 1) as f64) / columns as f64; let backlog: Vec<_> = std::iter::once(®ions.size.y) @@ -157,14 +156,13 @@ impl Layout for ColumnsNode { pub struct ColbreakNode { /// If `{true}`, the column break is skipped if the current column is /// already empty. - #[named] #[default(false)] pub weak: bool, } impl Behave for ColbreakNode { fn behaviour(&self) -> Behaviour { - if self.weak() { + if self.weak(StyleChain::default()) { Behaviour::Weak(1) } else { Behaviour::Destructive diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index bdce1922f..31a80aa24 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -23,11 +23,6 @@ use crate::prelude::*; /// Category: layout #[node(Layout)] pub struct BoxNode { - /// The contents of the box. - #[positional] - #[default] - pub body: Option, - /// The width of the box. /// /// Boxes can have [fractional]($type/fraction) widths, as the example @@ -40,13 +35,9 @@ pub struct BoxNode { /// ```example /// Line in #box(width: 1fr, line(length: 100%)) between. /// ``` - #[named] - #[default] pub width: Sizing, /// The height of the box. - #[named] - #[default] pub height: Smart>, /// An amount to shift the box's baseline by. @@ -54,39 +45,29 @@ pub struct BoxNode { /// ```example /// Image: #box(baseline: 40%, image("tiger.jpg", width: 2cm)). /// ``` - #[settable] #[resolve] - #[default] pub baseline: Rel, /// The box's background color. See the /// [rectangle's documentation]($func/rect.fill) for more details. - #[settable] - #[default] pub fill: Option, /// The box's border color. See the /// [rectangle's documentation]($func/rect.stroke) for more details. - #[settable] #[resolve] #[fold] - #[default] pub stroke: Sides>>, /// How much to round the box's corners. See the [rectangle's /// documentation]($func/rect.radius) for more details. - #[settable] #[resolve] #[fold] - #[default] pub radius: Corners>>, /// How much to pad the box's content. See the [rectangle's /// documentation]($func/rect.inset) for more details. - #[settable] #[resolve] #[fold] - #[default] pub inset: Sides>>, /// How much to expand the box's size without affecting the layout. @@ -103,11 +84,13 @@ pub struct BoxNode { /// outset: (y: 3pt), /// radius: 2pt, /// )[rectangle]. - #[settable] #[resolve] #[fold] - #[default] pub outset: Sides>>, + + /// The contents of the box. + #[positional] + pub body: Option, } impl Layout for BoxNode { @@ -117,14 +100,14 @@ impl Layout for BoxNode { styles: StyleChain, regions: Regions, ) -> SourceResult { - let width = match self.width() { + let width = match self.width(styles) { Sizing::Auto => Smart::Auto, Sizing::Rel(rel) => Smart::Custom(rel), Sizing::Fr(_) => Smart::Custom(Ratio::one().into()), }; // Resolve the sizing to a concrete size. - let sizing = Axes::new(width, self.height()); + let sizing = Axes::new(width, self.height(styles)); let expand = sizing.as_ref().map(Smart::is_custom); let size = sizing .resolve(styles) @@ -133,8 +116,8 @@ impl Layout for BoxNode { .unwrap_or(regions.base()); // Apply inset. - let mut body = self.body().unwrap_or_default(); - let inset = Self::inset_in(styles); + let mut body = self.body(styles).unwrap_or_default(); + let inset = self.inset(styles); if inset.iter().any(|v| !v.is_zero()) { body = body.padded(inset.map(|side| side.map(Length::from))); } @@ -145,20 +128,19 @@ impl Layout for BoxNode { let mut frame = body.layout(vt, styles, pod)?.into_frame(); // Apply baseline shift. - let shift = Self::baseline_in(styles).relative_to(frame.height()); + let shift = self.baseline(styles).relative_to(frame.height()); if !shift.is_zero() { frame.set_baseline(frame.baseline() - shift); } // Prepare fill and stroke. - let fill = Self::fill_in(styles); - let stroke = - Self::stroke_in(styles).map(|s| s.map(PartialStroke::unwrap_or_default)); + let fill = self.fill(styles); + let stroke = self.stroke(styles).map(|s| s.map(PartialStroke::unwrap_or_default)); // Add fill and/or stroke. if fill.is_some() || stroke.iter().any(Option::is_some) { - let outset = Self::outset_in(styles); - let radius = Self::radius_in(styles); + let outset = self.outset(styles); + let radius = self.radius(styles); frame.fill_and_stroke(fill, stroke, outset, radius); } @@ -216,27 +198,7 @@ impl Layout for BoxNode { /// Display: Block /// Category: layout #[node(Layout)] -#[set({ - let spacing = args.named("spacing")?; - styles.set_opt( - args.named("above")? - .map(VNode::block_around) - .or_else(|| spacing.map(VNode::block_spacing)) - .map(Self::set_above), - ); - styles.set_opt( - args.named("below")? - .map(VNode::block_around) - .or_else(|| spacing.map(VNode::block_spacing)) - .map(Self::set_below), - ); -})] pub struct BlockNode { - /// The contents of the block. - #[positional] - #[default] - pub body: Option, - /// The block's width. /// /// ```example @@ -248,8 +210,6 @@ pub struct BlockNode { /// lorem(10), /// ) /// ``` - #[named] - #[default] pub width: Smart>, /// The block's height. When the height is larger than the remaining space on @@ -265,8 +225,6 @@ pub struct BlockNode { /// fill: aqua, /// ) /// ``` - #[named] - #[default] pub height: Smart>, /// Whether the block can be broken and continue on the next page. @@ -281,55 +239,48 @@ pub struct BlockNode { /// lorem(15), /// ) /// ``` - #[settable] #[default(true)] pub breakable: bool, /// The block's background color. See the /// [rectangle's documentation]($func/rect.fill) for more details. - #[settable] - #[default] pub fill: Option, /// The block's border color. See the /// [rectangle's documentation]($func/rect.stroke) for more details. - #[settable] #[resolve] #[fold] - #[default] pub stroke: Sides>>, /// How much to round the block's corners. See the [rectangle's /// documentation]($func/rect.radius) for more details. - #[settable] #[resolve] #[fold] - #[default] pub radius: Corners>>, /// How much to pad the block's content. See the [rectangle's /// documentation]($func/rect.inset) for more details. - #[settable] #[resolve] #[fold] - #[default] pub inset: Sides>>, /// How much to expand the block's size without affecting the layout. See /// the [rectangle's documentation]($func/rect.outset) for more details. - #[settable] #[resolve] #[fold] - #[default] pub outset: Sides>>, - /// The spacing between this block and its predecessor. Takes precedence over - /// `spacing`. Can be used in combination with a show rule to adjust the - /// spacing around arbitrary block-level elements. + /// The spacing between this block and its predecessor. Takes precedence + /// over `spacing`. Can be used in combination with a show rule to adjust + /// the spacing around arbitrary block-level elements. /// /// The default value is `{1.2em}`. - #[settable] - #[skip] + #[parse( + let spacing = args.named("spacing")?; + args.named("above")? + .map(VNode::block_around) + .or_else(|| spacing.map(VNode::block_spacing)) + )] #[default(VNode::block_spacing(Em::new(1.2).into()))] pub above: VNode, @@ -337,16 +288,22 @@ pub struct BlockNode { /// over `spacing`. /// /// The default value is `{1.2em}`. - #[settable] - #[skip] + #[parse( + args.named("below")? + .map(VNode::block_around) + .or_else(|| spacing.map(VNode::block_spacing)) + )] #[default(VNode::block_spacing(Em::new(1.2).into()))] pub below: VNode, + /// The contents of the block. + #[positional] + pub body: Option, + /// Whether this block must stick to the following one. /// /// Use this to prevent page breaks between e.g. a heading and its body. - #[settable] - #[skip] + #[internal] #[default(false)] pub sticky: bool, } @@ -359,14 +316,14 @@ impl Layout for BlockNode { regions: Regions, ) -> SourceResult { // Apply inset. - let mut body = self.body().unwrap_or_default(); - let inset = Self::inset_in(styles); + let mut body = self.body(styles).unwrap_or_default(); + let inset = self.inset(styles); if inset.iter().any(|v| !v.is_zero()) { body = body.clone().padded(inset.map(|side| side.map(Length::from))); } // Resolve the sizing to a concrete size. - let sizing = Axes::new(self.width(), self.height()); + let sizing = Axes::new(self.width(styles), self.height(styles)); let mut expand = sizing.as_ref().map(Smart::is_custom); let mut size = sizing .resolve(styles) @@ -375,7 +332,7 @@ impl Layout for BlockNode { .unwrap_or(regions.base()); // Layout the child. - let mut frames = if Self::breakable_in(styles) { + let mut frames = if self.breakable(styles) { // Measure to ensure frames for all regions have the same width. if sizing.x == Smart::Auto { let pod = Regions::one(size, Axes::splat(false)); @@ -413,9 +370,8 @@ impl Layout for BlockNode { }; // Prepare fill and stroke. - let fill = Self::fill_in(styles); - let stroke = - Self::stroke_in(styles).map(|s| s.map(PartialStroke::unwrap_or_default)); + let fill = self.fill(styles); + let stroke = self.stroke(styles).map(|s| s.map(PartialStroke::unwrap_or_default)); // Add fill and/or stroke. if fill.is_some() || stroke.iter().any(Option::is_some) { @@ -424,8 +380,8 @@ impl Layout for BlockNode { skip = first.is_empty() && rest.iter().any(|frame| !frame.is_empty()); } - let outset = Self::outset_in(styles); - let radius = Self::radius_in(styles); + let outset = self.outset(styles); + let radius = self.radius(styles); for frame in frames.iter_mut().skip(skip as usize) { frame.fill_and_stroke(fill, stroke, outset, radius); } diff --git a/library/src/layout/enum.rs b/library/src/layout/enum.rs index 4876b6167..ee09d339a 100644 --- a/library/src/layout/enum.rs +++ b/library/src/layout/enum.rs @@ -48,37 +48,10 @@ use super::GridLayouter; /// content. All content that is indented more than an item's plus sign or dot /// becomes part of that item. /// -/// ## Parameters -/// - start: `NonZeroUsize` (named) -/// Which number to start the enumeration with. -/// -/// ```example -/// #enum( -/// start: 3, -/// [Skipping], -/// [Ahead], -/// ) -/// ``` -/// /// Display: Numbered List /// Category: layout -#[node(Construct, Layout)] +#[node(Layout)] pub struct EnumNode { - /// The numbered list's items. - /// - /// When using the enum syntax, adjacent items are automatically collected - /// into enumerations, even through constructs like for loops. - /// - /// ```example - /// #for phase in ( - /// "Launch", - /// "Orbit", - /// "Descent", - /// ) [+ #phase] - /// ``` - #[variadic] - pub children: Vec, - /// If this is `{false}`, the items are spaced apart with /// [enum spacing]($func/enum.spacing). If it is `{true}`, they use normal /// [leading]($func/par.leading) instead. This makes the enumeration more @@ -93,7 +66,6 @@ pub struct EnumNode { /// insert a blank line between the /// items. /// ``` - #[named] #[default(true)] pub tight: bool, @@ -116,10 +88,21 @@ pub struct EnumNode { /// + Superscript /// + Numbering! /// ``` - #[settable] #[default(Numbering::Pattern(NumberingPattern::from_str("1.").unwrap()))] pub numbering: Numbering, + /// Which number to start the enumeration with. + /// + /// ```example + /// #enum( + /// start: 3, + /// [Skipping], + /// [Ahead], + /// ) + /// ``` + #[default(NonZeroUsize::new(1).unwrap())] + pub start: NonZeroUsize, + /// Whether to display the full numbering, including the numbers of /// all parent enumerations. /// @@ -132,18 +115,14 @@ pub struct EnumNode { /// + Add integredients /// + Eat /// ``` - #[settable] #[default(false)] pub full: bool, /// The indentation of each item's label. - #[settable] #[resolve] - #[default] pub indent: Length, /// The space between the numbering and the body of each item. - #[settable] #[resolve] #[default(Em::new(0.5).into())] pub body_indent: Length, @@ -151,35 +130,29 @@ pub struct EnumNode { /// The spacing between the items of a wide (non-tight) enumeration. /// /// If set to `{auto}`, uses the spacing [below blocks]($func/block.below). - #[settable] - #[default] pub spacing: Smart, + /// The numbered list's items. + /// + /// When using the enum syntax, adjacent items are automatically collected + /// into enumerations, even through constructs like for loops. + /// + /// ```example + /// #for phase in ( + /// "Launch", + /// "Orbit", + /// "Descent", + /// ) [+ #phase] + /// ``` + #[variadic] + pub children: Vec, + /// The numbers of parent items. - #[settable] + #[internal] #[fold] - #[skip] - #[default] parents: Parent, } -impl Construct for EnumNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let mut items = args.all::()?; - if let Some(number) = args.named::("start")? { - if let Some(first) = items.first_mut() { - if first.number().is_none() { - *first = EnumItem::new(first.body()).with_number(Some(number)); - } - } - } - - Ok(Self::new(items) - .with_tight(args.named("tight")?.unwrap_or(true)) - .pack()) - } -} - impl Layout for EnumNode { fn layout( &self, @@ -187,23 +160,23 @@ impl Layout for EnumNode { styles: StyleChain, regions: Regions, ) -> SourceResult { - let numbering = Self::numbering_in(styles); - let indent = Self::indent_in(styles); - let body_indent = Self::body_indent_in(styles); - let gutter = if self.tight() { + let numbering = self.numbering(styles); + let indent = self.indent(styles); + let body_indent = self.body_indent(styles); + let gutter = if self.tight(styles) { ParNode::leading_in(styles).into() } else { - Self::spacing_in(styles) + self.spacing(styles) .unwrap_or_else(|| BlockNode::below_in(styles).amount()) }; let mut cells = vec![]; - let mut number = NonZeroUsize::new(1).unwrap(); - let mut parents = Self::parents_in(styles); - let full = Self::full_in(styles); + let mut number = self.start(styles); + let mut parents = self.parents(styles); + let full = self.full(styles); for item in self.children() { - number = item.number().unwrap_or(number); + number = item.number(styles).unwrap_or(number); let resolved = if full { parents.push(number); @@ -252,7 +225,6 @@ impl Layout for EnumNode { pub struct EnumItem { /// The item's number. #[positional] - #[default] pub number: Option, /// The item's body. diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index 5d679570d..7a063bcea 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -1,4 +1,4 @@ -use typst::model::{Style, StyledNode}; +use typst::model::StyledNode; use super::{AlignNode, BlockNode, ColbreakNode, ParNode, PlaceNode, Spacing, VNode}; use crate::prelude::*; @@ -40,8 +40,6 @@ impl Layout for FlowNode { if let Some(node) = child.to::() { layouter.layout_spacing(node, styles); } else if let Some(node) = child.to::() { - let barrier = Style::Barrier(child.id()); - let styles = styles.chain_one(&barrier); layouter.layout_par(vt, node, styles)?; } else if child.is::() || child.is::() @@ -49,8 +47,6 @@ impl Layout for FlowNode { || child.is::() || child.is::() { - let barrier = Style::Barrier(child.id()); - let styles = styles.chain_one(&barrier); layouter.layout_single(vt, &child, styles)?; } else if child.has::() { layouter.layout_multiple(vt, &child, styles)?; @@ -121,7 +117,7 @@ impl<'a> FlowLayouter<'a> { self.layout_item(match node.amount() { Spacing::Rel(v) => FlowItem::Absolute( v.resolve(styles).relative_to(self.initial.y), - node.weakness() > 0, + node.weakness(styles) > 0, ), Spacing::Fr(v) => FlowItem::Fractional(v), }); @@ -200,7 +196,7 @@ impl<'a> FlowLayouter<'a> { // Placed nodes that are out of flow produce placed items which aren't // aligned later. if let Some(placed) = block.to::() { - if placed.out_of_flow() { + if placed.out_of_flow(styles) { let frame = block.layout(vt, styles, self.regions)?.into_frame(); self.layout_item(FlowItem::Placed(frame)); return Ok(()); @@ -208,7 +204,14 @@ impl<'a> FlowLayouter<'a> { } // How to align the block. - let aligns = AlignNode::alignment_in(styles).resolve(styles); + let aligns = if let Some(align) = block.to::() { + align.alignment(styles) + } else if let Some(styled) = block.to::() { + AlignNode::alignment_in(styles.chain(&styled.map())) + } else { + AlignNode::alignment_in(styles) + } + .resolve(styles); // Layout the block itself. let sticky = BlockNode::sticky_in(styles); diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index 5d465f865..34514eac8 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -60,7 +60,7 @@ use super::Sizing; /// ``` /// /// ## Parameters -/// - gutter: `TrackSizings` (named) +/// - gutter: `TrackSizings` (named, settable) /// Defines the gaps between rows & columns. /// /// If there are more gutters than defined sizes, the last gutter is repeated. @@ -69,41 +69,36 @@ use super::Sizing; /// Category: layout #[node(Layout)] pub struct GridNode { - /// The contents of the table cells. - /// - /// The cells are populated in row-major order. - #[variadic] - pub children: Vec, - /// Defines the column sizes. /// /// Either specify a track size array or provide an integer to create a grid /// 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. - #[named] - #[default] pub columns: TrackSizings, /// Defines the row sizes. /// /// If there are more cells than fit the defined rows, the last row is /// repeated until there are no more cells. - #[named] - #[default] pub rows: TrackSizings, /// Defines the gaps between columns. Takes precedence over `gutter`. - #[named] - #[shorthand(gutter)] - #[default] + #[parse( + let gutter = args.named("gutter")?; + args.named("column-gutter")?.or_else(|| gutter.clone()) + )] pub column_gutter: TrackSizings, /// Defines the gaps between rows. Takes precedence over `gutter`. - #[named] - #[shorthand(gutter)] - #[default] + #[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))] pub row_gutter: TrackSizings, + + /// The contents of the table cells. + /// + /// The cells are populated in row-major order. + #[variadic] + pub children: Vec, } impl Layout for GridNode { @@ -117,8 +112,8 @@ impl Layout for GridNode { let cells = self.children(); let layouter = GridLayouter::new( vt, - Axes::new(&self.columns().0, &self.rows().0), - Axes::new(&self.column_gutter().0, &self.row_gutter().0), + Axes::new(&self.columns(styles).0, &self.rows(styles).0), + Axes::new(&self.column_gutter(styles).0, &self.row_gutter(styles).0), &cells, regions, styles, diff --git a/library/src/layout/list.rs b/library/src/layout/list.rs index e6e42263f..57b653c06 100644 --- a/library/src/layout/list.rs +++ b/library/src/layout/list.rs @@ -38,19 +38,6 @@ use super::GridLayouter; /// Category: layout #[node(Layout)] pub struct ListNode { - /// The bullet list's children. - /// - /// When using the list syntax, adjacent items are automatically collected - /// into lists, even through constructs like for loops. - /// - /// ```example - /// #for letter in "ABC" [ - /// - Letter #letter - /// ] - /// ``` - #[variadic] - pub children: Vec, - /// If this is `{false}`, the items are spaced apart with [list /// spacing]($func/list.spacing). If it is `{true}`, they use normal /// [leading]($func/par.leading) instead. This makes the list more compact, @@ -64,7 +51,6 @@ pub struct ListNode { /// - To make a list wide, simply insert /// a blank line between the items. /// ``` - #[named] #[default(true)] pub tight: bool, @@ -89,18 +75,14 @@ pub struct ListNode { /// - Items /// - Items /// ``` - #[settable] #[default(ListMarker::Content(vec![]))] pub marker: ListMarker, /// The indent of each item's marker. - #[settable] #[resolve] - #[default] pub indent: Length, /// The spacing between the marker and the body of each item. - #[settable] #[resolve] #[default(Em::new(0.5).into())] pub body_indent: Length, @@ -108,15 +90,24 @@ pub struct ListNode { /// The spacing between the items of a wide (non-tight) list. /// /// If set to `{auto}`, uses the spacing [below blocks]($func/block.below). - #[settable] - #[default] pub spacing: Smart, + /// The bullet list's children. + /// + /// When using the list syntax, adjacent items are automatically collected + /// into lists, even through constructs like for loops. + /// + /// ```example + /// #for letter in "ABC" [ + /// - Letter #letter + /// ] + /// ``` + #[variadic] + pub children: Vec, + /// The nesting depth. - #[settable] + #[internal] #[fold] - #[skip] - #[default] depth: Depth, } @@ -127,17 +118,17 @@ impl Layout for ListNode { styles: StyleChain, regions: Regions, ) -> SourceResult { - let indent = Self::indent_in(styles); - let body_indent = Self::body_indent_in(styles); - let gutter = if self.tight() { + let indent = self.indent(styles); + let body_indent = self.body_indent(styles); + let gutter = if self.tight(styles) { ParNode::leading_in(styles).into() } else { - Self::spacing_in(styles) + self.spacing(styles) .unwrap_or_else(|| BlockNode::below_in(styles).amount()) }; - let depth = Self::depth_in(styles); - let marker = Self::marker_in(styles).resolve(vt.world(), depth)?; + let depth = self.depth(styles); + let marker = self.marker(styles).resolve(vt.world(), depth)?; let mut cells = vec![]; for item in self.children() { diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index 6d57912f8..e846f6f06 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -48,7 +48,7 @@ use std::mem; use typed_arena::Arena; use typst::diag::SourceResult; use typst::model::{ - applicable, realize, Content, Node, SequenceNode, Style, StyleChain, StyleVecBuilder, + applicable, realize, Content, Node, SequenceNode, StyleChain, StyleVecBuilder, StyledNode, }; @@ -124,8 +124,6 @@ impl Layout for Content { let mut vt = Vt { world, provider, introspector }; let scratch = Scratch::default(); let (realized, styles) = realize_block(&mut vt, &scratch, node, styles)?; - let barrier = Style::Barrier(realized.id()); - let styles = styles.chain_one(&barrier); realized .with::() .unwrap() @@ -283,7 +281,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { let keep = content .to::() - .map_or(false, |pagebreak| !pagebreak.weak()); + .map_or(false, |pagebreak| !pagebreak.weak(styles)); self.interrupt_page(keep.then(|| styles))?; @@ -399,7 +397,7 @@ struct DocBuilder<'a> { impl<'a> DocBuilder<'a> { fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool { if let Some(pagebreak) = content.to::() { - self.keep_next = !pagebreak.weak(); + self.keep_next = !pagebreak.weak(styles); return true; } @@ -440,11 +438,11 @@ impl<'a> FlowBuilder<'a> { if content.has::() || content.is::() { let is_tight_list = if let Some(node) = content.to::() { - node.tight() + node.tight(styles) } else if let Some(node) = content.to::() { - node.tight() + node.tight(styles) } else if let Some(node) = content.to::() { - node.tight() + node.tight(styles) } else { false }; @@ -478,7 +476,7 @@ impl<'a> ParBuilder<'a> { || content.is::() || content.is::() || content.is::() - || content.to::().map_or(false, |node| !node.block()) + || content.to::().map_or(false, |node| !node.block(styles)) || content.is::() { self.0.push(content.clone(), styles); @@ -539,7 +537,7 @@ impl<'a> ListBuilder<'a> { .iter() .map(|(item, map)| { let item = item.to::().unwrap(); - ListItem::new(item.body().styled_with_map(map.clone())) + item.clone().with_body(item.body().styled_with_map(map.clone())) }) .collect::>(), ) @@ -551,8 +549,7 @@ impl<'a> ListBuilder<'a> { .iter() .map(|(item, map)| { let item = item.to::().unwrap(); - EnumItem::new(item.body().styled_with_map(map.clone())) - .with_number(item.number()) + item.clone().with_body(item.body().styled_with_map(map.clone())) }) .collect::>(), ) @@ -564,10 +561,11 @@ impl<'a> ListBuilder<'a> { .iter() .map(|(item, map)| { let item = item.to::().unwrap(); - TermItem::new( - item.term().styled_with_map(map.clone()), - item.description().styled_with_map(map.clone()), - ) + item.clone() + .with_term(item.term().styled_with_map(map.clone())) + .with_description( + item.description().styled_with_map(map.clone()), + ) }) .collect::>(), ) diff --git a/library/src/layout/pad.rs b/library/src/layout/pad.rs index 05aafc762..7d0bbe045 100644 --- a/library/src/layout/pad.rs +++ b/library/src/layout/pad.rs @@ -16,62 +16,44 @@ use crate::prelude::*; /// ``` /// /// ## Parameters -/// - x: `Rel` (named) +/// - x: `Rel` (named, settable) /// The horizontal padding. Both `left` and `right` take precedence over this. /// -/// - y: `Rel` (named) +/// - y: `Rel` (named, settable) /// The vertical padding. Both `top` and `bottom` take precedence over this. /// -/// - rest: `Rel` (named) +/// - rest: `Rel` (named, settable) /// The padding for all sides. All other parameters take precedence over this. /// /// Display: Padding /// Category: layout -#[node(Construct, Layout)] +#[node(Layout)] pub struct PadNode { + /// The padding at the left side. + #[parse( + let all = args.named("rest")?.or(args.find()?); + let x = args.named("x")?.or(all); + let y = args.named("y")?.or(all); + args.named("left")?.or(x) + )] + pub left: Rel, + + /// The padding at the top side. + #[parse(args.named("top")?.or(y))] + pub top: Rel, + + /// The padding at the right side. + #[parse(args.named("right")?.or(x))] + pub right: Rel, + + /// The padding at the bottom side. + #[parse(args.named("bottom")?.or(y))] + pub bottom: Rel, + /// The content to pad at the sides. #[positional] #[required] pub body: Content, - - /// The padding at the left side. - #[named] - #[default] - pub left: Rel, - - /// The padding at the right side. - #[named] - #[default] - pub right: Rel, - - /// The padding at the top side. - #[named] - #[default] - pub top: Rel, - - /// The padding at the bottom side. - #[named] - #[default] - pub bottom: Rel, -} - -impl Construct for PadNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let all = args.named("rest")?.or(args.find()?); - let x = args.named("x")?; - let y = args.named("y")?; - let left = args.named("left")?.or(x).or(all).unwrap_or_default(); - let top = args.named("top")?.or(y).or(all).unwrap_or_default(); - let right = args.named("right")?.or(x).or(all).unwrap_or_default(); - let bottom = args.named("bottom")?.or(y).or(all).unwrap_or_default(); - let body = args.expect::("body")?; - Ok(Self::new(body) - .with_left(left) - .with_top(top) - .with_bottom(bottom) - .with_right(right) - .pack()) - } } impl Layout for PadNode { @@ -81,10 +63,15 @@ impl Layout for PadNode { styles: StyleChain, regions: Regions, ) -> SourceResult { - let mut backlog = vec![]; + let sides = Sides::new( + self.left(styles), + self.top(styles), + self.right(styles), + self.bottom(styles), + ); // Layout child into padded regions. - let sides = Sides::new(self.left(), self.top(), self.right(), self.bottom()); + let mut backlog = vec![]; let padding = sides.resolve(styles); let pod = regions.map(&mut backlog, |size| shrink(size, padding)); let mut fragment = self.body().layout(vt, styles, pod)?; diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index 0c3de30cf..5fe3c90ad 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -21,29 +21,14 @@ use crate::prelude::*; /// ``` /// /// ## Parameters -/// - paper: `Paper` (positional, settable) +/// - paper: `Paper` (positional, named, settable) /// A standard paper size to set width and height. When this is not specified, /// Typst defaults to `{"a4"}` paper. /// /// Display: Page /// Category: layout #[node] -#[set({ - if let Some(paper) = args.named_or_find::("paper")? { - styles.set(Self::set_width(Smart::Custom(paper.width().into()))); - styles.set(Self::set_height(Smart::Custom(paper.height().into()))); - } -})] pub struct PageNode { - /// The contents of the page(s). - /// - /// Multiple pages will be created if the content does not fit on a single - /// page. A new page with the page properties prior to the function invocation - /// will be created after the body has been typeset. - #[positional] - #[required] - pub body: Content, - /// The width of the page. /// /// ```example @@ -56,8 +41,12 @@ pub struct PageNode { /// box(square(width: 1cm)) /// } /// ``` - #[settable] #[resolve] + #[parse( + let paper = args.named_or_find::("paper")?; + args.named("width")? + .or_else(|| paper.map(|paper| Smart::Custom(paper.width().into()))) + )] #[default(Smart::Custom(Paper::A4.width().into()))] pub width: Smart, @@ -67,8 +56,11 @@ pub struct PageNode { /// by inserting a [page break]($func/pagebreak). Most examples throughout /// this documentation use `{auto}` for the height of the page to /// dynamically grow and shrink to fit their content. - #[settable] #[resolve] + #[parse( + args.named("height")? + .or_else(|| paper.map(|paper| Smart::Custom(paper.height().into()))) + )] #[default(Smart::Custom(Paper::A4.height().into()))] pub height: Smart, @@ -90,7 +82,6 @@ pub struct PageNode { /// New York, NY 10001 \ /// +1 555 555 5555 /// ``` - #[settable] #[default(false)] pub flipped: bool, @@ -122,9 +113,7 @@ pub struct PageNode { /// fill: aqua, /// ) /// ``` - #[settable] #[fold] - #[default] pub margin: Sides>>>, /// How many columns the page has. @@ -141,7 +130,6 @@ pub struct PageNode { /// emissions and mitigate the impacts /// of a rapidly changing climate. /// ``` - #[settable] #[default(NonZeroUsize::new(1).unwrap())] pub columns: NonZeroUsize, @@ -157,8 +145,6 @@ pub struct PageNode { /// #set text(fill: rgb("fdfdfd")) /// *Dark mode enabled.* /// ``` - #[settable] - #[default] pub fill: Option, /// The page's header. @@ -180,8 +166,6 @@ pub struct PageNode { /// /// #lorem(18) /// ``` - #[settable] - #[default] pub header: Option, /// The page's footer. @@ -205,8 +189,6 @@ pub struct PageNode { /// /// #lorem(18) /// ``` - #[settable] - #[default] pub footer: Option, /// Content in the page's background. @@ -227,8 +209,6 @@ pub struct PageNode { /// In the year 2023, we plan to take over the world /// (of typesetting). /// ``` - #[settable] - #[default] pub background: Option, /// Content in the page's foreground. @@ -245,9 +225,16 @@ pub struct PageNode { /// "Weak Reject" because they did /// not understand our approach... /// ``` - #[settable] - #[default] pub foreground: Option, + + /// The contents of the page(s). + /// + /// Multiple pages will be created if the content does not fit on a single + /// page. A new page with the page properties prior to the function invocation + /// will be created after the body has been typeset. + #[positional] + #[required] + pub body: Content, } impl PageNode { @@ -260,10 +247,10 @@ impl PageNode { ) -> SourceResult { // When one of the lengths is infinite the page fits its content along // that axis. - let width = Self::width_in(styles).unwrap_or(Abs::inf()); - let height = Self::height_in(styles).unwrap_or(Abs::inf()); + let width = self.width(styles).unwrap_or(Abs::inf()); + let height = self.height(styles).unwrap_or(Abs::inf()); let mut size = Size::new(width, height); - if Self::flipped_in(styles) { + if self.flipped(styles) { std::mem::swap(&mut size.x, &mut size.y); } @@ -274,14 +261,14 @@ impl PageNode { // Determine the margins. let default = Rel::from(0.1190 * min); - let padding = Self::margin_in(styles).map(|side| side.unwrap_or(default)); + let padding = self.margin(styles).map(|side| side.unwrap_or(default)); let mut child = self.body(); // Realize columns. - let columns = Self::columns_in(styles); + let columns = self.columns(styles); if columns.get() > 1 { - child = ColumnsNode::new(columns, child).pack(); + child = ColumnsNode::new(child).with_count(columns).pack(); } // Realize margins. @@ -291,11 +278,11 @@ impl PageNode { let regions = Regions::repeat(size, size.map(Abs::is_finite)); let mut fragment = child.layout(vt, styles, regions)?; - let fill = Self::fill_in(styles); - let header = Self::header_in(styles); - let footer = Self::footer_in(styles); - let foreground = Self::foreground_in(styles); - let background = Self::background_in(styles); + let fill = self.fill(styles); + let header = self.header(styles); + let footer = self.footer(styles); + let foreground = self.foreground(styles); + let background = self.background(styles); // Realize overlays. for frame in &mut fragment { @@ -352,7 +339,6 @@ impl PageNode { pub struct PagebreakNode { /// If `{true}`, the page break is skipped if the current page is already /// empty. - #[named] #[default(false)] pub weak: bool, } diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs index 10388f882..93cca4523 100644 --- a/library/src/layout/par.rs +++ b/library/src/layout/par.rs @@ -43,11 +43,6 @@ use crate::text::{ /// Category: layout #[node(Construct)] pub struct ParNode { - /// The paragraph's children. - #[variadic] - #[skip] - pub children: Vec, - /// The indent the first line of a consecutive paragraph should have. /// /// The first paragraph on a page will never be indented. @@ -56,15 +51,12 @@ pub struct ParNode { /// space between paragraphs or indented first lines. Consider turning the /// [paragraph spacing]($func/block.spacing) off when using this property /// (e.g. using `[#show par: set block(spacing: 0pt)]`). - #[settable] #[resolve] - #[default] pub indent: Length, /// The spacing between lines. /// /// The default value is `{0.65em}`. - #[settable] #[resolve] #[default(Em::new(0.65).into())] pub leading: Length, @@ -78,7 +70,6 @@ pub struct ParNode { /// Note that the current [alignment]($func/align) still has an effect on /// the placement of the last line except if it ends with a [justified line /// break]($func/linebreak.justify). - #[settable] #[default(false)] pub justify: bool, @@ -105,9 +96,13 @@ pub struct ParNode { /// very aesthetic example is one /// of them. /// ``` - #[settable] #[default] pub linebreaks: Smart, + + /// The paragraph's children. + #[internal] + #[variadic] + pub children: Vec, } impl Construct for ParNode { @@ -115,9 +110,11 @@ impl Construct for ParNode { // The paragraph constructor is special: It doesn't create a paragraph // node. Instead, it just ensures that the passed content lives in a // separate paragraph and styles it. + let styles = Self::set(args)?; + let body = args.expect::("body")?; Ok(Content::sequence(vec![ ParbreakNode::new().pack(), - args.expect("body")?, + body.styled_with_map(styles), ParbreakNode::new().pack(), ])) } @@ -334,7 +331,7 @@ enum Segment<'a> { /// A math formula. Formula(&'a FormulaNode), /// A box with arbitrary content. - Box(&'a BoxNode), + Box(&'a BoxNode, bool), } impl Segment<'_> { @@ -343,8 +340,8 @@ impl Segment<'_> { match *self { Self::Text(len) => len, Self::Spacing(_) => SPACING_REPLACE.len_utf8(), - Self::Box(node) if node.width().is_fractional() => SPACING_REPLACE.len_utf8(), - Self::Formula(_) | Self::Box(_) => NODE_REPLACE.len_utf8(), + Self::Box(_, true) => SPACING_REPLACE.len_utf8(), + Self::Formula(_) | Self::Box(_, _) => NODE_REPLACE.len_utf8(), } } } @@ -540,7 +537,7 @@ fn collect<'a>( full.push(SPACING_REPLACE); Segment::Spacing(node.amount()) } else if let Some(node) = child.to::() { - let c = if node.justify() { '\u{2028}' } else { '\n' }; + let c = if node.justify(styles) { '\u{2028}' } else { '\n' }; full.push(c); Segment::Text(c.len_utf8()) } else if let Some(node) = child.to::() { @@ -561,21 +558,18 @@ fn collect<'a>( } }); - full.push_str(quoter.quote("es, node.double(), peeked)); + full.push_str(quoter.quote("es, node.double(styles), peeked)); } else { - full.push(if node.double() { '"' } else { '\'' }); + full.push(if node.double(styles) { '"' } else { '\'' }); } Segment::Text(full.len() - prev) } else if let Some(node) = child.to::() { full.push(NODE_REPLACE); Segment::Formula(node) } else if let Some(node) = child.to::() { - full.push(if node.width().is_fractional() { - SPACING_REPLACE - } else { - NODE_REPLACE - }); - Segment::Box(node) + let frac = node.width(styles).is_fractional(); + full.push(if frac { SPACING_REPLACE } else { NODE_REPLACE }); + Segment::Box(node, frac) } else if let Some(span) = child.span() { bail!(span, "unexpected document child"); } else { @@ -645,8 +639,8 @@ fn prepare<'a>( frame.translate(Point::with_y(TextNode::baseline_in(styles))); items.push(Item::Frame(frame)); } - Segment::Box(node) => { - if let Sizing::Fr(v) = node.width() { + Segment::Box(node, _) => { + if let Sizing::Fr(v) = node.width(styles) { items.push(Item::Fractional(v, Some((node, styles)))); } else { let pod = Regions::one(region, Axes::splat(false)); diff --git a/library/src/layout/place.rs b/library/src/layout/place.rs index b4aaf73df..8d7aa2299 100644 --- a/library/src/layout/place.rs +++ b/library/src/layout/place.rs @@ -34,11 +34,6 @@ pub struct PlaceNode { #[default(Axes::with_x(Some(GenAlign::Start)))] pub alignment: Axes>, - /// The content to place. - #[positional] - #[required] - pub body: Content, - /// The horizontal displacement of the placed content. /// /// ```example @@ -48,14 +43,15 @@ pub struct PlaceNode { /// place(center, dx: amount - 32pt, dy: amount)[A] /// } /// ``` - #[named] - #[default] pub dx: Rel, /// The vertical displacement of the placed content. - #[named] - #[default] pub dy: Rel, + + /// The content to place. + #[positional] + #[required] + pub body: Content, } impl Layout for PlaceNode { @@ -65,7 +61,7 @@ impl Layout for PlaceNode { styles: StyleChain, regions: Regions, ) -> SourceResult { - let out_of_flow = self.out_of_flow(); + let out_of_flow = self.out_of_flow(styles); // The pod is the base area of the region because for absolute // placement we don't really care about the already used area. @@ -77,8 +73,8 @@ impl Layout for PlaceNode { let child = self .body() - .moved(Axes::new(self.dx(), self.dy())) - .aligned(self.alignment()); + .moved(Axes::new(self.dx(styles), self.dy(styles))) + .aligned(self.alignment(styles)); let mut frame = child.layout(vt, styles, pod)?.into_frame(); @@ -95,8 +91,8 @@ impl PlaceNode { /// Whether this node wants to be placed relative to its its parent's base /// origin. Instead of relative to the parent's current flow/cursor /// position. - pub fn out_of_flow(&self) -> bool { - self.alignment().y.is_some() + pub fn out_of_flow(&self, styles: StyleChain) -> bool { + self.alignment(styles).y.is_some() } } diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs index 9f5527301..c11a2f060 100644 --- a/library/src/layout/spacing.rs +++ b/library/src/layout/spacing.rs @@ -42,7 +42,6 @@ pub struct HNode { /// #h(8pt, weak: true) on both /// sides, they do show up. /// ``` - #[named] #[default(false)] pub weak: bool, } @@ -51,7 +50,7 @@ impl Behave for HNode { fn behaviour(&self) -> Behaviour { if self.amount().is_fractional() { Behaviour::Destructive - } else if self.weak() { + } else if self.weak(StyleChain::default()) { Behaviour::Weak(1) } else { Behaviour::Ignorant @@ -86,7 +85,7 @@ impl Behave for HNode { /// ``` /// /// ## Parameters -/// - weak: `bool` (named) +/// - weak: `bool` (named, settable) /// If true, the spacing collapses at the start or end of a flow. Moreover, /// from multiple adjacent weak spacings all but the largest one collapse. /// Weak spacings will always collapse adjacent paragraph spacing, even if the @@ -103,7 +102,7 @@ impl Behave for HNode { /// /// Display: Spacing (V) /// Category: layout -#[node(Construct, Behave)] +#[node(Behave)] pub struct VNode { /// How much spacing to insert. #[positional] @@ -111,24 +110,11 @@ pub struct VNode { pub amount: Spacing, /// The node's weakness level, see also [`Behaviour`]. - #[named] - #[skip] - #[default] + #[internal] + #[parse(args.named("weak")?.map(|v: bool| v as usize))] pub weakness: usize, } -impl Construct for VNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let amount = args.expect("spacing")?; - let node = if args.named("weak")?.unwrap_or(false) { - Self::weak(amount) - } else { - Self::strong(amount) - }; - Ok(node.pack()) - } -} - impl VNode { /// Normal strong spacing. pub fn strong(amount: Spacing) -> Self { @@ -160,8 +146,8 @@ impl Behave for VNode { fn behaviour(&self) -> Behaviour { if self.amount().is_fractional() { Behaviour::Destructive - } else if self.weakness() > 0 { - Behaviour::Weak(self.weakness()) + } else if self.weakness(StyleChain::default()) > 0 { + Behaviour::Weak(self.weakness(StyleChain::default())) } else { Behaviour::Ignorant } diff --git a/library/src/layout/stack.rs b/library/src/layout/stack.rs index f4e4ab2c1..c21fa884b 100644 --- a/library/src/layout/stack.rs +++ b/library/src/layout/stack.rs @@ -22,24 +22,21 @@ use crate::prelude::*; /// Category: layout #[node(Layout)] pub struct StackNode { - /// The childfren to stack along the axis. - #[variadic] - pub children: Vec, - /// The direction along which the items are stacked. Possible values are: /// /// - `{ltr}`: Left to right. /// - `{rtl}`: Right to left. /// - `{ttb}`: Top to bottom. /// - `{btt}`: Bottom to top. - #[named] #[default(Dir::TTB)] pub dir: Dir, /// Spacing to insert between items where no explicit spacing was provided. - #[named] - #[default] pub spacing: Option, + + /// The childfren to stack along the axis. + #[variadic] + pub children: Vec, } impl Layout for StackNode { @@ -49,10 +46,10 @@ impl Layout for StackNode { styles: StyleChain, regions: Regions, ) -> SourceResult { - let mut layouter = StackLayouter::new(self.dir(), regions, styles); + let mut layouter = StackLayouter::new(self.dir(styles), regions, styles); // Spacing to insert before the next block. - let spacing = self.spacing(); + let spacing = self.spacing(styles); let mut deferred = None; for child in self.children() { @@ -201,12 +198,15 @@ impl<'a> StackLayouter<'a> { // Block-axis alignment of the `AlignNode` is respected // by the stack node. - let aligns = match block.to::() { - Some(styled) => AlignNode::alignment_in(styles.chain(&styled.map())), - None => AlignNode::alignment_in(styles), - }; + let aligns = if let Some(align) = block.to::() { + align.alignment(styles) + } else if let Some(styled) = block.to::() { + AlignNode::alignment_in(styles.chain(&styled.map())) + } else { + AlignNode::alignment_in(styles) + } + .resolve(styles); - let aligns = aligns.resolve(styles); let fragment = block.layout(vt, styles, self.regions)?; let len = fragment.len(); for (i, frame) in fragment.into_iter().enumerate() { diff --git a/library/src/layout/table.rs b/library/src/layout/table.rs index 1bd47df03..59635119b 100644 --- a/library/src/layout/table.rs +++ b/library/src/layout/table.rs @@ -30,7 +30,7 @@ use crate::prelude::*; /// ``` /// /// ## Parameters -/// - gutter: `TrackSizings` (named) +/// - gutter: `TrackSizings` (named, settable) /// Defines the gaps between rows & columns. /// See the [grid documentation]($func/grid) for more information on gutters. /// @@ -38,36 +38,27 @@ use crate::prelude::*; /// Category: layout #[node(Layout)] pub struct TableNode { - /// The contents of the table cells. - #[variadic] - pub children: Vec, - /// Defines the column sizes. /// See the [grid documentation]($func/grid) for more information on track /// sizing. - #[named] - #[default] pub columns: TrackSizings, /// Defines the row sizes. /// See the [grid documentation]($func/grid) for more information on track /// sizing. - #[named] - #[default] pub rows: TrackSizings, /// Defines the gaps between columns. Takes precedence over `gutter`. /// See the [grid documentation]($func/grid) for more information on gutters. - #[named] - #[shorthand(gutter)] - #[default] + #[parse( + let gutter = args.named("gutter")?; + args.named("column-gutter")?.or_else(|| gutter.clone()) + )] pub column_gutter: TrackSizings, /// Defines the gaps between rows. Takes precedence over `gutter`. /// See the [grid documentation]($func/grid) for more information on gutters. - #[named] - #[shorthand(gutter)] - #[default] + #[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))] pub row_gutter: TrackSizings, /// How to fill the cells. @@ -90,8 +81,6 @@ pub struct TableNode { /// [Profit:], [500 €], [1000 €], [1500 €], /// ) /// ``` - #[settable] - #[default] pub fill: Celled>, /// How to align the cell's content. @@ -99,15 +88,12 @@ pub struct TableNode { /// This can either be a single alignment or a function that returns an /// alignment. The function is passed the cell's column and row index, /// starting at zero. If set to `{auto}`, the outer alignment is used. - #[settable] - #[default] pub align: Celled>>>, /// How to stroke the cells. /// /// This can be a color, a stroke width, both, or `{none}` to disable /// the stroke. - #[settable] #[resolve] #[fold] #[default(Some(PartialStroke::default()))] @@ -116,9 +102,12 @@ pub struct TableNode { /// How much to pad the cells's content. /// /// The default value is `{5pt}`. - #[settable] #[default(Abs::pt(5.0).into())] pub inset: Rel, + + /// The contents of the table cells. + #[variadic] + pub children: Vec, } impl Layout for TableNode { @@ -128,11 +117,11 @@ impl Layout for TableNode { styles: StyleChain, regions: Regions, ) -> SourceResult { - let inset = Self::inset_in(styles); - let align = Self::align_in(styles); + let inset = self.inset(styles); + let align = self.align(styles); - let tracks = Axes::new(self.columns().0, self.rows().0); - let gutter = Axes::new(self.column_gutter().0, self.row_gutter().0); + let tracks = Axes::new(self.columns(styles).0, self.rows(styles).0); + let gutter = Axes::new(self.column_gutter(styles).0, self.row_gutter(styles).0); let cols = tracks.x.len().max(1); let cells: Vec<_> = self .children() @@ -151,8 +140,8 @@ impl Layout for TableNode { }) .collect::>()?; - let fill = Self::fill_in(styles); - let stroke = Self::stroke_in(styles).map(PartialStroke::unwrap_or_default); + let fill = self.fill(styles); + let stroke = self.stroke(styles).map(PartialStroke::unwrap_or_default); // Prepare grid layout by unifying content and gutter tracks. let layouter = GridLayouter::new( diff --git a/library/src/layout/terms.rs b/library/src/layout/terms.rs index d859a4472..8ab4edc6f 100644 --- a/library/src/layout/terms.rs +++ b/library/src/layout/terms.rs @@ -23,21 +23,6 @@ use crate::text::{SpaceNode, TextNode}; /// Category: layout #[node(Layout)] pub struct TermsNode { - /// The term list's children. - /// - /// When using the term list syntax, adjacent items are automatically - /// collected into term lists, even through constructs like for loops. - /// - /// ```example - /// #for year, product in ( - /// "1978": "TeX", - /// "1984": "LaTeX", - /// "2019": "Typst", - /// ) [/ #product: Born in #year.] - /// ``` - #[variadic] - pub children: Vec, - /// If this is `{false}`, the items are spaced apart with [term list /// spacing]($func/terms.spacing). If it is `{true}`, they use normal /// [leading]($func/par.leading) instead. This makes the term list more @@ -53,14 +38,11 @@ pub struct TermsNode { /// insert a blank line between the /// items. /// ``` - #[named] #[default(true)] pub tight: bool, /// The indentation of each item's term. - #[settable] #[resolve] - #[default] pub indent: Length, /// The hanging indent of the description. @@ -70,7 +52,6 @@ pub struct TermsNode { /// / Term: This term list does not /// make use of hanging indents. /// ``` - #[settable] #[resolve] #[default(Em::new(1.0).into())] pub hanging_indent: Length, @@ -78,9 +59,22 @@ pub struct TermsNode { /// The spacing between the items of a wide (non-tight) term list. /// /// If set to `{auto}`, uses the spacing [below blocks]($func/block.below). - #[settable] - #[default] pub spacing: Smart, + + /// The term list's children. + /// + /// When using the term list syntax, adjacent items are automatically + /// collected into term lists, even through constructs like for loops. + /// + /// ```example + /// #for year, product in ( + /// "1978": "TeX", + /// "1984": "LaTeX", + /// "2019": "Typst", + /// ) [/ #product: Born in #year.] + /// ``` + #[variadic] + pub children: Vec, } impl Layout for TermsNode { @@ -90,12 +84,12 @@ impl Layout for TermsNode { styles: StyleChain, regions: Regions, ) -> SourceResult { - let indent = Self::indent_in(styles); - let body_indent = Self::hanging_indent_in(styles); - let gutter = if self.tight() { + let indent = self.indent(styles); + let body_indent = self.hanging_indent(styles); + let gutter = if self.tight(styles) { ParNode::leading_in(styles).into() } else { - Self::spacing_in(styles) + self.spacing(styles) .unwrap_or_else(|| BlockNode::below_in(styles).amount()) }; diff --git a/library/src/layout/transform.rs b/library/src/layout/transform.rs index 41d3d120a..4521da329 100644 --- a/library/src/layout/transform.rs +++ b/library/src/layout/transform.rs @@ -25,20 +25,16 @@ use crate::prelude::*; /// Category: layout #[node(Layout)] pub struct MoveNode { + /// The horizontal displacement of the content. + pub dx: Rel, + + /// The vertical displacement of the content. + pub dy: Rel, + /// The content to move. #[positional] #[required] pub body: Content, - - /// The horizontal displacement of the content. - #[named] - #[default] - pub dx: Rel, - - /// The vertical displacement of the content. - #[named] - #[default] - pub dy: Rel, } impl Layout for MoveNode { @@ -50,7 +46,7 @@ impl Layout for MoveNode { ) -> SourceResult { let pod = Regions::one(regions.base(), Axes::splat(false)); let mut frame = self.body().layout(vt, styles, pod)?.into_frame(); - let delta = Axes::new(self.dx(), self.dy()).resolve(styles); + let delta = Axes::new(self.dx(styles), self.dy(styles)).resolve(styles); let delta = delta.zip(regions.base()).map(|(d, s)| d.relative_to(s)); frame.translate(delta.to_point()); Ok(Fragment::frame(frame)) @@ -83,14 +79,8 @@ pub struct RotateNode { /// ``` /// #[positional] - #[required] pub angle: Angle, - /// The content to rotate. - #[positional] - #[required] - pub body: Content, - /// The origin of the rotation. /// /// By default, the origin is the center of the rotated element. If, @@ -107,10 +97,13 @@ pub struct RotateNode { /// #box(rotate(30deg, origin: top + left, square())) /// #box(rotate(30deg, origin: bottom + right, square())) /// ``` - #[settable] #[resolve] - #[default] pub origin: Axes>, + + /// The content to rotate. + #[positional] + #[required] + pub body: Content, } impl Layout for RotateNode { @@ -122,10 +115,10 @@ impl Layout for RotateNode { ) -> SourceResult { let pod = Regions::one(regions.base(), Axes::splat(false)); let mut frame = self.body().layout(vt, styles, pod)?.into_frame(); - let origin = Self::origin_in(styles).unwrap_or(Align::CENTER_HORIZON); + let origin = self.origin(styles).unwrap_or(Align::CENTER_HORIZON); let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); let ts = Transform::translate(x, y) - .pre_concat(Transform::rotate(self.angle())) + .pre_concat(Transform::rotate(self.angle(styles))) .pre_concat(Transform::translate(-x, -y)); frame.transform(ts); Ok(Fragment::frame(frame)) @@ -146,24 +139,22 @@ impl Layout for RotateNode { /// /// Display: Scale /// Category: layout -#[node(Construct, Layout)] +#[node(Layout)] pub struct ScaleNode { - /// The content to scale. - #[positional] - #[required] - pub body: Content, - /// The horizontal scaling factor. /// /// The body will be mirrored horizontally if the parameter is negative. - #[named] + #[parse( + let all = args.find()?; + args.named("x")?.or(all) + )] #[default(Ratio::one())] pub x: Ratio, /// The vertical scaling factor. /// /// The body will be mirrored vertically if the parameter is negative. - #[named] + #[parse(args.named("y")?.or(all))] #[default(Ratio::one())] pub y: Ratio, @@ -175,19 +166,13 @@ pub struct ScaleNode { /// A#box(scale(75%)[A])A \ /// B#box(scale(75%, origin: bottom + left)[B])B /// ``` - #[settable] #[resolve] - #[default] pub origin: Axes>, -} -impl Construct for ScaleNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let all = args.find()?; - let x = args.named("x")?.or(all).unwrap_or(Ratio::one()); - let y = args.named("y")?.or(all).unwrap_or(Ratio::one()); - Ok(Self::new(args.expect::("body")?).with_x(x).with_y(y).pack()) - } + /// The content to scale. + #[positional] + #[required] + pub body: Content, } impl Layout for ScaleNode { @@ -199,10 +184,10 @@ impl Layout for ScaleNode { ) -> SourceResult { let pod = Regions::one(regions.base(), Axes::splat(false)); let mut frame = self.body().layout(vt, styles, pod)?.into_frame(); - let origin = Self::origin_in(styles).unwrap_or(Align::CENTER_HORIZON); + let origin = self.origin(styles).unwrap_or(Align::CENTER_HORIZON); let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); let transform = Transform::translate(x, y) - .pre_concat(Transform::scale(self.x(), self.y())) + .pre_concat(Transform::scale(self.x(styles), self.y(styles))) .pre_concat(Transform::translate(-x, -y)); frame.transform(transform); Ok(Fragment::frame(frame)) diff --git a/library/src/lib.rs b/library/src/lib.rs index fcd0bf45c..bcbd703de 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -176,11 +176,11 @@ fn items() -> LangItems { strong: |body| text::StrongNode::new(body).pack(), emph: |body| text::EmphNode::new(body).pack(), raw: |text, lang, block| { - let content = text::RawNode::new(text).with_block(block).pack(); - match lang { - Some(_) => content.styled(text::RawNode::set_lang(lang)), - None => content, + let mut node = text::RawNode::new(text).with_block(block); + if let Some(lang) = lang { + node = node.with_lang(Some(lang)); } + node.pack() }, link: |url| meta::LinkNode::from_url(url).pack(), ref_: |target| meta::RefNode::new(target).pack(), diff --git a/library/src/math/attach.rs b/library/src/math/attach.rs index 7ba1484a6..c2d7703db 100644 --- a/library/src/math/attach.rs +++ b/library/src/math/attach.rs @@ -21,13 +21,9 @@ pub struct AttachNode { pub base: Content, /// The top attachment. - #[named] - #[default] pub top: Option, /// The bottom attachment. - #[named] - #[default] pub bottom: Option, } @@ -40,11 +36,17 @@ impl LayoutMath for AttachNode { let base = ctx.layout_fragment(&base)?; ctx.style(ctx.style.for_subscript()); - let top = self.top().map(|node| ctx.layout_fragment(&node)).transpose()?; + let top = self + .top(ctx.styles()) + .map(|node| ctx.layout_fragment(&node)) + .transpose()?; ctx.unstyle(); ctx.style(ctx.style.for_superscript()); - let bottom = self.bottom().map(|node| ctx.layout_fragment(&node)).transpose()?; + let bottom = self + .bottom(ctx.styles()) + .map(|node| ctx.layout_fragment(&node)) + .transpose()?; ctx.unstyle(); let display_limits = display_limits diff --git a/library/src/math/delimited.rs b/library/src/math/delimited.rs index 63cb916c3..f9d22c432 100644 --- a/library/src/math/delimited.rs +++ b/library/src/math/delimited.rs @@ -16,23 +16,17 @@ pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1); /// /// Display: Left/Right /// Category: math -#[node(Construct, LayoutMath)] +#[node(LayoutMath)] pub struct LrNode { - /// The delimited content, including the delimiters. - #[positional] - #[required] - pub body: Content, - /// The size of the brackets, relative to the height of the wrapped content. /// /// Defaults to `{100%}`. - #[named] - #[default] pub size: Smart>, -} -impl Construct for LrNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { + /// The delimited content, including the delimiters. + #[positional] + #[required] + #[parse( let mut body = Content::empty(); for (i, arg) in args.all::()?.into_iter().enumerate() { if i > 0 { @@ -40,16 +34,16 @@ impl Construct for LrNode { } body += arg; } - let size = args.named::>>("size")?.unwrap_or_default(); - Ok(Self::new(body).with_size(size).pack()) - } + body + )] + pub body: Content, } impl LayoutMath for LrNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { let mut body = self.body(); if let Some(node) = body.to::() { - if node.size().is_auto() { + if node.size(ctx.styles()).is_auto() { body = node.body(); } } @@ -63,7 +57,7 @@ impl LayoutMath for LrNode { .unwrap_or_default(); let height = self - .size() + .size(ctx.styles()) .unwrap_or(Rel::one()) .resolve(ctx.styles()) .relative_to(2.0 * max_extent); diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs index d4bf52f3d..6dec645c0 100644 --- a/library/src/math/matrix.rs +++ b/library/src/math/matrix.rs @@ -18,24 +18,23 @@ const VERTICAL_PADDING: Ratio = Ratio::new(0.1); /// Category: math #[node(LayoutMath)] pub struct VecNode { - /// The elements of the vector. - #[variadic] - pub children: Vec, - /// The delimiter to use. /// /// ```example /// #set math.vec(delim: "[") /// $ vec(1, 2) $ /// ``` - #[settable] #[default(Delimiter::Paren)] pub delim: Delimiter, + + /// The elements of the vector. + #[variadic] + pub children: Vec, } impl LayoutMath for VecNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let delim = Self::delim_in(ctx.styles()); + let delim = self.delim(ctx.styles()); let frame = layout_vec_body(ctx, &self.children(), Align::Center)?; layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close())) } @@ -63,8 +62,17 @@ impl LayoutMath for VecNode { /// /// Display: Matrix /// Category: math -#[node(Construct, LayoutMath)] +#[node(LayoutMath)] pub struct MatNode { + /// The delimiter to use. + /// + /// ```example + /// #set math.mat(delim: "[") + /// $ mat(1, 2; 3, 4) $ + /// ``` + #[default(Delimiter::Paren)] + pub delim: Delimiter, + /// An array of arrays with the rows of the matrix. /// /// ```example @@ -73,21 +81,7 @@ pub struct MatNode { /// $ v := matrix $ /// ``` #[variadic] - pub rows: Vec>, - - /// The delimiter to use. - /// - /// ```example - /// #set math.mat(delim: "[") - /// $ mat(1, 2; 3, 4) $ - /// ``` - #[settable] - #[default(Delimiter::Paren)] - pub delim: Delimiter, -} - -impl Construct for MatNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { + #[parse( let mut rows = vec![]; let mut width = 0; @@ -109,13 +103,14 @@ impl Construct for MatNode { } } - Ok(Self::new(rows).pack()) - } + rows + )] + pub rows: Vec>, } impl LayoutMath for MatNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let delim = Self::delim_in(ctx.styles()); + let delim = self.delim(ctx.styles()); let frame = layout_mat_body(ctx, &self.rows())?; layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close())) } @@ -139,24 +134,23 @@ impl LayoutMath for MatNode { /// Category: math #[node(LayoutMath)] pub struct CasesNode { - /// The branches of the case distinction. - #[variadic] - pub children: Vec, - /// The delimiter to use. /// /// ```example /// #set math.cases(delim: "[") /// $ x = cases(1, 2) $ /// ``` - #[settable] #[default(Delimiter::Brace)] pub delim: Delimiter, + + /// The branches of the case distinction. + #[variadic] + pub children: Vec, } impl LayoutMath for CasesNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let delim = Self::delim_in(ctx.styles()); + let delim = self.delim(ctx.styles()); let frame = layout_vec_body(ctx, &self.children(), Align::Left)?; layout_delimiters(ctx, frame, Some(delim.open()), None) } diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 3f0b0607e..0c9dc3385 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -134,21 +134,20 @@ pub fn module() -> Module { /// Category: math #[node(Show, Finalize, Layout, LayoutMath)] pub struct FormulaNode { + /// Whether the formula is displayed as a separate block. + #[default(false)] + pub block: bool, + /// The content of the formula. #[positional] #[required] pub body: Content, - - /// Whether the formula is displayed as a separate block. - #[named] - #[default(false)] - pub block: bool, } impl Show for FormulaNode { - fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult { + fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult { let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::())); - if self.block() { + if self.block(styles) { realized = realized.aligned(Axes::with_x(Some(Align::Center.into()))) } Ok(realized) @@ -156,7 +155,7 @@ impl Show for FormulaNode { } impl Finalize for FormulaNode { - fn finalize(&self, realized: Content) -> Content { + fn finalize(&self, realized: Content, _: StyleChain) -> Content { realized .styled(TextNode::set_weight(FontWeight::from_number(450))) .styled(TextNode::set_font(FontList(vec![FontFamily::new( @@ -172,7 +171,7 @@ impl Layout for FormulaNode { styles: StyleChain, regions: Regions, ) -> SourceResult { - let block = self.block(); + let block = self.block(styles); // Find a math font. let variant = variant(styles); diff --git a/library/src/math/op.rs b/library/src/math/op.rs index c855cd92e..aa2e4cf7d 100644 --- a/library/src/math/op.rs +++ b/library/src/math/op.rs @@ -30,7 +30,6 @@ pub struct OpNode { /// Whether the operator should force attachments to display as limits. /// /// Defaults to `{false}`. - #[named] #[default(false)] pub limits: bool, } @@ -41,7 +40,7 @@ impl LayoutMath for OpNode { ctx.push( FrameFragment::new(ctx, frame) .with_class(MathClass::Large) - .with_limits(self.limits()), + .with_limits(self.limits(ctx.styles())), ); Ok(()) } diff --git a/library/src/math/underover.rs b/library/src/math/underover.rs index 87f30c0ff..2aabf1328 100644 --- a/library/src/math/underover.rs +++ b/library/src/math/underover.rs @@ -68,13 +68,12 @@ pub struct UnderbraceNode { /// The optional content below the brace. #[positional] - #[default] pub annotation: Option, } impl LayoutMath for UnderbraceNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - layout(ctx, &self.body(), &self.annotation(), '⏟', BRACE_GAP, false) + layout(ctx, &self.body(), &self.annotation(ctx.styles()), '⏟', BRACE_GAP, false) } } @@ -96,13 +95,12 @@ pub struct OverbraceNode { /// The optional content above the brace. #[positional] - #[default] pub annotation: Option, } impl LayoutMath for OverbraceNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - layout(ctx, &self.body(), &self.annotation(), '⏞', BRACE_GAP, true) + layout(ctx, &self.body(), &self.annotation(ctx.styles()), '⏞', BRACE_GAP, true) } } @@ -124,13 +122,12 @@ pub struct UnderbracketNode { /// The optional content below the bracket. #[positional] - #[default] pub annotation: Option, } impl LayoutMath for UnderbracketNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - layout(ctx, &self.body(), &self.annotation(), '⎵', BRACKET_GAP, false) + layout(ctx, &self.body(), &self.annotation(ctx.styles()), '⎵', BRACKET_GAP, false) } } @@ -152,13 +149,12 @@ pub struct OverbracketNode { /// The optional content above the bracket. #[positional] - #[default] pub annotation: Option, } impl LayoutMath for OverbracketNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - layout(ctx, &self.body(), &self.annotation(), '⎴', BRACKET_GAP, true) + layout(ctx, &self.body(), &self.annotation(ctx.styles()), '⎴', BRACKET_GAP, true) } } diff --git a/library/src/meta/document.rs b/library/src/meta/document.rs index 1325d85b0..8da3c731a 100644 --- a/library/src/meta/document.rs +++ b/library/src/meta/document.rs @@ -16,20 +16,17 @@ use crate::prelude::*; /// Category: meta #[node(LayoutRoot)] pub struct DocumentNode { - /// The page runs. - #[variadic] - pub children: Vec, - /// The document's title. This is often rendered as the title of the /// PDF viewer window. - #[settable] - #[default] pub title: Option, /// The document's authors. - #[settable] - #[default] pub author: Author, + + /// The page runs. + #[internal] + #[variadic] + pub children: Vec, } impl LayoutRoot for DocumentNode { @@ -58,8 +55,8 @@ impl LayoutRoot for DocumentNode { Ok(Document { pages, - title: Self::title_in(styles), - author: Self::author_in(styles).0, + title: self.title(styles), + author: self.author(styles).0, }) } } diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs index f0107a6a5..09cbc8b16 100644 --- a/library/src/meta/heading.rs +++ b/library/src/meta/heading.rs @@ -42,13 +42,7 @@ use crate::text::{TextNode, TextSize}; /// Category: meta #[node(Prepare, Show, Finalize)] pub struct HeadingNode { - /// The heading's title. - #[positional] - #[required] - pub body: Content, - /// The logical nesting depth of the heading, starting from one. - #[named] #[default(NonZeroUsize::new(1).unwrap())] pub level: NonZeroUsize, @@ -62,8 +56,6 @@ pub struct HeadingNode { /// == A subsection /// === A sub-subsection /// ``` - #[settable] - #[default] pub numbering: Option, /// Whether the heading should appear in the outline. @@ -78,9 +70,13 @@ pub struct HeadingNode { /// This heading does not appear /// in the outline. /// ``` - #[settable] #[default(true)] pub outlined: bool, + + /// The heading's title. + #[positional] + #[required] + pub body: Content, } impl Prepare for HeadingNode { @@ -106,11 +102,11 @@ impl Prepare for HeadingNode { } let mut numbers = Value::None; - if let Some(numbering) = Self::numbering_in(styles) { + if let Some(numbering) = self.numbering(styles) { numbers = numbering.apply(vt.world(), counter.advance(self))?; } - this.push_field("outlined", Value::Bool(Self::outlined_in(styles))); + this.push_field("outlined", Value::Bool(self.outlined(styles))); this.push_field("numbers", numbers); let meta = Meta::Node(my_id, this.clone()); @@ -132,8 +128,8 @@ impl Show for HeadingNode { } impl Finalize for HeadingNode { - fn finalize(&self, realized: Content) -> Content { - let level = self.level().get(); + fn finalize(&self, realized: Content, styles: StyleChain) -> Content { + let level = self.level(styles).get(); let scale = match level { 1 => 1.4, 2 => 1.2, @@ -165,7 +161,7 @@ impl HeadingCounter { /// Advance the counter and return the numbers for the given heading. pub fn advance(&mut self, heading: &HeadingNode) -> &[NonZeroUsize] { - let level = heading.level().get(); + let level = heading.level(StyleChain::default()).get(); if self.0.len() >= level { self.0[level - 1] = self.0[level - 1].saturating_add(1); diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs index 32b70153c..d4d4d8ca1 100644 --- a/library/src/meta/link.rs +++ b/library/src/meta/link.rs @@ -25,7 +25,7 @@ use crate::text::{Hyphenate, TextNode}; /// /// Display: Link /// Category: meta -#[node(Construct, Show, Finalize)] +#[node(Show, Finalize)] pub struct LinkNode { /// The destination the link points to. /// @@ -48,6 +48,10 @@ pub struct LinkNode { /// #[positional] #[required] + #[parse( + let dest = args.expect::("destination")?; + dest.clone() + )] pub dest: Destination, /// How the link is represented. @@ -56,7 +60,14 @@ pub struct LinkNode { /// parameter can be omitted. In this case, the URL will be shown as the /// link. #[positional] - #[default] + #[required] + #[parse(match &dest { + Destination::Url(url) => match args.eat()? { + Some(body) => body, + None => body_from_url(url), + }, + Destination::Internal(_) => args.expect("body")?, + })] pub body: Content, } @@ -64,21 +75,7 @@ impl LinkNode { /// Create a link node from a URL with its bare text. pub fn from_url(url: EcoString) -> Self { let body = body_from_url(&url); - Self::new(Destination::Url(url)).with_body(body) - } -} - -impl Construct for LinkNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let dest = args.expect::("destination")?; - let body = match &dest { - Destination::Url(url) => match args.eat()? { - Some(body) => body, - None => body_from_url(url), - }, - Destination::Internal(_) => args.expect("body")?, - }; - Ok(Self::new(dest).with_body(body).pack()) + Self::new(Destination::Url(url), body) } } @@ -89,7 +86,7 @@ impl Show for LinkNode { } impl Finalize for LinkNode { - fn finalize(&self, realized: Content) -> Content { + fn finalize(&self, realized: Content, _: StyleChain) -> Content { realized .styled(MetaNode::set_data(vec![Meta::Link(self.dest())])) .styled(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false)))) diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index 3ccc991eb..9e5614aaa 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -30,14 +30,11 @@ pub struct OutlineNode { /// language]($func/text.lang) will be used. This is the default. /// - When set to `{none}`, the outline will not have a title. /// - A custom title can be set by passing content. - #[settable] #[default(Some(Smart::Auto))] pub title: Option>, /// The maximum depth up to which headings are included in the outline. When /// this argument is `{none}`, all headings are included. - #[settable] - #[default] pub depth: Option, /// Whether to indent the subheadings to align the start of their numbering @@ -57,7 +54,6 @@ pub struct OutlineNode { /// == Products /// #lorem(10) /// ``` - #[settable] #[default(false)] pub indent: bool, @@ -69,7 +65,6 @@ pub struct OutlineNode { /// /// = A New Beginning /// ``` - #[settable] #[default(Some(RepeatNode::new(TextNode::packed(".")).pack()))] pub fill: Option, } @@ -102,7 +97,7 @@ impl Show for OutlineNode { styles: StyleChain, ) -> SourceResult { let mut seq = vec![ParbreakNode::new().pack()]; - if let Some(title) = Self::title_in(styles) { + if let Some(title) = self.title(styles) { let title = title.clone().unwrap_or_else(|| { TextNode::packed(match TextNode::lang_in(styles) { Lang::GERMAN => "Inhaltsverzeichnis", @@ -112,14 +107,15 @@ impl Show for OutlineNode { seq.push( HeadingNode::new(title) - .pack() - .styled(HeadingNode::set_numbering(None)) - .styled(HeadingNode::set_outlined(false)), + .with_level(NonZeroUsize::new(1).unwrap()) + .with_numbering(None) + .with_outlined(false) + .pack(), ); } - let indent = Self::indent_in(styles); - let depth = Self::depth_in(styles); + let indent = self.indent(styles); + let depth = self.depth(styles); let mut ancestors: Vec<&Content> = vec![]; for (_, node) in vt.locate(Selector::node::()) { @@ -129,13 +125,14 @@ impl Show for OutlineNode { let heading = node.to::().unwrap(); if let Some(depth) = depth { - if depth < heading.level() { + if depth < heading.level(StyleChain::default()) { continue; } } while ancestors.last().map_or(false, |last| { - last.to::().unwrap().level() >= heading.level() + last.to::().unwrap().level(StyleChain::default()) + >= heading.level(StyleChain::default()) }) { ancestors.pop(); } @@ -171,7 +168,7 @@ impl Show for OutlineNode { seq.push(start.linked(Destination::Internal(loc))); // Add filler symbols between the section name and page number. - if let Some(filler) = Self::fill_in(styles) { + if let Some(filler) = self.fill(styles) { seq.push(SpaceNode::new().pack()); seq.push( BoxNode::new() diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index 1b3167e10..2a552226f 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -15,11 +15,6 @@ use crate::prelude::*; /// Category: text #[node(Show)] pub struct UnderlineNode { - /// The content to underline. - #[positional] - #[required] - pub body: Content, - /// How to stroke the line. The text color and thickness are read from the /// font tables if `{auto}`. /// @@ -30,10 +25,8 @@ pub struct UnderlineNode { /// [care], /// ) /// ``` - #[settable] #[resolve] #[fold] - #[default] pub stroke: Smart, /// Position of the line relative to the baseline, read from the font tables @@ -44,9 +37,7 @@ pub struct UnderlineNode { /// The Tale Of A Faraway Line I /// ] /// ``` - #[settable] #[resolve] - #[default] pub offset: Smart, /// Amount that the line will be longer or shorter than its associated text. @@ -56,9 +47,7 @@ pub struct UnderlineNode { /// underline(extent: 2pt)[Chapter 1] /// ) /// ``` - #[settable] #[resolve] - #[default] pub extent: Length, /// Whether the line skips sections in which it would collide with the @@ -68,19 +57,23 @@ pub struct UnderlineNode { /// This #underline(evade: true)[is great]. /// This #underline(evade: false)[is less great]. /// ``` - #[settable] #[default(true)] pub evade: bool, + + /// The content to underline. + #[positional] + #[required] + pub body: Content, } impl Show for UnderlineNode { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult { Ok(self.body().styled(TextNode::set_deco(Decoration { line: DecoLine::Underline, - stroke: Self::stroke_in(styles).unwrap_or_default(), - offset: Self::offset_in(styles), - extent: Self::extent_in(styles), - evade: Self::evade_in(styles), + stroke: self.stroke(styles).unwrap_or_default(), + offset: self.offset(styles), + extent: self.extent(styles), + evade: self.evade(styles), }))) } } @@ -96,11 +89,6 @@ impl Show for UnderlineNode { /// Category: text #[node(Show)] pub struct OverlineNode { - /// The content to add a line over. - #[positional] - #[required] - pub body: Content, - /// How to stroke the line. The text color and thickness are read from the /// font tables if `{auto}`. /// @@ -112,10 +100,8 @@ pub struct OverlineNode { /// [The Forest Theme], /// ) /// ``` - #[settable] #[resolve] #[fold] - #[default] pub stroke: Smart, /// Position of the line relative to the baseline, read from the font tables @@ -126,9 +112,7 @@ pub struct OverlineNode { /// The Tale Of A Faraway Line II /// ] /// ``` - #[settable] #[resolve] - #[default] pub offset: Smart, /// Amount that the line will be longer or shorter than its associated text. @@ -138,9 +122,7 @@ pub struct OverlineNode { /// #set underline(extent: 4pt) /// #overline(underline[Typography Today]) /// ``` - #[settable] #[resolve] - #[default] pub extent: Length, /// Whether the line skips sections in which it would collide with the @@ -155,19 +137,23 @@ pub struct OverlineNode { /// [Temple], /// ) /// ``` - #[settable] #[default(true)] pub evade: bool, + + /// The content to add a line over. + #[positional] + #[required] + pub body: Content, } impl Show for OverlineNode { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult { Ok(self.body().styled(TextNode::set_deco(Decoration { line: DecoLine::Overline, - stroke: Self::stroke_in(styles).unwrap_or_default(), - offset: Self::offset_in(styles), - extent: Self::extent_in(styles), - evade: Self::evade_in(styles), + stroke: self.stroke(styles).unwrap_or_default(), + offset: self.offset(styles), + extent: self.extent(styles), + evade: self.evade(styles), }))) } } @@ -183,11 +169,6 @@ impl Show for OverlineNode { /// Category: text #[node(Show)] pub struct StrikeNode { - /// The content to strike through. - #[positional] - #[required] - pub body: Content, - /// How to stroke the line. The text color and thickness are read from the /// font tables if `{auto}`. /// @@ -198,10 +179,8 @@ pub struct StrikeNode { /// This is #strike(stroke: 1.5pt + red)[very stricken through]. \ /// This is #strike(stroke: 10pt)[redacted]. /// ``` - #[settable] #[resolve] #[fold] - #[default] pub stroke: Smart, /// Position of the line relative to the baseline, read from the font tables @@ -214,9 +193,7 @@ pub struct StrikeNode { /// This is #strike(offset: auto)[low-ish]. \ /// This is #strike(offset: -3.5pt)[on-top]. /// ``` - #[settable] #[resolve] - #[default] pub offset: Smart, /// Amount that the line will be longer or shorter than its associated text. @@ -225,19 +202,22 @@ pub struct StrikeNode { /// This #strike(extent: -2pt)[skips] parts of the word. /// This #strike(extent: 2pt)[extends] beyond the word. /// ``` - #[settable] #[resolve] - #[default] pub extent: Length, + + /// The content to strike through. + #[positional] + #[required] + pub body: Content, } impl Show for StrikeNode { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult { Ok(self.body().styled(TextNode::set_deco(Decoration { line: DecoLine::Strikethrough, - stroke: Self::stroke_in(styles).unwrap_or_default(), - offset: Self::offset_in(styles), - extent: Self::extent_in(styles), + stroke: self.stroke(styles).unwrap_or_default(), + offset: self.offset(styles), + extent: self.extent(styles), evade: false, }))) } diff --git a/library/src/text/misc.rs b/library/src/text/misc.rs index 91ac17487..64ab5bd20 100644 --- a/library/src/text/misc.rs +++ b/library/src/text/misc.rs @@ -51,7 +51,6 @@ pub struct LinebreakNode { /// line breaks in this paragraph #jb /// for an _interesting_ result. #jb /// ``` - #[named] #[default(false)] pub justify: bool, } @@ -85,25 +84,24 @@ impl Behave for LinebreakNode { /// Category: text #[node(Show)] pub struct StrongNode { - /// The content to strongly emphasize. - #[positional] - #[required] - pub body: Content, - /// The delta to apply on the font weight. /// /// ```example /// #set strong(delta: 0) /// No *effect!* /// ``` - #[settable] #[default(300)] pub delta: i64, + + /// The content to strongly emphasize. + #[positional] + #[required] + pub body: Content, } impl Show for StrongNode { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult { - Ok(self.body().styled(TextNode::set_delta(Delta(Self::delta_in(styles))))) + Ok(self.body().styled(TextNode::set_delta(Delta(self.delta(styles))))) } } diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index 2318c4269..83f4e2d74 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -46,12 +46,6 @@ use crate::prelude::*; /// Category: text #[node(Construct)] pub struct TextNode { - /// The text. - #[positional] - #[required] - #[skip] - pub text: EcoString, - /// A prioritized sequence of font families. /// /// When processing text, Typst tries all specified font families in order @@ -69,7 +63,6 @@ pub struct TextNode { /// هذا عربي. /// /// ``` - #[settable] #[default(FontList(vec![FontFamily::new("Linux Libertine")]))] pub font: FontList, @@ -90,7 +83,6 @@ pub struct TextNode { /// #set text(fallback: false) /// هذا عربي /// ``` - #[settable] #[default(true)] pub fallback: bool, @@ -111,8 +103,6 @@ pub struct TextNode { /// #text(font: "Linux Libertine", style: "italic")[Italic] /// #text(font: "DejaVu Sans", style: "oblique")[Oblique] /// ``` - #[settable] - #[default(FontStyle::Normal)] pub style: FontStyle, /// The desired thickness of the font's glyphs. Accepts an integer between @@ -132,8 +122,6 @@ pub struct TextNode { /// #text(weight: 500)[Medium] \ /// #text(weight: "bold")[Bold] /// ``` - #[settable] - #[default(FontWeight::REGULAR)] pub weight: FontWeight, /// The desired width of the glyphs. Accepts a ratio between `{50%}` and @@ -144,8 +132,6 @@ pub struct TextNode { /// #text(stretch: 75%)[Condensed] \ /// #text(stretch: 100%)[Normal] /// ``` - #[settable] - #[default(FontStretch::NORMAL)] pub stretch: FontStretch, /// The size of the glyphs. This value forms the basis of the `em` unit: @@ -158,8 +144,7 @@ pub struct TextNode { /// #set text(size: 20pt) /// very #text(1.5em)[big] text /// ``` - #[settable] - #[shorthand] + #[parse(args.named_or_find("size")?)] #[fold] #[default(Abs::pt(11.0))] pub size: TextSize, @@ -170,8 +155,7 @@ pub struct TextNode { /// #set text(fill: red) /// This text is red. /// ``` - #[shorthand] - #[settable] + #[parse(args.named_or_find("fill")?)] #[default(Color::BLACK.into())] pub fill: Paint, @@ -181,9 +165,7 @@ pub struct TextNode { /// #set text(tracking: 1.5pt) /// Distant text. /// ``` - #[settable] #[resolve] - #[default(Length::zero())] pub tracking: Length, /// The amount of space between words. @@ -195,7 +177,6 @@ pub struct TextNode { /// #set text(spacing: 200%) /// Text with distant words. /// ``` - #[settable] #[resolve] #[default(Rel::one())] pub spacing: Rel, @@ -206,9 +187,7 @@ pub struct TextNode { /// A #text(baseline: 3pt)[lowered] /// word. /// ``` - #[settable] #[resolve] - #[default(Length::zero())] pub baseline: Length, /// Whether certain glyphs can hang over into the margin in justified text. @@ -231,7 +210,6 @@ pub struct TextNode { /// margin, making the paragraph's /// edge less clear. /// ``` - #[settable] #[default(true)] pub overhang: bool, @@ -248,7 +226,6 @@ pub struct TextNode { /// #set text(top-edge: "cap-height") /// #rect(fill: aqua)[Typst] /// ``` - #[settable] #[default(TextEdge::Metric(VerticalFontMetric::CapHeight))] pub top_edge: TextEdge, @@ -265,7 +242,6 @@ pub struct TextNode { /// #set text(bottom-edge: "descender") /// #rect(fill: aqua)[Typst] /// ``` - #[settable] #[default(TextEdge::Metric(VerticalFontMetric::Baseline))] pub bottom_edge: TextEdge, @@ -286,15 +262,12 @@ pub struct TextNode { /// = Einleitung /// In diesem Dokument, ... /// ``` - #[settable] #[default(Lang::ENGLISH)] pub lang: Lang, /// An [ISO 3166-1 alpha-2 region code.](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) /// /// This lets the text processing pipeline make more informed choices. - #[settable] - #[default(None)] pub region: Option, /// The dominant direction for text and inline objects. Possible values are: @@ -321,9 +294,7 @@ pub struct TextNode { /// #set text(dir: rtl) /// هذا عربي. /// ``` - #[settable] #[resolve] - #[default(HorizontalDir(Smart::Auto))] pub dir: HorizontalDir, /// Whether to hyphenate text to improve line breaking. When `{auto}`, text @@ -343,9 +314,7 @@ pub struct TextNode { /// enabling hyphenation can /// improve justification. /// ``` - #[settable] #[resolve] - #[default(Hyphenate(Smart::Auto))] pub hyphenate: Hyphenate, /// Whether to apply kerning. @@ -363,7 +332,6 @@ pub struct TextNode { /// #set text(kerning: false) /// Totally /// ``` - #[settable] #[default(true)] pub kerning: bool, @@ -380,7 +348,6 @@ pub struct TextNode { /// #set text(alternates: true) /// 0, a, g, ß /// ``` - #[settable] #[default(false)] pub alternates: bool, @@ -389,8 +356,6 @@ pub struct TextNode { /// you need to consult your font to know which sets are available. When set /// to an integer between `{1}` and `{20}`, enables the corresponding /// OpenType font feature from `ss01`, ..., `ss20`. - #[settable] - #[default(None)] pub stylistic_set: Option, /// Whether standard ligatures are active. @@ -407,19 +372,16 @@ pub struct TextNode { /// #set text(ligatures: false) /// A fine ligature. /// ``` - #[settable] #[default(true)] pub ligatures: bool, /// Whether ligatures that should be used sparingly are active. Setting this /// to `{true}` enables the OpenType `dlig` font feature. - #[settable] #[default(false)] pub discretionary_ligatures: bool, /// Whether historical ligatures are active. Setting this to `{true}` /// enables the OpenType `hlig` font feature. - #[settable] #[default(false)] pub historical_ligatures: bool, @@ -434,8 +396,6 @@ pub struct TextNode { /// #set text(number-type: "old-style") /// Number 9. /// ``` - #[settable] - #[default(Smart::Auto)] pub number_type: Smart, /// The width of numbers / figures. When set to `{auto}`, the default @@ -451,8 +411,6 @@ pub struct TextNode { /// A 12 B 34. \ /// A 56 B 78. /// ``` - #[settable] - #[default(Smart::Auto)] pub number_width: Smart, /// Whether to have a slash through the zero glyph. Setting this to `{true}` @@ -461,7 +419,6 @@ pub struct TextNode { /// ```example /// 0, #text(slashed-zero: true)[0] /// ``` - #[settable] #[default(false)] pub slashed_zero: bool, @@ -472,7 +429,6 @@ pub struct TextNode { /// 1/2 \ /// #text(fractions: true)[1/2] /// ``` - #[settable] #[default(false)] pub fractions: bool, @@ -488,43 +444,39 @@ pub struct TextNode { /// #set text(features: ("frac",)) /// 1/2 /// ``` - #[settable] #[fold] - #[default(FontFeatures(vec![]))] pub features: FontFeatures, + /// The text. + #[internal] + #[positional] + #[required] + pub text: EcoString, + /// A delta to apply on the font weight. - #[settable] + #[internal] #[fold] - #[skip] - #[default(0)] pub delta: Delta, /// Whether the font style should be inverted. - #[settable] + #[internal] #[fold] - #[skip] #[default(false)] pub emph: Toggle, + /// Decorative lines. + #[internal] + #[fold] + pub deco: Decoration, + /// A case transformation that should be applied to the text. - #[settable] - #[skip] - #[default(None)] + #[internal] pub case: Option, /// Whether small capital glyphs should be used. ("smcp") - #[settable] - #[skip] + #[internal] #[default(false)] pub smallcaps: bool, - - /// Decorative lines. - #[settable] - #[fold] - #[skip] - #[default(vec![])] - pub deco: Decoration, } impl TextNode { @@ -539,7 +491,9 @@ impl Construct for TextNode { // The text constructor is special: It doesn't create a text node. // Instead, it leaves the passed argument structurally unchanged, but // styles all text in it. - args.expect("body") + let styles = Self::set(args)?; + let body = args.expect::("body")?; + Ok(body.styled_with_map(styles)) } } @@ -651,7 +605,7 @@ cast_to_value! { } /// The direction of text and inline objects in their line. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct HorizontalDir(pub Smart); cast_from_value! { @@ -680,7 +634,7 @@ impl Resolve for HorizontalDir { } /// Whether to hyphenate text. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct Hyphenate(pub Smart); cast_from_value! { diff --git a/library/src/text/quotes.rs b/library/src/text/quotes.rs index 1c6028712..863cc3bde 100644 --- a/library/src/text/quotes.rs +++ b/library/src/text/quotes.rs @@ -27,7 +27,6 @@ use crate::prelude::*; #[node] pub struct SmartQuoteNode { /// Whether this should be a double quote. - #[named] #[default(true)] pub double: bool, @@ -41,7 +40,6 @@ pub struct SmartQuoteNode { /// /// These are "dumb" quotes. /// ``` - #[settable] #[default(true)] pub enabled: bool, } diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index 8526a278f..36a0fc784 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -87,7 +87,6 @@ pub struct RawNode { /// rg "Hello World" /// ``` /// ```` - #[named] #[default(false)] pub block: bool, @@ -102,8 +101,6 @@ pub struct RawNode { /// This is *Typst!* /// ``` /// ```` - #[settable] - #[default] pub lang: Option, } @@ -114,7 +111,7 @@ impl Prepare for RawNode { mut this: Content, styles: StyleChain, ) -> SourceResult { - this.push_field("lang", Self::lang_in(styles).clone()); + this.push_field("lang", self.lang(styles).clone()); Ok(this) } } @@ -122,7 +119,7 @@ impl Prepare for RawNode { impl Show for RawNode { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult { let text = self.text(); - let lang = Self::lang_in(styles).as_ref().map(|s| s.to_lowercase()); + let lang = self.lang(styles).as_ref().map(|s| s.to_lowercase()); let foreground = THEME .settings .foreground @@ -170,7 +167,7 @@ impl Show for RawNode { TextNode::packed(text) }; - if self.block() { + if self.block(styles) { realized = BlockNode::new().with_body(Some(realized)).pack(); } @@ -179,7 +176,7 @@ impl Show for RawNode { } impl Finalize for RawNode { - fn finalize(&self, realized: Content) -> Content { + fn finalize(&self, realized: Content, _: StyleChain) -> Content { let mut map = StyleMap::new(); map.set(TextNode::set_overhang(false)); map.set(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false)))); diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs index 98718365d..ccdb01979 100644 --- a/library/src/text/shift.rs +++ b/library/src/text/shift.rs @@ -16,11 +16,6 @@ use crate::prelude::*; /// Category: text #[node(Show)] pub struct SubNode { - /// The text to display in subscript. - #[positional] - #[required] - pub body: Content, - /// Whether to prefer the dedicated subscript characters of the font. /// /// If this is enabled, Typst first tries to transform the text to subscript @@ -31,23 +26,25 @@ pub struct SubNode { /// N#sub(typographic: true)[1] /// N#sub(typographic: false)[1] /// ``` - #[settable] #[default(true)] pub typographic: bool, /// The baseline shift for synthetic subscripts. Does not apply if /// `typographic` is true and the font has subscript codepoints for the /// given `body`. - #[settable] #[default(Em::new(0.2).into())] pub baseline: Length, /// The font size for synthetic subscripts. Does not apply if /// `typographic` is true and the font has subscript codepoints for the /// given `body`. - #[settable] #[default(TextSize(Em::new(0.6).into()))] pub size: TextSize, + + /// The text to display in subscript. + #[positional] + #[required] + pub body: Content, } impl Show for SubNode { @@ -59,7 +56,7 @@ impl Show for SubNode { ) -> SourceResult { let body = self.body(); let mut transformed = None; - if Self::typographic_in(styles) { + if self.typographic(styles) { if let Some(text) = search_text(&body, true) { if is_shapable(vt, &text, styles) { transformed = Some(TextNode::packed(text)); @@ -68,8 +65,8 @@ impl Show for SubNode { }; Ok(transformed.unwrap_or_else(|| { - body.styled(TextNode::set_baseline(Self::baseline_in(styles))) - .styled(TextNode::set_size(Self::size_in(styles))) + body.styled(TextNode::set_baseline(self.baseline(styles))) + .styled(TextNode::set_size(self.size(styles))) })) } } @@ -87,11 +84,6 @@ impl Show for SubNode { /// Category: text #[node(Show)] pub struct SuperNode { - /// The text to display in superscript. - #[positional] - #[required] - pub body: Content, - /// Whether to prefer the dedicated superscript characters of the font. /// /// If this is enabled, Typst first tries to transform the text to @@ -102,23 +94,25 @@ pub struct SuperNode { /// N#super(typographic: true)[1] /// N#super(typographic: false)[1] /// ``` - #[settable] #[default(true)] pub typographic: bool, /// The baseline shift for synthetic superscripts. Does not apply if /// `typographic` is true and the font has superscript codepoints for the /// given `body`. - #[settable] #[default(Em::new(-0.5).into())] pub baseline: Length, /// The font size for synthetic superscripts. Does not apply if /// `typographic` is true and the font has superscript codepoints for the /// given `body`. - #[settable] #[default(TextSize(Em::new(0.6).into()))] pub size: TextSize, + + /// The text to display in superscript. + #[positional] + #[required] + pub body: Content, } impl Show for SuperNode { @@ -130,7 +124,7 @@ impl Show for SuperNode { ) -> SourceResult { let body = self.body(); let mut transformed = None; - if Self::typographic_in(styles) { + if self.typographic(styles) { if let Some(text) = search_text(&body, false) { if is_shapable(vt, &text, styles) { transformed = Some(TextNode::packed(text)); @@ -139,8 +133,8 @@ impl Show for SuperNode { }; Ok(transformed.unwrap_or_else(|| { - body.styled(TextNode::set_baseline(Self::baseline_in(styles))) - .styled(TextNode::set_size(Self::size_in(styles))) + body.styled(TextNode::set_baseline(self.baseline(styles))) + .styled(TextNode::set_size(self.size(styles))) })) } } diff --git a/library/src/visualize/image.rs b/library/src/visualize/image.rs index 1bc864e57..1fdb418b4 100644 --- a/library/src/visualize/image.rs +++ b/library/src/visualize/image.rs @@ -20,39 +20,29 @@ use crate::prelude::*; /// /// Display: Image /// Category: visualize -#[node(Construct, Layout)] +#[node(Layout)] pub struct ImageNode { /// Path to an image file. #[positional] #[required] - pub path: EcoString, - - /// The width of the image. - #[named] - #[default] - pub width: Smart>, - - /// The height of the image. - #[named] - #[default] - pub height: Smart>, - - /// How the image should adjust itself to a given area. - #[settable] - #[default(ImageFit::Cover)] - pub fit: ImageFit, -} - -impl Construct for ImageNode { - fn construct(vm: &Vm, args: &mut Args) -> SourceResult { + #[parse( let Spanned { v: path, span } = args.expect::>("path to image file")?; let path: EcoString = vm.locate(&path).at(span)?.to_string_lossy().into(); let _ = load(vm.world(), &path).at(span)?; - let width = args.named::>>("width")?.unwrap_or_default(); - let height = args.named::>>("height")?.unwrap_or_default(); - Ok(ImageNode::new(path).with_width(width).with_height(height).pack()) - } + path + )] + pub path: EcoString, + + /// The width of the image. + pub width: Smart>, + + /// The height of the image. + pub height: Smart>, + + /// How the image should adjust itself to a given area. + #[default(ImageFit::Cover)] + pub fit: ImageFit, } impl Layout for ImageNode { @@ -63,7 +53,7 @@ impl Layout for ImageNode { regions: Regions, ) -> SourceResult { let image = load(vt.world(), &self.path()).unwrap(); - let sizing = Axes::new(self.width(), self.height()); + let sizing = Axes::new(self.width(styles), self.height(styles)); let region = sizing .zip(regions.base()) .map(|(s, r)| s.map(|v| v.resolve(styles).relative_to(r))) @@ -90,7 +80,7 @@ impl Layout for ImageNode { }; // Compute the actual size of the fitted image. - let fit = Self::fit_in(styles); + let fit = self.fit(styles); let fitted = match fit { ImageFit::Cover | ImageFit::Contain => { if wide == (fit == ImageFit::Contain) { diff --git a/library/src/visualize/line.rs b/library/src/visualize/line.rs index 6beb7bebf..015abbb0f 100644 --- a/library/src/visualize/line.rs +++ b/library/src/visualize/line.rs @@ -9,34 +9,28 @@ use crate::prelude::*; /// #line(end: (50%, 50%)) /// ``` /// -/// ## Parameters -/// - end: `Axes>` (named) -/// The end point of the line. -/// Must be an array of exactly two relative lengths. -/// -/// - length: `Rel` (named) -/// The line's length. Mutually exclusive with `end`. -/// -/// - angle: `Angle` (named) -/// The angle at which the line points away from the origin. Mutually -/// exclusive with `end`. -/// /// Display: Line /// Category: visualize -#[node(Construct, Layout)] +#[node(Layout)] pub struct LineNode { /// The start point of the line. /// /// Must be an array of exactly two relative lengths. - #[named] - #[default] + #[resolve] pub start: Axes>, /// The offset from `start` where the line ends. - #[named] - #[default] - #[skip] - pub delta: Axes>, + #[resolve] + pub end: Smart>>, + + /// The line's length. Mutually exclusive with `end`. + #[resolve] + #[default(Abs::pt(30.0).into())] + pub length: Rel, + + /// The angle at which the line points away from the origin. Mutually + /// exclusive with `end`. + pub angle: Angle, /// How to stroke the line. This can be: /// @@ -50,33 +44,11 @@ pub struct LineNode { /// ```example /// #line(length: 100%, stroke: 2pt + red) /// ``` - #[settable] #[resolve] #[fold] - #[default] pub stroke: PartialStroke, } -impl Construct for LineNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let start = args.named("start")?.unwrap_or_default(); - let delta = match args.named::>>("end")? { - Some(end) => end.zip(start).map(|(to, from)| to - from), - None => { - let length = - args.named::>("length")?.unwrap_or(Abs::pt(30.0).into()); - - let angle = args.named::("angle")?.unwrap_or_default(); - let x = angle.cos() * length; - let y = angle.sin() * length; - - Axes::new(x, y) - } - }; - Ok(Self::new().with_start(start).with_delta(delta).pack()) - } -} - impl Layout for LineNode { fn layout( &self, @@ -84,27 +56,27 @@ impl Layout for LineNode { styles: StyleChain, regions: Regions, ) -> SourceResult { - let stroke = Self::stroke_in(styles).unwrap_or_default(); + let resolve = |axes: Axes>| { + axes.zip(regions.base()).map(|(l, b)| l.relative_to(b)) + }; - let origin = self - .start() - .resolve(styles) - .zip(regions.base()) - .map(|(l, b)| l.relative_to(b)); + let start = resolve(self.start(styles)); + let delta = + self.end(styles).map(|end| resolve(end) - start).unwrap_or_else(|| { + let length = self.length(styles); + let angle = self.angle(styles); + let x = angle.cos() * length; + let y = angle.sin() * length; + resolve(Axes::new(x, y)) + }); - let delta = self - .delta() - .resolve(styles) - .zip(regions.base()) - .map(|(l, b)| l.relative_to(b)); - - let size = origin.max(origin + delta).max(Size::zero()); + let stroke = self.stroke(styles).unwrap_or_default(); + let size = start.max(start + delta).max(Size::zero()); let target = regions.expand.select(regions.size, size); let mut frame = Frame::new(target); let shape = Geometry::Line(delta.to_point()).stroked(stroke); - frame.push(origin.to_point(), Element::Shape(shape)); - + frame.push(start.to_point(), Element::Shape(shape)); Ok(Fragment::frame(frame)) } } diff --git a/library/src/visualize/shape.rs b/library/src/visualize/shape.rs index fc1d462e4..ab9538465 100644 --- a/library/src/visualize/shape.rs +++ b/library/src/visualize/shape.rs @@ -20,22 +20,10 @@ use crate::prelude::*; /// Category: visualize #[node(Layout)] pub struct RectNode { - /// The content to place into the rectangle. - /// - /// When this is omitted, the rectangle takes on a default size of at most - /// `{45pt}` by `{30pt}`. - #[positional] - #[default] - pub body: Option, - /// The rectangle's width, relative to its parent container. - #[named] - #[default] pub width: Smart>, /// The rectangle's height, relative to its parent container. - #[named] - #[default] pub height: Smart>, /// How to fill the rectangle. @@ -46,8 +34,6 @@ pub struct RectNode { /// ```example /// #rect(fill: blue) /// ``` - #[settable] - #[default] pub fill: Option, /// How to stroke the rectangle. This can be: @@ -82,10 +68,8 @@ pub struct RectNode { /// rect(stroke: 2pt + red), /// ) /// ``` - #[settable] #[resolve] #[fold] - #[default] pub stroke: Smart>>>, /// How much to round the rectangle's corners, relative to the minimum of @@ -122,10 +106,8 @@ pub struct RectNode { /// ), /// ) /// ``` - #[settable] #[resolve] #[fold] - #[default] pub radius: Corners>>, /// How much to pad the rectangle's content. @@ -138,7 +120,6 @@ pub struct RectNode { /// ```example /// #rect(inset: 0pt)[Tight]) /// ``` - #[settable] #[resolve] #[fold] #[default(Sides::splat(Abs::pt(5.0).into()))] @@ -146,11 +127,16 @@ pub struct RectNode { /// How much to expand the rectangle's size without affecting the layout. /// See the [box's documentation]($func/box.outset) for more details. - #[settable] #[resolve] #[fold] - #[default] pub outset: Sides>>, + + /// The content to place into the rectangle. + /// + /// When this is omitted, the rectangle takes on a default size of at most + /// `{45pt}` by `{30pt}`. + #[positional] + pub body: Option, } impl Layout for RectNode { @@ -165,13 +151,13 @@ impl Layout for RectNode { styles, regions, ShapeKind::Rect, - &self.body(), - Axes::new(self.width(), self.height()), - Self::fill_in(styles), - Self::stroke_in(styles), - Self::inset_in(styles), - Self::outset_in(styles), - Self::radius_in(styles), + &self.body(styles), + Axes::new(self.width(styles), self.height(styles)), + self.fill(styles), + self.stroke(styles), + self.inset(styles), + self.outset(styles), + self.radius(styles), ) } } @@ -191,66 +177,57 @@ impl Layout for RectNode { /// ``` /// /// ## Parameters -/// - size: `Smart` (named) +/// - size: `Smart` (named, settable) /// The square's side length. This is mutually exclusive with `width` and /// `height`. /// /// Display: Square /// Category: visualize -#[node(Construct, Layout)] +#[node(Layout)] pub struct SquareNode { - /// The content to place into the square. The square expands to fit this - /// content, keeping the 1-1 aspect ratio. - /// - /// When this is omitted, the square takes on a default size of at most - /// `{30pt}`. - #[positional] - #[default] - pub body: Option, - /// The square's width. This is mutually exclusive with `size` and `height`. /// /// In contrast to `size`, this can be relative to the parent container's /// width. - #[named] - #[default] + #[parse( + let size = args.named::>("size")?.map(|s| s.map(Rel::from)); + match size { + None => args.named("width")?, + size => size, + } + )] pub width: Smart>, /// The square's height. This is mutually exclusive with `size` and `width`. /// /// In contrast to `size`, this can be relative to the parent container's /// height. - #[named] - #[default] + #[parse(match size { + None => args.named("height")?, + size => size, + })] pub height: Smart>, /// How to fill the square. See the /// [rectangle's documentation]($func/rect.fill) for more details. - #[settable] - #[default] pub fill: Option, /// How to stroke the square. See the [rectangle's /// documentation]($func/rect.stroke) for more details. - #[settable] #[resolve] #[fold] - #[default] pub stroke: Smart>>>, /// How much to round the square's corners. See the [rectangle's /// documentation]($func/rect.radius) for more details. - #[settable] #[resolve] #[fold] - #[default] pub radius: Corners>>, /// How much to pad the square's content. See the [rectangle's /// documentation]($func/rect.inset) for more details. /// /// The default value is `{5pt}`. - #[settable] #[resolve] #[fold] #[default(Sides::splat(Abs::pt(5.0).into()))] @@ -258,33 +235,17 @@ pub struct SquareNode { /// How much to expand the square's size without affecting the layout. See /// the [rectangle's documentation]($func/rect.outset) for more details. - #[settable] #[resolve] #[fold] - #[default] pub outset: Sides>>, -} -impl Construct for SquareNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let size = args.named::>("size")?.map(|s| s.map(Rel::from)); - let width = match size { - None => args.named("width")?, - size => size, - } - .unwrap_or_default(); - let height = match size { - None => args.named("height")?, - size => size, - } - .unwrap_or_default(); - let body = args.eat::()?; - Ok(Self::new() - .with_body(body) - .with_width(width) - .with_height(height) - .pack()) - } + /// The content to place into the square. The square expands to fit this + /// content, keeping the 1-1 aspect ratio. + /// + /// When this is omitted, the square takes on a default size of at most + /// `{30pt}`. + #[positional] + pub body: Option, } impl Layout for SquareNode { @@ -299,13 +260,13 @@ impl Layout for SquareNode { styles, regions, ShapeKind::Square, - &self.body(), - Axes::new(self.width(), self.height()), - Self::fill_in(styles), - Self::stroke_in(styles), - Self::inset_in(styles), - Self::outset_in(styles), - Self::radius_in(styles), + &self.body(styles), + Axes::new(self.width(styles), self.height(styles)), + self.fill(styles), + self.stroke(styles), + self.inset(styles), + self.outset(styles), + self.radius(styles), ) } } @@ -329,43 +290,26 @@ impl Layout for SquareNode { /// Category: visualize #[node(Layout)] pub struct EllipseNode { - /// The content to place into the ellipse. - /// - /// When this is omitted, the ellipse takes on a default size of at most - /// `{45pt}` by `{30pt}`. - #[positional] - #[default] - pub body: Option, - /// The ellipse's width, relative to its parent container. - #[named] - #[default] pub width: Smart>, /// The ellipse's height, relative to its parent container. - #[named] - #[default] pub height: Smart>, /// How to fill the ellipse. See the /// [rectangle's documentation]($func/rect.fill) for more details. - #[settable] - #[default] pub fill: Option, /// How to stroke the ellipse. See the [rectangle's /// documentation]($func/rect.stroke) for more details. - #[settable] #[resolve] #[fold] - #[default] pub stroke: Smart>, /// How much to pad the ellipse's content. See the [rectangle's /// documentation]($func/rect.inset) for more details. /// /// The default value is `{5pt}`. - #[settable] #[resolve] #[fold] #[default(Sides::splat(Abs::pt(5.0).into()))] @@ -373,11 +317,16 @@ pub struct EllipseNode { /// How much to expand the ellipse's size without affecting the layout. See /// the [rectangle's documentation]($func/rect.outset) for more details. - #[settable] #[resolve] #[fold] - #[default] pub outset: Sides>>, + + /// The content to place into the ellipse. + /// + /// When this is omitted, the ellipse takes on a default size of at most + /// `{45pt}` by `{30pt}`. + #[positional] + pub body: Option, } impl Layout for EllipseNode { @@ -392,12 +341,12 @@ impl Layout for EllipseNode { styles, regions, ShapeKind::Ellipse, - &self.body(), - Axes::new(self.width(), self.height()), - Self::fill_in(styles), - Self::stroke_in(styles).map(Sides::splat), - Self::inset_in(styles), - Self::outset_in(styles), + &self.body(styles), + Axes::new(self.width(styles), self.height(styles)), + self.fill(styles), + self.stroke(styles).map(Sides::splat), + self.inset(styles), + self.outset(styles), Corners::splat(Rel::zero()), ) } @@ -419,27 +368,28 @@ impl Layout for EllipseNode { /// ``` /// /// ## Parameters -/// - radius: `Length` (named) +/// - radius: `Length` (named, settable) /// The circle's radius. This is mutually exclusive with `width` and /// `height`. /// /// Display: Circle /// Category: visualize -#[node(Construct, Layout)] +#[node(Layout)] pub struct CircleNode { - /// The content to place into the circle. The circle expands to fit this - /// content, keeping the 1-1 aspect ratio. - #[positional] - #[default] - pub body: Option, - /// The circle's width. This is mutually exclusive with `radius` and /// `height`. /// /// In contrast to `size`, this can be relative to the parent container's /// width. - #[named] - #[default] + #[parse( + let size = args + .named::>("radius")? + .map(|s| s.map(|r| 2.0 * Rel::from(r))); + match size { + None => args.named("width")?, + size => size, + } + )] pub width: Smart>, /// The circle's height.This is mutually exclusive with `radius` and @@ -447,19 +397,18 @@ pub struct CircleNode { /// /// In contrast to `size`, this can be relative to the parent container's /// height. - #[named] - #[default] + #[parse(match size { + None => args.named("height")?, + size => size, + })] pub height: Smart>, /// How to fill the circle. See the /// [rectangle's documentation]($func/rect.fill) for more details. - #[settable] - #[default] pub fill: Option, /// How to stroke the circle. See the [rectangle's /// documentation]($func/rect.stroke) for more details. - #[settable] #[resolve] #[fold] #[default(Smart::Auto)] @@ -469,7 +418,6 @@ pub struct CircleNode { /// documentation]($func/rect.inset) for more details. /// /// The default value is `{5pt}`. - #[settable] #[resolve] #[fold] #[default(Sides::splat(Abs::pt(5.0).into()))] @@ -477,35 +425,14 @@ pub struct CircleNode { /// How much to expand the circle's size without affecting the layout. See /// the [rectangle's documentation]($func/rect.outset) for more details. - #[settable] #[resolve] #[fold] - #[default] pub outset: Sides>>, -} -impl Construct for CircleNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let size = args - .named::>("radius")? - .map(|s| s.map(|r| 2.0 * Rel::from(r))); - let width = match size { - None => args.named("width")?, - size => size, - } - .unwrap_or_default(); - let height = match size { - None => args.named("height")?, - size => size, - } - .unwrap_or_default(); - let body = args.eat::()?; - Ok(Self::new() - .with_body(body) - .with_width(width) - .with_height(height) - .pack()) - } + /// The content to place into the circle. The circle expands to fit this + /// content, keeping the 1-1 aspect ratio. + #[positional] + pub body: Option, } impl Layout for CircleNode { @@ -520,12 +447,12 @@ impl Layout for CircleNode { styles, regions, ShapeKind::Circle, - &self.body(), - Axes::new(self.width(), self.height()), - Self::fill_in(styles), - Self::stroke_in(styles).map(Sides::splat), - Self::inset_in(styles), - Self::outset_in(styles), + &self.body(styles), + Axes::new(self.width(styles), self.height(styles)), + self.fill(styles), + self.stroke(styles).map(Sides::splat), + self.inset(styles), + self.outset(styles), Corners::splat(Rel::zero()), ) } diff --git a/macros/src/func.rs b/macros/src/func.rs index 9889b3aec..843c193eb 100644 --- a/macros/src/func.rs +++ b/macros/src/func.rs @@ -143,8 +143,8 @@ fn params(docs: &mut String) -> Result<(Vec, Vec)> { for part in s.eat_until(')').split(',').map(str::trim).filter(|s| !s.is_empty()) { match part { - "named" => named = true, "positional" => positional = true, + "named" => named = true, "required" => required = true, "variadic" => variadic = true, "settable" => settable = true, @@ -152,8 +152,7 @@ fn params(docs: &mut String) -> Result<(Vec, Vec)> { } } - if (!named && !positional) || (variadic && !positional) || (required && variadic) - { + if (!named && !positional) || (variadic && !positional) { bail!(callsite, "invalid combination of parameter flags"); } @@ -169,10 +168,10 @@ fn params(docs: &mut String) -> Result<(Vec, Vec)> { cast: <#ty as ::typst::eval::Cast< ::typst::syntax::Spanned<::typst::eval::Value> >>::describe(), - named: #named, positional: #positional, - required: #required, + named: #named, variadic: #variadic, + required: #required, settable: #settable, } }); diff --git a/macros/src/node.rs b/macros/src/node.rs index a72988413..739cc79df 100644 --- a/macros/src/node.rs +++ b/macros/src/node.rs @@ -12,48 +12,64 @@ struct Node { ident: Ident, name: String, capable: Vec, - set: Option, fields: Vec, } +impl Node { + fn inherent(&self) -> impl Iterator { + self.fields.iter().filter(|field| field.inherent()) + } + + fn settable(&self) -> impl Iterator { + self.fields.iter().filter(|field| field.settable()) + } +} + struct Field { attrs: Vec, vis: syn::Visibility, + + name: String, ident: Ident, ident_in: Ident, with_ident: Ident, set_ident: Ident, - name: String, + internal: bool, positional: bool, required: bool, variadic: bool, - - named: bool, - shorthand: Option, - - settable: bool, fold: bool, resolve: bool, - skip: bool, + parse: Option, ty: syn::Type, output: syn::Type, - default: Option, + default: syn::Expr, } -enum Shorthand { - Positional, - Named(Ident), -} - -impl Node { - fn inherent(&self) -> impl Iterator + Clone { - self.fields.iter().filter(|field| !field.settable) +impl Field { + fn inherent(&self) -> bool { + self.required || self.variadic } - fn settable(&self) -> impl Iterator + Clone { - self.fields.iter().filter(|field| field.settable) + fn settable(&self) -> bool { + !self.inherent() + } +} + +struct FieldParser { + prefix: Vec, + expr: syn::Stmt, +} + +impl Parse for FieldParser { + fn parse(input: ParseStream) -> Result { + let mut stmts = syn::Block::parse_within(input)?; + let Some(expr) = stmts.pop() else { + return Err(input.error("expected at least on expression")); + }; + Ok(Self { prefix: stmts, expr }) } } @@ -70,34 +86,30 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { }; let mut attrs = field.attrs.clone(); + let variadic = has_attr(&mut attrs, "variadic"); + let mut field = Field { vis: field.vis.clone(), + + name: kebab_case(&ident), ident: ident.clone(), ident_in: Ident::new(&format!("{}_in", ident), ident.span()), with_ident: Ident::new(&format!("with_{}", ident), ident.span()), set_ident: Ident::new(&format!("set_{}", ident), ident.span()), - name: kebab_case(&ident), - positional: has_attr(&mut attrs, "positional"), - required: has_attr(&mut attrs, "required"), - variadic: has_attr(&mut attrs, "variadic"), - - named: has_attr(&mut attrs, "named"), - shorthand: parse_attr(&mut attrs, "shorthand")?.map(|v| match v { - None => Shorthand::Positional, - Some(ident) => Shorthand::Named(ident), - }), - - settable: has_attr(&mut attrs, "settable"), + internal: has_attr(&mut attrs, "internal"), + positional: has_attr(&mut attrs, "positional") || variadic, + required: has_attr(&mut attrs, "required") || variadic, + variadic, fold: has_attr(&mut attrs, "fold"), resolve: has_attr(&mut attrs, "resolve"), - skip: has_attr(&mut attrs, "skip"), + parse: parse_attr(&mut attrs, "parse")?.flatten(), ty: field.ty.clone(), output: field.ty.clone(), - default: parse_attr(&mut attrs, "default")?.map(|opt| { - opt.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }) - }), + default: parse_attr(&mut attrs, "default")? + .flatten() + .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }), attrs: { validate_attrs(&attrs)?; @@ -105,6 +117,14 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { }, }; + if field.required && (field.fold || field.resolve) { + bail!(ident, "required fields cannot be folded or resolved"); + } + + if field.required && !field.positional { + bail!(ident, "only positional fields can be required"); + } + if field.resolve { let output = &field.output; field.output = parse_quote! { <#output as ::typst::model::Resolve>::Output }; @@ -114,14 +134,6 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { field.output = parse_quote! { <#output as ::typst::model::Fold>::Output }; } - if !field.positional && !field.named && !field.variadic && !field.settable { - bail!(ident, "expected positional, named, variadic, or settable"); - } - - if !field.required && !field.variadic && field.default.is_none() { - bail!(ident, "non-required fields must have a default value"); - } - fields.push(field); } @@ -130,14 +142,13 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result { .into_iter() .collect(); - let mut attrs = body.attrs.clone(); + let attrs = body.attrs.clone(); Ok(Node { vis: body.vis.clone(), ident: body.ident.clone(), name: body.ident.to_string().trim_end_matches("Node").to_lowercase(), capable, fields, - set: parse_attr(&mut attrs, "set")?.flatten(), attrs: { validate_attrs(&attrs)?; attrs @@ -153,10 +164,10 @@ fn create(node: &Node) -> TokenStream { // Inherent methods and functions. let new = create_new_func(node); - let field_methods = node.inherent().map(create_field_method); - let with_fields_methods = node.inherent().map(create_with_field_method); + let field_methods = node.fields.iter().map(create_field_method); let field_in_methods = node.settable().map(create_field_in_method); - let field_style_methods = node.settable().map(create_field_style_method); + let with_fields_methods = node.fields.iter().map(create_with_field_method); + let field_style_methods = node.settable().map(create_set_field_method); // Trait implementations. let construct = node @@ -177,8 +188,8 @@ fn create(node: &Node) -> TokenStream { impl #ident { #new #(#field_methods)* - #(#with_fields_methods)* #(#field_in_methods)* + #(#with_fields_methods)* #(#field_style_methods)* /// The node's span. @@ -201,42 +212,73 @@ fn create(node: &Node) -> TokenStream { /// Create the `new` function for the node. fn create_new_func(node: &Node) -> TokenStream { - let relevant = node.inherent().filter(|field| field.required || field.variadic); - let params = relevant.clone().map(|field| { - let ident = &field.ident; - let ty = &field.ty; + let params = node.inherent().map(|Field { ident, ty, .. }| { quote! { #ident: #ty } }); - let pushes = relevant.map(|field| { - let ident = &field.ident; - let with_ident = &field.with_ident; + let builder_calls = node.inherent().map(|Field { ident, with_ident, .. }| { quote! { .#with_ident(#ident) } }); - let defaults = node - .inherent() - .filter_map(|field| field.default.as_ref().map(|default| (field, default))) - .map(|(field, default)| { - let with_ident = &field.with_ident; - quote! { .#with_ident(#default) } - }); quote! { /// Create a new node. pub fn new(#(#params),*) -> Self { Self(::typst::model::Content::new::()) - #(#pushes)* - #(#defaults)* + #(#builder_calls)* } } } /// Create an accessor methods for a field. fn create_field_method(field: &Field) -> TokenStream { - let Field { attrs, vis, ident, name, ty, .. } = field; - quote! { - #(#attrs)* - #vis fn #ident(&self) -> #ty { - self.0.cast_field(#name) + let Field { attrs, vis, ident, name, output, .. } = field; + if field.inherent() { + quote! { + #(#attrs)* + #vis fn #ident(&self) -> #output { + self.0.cast_required_field(#name) + } } + } else { + let access = + create_style_chain_access(field, quote! { self.0.field(#name).cloned() }); + quote! { + #(#attrs)* + #vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output { + #access + } + } + } +} + +/// Create a style chain access method for a field. +fn create_field_in_method(field: &Field) -> TokenStream { + let Field { vis, ident_in, name, output, .. } = field; + let doc = format!("Access the `{}` field in the given style chain.", name); + let access = create_style_chain_access(field, quote! { None }); + quote! { + #[doc = #doc] + #vis fn #ident_in(styles: ::typst::model::StyleChain) -> #output { + #access + } + } +} + +/// Create a style chain access method for a field. +fn create_style_chain_access(field: &Field, inherent: TokenStream) -> TokenStream { + let Field { name, ty, default, .. } = field; + let getter = match (field.fold, field.resolve) { + (false, false) => quote! { get }, + (false, true) => quote! { get_resolve }, + (true, false) => quote! { get_fold }, + (true, true) => quote! { get_resolve_fold }, + }; + + quote! { + styles.#getter::<#ty>( + ::typst::model::NodeId::of::(), + #name, + #inherent, + || #default, + ) } } @@ -252,70 +294,8 @@ fn create_with_field_method(field: &Field) -> TokenStream { } } -/// Create a style chain access method for a field. -fn create_field_in_method(field: &Field) -> TokenStream { - let Field { vis, ident_in, name, ty, output, default, .. } = field; - - let doc = format!("Access the `{}` field in the given style chain.", name); - let args = quote! { ::typst::model::NodeId::of::(), #name }; - - let body = if field.fold && field.resolve { - quote! { - fn next( - mut values: impl ::std::iter::Iterator, - styles: ::typst::model::StyleChain, - ) -> #output { - values - .next() - .map(|value| { - ::typst::model::Fold::fold( - ::typst::model::Resolve::resolve(value, styles), - next(values, styles), - ) - }) - .unwrap_or_else(|| #default) - } - next(styles.properties(#args), styles) - } - } else if field.fold { - quote! { - fn next( - mut values: impl ::std::iter::Iterator, - styles: ::typst::model::StyleChain, - ) -> #output { - values - .next() - .map(|value| { - ::typst::model::Fold::fold(value, next(values, styles)) - }) - .unwrap_or_else(|| #default) - } - next(styles.properties(#args), styles) - } - } else if field.resolve { - quote! { - ::typst::model::Resolve::resolve( - styles.property::<#ty>(#args).unwrap_or_else(|| #default), - styles - ) - } - } else { - quote! { - styles.property(#args).unwrap_or_else(|| #default) - } - }; - - quote! { - #[doc = #doc] - #[allow(unused)] - #vis fn #ident_in(styles: ::typst::model::StyleChain) -> #output { - #body - } - } -} - -/// Create a style creation method for a field. -fn create_field_style_method(field: &Field) -> TokenStream { +/// Create a setter method for a field. +fn create_set_field_method(field: &Field) -> TokenStream { let Field { vis, ident, set_ident, name, ty, .. } = field; let doc = format!("Create a style property for the `{}` field.", name); quote! { @@ -335,8 +315,17 @@ fn create_node_impl(node: &Node) -> TokenStream { let ident = &node.ident; let name = &node.name; let vtable_func = create_vtable_func(node); + let infos = node + .fields + .iter() + .filter(|field| !field.internal) + .map(create_param_info); quote! { impl ::typst::model::Node for #ident { + fn pack(self) -> ::typst::model::Content { + self.0 + } + fn id() -> ::typst::model::NodeId { static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta { name: #name, @@ -345,179 +334,145 @@ fn create_node_impl(node: &Node) -> TokenStream { ::typst::model::NodeId::from_meta(&META) } - fn pack(self) -> ::typst::model::Content { - self.0 + fn params() -> ::std::vec::Vec<::typst::eval::ParamInfo> { + ::std::vec![#(#infos),*] } } } } -/// Create the node's metadata vtable. +/// Create the node's casting vtable. fn create_vtable_func(node: &Node) -> TokenStream { let ident = &node.ident; - let checks = - node.capable - .iter() - .filter(|&ident| ident != "Construct") - .map(|capability| { - quote! { - if id == ::std::any::TypeId::of::() { - return Some(unsafe { - ::typst::util::fat::vtable(& - Self(::typst::model::Content::new::<#ident>()) as &dyn #capability - ) - }); - } - } - }); + let relevant = node.capable.iter().filter(|&ident| ident != "Construct"); + let checks = relevant.map(|capability| { + quote! { + if id == ::std::any::TypeId::of::() { + return Some(unsafe { + ::typst::util::fat::vtable(&null as &dyn #capability) + }); + } + } + }); quote! { |id| { + let null = Self(::typst::model::Content::new::<#ident>()); #(#checks)* None } } } +/// Create a parameter info for a field. +fn create_param_info(field: &Field) -> TokenStream { + let Field { name, positional, variadic, required, ty, .. } = field; + let named = !positional; + let settable = field.settable(); + let docs = documentation(&field.attrs); + let docs = docs.trim(); + quote! { + ::typst::eval::ParamInfo { + name: #name, + docs: #docs, + cast: <#ty as ::typst::eval::Cast< + ::typst::syntax::Spanned<::typst::eval::Value> + >>::describe(), + positional: #positional, + named: #named, + variadic: #variadic, + required: #required, + settable: #settable, + } + } +} + /// Create the node's `Construct` implementation. fn create_construct_impl(node: &Node) -> TokenStream { let ident = &node.ident; - let shorthands = create_construct_shorthands(node); - let builders = node.inherent().map(create_construct_builder_call); + let handlers = node + .fields + .iter() + .filter(|field| !field.internal || field.parse.is_some()) + .map(|field| { + let with_ident = &field.with_ident; + let (prefix, value) = create_field_parser(field); + if field.settable() { + quote! { + #prefix + if let Some(value) = #value { + node = node.#with_ident(value); + } + } + } else { + quote! { + #prefix + node = node.#with_ident(#value); + } + } + }); + quote! { impl ::typst::model::Construct for #ident { fn construct( - _: &::typst::eval::Vm, + vm: &::typst::eval::Vm, args: &mut ::typst::eval::Args, ) -> ::typst::diag::SourceResult<::typst::model::Content> { - #(#shorthands)* - Ok(::typst::model::Node::pack( - Self(::typst::model::Content::new::()) - #(#builders)*)) + let mut node = Self(::typst::model::Content::new::()); + #(#handlers)* + Ok(node.0) } } } } -/// Create let bindings for shorthands in the constructor. -fn create_construct_shorthands(node: &Node) -> impl Iterator + '_ { - let mut shorthands = vec![]; - for field in node.inherent() { - if let Some(Shorthand::Named(named)) = &field.shorthand { - shorthands.push(named); - } - } - - shorthands.sort(); - shorthands.dedup_by_key(|ident| ident.to_string()); - shorthands.into_iter().map(|ident| { - let string = ident.to_string(); - quote! { let #ident = args.named(#string)?; } - }) -} - -/// Create a builder call for the constructor. -fn create_construct_builder_call(field: &Field) -> TokenStream { - let name = &field.name; - let with_ident = &field.with_ident; - - let mut value = if field.variadic { - quote! { args.all()? } - } else if field.required { - quote! { args.expect(#name)? } - } else if let Some(shorthand) = &field.shorthand { - match shorthand { - Shorthand::Positional => quote! { args.named_or_find(#name)? }, - Shorthand::Named(named) => { - quote! { args.named(#name)?.or_else(|| #named.clone()) } - } - } - } else if field.named { - quote! { args.named(#name)? } - } else { - quote! { args.find()? } - }; - - if let Some(default) = &field.default { - value = quote! { #value.unwrap_or(#default) }; - } - - quote! { .#with_ident(#value) } -} - /// Create the node's `Set` implementation. fn create_set_impl(node: &Node) -> TokenStream { let ident = &node.ident; - let custom = node.set.as_ref().map(|block| { - quote! { (|| -> typst::diag::SourceResult<()> { #block; Ok(()) } )()?; } - }); - - let mut shorthands = vec![]; - let sets: Vec<_> = node - .settable() - .filter(|field| !field.skip) + let handlers = node + .fields + .iter() + .filter(|field| field.settable() && (!field.internal || field.parse.is_some())) .map(|field| { - let name = &field.name; let set_ident = &field.set_ident; - let value = match &field.shorthand { - Some(Shorthand::Positional) => quote! { args.named_or_find(#name)? }, - Some(Shorthand::Named(named)) => { - shorthands.push(named); - quote! { args.named(#name)?.or_else(|| #named.clone()) } + let (prefix, value) = create_field_parser(field); + quote! { + #prefix + if let Some(value) = #value { + styles.set(Self::#set_ident(value)); } - None => quote! { args.named(#name)? }, - }; - - quote! { styles.set_opt(#value.map(Self::#set_ident)); } - }) - .collect(); - - shorthands.sort(); - shorthands.dedup_by_key(|ident| ident.to_string()); - - let bindings = shorthands.into_iter().map(|ident| { - let string = ident.to_string(); - quote! { let #ident = args.named(#string)?; } - }); - - let infos = node.fields.iter().filter(|p| !p.skip).map(|field| { - let name = &field.name; - let value_ty = &field.ty; - let shorthand = matches!(field.shorthand, Some(Shorthand::Positional)); - let docs = documentation(&field.attrs); - let docs = docs.trim(); - quote! { - ::typst::eval::ParamInfo { - name: #name, - docs: #docs, - cast: <#value_ty as ::typst::eval::Cast< - ::typst::syntax::Spanned<::typst::eval::Value> - >>::describe(), - named: true, - positional: #shorthand, - required: false, - variadic: false, - settable: true, } - } - }); + }); quote! { impl ::typst::model::Set for #ident { fn set( args: &mut ::typst::eval::Args, - constructor: bool, ) -> ::typst::diag::SourceResult<::typst::model::StyleMap> { let mut styles = ::typst::model::StyleMap::new(); - #custom - #(#bindings)* - #(#sets)* + #(#handlers)* Ok(styles) } - - fn properties() -> ::std::vec::Vec<::typst::eval::ParamInfo> { - ::std::vec![#(#infos),*] - } } } } + +/// Create argument parsing code for a field. +fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) { + let name = &field.name; + if let Some(FieldParser { prefix, expr }) = &field.parse { + return (quote! { #(#prefix);* }, quote! { #expr }); + } + + let value = if field.variadic { + quote! { args.all()? } + } else if field.required { + quote! { args.expect(#name)? } + } else if field.positional { + quote! { args.find()? } + } else { + quote! { args.named(#name)? } + }; + + (quote! {}, value) +} diff --git a/src/doc.rs b/src/doc.rs index ce6f4c96e..67e13bc84 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -605,9 +605,7 @@ pub enum Meta { pub struct MetaNode { /// Metadata that should be attached to all elements affected by this style /// property. - #[settable] #[fold] - #[default] pub data: Vec, } diff --git a/src/eval/func.rs b/src/eval/func.rs index 8243b4f60..6f98e3166 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -54,15 +54,11 @@ impl Func { /// Create a new function from a native rust node. pub fn from_node(mut info: FuncInfo) -> Self { - info.params.extend(T::properties()); + info.params.extend(T::params()); Self( Arc::new(Prehashed::new(Repr::Native(Native { - func: |ctx, args| { - let styles = T::set(args, true)?; - let content = T::construct(ctx, args)?; - Ok(Value::Content(content.styled_with_map(styles.scoped()))) - }, - set: Some(|args| T::set(args, false)), + func: |vm, args| T::construct(vm, args).map(Value::Content), + set: Some(T::set), node: Some(NodeId::of::()), info, }))), @@ -281,10 +277,10 @@ pub struct ParamInfo { /// Can be true even if `positional` is true if the parameter can be given /// in both variants. pub named: bool, - /// Is the parameter required? - pub required: bool, /// Can the parameter be given any number of times? pub variadic: bool, + /// Is the parameter required? + pub required: bool, /// Is the parameter settable with a set rule? pub settable: bool, } diff --git a/src/model/content.rs b/src/model/content.rs index 05c5d430a..6b4f5e5d4 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -55,7 +55,7 @@ impl Content { /// Attach a span to the content. pub fn spanned(mut self, span: Span) -> Self { if let Some(styled) = self.to::() { - self = StyledNode::new(styled.body().spanned(span), styled.map()).pack(); + self = StyledNode::new(styled.map(), styled.body().spanned(span)).pack(); } self.span = Some(span); self @@ -78,9 +78,9 @@ impl Content { } else if let Some(styled) = self.to::() { let mut map = styled.map(); map.apply(styles); - StyledNode::new(styled.body(), map).pack() + StyledNode::new(map, styled.body()).pack() } else { - StyledNode::new(self, styles).pack() + StyledNode::new(styles, self).pack() } } @@ -161,7 +161,7 @@ impl Content { } #[track_caller] - pub fn cast_field(&self, name: &str) -> T { + pub fn cast_required_field(&self, name: &str) -> T { match self.field(name) { Some(value) => value.clone().cast().unwrap(), None => field_is_missing(name), @@ -329,15 +329,15 @@ impl Sum for Content { /// Category: special #[node] pub struct StyledNode { - /// The styled content. - #[positional] - #[required] - pub body: Content, - /// The styles. #[positional] #[required] pub map: StyleMap, + + /// The styled content. + #[positional] + #[required] + pub body: Content, } cast_from_value! { @@ -353,8 +353,8 @@ cast_from_value! { /// Category: special #[node] pub struct SequenceNode { + #[positional] #[variadic] - #[required] pub children: Vec, } @@ -370,11 +370,14 @@ impl Debug for Label { /// A constructable, stylable content node. pub trait Node: Construct + Set + Sized + 'static { + /// Pack a node into type-erased content. + fn pack(self) -> Content; + /// The node's ID. fn id() -> NodeId; - /// Pack a node into type-erased content. - fn pack(self) -> Content; + /// List the fields of the node. + fn params() -> Vec; } /// A unique identifier for a node. @@ -432,13 +435,7 @@ pub trait Construct { pub trait Set { /// Parse relevant arguments into style properties for this node. - /// - /// When `constructor` is true, [`construct`](Construct::construct) will run - /// after this invocation of `set` with the remaining arguments. - fn set(args: &mut Args, constructor: bool) -> SourceResult; - - /// List the settable properties. - fn properties() -> Vec; + fn set(args: &mut Args) -> SourceResult; } /// Indicates that a node cannot be labelled. diff --git a/src/model/realize.rs b/src/model/realize.rs index 2f38df518..502774bba 100644 --- a/src/model/realize.rs +++ b/src/model/realize.rs @@ -59,7 +59,7 @@ pub fn realize( if let Some(node) = target.with::() { if target.is_pristine() { if let Some(already) = realized { - realized = Some(node.finalize(already)); + realized = Some(node.finalize(already, styles)); } } } @@ -159,7 +159,7 @@ pub trait Finalize { /// Finalize the fully realized form of the node. Use this for effects that /// should work even in the face of a user-defined show rule, for example /// the linking behaviour of a link node. - fn finalize(&self, realized: Content) -> Content; + fn finalize(&self, realized: Content, styles: StyleChain) -> Content; } /// Guards content against being affected by the same show rule multiple times. diff --git a/src/model/styles.rs b/src/model/styles.rs index bd7a062f5..0b74e1621 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -34,11 +34,6 @@ impl StyleMap { self.0.push(Style::Property(property)); } - /// Set an inner value for a style property if it is `Some(_)`. - pub fn set_opt(&mut self, property: Option) { - self.0.extend(property.map(Style::Property)); - } - /// Remove the style that was last set. pub fn unset(&mut self) { self.0.pop(); @@ -49,30 +44,11 @@ impl StyleMap { self.0.splice(0..0, outer.0.iter().cloned()); } - /// Set an outer style. Like [`chain_one`](StyleChain::chain_one), but - /// in-place. - pub fn apply_one(&mut self, outer: Style) { - self.0.insert(0, outer); - } - - /// Mark all contained properties as _scoped_. This means that they only - /// apply to the first descendant node (of their type) in the hierarchy and - /// not its children, too. This is used by - /// [constructors](super::Construct::construct). - pub fn scoped(mut self) -> Self { - for entry in &mut self.0 { - if let Style::Property(property) = entry { - property.scoped = true; - } - } - self - } - /// Add an origin span to all contained properties. pub fn spanned(mut self, span: Span) -> Self { for entry in &mut self.0 { if let Style::Property(property) = entry { - property.origin = Some(span); + property.span = Some(span); } } self @@ -83,9 +59,8 @@ impl StyleMap { pub fn interruption(&self) -> Option> { let node = NodeId::of::(); self.0.iter().find_map(|entry| match entry { - Style::Property(property) => property.is_of(node).then(|| property.origin), + Style::Property(property) => property.is_of(node).then(|| property.span), Style::Recipe(recipe) => recipe.is_of(node).then(|| Some(recipe.span)), - _ => None, }) } } @@ -118,8 +93,6 @@ pub enum Style { Property(Property), /// A show rule recipe. Recipe(Recipe), - /// A barrier for scoped styles. - Barrier(NodeId), } impl Style { @@ -145,7 +118,6 @@ impl Debug for Style { match self { Self::Property(property) => property.fmt(f), Self::Recipe(recipe) => recipe.fmt(f), - Self::Barrier(id) => write!(f, "#[Barrier for {id:?}]"), } } } @@ -165,17 +137,14 @@ pub struct Property { name: EcoString, /// The property's value. value: Value, - /// Whether the property should only affect the first node down the - /// hierarchy. Used by constructors. - scoped: bool, /// The span of the set rule the property stems from. - origin: Option, + span: Option, } impl Property { /// Create a new property from a key-value pair. pub fn new(node: NodeId, name: EcoString, value: Value) -> Self { - Self { node, name, value, scoped: false, origin: None } + Self { node, name, value, span: None } } /// Whether this property is the given one. @@ -187,28 +156,11 @@ impl Property { pub fn is_of(&self, node: NodeId) -> bool { self.node == node } - - /// Access the property's value as the given type. - #[track_caller] - pub fn cast(&self) -> T { - self.value.clone().cast().unwrap_or_else(|err| { - panic!( - "{} (for {}.{} with value {:?})", - err, - self.node.name(), - self.name, - self.value - ) - }) - } } impl Debug for Property { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "#set {}({}: {:?})", self.node.name(), self.name, self.value)?; - if self.scoped { - write!(f, " [scoped]")?; - } Ok(()) } } @@ -381,55 +333,111 @@ impl<'a> StyleChain<'a> { /// Make the given style the first link of the this chain. pub fn chain_one<'b>(&'b self, style: &'b Style) -> StyleChain<'b> { - if let Style::Barrier(id) = style { - if !self - .entries() - .filter_map(Style::property) - .any(|p| p.scoped && *id == p.node) - { - return *self; - } - } - StyleChain { head: std::slice::from_ref(style), tail: Some(self), } } + /// Cast the first value for the given property in the chain. + pub fn get( + self, + node: NodeId, + name: &'a str, + inherent: Option, + default: impl Fn() -> T, + ) -> T { + self.properties::(node, name, inherent) + .next() + .unwrap_or_else(default) + } + + /// Cast the first value for the given property in the chain. + pub fn get_resolve( + self, + node: NodeId, + name: &'a str, + inherent: Option, + default: impl Fn() -> T, + ) -> T::Output { + self.get(node, name, inherent, default).resolve(self) + } + + /// Cast the first value for the given property in the chain. + pub fn get_fold( + self, + node: NodeId, + name: &'a str, + inherent: Option, + default: impl Fn() -> T::Output, + ) -> T::Output { + fn next( + mut values: impl Iterator, + styles: StyleChain, + default: &impl Fn() -> T::Output, + ) -> T::Output { + values + .next() + .map(|value| value.fold(next(values, styles, default))) + .unwrap_or_else(|| default()) + } + next(self.properties::(node, name, inherent), self, &default) + } + + /// Cast the first value for the given property in the chain. + pub fn get_resolve_fold( + self, + node: NodeId, + name: &'a str, + inherent: Option, + default: impl Fn() -> ::Output, + ) -> ::Output + where + T: Cast + Resolve, + T::Output: Fold, + { + fn next( + mut values: impl Iterator, + styles: StyleChain, + default: &impl Fn() -> ::Output, + ) -> ::Output + where + T: Resolve, + T::Output: Fold, + { + values + .next() + .map(|value| value.resolve(styles).fold(next(values, styles, default))) + .unwrap_or_else(|| default()) + } + next(self.properties::(node, name, inherent), self, &default) + } + /// Iterate over all style recipes in the chain. pub fn recipes(self) -> impl Iterator { self.entries().filter_map(Style::recipe) } - /// Cast the first value for the given property in the chain. - pub fn property(self, node: NodeId, name: &'a str) -> Option { - self.properties(node, name).next() - } - /// Iterate over all values for the given property in the chain. - pub fn properties( + pub fn properties( self, node: NodeId, name: &'a str, + inherent: Option, ) -> impl Iterator + '_ { - let mut barriers = 0; - self.entries().filter_map(move |entry| { - match entry { - Style::Property(property) => { - if property.is(node, name) { - if !property.scoped || barriers <= 1 { - return Some(property.cast()); - } - } - } - Style::Barrier(id) => { - barriers += (*id == node) as usize; - } - _ => {} - } - None - }) + inherent + .into_iter() + .chain( + self.entries() + .filter_map(Style::property) + .filter(move |property| property.is(node, name)) + .map(|property| property.value.clone()), + ) + .map(move |value| { + value.cast().unwrap_or_else(|err| { + panic!("{} (for {}.{})", err, node.name(), name) + }) + }) } /// Iterate over the entries of the chain.