mirror of
https://github.com/typst/typst
synced 2025-08-12 06:07:54 +08:00
Fallible layout
This commit is contained in:
parent
c5e67af22b
commit
35610a8c6a
@ -4,12 +4,14 @@ use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{StyleChain, Template};
|
||||
use crate::diag::TypResult;
|
||||
use crate::util::Prehashed;
|
||||
use crate::Vm;
|
||||
|
||||
/// A node that can be realized given some styles.
|
||||
pub trait Show {
|
||||
/// Realize the template in the given styles.
|
||||
fn show(&self, styles: StyleChain) -> Template;
|
||||
fn show(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Template>;
|
||||
|
||||
/// Convert to a packed show node.
|
||||
fn pack(self) -> ShowNode
|
||||
@ -40,8 +42,8 @@ impl ShowNode {
|
||||
}
|
||||
|
||||
impl Show for ShowNode {
|
||||
fn show(&self, styles: StyleChain) -> Template {
|
||||
self.0.show(styles)
|
||||
fn show(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Template> {
|
||||
self.0.show(vm, styles)
|
||||
}
|
||||
|
||||
fn pack(self) -> ShowNode {
|
||||
|
@ -165,20 +165,22 @@ impl Template {
|
||||
}
|
||||
|
||||
/// Layout this template into a collection of pages.
|
||||
pub fn layout(&self, vm: &mut Vm) -> Vec<Arc<Frame>> {
|
||||
pub fn layout(&self, vm: &mut Vm) -> TypResult<Vec<Arc<Frame>>> {
|
||||
let style_arena = Arena::new();
|
||||
let template_arena = Arena::new();
|
||||
|
||||
let mut builder = Builder::new(&style_arena, &template_arena, true);
|
||||
let chain = StyleChain::new(vm.styles);
|
||||
builder.process(self, chain);
|
||||
builder.process(self, vm, chain)?;
|
||||
builder.finish_page(true, false, chain);
|
||||
|
||||
let mut frames = vec![];
|
||||
let (pages, shared) = builder.pages.unwrap().finish();
|
||||
pages
|
||||
.iter()
|
||||
.flat_map(|(page, map)| page.layout(vm, map.chain(&shared)))
|
||||
.collect()
|
||||
for (page, map) in pages.iter() {
|
||||
frames.extend(page.layout(vm, map.chain(&shared))?);
|
||||
}
|
||||
|
||||
Ok(frames)
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,12 +271,12 @@ impl Layout for Template {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
let style_arena = Arena::new();
|
||||
let template_arena = Arena::new();
|
||||
|
||||
let mut builder = Builder::new(&style_arena, &template_arena, false);
|
||||
builder.process(self, styles);
|
||||
builder.process(self, vm, styles)?;
|
||||
builder.finish_par(styles);
|
||||
|
||||
let (flow, shared) = builder.flow.finish();
|
||||
@ -323,7 +325,12 @@ impl<'a> Builder<'a> {
|
||||
}
|
||||
|
||||
/// Process a template.
|
||||
fn process(&mut self, template: &'a Template, styles: StyleChain<'a>) {
|
||||
fn process(
|
||||
&mut self,
|
||||
template: &'a Template,
|
||||
vm: &mut Vm,
|
||||
styles: StyleChain<'a>,
|
||||
) -> TypResult<()> {
|
||||
match template {
|
||||
Template::Space => {
|
||||
self.par.weak(ParChild::Text(' '.into()), 0, styles);
|
||||
@ -382,8 +389,9 @@ impl<'a> Builder<'a> {
|
||||
}
|
||||
}
|
||||
Template::Show(node) => {
|
||||
let template = self.template_arena.alloc(node.show(styles));
|
||||
self.process(template, styles.unscoped(node.id()));
|
||||
let template = node.show(vm, styles)?;
|
||||
let stored = self.template_arena.alloc(template);
|
||||
self.process(stored, vm, styles.unscoped(node.id()))?;
|
||||
}
|
||||
Template::Styled(styled) => {
|
||||
let (sub, map) = styled.as_ref();
|
||||
@ -397,7 +405,7 @@ impl<'a> Builder<'a> {
|
||||
None => {}
|
||||
}
|
||||
|
||||
self.process(sub, styles);
|
||||
self.process(sub, vm, styles)?;
|
||||
|
||||
match interruption {
|
||||
Some(Interruption::Page) => self.finish_page(true, false, styles),
|
||||
@ -407,10 +415,12 @@ impl<'a> Builder<'a> {
|
||||
}
|
||||
Template::Sequence(seq) => {
|
||||
for sub in seq.iter() {
|
||||
self.process(sub, styles);
|
||||
self.process(sub, vm, styles)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Finish the currently built paragraph.
|
||||
|
@ -15,6 +15,7 @@ use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::diag::TypResult;
|
||||
use crate::eval::StyleChain;
|
||||
use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
|
||||
use crate::geom::{Align, Linear, Paint, Point, Sides, Size, Spec, Transform};
|
||||
@ -33,7 +34,7 @@ pub trait Layout {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>>;
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>>;
|
||||
|
||||
/// Convert to a packed node.
|
||||
fn pack(self) -> LayoutNode
|
||||
@ -137,7 +138,7 @@ impl Layout for LayoutNode {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
let styles = styles.barred(self.id());
|
||||
|
||||
#[cfg(not(feature = "layout-cache"))]
|
||||
@ -155,10 +156,10 @@ impl Layout for LayoutNode {
|
||||
// #[track_caller] annotation doesn't work.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
if let Some(frames) = vm.layout_cache.get(hash, regions) {
|
||||
frames
|
||||
Ok(frames)
|
||||
} else {
|
||||
vm.level += 1;
|
||||
let frames = self.0.layout(vm, regions, styles);
|
||||
let frames = self.0.layout(vm, regions, styles)?;
|
||||
vm.level -= 1;
|
||||
|
||||
let entry = FramesEntry::new(frames.clone(), vm.level);
|
||||
@ -175,7 +176,7 @@ impl Layout for LayoutNode {
|
||||
}
|
||||
|
||||
vm.layout_cache.insert(hash, entry);
|
||||
frames
|
||||
Ok(frames)
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,11 +249,11 @@ impl Layout for EmptyNode {
|
||||
_: &mut Vm,
|
||||
regions: &Regions,
|
||||
_: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
let size = regions.expand.select(regions.current, Size::zero());
|
||||
let mut cts = Constraints::new(regions.expand);
|
||||
cts.exact = regions.current.filter(regions.expand);
|
||||
vec![Frame::new(size).constrain(cts)]
|
||||
Ok(vec![Frame::new(size).constrain(cts)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,7 +272,7 @@ impl Layout for SizedNode {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
let is_auto = self.sizing.map_is_none();
|
||||
let is_rel = self.sizing.map(|s| s.map_or(false, Linear::is_relative));
|
||||
|
||||
@ -292,7 +293,7 @@ impl Layout for SizedNode {
|
||||
Regions::one(size, base, expand)
|
||||
};
|
||||
|
||||
let mut frames = self.child.layout(vm, &pod, styles);
|
||||
let mut frames = self.child.layout(vm, &pod, styles)?;
|
||||
let Constrained { item: frame, cts } = &mut frames[0];
|
||||
|
||||
// Ensure frame size matches regions size if expansion is on.
|
||||
@ -306,7 +307,7 @@ impl Layout for SizedNode {
|
||||
cts.exact = regions.current.filter(regions.expand | is_auto);
|
||||
cts.base = regions.base.filter(is_rel | is_auto);
|
||||
|
||||
frames
|
||||
Ok(frames)
|
||||
}
|
||||
}
|
||||
|
||||
@ -325,13 +326,13 @@ impl Layout for FillNode {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
let mut frames = self.child.layout(vm, regions, styles);
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
let mut frames = self.child.layout(vm, regions, styles)?;
|
||||
for Constrained { item: frame, .. } in &mut frames {
|
||||
let shape = Shape::filled(Geometry::Rect(frame.size), self.fill);
|
||||
Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
|
||||
}
|
||||
frames
|
||||
Ok(frames)
|
||||
}
|
||||
}
|
||||
|
||||
@ -350,12 +351,12 @@ impl Layout for StrokeNode {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
let mut frames = self.child.layout(vm, regions, styles);
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
let mut frames = self.child.layout(vm, regions, styles)?;
|
||||
for Constrained { item: frame, .. } in &mut frames {
|
||||
let shape = Shape::stroked(Geometry::Rect(frame.size), self.stroke);
|
||||
Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
|
||||
}
|
||||
frames
|
||||
Ok(frames)
|
||||
}
|
||||
}
|
||||
|
@ -283,9 +283,7 @@ impl<'a> Vm<'a> {
|
||||
/// diagnostics in the form of a vector of error message with file and span
|
||||
/// information.
|
||||
pub fn typeset(&mut self, id: SourceId) -> TypResult<Vec<Arc<Frame>>> {
|
||||
let module = self.evaluate(id)?;
|
||||
let frames = module.template.layout(self);
|
||||
Ok(frames)
|
||||
self.evaluate(id)?.template.layout(self)
|
||||
}
|
||||
|
||||
/// Resolve a user-entered path (relative to the source file) to be
|
||||
|
@ -27,7 +27,7 @@ impl Layout for AlignNode {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
// The child only needs to expand along an axis if there's no alignment.
|
||||
let mut pod = regions.clone();
|
||||
pod.expand &= self.aligns.map_is_none();
|
||||
@ -39,7 +39,7 @@ impl Layout for AlignNode {
|
||||
}
|
||||
|
||||
// Layout the child.
|
||||
let mut frames = self.child.layout(vm, &pod, passed.chain(&styles));
|
||||
let mut frames = self.child.layout(vm, &pod, passed.chain(&styles))?;
|
||||
|
||||
for ((current, base), Constrained { item: frame, cts }) in
|
||||
regions.iter().zip(&mut frames)
|
||||
@ -57,7 +57,7 @@ impl Layout for AlignNode {
|
||||
cts.exact = current.filter(regions.expand | cts.exact.map_is_some());
|
||||
}
|
||||
|
||||
frames
|
||||
Ok(frames)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ impl Layout for ColumnsNode {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
// Separating the infinite space into infinite columns does not make
|
||||
// much sense.
|
||||
if regions.current.x.is_infinite() {
|
||||
@ -59,7 +59,7 @@ impl Layout for ColumnsNode {
|
||||
};
|
||||
|
||||
// Layout the children.
|
||||
let mut frames = self.child.layout(vm, &pod, styles).into_iter();
|
||||
let mut frames = self.child.layout(vm, &pod, styles)?.into_iter();
|
||||
|
||||
let dir = styles.get(ParNode::DIR);
|
||||
let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize;
|
||||
@ -102,7 +102,7 @@ impl Layout for ColumnsNode {
|
||||
finished.push(output.constrain(cts));
|
||||
}
|
||||
|
||||
finished
|
||||
Ok(finished)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,15 +32,15 @@ impl<const L: DecoLine> DecoNode<L> {
|
||||
}
|
||||
|
||||
impl<const L: DecoLine> Show for DecoNode<L> {
|
||||
fn show(&self, styles: StyleChain) -> Template {
|
||||
self.0.clone().styled(TextNode::LINES, vec![Decoration {
|
||||
fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> {
|
||||
Ok(self.0.clone().styled(TextNode::LINES, vec![Decoration {
|
||||
line: L,
|
||||
stroke: styles.get(Self::STROKE),
|
||||
thickness: styles.get(Self::THICKNESS),
|
||||
offset: styles.get(Self::OFFSET),
|
||||
extent: styles.get(Self::EXTENT),
|
||||
evade: styles.get(Self::EVADE),
|
||||
}])
|
||||
}]))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ impl Layout for FlowNode {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
let mut layouter = FlowLayouter::new(regions);
|
||||
|
||||
for (child, map) in self.0.iter() {
|
||||
@ -56,12 +56,12 @@ impl Layout for FlowNode {
|
||||
layouter.layout_spacing(*kind);
|
||||
}
|
||||
FlowChild::Node(ref node) => {
|
||||
layouter.layout_node(vm, node, styles);
|
||||
layouter.layout_node(vm, node, styles)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layouter.finish()
|
||||
Ok(layouter.finish())
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,7 +161,12 @@ impl FlowLayouter {
|
||||
}
|
||||
|
||||
/// Layout a node.
|
||||
pub fn layout_node(&mut self, vm: &mut Vm, node: &LayoutNode, styles: StyleChain) {
|
||||
pub fn layout_node(
|
||||
&mut self,
|
||||
vm: &mut Vm,
|
||||
node: &LayoutNode,
|
||||
styles: StyleChain,
|
||||
) -> TypResult<()> {
|
||||
// Don't even try layouting into a full region.
|
||||
if self.regions.is_full() {
|
||||
self.finish_region();
|
||||
@ -171,9 +176,9 @@ impl FlowLayouter {
|
||||
// aligned later.
|
||||
if let Some(placed) = node.downcast::<PlaceNode>() {
|
||||
if placed.out_of_flow() {
|
||||
let frame = node.layout(vm, &self.regions, styles).remove(0);
|
||||
let frame = node.layout(vm, &self.regions, styles)?.remove(0);
|
||||
self.items.push(FlowItem::Placed(frame.item));
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,7 +193,7 @@ impl FlowLayouter {
|
||||
.unwrap_or(Align::Top),
|
||||
);
|
||||
|
||||
let frames = node.layout(vm, &self.regions, styles);
|
||||
let frames = node.layout(vm, &self.regions, styles)?;
|
||||
let len = frames.len();
|
||||
for (i, frame) in frames.into_iter().enumerate() {
|
||||
// Grow our size, shrink the region and save the frame for later.
|
||||
@ -202,6 +207,8 @@ impl FlowLayouter {
|
||||
self.finish_region();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Finish the frame for one region.
|
||||
|
@ -38,7 +38,7 @@ impl Layout for GridNode {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
// Prepare grid layout by unifying content and gutter tracks.
|
||||
let layouter = GridLayouter::new(
|
||||
self.tracks.as_deref(),
|
||||
@ -48,7 +48,7 @@ impl Layout for GridNode {
|
||||
styles,
|
||||
);
|
||||
|
||||
// Measure the columsna nd layout the grid row-by-row.
|
||||
// Measure the columns and layout the grid row-by-row.
|
||||
layouter.layout(vm)
|
||||
}
|
||||
}
|
||||
@ -205,19 +205,19 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
/// Determines the columns sizes and then layouts the grid row-by-row.
|
||||
pub fn layout(mut self, vm: &mut Vm) -> Vec<Constrained<Arc<Frame>>> {
|
||||
self.measure_columns(vm);
|
||||
pub fn layout(mut self, vm: &mut Vm) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
self.measure_columns(vm)?;
|
||||
|
||||
for y in 0 .. self.rows.len() {
|
||||
// Skip to next region if current one is full, but only for content
|
||||
// rows, not for gutter rows.
|
||||
if y % 2 == 0 && self.regions.is_full() {
|
||||
self.finish_region(vm);
|
||||
self.finish_region(vm)?;
|
||||
}
|
||||
|
||||
match self.rows[y] {
|
||||
TrackSizing::Auto => self.layout_auto_row(vm, y),
|
||||
TrackSizing::Linear(v) => self.layout_linear_row(vm, v, y),
|
||||
TrackSizing::Auto => self.layout_auto_row(vm, y)?,
|
||||
TrackSizing::Linear(v) => self.layout_linear_row(vm, v, y)?,
|
||||
TrackSizing::Fractional(v) => {
|
||||
self.cts.exact.y = Some(self.full);
|
||||
self.lrows.push(Row::Fr(v, y));
|
||||
@ -226,12 +226,12 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
self.finish_region(vm);
|
||||
self.finished
|
||||
self.finish_region(vm)?;
|
||||
Ok(self.finished)
|
||||
}
|
||||
|
||||
/// Determine all column sizes.
|
||||
fn measure_columns(&mut self, vm: &mut Vm) {
|
||||
fn measure_columns(&mut self, vm: &mut Vm) -> TypResult<()> {
|
||||
enum Case {
|
||||
/// The column sizing is only determined by specified linear sizes.
|
||||
PurelyLinear,
|
||||
@ -277,7 +277,7 @@ impl<'a> GridLayouter<'a> {
|
||||
let available = self.regions.current.x - linear;
|
||||
if available >= Length::zero() {
|
||||
// Determine size of auto columns.
|
||||
let (auto, count) = self.measure_auto_columns(vm, available);
|
||||
let (auto, count) = self.measure_auto_columns(vm, available)?;
|
||||
|
||||
// If there is remaining space, distribute it to fractional columns,
|
||||
// otherwise shrink auto columns.
|
||||
@ -308,6 +308,8 @@ impl<'a> GridLayouter<'a> {
|
||||
|
||||
// Sum up the resolved column sizes once here.
|
||||
self.used.x = self.rcols.iter().sum();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Measure the size that is available to auto columns.
|
||||
@ -315,7 +317,7 @@ impl<'a> GridLayouter<'a> {
|
||||
&mut self,
|
||||
vm: &mut Vm,
|
||||
available: Length,
|
||||
) -> (Length, usize) {
|
||||
) -> TypResult<(Length, usize)> {
|
||||
let mut auto = Length::zero();
|
||||
let mut count = 0;
|
||||
|
||||
@ -340,7 +342,7 @@ impl<'a> GridLayouter<'a> {
|
||||
pod.base.y = v.resolve(self.regions.base.y);
|
||||
}
|
||||
|
||||
let frame = node.layout(vm, &pod, self.styles).remove(0).item;
|
||||
let frame = node.layout(vm, &pod, self.styles)?.remove(0).item;
|
||||
resolved.set_max(frame.size.x);
|
||||
}
|
||||
}
|
||||
@ -350,7 +352,7 @@ impl<'a> GridLayouter<'a> {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
(auto, count)
|
||||
Ok((auto, count))
|
||||
}
|
||||
|
||||
/// Distribute remaining space to fractional columns.
|
||||
@ -394,7 +396,7 @@ impl<'a> GridLayouter<'a> {
|
||||
|
||||
/// Layout a row with automatic height. Such a row may break across multiple
|
||||
/// regions.
|
||||
fn layout_auto_row(&mut self, vm: &mut Vm, y: usize) {
|
||||
fn layout_auto_row(&mut self, vm: &mut Vm, y: usize) -> TypResult<()> {
|
||||
let mut resolved: Vec<Length> = vec![];
|
||||
|
||||
// Determine the size for each region of the row.
|
||||
@ -409,7 +411,7 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
let mut sizes = node
|
||||
.layout(vm, &pod, self.styles)
|
||||
.layout(vm, &pod, self.styles)?
|
||||
.into_iter()
|
||||
.map(|frame| frame.item.size.y);
|
||||
|
||||
@ -427,14 +429,14 @@ impl<'a> GridLayouter<'a> {
|
||||
|
||||
// Nothing to layout.
|
||||
if resolved.is_empty() {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Layout into a single region.
|
||||
if let &[first] = resolved.as_slice() {
|
||||
let frame = self.layout_single_row(vm, first, y);
|
||||
let frame = self.layout_single_row(vm, first, y)?;
|
||||
self.push_row(frame);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Expand all but the last region if the space is not
|
||||
@ -449,36 +451,40 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
// Layout into multiple regions.
|
||||
let frames = self.layout_multi_row(vm, &resolved, y);
|
||||
let frames = self.layout_multi_row(vm, &resolved, y)?;
|
||||
let len = frames.len();
|
||||
for (i, frame) in frames.into_iter().enumerate() {
|
||||
self.push_row(frame);
|
||||
if i + 1 < len {
|
||||
self.cts.exact.y = Some(self.full);
|
||||
self.finish_region(vm);
|
||||
self.finish_region(vm)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Layout a row with linear height. Such a row cannot break across multiple
|
||||
/// regions, but it may force a region break.
|
||||
fn layout_linear_row(&mut self, vm: &mut Vm, v: Linear, y: usize) {
|
||||
fn layout_linear_row(&mut self, vm: &mut Vm, v: Linear, y: usize) -> TypResult<()> {
|
||||
let resolved = v.resolve(self.regions.base.y);
|
||||
let frame = self.layout_single_row(vm, resolved, y);
|
||||
let frame = self.layout_single_row(vm, resolved, y)?;
|
||||
|
||||
// Skip to fitting region.
|
||||
let height = frame.size.y;
|
||||
while !self.regions.current.y.fits(height) && !self.regions.in_last() {
|
||||
self.cts.max.y = Some(self.used.y + height);
|
||||
self.finish_region(vm);
|
||||
self.finish_region(vm)?;
|
||||
|
||||
// Don't skip multiple regions for gutter and don't push a row.
|
||||
if y % 2 == 1 {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
self.push_row(frame);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Layout a row with fixed height and return its frame.
|
||||
@ -487,7 +493,7 @@ impl<'a> GridLayouter<'a> {
|
||||
vm: &mut Vm,
|
||||
height: Length,
|
||||
y: usize,
|
||||
) -> Frame {
|
||||
) -> TypResult<Frame> {
|
||||
let mut output = Frame::new(Size::new(self.used.x, height));
|
||||
let mut pos = Point::zero();
|
||||
|
||||
@ -502,14 +508,14 @@ impl<'a> GridLayouter<'a> {
|
||||
.select(self.regions.base, size);
|
||||
|
||||
let pod = Regions::one(size, base, Spec::splat(true));
|
||||
let frame = node.layout(vm, &pod, self.styles).remove(0);
|
||||
let frame = node.layout(vm, &pod, self.styles)?.remove(0);
|
||||
output.push_frame(pos, frame.item);
|
||||
}
|
||||
|
||||
pos.x += rcol;
|
||||
}
|
||||
|
||||
output
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Layout a row spanning multiple regions.
|
||||
@ -518,7 +524,7 @@ impl<'a> GridLayouter<'a> {
|
||||
vm: &mut Vm,
|
||||
heights: &[Length],
|
||||
y: usize,
|
||||
) -> Vec<Frame> {
|
||||
) -> TypResult<Vec<Frame>> {
|
||||
// Prepare frames.
|
||||
let mut outputs: Vec<_> = heights
|
||||
.iter()
|
||||
@ -542,7 +548,7 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
// Push the layouted frames into the individual output frames.
|
||||
let frames = node.layout(vm, &pod, self.styles);
|
||||
let frames = node.layout(vm, &pod, self.styles)?;
|
||||
for (output, frame) in outputs.iter_mut().zip(frames) {
|
||||
output.push_frame(pos, frame.item);
|
||||
}
|
||||
@ -551,7 +557,7 @@ impl<'a> GridLayouter<'a> {
|
||||
pos.x += rcol;
|
||||
}
|
||||
|
||||
outputs
|
||||
Ok(outputs)
|
||||
}
|
||||
|
||||
/// Push a row frame into the current region.
|
||||
@ -562,7 +568,7 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
/// Finish rows for one region.
|
||||
fn finish_region(&mut self, vm: &mut Vm) {
|
||||
fn finish_region(&mut self, vm: &mut Vm) -> TypResult<()> {
|
||||
// Determine the size of the grid in this region, expanding fully if
|
||||
// there are fr rows.
|
||||
let mut size = self.used;
|
||||
@ -584,7 +590,7 @@ impl<'a> GridLayouter<'a> {
|
||||
Row::Fr(v, y) => {
|
||||
let remaining = self.full - self.used.y;
|
||||
let height = v.resolve(self.fr, remaining);
|
||||
self.layout_single_row(vm, height, y)
|
||||
self.layout_single_row(vm, height, y)?
|
||||
}
|
||||
};
|
||||
|
||||
@ -600,6 +606,8 @@ impl<'a> GridLayouter<'a> {
|
||||
self.used.y = Length::zero();
|
||||
self.fr = Fractional::zero();
|
||||
self.cts = Constraints::new(self.expand);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the node in the cell in column `x` and row `y`.
|
||||
|
@ -43,7 +43,7 @@ impl HeadingNode {
|
||||
}
|
||||
|
||||
impl Show for HeadingNode {
|
||||
fn show(&self, styles: StyleChain) -> Template {
|
||||
fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> {
|
||||
let mut map = StyleMap::new();
|
||||
|
||||
let upscale = (1.6 - 0.1 * self.level as f64).max(0.75);
|
||||
@ -93,6 +93,8 @@ impl Show for HeadingNode {
|
||||
seq.push(Template::Vertical(below.into()));
|
||||
}
|
||||
|
||||
Template::block(Template::sequence(seq).styled_with_map(map))
|
||||
Ok(Template::block(
|
||||
Template::sequence(seq).styled_with_map(map),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -19,14 +19,14 @@ impl Layout for HideNode {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
let mut frames = self.0.layout(vm, regions, styles);
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
let mut frames = self.0.layout(vm, regions, styles)?;
|
||||
|
||||
// Clear the frames.
|
||||
for Constrained { item: frame, .. } in &mut frames {
|
||||
*frame = Arc::new(Frame { elements: vec![], ..**frame });
|
||||
}
|
||||
|
||||
frames
|
||||
Ok(frames)
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ impl Layout for ImageNode {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
let img = vm.images.get(self.0);
|
||||
let pxw = img.width() as f64;
|
||||
let pxh = img.height() as f64;
|
||||
@ -91,7 +91,7 @@ impl Layout for ImageNode {
|
||||
frame.link(url);
|
||||
}
|
||||
|
||||
vec![frame.constrain(Constraints::tight(regions))]
|
||||
Ok(vec![frame.constrain(Constraints::tight(regions))])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ impl LinkNode {
|
||||
}
|
||||
|
||||
impl Show for LinkNode {
|
||||
fn show(&self, styles: StyleChain) -> Template {
|
||||
fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> {
|
||||
let mut map = StyleMap::new();
|
||||
map.set(TextNode::LINK, Some(self.url.clone()));
|
||||
|
||||
@ -50,6 +50,6 @@ impl Show for LinkNode {
|
||||
body = body.underlined();
|
||||
}
|
||||
|
||||
body.styled_with_map(map)
|
||||
Ok(body.styled_with_map(map))
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ impl<const L: Labelling> ListNode<L> {
|
||||
}
|
||||
|
||||
impl<const L: Labelling> Show for ListNode<L> {
|
||||
fn show(&self, styles: StyleChain) -> Template {
|
||||
fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> {
|
||||
let em = styles.get(TextNode::SIZE).abs;
|
||||
let label_indent = styles.get(Self::LABEL_INDENT).resolve(em);
|
||||
let body_indent = styles.get(Self::BODY_INDENT).resolve(em);
|
||||
@ -40,7 +40,7 @@ impl<const L: Labelling> Show for ListNode<L> {
|
||||
ORDERED | _ => format_eco!("{}.", self.number.unwrap_or(1)),
|
||||
};
|
||||
|
||||
Template::block(GridNode {
|
||||
Ok(Template::block(GridNode {
|
||||
tracks: Spec::with_x(vec![
|
||||
TrackSizing::Linear(label_indent.into()),
|
||||
TrackSizing::Auto,
|
||||
@ -54,7 +54,7 @@ impl<const L: Labelling> Show for ListNode<L> {
|
||||
LayoutNode::default(),
|
||||
self.child.clone(),
|
||||
],
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,11 +22,11 @@ impl MathNode {
|
||||
}
|
||||
|
||||
impl Show for MathNode {
|
||||
fn show(&self, _: StyleChain) -> Template {
|
||||
fn show(&self, _: &mut Vm, _: StyleChain) -> TypResult<Template> {
|
||||
let mut template = Template::Text(self.formula.trim().into());
|
||||
if self.display {
|
||||
template = Template::Block(template.pack());
|
||||
}
|
||||
template.monospaced()
|
||||
Ok(template.monospaced())
|
||||
}
|
||||
}
|
||||
|
@ -37,10 +37,10 @@ impl Layout for PadNode {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
// Layout child into padded regions.
|
||||
let pod = regions.map(|size| shrink(size, self.padding));
|
||||
let mut frames = self.child.layout(vm, &pod, styles);
|
||||
let mut frames = self.child.layout(vm, &pod, styles)?;
|
||||
|
||||
for ((current, base), Constrained { item: frame, cts }) in
|
||||
regions.iter().zip(&mut frames)
|
||||
@ -70,7 +70,7 @@ impl Layout for PadNode {
|
||||
}
|
||||
}
|
||||
|
||||
frames
|
||||
Ok(frames)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ impl PageNode {
|
||||
|
||||
impl PageNode {
|
||||
/// Layout the page run into a sequence of frames, one per page.
|
||||
pub fn layout(&self, vm: &mut Vm, styles: StyleChain) -> Vec<Arc<Frame>> {
|
||||
pub fn layout(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Vec<Arc<Frame>>> {
|
||||
// When one of the lengths is infinite the page fits its content along
|
||||
// that axis.
|
||||
let width = styles.get(Self::WIDTH).unwrap_or(Length::inf());
|
||||
@ -103,11 +103,11 @@ impl PageNode {
|
||||
// Layout the child.
|
||||
let expand = size.map(Length::is_finite);
|
||||
let regions = Regions::repeat(size, size, expand);
|
||||
child
|
||||
.layout(vm, ®ions, styles)
|
||||
Ok(child
|
||||
.layout(vm, ®ions, styles)?
|
||||
.into_iter()
|
||||
.map(|c| c.item)
|
||||
.collect()
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ impl Layout for ParNode {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
// Collect all text into one string used for BiDi analysis.
|
||||
let text = self.collect_text();
|
||||
|
||||
@ -95,10 +95,10 @@ impl Layout for ParNode {
|
||||
|
||||
// Prepare paragraph layout by building a representation on which we can
|
||||
// do line breaking without layouting each and every line from scratch.
|
||||
let layouter = ParLayouter::new(self, vm, regions, &styles, bidi);
|
||||
let layouter = ParLayouter::new(self, vm, regions, &styles, bidi)?;
|
||||
|
||||
// Find suitable linebreaks.
|
||||
layouter.layout(vm, regions.clone())
|
||||
Ok(layouter.layout(vm, regions.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,7 +220,7 @@ impl<'a> ParLayouter<'a> {
|
||||
regions: &Regions,
|
||||
styles: &'a StyleChain<'a>,
|
||||
bidi: BidiInfo<'a>,
|
||||
) -> Self {
|
||||
) -> TypResult<Self> {
|
||||
let mut items = vec![];
|
||||
let mut ranges = vec![];
|
||||
|
||||
@ -255,7 +255,7 @@ impl<'a> ParLayouter<'a> {
|
||||
ParChild::Node(node) => {
|
||||
let size = Size::new(regions.current.x, regions.base.y);
|
||||
let pod = Regions::one(size, regions.base, Spec::splat(false));
|
||||
let frame = node.layout(vm, &pod, styles).remove(0);
|
||||
let frame = node.layout(vm, &pod, styles)?.remove(0);
|
||||
items.push(ParItem::Frame(Arc::take(frame.item)));
|
||||
ranges.push(range);
|
||||
}
|
||||
@ -266,7 +266,7 @@ impl<'a> ParLayouter<'a> {
|
||||
let align = styles.get(ParNode::ALIGN);
|
||||
let leading = styles.get(ParNode::LEADING).resolve(em);
|
||||
|
||||
Self { align, leading, bidi, items, ranges }
|
||||
Ok(Self { align, leading, bidi, items, ranges })
|
||||
}
|
||||
|
||||
/// Find first-fit line breaks and build the paragraph.
|
||||
|
@ -26,7 +26,7 @@ impl Layout for PlaceNode {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
let out_of_flow = self.out_of_flow();
|
||||
|
||||
// The pod is the base area of the region because for absolute
|
||||
@ -37,7 +37,7 @@ impl Layout for PlaceNode {
|
||||
Regions::one(regions.base, regions.base, expand)
|
||||
};
|
||||
|
||||
let mut frames = self.0.layout(vm, &pod, styles);
|
||||
let mut frames = self.0.layout(vm, &pod, styles)?;
|
||||
let Constrained { item: frame, cts } = &mut frames[0];
|
||||
|
||||
// If expansion is off, zero all sizes so that we don't take up any
|
||||
@ -51,7 +51,7 @@ impl Layout for PlaceNode {
|
||||
cts.base = regions.base.map(Some);
|
||||
cts.exact = regions.current.filter(regions.expand | out_of_flow);
|
||||
|
||||
frames
|
||||
Ok(frames)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ impl RawNode {
|
||||
}
|
||||
|
||||
impl Show for RawNode {
|
||||
fn show(&self, styles: StyleChain) -> Template {
|
||||
fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> {
|
||||
let lang = styles.get_ref(Self::LANG).as_ref();
|
||||
let foreground = THEME
|
||||
.settings
|
||||
@ -87,7 +87,7 @@ impl Show for RawNode {
|
||||
template = Template::Block(template.pack());
|
||||
}
|
||||
|
||||
template.monospaced()
|
||||
Ok(template.monospaced())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
let mut frames;
|
||||
if let Some(child) = &self.0 {
|
||||
let mut padding = styles.get(Self::PADDING);
|
||||
@ -61,7 +61,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
||||
let child = child.clone().padded(Sides::splat(padding));
|
||||
|
||||
let mut pod = Regions::one(regions.current, regions.base, regions.expand);
|
||||
frames = child.layout(vm, &pod, styles);
|
||||
frames = child.layout(vm, &pod, styles)?;
|
||||
|
||||
// Relayout with full expansion into square region to make sure
|
||||
// the result is really a square or circle.
|
||||
@ -77,7 +77,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
||||
|
||||
pod.current = Size::splat(length);
|
||||
pod.expand = Spec::splat(true);
|
||||
frames = child.layout(vm, &pod, styles);
|
||||
frames = child.layout(vm, &pod, styles)?;
|
||||
frames[0].cts = Constraints::tight(regions);
|
||||
}
|
||||
} else {
|
||||
@ -127,7 +127,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
||||
frame.link(url);
|
||||
}
|
||||
|
||||
frames
|
||||
Ok(frames)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ impl Layout for StackNode {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
let mut layouter = StackLayouter::new(self.dir, regions);
|
||||
|
||||
// Spacing to insert before the next node.
|
||||
@ -48,13 +48,13 @@ impl Layout for StackNode {
|
||||
layouter.layout_spacing(kind);
|
||||
}
|
||||
|
||||
layouter.layout_node(vm, node, styles);
|
||||
layouter.layout_node(vm, node, styles)?;
|
||||
deferred = self.spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layouter.finish()
|
||||
Ok(layouter.finish())
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,7 +163,12 @@ impl StackLayouter {
|
||||
}
|
||||
|
||||
/// Layout an arbitrary node.
|
||||
pub fn layout_node(&mut self, vm: &mut Vm, node: &LayoutNode, styles: StyleChain) {
|
||||
pub fn layout_node(
|
||||
&mut self,
|
||||
vm: &mut Vm,
|
||||
node: &LayoutNode,
|
||||
styles: StyleChain,
|
||||
) -> TypResult<()> {
|
||||
if self.regions.is_full() {
|
||||
self.finish_region();
|
||||
}
|
||||
@ -174,7 +179,7 @@ impl StackLayouter {
|
||||
.and_then(|node| node.aligns.get(self.axis))
|
||||
.unwrap_or(self.dir.start().into());
|
||||
|
||||
let frames = node.layout(vm, &self.regions, styles);
|
||||
let frames = node.layout(vm, &self.regions, styles)?;
|
||||
let len = frames.len();
|
||||
for (i, frame) in frames.into_iter().enumerate() {
|
||||
// Grow our size, shrink the region and save the frame for later.
|
||||
@ -188,6 +193,8 @@ impl StackLayouter {
|
||||
self.finish_region();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Advance to the next region.
|
||||
|
@ -55,7 +55,7 @@ impl TableNode {
|
||||
}
|
||||
|
||||
impl Show for TableNode {
|
||||
fn show(&self, styles: StyleChain) -> Template {
|
||||
fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> {
|
||||
let primary = styles.get(Self::PRIMARY);
|
||||
let secondary = styles.get(Self::SECONDARY);
|
||||
let thickness = styles.get(Self::THICKNESS);
|
||||
@ -85,10 +85,10 @@ impl Show for TableNode {
|
||||
})
|
||||
.collect();
|
||||
|
||||
Template::block(GridNode {
|
||||
Ok(Template::block(GridNode {
|
||||
tracks: self.tracks.clone(),
|
||||
gutter: self.gutter.clone(),
|
||||
children,
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -123,8 +123,8 @@ impl StrongNode {
|
||||
}
|
||||
|
||||
impl Show for StrongNode {
|
||||
fn show(&self, _: StyleChain) -> Template {
|
||||
self.0.clone().styled(TextNode::STRONG, true)
|
||||
fn show(&self, _: &mut Vm, _: StyleChain) -> TypResult<Template> {
|
||||
Ok(self.0.clone().styled(TextNode::STRONG, true))
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,8 +140,8 @@ impl EmphNode {
|
||||
}
|
||||
|
||||
impl Show for EmphNode {
|
||||
fn show(&self, _: StyleChain) -> Template {
|
||||
self.0.clone().styled(TextNode::EMPH, true)
|
||||
fn show(&self, _: &mut Vm, _: StyleChain) -> TypResult<Template> {
|
||||
Ok(self.0.clone().styled(TextNode::EMPH, true))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,9 +49,9 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
|
||||
vm: &mut Vm,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
||||
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
|
||||
let mut frames = self.child.layout(vm, regions, styles);
|
||||
let mut frames = self.child.layout(vm, regions, styles)?;
|
||||
|
||||
for Constrained { item: frame, .. } in &mut frames {
|
||||
let Spec { x, y } = origin.zip(frame.size).map(|(o, s)| o.resolve(s));
|
||||
@ -62,7 +62,7 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
|
||||
Arc::make_mut(frame).transform(transform);
|
||||
}
|
||||
|
||||
frames
|
||||
Ok(frames)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -275,14 +275,13 @@ fn test_part(
|
||||
ok &= test_reparse(ctx.sources.get(id).src(), i, rng);
|
||||
|
||||
let mut vm = Vm::new(ctx);
|
||||
let (frames, mut errors) = match vm.evaluate(id) {
|
||||
Ok(module) => {
|
||||
let (frames, mut errors) = match vm.typeset(id) {
|
||||
Ok(mut frames) => {
|
||||
let module = vm.evaluate(id).unwrap();
|
||||
if debug {
|
||||
println!("Template: {:#?}", module.template);
|
||||
}
|
||||
|
||||
let mut frames = module.template.layout(&mut vm);
|
||||
|
||||
#[cfg(feature = "layout-cache")]
|
||||
(ok &= test_incremental(ctx, i, &module.template, &frames));
|
||||
|
||||
@ -499,7 +498,7 @@ fn test_incremental(
|
||||
|
||||
ctx.layout_cache.turnaround();
|
||||
|
||||
let cached = silenced(|| template.layout(&mut Vm::new(ctx)));
|
||||
let cached = silenced(|| template.layout(&mut Vm::new(ctx)).unwrap());
|
||||
let total = reference.levels() - 1;
|
||||
let misses = ctx
|
||||
.layout_cache
|
||||
|
Loading…
x
Reference in New Issue
Block a user