diff --git a/Cargo.lock b/Cargo.lock index a043bea2e..0bd6322a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,8 +159,7 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "comemo" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf2ceb1049619fdad21077121c28bffa1552627a1a5f0a0ee97a83e53fe45fd0" +source = "git+https://github.com/typst/comemo#36fb31c76eb42d67244bd9c7a2630c29767912f2" dependencies = [ "comemo-macros", "siphasher", @@ -169,8 +168,7 @@ dependencies = [ [[package]] name = "comemo-macros" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4cdbf89b08c431b3ae1ed7d571f293f620311e529b4c2595a39161312d964e" +source = "git+https://github.com/typst/comemo#36fb31c76eb42d67244bd9c7a2630c29767912f2" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index d0b7a0269..5a7a15e85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ bench = false typst-macros = { path = "macros" } bitflags = "1" bytemuck = "1" -comemo = "0.1" +comemo = { git = "https://github.com/typst/comemo" } flate2 = "1" image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] } miniz_oxide = "0.5" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 8dd45bd6c..a3b91fac1 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -17,7 +17,7 @@ typst = { path = ".." } typst-library = { path = "../library" } chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } codespan-reporting = "0.11" -comemo = "0.1" +comemo = { git = "https://github.com/typst/comemo" } dirs = "4" elsa = "1.7" memmap2 = "0.5" diff --git a/library/Cargo.toml b/library/Cargo.toml index 75cb8efc7..e0a0c1f61 100644 --- a/library/Cargo.toml +++ b/library/Cargo.toml @@ -11,7 +11,7 @@ bench = false [dependencies] typst = { path = ".." } -comemo = "0.1" +comemo = { git = "https://github.com/typst/comemo" } csv = "1" hypher = "0.1" kurbo = "0.8" diff --git a/library/src/basics/heading.rs b/library/src/basics/heading.rs index b251f27b2..d1ea9da68 100644 --- a/library/src/basics/heading.rs +++ b/library/src/basics/heading.rs @@ -34,7 +34,7 @@ impl HeadingNode { } impl Show for HeadingNode { - fn show(&self, _: Tracked, _: StyleChain) -> Content { + fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> Content { BlockNode(self.body.clone()).pack() } } diff --git a/library/src/basics/list.rs b/library/src/basics/list.rs index c73ffea63..e35fe9fa8 100644 --- a/library/src/basics/list.rs +++ b/library/src/basics/list.rs @@ -78,7 +78,7 @@ impl ListNode { impl Layout for ListNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { @@ -104,7 +104,7 @@ impl Layout for ListNode { cells.push(Content::empty()); let label = if L == LIST || L == ENUM { - label.resolve(world, L, number)?.styled_with_map(map.clone()) + label.resolve(vt, L, number)?.styled_with_map(map.clone()) } else { Content::empty() }; @@ -137,7 +137,7 @@ impl Layout for ListNode { gutter: Axes::with_y(vec![gutter.into()]), cells, } - .layout(world, styles, regions) + .layout(vt, styles, regions) } } @@ -232,7 +232,7 @@ impl Label { /// Resolve the label based on the level. pub fn resolve( &self, - world: Tracked, + vt: &Vt, kind: ListKind, number: usize, ) -> SourceResult { @@ -246,7 +246,7 @@ impl Label { Self::Content(content) => content.clone(), Self::Func(func, span) => { let args = Args::new(*span, [Value::Int(number as i64)]); - func.call_detached(world, args)?.display() + func.call_detached(vt.world(), args)?.display() } }) } diff --git a/library/src/basics/table.rs b/library/src/basics/table.rs index bb900f3dd..38ee57a01 100644 --- a/library/src/basics/table.rs +++ b/library/src/basics/table.rs @@ -53,7 +53,7 @@ impl TableNode { impl Layout for TableNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { @@ -76,7 +76,7 @@ impl Layout for TableNode { let x = i % cols; let y = i / cols; - if let Some(fill) = fill.resolve(world, x, y)? { + if let Some(fill) = fill.resolve(vt, x, y)? { child = child.filled(fill); } @@ -89,7 +89,7 @@ impl Layout for TableNode { gutter: self.gutter.clone(), cells, } - .layout(world, styles, regions) + .layout(vt, styles, regions) } } @@ -104,17 +104,12 @@ pub enum Celled { impl Celled { /// Resolve the value based on the cell position. - pub fn resolve( - &self, - world: Tracked, - x: usize, - y: usize, - ) -> SourceResult { + pub fn resolve(&self, vt: &Vt, x: usize, y: usize) -> SourceResult { Ok(match self { Self::Value(value) => value.clone(), Self::Func(func, span) => { let args = Args::new(*span, [Value::Int(x as i64), Value::Int(y as i64)]); - func.call_detached(world, args)?.cast().at(*span)? + func.call_detached(vt.world(), args)?.cast().at(*span)? } }) } diff --git a/library/src/compute/foundations.rs b/library/src/compute/foundations.rs index 3e4101850..5134d4ac9 100644 --- a/library/src/compute/foundations.rs +++ b/library/src/compute/foundations.rs @@ -1,7 +1,6 @@ use crate::prelude::*; use comemo::Track; - use typst::model; use typst::syntax::Source; diff --git a/library/src/layout/align.rs b/library/src/layout/align.rs index a06f7edb4..42bf86e17 100644 --- a/library/src/layout/align.rs +++ b/library/src/layout/align.rs @@ -29,7 +29,7 @@ impl AlignNode { impl Layout for AlignNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { @@ -44,7 +44,7 @@ impl Layout for AlignNode { } // Layout the child. - let mut fragment = self.child.layout(world, styles.chain(&map), &pod)?; + let mut fragment = self.child.layout(vt, styles.chain(&map), &pod)?; for (region, frame) in regions.iter().zip(&mut fragment) { // 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 257cc62f6..a5d5aff52 100644 --- a/library/src/layout/columns.rs +++ b/library/src/layout/columns.rs @@ -29,14 +29,14 @@ impl ColumnsNode { impl Layout for ColumnsNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, 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(world, styles, regions); + return self.child.layout(vt, styles, regions); } // Determine the width of the gutter and each column. @@ -58,7 +58,7 @@ impl Layout for ColumnsNode { }; // Layout the children. - let mut frames = self.child.layout(world, styles, &pod)?.into_iter(); + let mut frames = self.child.layout(vt, 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 a77e02498..5790aae69 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -23,7 +23,7 @@ impl BoxNode { impl Layout for BoxNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { @@ -47,7 +47,7 @@ impl Layout for BoxNode { }; // Layout the child. - let mut frame = self.child.layout(world, styles, &pod)?.into_frame(); + let mut frame = self.child.layout(vt, styles, &pod)?.into_frame(); // Ensure frame size matches regions size if expansion is on. let target = regions.expand.select(regions.first, frame.size()); @@ -95,10 +95,10 @@ impl BlockNode { impl Layout for BlockNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { - self.0.layout(world, styles, regions) + self.0.layout(vt, styles, regions) } } diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index b644d73f6..2994d9c60 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -20,7 +20,7 @@ impl FlowNode { impl Layout for FlowNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { @@ -33,9 +33,9 @@ impl Layout for FlowNode { } else if let Some(node) = child.to::() { let barrier = Style::Barrier(child.id()); let styles = styles.chain_one(&barrier); - layouter.layout_par(world, node, styles)?; + layouter.layout_par(vt, node, styles)?; } else if child.has::() { - layouter.layout_block(world, child, styles)?; + layouter.layout_block(vt, child, styles)?; } else if child.is::() { layouter.finish_region(false); } else { @@ -122,16 +122,23 @@ impl FlowLayouter { /// Layout a paragraph. fn layout_par( &mut self, - world: Tracked, + vt: &mut Vt, par: &ParNode, styles: StyleChain, ) -> SourceResult<()> { let aligns = Axes::new(styles.get(ParNode::ALIGN), Align::Top); let leading = styles.get(ParNode::LEADING); let consecutive = self.last_was_par; - let fragment = par.layout(world, styles, &self.regions, consecutive)?; - let len = fragment.len(); + let fragment = par.layout( + vt, + styles, + consecutive, + self.regions.first.x, + self.regions.base, + self.regions.expand.x, + )?; + let len = fragment.len(); for (i, frame) in fragment.into_iter().enumerate() { if i > 0 { self.layout_item(FlowItem::Leading(leading)); @@ -151,7 +158,7 @@ impl FlowLayouter { /// Layout a block. fn layout_block( &mut self, - world: Tracked, + vt: &mut Vt, block: &Content, styles: StyleChain, ) -> SourceResult<()> { @@ -159,7 +166,7 @@ impl FlowLayouter { // aligned later. if let Some(placed) = block.to::() { if placed.out_of_flow() { - let frame = block.layout(world, styles, &self.regions)?.into_frame(); + let frame = block.layout(vt, styles, &self.regions)?.into_frame(); self.layout_item(FlowItem::Placed(frame)); return Ok(()); } @@ -180,7 +187,7 @@ impl FlowLayouter { // Layout the block itself. let sticky = styles.get(BlockNode::STICKY); - let fragment = block.layout(world, styles, &self.regions)?; + let fragment = block.layout(vt, styles, &self.regions)?; for frame in fragment { self.layout_item(FlowItem::Frame(frame, aligns, sticky)); } diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index a848a650d..ab1894237 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -36,13 +36,13 @@ impl GridNode { impl Layout for GridNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { // Prepare grid layout by unifying content and gutter tracks. let layouter = GridLayouter::new( - world, + vt, self.tracks.as_deref(), self.gutter.as_deref(), &self.cells, @@ -110,9 +110,9 @@ castable! { } /// Performs grid layout. -struct GridLayouter<'a> { +struct GridLayouter<'a, 'v> { /// The core context. - world: Tracked<'a, dyn World>, + vt: &'a mut Vt<'v>, /// The grid cells. cells: &'a [Content], /// The column tracks including gutter tracks. @@ -147,12 +147,12 @@ enum Row { Fr(Fr, usize), } -impl<'a> GridLayouter<'a> { +impl<'a, 'v> GridLayouter<'a, 'v> { /// Create a new grid layouter. /// /// This prepares grid layout by unifying content and gutter tracks. fn new( - world: Tracked<'a, dyn World>, + vt: &'a mut Vt<'v>, tracks: Axes<&[TrackSizing]>, gutter: Axes<&[TrackSizing]>, cells: &'a [Content], @@ -206,7 +206,7 @@ impl<'a> GridLayouter<'a> { regions.expand = Axes::new(true, false); Self { - world, + vt, cells, cols, rows, @@ -318,7 +318,7 @@ impl<'a> GridLayouter<'a> { v.resolve(self.styles).relative_to(self.regions.base.y); } - let frame = cell.layout(self.world, self.styles, &pod)?.into_frame(); + let frame = cell.layout(self.vt, self.styles, &pod)?.into_frame(); resolved.set_max(frame.width()); } } @@ -395,7 +395,7 @@ impl<'a> GridLayouter<'a> { } let mut sizes = cell - .layout(self.world, self.styles, &pod)? + .layout(self.vt, self.styles, &pod)? .into_iter() .map(|frame| frame.height()); @@ -483,7 +483,7 @@ impl<'a> GridLayouter<'a> { .select(self.regions.base, size); let pod = Regions::one(size, base, Axes::splat(true)); - let frame = cell.layout(self.world, self.styles, &pod)?.into_frame(); + let frame = cell.layout(self.vt, self.styles, &pod)?.into_frame(); output.push_frame(pos, frame); } @@ -519,7 +519,7 @@ impl<'a> GridLayouter<'a> { } // Push the layouted frames into the individual output frames. - let fragment = cell.layout(self.world, self.styles, &pod)?; + let fragment = cell.layout(self.vt, self.styles, &pod)?; for (output, frame) in outputs.iter_mut().zip(fragment) { output.push_frame(pos, frame); } diff --git a/library/src/layout/hide.rs b/library/src/layout/hide.rs index 64cbee64d..136dae2f9 100644 --- a/library/src/layout/hide.rs +++ b/library/src/layout/hide.rs @@ -14,11 +14,11 @@ impl HideNode { impl Layout for HideNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { - let mut fragment = self.0.layout(world, styles, regions)?; + let mut fragment = self.0.layout(vt, styles, regions)?; for frame in &mut fragment { frame.clear(); } diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index 8f9337ba4..e3691732e 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -32,7 +32,6 @@ pub use self::transform::*; use std::mem; -use comemo::Tracked; use typed_arena::Arena; use typst::diag::SourceResult; use typst::geom::*; @@ -40,7 +39,6 @@ use typst::model::{ applicable, capability, realize, Content, Node, SequenceNode, Style, StyleChain, StyleVecBuilder, StyledNode, }; -use typst::World; use crate::basics::{DescNode, EnumNode, ListItem, ListNode, DESC, ENUM, LIST}; use crate::meta::DocumentNode; @@ -52,23 +50,27 @@ use crate::text::{LinebreakNode, SmartQuoteNode, SpaceNode, TextNode}; #[capability] pub trait LayoutRoot { /// Layout into one frame per page. - fn layout_root( - &self, - world: Tracked, - styles: StyleChain, - ) -> SourceResult; + fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult; } impl LayoutRoot for Content { - #[comemo::memoize] - fn layout_root( - &self, - world: Tracked, - styles: StyleChain, - ) -> SourceResult { - let scratch = Scratch::default(); - let (realized, styles) = realize_root(world, &scratch, self, styles)?; - realized.with::().unwrap().layout_root(world, styles) + fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { + #[comemo::memoize] + fn cached( + node: &Content, + world: comemo::Tracked, + styles: StyleChain, + ) -> SourceResult { + let mut vt = Vt { world }; + let scratch = Scratch::default(); + let (realized, styles) = realize_root(&mut vt, &scratch, node, styles)?; + realized + .with::() + .unwrap() + .layout_root(&mut vt, styles) + } + + cached(self, vt.world, styles) } } @@ -78,25 +80,38 @@ pub trait Layout { /// Layout into one frame per region. fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult; } impl Layout for Content { - #[comemo::memoize] fn layout( &self, - world: Tracked, + vt: &mut Vt, 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 = styles.chain_one(&barrier); - realized.with::().unwrap().layout(world, styles, regions) + #[comemo::memoize] + fn cached( + node: &Content, + world: comemo::Tracked, + styles: StyleChain, + regions: &Regions, + ) -> SourceResult { + let mut vt = Vt { world }; + 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() + .layout(&mut vt, styles, regions) + } + + cached(self, vt.world, styles, regions) } } @@ -199,7 +214,7 @@ impl Regions { /// Realize into a node that is capable of root-level layout. fn realize_root<'a>( - world: Tracked<'a, dyn World>, + vt: &mut Vt, scratch: &'a Scratch<'a>, content: &'a Content, styles: StyleChain<'a>, @@ -208,7 +223,7 @@ fn realize_root<'a>( return Ok((content.clone(), styles)); } - let mut builder = Builder::new(world, &scratch, true); + let mut builder = Builder::new(vt, &scratch, true); builder.accept(content, styles)?; builder.interrupt_page(Some(styles))?; let (pages, shared) = builder.doc.unwrap().pages.finish(); @@ -217,7 +232,7 @@ fn realize_root<'a>( /// Realize into a node that is capable of block-level layout. fn realize_block<'a>( - world: Tracked<'a, dyn World>, + vt: &mut Vt, scratch: &'a Scratch<'a>, content: &'a Content, styles: StyleChain<'a>, @@ -226,7 +241,7 @@ fn realize_block<'a>( return Ok((content.clone(), styles)); } - let mut builder = Builder::new(world, &scratch, false); + let mut builder = Builder::new(vt, &scratch, false); builder.accept(content, styles)?; builder.interrupt_par()?; let (children, shared) = builder.flow.0.finish(); @@ -234,9 +249,9 @@ fn realize_block<'a>( } /// Builds a document or a flow node from content. -struct Builder<'a> { - /// The core context. - world: Tracked<'a, dyn World>, +struct Builder<'a, 'v, 't> { + /// The virtual typesetter. + vt: &'v mut Vt<'t>, /// Scratch arenas for building. scratch: &'a Scratch<'a>, /// The current document building state. @@ -258,10 +273,10 @@ struct Scratch<'a> { content: Arena, } -impl<'a> Builder<'a> { - fn new(world: Tracked<'a, dyn World>, scratch: &'a Scratch<'a>, top: bool) -> Self { +impl<'a, 'v, 't> Builder<'a, 'v, 't> { + fn new(vt: &'v mut Vt<'t>, scratch: &'a Scratch<'a>, top: bool) -> Self { Self { - world, + vt, scratch, doc: top.then(|| DocBuilder::default()), flow: FlowBuilder::default(), @@ -286,7 +301,7 @@ impl<'a> Builder<'a> { return Ok(()); } - if let Some(realized) = realize(self.world, content, styles)? { + if let Some(realized) = realize(self.vt, content, styles)? { let stored = self.scratch.content.alloc(realized); return self.accept(stored, styles); } diff --git a/library/src/layout/pad.rs b/library/src/layout/pad.rs index c688dd478..5b36c8c35 100644 --- a/library/src/layout/pad.rs +++ b/library/src/layout/pad.rs @@ -28,14 +28,14 @@ impl PadNode { impl Layout for PadNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, 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 fragment = self.child.layout(world, styles, &pod)?; + let mut fragment = self.child.layout(vt, styles, &pod)?; for frame in &mut fragment { // 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 9fe608ee9..08411ad1c 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -57,7 +57,7 @@ impl PageNode { /// Layout the page run into a sequence of frames, one per page. pub fn layout( &self, - world: Tracked, + vt: &mut Vt, mut page: usize, styles: StyleChain, ) -> SourceResult { @@ -97,7 +97,7 @@ impl PageNode { // Layout the child. let regions = Regions::repeat(size, size, size.map(Abs::is_finite)); - let mut fragment = child.layout(world, styles, ®ions)?; + let mut fragment = child.layout(vt, styles, ®ions)?; let header = styles.get(Self::HEADER); let footer = styles.get(Self::FOOTER); @@ -116,9 +116,9 @@ impl PageNode { (foreground, Point::zero(), size), (background, Point::zero(), size), ] { - if let Some(content) = marginal.resolve(world, page)? { + if let Some(content) = marginal.resolve(vt, page)? { let pod = Regions::one(area, area, Axes::splat(true)); - let sub = content.layout(world, styles, &pod)?.into_frame(); + let sub = content.layout(vt, styles, &pod)?.into_frame(); if std::ptr::eq(marginal, background) { frame.prepend_frame(pos, sub); } else { @@ -169,17 +169,13 @@ pub enum Marginal { impl Marginal { /// Resolve the marginal based on the page number. - pub fn resolve( - &self, - world: Tracked, - page: usize, - ) -> SourceResult> { + pub fn resolve(&self, vt: &Vt, page: usize) -> SourceResult> { Ok(match self { Self::None => None, Self::Content(content) => Some(content.clone()), Self::Func(func, span) => { let args = Args::new(*span, [Value::Int(page as i64)]); - Some(func.call_detached(world, args)?.display()) + Some(func.call_detached(vt.world(), args)?.display()) } }) } diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs index 82bea1b5a..e96845dd0 100644 --- a/library/src/layout/par.rs +++ b/library/src/layout/par.rs @@ -44,27 +44,43 @@ impl ParNode { impl ParNode { /// Layout the paragraph into a collection of lines. - #[comemo::memoize] pub fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, - regions: &Regions, consecutive: bool, + width: Abs, + base: Size, + expand: bool, ) -> SourceResult { - // Collect all text into one string for BiDi analysis. - let (text, segments) = collect(self, &styles, consecutive); + #[comemo::memoize] + fn cached( + par: &ParNode, + world: Tracked, + styles: StyleChain, + consecutive: bool, + width: Abs, + base: Size, + expand: bool, + ) -> SourceResult { + let mut vt = Vt { world }; - // Perform BiDi analysis and then prepare paragraph layout by building a - // representation on which we can do line breaking without layouting - // each and every line from scratch. - let p = prepare(world, self, &text, segments, regions, styles)?; + // Collect all text into one string for BiDi analysis. + let (text, segments) = collect(par, &styles, consecutive); - // Break the paragraph into lines. - let lines = linebreak(&p, regions.first.x); + // Perform BiDi analysis and then prepare paragraph layout by building a + // representation on which we can do line breaking without layouting + // each and every line from scratch. + let p = prepare(&mut vt, par, &text, segments, styles, width, base)?; - // Stack the lines into one frame per region. - finalize(&p, &lines, regions) + // Break the paragraph into lines. + let lines = linebreak(&vt, &p, width); + + // Stack the lines into one frame per region. + finalize(&mut vt, &p, &lines, width, base, expand) + } + + cached(self, vt.world, styles, consecutive, width, base, expand) } } @@ -143,8 +159,6 @@ 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. @@ -470,12 +484,13 @@ fn collect<'a>( /// Prepare paragraph layout by shaping the whole paragraph and layouting all /// contained inline-level content. fn prepare<'a>( - world: Tracked<'a, dyn World>, + vt: &mut Vt, par: &'a ParNode, text: &'a str, segments: Vec<(Segment<'a>, StyleChain<'a>)>, - regions: &Regions, styles: StyleChain<'a>, + width: Abs, + base: Size, ) -> SourceResult> { let bidi = BidiInfo::new( text, @@ -494,11 +509,11 @@ fn prepare<'a>( let end = cursor + segment.len(); match segment { Segment::Text(_) => { - shape_range(&mut items, world, &bidi, cursor..end, styles); + shape_range(&mut items, vt, &bidi, cursor..end, styles); } Segment::Spacing(spacing) => match spacing { Spacing::Relative(v) => { - let resolved = v.resolve(styles).relative_to(regions.base.x); + let resolved = v.resolve(styles).relative_to(base.x); items.push(Item::Absolute(resolved)); } Spacing::Fractional(v) => { @@ -509,9 +524,9 @@ fn prepare<'a>( if let Some(repeat) = inline.to::() { items.push(Item::Repeat(repeat, styles)); } 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(world, styles, &pod)?.into_frame(); + let size = Size::new(width, base.y); + let pod = Regions::one(size, base, Axes::splat(false)); + let mut frame = inline.layout(vt, styles, &pod)?.into_frame(); frame.translate(Point::with_y(styles.get(TextNode::BASELINE))); items.push(Item::Frame(frame)); } @@ -522,7 +537,6 @@ fn prepare<'a>( } Ok(Preparation { - world, bidi, items, styles, @@ -537,14 +551,14 @@ fn prepare<'a>( /// items for them. fn shape_range<'a>( items: &mut Vec>, - world: Tracked, + vt: &Vt, bidi: &BidiInfo<'a>, range: Range, styles: StyleChain<'a>, ) { let mut process = |text, level: BidiLevel| { let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL }; - let shaped = shape(world, text, styles, dir); + let shaped = shape(vt, text, styles, dir); items.push(Item::Text(shaped)); }; @@ -601,7 +615,7 @@ fn shared_get<'a, K: Key>( } /// Find suitable linebreaks. -fn linebreak<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec> { +fn linebreak<'a>(vt: &Vt, 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 @@ -611,22 +625,22 @@ fn linebreak<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec> { }); match linebreaks { - Linebreaks::Simple => linebreak_simple(p, width), - Linebreaks::Optimized => linebreak_optimized(p, width), + Linebreaks::Simple => linebreak_simple(vt, p, width), + Linebreaks::Optimized => linebreak_optimized(vt, 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>, width: Abs) -> Vec> { +fn linebreak_simple<'a>(vt: &Vt, 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, start..end, mandatory, hyphen); + let mut attempt = line(vt, 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 @@ -635,7 +649,7 @@ fn linebreak_simple<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec> { if let Some((last_attempt, last_end)) = last.take() { lines.push(last_attempt); start = last_end; - attempt = line(p, start..end, mandatory, hyphen); + attempt = line(vt, p, start..end, mandatory, hyphen); } } @@ -675,7 +689,7 @@ fn linebreak_simple<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec> { /// 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>, width: Abs) -> Vec> { +fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec> { /// The cost of a line or paragraph layout. type Cost = f64; @@ -698,7 +712,7 @@ fn linebreak_optimized<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec> let mut table = vec![Entry { pred: 0, total: 0.0, - line: line(p, 0..0, false, false), + line: line(vt, p, 0..0, false, false), }]; let em = p.styles.get(TextNode::SIZE); @@ -712,7 +726,7 @@ fn linebreak_optimized<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec> for (i, pred) in table.iter_mut().enumerate().skip(active) { // Layout the line. let start = pred.line.end; - let attempt = line(p, start..end, mandatory, hyphen); + let attempt = line(vt, p, start..end, mandatory, hyphen); // Determine how much the line's spaces would need to be stretched // to make it the desired width. @@ -787,7 +801,7 @@ fn linebreak_optimized<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec> /// Returns for each breakpoint the text index, whether the break is mandatory /// (after `\n`) and whether a hyphen is required (when breaking inside of a /// word). -fn breakpoints<'a>(p: &'a Preparation) -> Breakpoints<'a> { +fn breakpoints<'a>(p: &'a Preparation<'a>) -> Breakpoints<'a> { Breakpoints { p, linebreaks: LineBreakIterator::new(p.bidi.text), @@ -885,6 +899,7 @@ impl Breakpoints<'_> { /// Create a line which spans the given range. fn line<'a>( + vt: &Vt, p: &'a Preparation, mut range: Range, mandatory: bool, @@ -940,9 +955,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(p.world, shifted); + let mut reshaped = shaped.reshape(vt, shifted); if hyphen || shy { - reshaped.push_hyphen(p.world); + reshaped.push_hyphen(vt); } width += reshaped.width; last = Some(Item::Text(reshaped)); @@ -963,7 +978,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(p.world, shifted); + let reshaped = shaped.reshape(vt, shifted); width += reshaped.width; first = Some(Item::Text(reshaped)); } @@ -992,27 +1007,35 @@ fn line<'a>( /// Combine layouted lines into one frame per region. fn finalize( + vt: &mut Vt, p: &Preparation, lines: &[Line], - regions: &Regions, + mut width: Abs, + base: Size, + expand: bool, ) -> 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; - if !regions.expand.x && lines.iter().all(|line| line.fr().is_zero()) { + if !expand && lines.iter().all(|line| line.fr().is_zero()) { width = lines.iter().map(|line| line.width).max().unwrap_or_default(); } // Stack the lines into one frame per region. lines .iter() - .map(|line| commit(p, line, regions.base, width)) + .map(|line| commit(vt, p, line, base, width)) .collect::>() .map(Fragment::frames) } /// Commit to a line and build its frame. -fn commit(p: &Preparation, line: &Line, base: Size, width: Abs) -> SourceResult { +fn commit( + vt: &mut Vt, + p: &Preparation, + line: &Line, + base: Size, + width: Abs, +) -> SourceResult { let mut remaining = width - line.width; let mut offset = Abs::zero(); @@ -1079,7 +1102,7 @@ fn commit(p: &Preparation, line: &Line, base: Size, width: Abs) -> SourceResult< offset += v.share(fr, remaining); } Item::Text(shaped) => { - let frame = shaped.build(p.world, justification); + let frame = shaped.build(vt, justification); push(&mut offset, frame); } Item::Frame(frame) => { @@ -1090,7 +1113,7 @@ fn commit(p: &Preparation, line: &Line, base: Size, width: Abs) -> SourceResult< let fill = Fr::one().share(fr, remaining); let size = Size::new(fill, base.y); let pod = Regions::one(size, base, Axes::new(false, false)); - let frame = repeat.layout(p.world, *styles, &pod)?.into_frame(); + let frame = repeat.layout(vt, *styles, &pod)?.into_frame(); let width = frame.width(); let count = (fill / width).floor(); let remaining = fill % width; diff --git a/library/src/layout/place.rs b/library/src/layout/place.rs index 215b5b9f2..f95aff6a2 100644 --- a/library/src/layout/place.rs +++ b/library/src/layout/place.rs @@ -19,7 +19,7 @@ impl PlaceNode { impl Layout for PlaceNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { @@ -33,7 +33,7 @@ impl Layout for PlaceNode { Regions::one(regions.base, regions.base, expand) }; - let mut frame = self.0.layout(world, styles, &pod)?.into_frame(); + let mut frame = self.0.layout(vt, styles, &pod)?.into_frame(); // 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/repeat.rs b/library/src/layout/repeat.rs index d9323e1df..a0fceee90 100644 --- a/library/src/layout/repeat.rs +++ b/library/src/layout/repeat.rs @@ -14,11 +14,11 @@ impl RepeatNode { impl Layout for RepeatNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { - self.0.layout(world, styles, regions) + self.0.layout(vt, styles, regions) } } diff --git a/library/src/layout/stack.rs b/library/src/layout/stack.rs index c1073b261..5d0b072ff 100644 --- a/library/src/layout/stack.rs +++ b/library/src/layout/stack.rs @@ -29,7 +29,7 @@ impl StackNode { impl Layout for StackNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { @@ -49,7 +49,7 @@ impl Layout for StackNode { layouter.layout_spacing(kind); } - layouter.layout_block(world, block, styles)?; + layouter.layout_block(vt, block, styles)?; deferred = self.spacing; } } @@ -170,7 +170,7 @@ impl<'a> StackLayouter<'a> { /// Layout an arbitrary block. fn layout_block( &mut self, - world: Tracked, + vt: &mut Vt, block: &Content, styles: StyleChain, ) -> SourceResult<()> { @@ -195,7 +195,7 @@ impl<'a> StackLayouter<'a> { self.dir.start().into() }); - let fragment = block.layout(world, styles, &self.regions)?; + let fragment = block.layout(vt, styles, &self.regions)?; let len = fragment.len(); for (i, frame) in fragment.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 cfc4ac83e..16ee33165 100644 --- a/library/src/layout/transform.rs +++ b/library/src/layout/transform.rs @@ -27,11 +27,11 @@ impl MoveNode { impl Layout for MoveNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { - let mut fragment = self.child.layout(world, styles, regions)?; + let mut fragment = self.child.layout(vt, styles, regions)?; for frame in &mut fragment { let delta = self.delta.resolve(styles); let delta = delta.zip(frame.size()).map(|(d, s)| d.relative_to(s)); @@ -85,11 +85,11 @@ impl TransformNode { impl Layout for TransformNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { - let mut fragment = self.child.layout(world, styles, regions)?; + let mut fragment = self.child.layout(vt, styles, regions)?; for frame in &mut fragment { 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/math/mod.rs b/library/src/math/mod.rs index 7136c8b94..9d25d4853 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -30,7 +30,7 @@ impl MathNode { } impl Show for MathNode { - fn show(&self, _: Tracked, styles: StyleChain) -> Content { + fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> Content { let mut map = StyleMap::new(); map.set_family(FontFamily::new("NewComputerModernMath"), styles); @@ -51,11 +51,11 @@ impl Show for MathNode { impl Layout for MathNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, _: &Regions, ) -> SourceResult { - layout_tex(world, &self.texify(), self.display, styles) + layout_tex(vt, &self.texify(), self.display, styles) } } diff --git a/library/src/math/tex.rs b/library/src/math/tex.rs index 5f332f3ce..b2b6486e2 100644 --- a/library/src/math/tex.rs +++ b/library/src/math/tex.rs @@ -35,13 +35,14 @@ impl Texify for Content { /// Layout a TeX formula into a frame. pub fn layout_tex( - world: Tracked, + vt: &Vt, tex: &str, display: bool, styles: StyleChain, ) -> SourceResult { // Load the font. let variant = variant(styles); + let world = vt.world(); let mut font = None; for family in families(styles) { font = world.book().select(family, variant).and_then(|id| world.font(id)); diff --git a/library/src/meta/document.rs b/library/src/meta/document.rs index 309e1bda7..771816713 100644 --- a/library/src/meta/document.rs +++ b/library/src/meta/document.rs @@ -18,15 +18,11 @@ impl DocumentNode { impl LayoutRoot for DocumentNode { /// Layout the document into a sequence of frames, one per page. - fn layout_root( - &self, - world: Tracked, - styles: StyleChain, - ) -> SourceResult { + fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { let mut pages = vec![]; for (page, map) in self.0.iter() { let number = 1 + pages.len(); - let fragment = page.layout(world, number, styles.chain(map))?; + let fragment = page.layout(vt, number, styles.chain(map))?; pages.extend(fragment); } diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs index 44da9c5d7..e7d217e75 100644 --- a/library/src/meta/link.rs +++ b/library/src/meta/link.rs @@ -54,7 +54,7 @@ impl LinkNode { } impl Show for LinkNode { - fn show(&self, _: Tracked, _: StyleChain) -> Content { + fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> Content { self.body.clone() } } diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs index 948aa6f68..c8e8ebdc5 100644 --- a/library/src/meta/reference.rs +++ b/library/src/meta/reference.rs @@ -20,7 +20,7 @@ impl RefNode { } impl Show for RefNode { - fn show(&self, _: Tracked, _: StyleChain) -> Content { + fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> Content { TextNode::packed(format_eco!("@{}", self.0)) } } diff --git a/library/src/prelude.rs b/library/src/prelude.rs index 87fc8e0dc..e7d824cc0 100644 --- a/library/src/prelude.rs +++ b/library/src/prelude.rs @@ -6,7 +6,7 @@ pub use std::fmt::{self, Debug, Formatter}; pub use std::num::NonZeroUsize; #[doc(no_inline)] -pub use comemo::Tracked; +pub use comemo::{Track, Tracked, TrackedMut}; #[doc(no_inline)] pub use typst::diag::{bail, error, with_alternative, At, SourceResult, StrResult}; #[doc(no_inline)] @@ -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, Label, Node, NodeId, Resolve, Show, Smart, Str, - StyleChain, StyleMap, StyleVec, Unlabellable, Value, Vm, + Content, Dict, Finalize, Fold, Func, Label, Node, NodeId, Resolve, Selector, Show, + Smart, Str, StyleChain, StyleMap, StyleVec, Unlabellable, Value, Vm, Vt, }; #[doc(no_inline)] pub use typst::syntax::{Span, Spanned}; diff --git a/library/src/shared/ext.rs b/library/src/shared/ext.rs index 54a1598a2..2d2dc751b 100644 --- a/library/src/shared/ext.rs +++ b/library/src/shared/ext.rs @@ -105,11 +105,11 @@ impl FillNode {} impl Layout for FillNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { - let mut fragment = self.child.layout(world, styles, regions)?; + let mut fragment = self.child.layout(vt, styles, regions)?; for frame in &mut fragment { let shape = Geometry::Rect(frame.size()).filled(self.fill); frame.prepend(Point::zero(), Element::Shape(shape)); @@ -133,11 +133,11 @@ impl StrokeNode {} impl Layout for StrokeNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { - let mut fragment = self.child.layout(world, styles, regions)?; + let mut fragment = self.child.layout(vt, styles, regions)?; for frame in &mut fragment { let shape = Geometry::Rect(frame.size()).stroked(self.stroke); frame.prepend(Point::zero(), Element::Shape(shape)); diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index e81b219c1..4c9f6dcda 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -47,7 +47,7 @@ impl DecoNode { } impl Show for DecoNode { - fn show(&self, _: Tracked, styles: StyleChain) -> Content { + fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> Content { self.0.clone().styled( TextNode::DECO, Decoration { diff --git a/library/src/text/misc.rs b/library/src/text/misc.rs index 32e0cc1dd..c3897a8e1 100644 --- a/library/src/text/misc.rs +++ b/library/src/text/misc.rs @@ -62,7 +62,7 @@ impl StrongNode { } impl Show for StrongNode { - fn show(&self, _: Tracked, styles: StyleChain) -> Content { + fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> Content { self.0.clone().styled(TextNode::DELTA, Delta(styles.get(Self::DELTA))) } } @@ -104,7 +104,7 @@ impl EmphNode { } impl Show for EmphNode { - fn show(&self, _: Tracked, _: StyleChain) -> Content { + fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> Content { self.0.clone().styled(TextNode::EMPH, Toggle) } } diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index 6c7265139..c0bf105fc 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) -> Content { + fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> Content { let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase()); let foreground = THEME .settings diff --git a/library/src/text/shaping.rs b/library/src/text/shaping.rs index 88fec39dc..6dbbb535e 100644 --- a/library/src/text/shaping.rs +++ b/library/src/text/shaping.rs @@ -81,8 +81,8 @@ impl<'a> ShapedText<'a> { /// /// The `justification` defines how much extra advance width each /// [justifiable glyph](ShapedGlyph::is_justifiable) will get. - pub fn build(&self, world: Tracked, justification: Abs) -> Frame { - let (top, bottom) = self.measure(world); + pub fn build(&self, vt: &Vt, justification: Abs) -> Frame { + let (top, bottom) = self.measure(vt); let size = Size::new(self.width, top + bottom); let mut offset = Abs::zero(); @@ -137,7 +137,7 @@ impl<'a> ShapedText<'a> { } /// Measure the top and bottom extent of this text. - fn measure(&self, world: Tracked) -> (Abs, Abs) { + fn measure(&self, vt: &Vt) -> (Abs, Abs) { let mut top = Abs::zero(); let mut bottom = Abs::zero(); @@ -154,6 +154,7 @@ impl<'a> ShapedText<'a> { if self.glyphs.is_empty() { // When there are no glyphs, we just use the vertical metrics of the // first available font. + let world = vt.world(); for family in families(self.styles) { if let Some(font) = world .book() @@ -190,11 +191,7 @@ impl<'a> ShapedText<'a> { /// Reshape a range of the shaped text, reusing information from this /// shaping process if possible. - pub fn reshape( - &'a self, - world: Tracked, - text_range: Range, - ) -> ShapedText<'a> { + pub fn reshape(&'a self, vt: &Vt, text_range: Range) -> ShapedText<'a> { if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) { Self { text: &self.text[text_range], @@ -206,13 +203,14 @@ impl<'a> ShapedText<'a> { glyphs: Cow::Borrowed(glyphs), } } else { - shape(world, &self.text[text_range], self.styles, self.dir) + shape(vt, &self.text[text_range], self.styles, self.dir) } } /// Push a hyphen to end of the text. - pub fn push_hyphen(&mut self, world: Tracked) { + pub fn push_hyphen(&mut self, vt: &Vt) { families(self.styles).find_map(|family| { + let world = vt.world(); let font = world .book() .select(family, self.variant) @@ -303,7 +301,7 @@ impl Debug for ShapedText<'_> { /// Holds shaping results and metadata common to all shaped segments. struct ShapingContext<'a> { - world: Tracked<'a, dyn World>, + vt: &'a Vt<'a>, glyphs: Vec, used: Vec, styles: StyleChain<'a>, @@ -316,7 +314,7 @@ struct ShapingContext<'a> { /// Shape text into [`ShapedText`]. pub fn shape<'a>( - world: Tracked, + vt: &Vt, text: &'a str, styles: StyleChain<'a>, dir: Dir, @@ -324,7 +322,7 @@ pub fn shape<'a>( let size = styles.get(TextNode::SIZE); let mut ctx = ShapingContext { - world, + vt, size, glyphs: vec![], used: vec![], @@ -365,10 +363,11 @@ fn shape_segment<'a>( } // Find the next available family. - let book = ctx.world.book(); + let world = ctx.vt.world(); + let book = world.book(); let mut selection = families.find_map(|family| { book.select(family, ctx.variant) - .and_then(|id| ctx.world.font(id)) + .and_then(|id| world.font(id)) .filter(|font| !ctx.used.contains(font)) }); @@ -377,7 +376,7 @@ fn shape_segment<'a>( let first = ctx.used.first().map(Font::info); selection = book .select_fallback(first, ctx.variant, text) - .and_then(|id| ctx.world.font(id)) + .and_then(|id| world.font(id)) .filter(|font| !ctx.used.contains(font)); } diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs index df8ec5e63..3419cc91d 100644 --- a/library/src/text/shift.rs +++ b/library/src/text/shift.rs @@ -43,11 +43,11 @@ impl ShiftNode { } impl Show for ShiftNode { - fn show(&self, world: Tracked, styles: StyleChain) -> Content { + fn show(&self, vt: &mut Vt, _: &Content, styles: StyleChain) -> Content { let mut transformed = None; if styles.get(Self::TYPOGRAPHIC) { if let Some(text) = search_text(&self.0, S) { - if is_shapable(world, &text, styles) { + if is_shapable(vt, &text, styles) { transformed = Some(TextNode::packed(text)); } } @@ -85,7 +85,8 @@ fn search_text(content: &Content, mode: ShiftKind) -> Option { /// Checks whether the first retrievable family contains all code points of the /// given string. -fn is_shapable(world: Tracked, text: &str, styles: StyleChain) -> bool { +fn is_shapable(vt: &Vt, text: &str, styles: StyleChain) -> bool { + let world = vt.world(); for family in styles.get(TextNode::FAMILY).0.iter() { if let Some(font) = world .book() diff --git a/library/src/visualize/image.rs b/library/src/visualize/image.rs index 6e8b6147f..077fe76d4 100644 --- a/library/src/visualize/image.rs +++ b/library/src/visualize/image.rs @@ -40,7 +40,7 @@ impl ImageNode { impl Layout for ImageNode { fn layout( &self, - _: Tracked, + _: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { diff --git a/library/src/visualize/line.rs b/library/src/visualize/line.rs index 8acf5bb6e..505ad4df3 100644 --- a/library/src/visualize/line.rs +++ b/library/src/visualize/line.rs @@ -39,7 +39,7 @@ impl LineNode { impl Layout for LineNode { fn layout( &self, - _: Tracked, + _: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { diff --git a/library/src/visualize/shape.rs b/library/src/visualize/shape.rs index daecd580b..f2d15cab5 100644 --- a/library/src/visualize/shape.rs +++ b/library/src/visualize/shape.rs @@ -75,7 +75,7 @@ impl ShapeNode { impl Layout for ShapeNode { fn layout( &self, - world: Tracked, + vt: &mut Vt, styles: StyleChain, regions: &Regions, ) -> SourceResult { @@ -90,7 +90,7 @@ impl Layout 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(world, styles, &pod)?.into_frame(); + frame = child.layout(vt, styles, &pod)?.into_frame(); // Relayout with full expansion into square region to make sure // the result is really a square or circle. @@ -106,7 +106,7 @@ impl Layout for ShapeNode { pod.first = Size::splat(length); pod.expand = Axes::splat(true); - frame = child.layout(world, styles, &pod)?.into_frame(); + frame = child.layout(vt, styles, &pod)?.into_frame(); } } else { // The default size that a shape takes on if it has no child and diff --git a/src/model/library.rs b/src/model/library.rs index 518caca12..eee696753 100644 --- a/src/model/library.rs +++ b/src/model/library.rs @@ -2,15 +2,13 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::num::NonZeroUsize; -use comemo::Tracked; use once_cell::sync::OnceCell; -use super::{Content, NodeId, Scope, StyleChain, StyleMap}; +use super::{Content, NodeId, Scope, StyleChain, StyleMap, Vt}; use crate::diag::SourceResult; use crate::doc::Document; use crate::geom::{Abs, Dir}; use crate::util::{hash128, EcoString}; -use crate::World; /// Definition of Typst's standard library. #[derive(Debug, Clone, Hash)] @@ -27,11 +25,8 @@ pub struct Library { #[derive(Clone)] pub struct LangItems { /// The root layout function. - pub layout: fn( - world: Tracked, - content: &Content, - styles: StyleChain, - ) -> SourceResult, + pub layout: + fn(vt: &mut Vt, content: &Content, styles: StyleChain) -> SourceResult, /// Access the em size. pub em: fn(StyleChain) -> Abs, /// Access the text direction. diff --git a/src/model/realize.rs b/src/model/realize.rs index 9d7c4aec5..46e0d678e 100644 --- a/src/model/realize.rs +++ b/src/model/realize.rs @@ -1,8 +1,5 @@ -use comemo::Tracked; - -use super::{capability, Content, NodeId, Recipe, Selector, StyleChain}; +use super::{capability, Content, NodeId, Recipe, Selector, StyleChain, Vt}; 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 { @@ -22,7 +19,7 @@ pub fn applicable(target: &Content, styles: StyleChain) -> bool { /// Apply the show rules in the given style chain to a target. pub fn realize( - world: Tracked, + vt: &mut Vt, target: &Content, styles: StyleChain, ) -> SourceResult> { @@ -34,7 +31,7 @@ pub fn realize( 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)? { + if let Some(content) = try_apply(vt, &target, recipe, guard)? { realized = Some(content); break; } @@ -46,7 +43,7 @@ pub fn realize( 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)); + realized = Some(showable.show(vt, target, styles)); } } @@ -64,7 +61,7 @@ pub fn realize( /// Try to apply a recipe to the target. fn try_apply( - world: Tracked, + vt: &Vt, target: &Content, recipe: &Recipe, guard: Guard, @@ -75,7 +72,7 @@ fn try_apply( return Ok(None); } - recipe.apply(world, target.clone().guarded(guard)).map(Some) + recipe.apply(vt.world(), target.clone().guarded(guard)).map(Some) } Some(Selector::Label(label)) => { @@ -83,7 +80,7 @@ fn try_apply( return Ok(None); } - recipe.apply(world, target.clone().guarded(guard)).map(Some) + recipe.apply(vt.world(), target.clone().guarded(guard)).map(Some) } Some(Selector::Regex(regex)) => { @@ -107,7 +104,7 @@ fn try_apply( } let piece = make(m.as_str().into()).guarded(guard); - let transformed = recipe.apply(world, piece)?; + let transformed = recipe.apply(vt.world(), piece)?; result.push(transformed); cursor = m.end(); } @@ -131,7 +128,7 @@ fn try_apply( #[capability] pub trait Show { /// Execute the base recipe for this node. - fn show(&self, world: Tracked, styles: StyleChain) -> Content; + fn show(&self, vt: &mut Vt, this: &Content, styles: StyleChain) -> Content; } /// Post-process a node after it was realized. diff --git a/src/model/typeset.rs b/src/model/typeset.rs index 451c6eb0f..b422e4b61 100644 --- a/src/model/typeset.rs +++ b/src/model/typeset.rs @@ -10,5 +10,23 @@ use crate::World; pub fn typeset(world: Tracked, content: &Content) -> SourceResult { let library = world.library(); let styles = StyleChain::new(&library.styles); - (library.items.layout)(world, content, styles) + let mut vt = Vt { world }; + (library.items.layout)(&mut vt, content, styles) +} + +/// A virtual typesetter. +/// +/// Holds the state needed to [typeset] content. This is the equivalent to the +/// [Vm](super::Vm) for typesetting. +pub struct Vt<'a> { + /// The compilation environment. + #[doc(hidden)] + pub world: Tracked<'a, dyn World>, +} + +impl<'a> Vt<'a> { + /// Access the underlying world. + pub fn world(&self) -> Tracked<'a, dyn World> { + self.world + } } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index cf77ca152..c4ff32647 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dev-dependencies] typst = { path = ".." } typst-library = { path = "../library" } -comemo = "0.1" +comemo = { git = "https://github.com/typst/comemo" } elsa = "1.7" iai = { git = "https://github.com/reknih/iai" } once_cell = "1"