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]]
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",

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -34,7 +34,7 @@ impl 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()
}
}

View File

@ -78,7 +78,7 @@ impl<const L: ListKind> ListNode<L> {
impl<const L: ListKind> Layout for ListNode<L> {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> SourceResult<Fragment> {
@ -104,7 +104,7 @@ impl<const L: ListKind> Layout for ListNode<L> {
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<const L: ListKind> Layout for ListNode<L> {
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<dyn World>,
vt: &Vt,
kind: ListKind,
number: usize,
) -> SourceResult<Content> {
@ -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()
}
})
}

View File

@ -53,7 +53,7 @@ impl TableNode {
impl Layout for TableNode {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> SourceResult<Fragment> {
@ -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<T> {
impl<T: Cast + Clone> Celled<T> {
/// Resolve the value based on the cell position.
pub fn resolve(
&self,
world: Tracked<dyn World>,
x: usize,
y: usize,
) -> SourceResult<T> {
pub fn resolve(&self, vt: &Vt, x: usize, y: usize) -> SourceResult<T> {
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)?
}
})
}

View File

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

View File

@ -29,7 +29,7 @@ impl AlignNode {
impl Layout for AlignNode {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> SourceResult<Fragment> {
@ -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.

View File

@ -29,14 +29,14 @@ impl ColumnsNode {
impl Layout for ColumnsNode {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> SourceResult<Fragment> {
// 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);

View File

@ -23,7 +23,7 @@ impl BoxNode {
impl Layout for BoxNode {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> SourceResult<Fragment> {
@ -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<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> 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 {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> SourceResult<Fragment> {
@ -33,9 +33,9 @@ impl Layout for FlowNode {
} else if let Some(node) = child.to::<ParNode>() {
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::<dyn Layout>() {
layouter.layout_block(world, child, styles)?;
layouter.layout_block(vt, child, styles)?;
} else if child.is::<ColbreakNode>() {
layouter.finish_region(false);
} else {
@ -122,16 +122,23 @@ impl FlowLayouter {
/// Layout a paragraph.
fn layout_par(
&mut self,
world: Tracked<dyn World>,
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<dyn World>,
vt: &mut Vt,
block: &Content,
styles: StyleChain,
) -> SourceResult<()> {
@ -159,7 +166,7 @@ impl FlowLayouter {
// aligned later.
if let Some(placed) = block.to::<PlaceNode>() {
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));
}

View File

@ -36,13 +36,13 @@ impl GridNode {
impl Layout for GridNode {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> SourceResult<Fragment> {
// 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);
}

View File

@ -14,11 +14,11 @@ impl HideNode {
impl Layout for HideNode {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> 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 {
frame.clear();
}

View File

@ -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<dyn World>,
styles: StyleChain,
) -> SourceResult<Document>;
fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document>;
}
impl LayoutRoot for Content {
#[comemo::memoize]
fn layout_root(
&self,
world: Tracked<dyn World>,
styles: StyleChain,
) -> SourceResult<Document> {
let scratch = Scratch::default();
let (realized, styles) = realize_root(world, &scratch, self, styles)?;
realized.with::<dyn LayoutRoot>().unwrap().layout_root(world, styles)
fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document> {
#[comemo::memoize]
fn cached(
node: &Content,
world: comemo::Tracked<dyn World>,
styles: StyleChain,
) -> SourceResult<Document> {
let mut vt = Vt { world };
let scratch = Scratch::default();
let (realized, styles) = realize_root(&mut vt, &scratch, node, 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.
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> SourceResult<Fragment>;
}
impl Layout for Content {
#[comemo::memoize]
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> SourceResult<Fragment> {
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::<dyn Layout>().unwrap().layout(world, styles, regions)
#[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 (realized, styles) = realize_block(&mut vt, &scratch, node, styles)?;
let barrier = Style::Barrier(realized.id());
let styles = styles.chain_one(&barrier);
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.
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<Content>,
}
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);
}

View File

@ -28,14 +28,14 @@ impl PadNode {
impl Layout for PadNode {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> SourceResult<Fragment> {
// 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

View File

@ -57,7 +57,7 @@ impl PageNode {
/// Layout the page run into a sequence of frames, one per page.
pub fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
mut page: usize,
styles: StyleChain,
) -> SourceResult<Fragment> {
@ -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, &regions)?;
let mut fragment = child.layout(vt, styles, &regions)?;
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<dyn World>,
page: usize,
) -> SourceResult<Option<Content>> {
pub fn resolve(&self, vt: &Vt, page: usize) -> SourceResult<Option<Content>> {
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())
}
})
}

View File

@ -44,27 +44,43 @@ impl ParNode {
impl ParNode {
/// Layout the paragraph into a collection of lines.
#[comemo::memoize]
pub fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
consecutive: bool,
width: Abs,
base: Size,
expand: bool,
) -> SourceResult<Fragment> {
// Collect all text into one string for BiDi analysis.
let (text, segments) = collect(self, &styles, consecutive);
#[comemo::memoize]
fn cached(
par: &ParNode,
world: Tracked<dyn World>,
styles: StyleChain,
consecutive: bool,
width: Abs,
base: Size,
expand: bool,
) -> SourceResult<Fragment> {
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<Preparation<'a>> {
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::<RepeatNode>() {
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<Item<'a>>,
world: Tracked<dyn World>,
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<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(|| {
if p.styles.get(ParNode::JUSTIFY) {
Linebreaks::Optimized
@ -611,22 +625,22 @@ fn linebreak<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
});
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<Line<'a>> {
fn linebreak_simple<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
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<Line<'a>> {
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<Line<'a>> {
/// computed and stored in dynamic programming table) is minimal. The final
/// result is simply the layout determined for the last breakpoint at the end of
/// text.
fn linebreak_optimized<'a>(p: &'a Preparation<'a>, 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.
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 {
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<Line<'a>>
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<Line<'a>>
/// 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<Fragment> {
// 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::<SourceResult<_>>()
.map(Fragment::frames)
}
/// 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 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;

View File

@ -19,7 +19,7 @@ impl PlaceNode {
impl Layout for PlaceNode {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> SourceResult<Fragment> {
@ -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.

View File

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

View File

@ -27,11 +27,11 @@ impl MoveNode {
impl Layout for MoveNode {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> 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 {
let delta = self.delta.resolve(styles);
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> {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> 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 {
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));

View File

@ -30,7 +30,7 @@ impl 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();
map.set_family(FontFamily::new("NewComputerModernMath"), styles);
@ -51,11 +51,11 @@ impl Show for MathNode {
impl Layout for MathNode {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
_: &Regions,
) -> 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.
pub fn layout_tex(
world: Tracked<dyn World>,
vt: &Vt,
tex: &str,
display: bool,
styles: StyleChain,
) -> SourceResult<Fragment> {
// 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));

View File

@ -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<dyn World>,
styles: StyleChain,
) -> SourceResult<Document> {
fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document> {
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);
}

View File

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

View File

@ -20,7 +20,7 @@ impl 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))
}
}

View File

@ -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};

View File

@ -105,11 +105,11 @@ impl FillNode {}
impl Layout for FillNode {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> 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 {
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<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> 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 {
let shape = Geometry::Rect(frame.size()).stroked(self.stroke);
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> {
fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> Content {
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> Content {
self.0.clone().styled(
TextNode::DECO,
Decoration {

View File

@ -62,7 +62,7 @@ impl 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)))
}
}
@ -104,7 +104,7 @@ impl 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)
}
}

View File

@ -43,7 +43,7 @@ impl 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 foreground = THEME
.settings

View File

@ -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<dyn World>, 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<dyn World>) -> (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<dyn World>,
text_range: Range<usize>,
) -> ShapedText<'a> {
pub fn reshape(&'a self, vt: &Vt, text_range: Range<usize>) -> 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<dyn World>) {
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<ShapedGlyph>,
used: Vec<Font>,
styles: StyleChain<'a>,
@ -316,7 +314,7 @@ struct ShapingContext<'a> {
/// Shape text into [`ShapedText`].
pub fn shape<'a>(
world: Tracked<dyn World>,
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));
}

View File

@ -43,11 +43,11 @@ impl<const S: ShiftKind> 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;
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<EcoString> {
/// Checks whether the first retrievable family contains all code points of the
/// 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() {
if let Some(font) = world
.book()

View File

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

View File

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

View File

@ -75,7 +75,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
impl<const S: ShapeKind> Layout for ShapeNode<S> {
fn layout(
&self,
world: Tracked<dyn World>,
vt: &mut Vt,
styles: StyleChain,
regions: &Regions,
) -> 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 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<const S: ShapeKind> Layout for ShapeNode<S> {
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

View File

@ -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<dyn World>,
content: &Content,
styles: StyleChain,
) -> SourceResult<Document>,
pub layout:
fn(vt: &mut Vt, content: &Content, styles: StyleChain) -> SourceResult<Document>,
/// Access the em size.
pub em: fn(StyleChain) -> Abs,
/// Access the text direction.

View File

@ -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<dyn World>,
vt: &mut Vt,
target: &Content,
styles: StyleChain,
) -> SourceResult<Option<Content>> {
@ -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::<dyn Show>() {
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<dyn World>,
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<dyn World>, styles: StyleChain) -> Content;
fn show(&self, vt: &mut Vt, this: &Content, styles: StyleChain) -> Content;
}
/// 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> {
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
}
}

View File

@ -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"