Introduce virtual typesetter

This commit is contained in:
Laurenz 2022-12-02 13:17:07 +01:00
parent 33ab1fdbdd
commit 5110a41de1
41 changed files with 268 additions and 228 deletions

6
Cargo.lock generated
View File

@ -159,8 +159,7 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]] [[package]]
name = "comemo" name = "comemo"
version = "0.1.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/typst/comemo#36fb31c76eb42d67244bd9c7a2630c29767912f2"
checksum = "bf2ceb1049619fdad21077121c28bffa1552627a1a5f0a0ee97a83e53fe45fd0"
dependencies = [ dependencies = [
"comemo-macros", "comemo-macros",
"siphasher", "siphasher",
@ -169,8 +168,7 @@ dependencies = [
[[package]] [[package]]
name = "comemo-macros" name = "comemo-macros"
version = "0.1.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/typst/comemo#36fb31c76eb42d67244bd9c7a2630c29767912f2"
checksum = "aa4cdbf89b08c431b3ae1ed7d571f293f620311e529b4c2595a39161312d964e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -15,7 +15,7 @@ bench = false
typst-macros = { path = "macros" } typst-macros = { path = "macros" }
bitflags = "1" bitflags = "1"
bytemuck = "1" bytemuck = "1"
comemo = "0.1" comemo = { git = "https://github.com/typst/comemo" }
flate2 = "1" flate2 = "1"
image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] } image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] }
miniz_oxide = "0.5" miniz_oxide = "0.5"

View File

@ -17,7 +17,7 @@ typst = { path = ".." }
typst-library = { path = "../library" } typst-library = { path = "../library" }
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
codespan-reporting = "0.11" codespan-reporting = "0.11"
comemo = "0.1" comemo = { git = "https://github.com/typst/comemo" }
dirs = "4" dirs = "4"
elsa = "1.7" elsa = "1.7"
memmap2 = "0.5" memmap2 = "0.5"

View File

@ -11,7 +11,7 @@ bench = false
[dependencies] [dependencies]
typst = { path = ".." } typst = { path = ".." }
comemo = "0.1" comemo = { git = "https://github.com/typst/comemo" }
csv = "1" csv = "1"
hypher = "0.1" hypher = "0.1"
kurbo = "0.8" kurbo = "0.8"

View File

@ -34,7 +34,7 @@ impl HeadingNode {
} }
impl Show for HeadingNode { impl Show for HeadingNode {
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> Content { fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> Content {
BlockNode(self.body.clone()).pack() BlockNode(self.body.clone()).pack()
} }
} }

View File

@ -78,7 +78,7 @@ impl<const L: ListKind> ListNode<L> {
impl<const L: ListKind> Layout for ListNode<L> { impl<const L: ListKind> Layout for ListNode<L> {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
@ -104,7 +104,7 @@ impl<const L: ListKind> Layout for ListNode<L> {
cells.push(Content::empty()); cells.push(Content::empty());
let label = if L == LIST || L == ENUM { 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 { } else {
Content::empty() Content::empty()
}; };
@ -137,7 +137,7 @@ impl<const L: ListKind> Layout for ListNode<L> {
gutter: Axes::with_y(vec![gutter.into()]), gutter: Axes::with_y(vec![gutter.into()]),
cells, cells,
} }
.layout(world, styles, regions) .layout(vt, styles, regions)
} }
} }
@ -232,7 +232,7 @@ impl Label {
/// Resolve the label based on the level. /// Resolve the label based on the level.
pub fn resolve( pub fn resolve(
&self, &self,
world: Tracked<dyn World>, vt: &Vt,
kind: ListKind, kind: ListKind,
number: usize, number: usize,
) -> SourceResult<Content> { ) -> SourceResult<Content> {
@ -246,7 +246,7 @@ impl Label {
Self::Content(content) => content.clone(), Self::Content(content) => content.clone(),
Self::Func(func, span) => { Self::Func(func, span) => {
let args = Args::new(*span, [Value::Int(number as i64)]); let args = Args::new(*span, [Value::Int(number as i64)]);
func.call_detached(world, args)?.display() func.call_detached(vt.world(), args)?.display()
} }
}) })
} }

View File

@ -53,7 +53,7 @@ impl TableNode {
impl Layout for TableNode { impl Layout for TableNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
@ -76,7 +76,7 @@ impl Layout for TableNode {
let x = i % cols; let x = i % cols;
let y = 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); child = child.filled(fill);
} }
@ -89,7 +89,7 @@ impl Layout for TableNode {
gutter: self.gutter.clone(), gutter: self.gutter.clone(),
cells, cells,
} }
.layout(world, styles, regions) .layout(vt, styles, regions)
} }
} }
@ -104,17 +104,12 @@ pub enum Celled<T> {
impl<T: Cast + Clone> Celled<T> { impl<T: Cast + Clone> Celled<T> {
/// Resolve the value based on the cell position. /// Resolve the value based on the cell position.
pub fn resolve( pub fn resolve(&self, vt: &Vt, x: usize, y: usize) -> SourceResult<T> {
&self,
world: Tracked<dyn World>,
x: usize,
y: usize,
) -> SourceResult<T> {
Ok(match self { Ok(match self {
Self::Value(value) => value.clone(), Self::Value(value) => value.clone(),
Self::Func(func, span) => { Self::Func(func, span) => {
let args = Args::new(*span, [Value::Int(x as i64), Value::Int(y as i64)]); 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)?
} }
}) })
} }

View File

@ -1,7 +1,6 @@
use crate::prelude::*; use crate::prelude::*;
use comemo::Track; use comemo::Track;
use typst::model; use typst::model;
use typst::syntax::Source; use typst::syntax::Source;

View File

@ -29,7 +29,7 @@ impl AlignNode {
impl Layout for AlignNode { impl Layout for AlignNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
@ -44,7 +44,7 @@ impl Layout for AlignNode {
} }
// Layout the child. // 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) { for (region, frame) in regions.iter().zip(&mut fragment) {
// Align in the target size. The target size depends on whether we // Align in the target size. The target size depends on whether we
// should expand. // should expand.

View File

@ -29,14 +29,14 @@ impl ColumnsNode {
impl Layout for ColumnsNode { impl Layout for ColumnsNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
// Separating the infinite space into infinite columns does not make // Separating the infinite space into infinite columns does not make
// much sense. // much sense.
if !regions.first.x.is_finite() { 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. // Determine the width of the gutter and each column.
@ -58,7 +58,7 @@ impl Layout for ColumnsNode {
}; };
// Layout the children. // 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 mut finished = vec![];
let dir = styles.get(TextNode::DIR); let dir = styles.get(TextNode::DIR);

View File

@ -23,7 +23,7 @@ impl BoxNode {
impl Layout for BoxNode { impl Layout for BoxNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
@ -47,7 +47,7 @@ impl Layout for BoxNode {
}; };
// Layout the child. // 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. // Ensure frame size matches regions size if expansion is on.
let target = regions.expand.select(regions.first, frame.size()); let target = regions.expand.select(regions.first, frame.size());
@ -95,10 +95,10 @@ impl BlockNode {
impl Layout for BlockNode { impl Layout for BlockNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
self.0.layout(world, styles, regions) self.0.layout(vt, styles, regions)
} }
} }

View File

@ -20,7 +20,7 @@ impl FlowNode {
impl Layout for FlowNode { impl Layout for FlowNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
@ -33,9 +33,9 @@ impl Layout for FlowNode {
} else if let Some(node) = child.to::<ParNode>() { } else if let Some(node) = child.to::<ParNode>() {
let barrier = Style::Barrier(child.id()); let barrier = Style::Barrier(child.id());
let styles = styles.chain_one(&barrier); let styles = styles.chain_one(&barrier);
layouter.layout_par(world, node, styles)?; layouter.layout_par(vt, node, styles)?;
} else if child.has::<dyn Layout>() { } else if child.has::<dyn Layout>() {
layouter.layout_block(world, child, styles)?; layouter.layout_block(vt, child, styles)?;
} else if child.is::<ColbreakNode>() { } else if child.is::<ColbreakNode>() {
layouter.finish_region(false); layouter.finish_region(false);
} else { } else {
@ -122,16 +122,23 @@ impl FlowLayouter {
/// Layout a paragraph. /// Layout a paragraph.
fn layout_par( fn layout_par(
&mut self, &mut self,
world: Tracked<dyn World>, vt: &mut Vt,
par: &ParNode, par: &ParNode,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<()> { ) -> SourceResult<()> {
let aligns = Axes::new(styles.get(ParNode::ALIGN), Align::Top); let aligns = Axes::new(styles.get(ParNode::ALIGN), Align::Top);
let leading = styles.get(ParNode::LEADING); let leading = styles.get(ParNode::LEADING);
let consecutive = self.last_was_par; let consecutive = self.last_was_par;
let fragment = par.layout(world, styles, &self.regions, consecutive)?; let fragment = par.layout(
let len = fragment.len(); 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() { for (i, frame) in fragment.into_iter().enumerate() {
if i > 0 { if i > 0 {
self.layout_item(FlowItem::Leading(leading)); self.layout_item(FlowItem::Leading(leading));
@ -151,7 +158,7 @@ impl FlowLayouter {
/// Layout a block. /// Layout a block.
fn layout_block( fn layout_block(
&mut self, &mut self,
world: Tracked<dyn World>, vt: &mut Vt,
block: &Content, block: &Content,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<()> { ) -> SourceResult<()> {
@ -159,7 +166,7 @@ impl FlowLayouter {
// aligned later. // aligned later.
if let Some(placed) = block.to::<PlaceNode>() { if let Some(placed) = block.to::<PlaceNode>() {
if placed.out_of_flow() { 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)); self.layout_item(FlowItem::Placed(frame));
return Ok(()); return Ok(());
} }
@ -180,7 +187,7 @@ impl FlowLayouter {
// Layout the block itself. // Layout the block itself.
let sticky = styles.get(BlockNode::STICKY); 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 { for frame in fragment {
self.layout_item(FlowItem::Frame(frame, aligns, sticky)); self.layout_item(FlowItem::Frame(frame, aligns, sticky));
} }

View File

@ -36,13 +36,13 @@ impl GridNode {
impl Layout for GridNode { impl Layout for GridNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
// Prepare grid layout by unifying content and gutter tracks. // Prepare grid layout by unifying content and gutter tracks.
let layouter = GridLayouter::new( let layouter = GridLayouter::new(
world, vt,
self.tracks.as_deref(), self.tracks.as_deref(),
self.gutter.as_deref(), self.gutter.as_deref(),
&self.cells, &self.cells,
@ -110,9 +110,9 @@ castable! {
} }
/// Performs grid layout. /// Performs grid layout.
struct GridLayouter<'a> { struct GridLayouter<'a, 'v> {
/// The core context. /// The core context.
world: Tracked<'a, dyn World>, vt: &'a mut Vt<'v>,
/// The grid cells. /// The grid cells.
cells: &'a [Content], cells: &'a [Content],
/// The column tracks including gutter tracks. /// The column tracks including gutter tracks.
@ -147,12 +147,12 @@ enum Row {
Fr(Fr, usize), Fr(Fr, usize),
} }
impl<'a> GridLayouter<'a> { impl<'a, 'v> GridLayouter<'a, 'v> {
/// Create a new grid layouter. /// Create a new grid layouter.
/// ///
/// This prepares grid layout by unifying content and gutter tracks. /// This prepares grid layout by unifying content and gutter tracks.
fn new( fn new(
world: Tracked<'a, dyn World>, vt: &'a mut Vt<'v>,
tracks: Axes<&[TrackSizing]>, tracks: Axes<&[TrackSizing]>,
gutter: Axes<&[TrackSizing]>, gutter: Axes<&[TrackSizing]>,
cells: &'a [Content], cells: &'a [Content],
@ -206,7 +206,7 @@ impl<'a> GridLayouter<'a> {
regions.expand = Axes::new(true, false); regions.expand = Axes::new(true, false);
Self { Self {
world, vt,
cells, cells,
cols, cols,
rows, rows,
@ -318,7 +318,7 @@ impl<'a> GridLayouter<'a> {
v.resolve(self.styles).relative_to(self.regions.base.y); 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()); resolved.set_max(frame.width());
} }
} }
@ -395,7 +395,7 @@ impl<'a> GridLayouter<'a> {
} }
let mut sizes = cell let mut sizes = cell
.layout(self.world, self.styles, &pod)? .layout(self.vt, self.styles, &pod)?
.into_iter() .into_iter()
.map(|frame| frame.height()); .map(|frame| frame.height());
@ -483,7 +483,7 @@ impl<'a> GridLayouter<'a> {
.select(self.regions.base, size); .select(self.regions.base, size);
let pod = Regions::one(size, base, Axes::splat(true)); 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); output.push_frame(pos, frame);
} }
@ -519,7 +519,7 @@ impl<'a> GridLayouter<'a> {
} }
// Push the layouted frames into the individual output frames. // 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) { for (output, frame) in outputs.iter_mut().zip(fragment) {
output.push_frame(pos, frame); output.push_frame(pos, frame);
} }

View File

@ -14,11 +14,11 @@ impl HideNode {
impl Layout for HideNode { impl Layout for HideNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let mut fragment = self.0.layout(world, styles, regions)?; let mut fragment = self.0.layout(vt, styles, regions)?;
for frame in &mut fragment { for frame in &mut fragment {
frame.clear(); frame.clear();
} }

View File

@ -32,7 +32,6 @@ pub use self::transform::*;
use std::mem; use std::mem;
use comemo::Tracked;
use typed_arena::Arena; use typed_arena::Arena;
use typst::diag::SourceResult; use typst::diag::SourceResult;
use typst::geom::*; use typst::geom::*;
@ -40,7 +39,6 @@ use typst::model::{
applicable, capability, realize, Content, Node, SequenceNode, Style, StyleChain, applicable, capability, realize, Content, Node, SequenceNode, Style, StyleChain,
StyleVecBuilder, StyledNode, StyleVecBuilder, StyledNode,
}; };
use typst::World;
use crate::basics::{DescNode, EnumNode, ListItem, ListNode, DESC, ENUM, LIST}; use crate::basics::{DescNode, EnumNode, ListItem, ListNode, DESC, ENUM, LIST};
use crate::meta::DocumentNode; use crate::meta::DocumentNode;
@ -52,23 +50,27 @@ use crate::text::{LinebreakNode, SmartQuoteNode, SpaceNode, TextNode};
#[capability] #[capability]
pub trait LayoutRoot { pub trait LayoutRoot {
/// Layout into one frame per page. /// Layout into one frame per page.
fn layout_root( fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document>;
&self,
world: Tracked<dyn World>,
styles: StyleChain,
) -> SourceResult<Document>;
} }
impl LayoutRoot for Content { impl LayoutRoot for Content {
fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document> {
#[comemo::memoize] #[comemo::memoize]
fn layout_root( fn cached(
&self, node: &Content,
world: Tracked<dyn World>, world: comemo::Tracked<dyn World>,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Document> { ) -> SourceResult<Document> {
let mut vt = Vt { world };
let scratch = Scratch::default(); let scratch = Scratch::default();
let (realized, styles) = realize_root(world, &scratch, self, styles)?; let (realized, styles) = realize_root(&mut vt, &scratch, node, styles)?;
realized.with::<dyn LayoutRoot>().unwrap().layout_root(world, styles) realized
.with::<dyn LayoutRoot>()
.unwrap()
.layout_root(&mut vt, styles)
}
cached(self, vt.world, styles)
} }
} }
@ -78,25 +80,38 @@ pub trait Layout {
/// Layout into one frame per region. /// Layout into one frame per region.
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment>; ) -> SourceResult<Fragment>;
} }
impl Layout for Content { impl Layout for Content {
#[comemo::memoize]
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
#[comemo::memoize]
fn cached(
node: &Content,
world: comemo::Tracked<dyn World>,
styles: StyleChain,
regions: &Regions,
) -> SourceResult<Fragment> {
let mut vt = Vt { world };
let scratch = Scratch::default(); let scratch = Scratch::default();
let (realized, styles) = realize_block(world, &scratch, self, styles)?; let (realized, styles) = realize_block(&mut vt, &scratch, node, styles)?;
let barrier = Style::Barrier(realized.id()); let barrier = Style::Barrier(realized.id());
let styles = styles.chain_one(&barrier); let styles = styles.chain_one(&barrier);
realized.with::<dyn Layout>().unwrap().layout(world, styles, regions) realized
.with::<dyn Layout>()
.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. /// Realize into a node that is capable of root-level layout.
fn realize_root<'a>( fn realize_root<'a>(
world: Tracked<'a, dyn World>, vt: &mut Vt,
scratch: &'a Scratch<'a>, scratch: &'a Scratch<'a>,
content: &'a Content, content: &'a Content,
styles: StyleChain<'a>, styles: StyleChain<'a>,
@ -208,7 +223,7 @@ fn realize_root<'a>(
return Ok((content.clone(), styles)); 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.accept(content, styles)?;
builder.interrupt_page(Some(styles))?; builder.interrupt_page(Some(styles))?;
let (pages, shared) = builder.doc.unwrap().pages.finish(); 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. /// Realize into a node that is capable of block-level layout.
fn realize_block<'a>( fn realize_block<'a>(
world: Tracked<'a, dyn World>, vt: &mut Vt,
scratch: &'a Scratch<'a>, scratch: &'a Scratch<'a>,
content: &'a Content, content: &'a Content,
styles: StyleChain<'a>, styles: StyleChain<'a>,
@ -226,7 +241,7 @@ fn realize_block<'a>(
return Ok((content.clone(), styles)); 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.accept(content, styles)?;
builder.interrupt_par()?; builder.interrupt_par()?;
let (children, shared) = builder.flow.0.finish(); let (children, shared) = builder.flow.0.finish();
@ -234,9 +249,9 @@ fn realize_block<'a>(
} }
/// Builds a document or a flow node from content. /// Builds a document or a flow node from content.
struct Builder<'a> { struct Builder<'a, 'v, 't> {
/// The core context. /// The virtual typesetter.
world: Tracked<'a, dyn World>, vt: &'v mut Vt<'t>,
/// Scratch arenas for building. /// Scratch arenas for building.
scratch: &'a Scratch<'a>, scratch: &'a Scratch<'a>,
/// The current document building state. /// The current document building state.
@ -258,10 +273,10 @@ struct Scratch<'a> {
content: Arena<Content>, content: Arena<Content>,
} }
impl<'a> Builder<'a> { impl<'a, 'v, 't> Builder<'a, 'v, 't> {
fn new(world: Tracked<'a, dyn World>, scratch: &'a Scratch<'a>, top: bool) -> Self { fn new(vt: &'v mut Vt<'t>, scratch: &'a Scratch<'a>, top: bool) -> Self {
Self { Self {
world, vt,
scratch, scratch,
doc: top.then(|| DocBuilder::default()), doc: top.then(|| DocBuilder::default()),
flow: FlowBuilder::default(), flow: FlowBuilder::default(),
@ -286,7 +301,7 @@ impl<'a> Builder<'a> {
return Ok(()); 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); let stored = self.scratch.content.alloc(realized);
return self.accept(stored, styles); return self.accept(stored, styles);
} }

View File

@ -28,14 +28,14 @@ impl PadNode {
impl Layout for PadNode { impl Layout for PadNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
// Layout child into padded regions. // Layout child into padded regions.
let padding = self.padding.resolve(styles); let padding = self.padding.resolve(styles);
let pod = regions.map(|size| shrink(size, padding)); 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 { for frame in &mut fragment {
// Apply the padding inversely such that the grown size padded // Apply the padding inversely such that the grown size padded

View File

@ -57,7 +57,7 @@ impl PageNode {
/// Layout the page run into a sequence of frames, one per page. /// Layout the page run into a sequence of frames, one per page.
pub fn layout( pub fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
mut page: usize, mut page: usize,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
@ -97,7 +97,7 @@ impl PageNode {
// Layout the child. // Layout the child.
let regions = Regions::repeat(size, size, size.map(Abs::is_finite)); let regions = Regions::repeat(size, size, size.map(Abs::is_finite));
let mut fragment = child.layout(world, styles, &regions)?; let mut fragment = child.layout(vt, styles, &regions)?;
let header = styles.get(Self::HEADER); let header = styles.get(Self::HEADER);
let footer = styles.get(Self::FOOTER); let footer = styles.get(Self::FOOTER);
@ -116,9 +116,9 @@ impl PageNode {
(foreground, Point::zero(), size), (foreground, Point::zero(), size),
(background, 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 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) { if std::ptr::eq(marginal, background) {
frame.prepend_frame(pos, sub); frame.prepend_frame(pos, sub);
} else { } else {
@ -169,17 +169,13 @@ pub enum Marginal {
impl Marginal { impl Marginal {
/// Resolve the marginal based on the page number. /// Resolve the marginal based on the page number.
pub fn resolve( pub fn resolve(&self, vt: &Vt, page: usize) -> SourceResult<Option<Content>> {
&self,
world: Tracked<dyn World>,
page: usize,
) -> SourceResult<Option<Content>> {
Ok(match self { Ok(match self {
Self::None => None, Self::None => None,
Self::Content(content) => Some(content.clone()), Self::Content(content) => Some(content.clone()),
Self::Func(func, span) => { Self::Func(func, span) => {
let args = Args::new(*span, [Value::Int(page as i64)]); 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())
} }
}) })
} }

View File

@ -44,27 +44,43 @@ impl ParNode {
impl ParNode { impl ParNode {
/// Layout the paragraph into a collection of lines. /// Layout the paragraph into a collection of lines.
#[comemo::memoize]
pub fn layout( pub fn layout(
&self, &self,
vt: &mut Vt,
styles: StyleChain,
consecutive: bool,
width: Abs,
base: Size,
expand: bool,
) -> SourceResult<Fragment> {
#[comemo::memoize]
fn cached(
par: &ParNode,
world: Tracked<dyn World>, world: Tracked<dyn World>,
styles: StyleChain, styles: StyleChain,
regions: &Regions,
consecutive: bool, consecutive: bool,
width: Abs,
base: Size,
expand: bool,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let mut vt = Vt { world };
// Collect all text into one string for BiDi analysis. // Collect all text into one string for BiDi analysis.
let (text, segments) = collect(self, &styles, consecutive); let (text, segments) = collect(par, &styles, consecutive);
// Perform BiDi analysis and then prepare paragraph layout by building a // Perform BiDi analysis and then prepare paragraph layout by building a
// representation on which we can do line breaking without layouting // representation on which we can do line breaking without layouting
// each and every line from scratch. // each and every line from scratch.
let p = prepare(world, self, &text, segments, regions, styles)?; let p = prepare(&mut vt, par, &text, segments, styles, width, base)?;
// Break the paragraph into lines. // Break the paragraph into lines.
let lines = linebreak(&p, regions.first.x); let lines = linebreak(&vt, &p, width);
// Stack the lines into one frame per region. // Stack the lines into one frame per region.
finalize(&p, &lines, regions) 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 /// Only when a line break falls onto a text index that is not safe-to-break per
/// rustybuzz, we have to reshape that portion. /// rustybuzz, we have to reshape that portion.
struct Preparation<'a> { struct Preparation<'a> {
/// The compilation environment.
world: Tracked<'a, dyn World>,
/// Bidirectional text embedding levels for the paragraph. /// Bidirectional text embedding levels for the paragraph.
bidi: BidiInfo<'a>, bidi: BidiInfo<'a>,
/// Text runs, spacing and layouted nodes. /// Text runs, spacing and layouted nodes.
@ -470,12 +484,13 @@ fn collect<'a>(
/// Prepare paragraph layout by shaping the whole paragraph and layouting all /// Prepare paragraph layout by shaping the whole paragraph and layouting all
/// contained inline-level content. /// contained inline-level content.
fn prepare<'a>( fn prepare<'a>(
world: Tracked<'a, dyn World>, vt: &mut Vt,
par: &'a ParNode, par: &'a ParNode,
text: &'a str, text: &'a str,
segments: Vec<(Segment<'a>, StyleChain<'a>)>, segments: Vec<(Segment<'a>, StyleChain<'a>)>,
regions: &Regions,
styles: StyleChain<'a>, styles: StyleChain<'a>,
width: Abs,
base: Size,
) -> SourceResult<Preparation<'a>> { ) -> SourceResult<Preparation<'a>> {
let bidi = BidiInfo::new( let bidi = BidiInfo::new(
text, text,
@ -494,11 +509,11 @@ fn prepare<'a>(
let end = cursor + segment.len(); let end = cursor + segment.len();
match segment { match segment {
Segment::Text(_) => { 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 { Segment::Spacing(spacing) => match spacing {
Spacing::Relative(v) => { 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)); items.push(Item::Absolute(resolved));
} }
Spacing::Fractional(v) => { Spacing::Fractional(v) => {
@ -509,9 +524,9 @@ fn prepare<'a>(
if let Some(repeat) = inline.to::<RepeatNode>() { if let Some(repeat) = inline.to::<RepeatNode>() {
items.push(Item::Repeat(repeat, styles)); items.push(Item::Repeat(repeat, styles));
} else { } else {
let size = Size::new(regions.first.x, regions.base.y); let size = Size::new(width, base.y);
let pod = Regions::one(size, regions.base, Axes::splat(false)); let pod = Regions::one(size, base, Axes::splat(false));
let mut frame = inline.layout(world, styles, &pod)?.into_frame(); let mut frame = inline.layout(vt, styles, &pod)?.into_frame();
frame.translate(Point::with_y(styles.get(TextNode::BASELINE))); frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
items.push(Item::Frame(frame)); items.push(Item::Frame(frame));
} }
@ -522,7 +537,6 @@ fn prepare<'a>(
} }
Ok(Preparation { Ok(Preparation {
world,
bidi, bidi,
items, items,
styles, styles,
@ -537,14 +551,14 @@ fn prepare<'a>(
/// items for them. /// items for them.
fn shape_range<'a>( fn shape_range<'a>(
items: &mut Vec<Item<'a>>, items: &mut Vec<Item<'a>>,
world: Tracked<dyn World>, vt: &Vt,
bidi: &BidiInfo<'a>, bidi: &BidiInfo<'a>,
range: Range, range: Range,
styles: StyleChain<'a>, styles: StyleChain<'a>,
) { ) {
let mut process = |text, level: BidiLevel| { let mut process = |text, level: BidiLevel| {
let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL }; 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)); items.push(Item::Text(shaped));
}; };
@ -601,7 +615,7 @@ fn shared_get<'a, K: Key>(
} }
/// Find suitable linebreaks. /// Find suitable linebreaks.
fn linebreak<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> { fn linebreak<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
let linebreaks = p.styles.get(ParNode::LINEBREAKS).unwrap_or_else(|| { let linebreaks = p.styles.get(ParNode::LINEBREAKS).unwrap_or_else(|| {
if p.styles.get(ParNode::JUSTIFY) { if p.styles.get(ParNode::JUSTIFY) {
Linebreaks::Optimized Linebreaks::Optimized
@ -611,22 +625,22 @@ fn linebreak<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
}); });
match linebreaks { match linebreaks {
Linebreaks::Simple => linebreak_simple(p, width), Linebreaks::Simple => linebreak_simple(vt, p, width),
Linebreaks::Optimized => linebreak_optimized(p, width), Linebreaks::Optimized => linebreak_optimized(vt, p, width),
} }
} }
/// Perform line breaking in simple first-fit style. This means that we build /// 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 /// lines greedily, always taking the longest possible line. This may lead to
/// very unbalanced line, but is fast and simple. /// very unbalanced line, but is fast and simple.
fn linebreak_simple<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> { fn linebreak_simple<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
let mut lines = vec![]; let mut lines = vec![];
let mut start = 0; let mut start = 0;
let mut last = None; let mut last = None;
for (end, mandatory, hyphen) in breakpoints(p) { for (end, mandatory, hyphen) in breakpoints(p) {
// Compute the line and its size. // 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 // 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 // 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<Line<'a>> {
if let Some((last_attempt, last_end)) = last.take() { if let Some((last_attempt, last_end)) = last.take() {
lines.push(last_attempt); lines.push(last_attempt);
start = last_end; 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<Line<'a>> {
/// computed and stored in dynamic programming table) is minimal. The final /// 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 /// result is simply the layout determined for the last breakpoint at the end of
/// text. /// text.
fn linebreak_optimized<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> { fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
/// The cost of a line or paragraph layout. /// The cost of a line or paragraph layout.
type Cost = f64; type Cost = f64;
@ -698,7 +712,7 @@ fn linebreak_optimized<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>>
let mut table = vec![Entry { let mut table = vec![Entry {
pred: 0, pred: 0,
total: 0.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); let em = p.styles.get(TextNode::SIZE);
@ -712,7 +726,7 @@ fn linebreak_optimized<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>>
for (i, pred) in table.iter_mut().enumerate().skip(active) { for (i, pred) in table.iter_mut().enumerate().skip(active) {
// Layout the line. // Layout the line.
let start = pred.line.end; 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 // Determine how much the line's spaces would need to be stretched
// to make it the desired width. // to make it the desired width.
@ -787,7 +801,7 @@ fn linebreak_optimized<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>>
/// Returns for each breakpoint the text index, whether the break is mandatory /// 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 /// (after `\n`) and whether a hyphen is required (when breaking inside of a
/// word). /// word).
fn breakpoints<'a>(p: &'a Preparation) -> Breakpoints<'a> { fn breakpoints<'a>(p: &'a Preparation<'a>) -> Breakpoints<'a> {
Breakpoints { Breakpoints {
p, p,
linebreaks: LineBreakIterator::new(p.bidi.text), linebreaks: LineBreakIterator::new(p.bidi.text),
@ -885,6 +899,7 @@ impl Breakpoints<'_> {
/// Create a line which spans the given range. /// Create a line which spans the given range.
fn line<'a>( fn line<'a>(
vt: &Vt,
p: &'a Preparation, p: &'a Preparation,
mut range: Range, mut range: Range,
mandatory: bool, mandatory: bool,
@ -940,9 +955,9 @@ fn line<'a>(
if hyphen || start + shaped.text.len() > range.end { if hyphen || start + shaped.text.len() > range.end {
if hyphen || start < range.end || before.is_empty() { if hyphen || start < range.end || before.is_empty() {
let shifted = start - base..range.end - base; 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 { if hyphen || shy {
reshaped.push_hyphen(p.world); reshaped.push_hyphen(vt);
} }
width += reshaped.width; width += reshaped.width;
last = Some(Item::Text(reshaped)); last = Some(Item::Text(reshaped));
@ -963,7 +978,7 @@ fn line<'a>(
if range.start + shaped.text.len() > end { if range.start + shaped.text.len() > end {
if range.start < end { if range.start < end {
let shifted = range.start - base..end - base; let shifted = range.start - base..end - base;
let reshaped = shaped.reshape(p.world, shifted); let reshaped = shaped.reshape(vt, shifted);
width += reshaped.width; width += reshaped.width;
first = Some(Item::Text(reshaped)); first = Some(Item::Text(reshaped));
} }
@ -992,27 +1007,35 @@ fn line<'a>(
/// Combine layouted lines into one frame per region. /// Combine layouted lines into one frame per region.
fn finalize( fn finalize(
vt: &mut Vt,
p: &Preparation, p: &Preparation,
lines: &[Line], lines: &[Line],
regions: &Regions, mut width: Abs,
base: Size,
expand: bool,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
// Determine the paragraph's width: Full width of the region if we // Determine the paragraph's width: Full width of the region if we
// should expand or there's fractional spacing, fit-to-width otherwise. // should expand or there's fractional spacing, fit-to-width otherwise.
let mut width = regions.first.x; if !expand && lines.iter().all(|line| line.fr().is_zero()) {
if !regions.expand.x && lines.iter().all(|line| line.fr().is_zero()) {
width = lines.iter().map(|line| line.width).max().unwrap_or_default(); width = lines.iter().map(|line| line.width).max().unwrap_or_default();
} }
// Stack the lines into one frame per region. // Stack the lines into one frame per region.
lines lines
.iter() .iter()
.map(|line| commit(p, line, regions.base, width)) .map(|line| commit(vt, p, line, base, width))
.collect::<SourceResult<_>>() .collect::<SourceResult<_>>()
.map(Fragment::frames) .map(Fragment::frames)
} }
/// Commit to a line and build its frame. /// Commit to a line and build its frame.
fn commit(p: &Preparation, line: &Line, base: Size, width: Abs) -> SourceResult<Frame> { fn commit(
vt: &mut Vt,
p: &Preparation,
line: &Line,
base: Size,
width: Abs,
) -> SourceResult<Frame> {
let mut remaining = width - line.width; let mut remaining = width - line.width;
let mut offset = Abs::zero(); 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); offset += v.share(fr, remaining);
} }
Item::Text(shaped) => { Item::Text(shaped) => {
let frame = shaped.build(p.world, justification); let frame = shaped.build(vt, justification);
push(&mut offset, frame); push(&mut offset, frame);
} }
Item::Frame(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 fill = Fr::one().share(fr, remaining);
let size = Size::new(fill, base.y); let size = Size::new(fill, base.y);
let pod = Regions::one(size, base, Axes::new(false, false)); 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 width = frame.width();
let count = (fill / width).floor(); let count = (fill / width).floor();
let remaining = fill % width; let remaining = fill % width;

View File

@ -19,7 +19,7 @@ impl PlaceNode {
impl Layout for PlaceNode { impl Layout for PlaceNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
@ -33,7 +33,7 @@ impl Layout for PlaceNode {
Regions::one(regions.base, regions.base, expand) 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 // If expansion is off, zero all sizes so that we don't take up any
// space in our parent. Otherwise, respect the expand settings. // space in our parent. Otherwise, respect the expand settings.

View File

@ -14,11 +14,11 @@ impl RepeatNode {
impl Layout for RepeatNode { impl Layout for RepeatNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
self.0.layout(world, styles, regions) self.0.layout(vt, styles, regions)
} }
} }

View File

@ -29,7 +29,7 @@ impl StackNode {
impl Layout for StackNode { impl Layout for StackNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
@ -49,7 +49,7 @@ impl Layout for StackNode {
layouter.layout_spacing(kind); layouter.layout_spacing(kind);
} }
layouter.layout_block(world, block, styles)?; layouter.layout_block(vt, block, styles)?;
deferred = self.spacing; deferred = self.spacing;
} }
} }
@ -170,7 +170,7 @@ impl<'a> StackLayouter<'a> {
/// Layout an arbitrary block. /// Layout an arbitrary block.
fn layout_block( fn layout_block(
&mut self, &mut self,
world: Tracked<dyn World>, vt: &mut Vt,
block: &Content, block: &Content,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<()> { ) -> SourceResult<()> {
@ -195,7 +195,7 @@ impl<'a> StackLayouter<'a> {
self.dir.start().into() self.dir.start().into()
}); });
let fragment = block.layout(world, styles, &self.regions)?; let fragment = block.layout(vt, styles, &self.regions)?;
let len = fragment.len(); let len = fragment.len();
for (i, frame) in fragment.into_iter().enumerate() { for (i, frame) in fragment.into_iter().enumerate() {
// Grow our size, shrink the region and save the frame for later. // Grow our size, shrink the region and save the frame for later.

View File

@ -27,11 +27,11 @@ impl MoveNode {
impl Layout for MoveNode { impl Layout for MoveNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let mut fragment = self.child.layout(world, styles, regions)?; let mut fragment = self.child.layout(vt, styles, regions)?;
for frame in &mut fragment { for frame in &mut fragment {
let delta = self.delta.resolve(styles); let delta = self.delta.resolve(styles);
let delta = delta.zip(frame.size()).map(|(d, s)| d.relative_to(s)); let delta = delta.zip(frame.size()).map(|(d, s)| d.relative_to(s));
@ -85,11 +85,11 @@ impl<const T: TransformKind> TransformNode<T> {
impl<const T: TransformKind> Layout for TransformNode<T> { impl<const T: TransformKind> Layout for TransformNode<T> {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let mut fragment = self.child.layout(world, styles, regions)?; let mut fragment = self.child.layout(vt, styles, regions)?;
for frame in &mut fragment { for frame in &mut fragment {
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON); 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)); let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));

View File

@ -30,7 +30,7 @@ impl MathNode {
} }
impl Show for MathNode { impl Show for MathNode {
fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> Content { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> Content {
let mut map = StyleMap::new(); let mut map = StyleMap::new();
map.set_family(FontFamily::new("NewComputerModernMath"), styles); map.set_family(FontFamily::new("NewComputerModernMath"), styles);
@ -51,11 +51,11 @@ impl Show for MathNode {
impl Layout for MathNode { impl Layout for MathNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
_: &Regions, _: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
layout_tex(world, &self.texify(), self.display, styles) layout_tex(vt, &self.texify(), self.display, styles)
} }
} }

View File

@ -35,13 +35,14 @@ impl Texify for Content {
/// Layout a TeX formula into a frame. /// Layout a TeX formula into a frame.
pub fn layout_tex( pub fn layout_tex(
world: Tracked<dyn World>, vt: &Vt,
tex: &str, tex: &str,
display: bool, display: bool,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
// Load the font. // Load the font.
let variant = variant(styles); let variant = variant(styles);
let world = vt.world();
let mut font = None; let mut font = None;
for family in families(styles) { for family in families(styles) {
font = world.book().select(family, variant).and_then(|id| world.font(id)); font = world.book().select(family, variant).and_then(|id| world.font(id));

View File

@ -18,15 +18,11 @@ impl DocumentNode {
impl LayoutRoot for DocumentNode { impl LayoutRoot for DocumentNode {
/// Layout the document into a sequence of frames, one per page. /// Layout the document into a sequence of frames, one per page.
fn layout_root( fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document> {
&self,
world: Tracked<dyn World>,
styles: StyleChain,
) -> SourceResult<Document> {
let mut pages = vec![]; let mut pages = vec![];
for (page, map) in self.0.iter() { for (page, map) in self.0.iter() {
let number = 1 + pages.len(); 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); pages.extend(fragment);
} }

View File

@ -54,7 +54,7 @@ impl LinkNode {
} }
impl Show for LinkNode { impl Show for LinkNode {
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> Content { fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> Content {
self.body.clone() self.body.clone()
} }
} }

View File

@ -20,7 +20,7 @@ impl RefNode {
} }
impl Show for RefNode { impl Show for RefNode {
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> Content { fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> Content {
TextNode::packed(format_eco!("@{}", self.0)) TextNode::packed(format_eco!("@{}", self.0))
} }
} }

View File

@ -6,7 +6,7 @@ pub use std::fmt::{self, Debug, Formatter};
pub use std::num::NonZeroUsize; pub use std::num::NonZeroUsize;
#[doc(no_inline)] #[doc(no_inline)]
pub use comemo::Tracked; pub use comemo::{Track, Tracked, TrackedMut};
#[doc(no_inline)] #[doc(no_inline)]
pub use typst::diag::{bail, error, with_alternative, At, SourceResult, StrResult}; pub use typst::diag::{bail, error, with_alternative, At, SourceResult, StrResult};
#[doc(no_inline)] #[doc(no_inline)]
@ -16,8 +16,8 @@ pub use typst::geom::*;
#[doc(no_inline)] #[doc(no_inline)]
pub use typst::model::{ pub use typst::model::{
array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast, array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast,
Content, Dict, Finalize, Fold, Func, Label, Node, NodeId, Resolve, Show, Smart, Str, Content, Dict, Finalize, Fold, Func, Label, Node, NodeId, Resolve, Selector, Show,
StyleChain, StyleMap, StyleVec, Unlabellable, Value, Vm, Smart, Str, StyleChain, StyleMap, StyleVec, Unlabellable, Value, Vm, Vt,
}; };
#[doc(no_inline)] #[doc(no_inline)]
pub use typst::syntax::{Span, Spanned}; pub use typst::syntax::{Span, Spanned};

View File

@ -105,11 +105,11 @@ impl FillNode {}
impl Layout for FillNode { impl Layout for FillNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let mut fragment = self.child.layout(world, styles, regions)?; let mut fragment = self.child.layout(vt, styles, regions)?;
for frame in &mut fragment { for frame in &mut fragment {
let shape = Geometry::Rect(frame.size()).filled(self.fill); let shape = Geometry::Rect(frame.size()).filled(self.fill);
frame.prepend(Point::zero(), Element::Shape(shape)); frame.prepend(Point::zero(), Element::Shape(shape));
@ -133,11 +133,11 @@ impl StrokeNode {}
impl Layout for StrokeNode { impl Layout for StrokeNode {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let mut fragment = self.child.layout(world, styles, regions)?; let mut fragment = self.child.layout(vt, styles, regions)?;
for frame in &mut fragment { for frame in &mut fragment {
let shape = Geometry::Rect(frame.size()).stroked(self.stroke); let shape = Geometry::Rect(frame.size()).stroked(self.stroke);
frame.prepend(Point::zero(), Element::Shape(shape)); frame.prepend(Point::zero(), Element::Shape(shape));

View File

@ -47,7 +47,7 @@ impl<const L: DecoLine> DecoNode<L> {
} }
impl<const L: DecoLine> Show for DecoNode<L> { impl<const L: DecoLine> Show for DecoNode<L> {
fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> Content { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> Content {
self.0.clone().styled( self.0.clone().styled(
TextNode::DECO, TextNode::DECO,
Decoration { Decoration {

View File

@ -62,7 +62,7 @@ impl StrongNode {
} }
impl Show for StrongNode { impl Show for StrongNode {
fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> Content { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> Content {
self.0.clone().styled(TextNode::DELTA, Delta(styles.get(Self::DELTA))) self.0.clone().styled(TextNode::DELTA, Delta(styles.get(Self::DELTA)))
} }
} }
@ -104,7 +104,7 @@ impl EmphNode {
} }
impl Show for EmphNode { impl Show for EmphNode {
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> Content { fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> Content {
self.0.clone().styled(TextNode::EMPH, Toggle) self.0.clone().styled(TextNode::EMPH, Toggle)
} }
} }

View File

@ -43,7 +43,7 @@ impl RawNode {
} }
impl Show for RawNode { impl Show for RawNode {
fn show(&self, _: Tracked<dyn World>, 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 lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase());
let foreground = THEME let foreground = THEME
.settings .settings

View File

@ -81,8 +81,8 @@ impl<'a> ShapedText<'a> {
/// ///
/// The `justification` defines how much extra advance width each /// The `justification` defines how much extra advance width each
/// [justifiable glyph](ShapedGlyph::is_justifiable) will get. /// [justifiable glyph](ShapedGlyph::is_justifiable) will get.
pub fn build(&self, world: Tracked<dyn World>, justification: Abs) -> Frame { pub fn build(&self, vt: &Vt, justification: Abs) -> Frame {
let (top, bottom) = self.measure(world); let (top, bottom) = self.measure(vt);
let size = Size::new(self.width, top + bottom); let size = Size::new(self.width, top + bottom);
let mut offset = Abs::zero(); let mut offset = Abs::zero();
@ -137,7 +137,7 @@ impl<'a> ShapedText<'a> {
} }
/// Measure the top and bottom extent of this text. /// Measure the top and bottom extent of this text.
fn measure(&self, world: Tracked<dyn World>) -> (Abs, Abs) { fn measure(&self, vt: &Vt) -> (Abs, Abs) {
let mut top = Abs::zero(); let mut top = Abs::zero();
let mut bottom = Abs::zero(); let mut bottom = Abs::zero();
@ -154,6 +154,7 @@ impl<'a> ShapedText<'a> {
if self.glyphs.is_empty() { if self.glyphs.is_empty() {
// When there are no glyphs, we just use the vertical metrics of the // When there are no glyphs, we just use the vertical metrics of the
// first available font. // first available font.
let world = vt.world();
for family in families(self.styles) { for family in families(self.styles) {
if let Some(font) = world if let Some(font) = world
.book() .book()
@ -190,11 +191,7 @@ impl<'a> ShapedText<'a> {
/// Reshape a range of the shaped text, reusing information from this /// Reshape a range of the shaped text, reusing information from this
/// shaping process if possible. /// shaping process if possible.
pub fn reshape( pub fn reshape(&'a self, vt: &Vt, text_range: Range<usize>) -> ShapedText<'a> {
&'a self,
world: Tracked<dyn World>,
text_range: Range<usize>,
) -> ShapedText<'a> {
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) { if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
Self { Self {
text: &self.text[text_range], text: &self.text[text_range],
@ -206,13 +203,14 @@ impl<'a> ShapedText<'a> {
glyphs: Cow::Borrowed(glyphs), glyphs: Cow::Borrowed(glyphs),
} }
} else { } 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. /// Push a hyphen to end of the text.
pub fn push_hyphen(&mut self, world: Tracked<dyn World>) { pub fn push_hyphen(&mut self, vt: &Vt) {
families(self.styles).find_map(|family| { families(self.styles).find_map(|family| {
let world = vt.world();
let font = world let font = world
.book() .book()
.select(family, self.variant) .select(family, self.variant)
@ -303,7 +301,7 @@ impl Debug for ShapedText<'_> {
/// Holds shaping results and metadata common to all shaped segments. /// Holds shaping results and metadata common to all shaped segments.
struct ShapingContext<'a> { struct ShapingContext<'a> {
world: Tracked<'a, dyn World>, vt: &'a Vt<'a>,
glyphs: Vec<ShapedGlyph>, glyphs: Vec<ShapedGlyph>,
used: Vec<Font>, used: Vec<Font>,
styles: StyleChain<'a>, styles: StyleChain<'a>,
@ -316,7 +314,7 @@ struct ShapingContext<'a> {
/// Shape text into [`ShapedText`]. /// Shape text into [`ShapedText`].
pub fn shape<'a>( pub fn shape<'a>(
world: Tracked<dyn World>, vt: &Vt,
text: &'a str, text: &'a str,
styles: StyleChain<'a>, styles: StyleChain<'a>,
dir: Dir, dir: Dir,
@ -324,7 +322,7 @@ pub fn shape<'a>(
let size = styles.get(TextNode::SIZE); let size = styles.get(TextNode::SIZE);
let mut ctx = ShapingContext { let mut ctx = ShapingContext {
world, vt,
size, size,
glyphs: vec![], glyphs: vec![],
used: vec![], used: vec![],
@ -365,10 +363,11 @@ fn shape_segment<'a>(
} }
// Find the next available family. // 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| { let mut selection = families.find_map(|family| {
book.select(family, ctx.variant) book.select(family, ctx.variant)
.and_then(|id| ctx.world.font(id)) .and_then(|id| world.font(id))
.filter(|font| !ctx.used.contains(font)) .filter(|font| !ctx.used.contains(font))
}); });
@ -377,7 +376,7 @@ fn shape_segment<'a>(
let first = ctx.used.first().map(Font::info); let first = ctx.used.first().map(Font::info);
selection = book selection = book
.select_fallback(first, ctx.variant, text) .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)); .filter(|font| !ctx.used.contains(font));
} }

View File

@ -43,11 +43,11 @@ impl<const S: ShiftKind> ShiftNode<S> {
} }
impl<const S: ShiftKind> Show for ShiftNode<S> { impl<const S: ShiftKind> Show for ShiftNode<S> {
fn show(&self, world: Tracked<dyn World>, styles: StyleChain) -> Content { fn show(&self, vt: &mut Vt, _: &Content, styles: StyleChain) -> Content {
let mut transformed = None; let mut transformed = None;
if styles.get(Self::TYPOGRAPHIC) { if styles.get(Self::TYPOGRAPHIC) {
if let Some(text) = search_text(&self.0, S) { 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)); transformed = Some(TextNode::packed(text));
} }
} }
@ -85,7 +85,8 @@ fn search_text(content: &Content, mode: ShiftKind) -> Option<EcoString> {
/// Checks whether the first retrievable family contains all code points of the /// Checks whether the first retrievable family contains all code points of the
/// given string. /// given string.
fn is_shapable(world: Tracked<dyn World>, 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() { for family in styles.get(TextNode::FAMILY).0.iter() {
if let Some(font) = world if let Some(font) = world
.book() .book()

View File

@ -40,7 +40,7 @@ impl ImageNode {
impl Layout for ImageNode { impl Layout for ImageNode {
fn layout( fn layout(
&self, &self,
_: Tracked<dyn World>, _: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {

View File

@ -39,7 +39,7 @@ impl LineNode {
impl Layout for LineNode { impl Layout for LineNode {
fn layout( fn layout(
&self, &self,
_: Tracked<dyn World>, _: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {

View File

@ -75,7 +75,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
impl<const S: ShapeKind> Layout for ShapeNode<S> { impl<const S: ShapeKind> Layout for ShapeNode<S> {
fn layout( fn layout(
&self, &self,
world: Tracked<dyn World>, vt: &mut Vt,
styles: StyleChain, styles: StyleChain,
regions: &Regions, regions: &Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
@ -90,7 +90,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
let child = child.clone().padded(inset.map(|side| side.map(Length::from))); let child = child.clone().padded(inset.map(|side| side.map(Length::from)));
let mut pod = Regions::one(regions.first, regions.base, regions.expand); 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 // Relayout with full expansion into square region to make sure
// the result is really a square or circle. // the result is really a square or circle.
@ -106,7 +106,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
pod.first = Size::splat(length); pod.first = Size::splat(length);
pod.expand = Axes::splat(true); pod.expand = Axes::splat(true);
frame = child.layout(world, styles, &pod)?.into_frame(); frame = child.layout(vt, styles, &pod)?.into_frame();
} }
} else { } else {
// The default size that a shape takes on if it has no child and // The default size that a shape takes on if it has no child and

View File

@ -2,15 +2,13 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use comemo::Tracked;
use once_cell::sync::OnceCell; 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::diag::SourceResult;
use crate::doc::Document; use crate::doc::Document;
use crate::geom::{Abs, Dir}; use crate::geom::{Abs, Dir};
use crate::util::{hash128, EcoString}; use crate::util::{hash128, EcoString};
use crate::World;
/// Definition of Typst's standard library. /// Definition of Typst's standard library.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
@ -27,11 +25,8 @@ pub struct Library {
#[derive(Clone)] #[derive(Clone)]
pub struct LangItems { pub struct LangItems {
/// The root layout function. /// The root layout function.
pub layout: fn( pub layout:
world: Tracked<dyn World>, fn(vt: &mut Vt, content: &Content, styles: StyleChain) -> SourceResult<Document>,
content: &Content,
styles: StyleChain,
) -> SourceResult<Document>,
/// Access the em size. /// Access the em size.
pub em: fn(StyleChain) -> Abs, pub em: fn(StyleChain) -> Abs,
/// Access the text direction. /// Access the text direction.

View File

@ -1,8 +1,5 @@
use comemo::Tracked; use super::{capability, Content, NodeId, Recipe, Selector, StyleChain, Vt};
use super::{capability, Content, NodeId, Recipe, Selector, StyleChain};
use crate::diag::SourceResult; use crate::diag::SourceResult;
use crate::World;
/// Whether the target is affected by show rules in the given style chain. /// Whether the target is affected by show rules in the given style chain.
pub fn applicable(target: &Content, styles: StyleChain) -> bool { 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. /// Apply the show rules in the given style chain to a target.
pub fn realize( pub fn realize(
world: Tracked<dyn World>, vt: &mut Vt,
target: &Content, target: &Content,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Option<Content>> { ) -> SourceResult<Option<Content>> {
@ -34,7 +31,7 @@ pub fn realize(
for recipe in styles.recipes() { for recipe in styles.recipes() {
let guard = Guard::Nth(n); let guard = Guard::Nth(n);
if recipe.applicable(target) && !target.is_guarded(guard) { 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); realized = Some(content);
break; break;
} }
@ -46,7 +43,7 @@ pub fn realize(
if let Some(showable) = target.with::<dyn Show>() { if let Some(showable) = target.with::<dyn Show>() {
let guard = Guard::Base(target.id()); let guard = Guard::Base(target.id());
if realized.is_none() && !target.is_guarded(guard) { 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. /// Try to apply a recipe to the target.
fn try_apply( fn try_apply(
world: Tracked<dyn World>, vt: &Vt,
target: &Content, target: &Content,
recipe: &Recipe, recipe: &Recipe,
guard: Guard, guard: Guard,
@ -75,7 +72,7 @@ fn try_apply(
return Ok(None); 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)) => { Some(Selector::Label(label)) => {
@ -83,7 +80,7 @@ fn try_apply(
return Ok(None); 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)) => { Some(Selector::Regex(regex)) => {
@ -107,7 +104,7 @@ fn try_apply(
} }
let piece = make(m.as_str().into()).guarded(guard); 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); result.push(transformed);
cursor = m.end(); cursor = m.end();
} }
@ -131,7 +128,7 @@ fn try_apply(
#[capability] #[capability]
pub trait Show { pub trait Show {
/// Execute the base recipe for this node. /// Execute the base recipe for this node.
fn show(&self, world: Tracked<dyn World>, styles: StyleChain) -> Content; fn show(&self, vt: &mut Vt, this: &Content, styles: StyleChain) -> Content;
} }
/// Post-process a node after it was realized. /// Post-process a node after it was realized.

View File

@ -10,5 +10,23 @@ use crate::World;
pub fn typeset(world: Tracked<dyn World>, content: &Content) -> SourceResult<Document> { pub fn typeset(world: Tracked<dyn World>, content: &Content) -> SourceResult<Document> {
let library = world.library(); let library = world.library();
let styles = StyleChain::new(&library.styles); 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
}
} }

View File

@ -7,7 +7,7 @@ edition = "2021"
[dev-dependencies] [dev-dependencies]
typst = { path = ".." } typst = { path = ".." }
typst-library = { path = "../library" } typst-library = { path = "../library" }
comemo = "0.1" comemo = { git = "https://github.com/typst/comemo" }
elsa = "1.7" elsa = "1.7"
iai = { git = "https://github.com/reknih/iai" } iai = { git = "https://github.com/reknih/iai" }
once_cell = "1" once_cell = "1"