Introduce virtual typesetter
This commit is contained in:
parent
33ab1fdbdd
commit
5110a41de1
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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)?
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use comemo::Track;
|
||||
|
||||
use typst::model;
|
||||
use typst::syntax::Source;
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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, ®ions)?;
|
||||
let mut fragment = child.layout(vt, styles, ®ions)?;
|
||||
|
||||
let header = styles.get(Self::HEADER);
|
||||
let footer = styles.get(Self::FOOTER);
|
||||
@ -116,9 +116,9 @@ impl PageNode {
|
||||
(foreground, Point::zero(), size),
|
||||
(background, Point::zero(), size),
|
||||
] {
|
||||
if let Some(content) = marginal.resolve(world, page)? {
|
||||
if let Some(content) = marginal.resolve(vt, page)? {
|
||||
let pod = Regions::one(area, area, Axes::splat(true));
|
||||
let sub = content.layout(world, styles, &pod)?.into_frame();
|
||||
let sub = content.layout(vt, styles, &pod)?.into_frame();
|
||||
if std::ptr::eq(marginal, background) {
|
||||
frame.prepend_frame(pos, sub);
|
||||
} else {
|
||||
@ -169,17 +169,13 @@ pub enum Marginal {
|
||||
|
||||
impl Marginal {
|
||||
/// Resolve the marginal based on the page number.
|
||||
pub fn resolve(
|
||||
&self,
|
||||
world: Tracked<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())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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));
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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));
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -40,7 +40,7 @@ impl ImageNode {
|
||||
impl Layout for ImageNode {
|
||||
fn layout(
|
||||
&self,
|
||||
_: Tracked<dyn World>,
|
||||
_: &mut Vt,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
|
@ -39,7 +39,7 @@ impl LineNode {
|
||||
impl Layout for LineNode {
|
||||
fn layout(
|
||||
&self,
|
||||
_: Tracked<dyn World>,
|
||||
_: &mut Vt,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user