diff --git a/library/src/core/ext.rs b/library/src/core/ext.rs index 44479e9d4..f90260ad4 100644 --- a/library/src/core/ext.rs +++ b/library/src/core/ext.rs @@ -70,7 +70,7 @@ impl ContentExt for Content { } } -/// Additional methods for the style chain. +/// Additional methods for style maps. pub trait StyleMapExt { /// Set a font family composed of a preferred family and existing families /// from a style chain. @@ -106,10 +106,10 @@ impl LayoutBlock for FillNode { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult> { - let mut frames = self.child.layout_block(world, regions, styles)?; + let mut frames = self.child.layout_block(world, styles, regions)?; for frame in &mut frames { let shape = Geometry::Rect(frame.size()).filled(self.fill); frame.prepend(Point::zero(), Element::Shape(shape)); @@ -134,10 +134,10 @@ impl LayoutBlock for StrokeNode { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult> { - let mut frames = self.child.layout_block(world, regions, styles)?; + let mut frames = self.child.layout_block(world, styles, regions)?; for frame in &mut frames { let shape = Geometry::Rect(frame.size()).stroked(self.stroke); frame.prepend(Point::zero(), Element::Shape(shape)); diff --git a/library/src/graphics/hide.rs b/library/src/graphics/hide.rs index f79b31ae6..3a21c2c70 100644 --- a/library/src/graphics/hide.rs +++ b/library/src/graphics/hide.rs @@ -15,10 +15,10 @@ impl LayoutInline for HideNode { fn layout_inline( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult { - let mut frame = self.0.layout_inline(world, regions, styles)?; + let mut frame = self.0.layout_inline(world, styles, regions)?; frame.clear(); Ok(frame) } diff --git a/library/src/graphics/image.rs b/library/src/graphics/image.rs index 202abebe7..de3384df1 100644 --- a/library/src/graphics/image.rs +++ b/library/src/graphics/image.rs @@ -41,8 +41,8 @@ impl LayoutInline for ImageNode { fn layout_inline( &self, _: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult { let pxw = self.0.width() as f64; let pxh = self.0.height() as f64; diff --git a/library/src/graphics/line.rs b/library/src/graphics/line.rs index e7c347b2e..11f0be32c 100644 --- a/library/src/graphics/line.rs +++ b/library/src/graphics/line.rs @@ -40,8 +40,8 @@ impl LayoutInline for LineNode { fn layout_inline( &self, _: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult { let stroke = styles.get(Self::STROKE).unwrap_or_default(); diff --git a/library/src/graphics/shape.rs b/library/src/graphics/shape.rs index ebdc17170..4c9fec07d 100644 --- a/library/src/graphics/shape.rs +++ b/library/src/graphics/shape.rs @@ -76,8 +76,8 @@ impl LayoutInline for ShapeNode { fn layout_inline( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult { let mut frame; if let Some(child) = &self.0 { @@ -90,7 +90,7 @@ impl LayoutInline for ShapeNode { let child = child.clone().padded(inset.map(|side| side.map(Length::from))); let mut pod = Regions::one(regions.first, regions.base, regions.expand); - frame = child.layout_inline(world, &pod, styles)?; + frame = child.layout_inline(world, styles, &pod)?; // Relayout with full expansion into square region to make sure // the result is really a square or circle. @@ -106,7 +106,7 @@ impl LayoutInline for ShapeNode { pod.first = Size::splat(length); pod.expand = Axes::splat(true); - frame = child.layout_inline(world, &pod, styles)?; + frame = child.layout_inline(world, styles, &pod)?; } } else { // The default size that a shape takes on if it has no child and diff --git a/library/src/layout/align.rs b/library/src/layout/align.rs index 6337c9416..10a4a2ed3 100644 --- a/library/src/layout/align.rs +++ b/library/src/layout/align.rs @@ -30,21 +30,21 @@ impl LayoutBlock for AlignNode { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult> { // The child only needs to expand along an axis if there's no alignment. let mut pod = regions.clone(); pod.expand &= self.aligns.as_ref().map(Option::is_none); // Align paragraphs inside the child. - let mut passed = StyleMap::new(); + let mut map = StyleMap::new(); if let Some(align) = self.aligns.x { - passed.set(ParNode::ALIGN, HorizontalAlign(align)); + map.set(ParNode::ALIGN, HorizontalAlign(align)); } // Layout the child. - let mut frames = self.child.layout_block(world, &pod, passed.chain(&styles))?; + let mut frames = self.child.layout_block(world, styles.chain(&map), &pod)?; for (region, frame) in regions.iter().zip(&mut frames) { // Align in the target size. The target size depends on whether we // should expand. diff --git a/library/src/layout/columns.rs b/library/src/layout/columns.rs index 6e1aaeae3..b18ba49fe 100644 --- a/library/src/layout/columns.rs +++ b/library/src/layout/columns.rs @@ -30,13 +30,13 @@ impl LayoutBlock for ColumnsNode { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult> { // Separating the infinite space into infinite columns does not make // much sense. if !regions.first.x.is_finite() { - return self.child.layout_block(world, regions, styles); + return self.child.layout_block(world, styles, regions); } // Determine the width of the gutter and each column. @@ -58,7 +58,7 @@ impl LayoutBlock for ColumnsNode { }; // Layout the children. - let mut frames = self.child.layout_block(world, &pod, styles)?.into_iter(); + let mut frames = self.child.layout_block(world, styles, &pod)?.into_iter(); let mut finished = vec![]; let dir = styles.get(TextNode::DIR); diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index 9a6ccb9f3..b299c0fcc 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -24,8 +24,8 @@ impl LayoutInline for BoxNode { fn layout_inline( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult { // The "pod" is the region into which the child will be layouted. let pod = { @@ -47,7 +47,7 @@ impl LayoutInline for BoxNode { }; // Layout the child. - let mut frame = self.child.layout_inline(world, &pod, styles)?; + let mut frame = self.child.layout_inline(world, styles, &pod)?; // Ensure frame size matches regions size if expansion is on. let target = regions.expand.select(regions.first, frame.size()); @@ -91,9 +91,9 @@ impl LayoutBlock for BlockNode { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult> { - self.0.layout_block(world, regions, styles) + self.0.layout_block(world, styles, regions) } } diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index 4508023a7..3338da097 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -18,13 +18,13 @@ impl LayoutBlock for FlowNode { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult> { let mut layouter = FlowLayouter::new(regions); for (child, map) in self.0.iter() { - let styles = map.chain(&styles); + let styles = styles.chain(&map); if let Some(&node) = child.to::() { layouter.layout_spacing(node.amount, styles); } else if child.has::() { @@ -136,7 +136,7 @@ impl FlowLayouter { // aligned later. if let Some(placed) = block.to::() { if placed.out_of_flow() { - let frame = block.layout_block(world, &self.regions, styles)?.remove(0); + let frame = block.layout_block(world, styles, &self.regions)?.remove(0); self.items.push(FlowItem::Placed(frame)); return Ok(()); } @@ -162,11 +162,11 @@ impl FlowLayouter { if !self.last_block_was_par && is_par && !styles.get(ParNode::INDENT).is_zero() { let property = Property::new(ParNode::INDENT, Length::zero()); reset = Style::Property(property); - chained = reset.chain(&styles); + chained = styles.chain_one(&reset); } // Layout the block itself. - let frames = block.layout_block(world, &self.regions, chained)?; + let frames = block.layout_block(world, chained, &self.regions)?; let len = frames.len(); for (i, frame) in frames.into_iter().enumerate() { // Grow our size, shrink the region and save the frame for later. diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index 3b5afcc57..4cbef4214 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -37,8 +37,8 @@ impl LayoutBlock for GridNode { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult> { // Prepare grid layout by unifying content and gutter tracks. let layouter = GridLayouter::new( @@ -321,7 +321,7 @@ impl<'a> GridLayouter<'a> { } let frame = - cell.layout_block(self.world, &pod, self.styles)?.remove(0); + cell.layout_block(self.world, self.styles, &pod)?.remove(0); resolved.set_max(frame.width()); } } @@ -391,7 +391,7 @@ impl<'a> GridLayouter<'a> { } let mut sizes = cell - .layout_block(self.world, &pod, self.styles)? + .layout_block(self.world, self.styles, &pod)? .into_iter() .map(|frame| frame.height()); @@ -480,7 +480,7 @@ impl<'a> GridLayouter<'a> { .select(self.regions.base, size); let pod = Regions::one(size, base, Axes::splat(true)); - let frame = cell.layout_block(self.world, &pod, self.styles)?.remove(0); + let frame = cell.layout_block(self.world, self.styles, &pod)?.remove(0); output.push_frame(pos, frame); } @@ -520,7 +520,7 @@ impl<'a> GridLayouter<'a> { } // Push the layouted frames into the individual output frames. - let frames = cell.layout_block(self.world, &pod, self.styles)?; + let frames = cell.layout_block(self.world, self.styles, &pod)?; for (output, frame) in outputs.iter_mut().zip(frames) { output.push_frame(pos, frame); } diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index 983b96ba7..44b7b6d82 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -32,8 +32,8 @@ use typst::diag::SourceResult; use typst::frame::Frame; use typst::geom::*; use typst::model::{ - capability, Content, Node, SequenceNode, Style, StyleChain, StyleVecBuilder, - StyledNode, + applicable, capability, realize, Content, Node, SequenceNode, Style, StyleChain, + StyleVecBuilder, StyledNode, }; use typst::World; @@ -77,8 +77,8 @@ pub trait LayoutBlock { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult>; } @@ -87,17 +87,17 @@ impl LayoutBlock for Content { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult> { let scratch = Scratch::default(); let (realized, styles) = realize_block(world, &scratch, self, styles)?; let barrier = Style::Barrier(realized.id()); - let styles = barrier.chain(&styles); + let styles = styles.chain_one(&barrier); realized .with::() .unwrap() - .layout_block(world, regions, styles) + .layout_block(world, styles, regions) } } @@ -108,8 +108,8 @@ pub trait LayoutInline { fn layout_inline( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult; } @@ -118,22 +118,22 @@ impl LayoutInline for Content { fn layout_inline( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult { assert!(regions.backlog.is_empty()); assert!(regions.last.is_none()); - if self.has::() && !styles.applicable(self) { + if self.has::() && !applicable(self, styles) { let barrier = Style::Barrier(self.id()); - let styles = barrier.chain(&styles); + let styles = styles.chain_one(&barrier); return self .with::() .unwrap() - .layout_inline(world, regions, styles); + .layout_inline(world, styles, regions); } - Ok(self.layout_block(world, regions, styles)?.remove(0)) + Ok(self.layout_block(world, styles, regions)?.remove(0)) } } @@ -237,7 +237,7 @@ fn realize_root<'a>( content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<(Content, StyleChain<'a>)> { - if content.has::() && !styles.applicable(content) { + if content.has::() && !applicable(content, styles) { return Ok((content.clone(), styles)); } @@ -255,7 +255,7 @@ fn realize_block<'a>( content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<(Content, StyleChain<'a>)> { - if content.has::() && !styles.applicable(content) { + if content.has::() && !applicable(content, styles) { return Ok((content.clone(), styles)); } @@ -319,7 +319,7 @@ impl<'a> Builder<'a> { return Ok(()); } - if let Some(realized) = styles.show(self.world, content)? { + if let Some(realized) = realize(self.world, content, styles)? { let stored = self.scratch.content.alloc(realized); return self.accept(stored, styles); } @@ -366,7 +366,7 @@ impl<'a> Builder<'a> { styles: StyleChain<'a>, ) -> SourceResult<()> { let stored = self.scratch.styles.alloc(styles); - let styles = styled.map.chain(stored); + let styles = stored.chain(&styled.map); self.interrupt_style(&styled.map, None)?; self.accept(&styled.sub, styles)?; self.interrupt_style(&styled.map, Some(styles))?; diff --git a/library/src/layout/pad.rs b/library/src/layout/pad.rs index d860e54d6..4389d990f 100644 --- a/library/src/layout/pad.rs +++ b/library/src/layout/pad.rs @@ -29,13 +29,13 @@ impl LayoutBlock for PadNode { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult> { // Layout child into padded regions. let padding = self.padding.resolve(styles); let pod = regions.map(|size| shrink(size, padding)); - let mut frames = self.child.layout_block(world, &pod, styles)?; + let mut frames = self.child.layout_block(world, styles, &pod)?; for frame in &mut frames { // Apply the padding inversely such that the grown size padded diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index f32493476..9d5d4f561 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -97,7 +97,7 @@ impl PageNode { // Layout the child. let regions = Regions::repeat(size, size, size.map(Abs::is_finite)); - let mut frames = child.layout_block(world, ®ions, styles)?; + let mut frames = child.layout_block(world, styles, ®ions)?; let header = styles.get(Self::HEADER); let footer = styles.get(Self::FOOTER); @@ -118,7 +118,7 @@ impl PageNode { ] { if let Some(content) = marginal.resolve(world, page)? { let pod = Regions::one(area, area, Axes::splat(true)); - let sub = content.layout_block(world, &pod, styles)?.remove(0); + let sub = content.layout_block(world, styles, &pod)?.remove(0); if std::ptr::eq(marginal, background) { frame.prepend_frame(pos, sub); } else { @@ -402,9 +402,9 @@ papers! { // ---------------------------------------------------------------------- // // Other - (NEWSPAPER_COMPACT: 280.0, 430.0, "newspaper-compact") - (NEWSPAPER_BERLINER: 315.0, 470.0, "newspaper-berliner") - (NEWSPAPER_BROADSHEET: 381.0, 578.0, "newspaper-broadsheet") + (NEWSPAPER_COMPACT: 280.0, 430.0, "newspaper-compact") + (NEWSPAPER_BERLINER: 315.0, 470.0, "newspaper-berliner") + (NEWSPAPER_BROADSHEET: 381.0, 578.0, "newspaper-broadsheet") (PRESENTATION_16_9: 297.0, 167.0625, "presentation-16-9") - (PRESENTATION_4_3: 280.0, 210.0, "presentation-4-3") + (PRESENTATION_4_3: 280.0, 210.0, "presentation-4-3") } diff --git a/library/src/layout/place.rs b/library/src/layout/place.rs index 221e90082..af3130738 100644 --- a/library/src/layout/place.rs +++ b/library/src/layout/place.rs @@ -20,8 +20,8 @@ impl LayoutBlock for PlaceNode { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult> { let out_of_flow = self.out_of_flow(); @@ -33,7 +33,7 @@ impl LayoutBlock for PlaceNode { Regions::one(regions.base, regions.base, expand) }; - let mut frames = self.0.layout_block(world, &pod, styles)?; + let mut frames = self.0.layout_block(world, styles, &pod)?; // If expansion is off, zero all sizes so that we don't take up any // space in our parent. Otherwise, respect the expand settings. diff --git a/library/src/layout/stack.rs b/library/src/layout/stack.rs index 52de2c482..c935d9715 100644 --- a/library/src/layout/stack.rs +++ b/library/src/layout/stack.rs @@ -31,8 +31,8 @@ impl LayoutBlock for StackNode { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult> { let mut layouter = StackLayouter::new(self.dir, regions, styles); @@ -189,14 +189,14 @@ impl<'a> StackLayouter<'a> { if let Some(styled) = block.to::() { let map = &styled.map; if map.contains(ParNode::ALIGN) { - return StyleChain::with_root(map).get(ParNode::ALIGN); + return StyleChain::new(map).get(ParNode::ALIGN); } } self.dir.start().into() }); - let frames = block.layout_block(world, &self.regions, styles)?; + let frames = block.layout_block(world, styles, &self.regions)?; let len = frames.len(); for (i, frame) in frames.into_iter().enumerate() { // Grow our size, shrink the region and save the frame for later. diff --git a/library/src/layout/transform.rs b/library/src/layout/transform.rs index bee38cb44..f09b4e65d 100644 --- a/library/src/layout/transform.rs +++ b/library/src/layout/transform.rs @@ -28,10 +28,10 @@ impl LayoutInline for MoveNode { fn layout_inline( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult { - let mut frame = self.child.layout_inline(world, regions, styles)?; + let mut frame = self.child.layout_inline(world, styles, regions)?; let delta = self.delta.resolve(styles); let delta = delta.zip(frame.size()).map(|(d, s)| d.relative_to(s)); frame.translate(delta.to_point()); @@ -82,10 +82,10 @@ impl LayoutInline for TransformNode { fn layout_inline( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult { - let mut frame = self.child.layout_inline(world, regions, styles)?; + let mut frame = self.child.layout_inline(world, styles, regions)?; let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON); let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); diff --git a/library/src/lib.rs b/library/src/lib.rs index 7fcad1837..c2234446f 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -159,7 +159,7 @@ fn styles() -> StyleMap { /// Construct the standard lang item mapping. fn items() -> LangItems { LangItems { - layout: |content, world, styles| content.layout_root(world, styles), + layout: |world, content, styles| content.layout_root(world, styles), em: |styles| styles.get(text::TextNode::SIZE), dir: |styles| styles.get(text::TextNode::DIR), space: || text::SpaceNode.pack(), diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 1ea03d70a..3b1d66e9a 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -4,6 +4,8 @@ mod tex; use std::fmt::Write; +use typst::model::Guard; + use self::tex::{layout_tex, Texify}; use crate::prelude::*; use crate::text::FontFamily; @@ -28,21 +30,21 @@ impl MathNode { } impl Show for MathNode { - fn show(&self, _: Tracked, styles: StyleChain) -> SourceResult { + fn show(&self, _: Tracked, styles: StyleChain) -> Content { let mut map = StyleMap::new(); map.set_family(FontFamily::new("NewComputerModernMath"), styles); let mut realized = self .clone() .pack() - .guarded(RecipeId::Base(NodeId::of::())) + .guarded(Guard::Base(NodeId::of::())) .styled_with_map(map); if self.display { realized = realized.aligned(Axes::with_x(Some(Align::Center.into()))) } - Ok(realized) + realized } } @@ -50,10 +52,10 @@ impl LayoutInline for MathNode { fn layout_inline( &self, world: Tracked, - _: &Regions, styles: StyleChain, + _: &Regions, ) -> SourceResult { - layout_tex(&self.texify(), self.display, world, styles) + layout_tex(world, &self.texify(), self.display, styles) } } diff --git a/library/src/math/tex.rs b/library/src/math/tex.rs index ffde719be..a85bab189 100644 --- a/library/src/math/tex.rs +++ b/library/src/math/tex.rs @@ -35,9 +35,9 @@ impl Texify for Content { /// Layout a TeX formula into a frame. pub fn layout_tex( + world: Tracked, tex: &str, display: bool, - world: Tracked, styles: StyleChain, ) -> SourceResult { // Load the font. diff --git a/library/src/prelude.rs b/library/src/prelude.rs index 02d4ad10f..36ff05619 100644 --- a/library/src/prelude.rs +++ b/library/src/prelude.rs @@ -16,8 +16,8 @@ pub use typst::geom::*; #[doc(no_inline)] pub use typst::model::{ array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast, - Content, Dict, Finalize, Fold, Func, Node, NodeId, RecipeId, Resolve, Show, Smart, - Str, StyleChain, StyleMap, StyleVec, Unlabellable, Value, Vm, + Content, Dict, Finalize, Fold, Func, Node, NodeId, Resolve, Show, Smart, Str, + StyleChain, StyleMap, StyleVec, Unlabellable, Value, Vm, }; #[doc(no_inline)] pub use typst::syntax::{Span, Spanned}; diff --git a/library/src/structure/doc.rs b/library/src/structure/doc.rs index 42ad628e6..e471a8523 100644 --- a/library/src/structure/doc.rs +++ b/library/src/structure/doc.rs @@ -18,7 +18,7 @@ impl LayoutRoot for DocNode { let mut frames = vec![]; for (page, map) in self.0.iter() { let number = 1 + frames.len(); - frames.extend(page.layout(world, number, map.chain(&styles))?); + frames.extend(page.layout(world, number, styles.chain(map))?); } Ok(frames) } diff --git a/library/src/structure/heading.rs b/library/src/structure/heading.rs index cf732a4ea..063f3c977 100644 --- a/library/src/structure/heading.rs +++ b/library/src/structure/heading.rs @@ -34,18 +34,13 @@ impl HeadingNode { } impl Show for HeadingNode { - fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(BlockNode(self.body.clone()).pack()) + fn show(&self, _: Tracked, _: StyleChain) -> Content { + BlockNode(self.body.clone()).pack() } } impl Finalize for HeadingNode { - fn finalize( - &self, - _: Tracked, - _: StyleChain, - realized: Content, - ) -> SourceResult { + fn finalize(&self, realized: Content) -> Content { let scale = match self.level.get() { 1 => 1.4, 2 => 1.2, @@ -61,7 +56,6 @@ impl Finalize for HeadingNode { map.set(TextNode::WEIGHT, FontWeight::BOLD); map.set(BlockNode::ABOVE, VNode::block_around(above.into())); map.set(BlockNode::BELOW, VNode::block_around(below.into())); - - Ok(realized.styled_with_map(map)) + realized.styled_with_map(map) } } diff --git a/library/src/structure/list.rs b/library/src/structure/list.rs index 222dcbcff..c35ffd449 100644 --- a/library/src/structure/list.rs +++ b/library/src/structure/list.rs @@ -81,8 +81,8 @@ impl LayoutBlock for ListNode { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult> { let mut cells = vec![]; let mut number = 1; @@ -139,7 +139,7 @@ impl LayoutBlock for ListNode { gutter: Axes::with_y(vec![gutter.into()]), cells, } - .layout_block(world, regions, styles) + .layout_block(world, styles, regions) } } diff --git a/library/src/structure/reference.rs b/library/src/structure/reference.rs index 361f8c257..948aa6f68 100644 --- a/library/src/structure/reference.rs +++ b/library/src/structure/reference.rs @@ -20,7 +20,7 @@ impl RefNode { } impl Show for RefNode { - fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(TextNode::packed(format_eco!("@{}", self.0))) + fn show(&self, _: Tracked, _: StyleChain) -> Content { + TextNode::packed(format_eco!("@{}", self.0)) } } diff --git a/library/src/structure/table.rs b/library/src/structure/table.rs index 54413b637..4dd14cddd 100644 --- a/library/src/structure/table.rs +++ b/library/src/structure/table.rs @@ -54,8 +54,8 @@ impl LayoutBlock for TableNode { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult> { let fill = styles.get(Self::FILL); let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default); @@ -89,7 +89,7 @@ impl LayoutBlock for TableNode { gutter: self.gutter.clone(), cells, } - .layout_block(world, regions, styles) + .layout_block(world, styles, regions) } } diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index 3357f76c3..33c13e904 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -47,8 +47,8 @@ impl DecoNode { } impl Show for DecoNode { - fn show(&self, _: Tracked, styles: StyleChain) -> SourceResult { - Ok(self.0.clone().styled( + fn show(&self, _: Tracked, styles: StyleChain) -> Content { + self.0.clone().styled( TextNode::DECO, Decoration { line: L, @@ -57,7 +57,7 @@ impl Show for DecoNode { extent: styles.get(Self::EXTENT), evade: styles.get(Self::EVADE), }, - )) + ) } } diff --git a/library/src/text/link.rs b/library/src/text/link.rs index f682eea9e..1b87def2a 100644 --- a/library/src/text/link.rs +++ b/library/src/text/link.rs @@ -54,18 +54,13 @@ impl LinkNode { } impl Show for LinkNode { - fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(self.body.clone()) + fn show(&self, _: Tracked, _: StyleChain) -> Content { + self.body.clone() } } impl Finalize for LinkNode { - fn finalize( - &self, - _: Tracked, - _: StyleChain, - realized: Content, - ) -> SourceResult { - Ok(realized.styled(Self::DEST, Some(self.dest.clone()))) + fn finalize(&self, realized: Content) -> Content { + realized.styled(Self::DEST, Some(self.dest.clone())) } } diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index 813432108..3fd30e9e9 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -527,8 +527,8 @@ impl StrongNode { } impl Show for StrongNode { - fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(self.0.clone().styled(TextNode::BOLD, Toggle)) + fn show(&self, _: Tracked, _: StyleChain) -> Content { + self.0.clone().styled(TextNode::BOLD, Toggle) } } @@ -551,8 +551,8 @@ impl EmphNode { } impl Show for EmphNode { - fn show(&self, _: Tracked, _: StyleChain) -> SourceResult { - Ok(self.0.clone().styled(TextNode::ITALIC, Toggle)) + fn show(&self, _: Tracked, _: StyleChain) -> Content { + self.0.clone().styled(TextNode::ITALIC, Toggle) } } diff --git a/library/src/text/par.rs b/library/src/text/par.rs index 1dd6a42c6..4c22c0348 100644 --- a/library/src/text/par.rs +++ b/library/src/text/par.rs @@ -47,8 +47,8 @@ impl LayoutBlock for ParNode { fn layout_block( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult> { // Collect all text into one string for BiDi analysis. let (text, segments) = collect(self, &styles); @@ -59,10 +59,10 @@ impl LayoutBlock for ParNode { let p = prepare(world, self, &text, segments, regions, styles)?; // Break the paragraph into lines. - let lines = linebreak(&p, world, regions.first.x); + let lines = linebreak(&p, regions.first.x); // Stack the lines into one frame per region. - stack(&p, world, &lines, regions) + stack(&p, &lines, regions) } } @@ -141,10 +141,10 @@ impl LayoutInline for RepeatNode { fn layout_inline( &self, world: Tracked, - regions: &Regions, styles: StyleChain, + regions: &Regions, ) -> SourceResult { - self.0.layout_inline(world, regions, styles) + self.0.layout_inline(world, styles, regions) } } @@ -163,6 +163,8 @@ const NODE_REPLACE: char = '\u{FFFC}'; // Object Replacement Character /// Only when a line break falls onto a text index that is not safe-to-break per /// rustybuzz, we have to reshape that portion. struct Preparation<'a> { + /// The compilation environment. + world: Tracked<'a, dyn World>, /// Bidirectional text embedding levels for the paragraph. bidi: BidiInfo<'a>, /// Text runs, spacing and layouted nodes. @@ -416,7 +418,7 @@ fn collect<'a>( } while let Some((child, map)) = iter.next() { - let styles = map.chain(styles); + let styles = styles.chain(map); let segment = if child.is::() { full.push(' '); Segment::Text(1) @@ -487,7 +489,7 @@ fn collect<'a>( /// Prepare paragraph layout by shaping the whole paragraph and layouting all /// contained inline-level content. fn prepare<'a>( - world: Tracked, + world: Tracked<'a, dyn World>, par: &'a ParNode, text: &'a str, segments: Vec<(Segment<'a>, StyleChain<'a>)>, @@ -528,7 +530,7 @@ fn prepare<'a>( } else { let size = Size::new(regions.first.x, regions.base.y); let pod = Regions::one(size, regions.base, Axes::splat(false)); - let mut frame = inline.layout_inline(world, &pod, styles)?; + let mut frame = inline.layout_inline(world, styles, &pod)?; frame.translate(Point::with_y(styles.get(TextNode::BASELINE))); items.push(Item::Frame(frame)); } @@ -539,6 +541,7 @@ fn prepare<'a>( } Ok(Preparation { + world, bidi, items, styles, @@ -618,11 +621,7 @@ fn shared_get<'a, K: Key>( } /// Find suitable linebreaks. -fn linebreak<'a>( - p: &'a Preparation<'a>, - world: Tracked, - width: Abs, -) -> Vec> { +fn linebreak<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec> { let linebreaks = p.styles.get(ParNode::LINEBREAKS).unwrap_or_else(|| { if p.styles.get(ParNode::JUSTIFY) { Linebreaks::Optimized @@ -632,26 +631,22 @@ fn linebreak<'a>( }); match linebreaks { - Linebreaks::Simple => linebreak_simple(p, world, width), - Linebreaks::Optimized => linebreak_optimized(p, world, width), + Linebreaks::Simple => linebreak_simple(p, width), + Linebreaks::Optimized => linebreak_optimized(p, width), } } /// Perform line breaking in simple first-fit style. This means that we build /// lines greedily, always taking the longest possible line. This may lead to /// very unbalanced line, but is fast and simple. -fn linebreak_simple<'a>( - p: &'a Preparation<'a>, - world: Tracked, - width: Abs, -) -> Vec> { +fn linebreak_simple<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec> { let mut lines = vec![]; let mut start = 0; let mut last = None; for (end, mandatory, hyphen) in breakpoints(p) { // Compute the line and its size. - let mut attempt = line(p, world, start..end, mandatory, hyphen); + let mut attempt = line(p, start..end, mandatory, hyphen); // If the line doesn't fit anymore, we push the last fitting attempt // into the stack and rebuild the line from the attempt's end. The @@ -660,7 +655,7 @@ fn linebreak_simple<'a>( if let Some((last_attempt, last_end)) = last.take() { lines.push(last_attempt); start = last_end; - attempt = line(p, world, start..end, mandatory, hyphen); + attempt = line(p, start..end, mandatory, hyphen); } } @@ -700,11 +695,7 @@ fn linebreak_simple<'a>( /// computed and stored in dynamic programming table) is minimal. The final /// result is simply the layout determined for the last breakpoint at the end of /// text. -fn linebreak_optimized<'a>( - p: &'a Preparation<'a>, - world: Tracked, - width: Abs, -) -> Vec> { +fn linebreak_optimized<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec> { /// The cost of a line or paragraph layout. type Cost = f64; @@ -727,7 +718,7 @@ fn linebreak_optimized<'a>( let mut table = vec![Entry { pred: 0, total: 0.0, - line: line(p, world, 0..0, false, false), + line: line(p, 0..0, false, false), }]; let em = p.styles.get(TextNode::SIZE); @@ -741,7 +732,7 @@ fn linebreak_optimized<'a>( for (i, pred) in table.iter_mut().enumerate().skip(active) { // Layout the line. let start = pred.line.end; - let attempt = line(p, world, start..end, mandatory, hyphen); + let attempt = line(p, start..end, mandatory, hyphen); // Determine how much the line's spaces would need to be stretched // to make it the desired width. @@ -915,7 +906,6 @@ impl Breakpoints<'_> { /// Create a line which spans the given range. fn line<'a>( p: &'a Preparation, - world: Tracked, mut range: Range, mandatory: bool, hyphen: bool, @@ -970,9 +960,9 @@ fn line<'a>( if hyphen || start + shaped.text.len() > range.end { if hyphen || start < range.end || before.is_empty() { let shifted = start - base..range.end - base; - let mut reshaped = shaped.reshape(world, shifted); + let mut reshaped = shaped.reshape(p.world, shifted); if hyphen || shy { - reshaped.push_hyphen(world); + reshaped.push_hyphen(p.world); } width += reshaped.width; last = Some(Item::Text(reshaped)); @@ -993,7 +983,7 @@ fn line<'a>( if range.start + shaped.text.len() > end { if range.start < end { let shifted = range.start - base..end - base; - let reshaped = shaped.reshape(world, shifted); + let reshaped = shaped.reshape(p.world, shifted); width += reshaped.width; first = Some(Item::Text(reshaped)); } @@ -1021,12 +1011,7 @@ fn line<'a>( } /// Combine layouted lines into one frame per region. -fn stack( - p: &Preparation, - world: Tracked, - lines: &[Line], - regions: &Regions, -) -> SourceResult> { +fn stack(p: &Preparation, lines: &[Line], regions: &Regions) -> SourceResult> { // Determine the paragraph's width: Full width of the region if we // should expand or there's fractional spacing, fit-to-width otherwise. let mut width = regions.first.x; @@ -1042,7 +1027,7 @@ fn stack( // Stack the lines into one frame per region. for line in lines { - let frame = commit(p, world, line, ®ions, width)?; + let frame = commit(p, line, ®ions, width)?; let height = frame.size().y; while !regions.first.y.fits(height) && !regions.in_last() { @@ -1071,7 +1056,6 @@ fn stack( /// Commit to a line and build its frame. fn commit( p: &Preparation, - world: Tracked, line: &Line, regions: &Regions, width: Abs, @@ -1142,7 +1126,7 @@ fn commit( offset += v.share(fr, remaining); } Item::Text(shaped) => { - let frame = shaped.build(world, justification); + let frame = shaped.build(p.world, justification); push(&mut offset, frame); } Item::Frame(frame) => { @@ -1153,7 +1137,7 @@ fn commit( let fill = Fr::one().share(fr, remaining); let size = Size::new(fill, regions.base.y); let pod = Regions::one(size, regions.base, Axes::new(false, false)); - let frame = repeat.layout_inline(world, &pod, *styles)?; + let frame = repeat.layout_inline(p.world, *styles, &pod)?; let width = frame.width(); let count = (fill / width).floor(); let remaining = fill % width; diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index e47875dcc..6c7265139 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -43,7 +43,7 @@ impl RawNode { } impl Show for RawNode { - fn show(&self, _: Tracked, styles: StyleChain) -> SourceResult { + fn show(&self, _: Tracked, styles: StyleChain) -> Content { let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase()); let foreground = THEME .settings @@ -96,7 +96,7 @@ impl Show for RawNode { map.set(TextNode::SMART_QUOTES, false); map.set_family(FontFamily::new("IBM Plex Mono"), styles); - Ok(realized.styled_with_map(map)) + realized.styled_with_map(map) } } diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs index b05f68b7d..df8ec5e63 100644 --- a/library/src/text/shift.rs +++ b/library/src/text/shift.rs @@ -43,11 +43,7 @@ impl ShiftNode { } impl Show for ShiftNode { - fn show( - &self, - world: Tracked, - styles: StyleChain, - ) -> SourceResult { + fn show(&self, world: Tracked, styles: StyleChain) -> Content { let mut transformed = None; if styles.get(Self::TYPOGRAPHIC) { if let Some(text) = search_text(&self.0, S) { @@ -57,12 +53,12 @@ impl Show for ShiftNode { } }; - Ok(transformed.unwrap_or_else(|| { + transformed.unwrap_or_else(|| { let mut map = StyleMap::new(); map.set(TextNode::BASELINE, styles.get(Self::BASELINE)); map.set(TextNode::SIZE, styles.get(Self::SIZE)); self.0.clone().styled_with_map(map) - })) + }) } } diff --git a/macros/src/node.rs b/macros/src/node.rs index d08fc66b4..4add9bc38 100644 --- a/macros/src/node.rs +++ b/macros/src/node.rs @@ -8,10 +8,7 @@ use super::*; /// Expand the `#[node]` macro. pub fn expand(attr: TokenStream, body: syn::ItemImpl) -> Result { let node = prepare(attr, body)?; - let scope = create(&node)?; - Ok(quote! { - const _: () = { #scope }; - }) + create(&node) } /// Details about a node. @@ -236,6 +233,7 @@ fn create(node: &Node) -> Result { let mut modules: Vec = vec![]; let mut items: Vec = vec![]; + let scope = quote::format_ident!("__{}_keys", node.self_name); for property in &node.properties { let (key, module) = create_property_module(node, &property); @@ -246,7 +244,8 @@ fn create(node: &Node) -> Result { let vis = &property.vis; items.push(parse_quote! { #(#attrs)* - #vis const #name: #name::#key = #name::Key(::std::marker::PhantomData); + #vis const #name: #scope::#name::#key + = #scope::#name::Key(::std::marker::PhantomData); }); } @@ -255,8 +254,11 @@ fn create(node: &Node) -> Result { Ok(quote! { #body - #node_impl - #(#modules)* + mod #scope { + use super::*; + #node_impl + #(#modules)* + } }) } @@ -428,7 +430,7 @@ fn create_property_module(node: &Node, property: &Property) -> (syn::Type, syn:: // Generate the module code. let module = parse_quote! { #[allow(non_snake_case)] - mod #name { #scope } + pub mod #name { #scope } }; (key, module) diff --git a/src/diag.rs b/src/diag.rs index 99ef12914..58eebf8bc 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -100,7 +100,7 @@ pub enum Tracepoint { /// A function call. Call(Option), /// A show rule application. - Apply(EcoString), + Show(EcoString), /// A module import. Import, } @@ -114,7 +114,7 @@ impl Display for Tracepoint { Tracepoint::Call(None) => { write!(f, "error occured in this function call") } - Tracepoint::Apply(name) => { + Tracepoint::Show(name) => { write!(f, "error occured while applying show rule to this {name}") } Tracepoint::Import => { diff --git a/src/export/render.rs b/src/export/render.rs index bc82e9c84..7cff7ad83 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -14,7 +14,7 @@ use crate::geom::{ }; use crate::image::{DecodedImage, Image}; -/// Export a frame into a rendered image. +/// Export a frame into a raster image. /// /// This renders the frame at the given number of pixels per printer's point and /// returns the resulting `tiny-skia` pixel buffer. diff --git a/src/lib.rs b/src/lib.rs index 4607cd0c2..6e0a68a57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,21 +1,21 @@ -//! The compiler for the _Typst_ typesetting language. +//! The compiler for the _Typst_ markup language. //! //! # Steps -//! - **Parsing:** The parsing step first transforms a plain string into an +//! - **Parsing:** The compiler first transforms a plain string into an //! [iterator of tokens][tokens]. This token stream is [parsed] into a [syntax //! tree]. The tree itself is untyped, but the [AST] module provides a typed //! layer over it. //! - **Evaluation:** The next step is to [evaluate] the markup. This produces a //! [module], consisting of a scope of values that were exported by the code -//! and [content], a hierarchical, styled representation of the text, -//! structure, layouts, etc. of the module. The nodes of the content tree are -//! well structured and order-independent and thus much better suited for -//! layouting than the raw markup. -//! - **Layouting:** Next, the content is layouted into a portable version of -//! the typeset document. The output of this is a collection of [`Frame`]s -//! (one per page), ready for exporting. -//! - **Exporting:** The finished layout can be exported into a supported -//! format. Currently, the only supported output format is [PDF]. +//! and [content], a hierarchical, styled representation of what was written +//! in the source file. The nodes of the content tree are well structured and +//! order-independent and thus much better suited for further processing than +//! the raw markup. +//! - **Typesetting:** Next, the content is [typeset] into a collection of +//! [`Frame`]s (one per page) with elements and fixed positions, ready for +//! exporting. +//! - **Exporting:** These frames can finally be exported into an output format +//! (currently supported are [PDF] and [raster images]). //! //! [tokens]: syntax::Tokens //! [parsed]: syntax::parse @@ -24,7 +24,9 @@ //! [evaluate]: model::eval //! [module]: model::Module //! [content]: model::Content +//! [typeset]: model::typeset //! [PDF]: export::pdf +//! [raster images]: export::render extern crate self as typst; @@ -73,7 +75,7 @@ pub fn compile( /// The environment in which typesetting occurs. #[comemo::track] pub trait World { - /// The compilation root. + /// The path relative to which absolute paths are. fn root(&self) -> &Path; /// The standard library. diff --git a/src/model/content.rs b/src/model/content.rs index 90df5a4e0..0faf76cb7 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -9,9 +9,7 @@ use comemo::Tracked; use siphasher::sip128::{Hasher128, SipHasher}; use typst_macros::node; -use super::{ - Args, Key, Property, Recipe, RecipeId, Style, StyleMap, Unlabellable, Value, Vm, -}; +use super::{capability, Args, Guard, Key, Property, Recipe, Style, StyleMap, Value, Vm}; use crate::diag::{SourceResult, StrResult}; use crate::syntax::Span; use crate::util::{EcoString, ReadableTypeId}; @@ -21,7 +19,7 @@ use crate::World; #[derive(Clone, Hash)] pub struct Content { obj: Arc, - guards: Vec, + guards: Vec, span: Option, label: Option, } @@ -69,11 +67,11 @@ impl Content { /// Style this content with a style entry. pub fn styled_with_entry(mut self, style: Style) -> Self { if let Some(styled) = self.to_mut::() { - styled.map.apply(style); + styled.map.apply_one(style); self } else if let Some(styled) = self.to::() { let mut map = styled.map.clone(); - map.apply(style); + map.apply_one(style); StyledNode { sub: styled.sub.clone(), map }.pack() } else { StyledNode { sub: self, map: style.into() }.pack() @@ -87,7 +85,7 @@ impl Content { } if let Some(styled) = self.to_mut::() { - styled.map.apply_map(&styles); + styled.map.apply(styles); return self; } @@ -101,7 +99,7 @@ impl Content { recipe: Recipe, ) -> SourceResult { if recipe.selector.is_none() { - recipe.transform.apply(world, recipe.span, self) + recipe.apply(world, self) } else { Ok(self.styled_with_entry(Style::Recipe(recipe))) } @@ -185,7 +183,7 @@ impl Content { /// Disable a show rule recipe. #[doc(hidden)] - pub fn guarded(mut self, id: RecipeId) -> Self { + pub fn guarded(mut self, id: Guard) -> Self { self.guards.push(id); self } @@ -201,7 +199,7 @@ impl Content { } /// Check whether a show rule recipe is disabled. - pub(super) fn is_guarded(&self, id: RecipeId) -> bool { + pub(super) fn is_guarded(&self, id: Guard) -> bool { self.guards.contains(&id) } @@ -378,12 +376,12 @@ pub trait Node: 'static + Capable { fn field(&self, name: &str) -> Option; } -/// A unique identifier for a node. +/// A unique identifier for a node type. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct NodeId(ReadableTypeId); impl NodeId { - /// The id of the given node. + /// The id of the given node type. pub fn of() -> Self { Self(ReadableTypeId::of::()) } @@ -397,7 +395,8 @@ impl Debug for NodeId { /// A capability a node can have. /// -/// This is implemented by trait objects. +/// Should be implemented by trait objects that are accessible through +/// [`Capable`]. pub trait Capability: 'static {} /// Dynamically access a trait implementation at runtime. @@ -406,3 +405,7 @@ pub unsafe trait Capable { /// if `self` implements the trait. fn vtable(&self, of: TypeId) -> Option<*const ()>; } + +/// Indicates that a node cannot be labelled. +#[capability] +pub trait Unlabellable {} diff --git a/src/model/dict.rs b/src/model/dict.rs index 270c2c9cd..1c2997958 100644 --- a/src/model/dict.rs +++ b/src/model/dict.rs @@ -104,7 +104,7 @@ impl Dict { self.0.values().cloned().collect() } - /// Transform each pair in the array with a function. + /// Transform each pair in the dictionary with a function. pub fn map(&self, vm: &Vm, f: Spanned) -> SourceResult { self.iter() .map(|(key, value)| { diff --git a/src/model/eval.rs b/src/model/eval.rs index e68f6e7a4..6a93ca135 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -2,19 +2,22 @@ use std::collections::BTreeMap; use std::mem; +use std::path::PathBuf; use comemo::{Track, Tracked}; use unicode_segmentation::UnicodeSegmentation; use super::{ - methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func, - Recipe, Scope, Scopes, Selector, StyleMap, Transform, Value, Vm, + methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Func, + LangItems, Recipe, Scope, Scopes, Selector, StyleMap, Transform, Value, +}; +use crate::diag::{ + bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, }; -use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint}; use crate::geom::{Abs, Angle, Em, Fr, Ratio}; use crate::syntax::ast::AstNode; use crate::syntax::{ast, Source, SourceId, Span, Spanned, Unit}; -use crate::util::{format_eco, EcoString}; +use crate::util::{format_eco, EcoString, PathExt}; use crate::World; /// Evaluate a source file and return the resulting module. @@ -54,6 +57,94 @@ pub fn eval( Ok(Module { scope: vm.scopes.top, content: result? }) } +/// A virtual machine. +/// +/// Holds the state needed to [evaluate](super::eval()) Typst sources. A new +/// virtual machine is created for each module evaluation and function call. +pub struct Vm<'a> { + /// The compilation environment. + pub(super) world: Tracked<'a, dyn World>, + /// The language items. + pub(super) items: LangItems, + /// The route of source ids the VM took to reach its current location. + pub(super) route: Tracked<'a, Route>, + /// The current location. + pub(super) location: SourceId, + /// A control flow event that is currently happening. + pub(super) flow: Option, + /// The stack of scopes. + pub(super) scopes: Scopes<'a>, +} + +impl<'a> Vm<'a> { + /// Create a new virtual machine. + pub fn new( + world: Tracked<'a, dyn World>, + route: Tracked<'a, Route>, + location: SourceId, + scopes: Scopes<'a>, + ) -> Self { + Self { + world, + items: world.library().items.clone(), + route, + location, + flow: None, + scopes, + } + } + + /// Access the underlying world. + pub fn world(&self) -> Tracked { + self.world + } + + /// Resolve a user-entered path to be relative to the compilation + /// environment's root. + pub fn locate(&self, path: &str) -> StrResult { + if !self.location.is_detached() { + if let Some(path) = path.strip_prefix('/') { + return Ok(self.world.root().join(path).normalize()); + } + + if let Some(dir) = self.world.source(self.location).path().parent() { + return Ok(dir.join(path).normalize()); + } + } + + Err("cannot access file system from here".into()) + } +} + +/// A control flow event that occurred during evaluation. +#[derive(Debug, Clone, PartialEq)] +pub enum Flow { + /// Stop iteration in a loop. + Break(Span), + /// Skip the remainder of the current iteration in a loop. + Continue(Span), + /// Stop execution of a function early, optionally returning an explicit + /// value. + Return(Span, Option), +} + +impl Flow { + /// Return an error stating that this control flow is forbidden. + pub fn forbidden(&self) -> SourceError { + match *self { + Self::Break(span) => { + error!(span, "cannot break outside of loop") + } + Self::Continue(span) => { + error!(span, "cannot continue outside of loop") + } + Self::Return(span, _) => { + error!(span, "cannot return outside of function") + } + } + } +} + /// A route of source ids. #[derive(Default)] pub struct Route { @@ -87,7 +178,7 @@ impl Route { } } -/// An evaluated module, ready for importing or layouting. +/// An evaluated module, ready for importing or typesetting. #[derive(Debug, Clone)] pub struct Module { /// The top-level definitions that were bound in this module. @@ -700,18 +791,19 @@ impl Eval for ast::MethodCall { fn eval(&self, vm: &mut Vm) -> SourceResult { let span = self.span(); let method = self.method().take(); - let point = || Tracepoint::Call(Some(method.clone())); - Ok(if methods::is_mutating(&method) { + let result = if methods::is_mutating(&method) { let args = self.args().eval(vm)?; let value = self.target().access(vm)?; - methods::call_mut(value, &method, args, span).trace(vm.world, point, span)?; - Value::None + methods::call_mut(value, &method, args, span).map(|()| Value::None) } else { let value = self.target().eval(vm)?; let args = self.args().eval(vm)?; - methods::call(vm, value, &method, args, span).trace(vm.world, point, span)? - }) + methods::call(vm, value, &method, args, span) + }; + + let point = || Tracepoint::Call(Some(method.clone())); + result.trace(vm.world, point, span) } } diff --git a/src/model/func.rs b/src/model/func.rs index c0431ac64..4a2a162b0 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -52,7 +52,7 @@ impl Func { } /// Create a new function from a closure. - pub fn from_closure(closure: Closure) -> Self { + pub(super) fn from_closure(closure: Closure) -> Self { Self(Arc::new(Repr::Closure(closure))) } @@ -127,7 +127,8 @@ impl Func { } } - /// Create a selector from this node and the given arguments. + /// Create a selector for this function's node type, filtering by node's + /// whose [fields](super::Content::field) match the given arguments. pub fn where_(self, args: &mut Args) -> StrResult { match self.0.as_ref() { Repr::Native(Native { node: Some(id), .. }) => { @@ -178,7 +179,7 @@ impl Hash for Native { /// A user-defined closure. #[derive(Hash)] -pub struct Closure { +pub(super) struct Closure { /// The source file where the closure was defined. pub location: SourceId, /// The name of the closure. @@ -196,7 +197,7 @@ pub struct Closure { impl Closure { /// Call the function in the context with the arguments. - pub fn call(&self, vm: &Vm, args: &mut Args) -> SourceResult { + fn call(&self, vm: &Vm, args: &mut Args) -> SourceResult { // Don't leak the scopes from the call site. Instead, we use the scope // of captured variables we collected earlier. let mut scopes = Scopes::new(None); @@ -241,7 +242,7 @@ impl Closure { } /// The number of positional arguments this function takes, if known. - pub fn argc(&self) -> Option { + fn argc(&self) -> Option { if self.sink.is_some() { return None; } @@ -272,20 +273,6 @@ impl<'a> CapturesVisitor<'a> { self.captures } - /// Bind a new internal variable. - pub fn bind(&mut self, ident: ast::Ident) { - self.internal.top.define(ident.take(), Value::None); - } - - /// Capture a variable if it isn't internal. - pub fn capture(&mut self, ident: ast::Ident) { - if self.internal.get(&ident).is_err() { - if let Ok(value) = self.external.get(&ident) { - self.captures.define_captured(ident.take(), value.clone()); - } - } - } - /// Visit any node and collect all captured variables. pub fn visit(&mut self, node: &SyntaxNode) { match node.cast() { @@ -366,6 +353,20 @@ impl<'a> CapturesVisitor<'a> { } } } + + /// Bind a new internal variable. + fn bind(&mut self, ident: ast::Ident) { + self.internal.top.define(ident.take(), Value::None); + } + + /// Capture a variable if it isn't internal. + fn capture(&mut self, ident: ast::Ident) { + if self.internal.get(&ident).is_err() { + if let Ok(value) = self.external.get(&ident) { + self.captures.define_captured(ident.take(), value.clone()); + } + } + } } #[cfg(test)] diff --git a/src/model/library.rs b/src/model/library.rs index 8433e5146..2ee09b270 100644 --- a/src/model/library.rs +++ b/src/model/library.rs @@ -12,7 +12,7 @@ use crate::geom::{Abs, Dir}; use crate::util::{hash128, EcoString}; use crate::World; -/// A Typst standard library. +/// Definition of Typst's standard library. #[derive(Debug, Clone, Hash)] pub struct Library { /// The scope containing definitions that are available everywhere. @@ -23,13 +23,13 @@ pub struct Library { pub items: LangItems, } -/// Definition of certain standard library items the language is aware of. +/// Definition of library items the language is aware of. #[derive(Clone)] pub struct LangItems { /// The root layout function. pub layout: fn( - content: &Content, world: Tracked, + content: &Content, styles: StyleChain, ) -> SourceResult>, /// Access the em size. diff --git a/src/model/mod.rs b/src/model/mod.rs index f5c1e6dde..6943217b7 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -20,9 +20,9 @@ mod eval; mod func; mod methods; mod ops; +mod realize; mod scope; mod typeset; -mod vm; #[doc(hidden)] pub use once_cell; @@ -36,9 +36,9 @@ pub use self::dict::*; pub use self::eval::*; pub use self::func::*; pub use self::library::*; +pub use self::realize::*; pub use self::scope::*; pub use self::str::*; pub use self::styles::*; pub use self::typeset::*; pub use self::value::*; -pub use self::vm::*; diff --git a/src/model/realize.rs b/src/model/realize.rs new file mode 100644 index 000000000..d63c1aac1 --- /dev/null +++ b/src/model/realize.rs @@ -0,0 +1,145 @@ +use comemo::Tracked; + +use super::{capability, Content, NodeId, Recipe, Selector, StyleChain}; +use crate::diag::SourceResult; +use crate::World; + +/// Whether the target is affected by show rules in the given style chain. +pub fn applicable(target: &Content, styles: StyleChain) -> bool { + // Find out how many recipes there are. + let mut n = styles.recipes().count(); + + // Find out whether any recipe matches and is unguarded. + for recipe in styles.recipes() { + if recipe.applicable(target) && !target.is_guarded(Guard::Nth(n)) { + return true; + } + n -= 1; + } + + false +} + +/// Apply the show rules in the given style chain to a target. +pub fn realize( + world: Tracked, + target: &Content, + styles: StyleChain, +) -> SourceResult> { + // Find out how many recipes there are. + let mut n = styles.recipes().count(); + + // Find an applicable recipe. + let mut realized = None; + for recipe in styles.recipes() { + let guard = Guard::Nth(n); + if recipe.applicable(target) && !target.is_guarded(guard) { + if let Some(content) = try_apply(world, &target, recipe, guard)? { + realized = Some(content); + break; + } + } + n -= 1; + } + + // Realize if there was no matching recipe. + if let Some(showable) = target.with::() { + let guard = Guard::Base(target.id()); + if realized.is_none() && !target.is_guarded(guard) { + realized = Some(showable.show(world, styles)); + } + } + + // Finalize only if this is the first application for this node. + if let Some(node) = target.with::() { + if target.is_pristine() { + if let Some(already) = realized { + realized = Some(node.finalize(already)); + } + } + } + + Ok(realized) +} + +/// Try to apply a recipe to the target. +fn try_apply( + world: Tracked, + target: &Content, + recipe: &Recipe, + guard: Guard, +) -> SourceResult> { + match &recipe.selector { + Some(Selector::Node(id, _)) => { + if target.id() != *id { + return Ok(None); + } + + recipe.apply(world, target.clone().guarded(guard)).map(Some) + } + + Some(Selector::Regex(regex)) => { + let Some(text) = item!(text_str)(&target) else { + return Ok(None); + }; + + let make = |s| { + let mut content = item!(text)(s); + content.copy_meta(&target); + content + }; + + let mut result = vec![]; + let mut cursor = 0; + + for m in regex.find_iter(text) { + let start = m.start(); + if cursor < start { + result.push(make(text[cursor..start].into())); + } + + let piece = make(m.as_str().into()).guarded(guard); + let transformed = recipe.apply(world, piece)?; + result.push(transformed); + cursor = m.end(); + } + + if result.is_empty() { + return Ok(None); + } + + if cursor < text.len() { + result.push(make(text[cursor..].into())); + } + + Ok(Some(Content::sequence(result))) + } + + None => Ok(None), + } +} + +/// The base recipe for a node. +#[capability] +pub trait Show { + /// Execute the base recipe for this node. + fn show(&self, world: Tracked, styles: StyleChain) -> Content; +} + +/// Post-process a node after it was realized. +#[capability] +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; +} + +/// Guards content against being affected by the same show rule multiple times. +#[derive(Debug, Copy, Clone, PartialEq, Hash)] +pub enum Guard { + /// The nth recipe from the top of the chain. + Nth(usize), + /// The [base recipe](Show) for a kind of node. + Base(NodeId), +} diff --git a/src/model/str.rs b/src/model/str.rs index 454c561f6..1fcf7075d 100644 --- a/src/model/str.rs +++ b/src/model/str.rs @@ -77,69 +77,69 @@ impl Str { } /// Whether the given pattern exists in this string. - pub fn contains(&self, pattern: Pattern) -> bool { + pub fn contains(&self, pattern: StrPattern) -> bool { match pattern { - Pattern::Str(pat) => self.0.contains(pat.as_str()), - Pattern::Regex(re) => re.is_match(self), + StrPattern::Str(pat) => self.0.contains(pat.as_str()), + StrPattern::Regex(re) => re.is_match(self), } } /// Whether this string begins with the given pattern. - pub fn starts_with(&self, pattern: Pattern) -> bool { + pub fn starts_with(&self, pattern: StrPattern) -> bool { match pattern { - Pattern::Str(pat) => self.0.starts_with(pat.as_str()), - Pattern::Regex(re) => re.find(self).map_or(false, |m| m.start() == 0), + StrPattern::Str(pat) => self.0.starts_with(pat.as_str()), + StrPattern::Regex(re) => re.find(self).map_or(false, |m| m.start() == 0), } } /// Whether this string ends with the given pattern. - pub fn ends_with(&self, pattern: Pattern) -> bool { + pub fn ends_with(&self, pattern: StrPattern) -> bool { match pattern { - Pattern::Str(pat) => self.0.ends_with(pat.as_str()), - Pattern::Regex(re) => { + StrPattern::Str(pat) => self.0.ends_with(pat.as_str()), + StrPattern::Regex(re) => { re.find_iter(self).last().map_or(false, |m| m.end() == self.0.len()) } } } /// The text of the pattern's first match in this string. - pub fn find(&self, pattern: Pattern) -> Option { + pub fn find(&self, pattern: StrPattern) -> Option { match pattern { - Pattern::Str(pat) => self.0.contains(pat.as_str()).then(|| pat), - Pattern::Regex(re) => re.find(self).map(|m| m.as_str().into()), + StrPattern::Str(pat) => self.0.contains(pat.as_str()).then(|| pat), + StrPattern::Regex(re) => re.find(self).map(|m| m.as_str().into()), } } /// The position of the pattern's first match in this string. - pub fn position(&self, pattern: Pattern) -> Option { + pub fn position(&self, pattern: StrPattern) -> Option { match pattern { - Pattern::Str(pat) => self.0.find(pat.as_str()).map(|i| i as i64), - Pattern::Regex(re) => re.find(self).map(|m| m.start() as i64), + StrPattern::Str(pat) => self.0.find(pat.as_str()).map(|i| i as i64), + StrPattern::Regex(re) => re.find(self).map(|m| m.start() as i64), } } /// The start and, text and capture groups (if any) of the first match of /// the pattern in this string. - pub fn match_(&self, pattern: Pattern) -> Option { + pub fn match_(&self, pattern: StrPattern) -> Option { match pattern { - Pattern::Str(pat) => { + StrPattern::Str(pat) => { self.0.match_indices(pat.as_str()).next().map(match_to_dict) } - Pattern::Regex(re) => re.captures(self).map(captures_to_dict), + StrPattern::Regex(re) => re.captures(self).map(captures_to_dict), } } /// The start, end, text and capture groups (if any) of all matches of the /// pattern in this string. - pub fn matches(&self, pattern: Pattern) -> Array { + pub fn matches(&self, pattern: StrPattern) -> Array { match pattern { - Pattern::Str(pat) => self + StrPattern::Str(pat) => self .0 .match_indices(pat.as_str()) .map(match_to_dict) .map(Value::Dict) .collect(), - Pattern::Regex(re) => re + StrPattern::Regex(re) => re .captures_iter(self) .map(captures_to_dict) .map(Value::Dict) @@ -148,14 +148,14 @@ impl Str { } /// Split this string at whitespace or a specific pattern. - pub fn split(&self, pattern: Option) -> Array { + pub fn split(&self, pattern: Option) -> Array { let s = self.as_str(); match pattern { None => s.split_whitespace().map(|v| Value::Str(v.into())).collect(), - Some(Pattern::Str(pat)) => { + Some(StrPattern::Str(pat)) => { s.split(pat.as_str()).map(|v| Value::Str(v.into())).collect() } - Some(Pattern::Regex(re)) => { + Some(StrPattern::Regex(re)) => { re.split(s).map(|v| Value::Str(v.into())).collect() } } @@ -167,7 +167,7 @@ impl Str { /// pattern. pub fn trim( &self, - pattern: Option, + pattern: Option, at: Option, repeat: bool, ) -> Self { @@ -180,7 +180,7 @@ impl Str { Some(StrSide::Start) => self.0.trim_start(), Some(StrSide::End) => self.0.trim_end(), }, - Some(Pattern::Str(pat)) => { + Some(StrPattern::Str(pat)) => { let pat = pat.as_str(); let mut s = self.as_str(); if repeat { @@ -200,7 +200,7 @@ impl Str { } s } - Some(Pattern::Regex(re)) => { + Some(StrPattern::Regex(re)) => { let s = self.as_str(); let mut last = 0; let mut range = 0..s.len(); @@ -240,13 +240,13 @@ impl Str { /// Replace at most `count` occurances of the given pattern with a /// replacement string (beginning from the start). - pub fn replace(&self, pattern: Pattern, with: Self, count: Option) -> Self { + pub fn replace(&self, pattern: StrPattern, with: Self, count: Option) -> Self { match pattern { - Pattern::Str(pat) => match count { + StrPattern::Str(pat) => match count { Some(n) => self.0.replacen(pat.as_str(), &with, n).into(), None => self.0.replace(pat.as_str(), &with).into(), }, - Pattern::Regex(re) => match count { + StrPattern::Regex(re) => match count { Some(n) => re.replacen(self, n, with.as_str()).into(), None => re.replace(self, with.as_str()).into(), }, @@ -433,7 +433,7 @@ impl Hash for Regex { /// A pattern which can be searched for in a string. #[derive(Debug, Clone)] -pub enum Pattern { +pub enum StrPattern { /// Just a string. Str(Str), /// A regular expression. @@ -441,7 +441,7 @@ pub enum Pattern { } castable! { - Pattern, + StrPattern, Expected: "string or regular expression", Value::Str(text) => Self::Str(text), @regex: Regex => Self::Regex(regex.clone()), diff --git a/src/model/styles.rs b/src/model/styles.rs index 96eb0ab42..966a57ec0 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use comemo::{Prehashed, Tracked}; -use super::{capability, Args, Content, Dict, Func, NodeId, Regex, Smart, Value}; +use super::{Args, Content, Dict, Func, NodeId, Regex, Smart, Value}; use crate::diag::{SourceResult, Trace, Tracepoint}; use crate::geom::{ Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides, @@ -55,34 +55,15 @@ impl StyleMap { .any(|property| property.is::()) } - /// Make `self` the first link of the `tail` chain. - /// - /// The resulting style chain contains styles from `self` as well as - /// `tail`. The ones from `self` take precedence over the ones from - /// `tail`. For folded properties `self` contributes the inner value. - pub fn chain<'a>(&'a self, tail: &'a StyleChain<'a>) -> StyleChain<'a> { - if self.is_empty() { - *tail - } else { - StyleChain { head: &self.0, tail: Some(tail) } - } + /// Apply outer styles. Like [`chain`](StyleChain::chain), but in-place. + pub fn apply(&mut self, outer: Self) { + self.0.splice(0..0, outer.0.iter().cloned()); } - /// Set an outer style. - /// - /// Like [`chain`](Self::chain) or [`apply_map`](Self::apply_map), but with - /// only a entry. - pub fn apply(&mut self, style: Style) { - self.0.insert(0, style); - } - - /// Apply styles from `tail` in-place. The resulting style map is equivalent - /// to the style chain created by `self.chain(StyleChain::new(tail))`. - /// - /// This is useful over `chain` when you want to combine two maps, but you - /// still need an owned map without a lifetime. - pub fn apply_map(&mut self, tail: &Self) { - self.0.splice(0..0, tail.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 @@ -131,21 +112,6 @@ pub enum Style { } impl Style { - /// Make this style the first link of the `tail` chain. - pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> { - if let Style::Barrier(id) = self { - if !tail - .entries() - .filter_map(Style::property) - .any(|p| p.scoped() && *id == p.node()) - { - return *tail; - } - } - - StyleChain { head: std::slice::from_ref(self), tail: Some(tail) } - } - /// If this is a property, return it. pub fn property(&self) -> Option<&Property> { match self { @@ -184,32 +150,6 @@ impl Debug for Style { } } -/// A style property key. -/// -/// This trait is not intended to be implemented manually, but rather through -/// the `#[node]` proc-macro. -pub trait Key: Copy + 'static { - /// The unfolded type which this property is stored as in a style map. - type Value: Debug + Clone + Hash + Sync + Send + 'static; - - /// The folded type of value that is returned when reading this property - /// from a style chain. - type Output<'a>; - - /// The name of the property, used for debug printing. - const NAME: &'static str; - - /// The id of the node the key belongs to. - fn node() -> NodeId; - - /// Compute an output value from a sequence of values belonging to this key, - /// folding if necessary. - fn get<'a>( - chain: StyleChain<'a>, - values: impl Iterator, - ) -> Self::Output<'a>; -} - /// A style property originating from a set rule or constructor. #[derive(Clone, Hash)] pub struct Property { @@ -308,6 +248,32 @@ where } } +/// A style property key. +/// +/// This trait is not intended to be implemented manually, but rather through +/// the `#[node]` proc-macro. +pub trait Key: Copy + 'static { + /// The unfolded type which this property is stored as in a style map. + type Value: Debug + Clone + Hash + Sync + Send + 'static; + + /// The folded type of value that is returned when reading this property + /// from a style chain. + type Output<'a>; + + /// The name of the property, used for debug printing. + const NAME: &'static str; + + /// The id of the node the key belongs to. + fn node() -> NodeId; + + /// Compute an output value from a sequence of values belonging to this key, + /// folding if necessary. + fn get<'a>( + chain: StyleChain<'a>, + values: impl Iterator, + ) -> Self::Output<'a>; +} + /// A unique identifier for a property key. #[derive(Copy, Clone, Eq, PartialEq, Hash)] struct KeyId(ReadableTypeId); @@ -325,35 +291,6 @@ impl Debug for KeyId { } } -/// A built-in show rule for a node. -#[capability] -pub trait Show { - /// Execute the base recipe for this node. - fn show( - &self, - world: Tracked, - styles: StyleChain, - ) -> SourceResult; -} - -/// Post-process a node after it was realized. -#[capability] -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, - world: Tracked, - styles: StyleChain, - realized: Content, - ) -> SourceResult; -} - -/// Indicates that a node cannot be labelled. -#[capability] -pub trait Unlabellable {} - /// A show rule recipe. #[derive(Clone, PartialEq, Hash)] pub struct Recipe { @@ -366,6 +303,14 @@ pub struct Recipe { } impl Recipe { + /// Whether this recipe is for the given node. + pub fn is_of(&self, node: NodeId) -> bool { + match self.selector { + Some(Selector::Node(id, _)) => id == node, + _ => false, + } + } + /// Whether the recipe is applicable to the target. pub fn applicable(&self, target: &Content) -> bool { self.selector @@ -373,74 +318,24 @@ impl Recipe { .map_or(false, |selector| selector.matches(target)) } - /// Try to apply the recipe to the target. + /// Apply the recipe to the given content. pub fn apply( &self, world: Tracked, - sel: RecipeId, - target: &Content, - ) -> SourceResult> { - let content = match &self.selector { - Some(Selector::Node(id, _)) => { - if target.id() != *id { - return Ok(None); + content: Content, + ) -> SourceResult { + match &self.transform { + Transform::Content(content) => Ok(content.clone()), + Transform::Func(func) => { + let args = Args::new(self.span, [Value::Content(content.clone())]); + let mut result = func.call_detached(world, args); + if let Some(span) = content.span() { + let point = || Tracepoint::Show(content.name().into()); + result = result.trace(world, point, span); } - - self.transform.apply(world, self.span, target.clone().guarded(sel))? + Ok(result?.display()) } - - Some(Selector::Regex(regex)) => { - let Some(text) = item!(text_str)(target) else { - return Ok(None); - }; - - let make = |s| { - let mut content = item!(text)(s); - content.copy_meta(target); - content - }; - - let mut result = vec![]; - let mut cursor = 0; - - for m in regex.find_iter(text) { - let start = m.start(); - if cursor < start { - result.push(make(text[cursor..start].into())); - } - - let transformed = self.transform.apply( - world, - self.span, - make(m.as_str().into()).guarded(sel), - )?; - - result.push(transformed); - cursor = m.end(); - } - - if result.is_empty() { - return Ok(None); - } - - if cursor < text.len() { - result.push(make(text[cursor..].into())); - } - - Content::sequence(result) - } - - None => return Ok(None), - }; - - Ok(Some(content)) - } - - /// Whether this recipe is for the given node. - pub fn is_of(&self, node: NodeId) -> bool { - match self.selector { - Some(Selector::Node(id, _)) => id == node, - _ => false, + Transform::Style(styles) => Ok(content.styled_with_map(styles.clone())), } } } @@ -495,39 +390,6 @@ pub enum Transform { Style(StyleMap), } -impl Transform { - /// Apply the transform. - pub fn apply( - &self, - world: Tracked, - rule_span: Span, - content: Content, - ) -> SourceResult { - match self { - Transform::Content(content) => Ok(content.clone()), - Transform::Func(func) => { - let args = Args::new(rule_span, [Value::Content(content.clone())]); - let mut result = func.call_detached(world, args); - if let Some(span) = content.span() { - let point = || Tracepoint::Apply(content.name().into()); - result = result.trace(world, point, span); - } - Ok(result?.display()) - } - Transform::Style(styles) => Ok(content.styled_with_map(styles.clone())), - } - } -} - -/// Identifies a show rule recipe. -#[derive(Debug, Copy, Clone, PartialEq, Hash)] -pub enum RecipeId { - /// The nth recipe from the top of the chain. - Nth(usize), - /// The base recipe for a kind of node. - Base(NodeId), -} - /// A chain of style maps, similar to a linked list. /// /// A style chain allows to combine properties from multiple style maps in a @@ -544,14 +406,40 @@ pub struct StyleChain<'a> { } impl<'a> StyleChain<'a> { - /// Create a new, empty style chain. - pub fn new() -> Self { - Self::default() + /// Start a new style chain with a root map. + pub fn new(root: &'a StyleMap) -> Self { + Self { head: &root.0, tail: None } } - /// Start a new style chain with a root map. - pub fn with_root(root: &'a StyleMap) -> Self { - Self { head: &root.0, tail: None } + /// Make the given map the first link of this chain. + /// + /// The resulting style chain contains styles from `map` as well as + /// `self`. The ones from `map` take precedence over the ones from + /// `self`. For folded properties `map` contributes the inner value. + pub fn chain<'b>(&'b self, map: &'b StyleMap) -> StyleChain<'b> { + if map.is_empty() { + *self + } else { + StyleChain { head: &map.0, tail: Some(self) } + } + } + + /// 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), + } } /// Get the output value of a style property. @@ -563,78 +451,9 @@ impl<'a> StyleChain<'a> { K::get(self, self.values(key)) } - /// Apply show recipes in this style chain to a target. - pub fn show( - self, - world: Tracked, - target: &Content, - ) -> SourceResult> { - // Find out how many recipes there are. - let mut n = self.entries().filter_map(Style::recipe).count(); - - // Find an applicable recipe. - let mut realized = None; - for recipe in self.entries().filter_map(Style::recipe) { - let sel = RecipeId::Nth(n); - if recipe.applicable(target) && !target.is_guarded(sel) { - if let Some(content) = recipe.apply(world, sel, target)? { - realized = Some(content); - break; - } - } - n -= 1; - } - - // Realize if there was no matching recipe. - let base = RecipeId::Base(target.id()); - if realized.is_none() && !target.is_guarded(base) { - if let Some(showable) = target.with::() { - realized = Some(showable.show(world, self)?); - } - } - - // Finalize only if this is the first application for this node. - if let Some(node) = target.with::() { - if target.is_pristine() { - if let Some(content) = realized { - realized = Some(node.finalize(world, self, content)?); - } - } - } - - Ok(realized) - } - - /// Whether the style chain has a matching recipe for the content. - pub fn applicable(self, target: &Content) -> bool { - // Find out how many recipes there are. - let mut n = self.entries().filter_map(Style::recipe).count(); - - // Find out whether any recipe matches and is unguarded. - for recipe in self.entries().filter_map(Style::recipe) { - if recipe.applicable(target) && !target.is_guarded(RecipeId::Nth(n)) { - return true; - } - n -= 1; - } - - false - } - - /// Remove the last link from the chain. - fn pop(&mut self) { - *self = self.tail.copied().unwrap_or_default(); - } - - /// Build a style map from the suffix (all links beyond the `len`) of the - /// chain. - fn suffix(self, len: usize) -> StyleMap { - let mut suffix = StyleMap::new(); - let take = self.links().count().saturating_sub(len); - for link in self.links().take(take) { - suffix.0.splice(0..0, link.iter().cloned()); - } - suffix + /// Iterate over all style recipes in the chain. + pub fn recipes(self) -> impl Iterator { + self.entries().filter_map(Style::recipe) } /// Iterate over all values for the given property in the chain. @@ -655,6 +474,22 @@ impl<'a> StyleChain<'a> { fn links(self) -> Links<'a> { Links(Some(self)) } + + /// Build a style map from the suffix (all links beyond the `len`) of the + /// chain. + fn suffix(self, len: usize) -> StyleMap { + let mut suffix = StyleMap::new(); + let take = self.links().count().saturating_sub(len); + for link in self.links().take(take) { + suffix.0.splice(0..0, link.iter().cloned()); + } + suffix + } + + /// Remove the last link from the chain. + fn pop(&mut self) { + *self = self.tail.copied().unwrap_or_default(); + } } impl Debug for StyleChain<'_> { @@ -675,37 +510,6 @@ impl PartialEq for StyleChain<'_> { } } -/// An iterator over the values in a style chain. -struct Values<'a, K> { - entries: Entries<'a>, - key: PhantomData, - barriers: usize, -} - -impl<'a, K: Key> Iterator for Values<'a, K> { - type Item = &'a K::Value; - - fn next(&mut self) -> Option { - for entry in &mut self.entries { - match entry { - Style::Property(property) => { - if let Some(value) = property.downcast::() { - if !property.scoped() || self.barriers <= 1 { - return Some(value); - } - } - } - Style::Barrier(id) => { - self.barriers += (*id == K::node()) as usize; - } - _ => {} - } - } - - None - } -} - /// An iterator over the entries in a style chain. struct Entries<'a> { inner: std::slice::Iter<'a, Style>, @@ -742,6 +546,37 @@ impl<'a> Iterator for Links<'a> { } } +/// An iterator over the values in a style chain. +struct Values<'a, K> { + entries: Entries<'a>, + key: PhantomData, + barriers: usize, +} + +impl<'a, K: Key> Iterator for Values<'a, K> { + type Item = &'a K::Value; + + fn next(&mut self) -> Option { + for entry in &mut self.entries { + match entry { + Style::Property(property) => { + if let Some(value) = property.downcast::() { + if !property.scoped() || self.barriers <= 1 { + return Some(value); + } + } + } + Style::Barrier(id) => { + self.barriers += (*id == K::node()) as usize; + } + _ => {} + } + } + + None + } +} + /// A sequence of items with associated styles. #[derive(Clone, Hash)] pub struct StyleVec { diff --git a/src/model/typeset.rs b/src/model/typeset.rs index 2026bf995..ad2af3b20 100644 --- a/src/model/typeset.rs +++ b/src/model/typeset.rs @@ -10,8 +10,9 @@ use crate::World; /// Returns either a vector of frames representing individual pages or /// diagnostics in the form of a vector of error message with file and span /// information. +#[comemo::memoize] pub fn typeset(world: Tracked, content: &Content) -> SourceResult> { let library = world.library(); - let styles = StyleChain::with_root(&library.styles); - (library.items.layout)(content, world, styles) + let styles = StyleChain::new(&library.styles); + (library.items.layout)(world, content, styles) } diff --git a/src/model/vm.rs b/src/model/vm.rs deleted file mode 100644 index d3509eae3..000000000 --- a/src/model/vm.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::path::PathBuf; - -use comemo::Tracked; - -use super::{LangItems, Route, Scopes, Value}; -use crate::diag::{error, SourceError, StrResult}; -use crate::syntax::{SourceId, Span}; -use crate::util::PathExt; -use crate::World; - -/// A virtual machine. -/// -/// Holds the state needed to evaluate Typst sources. A new virtual machine is -/// created for each module evaluation and function call. -pub struct Vm<'a> { - /// The core context. - pub(crate) world: Tracked<'a, dyn World>, - /// The route of source ids the VM took to reach its current location. - pub(crate) route: Tracked<'a, Route>, - /// The current location. - pub(crate) location: SourceId, - /// The stack of scopes. - pub(crate) scopes: Scopes<'a>, - /// A control flow event that is currently happening. - pub(crate) flow: Option, - /// The language items. - pub(crate) items: LangItems, -} - -impl<'a> Vm<'a> { - /// Create a new virtual machine. - pub fn new( - world: Tracked<'a, dyn World>, - route: Tracked<'a, Route>, - location: SourceId, - scopes: Scopes<'a>, - ) -> Self { - Self { - world, - route, - location, - scopes, - flow: None, - items: world.library().items.clone(), - } - } - - /// Access the underlying world. - pub fn world(&self) -> Tracked { - self.world - } - - /// Resolve a user-entered path to be relative to the compilation - /// environment's root. - pub fn locate(&self, path: &str) -> StrResult { - if !self.location.is_detached() { - if let Some(path) = path.strip_prefix('/') { - return Ok(self.world.root().join(path).normalize()); - } - - if let Some(dir) = self.world.source(self.location).path().parent() { - return Ok(dir.join(path).normalize()); - } - } - - Err("cannot access file system from here".into()) - } -} - -/// A control flow event that occurred during evaluation. -#[derive(Debug, Clone, PartialEq)] -pub enum Flow { - /// Stop iteration in a loop. - Break(Span), - /// Skip the remainder of the current iteration in a loop. - Continue(Span), - /// Stop execution of a function early, optionally returning an explicit - /// value. - Return(Span, Option), -} - -impl Flow { - /// Return an error stating that this control flow is forbidden. - pub fn forbidden(&self) -> SourceError { - match *self { - Self::Break(span) => { - error!(span, "cannot break outside of loop") - } - Self::Continue(span) => { - error!(span, "cannot continue outside of loop") - } - Self::Return(span, _) => { - error!(span, "cannot return outside of function") - } - } - } -}