diff --git a/Cargo.lock b/Cargo.lock index 890e54ebf..8da75a6c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,17 +190,6 @@ dependencies = [ "regex", ] -[[package]] -name = "filedescriptor" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" -dependencies = [ - "libc", - "thiserror", - "winapi", -] - [[package]] name = "flate2" version = "1.0.22" @@ -779,26 +768,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "thiserror" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "time" version = "0.3.7" @@ -843,7 +812,6 @@ dependencies = [ "bytemuck", "codespan-reporting", "dirs", - "filedescriptor", "flate2", "fxhash", "iai", diff --git a/Cargo.toml b/Cargo.toml index d96bd98e4..d709b0b06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,6 @@ memmap2 = { version = "0.5", optional = true } walkdir = { version = "2", optional = true } [dev-dependencies] -filedescriptor = "0.8" iai = { git = "https://github.com/reknih/iai" } walkdir = "2" diff --git a/src/layout/mod.rs b/src/eval/layout.rs similarity index 62% rename from src/layout/mod.rs rename to src/eval/layout.rs index afb2621b6..7608023ab 100644 --- a/src/layout/mod.rs +++ b/src/eval/layout.rs @@ -1,22 +1,14 @@ //! Layouting infrastructure. -mod constraints; -mod incremental; -mod regions; - -pub use constraints::*; -pub use incremental::*; -pub use regions::*; - use std::any::{Any, TypeId}; use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; +use std::hash::Hash; 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}; +use crate::geom::{Align, Length, Linear, Paint, Point, Sides, Size, Spec, Transform}; use crate::library::{AlignNode, PadNode, TransformNode, MOVE}; use crate::util::Prehashed; use crate::Vm; @@ -32,7 +24,7 @@ pub trait Layout { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>>; + ) -> TypResult>>; /// Convert to a packed node. fn pack(self) -> LayoutNode @@ -43,6 +35,102 @@ pub trait Layout { } } +/// A sequence of regions to layout into. +#[derive(Debug, Clone)] +pub struct Regions { + /// The (remaining) size of the first region. + pub first: Size, + /// The base size for relative sizing. + pub base: Size, + /// The height of followup regions. The width is the same for all regions. + pub backlog: std::vec::IntoIter, + /// The height of the final region that is repeated once the backlog is + /// drained. The width is the same for all regions. + pub last: Option, + /// Whether nodes should expand to fill the regions instead of shrinking to + /// fit the content. + pub expand: Spec, +} + +impl Regions { + /// Create a new region sequence with exactly one region. + pub fn one(size: Size, base: Size, expand: Spec) -> Self { + Self { + first: size, + base, + backlog: vec![].into_iter(), + last: None, + expand, + } + } + + /// Create a new sequence of same-size regions that repeats indefinitely. + pub fn repeat(size: Size, base: Size, expand: Spec) -> Self { + Self { + first: size, + base, + backlog: vec![].into_iter(), + last: Some(size.y), + expand, + } + } + + /// Create new regions where all sizes are mapped with `f`. + /// + /// Note that since all regions must have the same width, the width returned + /// by `f` is ignored for the backlog and the final region. + pub fn map(&self, mut f: F) -> Self + where + F: FnMut(Size) -> Size, + { + let x = self.first.x; + Self { + first: f(self.first), + base: f(self.base), + backlog: self + .backlog + .as_slice() + .iter() + .map(|&y| f(Size::new(x, y)).y) + .collect::>() + .into_iter(), + last: self.last.map(|y| f(Size::new(x, y)).y), + expand: self.expand, + } + } + + /// Whether the first region is full and a region break is called for. + pub fn is_full(&self) -> bool { + Length::zero().fits(self.first.y) && !self.in_last() + } + + /// Whether the first region is the last usable region. + /// + /// If this is true, calling `next()` will have no effect. + pub fn in_last(&self) -> bool { + self.backlog.len() == 0 && self.last.map_or(true, |height| self.first.y == height) + } + + /// Advance to the next region if there is any. + pub fn next(&mut self) { + if let Some(height) = self.backlog.next().or(self.last) { + self.first.y = height; + self.base.y = height; + } + } + + /// An iterator that returns the sizes of the first and all following + /// regions, equivalently to what would be produced by calling + /// [`next()`](Self::next) repeatedly until all regions are exhausted. + /// This iterater may be infinite. + pub fn iter(&self) -> impl Iterator + '_ { + let first = std::iter::once(self.first); + let backlog = self.backlog.as_slice().iter(); + let last = self.last.iter().cycle(); + first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h))) + } +} + /// A type-erased layouting node with a precomputed hash. #[derive(Clone, Hash)] pub struct LayoutNode(Arc>); @@ -136,41 +224,9 @@ impl Layout for LayoutNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { - let styles = styles.barred(self.id()); - - let hash = { - let mut state = fxhash::FxHasher64::default(); - self.hash(&mut state); - styles.hash(&mut state); - state.finish() - }; - - // This is not written with `unwrap_or_else`, because then the - // #[track_caller] annotation doesn't work. - if let Some(frames) = vm.layout_cache.get(hash, regions) { - Ok(frames) - } else { - vm.level += 1; - let frames = self.0.layout(vm, regions, styles)?; - vm.level -= 1; - - let entry = FramesEntry::new(frames.clone(), vm.level); - - #[cfg(debug_assertions)] - if !entry.check(regions) { - eprintln!("node: {:#?}", self.0); - eprintln!("regions: {regions:#?}"); - eprintln!( - "constraints: {:#?}", - frames.iter().map(|c| c.cts).collect::>(), - ); - panic!("constraints did not match regions they were created for"); - } - - vm.layout_cache.insert(hash, entry); - Ok(frames) - } + ) -> TypResult>> { + // TODO(query) + self.0.layout(vm, regions, styles.barred(self.id())) } fn pack(self) -> LayoutNode { @@ -221,11 +277,10 @@ impl Layout for EmptyNode { _: &mut Vm, regions: &Regions, _: StyleChain, - ) -> TypResult>>> { - let size = regions.expand.select(regions.current, Size::zero()); - let mut cts = Constraints::new(regions.expand); - cts.exact = regions.current.filter(regions.expand); - Ok(vec![Frame::new(size).constrain(cts)]) + ) -> TypResult>> { + Ok(vec![Arc::new(Frame::new( + regions.expand.select(regions.first, Size::zero()), + ))]) } } @@ -244,10 +299,7 @@ impl Layout for SizedNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { - let is_auto = self.sizing.map_is_none(); - let is_rel = self.sizing.map(|s| s.map_or(false, Linear::is_relative)); - + ) -> TypResult>> { // The "pod" is the region into which the child will be layouted. let pod = { // Resolve the sizing to a concrete size. @@ -255,30 +307,25 @@ impl Layout for SizedNode { .sizing .zip(regions.base) .map(|(s, b)| s.map(|v| v.resolve(b))) - .unwrap_or(regions.current); + .unwrap_or(regions.first); // Select the appropriate base and expansion for the child depending // on whether it is automatically or linearly sized. + let is_auto = self.sizing.map_is_none(); let base = is_auto.select(regions.base, size); let expand = regions.expand | !is_auto; Regions::one(size, base, expand) }; + // Layout the child. 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. - let target = regions.expand.select(regions.current, frame.size); + let frame = &mut frames[0]; + let target = regions.expand.select(regions.first, frame.size); Arc::make_mut(frame).resize(target, Align::LEFT_TOP); - // Set base & exact constraints if the child is automatically sized - // since we don't know what the child might have done. Also set base if - // our sizing is relative. - *cts = Constraints::new(regions.expand); - cts.exact = regions.current.filter(regions.expand | is_auto); - cts.base = regions.base.filter(is_rel | is_auto); - Ok(frames) } } @@ -298,9 +345,9 @@ impl Layout for FillNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { let mut frames = self.child.layout(vm, regions, styles)?; - for Constrained { item: frame, .. } in &mut frames { + for frame in &mut frames { let shape = Shape::filled(Geometry::Rect(frame.size), self.fill); Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); } @@ -323,9 +370,9 @@ impl Layout for StrokeNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { let mut frames = self.child.layout(vm, regions, styles)?; - for Constrained { item: frame, .. } in &mut frames { + for frame in &mut frames { let shape = Shape::stroked(Geometry::Rect(frame.size), self.stroke); Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 1b61ac151..a67a21d4c 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -12,6 +12,7 @@ mod capture; mod class; mod collapse; mod func; +mod layout; mod ops; mod scope; mod show; @@ -23,6 +24,7 @@ pub use class::*; pub use collapse::*; pub use dict::*; pub use func::*; +pub use layout::*; pub use scope::*; pub use show::*; pub use styles::*; diff --git a/src/eval/template.rs b/src/eval/template.rs index a1d2297d0..b4e3c76d9 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -7,10 +7,10 @@ use std::ops::{Add, AddAssign}; use typed_arena::Arena; use super::{ - CollapsingBuilder, Interruption, Property, Show, ShowNode, StyleMap, StyleVecBuilder, + CollapsingBuilder, Interruption, Layout, LayoutNode, Property, Show, ShowNode, + StyleMap, StyleVecBuilder, }; use crate::diag::StrResult; -use crate::layout::{Layout, LayoutNode}; use crate::library::prelude::*; use crate::library::{ DecoNode, FlowChild, FlowNode, ListItem, ListKind, ListNode, PageNode, ParChild, @@ -240,7 +240,7 @@ impl Layout for Template { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { let sya = Arena::new(); let tpa = Arena::new(); diff --git a/src/eval/value.rs b/src/eval/value.rs index 952c7293d..009a1463a 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -4,10 +4,9 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use super::{ops, Args, Array, Class, Dict, Func, Template}; +use super::{ops, Args, Array, Class, Dict, Func, Layout, Template}; use crate::diag::StrResult; use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor}; -use crate::layout::Layout; use crate::syntax::Spanned; use crate::util::EcoString; diff --git a/src/export/render.rs b/src/export/render.rs index 8b7aa46dd..1a08b60b5 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -1,6 +1,5 @@ //! Rendering into raster images. -use std::collections::{hash_map::Entry, HashMap}; use std::io::Read; use image::{GenericImageView, Rgba}; @@ -8,26 +7,12 @@ use tiny_skia as sk; use ttf_parser::{GlyphId, OutlineBuilder}; use usvg::FitTo; -use crate::font::{Face, FaceId}; +use crate::font::Face; use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text}; use crate::geom::{self, Length, Paint, PathElement, Size, Transform}; use crate::image::{Image, RasterImage, Svg}; use crate::Context; -/// Caches rendering artifacts. -#[derive(Default, Clone)] -pub struct RenderCache { - /// Glyphs prepared for rendering. - glyphs: HashMap<(FaceId, GlyphId), pixglyph::Glyph>, -} - -impl RenderCache { - /// Create a new, empty rendering cache. - pub fn new() -> Self { - Self::default() - } -} - /// Export a frame into a rendered image. /// /// This renders the frame at the given number of pixels per printer's point and @@ -131,7 +116,6 @@ fn render_text( text: &Text, ) { let face = ctx.fonts.get(text.face_id); - let cache = &mut ctx.render_cache; let mut x = 0.0; for glyph in &text.glyphs { @@ -141,7 +125,7 @@ fn render_text( render_svg_glyph(canvas, ts, mask, text, face, id) .or_else(|| render_bitmap_glyph(canvas, ts, mask, text, face, id)) - .or_else(|| render_outline_glyph(canvas, ts, mask, cache, text, face, id)); + .or_else(|| render_outline_glyph(canvas, ts, mask, text, face, id)); x += glyph.x_advance.resolve(text.size).to_f32(); } @@ -227,7 +211,6 @@ fn render_outline_glyph( canvas: &mut sk::Pixmap, ts: sk::Transform, mask: Option<&sk::ClipMask>, - cache: &mut RenderCache, text: &Text, face: &Face, id: GlyphId, @@ -255,15 +238,10 @@ fn render_outline_glyph( return Some(()); } + // TODO(query) // Try to retrieve a prepared glyph or prepare it from scratch if it // doesn't exist, yet. - let glyph = match cache.glyphs.entry((text.face_id, id)) { - Entry::Occupied(entry) => entry.into_mut(), - Entry::Vacant(entry) => { - let glyph = pixglyph::Glyph::load(face.ttf(), id)?; - entry.insert(glyph) - } - }; + let glyph = pixglyph::Glyph::load(face.ttf(), id)?; // Rasterize the glyph with `pixglyph`. let bitmap = glyph.rasterize(ts.tx, ts.ty, ppem); diff --git a/src/layout/constraints.rs b/src/layout/constraints.rs deleted file mode 100644 index 3bdbc4bce..000000000 --- a/src/layout/constraints.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::sync::Arc; - -use super::Regions; -use crate::frame::Frame; -use crate::geom::{Length, Size, Spec}; - -/// Constrain a frame with constraints. -pub trait Constrain { - /// Reference-count the frame and wrap it with constraints. - fn constrain(self, cts: Constraints) -> Constrained>; -} - -impl Constrain for Frame { - fn constrain(self, cts: Constraints) -> Constrained> { - Constrained::new(Arc::new(self), cts) - } -} - -/// Carries an item that is only valid in certain regions and the constraints -/// that describe these regions. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct Constrained { - /// The item that is only valid if the constraints are fullfilled. - pub item: T, - /// Constraints on regions in which the item is valid. - pub cts: Constraints, -} - -impl Constrained { - /// Constrain an item with constraints. - pub fn new(item: T, cts: Constraints) -> Self { - Self { item, cts } - } -} - -/// Describe regions that match them. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct Constraints { - /// The minimum available length in the region. - pub min: Spec>, - /// The maximum available length in the region. - pub max: Spec>, - /// The available length in the region. - pub exact: Spec>, - /// The base length of the region used for relative length resolution. - pub base: Spec>, - /// The expand settings of the region. - pub expand: Spec, -} - -impl Constraints { - /// Create a new region constraint. - pub fn new(expand: Spec) -> Self { - Self { - min: Spec::default(), - max: Spec::default(), - exact: Spec::default(), - base: Spec::default(), - expand, - } - } - - /// Create tight constraints for a region. - pub fn tight(regions: &Regions) -> Self { - Self { - min: Spec::default(), - max: Spec::default(), - exact: regions.current.map(Some), - base: regions.base.map(Some), - expand: regions.expand, - } - } - - /// Check whether the constraints are fullfilled in a region with the given - /// properties. - pub fn check(&self, current: Size, base: Size, expand: Spec) -> bool { - self.expand == expand - && verify(self.min, current, |m, c| c.fits(m)) - && verify(self.max, current, |m, c| m.fits(c)) - && verify(self.exact, current, Length::approx_eq) - && verify(self.base, base, Length::approx_eq) - } -} - -/// Verify a single constraint. -fn verify(spec: Spec>, size: Size, f: fn(Length, Length) -> bool) -> bool { - spec.zip(size).all(|&(opt, s)| opt.map_or(true, |m| f(m, s))) -} diff --git a/src/layout/incremental.rs b/src/layout/incremental.rs deleted file mode 100644 index b68ddcdc8..000000000 --- a/src/layout/incremental.rs +++ /dev/null @@ -1,451 +0,0 @@ -use std::cmp::Reverse; -use std::collections::HashMap; -use std::sync::Arc; - -use itertools::Itertools; - -use super::{Constrained, Regions}; -use crate::frame::Frame; -use crate::geom::Scalar; - -const TEMP_LEN: usize = 4; - -/// Caches layouting artifacts. -#[derive(Default, Clone)] -pub struct LayoutCache { - /// Maps from node hashes to the resulting frames and regions in which the - /// frames are valid. The right hand side of the hash map is a vector of - /// results because across one or more compilations, multiple different - /// layouts of the same node may have been requested. - frames: HashMap>, - /// In how many compilations this cache has been used. - age: usize, - /// What cache eviction policy should be used. - policy: EvictionPolicy, - /// The maximum number of entries this cache should have. Can be exceeded if - /// there are more must-keep entries. - max_size: usize, -} - -impl LayoutCache { - /// Create a new, empty layout cache. - pub fn new(policy: EvictionPolicy, max_size: usize) -> Self { - Self { - frames: HashMap::default(), - age: 0, - policy, - max_size, - } - } - - /// Whether the cache is empty. - pub fn is_empty(&self) -> bool { - self.frames.values().all(|entry| entry.is_empty()) - } - - /// Amount of items in the cache. - pub fn len(&self) -> usize { - self.frames.values().map(Vec::len).sum() - } - - /// The number of levels stored in the cache. - pub fn levels(&self) -> usize { - self.entries().map(|entry| entry.level + 1).max().unwrap_or(0) - } - - /// An iterator over all entries in the cache. - pub fn entries(&self) -> impl Iterator + '_ { - self.frames.values().flatten() - } - - /// Fetch matching cached frames if there are any. - pub fn get( - &mut self, - hash: u64, - regions: &Regions, - ) -> Option>>> { - self.frames - .get_mut(&hash)? - .iter_mut() - .find_map(|entry| entry.lookup(regions)) - } - - /// Insert a new frame entry into the cache. - pub fn insert(&mut self, hash: u64, entry: FramesEntry) { - self.frames.entry(hash).or_default().push(entry); - } - - /// Clear the cache. - pub fn clear(&mut self) { - self.frames.clear(); - } - - /// Retains all elements for which the closure on the level returns `true`. - pub fn retain(&mut self, mut f: F) - where - F: FnMut(usize) -> bool, - { - for entries in self.frames.values_mut() { - entries.retain(|entry| f(entry.level)); - } - } - - /// Prepare the cache for the next round of compilation. - pub fn turnaround(&mut self) { - self.age += 1; - for entry in self.frames.values_mut().flatten() { - if entry.temperature[0] > 0 { - entry.used_cycles += 1; - } - - let last = *entry.temperature.last().unwrap(); - for i in (1 .. TEMP_LEN).rev() { - entry.temperature[i] = entry.temperature[i - 1]; - } - - entry.temperature[0] = 0; - entry.ancient_hits += last as usize; - entry.age += 1; - } - - self.evict(); - self.frames.retain(|_, v| !v.is_empty()); - } - - /// Evict the cache according to the policy. - fn evict(&mut self) { - let len = self.len(); - if len <= self.max_size { - return; - } - - match self.policy { - EvictionPolicy::LeastRecentlyUsed => { - // We find the element with the largest cooldown that cannot fit - // anymore. - let threshold = self - .entries() - .map(|f| Reverse(f.cooldown())) - .k_smallest(len - self.max_size) - .last() - .unwrap() - .0; - - for entries in self.frames.values_mut() { - entries.retain(|f| f.cooldown() < threshold); - } - } - EvictionPolicy::LeastFrequentlyUsed => { - let threshold = self - .entries() - .map(|f| Scalar(f.hits() as f64 / f.age() as f64)) - .k_smallest(len - self.max_size) - .last() - .unwrap() - .0; - - for entries in self.frames.values_mut() { - entries.retain(|f| f.hits() as f64 / f.age() as f64 > threshold); - } - } - EvictionPolicy::Patterns => { - let kept = self.entries().filter(|f| f.properties().must_keep()).count(); - - let remaining_capacity = self.max_size - kept.min(self.max_size); - if len - kept <= remaining_capacity { - return; - } - - let threshold = self - .entries() - .filter(|f| !f.properties().must_keep()) - .map(|f| Scalar(f.hits() as f64 / f.age() as f64)) - .k_smallest((len - kept) - remaining_capacity) - .last() - .unwrap() - .0; - - for entries in self.frames.values_mut() { - entries.retain(|f| { - f.properties().must_keep() - || f.hits() as f64 / f.age() as f64 > threshold - }); - } - } - EvictionPolicy::None => {} - } - } -} - -/// Cached frames from past layouting. -#[derive(Debug, Clone)] -pub struct FramesEntry { - /// The cached frames for a node. - frames: Vec>>, - /// How nested the frame was in the context is was originally appearing in. - level: usize, - /// For how long the element already exists. - age: usize, - /// How much the element was accessed during the last five compilations, the - /// most recent one being the first element. - temperature: [u8; TEMP_LEN], - /// All past usages that do not fit in the temperature array. - ancient_hits: usize, - /// Amount of cycles in which the element has been used at all. - used_cycles: usize, -} - -impl FramesEntry { - /// Construct a new instance. - pub fn new(frames: Vec>>, level: usize) -> Self { - Self { - frames, - level, - age: 1, - temperature: [0; TEMP_LEN], - ancient_hits: 0, - used_cycles: 0, - } - } - - /// Checks if the cached frames are valid in the given regions and returns - /// them if so. - pub fn lookup(&mut self, regions: &Regions) -> Option>>> { - self.check(regions).then(|| { - self.temperature[0] = self.temperature[0].saturating_add(1); - self.frames.clone() - }) - } - - /// Checks if the cached frames are valid in the given regions. - pub fn check(&self, regions: &Regions) -> bool { - let mut iter = regions.iter(); - self.frames.iter().all(|frame| { - iter.next().map_or(false, |(current, base)| { - frame.cts.check(current, base, regions.expand) - }) - }) - } - - /// How nested the frame was in the context is was originally appearing in. - pub fn level(&self) -> usize { - self.level - } - - /// The number of compilation cycles this item has remained in the cache. - pub fn age(&self) -> usize { - self.age - } - - /// Whether this element was used in the last compilation cycle. - pub fn hit(&self) -> bool { - self.temperature[0] != 0 - } - - /// Get the total amount of hits over the lifetime of this item. - pub fn hits(&self) -> usize { - self.temperature.into_iter().map(usize::from).sum::() + self.ancient_hits - } - - /// The amount of consecutive cycles in which this item has not been used. - pub fn cooldown(&self) -> usize { - let mut cycle = 0; - for &temp in &self.temperature[.. self.age.min(TEMP_LEN)] { - if temp > 0 { - return cycle; - } - cycle += 1; - } - cycle - } - - /// Properties that describe how this entry's temperature evolved. - pub fn properties(&self) -> PatternProperties { - let mut all_zeros = true; - let mut multi_use = false; - let mut decreasing = true; - let mut sparse = false; - let mut abandoned = false; - - let mut last = None; - let mut all_same = true; - - for (i, &temp) in self.temperature.iter().enumerate() { - if temp == 0 && !all_zeros { - sparse = true; - } - - if temp != 0 { - all_zeros = false; - } - - if all_zeros && i == 1 { - abandoned = true; - } - - if temp > 1 { - multi_use = true; - } - - if let Some(prev) = last { - if prev > temp { - decreasing = false; - } - - if temp != prev { - all_same = false; - } - } - - last = Some(temp); - } - - if self.age > TEMP_LEN && self.age - TEMP_LEN <= self.ancient_hits { - multi_use = true; - } - - if self.ancient_hits > 0 { - all_zeros = false; - } - - PatternProperties { - mature: self.age > TEMP_LEN, - hit: self.temperature[0] >= 1, - top_level: self.level == 0, - all_zeros, - multi_use, - decreasing: decreasing && !all_same, - sparse, - abandoned, - } - } -} - -/// Cache eviction strategies. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum EvictionPolicy { - /// Evict the least recently used item. - LeastRecentlyUsed, - /// Evict the least frequently used item. - LeastFrequentlyUsed, - /// Use the pattern verdicts. - Patterns, - /// Do not evict. - None, -} - -impl Default for EvictionPolicy { - fn default() -> Self { - Self::Patterns - } -} - -/// Describes the properties that this entry's temperature array has. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct PatternProperties { - /// There only are zero values. - pub all_zeros: bool, - /// The entry exists for more or equal time as the temperature array is long. - pub mature: bool, - /// The entry was used more than one time in at least one compilation. - pub multi_use: bool, - /// The entry was used in the last compilation. - pub hit: bool, - /// The temperature is monotonously decreasing in non-terminal temperature fields. - pub decreasing: bool, - /// There are zero temperatures after non-zero temperatures. - pub sparse: bool, - /// There are multiple zero temperatures at the front of the temperature array. - pub abandoned: bool, - /// If the item is on the top level. - pub top_level: bool, -} - -impl PatternProperties { - /// Check if it is vital to keep an entry based on its properties. - pub fn must_keep(&self) -> bool { - // Keep an undo stack. - (self.top_level && !self.mature) - // Keep the most recently created items, even if they have not yet - // been used. - || (self.all_zeros && !self.mature) - || (self.multi_use && !self.abandoned) - || self.hit - || self.sparse - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::geom::{Size, Spec}; - use crate::layout::Constraints; - - fn empty_frames() -> Vec>> { - vec![Constrained { - item: Arc::new(Frame::default()), - cts: Constraints::new(Spec::splat(false)), - }] - } - - fn zero_regions() -> Regions { - Regions::one(Size::zero(), Size::zero(), Spec::splat(false)) - } - - #[test] - fn test_layout_incremental_temperature() { - let mut cache = LayoutCache::new(EvictionPolicy::None, 20); - let regions = zero_regions(); - cache.policy = EvictionPolicy::None; - cache.insert(0, FramesEntry::new(empty_frames(), 0)); - - let entry = cache.frames.get(&0).unwrap().first().unwrap(); - assert_eq!(entry.age(), 1); - assert_eq!(entry.temperature, [0, 0, 0, 0]); - assert_eq!(entry.ancient_hits, 0); - assert_eq!(entry.used_cycles, 0); - assert_eq!(entry.level, 0); - - cache.get(0, ®ions).unwrap(); - let entry = cache.frames.get(&0).unwrap().first().unwrap(); - assert_eq!(entry.age(), 1); - assert_eq!(entry.temperature, [1, 0, 0, 0]); - assert_eq!(entry.ancient_hits, 0); - - cache.turnaround(); - let entry = cache.frames.get(&0).unwrap().first().unwrap(); - assert_eq!(entry.age(), 2); - assert_eq!(entry.temperature, [0, 1, 0, 0]); - assert_eq!(entry.ancient_hits, 0); - assert_eq!(entry.used_cycles, 1); - - cache.get(0, ®ions).unwrap(); - for _ in 0 .. 4 { - cache.turnaround(); - } - - let entry = cache.frames.get(&0).unwrap().first().unwrap(); - assert_eq!(entry.age(), 6); - assert_eq!(entry.temperature, [0, 0, 0, 0]); - assert_eq!(entry.ancient_hits, 2); - assert_eq!(entry.used_cycles, 2); - } - - #[test] - fn test_layout_incremental_properties() { - let mut cache = LayoutCache::new(EvictionPolicy::None, 20); - cache.policy = EvictionPolicy::None; - cache.insert(0, FramesEntry::new(empty_frames(), 1)); - - let props = cache.frames.get(&0).unwrap().first().unwrap().properties(); - assert_eq!(props.top_level, false); - assert_eq!(props.mature, false); - assert_eq!(props.multi_use, false); - assert_eq!(props.hit, false); - assert_eq!(props.decreasing, false); - assert_eq!(props.sparse, false); - assert_eq!(props.abandoned, true); - assert_eq!(props.all_zeros, true); - assert_eq!(props.must_keep(), true); - } -} diff --git a/src/layout/regions.rs b/src/layout/regions.rs deleted file mode 100644 index 3f8b6d259..000000000 --- a/src/layout/regions.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::geom::{Length, Size, Spec}; - -/// A sequence of regions to layout into. -#[derive(Debug, Clone)] -pub struct Regions { - /// The remaining size of the current region. - pub current: Size, - /// The base size for relative sizing. - pub base: Size, - /// The height of followup regions. The width is the same for all regions. - pub backlog: std::vec::IntoIter, - /// The height of the final region that is repeated once the backlog is - /// drained. The width is the same for all regions. - pub last: Option, - /// Whether nodes should expand to fill the regions instead of shrinking to - /// fit the content. - pub expand: Spec, -} - -impl Regions { - /// Create a new region sequence with exactly one region. - pub fn one(size: Size, base: Size, expand: Spec) -> Self { - Self { - current: size, - base, - backlog: vec![].into_iter(), - last: None, - expand, - } - } - - /// Create a new sequence of same-size regions that repeats indefinitely. - pub fn repeat(size: Size, base: Size, expand: Spec) -> Self { - Self { - current: size, - base, - backlog: vec![].into_iter(), - last: Some(size.y), - expand, - } - } - - /// Create new regions where all sizes are mapped with `f`. - /// - /// Note that since all regions must have the same width, the width returned - /// by `f` is ignored for the backlog and the final region. - pub fn map(&self, mut f: F) -> Self - where - F: FnMut(Size) -> Size, - { - let x = self.current.x; - Self { - current: f(self.current), - base: f(self.base), - backlog: self - .backlog - .as_slice() - .iter() - .map(|&y| f(Size::new(x, y)).y) - .collect::>() - .into_iter(), - last: self.last.map(|y| f(Size::new(x, y)).y), - expand: self.expand, - } - } - - /// Whether the current region is full and a region break is called for. - pub fn is_full(&self) -> bool { - Length::zero().fits(self.current.y) && !self.in_last() - } - - /// Whether `current` is the last usable region. - /// - /// If this is true, calling `next()` will have no effect. - pub fn in_last(&self) -> bool { - self.backlog.len() == 0 - && self.last.map_or(true, |height| self.current.y == height) - } - - /// Advance to the next region if there is any. - pub fn next(&mut self) { - if let Some(height) = self.backlog.next().or(self.last) { - self.current.y = height; - self.base.y = height; - } - } - - /// An iterator that returns pairs of `(current, base)` that are equivalent - /// to what would be produced by calling [`next()`](Self::next) repeatedly - /// until all regions are exhausted. - pub fn iter(&self) -> impl Iterator + '_ { - let first = std::iter::once((self.current, self.base)); - let backlog = self.backlog.as_slice().iter(); - let last = self.last.iter().cycle(); - first.chain(backlog.chain(last).map(|&height| { - ( - Size::new(self.current.x, height), - Size::new(self.base.x, height), - ) - })) - } -} diff --git a/src/lib.rs b/src/lib.rs index 13c63885d..cfbdcae7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,6 @@ pub mod font; pub mod frame; pub mod geom; pub mod image; -pub mod layout; pub mod library; pub mod loading; pub mod parse; @@ -58,11 +57,9 @@ use std::sync::Arc; use crate::diag::TypResult; use crate::eval::{Eval, Module, Scope, Scopes, StyleMap}; -use crate::export::RenderCache; use crate::font::FontStore; use crate::frame::Frame; use crate::image::ImageStore; -use crate::layout::{EvictionPolicy, LayoutCache}; use crate::loading::Loader; use crate::source::{SourceId, SourceStore}; @@ -76,10 +73,6 @@ pub struct Context { pub fonts: FontStore, /// Stores decoded images. pub images: ImageStore, - /// Caches layouting artifacts. - pub layout_cache: LayoutCache, - /// Caches rendering artifacts. - pub render_cache: RenderCache, /// The standard library scope. std: Scope, /// The default styles. @@ -115,11 +108,6 @@ impl Context { pub fn typeset(&mut self, id: SourceId) -> TypResult>> { Vm::new(self).typeset(id) } - - /// Garbage-collect caches. - pub fn turnaround(&mut self) { - self.layout_cache.turnaround(); - } } /// A builder for a [`Context`]. @@ -128,8 +116,6 @@ impl Context { pub struct ContextBuilder { std: Option, styles: Option, - policy: EvictionPolicy, - max_size: usize, } impl ContextBuilder { @@ -146,21 +132,6 @@ impl ContextBuilder { self } - /// The policy for eviction of the layout cache. - pub fn cache_policy(mut self, policy: EvictionPolicy) -> Self { - self.policy = policy; - self - } - - /// The maximum number of entries the layout cache should have. - /// - /// Note that this can be exceeded if more entries are categorized as [must - /// keep][crate::layout::PatternProperties::must_keep]. - pub fn cache_max_size(mut self, max_size: usize) -> Self { - self.max_size = max_size; - self - } - /// Finish building the context by providing the `loader` used to load /// fonts, images, source files and other resources. pub fn build(self, loader: Arc) -> Context { @@ -169,8 +140,6 @@ impl ContextBuilder { fonts: FontStore::new(Arc::clone(&loader)), images: ImageStore::new(Arc::clone(&loader)), loader, - layout_cache: LayoutCache::new(self.policy, self.max_size), - render_cache: RenderCache::new(), std: self.std.unwrap_or_else(library::new), styles: self.styles.unwrap_or_default(), } @@ -179,12 +148,7 @@ impl ContextBuilder { impl Default for ContextBuilder { fn default() -> Self { - Self { - std: None, - styles: None, - policy: EvictionPolicy::default(), - max_size: 2000, - } + Self { std: None, styles: None } } } @@ -198,8 +162,6 @@ pub struct Vm<'a> { pub fonts: &'a mut FontStore, /// Stores decoded images. pub images: &'a mut ImageStore, - /// Caches layouting artifacts. - pub layout_cache: &'a mut LayoutCache, /// The default styles. pub styles: &'a StyleMap, /// The stack of imported files that led to evaluation of the current file. @@ -223,7 +185,6 @@ impl<'a> Vm<'a> { sources: &mut ctx.sources, fonts: &mut ctx.fonts, images: &mut ctx.images, - layout_cache: &mut ctx.layout_cache, styles: &ctx.styles, route: vec![], modules: HashMap::new(), diff --git a/src/library/align.rs b/src/library/align.rs index a7e6d3cb2..8ea9ddaf8 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -27,7 +27,7 @@ impl Layout for AlignNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { // 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(); @@ -40,20 +40,13 @@ impl Layout for AlignNode { // Layout the child. let mut frames = self.child.layout(vm, &pod, passed.chain(&styles))?; - for ((current, base), Constrained { item: frame, cts }) in - regions.iter().zip(&mut frames) - { + for (region, frame) in regions.iter().zip(&mut frames) { // Align in the target size. The target size depends on whether we // should expand. - let target = regions.expand.select(current, frame.size); + let target = regions.expand.select(region, frame.size); let default = Spec::new(Align::Left, Align::Top); let aligns = self.aligns.unwrap_or(default); Arc::make_mut(frame).resize(target, aligns); - - // Set constraints. - cts.expand = regions.expand; - cts.base = base.filter(cts.base.map_is_some()); - cts.exact = current.filter(regions.expand | cts.exact.map_is_some()); } Ok(frames) diff --git a/src/library/columns.rs b/src/library/columns.rs index 049fa8b91..bae23dd31 100644 --- a/src/library/columns.rs +++ b/src/library/columns.rs @@ -32,23 +32,23 @@ impl Layout for ColumnsNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { // Separating the infinite space into infinite columns does not make // much sense. - if regions.current.x.is_infinite() { + if regions.first.x.is_infinite() { return self.child.layout(vm, regions, styles); } // Determine the width of the gutter and each column. let columns = self.columns.get(); let gutter = styles.get(Self::GUTTER).resolve(regions.base.x); - let width = (regions.current.x - gutter * (columns - 1) as f64) / columns as f64; + let width = (regions.first.x - gutter * (columns - 1) as f64) / columns as f64; // Create the pod regions. let pod = Regions { - current: Size::new(width, regions.current.y), + first: Size::new(width, regions.first.y), base: Size::new(width, regions.base.y), - backlog: std::iter::once(®ions.current.y) + backlog: std::iter::once(®ions.first.y) .chain(regions.backlog.as_slice()) .flat_map(|&height| std::iter::repeat(height).take(columns)) .skip(1) @@ -66,18 +66,18 @@ impl Layout for ColumnsNode { let mut finished = vec![]; // Stitch together the columns for each region. - for (current, base) in regions.iter().take(total_regions) { + for region in regions.iter().take(total_regions) { // The height should be the parent height if the node shall expand. // Otherwise its the maximum column height for the frame. In that // case, the frame is first created with zero height and then // resized. - let height = if regions.expand.y { current.y } else { Length::zero() }; - let mut output = Frame::new(Size::new(regions.current.x, height)); + let height = if regions.expand.y { region.y } else { Length::zero() }; + let mut output = Frame::new(Size::new(regions.first.x, height)); let mut cursor = Length::zero(); for _ in 0 .. columns { let frame = match frames.next() { - Some(frame) => frame.item, + Some(frame) => frame, None => break, }; @@ -89,17 +89,14 @@ impl Layout for ColumnsNode { let x = if dir.is_positive() { cursor } else { - regions.current.x - cursor - width + regions.first.x - cursor - width }; output.push_frame(Point::with_x(x), frame); cursor += width + gutter; } - let mut cts = Constraints::new(regions.expand); - cts.base = base.map(Some); - cts.exact = current.map(Some); - finished.push(output.constrain(cts)); + finished.push(Arc::new(output)); } Ok(finished) diff --git a/src/library/flow.rs b/src/library/flow.rs index 135fd3276..bc3bf0f1a 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -31,7 +31,7 @@ impl Layout for FlowNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { let mut layouter = FlowLayouter::new(regions); for (child, map) in self.0.iter() { @@ -96,7 +96,7 @@ pub struct FlowLayouter { regions: Regions, /// Whether the flow should expand to fill the region. expand: Spec, - /// The full size of `regions.current` that was available before we started + /// The full size of `regions.size` that was available before we started /// subtracting. full: Size, /// The size used by the frames for the current region. @@ -106,7 +106,7 @@ pub struct FlowLayouter { /// Spacing and layouted nodes. items: Vec, /// Finished frames for previous regions. - finished: Vec>>, + finished: Vec>, } /// A prepared item in a flow layout. @@ -125,7 +125,7 @@ impl FlowLayouter { /// Create a new flow layouter. pub fn new(regions: &Regions) -> Self { let expand = regions.expand; - let full = regions.current; + let full = regions.first; // Disable vertical expansion for children. let mut regions = regions.clone(); @@ -148,8 +148,8 @@ impl FlowLayouter { SpacingKind::Linear(v) => { // Resolve the linear and limit it to the remaining space. let resolved = v.resolve(self.full.y); - let limited = resolved.min(self.regions.current.y); - self.regions.current.y -= limited; + let limited = resolved.min(self.regions.first.y); + self.regions.first.y -= limited; self.used.y += limited; self.items.push(FlowItem::Absolute(resolved)); } @@ -177,7 +177,7 @@ impl FlowLayouter { if let Some(placed) = node.downcast::() { if placed.out_of_flow() { let frame = node.layout(vm, &self.regions, styles)?.remove(0); - self.items.push(FlowItem::Placed(frame.item)); + self.items.push(FlowItem::Placed(frame)); return Ok(()); } } @@ -197,11 +197,11 @@ impl FlowLayouter { let len = frames.len(); for (i, frame) in frames.into_iter().enumerate() { // Grow our size, shrink the region and save the frame for later. - let size = frame.item.size; + let size = frame.size; self.used.y += size.y; self.used.x.set_max(size.x); - self.regions.current.y -= size.y; - self.items.push(FlowItem::Frame(frame.item, aligns)); + self.regions.first.y -= size.y; + self.items.push(FlowItem::Frame(frame, aligns)); if i + 1 < len { self.finish_region(); @@ -251,21 +251,16 @@ impl FlowLayouter { } } - // Generate tight constraints for now. - let mut cts = Constraints::new(self.expand); - cts.exact = self.full.map(Some); - cts.base = self.regions.base.map(Some); - // Advance to the next region. self.regions.next(); - self.full = self.regions.current; + self.full = self.regions.first; self.used = Size::zero(); self.fr = Fractional::zero(); - self.finished.push(output.constrain(cts)); + self.finished.push(Arc::new(output)); } /// Finish layouting and return the resulting frames. - pub fn finish(mut self) -> Vec>> { + pub fn finish(mut self) -> Vec> { if self.expand.y { while self.regions.backlog.len() > 0 { self.finish_region(); diff --git a/src/library/grid.rs b/src/library/grid.rs index 5f1edb1cc..fc62f8eb9 100644 --- a/src/library/grid.rs +++ b/src/library/grid.rs @@ -38,7 +38,7 @@ impl Layout for GridNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { // Prepare grid layout by unifying content and gutter tracks. let layouter = GridLayouter::new( self.tracks.as_deref(), @@ -105,8 +105,6 @@ pub struct GridLayouter<'a> { rcols: Vec, /// Rows in the current region. lrows: Vec, - /// Whether the grid itself should expand to fill the region. - expand: Spec, /// The full height of the current region. full: Length, /// The used-up size of the current region. The horizontal size is @@ -114,10 +112,8 @@ pub struct GridLayouter<'a> { used: Size, /// The sum of fractional ratios in the current region. fr: Fractional, - /// Constraints for the active region. - cts: Constraints, /// Frames for finished regions. - finished: Vec>>, + finished: Vec>, } /// Produced by initial row layout, auto and linear rows are already finished, @@ -177,8 +173,7 @@ impl<'a> GridLayouter<'a> { cols.pop(); rows.pop(); - let expand = regions.expand; - let full = regions.current.y; + let full = regions.first.y; let rcols = vec![Length::zero(); cols.len()]; let lrows = vec![]; @@ -195,17 +190,15 @@ impl<'a> GridLayouter<'a> { styles, rcols, lrows, - expand, full, used: Size::zero(), fr: Fractional::zero(), - cts: Constraints::new(expand), finished: vec![], } } /// Determines the columns sizes and then layouts the grid row-by-row. - pub fn layout(mut self, vm: &mut Vm) -> TypResult>>> { + pub fn layout(mut self, vm: &mut Vm) -> TypResult>> { self.measure_columns(vm)?; for y in 0 .. self.rows.len() { @@ -219,7 +212,6 @@ impl<'a> GridLayouter<'a> { 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)); self.fr += v; } @@ -232,22 +224,6 @@ impl<'a> GridLayouter<'a> { /// Determine all column sizes. fn measure_columns(&mut self, vm: &mut Vm) -> TypResult<()> { - enum Case { - /// The column sizing is only determined by specified linear sizes. - PurelyLinear, - /// The column sizing would be affected by the region size if it was - /// smaller. - Fitting, - /// The column sizing is affected by the region size. - Exact, - /// The column sizing would be affected by the region size if it was - /// larger. - Overflowing, - } - - // The different cases affecting constraints. - let mut case = Case::PurelyLinear; - // Sum of sizes of resolved linear tracks. let mut linear = Length::zero(); @@ -258,23 +234,18 @@ impl<'a> GridLayouter<'a> { // fractional tracks. for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { match col { - TrackSizing::Auto => { - case = Case::Fitting; - } + TrackSizing::Auto => {} TrackSizing::Linear(v) => { let resolved = v.resolve(self.regions.base.x); *rcol = resolved; linear += resolved; } - TrackSizing::Fractional(v) => { - case = Case::Fitting; - fr += v; - } + TrackSizing::Fractional(v) => fr += v, } } // Size that is not used by fixed-size columns. - let available = self.regions.current.x - linear; + let available = self.regions.first.x - linear; if available >= Length::zero() { // Determine size of auto columns. let (auto, count) = self.measure_auto_columns(vm, available)?; @@ -285,25 +256,10 @@ impl<'a> GridLayouter<'a> { if remaining >= Length::zero() { if !fr.is_zero() { self.grow_fractional_columns(remaining, fr); - case = Case::Exact; } } else { self.shrink_auto_columns(available, count); - case = Case::Exact; } - } else if matches!(case, Case::Fitting) { - case = Case::Overflowing; - } - - // Children could depend on base. - self.cts.base = self.regions.base.map(Some); - - // Set constraints depending on the case we hit. - match case { - Case::PurelyLinear => {} - Case::Fitting => self.cts.min.x = Some(self.used.x), - Case::Exact => self.cts.exact.x = Some(self.regions.current.x), - Case::Overflowing => self.cts.max.x = Some(linear), } // Sum up the resolved column sizes once here. @@ -342,7 +298,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); resolved.set_max(frame.size.x); } } @@ -403,7 +359,7 @@ impl<'a> GridLayouter<'a> { for (x, &rcol) in self.rcols.iter().enumerate() { if let Some(node) = self.cell(x, y) { let mut pod = self.regions.clone(); - pod.current.x = rcol; + pod.first.x = rcol; // All widths should be `rcol` except the base for auto columns. if self.cols[x] == TrackSizing::Auto { @@ -413,7 +369,7 @@ impl<'a> GridLayouter<'a> { let mut sizes = node .layout(vm, &pod, self.styles)? .into_iter() - .map(|frame| frame.item.size.y); + .map(|frame| frame.size.y); // For each region, we want to know the maximum height any // column requires. @@ -443,10 +399,8 @@ impl<'a> GridLayouter<'a> { // eaten up by any fr rows. if self.fr.is_zero() { let len = resolved.len(); - for (target, (current, _)) in - resolved[.. len - 1].iter_mut().zip(self.regions.iter()) - { - target.set_max(current.y); + for (region, target) in self.regions.iter().zip(&mut resolved[.. len - 1]) { + target.set_max(region.y); } } @@ -456,7 +410,6 @@ impl<'a> GridLayouter<'a> { 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)?; } } @@ -472,8 +425,7 @@ impl<'a> GridLayouter<'a> { // 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); + while !self.regions.first.y.fits(height) && !self.regions.in_last() { self.finish_region(vm)?; // Don't skip multiple regions for gutter and don't push a row. @@ -509,7 +461,7 @@ impl<'a> GridLayouter<'a> { let pod = Regions::one(size, base, Spec::splat(true)); let frame = node.layout(vm, &pod, self.styles)?.remove(0); - output.push_frame(pos, frame.item); + output.push_frame(pos, frame); } pos.x += rcol; @@ -540,7 +492,7 @@ impl<'a> GridLayouter<'a> { let mut pos = Point::zero(); for (x, &rcol) in self.rcols.iter().enumerate() { if let Some(node) = self.cell(x, y) { - pod.current.x = rcol; + pod.first.x = rcol; // All widths should be `rcol` except the base for auto columns. if self.cols[x] == TrackSizing::Auto { @@ -550,7 +502,7 @@ impl<'a> GridLayouter<'a> { // Push the layouted frames into the individual output frames. let frames = node.layout(vm, &pod, self.styles)?; for (output, frame) in outputs.iter_mut().zip(frames) { - output.push_frame(pos, frame.item); + output.push_frame(pos, frame); } } @@ -562,7 +514,7 @@ impl<'a> GridLayouter<'a> { /// Push a row frame into the current region. fn push_row(&mut self, frame: Frame) { - self.regions.current.y -= frame.size.y; + self.regions.first.y -= frame.size.y; self.used.y += frame.size.y; self.lrows.push(Row::Frame(frame)); } @@ -574,9 +526,6 @@ impl<'a> GridLayouter<'a> { let mut size = self.used; if self.fr.get() > 0.0 && self.full.is_finite() { size.y = self.full; - self.cts.exact.y = Some(self.full); - } else { - self.cts.min.y = Some(size.y.min(self.full)); } // The frame for the region. @@ -599,13 +548,11 @@ impl<'a> GridLayouter<'a> { pos.y += height; } - self.cts.base = self.regions.base.map(Some); - self.finished.push(output.constrain(self.cts)); + self.finished.push(Arc::new(output)); self.regions.next(); - self.full = self.regions.current.y; + self.full = self.regions.first.y; self.used.y = Length::zero(); self.fr = Fractional::zero(); - self.cts = Constraints::new(self.expand); Ok(()) } diff --git a/src/library/hide.rs b/src/library/hide.rs index ea2227f12..dcc730886 100644 --- a/src/library/hide.rs +++ b/src/library/hide.rs @@ -19,11 +19,11 @@ impl Layout for HideNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { let mut frames = self.0.layout(vm, regions, styles)?; // Clear the frames. - for Constrained { item: frame, .. } in &mut frames { + for frame in &mut frames { *frame = Arc::new(Frame { elements: vec![], ..**frame }); } diff --git a/src/library/image.rs b/src/library/image.rs index 05877126d..000aec739 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -39,24 +39,24 @@ impl Layout for ImageNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { let img = vm.images.get(self.0); let pxw = img.width() as f64; let pxh = img.height() as f64; let px_ratio = pxw / pxh; // Find out whether the image is wider or taller than the target size. - let &Regions { current, expand, .. } = regions; - let current_ratio = current.x / current.y; - let wide = px_ratio > current_ratio; + let &Regions { first, expand, .. } = regions; + let region_ratio = first.x / first.y; + let wide = px_ratio > region_ratio; // The space into which the image will be placed according to its fit. let target = if expand.x && expand.y { - current - } else if expand.x || (!expand.y && wide && current.x.is_finite()) { - Size::new(current.x, current.y.min(current.x.safe_div(px_ratio))) - } else if current.y.is_finite() { - Size::new(current.x.min(current.y * px_ratio), current.y) + first + } else if expand.x || (!expand.y && wide && first.x.is_finite()) { + Size::new(first.x, first.y.min(first.x.safe_div(px_ratio))) + } else if first.y.is_finite() { + Size::new(first.x.min(first.y * px_ratio), first.y) } else { Size::new(Length::pt(pxw), Length::pt(pxh)) }; @@ -91,7 +91,7 @@ impl Layout for ImageNode { frame.link(url); } - Ok(vec![frame.constrain(Constraints::tight(regions))]) + Ok(vec![Arc::new(frame)]) } } diff --git a/src/library/mod.rs b/src/library/mod.rs index ad66f2c3a..980f45c84 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -66,14 +66,12 @@ pub mod prelude { pub use crate::diag::{with_alternative, At, StrResult, TypResult}; pub use crate::eval::{ - Arg, Args, Cast, Construct, Func, Merge, Property, Scope, Set, Show, ShowNode, - Smart, StyleChain, StyleMap, StyleVec, Template, Value, + Arg, Args, Cast, Construct, Func, Layout, LayoutNode, Merge, Property, Regions, + Scope, Set, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Template, + Value, }; pub use crate::frame::*; pub use crate::geom::*; - pub use crate::layout::{ - Constrain, Constrained, Constraints, Layout, LayoutNode, Regions, - }; pub use crate::syntax::{Span, Spanned}; pub use crate::util::{EcoString, OptionExt}; pub use crate::Vm; diff --git a/src/library/pad.rs b/src/library/pad.rs index ca45a1cae..05b658bde 100644 --- a/src/library/pad.rs +++ b/src/library/pad.rs @@ -33,14 +33,12 @@ impl Layout for PadNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { // Layout child into padded regions. let pod = regions.map(|size| shrink(size, self.padding)); let mut frames = self.child.layout(vm, &pod, styles)?; - for ((current, base), Constrained { item: frame, cts }) in - regions.iter().zip(&mut frames) - { + for frame in &mut frames { // Apply the padding inversely such that the grown size padded // yields the frame's size. let padded = grow(frame.size, self.padding); @@ -51,19 +49,6 @@ impl Layout for PadNode { let frame = Arc::make_mut(frame); frame.size = padded; frame.translate(offset); - - // Set exact and base constraints if the child had them. Also set - // base if our padding is relative. - let is_rel = self.padding.sum_by_axis().map(Linear::is_relative); - cts.exact = current.filter(cts.exact.map_is_some()); - cts.base = base.filter(is_rel | cts.base.map_is_some()); - - // Inflate min and max contraints by the padding. - for spec in [&mut cts.min, &mut cts.max] { - spec.as_mut() - .zip(padding.sum_by_axis()) - .map(|(s, p)| s.as_mut().map(|v| *v += p)); - } } Ok(frames) diff --git a/src/library/page.rs b/src/library/page.rs index 07266ec6a..718234c64 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -115,15 +115,12 @@ impl PageNode { // Layout the child. let regions = Regions::repeat(size, size, size.map(Length::is_finite)); - let mut frames: Vec<_> = child - .layout(vm, ®ions, styles)? - .into_iter() - .map(|c| c.item) - .collect(); + let mut frames = child.layout(vm, ®ions, styles)?; let header = styles.get_ref(Self::HEADER); let footer = styles.get_ref(Self::FOOTER); + // Realize header and footer. for frame in &mut frames { let size = frame.size; let padding = padding.resolve(size); @@ -136,7 +133,7 @@ impl PageNode { let w = size.x - padding.left - padding.right; let area = Size::new(w, h); let pod = Regions::one(area, area, area.map(Length::is_finite)); - let sub = template.layout(vm, &pod, styles)?.remove(0).item; + let sub = template.layout(vm, &pod, styles)?.remove(0); Arc::make_mut(frame).push_frame(pos, sub); } } diff --git a/src/library/par.rs b/src/library/par.rs index 857d986fd..eab67c536 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -86,7 +86,7 @@ impl Layout for ParNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { // Collect all text into one string used for BiDi analysis. let text = self.collect_text(); let level = Level::from_dir(styles.get(Self::DIR)); @@ -113,7 +113,7 @@ impl Layout for ParNode { // If the line doesn't fit anymore, we push the last fitting attempt // into the stack and rebuild the line from its end. The resulting // line cannot be broken up further. - if !regions.current.x.fits(line.size.x) { + if !regions.first.x.fits(line.size.x) { if let Some((last_line, last_end)) = last.take() { lines.push(last_line); start = last_end; @@ -124,7 +124,7 @@ impl Layout for ParNode { // Finish the current line if there is a mandatory line break (i.e. // due to "\n") or if the line doesn't fit horizontally already // since no shorter line will be possible. - if mandatory || !regions.current.x.fits(line.size.x) { + if mandatory || !regions.first.x.fits(line.size.x) { lines.push(line); start = end; last = None; @@ -139,7 +139,7 @@ impl Layout for ParNode { // Determine the paragraph's width: Fit to width if we shoudn't expand // and there's no fractional spacing. - let mut width = regions.current.x; + let mut width = regions.first.x; if !regions.expand.x && lines.iter().all(|line| line.fr.is_zero()) { width = lines.iter().map(|line| line.size.x).max().unwrap_or_default(); } @@ -149,15 +149,13 @@ impl Layout for ParNode { let mut finished = vec![]; let mut first = true; let mut output = Frame::new(Size::with_x(width)); - let mut cts = Constraints::tight(®ions); // Stack the lines into one frame per region. for line in lines { - while !regions.current.y.fits(line.size.y) && !regions.in_last() { - finished.push(output.constrain(cts)); + while !regions.first.y.fits(line.size.y) && !regions.in_last() { + finished.push(Arc::new(output)); output = Frame::new(Size::with_x(width)); regions.next(); - cts = Constraints::tight(®ions); first = true; } @@ -170,11 +168,11 @@ impl Layout for ParNode { output.size.y += frame.size.y; output.merge_frame(pos, frame); - regions.current.y -= line.size.y + leading; + regions.first.y -= line.size.y + leading; first = false; } - finished.push(output.constrain(cts)); + finished.push(Arc::new(output)); Ok(finished) } } @@ -316,7 +314,7 @@ impl<'a> ParLayout<'a> { } ParChild::Spacing(kind) => match *kind { SpacingKind::Linear(v) => { - let resolved = v.resolve(regions.current.x); + let resolved = v.resolve(regions.first.x); items.push(ParItem::Absolute(resolved)); ranges.push(range); } @@ -326,10 +324,10 @@ impl<'a> ParLayout<'a> { } }, ParChild::Node(node) => { - let size = Size::new(regions.current.x, regions.base.y); + let size = Size::new(regions.first.x, regions.base.y); let pod = Regions::one(size, regions.base, Spec::splat(false)); let frame = node.layout(vm, &pod, styles)?.remove(0); - items.push(ParItem::Frame(Arc::take(frame.item))); + items.push(ParItem::Frame(Arc::take(frame))); ranges.push(range); } } diff --git a/src/library/place.rs b/src/library/place.rs index 9bcd3c37f..34746d5b9 100644 --- a/src/library/place.rs +++ b/src/library/place.rs @@ -26,11 +26,11 @@ impl Layout for PlaceNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { let out_of_flow = self.out_of_flow(); // The pod is the base area of the region because for absolute - // placement we don't really care about the already used area (current). + // placement we don't really care about the already used area. let pod = { let finite = regions.base.map(Length::is_finite); let expand = finite & (regions.expand | out_of_flow); @@ -38,26 +38,20 @@ impl Layout for PlaceNode { }; 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 // space in our parent. Otherwise, respect the expand settings. - let target = regions.expand.select(regions.current, Size::zero()); + let frame = &mut frames[0]; + let target = regions.expand.select(regions.first, Size::zero()); Arc::make_mut(frame).resize(target, Align::LEFT_TOP); - // Set base constraint because our pod size is base and exact - // constraints if we needed to expand or offset. - *cts = Constraints::new(regions.expand); - cts.base = regions.base.map(Some); - cts.exact = regions.current.filter(regions.expand | out_of_flow); - Ok(frames) } } impl PlaceNode { /// Whether this node wants to be placed relative to its its parent's base - /// origin. instead of relative to the parent's current flow/cursor + /// origin. Instead of relative to the parent's current flow/cursor /// position. pub fn out_of_flow(&self) -> bool { self.0 diff --git a/src/library/shape.rs b/src/library/shape.rs index 518254ce7..955aef569 100644 --- a/src/library/shape.rs +++ b/src/library/shape.rs @@ -49,7 +49,7 @@ impl Layout for ShapeNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { let mut frames; if let Some(child) = &self.0 { let mut padding = styles.get(Self::PADDING); @@ -60,48 +60,47 @@ impl Layout for ShapeNode { // Pad the child. let child = child.clone().padded(Sides::splat(padding)); - let mut pod = Regions::one(regions.current, regions.base, regions.expand); + let mut pod = Regions::one(regions.first, regions.base, regions.expand); frames = child.layout(vm, &pod, styles)?; // Relayout with full expansion into square region to make sure // the result is really a square or circle. if is_quadratic(S) { let length = if regions.expand.x || regions.expand.y { - let target = regions.expand.select(regions.current, Size::zero()); + let target = regions.expand.select(regions.first, Size::zero()); target.x.max(target.y) } else { - let size = frames[0].item.size; + let size = frames[0].size; let desired = size.x.max(size.y); - desired.min(regions.current.x).min(regions.current.y) + desired.min(regions.first.x).min(regions.first.y) }; - pod.current = Size::splat(length); + pod.first = Size::splat(length); pod.expand = Spec::splat(true); frames = child.layout(vm, &pod, styles)?; - frames[0].cts = Constraints::tight(regions); } } else { // The default size that a shape takes on if it has no child and // enough space. let mut size = - Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.current); + Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.first); if is_quadratic(S) { let length = if regions.expand.x || regions.expand.y { - let target = regions.expand.select(regions.current, Size::zero()); + let target = regions.expand.select(regions.first, Size::zero()); target.x.max(target.y) } else { size.x.min(size.y) }; size = Size::splat(length); } else { - size = regions.expand.select(regions.current, size); + size = regions.expand.select(regions.first, size); } - frames = vec![Frame::new(size).constrain(Constraints::tight(regions))]; + frames = vec![Arc::new(Frame::new(size))]; } - let frame = Arc::make_mut(&mut frames[0].item); + let frame = Arc::make_mut(&mut frames[0]); // Add fill and/or stroke. let fill = styles.get(Self::FILL); diff --git a/src/library/stack.rs b/src/library/stack.rs index c7985d872..6768a0a0a 100644 --- a/src/library/stack.rs +++ b/src/library/stack.rs @@ -31,7 +31,7 @@ impl Layout for StackNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { let mut layouter = StackLayouter::new(self.dir, regions); // Spacing to insert before the next node. @@ -106,7 +106,7 @@ pub struct StackLayouter { /// fractional spacing. items: Vec, /// Finished frames for previous regions. - finished: Vec>>, + finished: Vec>, } /// A prepared item in a stack layout. @@ -124,7 +124,7 @@ impl StackLayouter { pub fn new(dir: Dir, regions: &Regions) -> Self { let axis = dir.axis(); let expand = regions.expand; - let full = regions.current; + let full = regions.first; // Disable expansion along the block axis for children. let mut regions = regions.clone(); @@ -149,7 +149,7 @@ impl StackLayouter { SpacingKind::Linear(v) => { // Resolve the linear and limit it to the remaining space. let resolved = v.resolve(self.regions.base.get(self.axis)); - let remaining = self.regions.current.get_mut(self.axis); + let remaining = self.regions.first.get_mut(self.axis); let limited = resolved.min(*remaining); *remaining -= limited; self.used.main += limited; @@ -183,11 +183,11 @@ impl StackLayouter { let len = frames.len(); for (i, frame) in frames.into_iter().enumerate() { // Grow our size, shrink the region and save the frame for later. - let size = frame.item.size.to_gen(self.axis); + let size = frame.size.to_gen(self.axis); self.used.main += size.main; self.used.cross.set_max(size.cross); - *self.regions.current.get_mut(self.axis) -= size.main; - self.items.push(StackItem::Frame(frame.item, align)); + *self.regions.first.get_mut(self.axis) -= size.main; + self.items.push(StackItem::Frame(frame, align)); if i + 1 < len { self.finish_region(); @@ -245,21 +245,16 @@ impl StackLayouter { } } - // Generate tight constraints for now. - let mut cts = Constraints::new(self.expand); - cts.exact = self.full.map(Some); - cts.base = self.regions.base.map(Some); - // Advance to the next region. self.regions.next(); - self.full = self.regions.current; + self.full = self.regions.first; self.used = Gen::zero(); self.fr = Fractional::zero(); - self.finished.push(output.constrain(cts)); + self.finished.push(Arc::new(output)); } /// Finish layouting and return the resulting frames. - pub fn finish(mut self) -> Vec>> { + pub fn finish(mut self) -> Vec> { self.finish_region(); self.finished } diff --git a/src/library/transform.rs b/src/library/transform.rs index 5cdb2f6f6..84553fb58 100644 --- a/src/library/transform.rs +++ b/src/library/transform.rs @@ -49,11 +49,11 @@ impl Layout for TransformNode { vm: &mut Vm, regions: &Regions, styles: StyleChain, - ) -> TypResult>>> { + ) -> TypResult>> { let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON); let mut frames = self.child.layout(vm, regions, styles)?; - for Constrained { item: frame, .. } in &mut frames { + for frame in &mut frames { let Spec { x, y } = origin.zip(frame.size).map(|(o, s)| o.resolve(s)); let transform = Transform::translation(x, y) .pre_concat(self.transform) diff --git a/tests/typeset.rs b/tests/typeset.rs index 00075166c..e0530f9bc 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -1,11 +1,10 @@ use std::env; use std::ffi::OsStr; -use std::fs::{self, File}; +use std::fs; use std::ops::Range; use std::path::Path; use std::sync::Arc; -use filedescriptor::{FileDescriptor, StdioDescriptor::*}; use tiny_skia as sk; use walkdir::WalkDir; @@ -16,7 +15,7 @@ use typst::geom::{Length, RgbaColor}; use typst::library::{PageNode, TextNode}; use typst::loading::FsLoader; use typst::parse::Scanner; -use typst::source::{SourceFile, SourceId}; +use typst::source::SourceFile; use typst::syntax::Span; use typst::{Context, Vm}; @@ -269,19 +268,16 @@ 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.typeset(id) { - Ok(mut frames) => { - ok &= test_incremental(ctx, i, id, &frames); - - if !compare_ref { - frames.clear(); - } - - (frames, vec![]) - } + let (mut frames, mut errors) = match vm.typeset(id) { + Ok(frames) => (frames, vec![]), Err(errors) => (vec![], *errors), }; + // Don't retain frames if we don't wanna compare with reference images. + if !compare_ref { + frames.clear(); + } + // TODO: Also handle errors from other files. errors.retain(|error| error.span.source == id); for error in &mut errors { @@ -467,53 +463,6 @@ fn test_reparse(src: &str, i: usize, rng: &mut LinearShift) -> bool { ok } -fn test_incremental( - ctx: &mut Context, - i: usize, - id: SourceId, - frames: &[Arc], -) -> bool { - let mut ok = true; - - let reference = ctx.layout_cache.clone(); - for level in 0 .. reference.levels() { - ctx.layout_cache = reference.clone(); - ctx.layout_cache.retain(|x| x == level); - if ctx.layout_cache.is_empty() { - continue; - } - - ctx.layout_cache.turnaround(); - - let cached = silenced(|| ctx.typeset(id).unwrap()); - let total = reference.levels() - 1; - let misses = ctx - .layout_cache - .entries() - .filter(|e| e.level() == level && !e.hit() && e.age() == 2) - .count(); - - if misses > 0 { - println!( - " Subtest {i} relayout had {misses} cache misses on level {level} of {total} ❌", - ); - ok = false; - } - - if cached != frames { - println!( - " Subtest {i} relayout differs from clean pass on level {level} ❌", - ); - ok = false; - } - } - - ctx.layout_cache = reference; - ctx.layout_cache.turnaround(); - - ok -} - /// Draw all frames into one image with padding in between. fn render(ctx: &mut Context, frames: &[Arc]) -> sk::Pixmap { let pixel_per_pt = 2.0; @@ -582,21 +531,6 @@ fn render_links( } } -/// Disable stdout and stderr during execution of `f`. -fn silenced(f: F) -> T -where - F: FnOnce() -> T, -{ - let path = if cfg!(windows) { "NUL" } else { "/dev/null" }; - let null = File::create(path).unwrap(); - let stderr = FileDescriptor::redirect_stdio(&null, Stderr).unwrap(); - let stdout = FileDescriptor::redirect_stdio(&null, Stdout).unwrap(); - let result = f(); - FileDescriptor::redirect_stdio(&stderr, Stderr).unwrap(); - FileDescriptor::redirect_stdio(&stdout, Stdout).unwrap(); - result -} - /// This is an Linear-feedback shift register using XOR as its shifting /// function. It can be used as PRNG. struct LinearShift(u64);