mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Remove layout cache
This commit is contained in:
parent
c7e52f2048
commit
2bf32c51bc
32
Cargo.lock
generated
32
Cargo.lock
generated
@ -190,17 +190,6 @@ dependencies = [
|
|||||||
"regex",
|
"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]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
@ -779,26 +768,6 @@ dependencies = [
|
|||||||
"winapi-util",
|
"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]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@ -843,7 +812,6 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
"dirs",
|
"dirs",
|
||||||
"filedescriptor",
|
|
||||||
"flate2",
|
"flate2",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
"iai",
|
"iai",
|
||||||
|
@ -64,7 +64,6 @@ memmap2 = { version = "0.5", optional = true }
|
|||||||
walkdir = { version = "2", optional = true }
|
walkdir = { version = "2", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
filedescriptor = "0.8"
|
|
||||||
iai = { git = "https://github.com/reknih/iai" }
|
iai = { git = "https://github.com/reknih/iai" }
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
|
|
||||||
|
@ -1,22 +1,14 @@
|
|||||||
//! Layouting infrastructure.
|
//! Layouting infrastructure.
|
||||||
|
|
||||||
mod constraints;
|
|
||||||
mod incremental;
|
|
||||||
mod regions;
|
|
||||||
|
|
||||||
pub use constraints::*;
|
|
||||||
pub use incremental::*;
|
|
||||||
pub use regions::*;
|
|
||||||
|
|
||||||
use std::any::{Any, TypeId};
|
use std::any::{Any, TypeId};
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::Hash;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::eval::StyleChain;
|
use crate::eval::StyleChain;
|
||||||
use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
|
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::library::{AlignNode, PadNode, TransformNode, MOVE};
|
||||||
use crate::util::Prehashed;
|
use crate::util::Prehashed;
|
||||||
use crate::Vm;
|
use crate::Vm;
|
||||||
@ -32,7 +24,7 @@ pub trait Layout {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>>;
|
) -> TypResult<Vec<Arc<Frame>>>;
|
||||||
|
|
||||||
/// Convert to a packed node.
|
/// Convert to a packed node.
|
||||||
fn pack(self) -> LayoutNode
|
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<Length>,
|
||||||
|
/// 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<Length>,
|
||||||
|
/// Whether nodes should expand to fill the regions instead of shrinking to
|
||||||
|
/// fit the content.
|
||||||
|
pub expand: Spec<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Regions {
|
||||||
|
/// Create a new region sequence with exactly one region.
|
||||||
|
pub fn one(size: Size, base: Size, expand: Spec<bool>) -> 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<bool>) -> 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<F>(&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::<Vec<_>>()
|
||||||
|
.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<Item = Size> + '_ {
|
||||||
|
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.
|
/// A type-erased layouting node with a precomputed hash.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub struct LayoutNode(Arc<Prehashed<dyn Bounds>>);
|
pub struct LayoutNode(Arc<Prehashed<dyn Bounds>>);
|
||||||
@ -136,41 +224,9 @@ impl Layout for LayoutNode {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let styles = styles.barred(self.id());
|
// TODO(query)
|
||||||
|
self.0.layout(vm, regions, 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::<Vec<_>>(),
|
|
||||||
);
|
|
||||||
panic!("constraints did not match regions they were created for");
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.layout_cache.insert(hash, entry);
|
|
||||||
Ok(frames)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack(self) -> LayoutNode {
|
fn pack(self) -> LayoutNode {
|
||||||
@ -221,11 +277,10 @@ impl Layout for EmptyNode {
|
|||||||
_: &mut Vm,
|
_: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
_: StyleChain,
|
_: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let size = regions.expand.select(regions.current, Size::zero());
|
Ok(vec![Arc::new(Frame::new(
|
||||||
let mut cts = Constraints::new(regions.expand);
|
regions.expand.select(regions.first, Size::zero()),
|
||||||
cts.exact = regions.current.filter(regions.expand);
|
))])
|
||||||
Ok(vec![Frame::new(size).constrain(cts)])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,10 +299,7 @@ impl Layout for SizedNode {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let is_auto = self.sizing.map_is_none();
|
|
||||||
let is_rel = self.sizing.map(|s| s.map_or(false, Linear::is_relative));
|
|
||||||
|
|
||||||
// The "pod" is the region into which the child will be layouted.
|
// The "pod" is the region into which the child will be layouted.
|
||||||
let pod = {
|
let pod = {
|
||||||
// Resolve the sizing to a concrete size.
|
// Resolve the sizing to a concrete size.
|
||||||
@ -255,30 +307,25 @@ impl Layout for SizedNode {
|
|||||||
.sizing
|
.sizing
|
||||||
.zip(regions.base)
|
.zip(regions.base)
|
||||||
.map(|(s, b)| s.map(|v| v.resolve(b)))
|
.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
|
// Select the appropriate base and expansion for the child depending
|
||||||
// on whether it is automatically or linearly sized.
|
// 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 base = is_auto.select(regions.base, size);
|
||||||
let expand = regions.expand | !is_auto;
|
let expand = regions.expand | !is_auto;
|
||||||
|
|
||||||
Regions::one(size, base, expand)
|
Regions::one(size, base, expand)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Layout the child.
|
||||||
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.
|
// 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);
|
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)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -298,9 +345,9 @@ impl Layout for FillNode {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
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 {
|
for frame in &mut frames {
|
||||||
let shape = Shape::filled(Geometry::Rect(frame.size), self.fill);
|
let shape = Shape::filled(Geometry::Rect(frame.size), self.fill);
|
||||||
Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
|
Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
|
||||||
}
|
}
|
||||||
@ -323,9 +370,9 @@ impl Layout for StrokeNode {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
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 {
|
for frame in &mut frames {
|
||||||
let shape = Shape::stroked(Geometry::Rect(frame.size), self.stroke);
|
let shape = Shape::stroked(Geometry::Rect(frame.size), self.stroke);
|
||||||
Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
|
Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
|
||||||
}
|
}
|
@ -12,6 +12,7 @@ mod capture;
|
|||||||
mod class;
|
mod class;
|
||||||
mod collapse;
|
mod collapse;
|
||||||
mod func;
|
mod func;
|
||||||
|
mod layout;
|
||||||
mod ops;
|
mod ops;
|
||||||
mod scope;
|
mod scope;
|
||||||
mod show;
|
mod show;
|
||||||
@ -23,6 +24,7 @@ pub use class::*;
|
|||||||
pub use collapse::*;
|
pub use collapse::*;
|
||||||
pub use dict::*;
|
pub use dict::*;
|
||||||
pub use func::*;
|
pub use func::*;
|
||||||
|
pub use layout::*;
|
||||||
pub use scope::*;
|
pub use scope::*;
|
||||||
pub use show::*;
|
pub use show::*;
|
||||||
pub use styles::*;
|
pub use styles::*;
|
||||||
|
@ -7,10 +7,10 @@ use std::ops::{Add, AddAssign};
|
|||||||
use typed_arena::Arena;
|
use typed_arena::Arena;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
CollapsingBuilder, Interruption, Property, Show, ShowNode, StyleMap, StyleVecBuilder,
|
CollapsingBuilder, Interruption, Layout, LayoutNode, Property, Show, ShowNode,
|
||||||
|
StyleMap, StyleVecBuilder,
|
||||||
};
|
};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::layout::{Layout, LayoutNode};
|
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::{
|
use crate::library::{
|
||||||
DecoNode, FlowChild, FlowNode, ListItem, ListKind, ListNode, PageNode, ParChild,
|
DecoNode, FlowChild, FlowNode, ListItem, ListKind, ListNode, PageNode, ParChild,
|
||||||
@ -240,7 +240,7 @@ impl Layout for Template {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let sya = Arena::new();
|
let sya = Arena::new();
|
||||||
let tpa = Arena::new();
|
let tpa = Arena::new();
|
||||||
|
|
||||||
|
@ -4,10 +4,9 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::Arc;
|
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::diag::StrResult;
|
||||||
use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor};
|
use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor};
|
||||||
use crate::layout::Layout;
|
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
//! Rendering into raster images.
|
//! Rendering into raster images.
|
||||||
|
|
||||||
use std::collections::{hash_map::Entry, HashMap};
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
use image::{GenericImageView, Rgba};
|
use image::{GenericImageView, Rgba};
|
||||||
@ -8,26 +7,12 @@ use tiny_skia as sk;
|
|||||||
use ttf_parser::{GlyphId, OutlineBuilder};
|
use ttf_parser::{GlyphId, OutlineBuilder};
|
||||||
use usvg::FitTo;
|
use usvg::FitTo;
|
||||||
|
|
||||||
use crate::font::{Face, FaceId};
|
use crate::font::Face;
|
||||||
use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text};
|
use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text};
|
||||||
use crate::geom::{self, Length, Paint, PathElement, Size, Transform};
|
use crate::geom::{self, Length, Paint, PathElement, Size, Transform};
|
||||||
use crate::image::{Image, RasterImage, Svg};
|
use crate::image::{Image, RasterImage, Svg};
|
||||||
use crate::Context;
|
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.
|
/// Export a frame into a rendered image.
|
||||||
///
|
///
|
||||||
/// This renders the frame at the given number of pixels per printer's point and
|
/// This renders the frame at the given number of pixels per printer's point and
|
||||||
@ -131,7 +116,6 @@ fn render_text(
|
|||||||
text: &Text,
|
text: &Text,
|
||||||
) {
|
) {
|
||||||
let face = ctx.fonts.get(text.face_id);
|
let face = ctx.fonts.get(text.face_id);
|
||||||
let cache = &mut ctx.render_cache;
|
|
||||||
|
|
||||||
let mut x = 0.0;
|
let mut x = 0.0;
|
||||||
for glyph in &text.glyphs {
|
for glyph in &text.glyphs {
|
||||||
@ -141,7 +125,7 @@ fn render_text(
|
|||||||
|
|
||||||
render_svg_glyph(canvas, ts, mask, text, face, id)
|
render_svg_glyph(canvas, ts, mask, text, face, id)
|
||||||
.or_else(|| render_bitmap_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();
|
x += glyph.x_advance.resolve(text.size).to_f32();
|
||||||
}
|
}
|
||||||
@ -227,7 +211,6 @@ fn render_outline_glyph(
|
|||||||
canvas: &mut sk::Pixmap,
|
canvas: &mut sk::Pixmap,
|
||||||
ts: sk::Transform,
|
ts: sk::Transform,
|
||||||
mask: Option<&sk::ClipMask>,
|
mask: Option<&sk::ClipMask>,
|
||||||
cache: &mut RenderCache,
|
|
||||||
text: &Text,
|
text: &Text,
|
||||||
face: &Face,
|
face: &Face,
|
||||||
id: GlyphId,
|
id: GlyphId,
|
||||||
@ -255,15 +238,10 @@ fn render_outline_glyph(
|
|||||||
return Some(());
|
return Some(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(query)
|
||||||
// Try to retrieve a prepared glyph or prepare it from scratch if it
|
// Try to retrieve a prepared glyph or prepare it from scratch if it
|
||||||
// doesn't exist, yet.
|
// doesn't exist, yet.
|
||||||
let glyph = match cache.glyphs.entry((text.face_id, id)) {
|
let glyph = pixglyph::Glyph::load(face.ttf(), id)?;
|
||||||
Entry::Occupied(entry) => entry.into_mut(),
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
let glyph = pixglyph::Glyph::load(face.ttf(), id)?;
|
|
||||||
entry.insert(glyph)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Rasterize the glyph with `pixglyph`.
|
// Rasterize the glyph with `pixglyph`.
|
||||||
let bitmap = glyph.rasterize(ts.tx, ts.ty, ppem);
|
let bitmap = glyph.rasterize(ts.tx, ts.ty, ppem);
|
||||||
|
@ -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<Arc<Frame>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Constrain for Frame {
|
|
||||||
fn constrain(self, cts: Constraints) -> Constrained<Arc<Frame>> {
|
|
||||||
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<T> {
|
|
||||||
/// 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<T> Constrained<T> {
|
|
||||||
/// 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<Option<Length>>,
|
|
||||||
/// The maximum available length in the region.
|
|
||||||
pub max: Spec<Option<Length>>,
|
|
||||||
/// The available length in the region.
|
|
||||||
pub exact: Spec<Option<Length>>,
|
|
||||||
/// The base length of the region used for relative length resolution.
|
|
||||||
pub base: Spec<Option<Length>>,
|
|
||||||
/// The expand settings of the region.
|
|
||||||
pub expand: Spec<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Constraints {
|
|
||||||
/// Create a new region constraint.
|
|
||||||
pub fn new(expand: Spec<bool>) -> 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>) -> 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<Option<Length>>, size: Size, f: fn(Length, Length) -> bool) -> bool {
|
|
||||||
spec.zip(size).all(|&(opt, s)| opt.map_or(true, |m| f(m, s)))
|
|
||||||
}
|
|
@ -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<u64, Vec<FramesEntry>>,
|
|
||||||
/// 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<Item = &FramesEntry> + '_ {
|
|
||||||
self.frames.values().flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetch matching cached frames if there are any.
|
|
||||||
pub fn get(
|
|
||||||
&mut self,
|
|
||||||
hash: u64,
|
|
||||||
regions: &Regions,
|
|
||||||
) -> Option<Vec<Constrained<Arc<Frame>>>> {
|
|
||||||
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<F>(&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<Constrained<Arc<Frame>>>,
|
|
||||||
/// 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<Constrained<Arc<Frame>>>, 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<Vec<Constrained<Arc<Frame>>>> {
|
|
||||||
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::<usize>() + 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<Constrained<Arc<Frame>>> {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<Length>,
|
|
||||||
/// 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<Length>,
|
|
||||||
/// Whether nodes should expand to fill the regions instead of shrinking to
|
|
||||||
/// fit the content.
|
|
||||||
pub expand: Spec<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Regions {
|
|
||||||
/// Create a new region sequence with exactly one region.
|
|
||||||
pub fn one(size: Size, base: Size, expand: Spec<bool>) -> 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<bool>) -> 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<F>(&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::<Vec<_>>()
|
|
||||||
.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<Item = (Size, Size)> + '_ {
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
41
src/lib.rs
41
src/lib.rs
@ -44,7 +44,6 @@ pub mod font;
|
|||||||
pub mod frame;
|
pub mod frame;
|
||||||
pub mod geom;
|
pub mod geom;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod layout;
|
|
||||||
pub mod library;
|
pub mod library;
|
||||||
pub mod loading;
|
pub mod loading;
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
@ -58,11 +57,9 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::eval::{Eval, Module, Scope, Scopes, StyleMap};
|
use crate::eval::{Eval, Module, Scope, Scopes, StyleMap};
|
||||||
use crate::export::RenderCache;
|
|
||||||
use crate::font::FontStore;
|
use crate::font::FontStore;
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::image::ImageStore;
|
use crate::image::ImageStore;
|
||||||
use crate::layout::{EvictionPolicy, LayoutCache};
|
|
||||||
use crate::loading::Loader;
|
use crate::loading::Loader;
|
||||||
use crate::source::{SourceId, SourceStore};
|
use crate::source::{SourceId, SourceStore};
|
||||||
|
|
||||||
@ -76,10 +73,6 @@ pub struct Context {
|
|||||||
pub fonts: FontStore,
|
pub fonts: FontStore,
|
||||||
/// Stores decoded images.
|
/// Stores decoded images.
|
||||||
pub images: ImageStore,
|
pub images: ImageStore,
|
||||||
/// Caches layouting artifacts.
|
|
||||||
pub layout_cache: LayoutCache,
|
|
||||||
/// Caches rendering artifacts.
|
|
||||||
pub render_cache: RenderCache,
|
|
||||||
/// The standard library scope.
|
/// The standard library scope.
|
||||||
std: Scope,
|
std: Scope,
|
||||||
/// The default styles.
|
/// The default styles.
|
||||||
@ -115,11 +108,6 @@ impl Context {
|
|||||||
pub fn typeset(&mut self, id: SourceId) -> TypResult<Vec<Arc<Frame>>> {
|
pub fn typeset(&mut self, id: SourceId) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
Vm::new(self).typeset(id)
|
Vm::new(self).typeset(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Garbage-collect caches.
|
|
||||||
pub fn turnaround(&mut self) {
|
|
||||||
self.layout_cache.turnaround();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A builder for a [`Context`].
|
/// A builder for a [`Context`].
|
||||||
@ -128,8 +116,6 @@ impl Context {
|
|||||||
pub struct ContextBuilder {
|
pub struct ContextBuilder {
|
||||||
std: Option<Scope>,
|
std: Option<Scope>,
|
||||||
styles: Option<StyleMap>,
|
styles: Option<StyleMap>,
|
||||||
policy: EvictionPolicy,
|
|
||||||
max_size: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextBuilder {
|
impl ContextBuilder {
|
||||||
@ -146,21 +132,6 @@ impl ContextBuilder {
|
|||||||
self
|
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
|
/// Finish building the context by providing the `loader` used to load
|
||||||
/// fonts, images, source files and other resources.
|
/// fonts, images, source files and other resources.
|
||||||
pub fn build(self, loader: Arc<dyn Loader>) -> Context {
|
pub fn build(self, loader: Arc<dyn Loader>) -> Context {
|
||||||
@ -169,8 +140,6 @@ impl ContextBuilder {
|
|||||||
fonts: FontStore::new(Arc::clone(&loader)),
|
fonts: FontStore::new(Arc::clone(&loader)),
|
||||||
images: ImageStore::new(Arc::clone(&loader)),
|
images: ImageStore::new(Arc::clone(&loader)),
|
||||||
loader,
|
loader,
|
||||||
layout_cache: LayoutCache::new(self.policy, self.max_size),
|
|
||||||
render_cache: RenderCache::new(),
|
|
||||||
std: self.std.unwrap_or_else(library::new),
|
std: self.std.unwrap_or_else(library::new),
|
||||||
styles: self.styles.unwrap_or_default(),
|
styles: self.styles.unwrap_or_default(),
|
||||||
}
|
}
|
||||||
@ -179,12 +148,7 @@ impl ContextBuilder {
|
|||||||
|
|
||||||
impl Default for ContextBuilder {
|
impl Default for ContextBuilder {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self { std: None, styles: None }
|
||||||
std: None,
|
|
||||||
styles: None,
|
|
||||||
policy: EvictionPolicy::default(),
|
|
||||||
max_size: 2000,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,8 +162,6 @@ pub struct Vm<'a> {
|
|||||||
pub fonts: &'a mut FontStore,
|
pub fonts: &'a mut FontStore,
|
||||||
/// Stores decoded images.
|
/// Stores decoded images.
|
||||||
pub images: &'a mut ImageStore,
|
pub images: &'a mut ImageStore,
|
||||||
/// Caches layouting artifacts.
|
|
||||||
pub layout_cache: &'a mut LayoutCache,
|
|
||||||
/// The default styles.
|
/// The default styles.
|
||||||
pub styles: &'a StyleMap,
|
pub styles: &'a StyleMap,
|
||||||
/// The stack of imported files that led to evaluation of the current file.
|
/// 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,
|
sources: &mut ctx.sources,
|
||||||
fonts: &mut ctx.fonts,
|
fonts: &mut ctx.fonts,
|
||||||
images: &mut ctx.images,
|
images: &mut ctx.images,
|
||||||
layout_cache: &mut ctx.layout_cache,
|
|
||||||
styles: &ctx.styles,
|
styles: &ctx.styles,
|
||||||
route: vec![],
|
route: vec![],
|
||||||
modules: HashMap::new(),
|
modules: HashMap::new(),
|
||||||
|
@ -27,7 +27,7 @@ impl Layout for AlignNode {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
// The child only needs to expand along an axis if there's no alignment.
|
// The child only needs to expand along an axis if there's no alignment.
|
||||||
let mut pod = regions.clone();
|
let mut pod = regions.clone();
|
||||||
pod.expand &= self.aligns.map_is_none();
|
pod.expand &= self.aligns.map_is_none();
|
||||||
@ -40,20 +40,13 @@ impl Layout for AlignNode {
|
|||||||
|
|
||||||
// Layout the child.
|
// 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
|
for (region, frame) in regions.iter().zip(&mut frames) {
|
||||||
regions.iter().zip(&mut frames)
|
|
||||||
{
|
|
||||||
// Align in the target size. The target size depends on whether we
|
// Align in the target size. The target size depends on whether we
|
||||||
// should expand.
|
// should expand.
|
||||||
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 default = Spec::new(Align::Left, Align::Top);
|
||||||
let aligns = self.aligns.unwrap_or(default);
|
let aligns = self.aligns.unwrap_or(default);
|
||||||
Arc::make_mut(frame).resize(target, aligns);
|
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)
|
Ok(frames)
|
||||||
|
@ -32,23 +32,23 @@ impl Layout for ColumnsNode {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
// Separating the infinite space into infinite columns does not make
|
// Separating the infinite space into infinite columns does not make
|
||||||
// much sense.
|
// much sense.
|
||||||
if regions.current.x.is_infinite() {
|
if regions.first.x.is_infinite() {
|
||||||
return self.child.layout(vm, regions, styles);
|
return self.child.layout(vm, regions, styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the width of the gutter and each column.
|
// Determine the width of the gutter and each column.
|
||||||
let columns = self.columns.get();
|
let columns = self.columns.get();
|
||||||
let gutter = styles.get(Self::GUTTER).resolve(regions.base.x);
|
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.
|
// Create the pod regions.
|
||||||
let 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),
|
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())
|
.chain(regions.backlog.as_slice())
|
||||||
.flat_map(|&height| std::iter::repeat(height).take(columns))
|
.flat_map(|&height| std::iter::repeat(height).take(columns))
|
||||||
.skip(1)
|
.skip(1)
|
||||||
@ -66,18 +66,18 @@ impl Layout for ColumnsNode {
|
|||||||
let mut finished = vec![];
|
let mut finished = vec![];
|
||||||
|
|
||||||
// Stitch together the columns for each region.
|
// 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.
|
// The height should be the parent height if the node shall expand.
|
||||||
// Otherwise its the maximum column height for the frame. In that
|
// Otherwise its the maximum column height for the frame. In that
|
||||||
// case, the frame is first created with zero height and then
|
// case, the frame is first created with zero height and then
|
||||||
// resized.
|
// resized.
|
||||||
let height = if regions.expand.y { current.y } else { Length::zero() };
|
let height = if regions.expand.y { region.y } else { Length::zero() };
|
||||||
let mut output = Frame::new(Size::new(regions.current.x, height));
|
let mut output = Frame::new(Size::new(regions.first.x, height));
|
||||||
let mut cursor = Length::zero();
|
let mut cursor = Length::zero();
|
||||||
|
|
||||||
for _ in 0 .. columns {
|
for _ in 0 .. columns {
|
||||||
let frame = match frames.next() {
|
let frame = match frames.next() {
|
||||||
Some(frame) => frame.item,
|
Some(frame) => frame,
|
||||||
None => break,
|
None => break,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,17 +89,14 @@ impl Layout for ColumnsNode {
|
|||||||
let x = if dir.is_positive() {
|
let x = if dir.is_positive() {
|
||||||
cursor
|
cursor
|
||||||
} else {
|
} else {
|
||||||
regions.current.x - cursor - width
|
regions.first.x - cursor - width
|
||||||
};
|
};
|
||||||
|
|
||||||
output.push_frame(Point::with_x(x), frame);
|
output.push_frame(Point::with_x(x), frame);
|
||||||
cursor += width + gutter;
|
cursor += width + gutter;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cts = Constraints::new(regions.expand);
|
finished.push(Arc::new(output));
|
||||||
cts.base = base.map(Some);
|
|
||||||
cts.exact = current.map(Some);
|
|
||||||
finished.push(output.constrain(cts));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(finished)
|
Ok(finished)
|
||||||
|
@ -31,7 +31,7 @@ impl Layout for FlowNode {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let mut layouter = FlowLayouter::new(regions);
|
let mut layouter = FlowLayouter::new(regions);
|
||||||
|
|
||||||
for (child, map) in self.0.iter() {
|
for (child, map) in self.0.iter() {
|
||||||
@ -96,7 +96,7 @@ pub struct FlowLayouter {
|
|||||||
regions: Regions,
|
regions: Regions,
|
||||||
/// Whether the flow should expand to fill the region.
|
/// Whether the flow should expand to fill the region.
|
||||||
expand: Spec<bool>,
|
expand: Spec<bool>,
|
||||||
/// 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.
|
/// subtracting.
|
||||||
full: Size,
|
full: Size,
|
||||||
/// The size used by the frames for the current region.
|
/// The size used by the frames for the current region.
|
||||||
@ -106,7 +106,7 @@ pub struct FlowLayouter {
|
|||||||
/// Spacing and layouted nodes.
|
/// Spacing and layouted nodes.
|
||||||
items: Vec<FlowItem>,
|
items: Vec<FlowItem>,
|
||||||
/// Finished frames for previous regions.
|
/// Finished frames for previous regions.
|
||||||
finished: Vec<Constrained<Arc<Frame>>>,
|
finished: Vec<Arc<Frame>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A prepared item in a flow layout.
|
/// A prepared item in a flow layout.
|
||||||
@ -125,7 +125,7 @@ impl FlowLayouter {
|
|||||||
/// Create a new flow layouter.
|
/// Create a new flow layouter.
|
||||||
pub fn new(regions: &Regions) -> Self {
|
pub fn new(regions: &Regions) -> Self {
|
||||||
let expand = regions.expand;
|
let expand = regions.expand;
|
||||||
let full = regions.current;
|
let full = regions.first;
|
||||||
|
|
||||||
// Disable vertical expansion for children.
|
// Disable vertical expansion for children.
|
||||||
let mut regions = regions.clone();
|
let mut regions = regions.clone();
|
||||||
@ -148,8 +148,8 @@ impl FlowLayouter {
|
|||||||
SpacingKind::Linear(v) => {
|
SpacingKind::Linear(v) => {
|
||||||
// Resolve the linear and limit it to the remaining space.
|
// Resolve the linear and limit it to the remaining space.
|
||||||
let resolved = v.resolve(self.full.y);
|
let resolved = v.resolve(self.full.y);
|
||||||
let limited = resolved.min(self.regions.current.y);
|
let limited = resolved.min(self.regions.first.y);
|
||||||
self.regions.current.y -= limited;
|
self.regions.first.y -= limited;
|
||||||
self.used.y += limited;
|
self.used.y += limited;
|
||||||
self.items.push(FlowItem::Absolute(resolved));
|
self.items.push(FlowItem::Absolute(resolved));
|
||||||
}
|
}
|
||||||
@ -177,7 +177,7 @@ impl FlowLayouter {
|
|||||||
if let Some(placed) = node.downcast::<PlaceNode>() {
|
if let Some(placed) = node.downcast::<PlaceNode>() {
|
||||||
if placed.out_of_flow() {
|
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));
|
self.items.push(FlowItem::Placed(frame));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,11 +197,11 @@ impl FlowLayouter {
|
|||||||
let len = frames.len();
|
let len = frames.len();
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, frame) in frames.into_iter().enumerate() {
|
||||||
// Grow our size, shrink the region and save the frame for later.
|
// Grow our size, shrink the region and save the frame for later.
|
||||||
let size = frame.item.size;
|
let size = frame.size;
|
||||||
self.used.y += size.y;
|
self.used.y += size.y;
|
||||||
self.used.x.set_max(size.x);
|
self.used.x.set_max(size.x);
|
||||||
self.regions.current.y -= size.y;
|
self.regions.first.y -= size.y;
|
||||||
self.items.push(FlowItem::Frame(frame.item, aligns));
|
self.items.push(FlowItem::Frame(frame, aligns));
|
||||||
|
|
||||||
if i + 1 < len {
|
if i + 1 < len {
|
||||||
self.finish_region();
|
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.
|
// Advance to the next region.
|
||||||
self.regions.next();
|
self.regions.next();
|
||||||
self.full = self.regions.current;
|
self.full = self.regions.first;
|
||||||
self.used = Size::zero();
|
self.used = Size::zero();
|
||||||
self.fr = Fractional::zero();
|
self.fr = Fractional::zero();
|
||||||
self.finished.push(output.constrain(cts));
|
self.finished.push(Arc::new(output));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish layouting and return the resulting frames.
|
/// Finish layouting and return the resulting frames.
|
||||||
pub fn finish(mut self) -> Vec<Constrained<Arc<Frame>>> {
|
pub fn finish(mut self) -> Vec<Arc<Frame>> {
|
||||||
if self.expand.y {
|
if self.expand.y {
|
||||||
while self.regions.backlog.len() > 0 {
|
while self.regions.backlog.len() > 0 {
|
||||||
self.finish_region();
|
self.finish_region();
|
||||||
|
@ -38,7 +38,7 @@ impl Layout for GridNode {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
// Prepare grid layout by unifying content and gutter tracks.
|
// Prepare grid layout by unifying content and gutter tracks.
|
||||||
let layouter = GridLayouter::new(
|
let layouter = GridLayouter::new(
|
||||||
self.tracks.as_deref(),
|
self.tracks.as_deref(),
|
||||||
@ -105,8 +105,6 @@ pub struct GridLayouter<'a> {
|
|||||||
rcols: Vec<Length>,
|
rcols: Vec<Length>,
|
||||||
/// Rows in the current region.
|
/// Rows in the current region.
|
||||||
lrows: Vec<Row>,
|
lrows: Vec<Row>,
|
||||||
/// Whether the grid itself should expand to fill the region.
|
|
||||||
expand: Spec<bool>,
|
|
||||||
/// The full height of the current region.
|
/// The full height of the current region.
|
||||||
full: Length,
|
full: Length,
|
||||||
/// The used-up size of the current region. The horizontal size is
|
/// The used-up size of the current region. The horizontal size is
|
||||||
@ -114,10 +112,8 @@ pub struct GridLayouter<'a> {
|
|||||||
used: Size,
|
used: Size,
|
||||||
/// The sum of fractional ratios in the current region.
|
/// The sum of fractional ratios in the current region.
|
||||||
fr: Fractional,
|
fr: Fractional,
|
||||||
/// Constraints for the active region.
|
|
||||||
cts: Constraints,
|
|
||||||
/// Frames for finished regions.
|
/// Frames for finished regions.
|
||||||
finished: Vec<Constrained<Arc<Frame>>>,
|
finished: Vec<Arc<Frame>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produced by initial row layout, auto and linear rows are already finished,
|
/// Produced by initial row layout, auto and linear rows are already finished,
|
||||||
@ -177,8 +173,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
cols.pop();
|
cols.pop();
|
||||||
rows.pop();
|
rows.pop();
|
||||||
|
|
||||||
let expand = regions.expand;
|
let full = regions.first.y;
|
||||||
let full = regions.current.y;
|
|
||||||
let rcols = vec![Length::zero(); cols.len()];
|
let rcols = vec![Length::zero(); cols.len()];
|
||||||
let lrows = vec![];
|
let lrows = vec![];
|
||||||
|
|
||||||
@ -195,17 +190,15 @@ impl<'a> GridLayouter<'a> {
|
|||||||
styles,
|
styles,
|
||||||
rcols,
|
rcols,
|
||||||
lrows,
|
lrows,
|
||||||
expand,
|
|
||||||
full,
|
full,
|
||||||
used: Size::zero(),
|
used: Size::zero(),
|
||||||
fr: Fractional::zero(),
|
fr: Fractional::zero(),
|
||||||
cts: Constraints::new(expand),
|
|
||||||
finished: vec![],
|
finished: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the columns sizes and then layouts the grid row-by-row.
|
/// Determines the columns sizes and then layouts the grid row-by-row.
|
||||||
pub fn layout(mut self, vm: &mut Vm) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
pub fn layout(mut self, vm: &mut Vm) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
self.measure_columns(vm)?;
|
self.measure_columns(vm)?;
|
||||||
|
|
||||||
for y in 0 .. self.rows.len() {
|
for y in 0 .. self.rows.len() {
|
||||||
@ -219,7 +212,6 @@ impl<'a> GridLayouter<'a> {
|
|||||||
TrackSizing::Auto => self.layout_auto_row(vm, y)?,
|
TrackSizing::Auto => self.layout_auto_row(vm, y)?,
|
||||||
TrackSizing::Linear(v) => self.layout_linear_row(vm, v, y)?,
|
TrackSizing::Linear(v) => self.layout_linear_row(vm, v, y)?,
|
||||||
TrackSizing::Fractional(v) => {
|
TrackSizing::Fractional(v) => {
|
||||||
self.cts.exact.y = Some(self.full);
|
|
||||||
self.lrows.push(Row::Fr(v, y));
|
self.lrows.push(Row::Fr(v, y));
|
||||||
self.fr += v;
|
self.fr += v;
|
||||||
}
|
}
|
||||||
@ -232,22 +224,6 @@ impl<'a> GridLayouter<'a> {
|
|||||||
|
|
||||||
/// Determine all column sizes.
|
/// Determine all column sizes.
|
||||||
fn measure_columns(&mut self, vm: &mut Vm) -> TypResult<()> {
|
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.
|
// Sum of sizes of resolved linear tracks.
|
||||||
let mut linear = Length::zero();
|
let mut linear = Length::zero();
|
||||||
|
|
||||||
@ -258,23 +234,18 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// fractional tracks.
|
// fractional tracks.
|
||||||
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
|
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
|
||||||
match col {
|
match col {
|
||||||
TrackSizing::Auto => {
|
TrackSizing::Auto => {}
|
||||||
case = Case::Fitting;
|
|
||||||
}
|
|
||||||
TrackSizing::Linear(v) => {
|
TrackSizing::Linear(v) => {
|
||||||
let resolved = v.resolve(self.regions.base.x);
|
let resolved = v.resolve(self.regions.base.x);
|
||||||
*rcol = resolved;
|
*rcol = resolved;
|
||||||
linear += resolved;
|
linear += resolved;
|
||||||
}
|
}
|
||||||
TrackSizing::Fractional(v) => {
|
TrackSizing::Fractional(v) => fr += v,
|
||||||
case = Case::Fitting;
|
|
||||||
fr += v;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size that is not used by fixed-size columns.
|
// 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() {
|
if available >= Length::zero() {
|
||||||
// Determine size of auto columns.
|
// Determine size of auto columns.
|
||||||
let (auto, count) = self.measure_auto_columns(vm, available)?;
|
let (auto, count) = self.measure_auto_columns(vm, available)?;
|
||||||
@ -285,25 +256,10 @@ impl<'a> GridLayouter<'a> {
|
|||||||
if remaining >= Length::zero() {
|
if remaining >= Length::zero() {
|
||||||
if !fr.is_zero() {
|
if !fr.is_zero() {
|
||||||
self.grow_fractional_columns(remaining, fr);
|
self.grow_fractional_columns(remaining, fr);
|
||||||
case = Case::Exact;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.shrink_auto_columns(available, count);
|
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.
|
// 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);
|
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);
|
resolved.set_max(frame.size.x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -403,7 +359,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
for (x, &rcol) in self.rcols.iter().enumerate() {
|
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||||
if let Some(node) = self.cell(x, y) {
|
if let Some(node) = self.cell(x, y) {
|
||||||
let mut pod = self.regions.clone();
|
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.
|
// All widths should be `rcol` except the base for auto columns.
|
||||||
if self.cols[x] == TrackSizing::Auto {
|
if self.cols[x] == TrackSizing::Auto {
|
||||||
@ -413,7 +369,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let mut sizes = node
|
let mut sizes = node
|
||||||
.layout(vm, &pod, self.styles)?
|
.layout(vm, &pod, self.styles)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|frame| frame.item.size.y);
|
.map(|frame| frame.size.y);
|
||||||
|
|
||||||
// For each region, we want to know the maximum height any
|
// For each region, we want to know the maximum height any
|
||||||
// column requires.
|
// column requires.
|
||||||
@ -443,10 +399,8 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// eaten up by any fr rows.
|
// eaten up by any fr rows.
|
||||||
if self.fr.is_zero() {
|
if self.fr.is_zero() {
|
||||||
let len = resolved.len();
|
let len = resolved.len();
|
||||||
for (target, (current, _)) in
|
for (region, target) in self.regions.iter().zip(&mut resolved[.. len - 1]) {
|
||||||
resolved[.. len - 1].iter_mut().zip(self.regions.iter())
|
target.set_max(region.y);
|
||||||
{
|
|
||||||
target.set_max(current.y);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,7 +410,6 @@ impl<'a> GridLayouter<'a> {
|
|||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, frame) in frames.into_iter().enumerate() {
|
||||||
self.push_row(frame);
|
self.push_row(frame);
|
||||||
if i + 1 < len {
|
if i + 1 < len {
|
||||||
self.cts.exact.y = Some(self.full);
|
|
||||||
self.finish_region(vm)?;
|
self.finish_region(vm)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -472,8 +425,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
|
|
||||||
// Skip to fitting region.
|
// Skip to fitting region.
|
||||||
let height = frame.size.y;
|
let height = frame.size.y;
|
||||||
while !self.regions.current.y.fits(height) && !self.regions.in_last() {
|
while !self.regions.first.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.
|
// 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 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);
|
output.push_frame(pos, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
pos.x += rcol;
|
pos.x += rcol;
|
||||||
@ -540,7 +492,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let mut pos = Point::zero();
|
let mut pos = Point::zero();
|
||||||
for (x, &rcol) in self.rcols.iter().enumerate() {
|
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||||
if let Some(node) = self.cell(x, y) {
|
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.
|
// All widths should be `rcol` except the base for auto columns.
|
||||||
if self.cols[x] == TrackSizing::Auto {
|
if self.cols[x] == TrackSizing::Auto {
|
||||||
@ -550,7 +502,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// Push the layouted frames into the individual output frames.
|
// 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) {
|
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.
|
/// Push a row frame into the current region.
|
||||||
fn push_row(&mut self, frame: Frame) {
|
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.used.y += frame.size.y;
|
||||||
self.lrows.push(Row::Frame(frame));
|
self.lrows.push(Row::Frame(frame));
|
||||||
}
|
}
|
||||||
@ -574,9 +526,6 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let mut size = self.used;
|
let mut size = self.used;
|
||||||
if self.fr.get() > 0.0 && self.full.is_finite() {
|
if self.fr.get() > 0.0 && self.full.is_finite() {
|
||||||
size.y = self.full;
|
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.
|
// The frame for the region.
|
||||||
@ -599,13 +548,11 @@ impl<'a> GridLayouter<'a> {
|
|||||||
pos.y += height;
|
pos.y += height;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cts.base = self.regions.base.map(Some);
|
self.finished.push(Arc::new(output));
|
||||||
self.finished.push(output.constrain(self.cts));
|
|
||||||
self.regions.next();
|
self.regions.next();
|
||||||
self.full = self.regions.current.y;
|
self.full = self.regions.first.y;
|
||||||
self.used.y = Length::zero();
|
self.used.y = Length::zero();
|
||||||
self.fr = Fractional::zero();
|
self.fr = Fractional::zero();
|
||||||
self.cts = Constraints::new(self.expand);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,11 @@ impl Layout for HideNode {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let mut frames = self.0.layout(vm, regions, styles)?;
|
let mut frames = self.0.layout(vm, regions, styles)?;
|
||||||
|
|
||||||
// Clear the frames.
|
// Clear the frames.
|
||||||
for Constrained { item: frame, .. } in &mut frames {
|
for frame in &mut frames {
|
||||||
*frame = Arc::new(Frame { elements: vec![], ..**frame });
|
*frame = Arc::new(Frame { elements: vec![], ..**frame });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,24 +39,24 @@ impl Layout for ImageNode {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let img = vm.images.get(self.0);
|
let img = vm.images.get(self.0);
|
||||||
let pxw = img.width() as f64;
|
let pxw = img.width() as f64;
|
||||||
let pxh = img.height() as f64;
|
let pxh = img.height() as f64;
|
||||||
let px_ratio = pxw / pxh;
|
let px_ratio = pxw / pxh;
|
||||||
|
|
||||||
// Find out whether the image is wider or taller than the target size.
|
// Find out whether the image is wider or taller than the target size.
|
||||||
let &Regions { current, expand, .. } = regions;
|
let &Regions { first, expand, .. } = regions;
|
||||||
let current_ratio = current.x / current.y;
|
let region_ratio = first.x / first.y;
|
||||||
let wide = px_ratio > current_ratio;
|
let wide = px_ratio > region_ratio;
|
||||||
|
|
||||||
// The space into which the image will be placed according to its fit.
|
// The space into which the image will be placed according to its fit.
|
||||||
let target = if expand.x && expand.y {
|
let target = if expand.x && expand.y {
|
||||||
current
|
first
|
||||||
} else if expand.x || (!expand.y && wide && current.x.is_finite()) {
|
} else if expand.x || (!expand.y && wide && first.x.is_finite()) {
|
||||||
Size::new(current.x, current.y.min(current.x.safe_div(px_ratio)))
|
Size::new(first.x, first.y.min(first.x.safe_div(px_ratio)))
|
||||||
} else if current.y.is_finite() {
|
} else if first.y.is_finite() {
|
||||||
Size::new(current.x.min(current.y * px_ratio), current.y)
|
Size::new(first.x.min(first.y * px_ratio), first.y)
|
||||||
} else {
|
} else {
|
||||||
Size::new(Length::pt(pxw), Length::pt(pxh))
|
Size::new(Length::pt(pxw), Length::pt(pxh))
|
||||||
};
|
};
|
||||||
@ -91,7 +91,7 @@ impl Layout for ImageNode {
|
|||||||
frame.link(url);
|
frame.link(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(vec![frame.constrain(Constraints::tight(regions))])
|
Ok(vec![Arc::new(frame)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,14 +66,12 @@ pub mod prelude {
|
|||||||
|
|
||||||
pub use crate::diag::{with_alternative, At, StrResult, TypResult};
|
pub use crate::diag::{with_alternative, At, StrResult, TypResult};
|
||||||
pub use crate::eval::{
|
pub use crate::eval::{
|
||||||
Arg, Args, Cast, Construct, Func, Merge, Property, Scope, Set, Show, ShowNode,
|
Arg, Args, Cast, Construct, Func, Layout, LayoutNode, Merge, Property, Regions,
|
||||||
Smart, StyleChain, StyleMap, StyleVec, Template, Value,
|
Scope, Set, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Template,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
pub use crate::frame::*;
|
pub use crate::frame::*;
|
||||||
pub use crate::geom::*;
|
pub use crate::geom::*;
|
||||||
pub use crate::layout::{
|
|
||||||
Constrain, Constrained, Constraints, Layout, LayoutNode, Regions,
|
|
||||||
};
|
|
||||||
pub use crate::syntax::{Span, Spanned};
|
pub use crate::syntax::{Span, Spanned};
|
||||||
pub use crate::util::{EcoString, OptionExt};
|
pub use crate::util::{EcoString, OptionExt};
|
||||||
pub use crate::Vm;
|
pub use crate::Vm;
|
||||||
|
@ -33,14 +33,12 @@ impl Layout for PadNode {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
// Layout child into padded regions.
|
// Layout child into padded regions.
|
||||||
let pod = regions.map(|size| shrink(size, self.padding));
|
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
|
for frame in &mut frames {
|
||||||
regions.iter().zip(&mut frames)
|
|
||||||
{
|
|
||||||
// Apply the padding inversely such that the grown size padded
|
// Apply the padding inversely such that the grown size padded
|
||||||
// yields the frame's size.
|
// yields the frame's size.
|
||||||
let padded = grow(frame.size, self.padding);
|
let padded = grow(frame.size, self.padding);
|
||||||
@ -51,19 +49,6 @@ impl Layout for PadNode {
|
|||||||
let frame = Arc::make_mut(frame);
|
let frame = Arc::make_mut(frame);
|
||||||
frame.size = padded;
|
frame.size = padded;
|
||||||
frame.translate(offset);
|
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)
|
Ok(frames)
|
||||||
|
@ -115,15 +115,12 @@ impl PageNode {
|
|||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let regions = Regions::repeat(size, size, size.map(Length::is_finite));
|
let regions = Regions::repeat(size, size, size.map(Length::is_finite));
|
||||||
let mut frames: Vec<_> = child
|
let mut frames = child.layout(vm, ®ions, styles)?;
|
||||||
.layout(vm, ®ions, styles)?
|
|
||||||
.into_iter()
|
|
||||||
.map(|c| c.item)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let header = styles.get_ref(Self::HEADER);
|
let header = styles.get_ref(Self::HEADER);
|
||||||
let footer = styles.get_ref(Self::FOOTER);
|
let footer = styles.get_ref(Self::FOOTER);
|
||||||
|
|
||||||
|
// Realize header and footer.
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
let size = frame.size;
|
let size = frame.size;
|
||||||
let padding = padding.resolve(size);
|
let padding = padding.resolve(size);
|
||||||
@ -136,7 +133,7 @@ impl PageNode {
|
|||||||
let w = size.x - padding.left - padding.right;
|
let w = size.x - padding.left - padding.right;
|
||||||
let area = Size::new(w, h);
|
let area = Size::new(w, h);
|
||||||
let pod = Regions::one(area, area, area.map(Length::is_finite));
|
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);
|
Arc::make_mut(frame).push_frame(pos, sub);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ impl Layout for ParNode {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
// Collect all text into one string used for BiDi analysis.
|
// Collect all text into one string used for BiDi analysis.
|
||||||
let text = self.collect_text();
|
let text = self.collect_text();
|
||||||
let level = Level::from_dir(styles.get(Self::DIR));
|
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
|
// 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
|
// into the stack and rebuild the line from its end. The resulting
|
||||||
// line cannot be broken up further.
|
// 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() {
|
if let Some((last_line, last_end)) = last.take() {
|
||||||
lines.push(last_line);
|
lines.push(last_line);
|
||||||
start = last_end;
|
start = last_end;
|
||||||
@ -124,7 +124,7 @@ impl Layout for ParNode {
|
|||||||
// Finish the current line if there is a mandatory line break (i.e.
|
// 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
|
// due to "\n") or if the line doesn't fit horizontally already
|
||||||
// since no shorter line will be possible.
|
// 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);
|
lines.push(line);
|
||||||
start = end;
|
start = end;
|
||||||
last = None;
|
last = None;
|
||||||
@ -139,7 +139,7 @@ impl Layout for ParNode {
|
|||||||
|
|
||||||
// Determine the paragraph's width: Fit to width if we shoudn't expand
|
// Determine the paragraph's width: Fit to width if we shoudn't expand
|
||||||
// and there's no fractional spacing.
|
// 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()) {
|
if !regions.expand.x && lines.iter().all(|line| line.fr.is_zero()) {
|
||||||
width = lines.iter().map(|line| line.size.x).max().unwrap_or_default();
|
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 finished = vec![];
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
let mut output = Frame::new(Size::with_x(width));
|
let mut output = Frame::new(Size::with_x(width));
|
||||||
let mut cts = Constraints::tight(®ions);
|
|
||||||
|
|
||||||
// Stack the lines into one frame per region.
|
// Stack the lines into one frame per region.
|
||||||
for line in lines {
|
for line in lines {
|
||||||
while !regions.current.y.fits(line.size.y) && !regions.in_last() {
|
while !regions.first.y.fits(line.size.y) && !regions.in_last() {
|
||||||
finished.push(output.constrain(cts));
|
finished.push(Arc::new(output));
|
||||||
output = Frame::new(Size::with_x(width));
|
output = Frame::new(Size::with_x(width));
|
||||||
regions.next();
|
regions.next();
|
||||||
cts = Constraints::tight(®ions);
|
|
||||||
first = true;
|
first = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,11 +168,11 @@ impl Layout for ParNode {
|
|||||||
output.size.y += frame.size.y;
|
output.size.y += frame.size.y;
|
||||||
output.merge_frame(pos, frame);
|
output.merge_frame(pos, frame);
|
||||||
|
|
||||||
regions.current.y -= line.size.y + leading;
|
regions.first.y -= line.size.y + leading;
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
finished.push(output.constrain(cts));
|
finished.push(Arc::new(output));
|
||||||
Ok(finished)
|
Ok(finished)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -316,7 +314,7 @@ impl<'a> ParLayout<'a> {
|
|||||||
}
|
}
|
||||||
ParChild::Spacing(kind) => match *kind {
|
ParChild::Spacing(kind) => match *kind {
|
||||||
SpacingKind::Linear(v) => {
|
SpacingKind::Linear(v) => {
|
||||||
let resolved = v.resolve(regions.current.x);
|
let resolved = v.resolve(regions.first.x);
|
||||||
items.push(ParItem::Absolute(resolved));
|
items.push(ParItem::Absolute(resolved));
|
||||||
ranges.push(range);
|
ranges.push(range);
|
||||||
}
|
}
|
||||||
@ -326,10 +324,10 @@ impl<'a> ParLayout<'a> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ParChild::Node(node) => {
|
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 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)));
|
items.push(ParItem::Frame(Arc::take(frame)));
|
||||||
ranges.push(range);
|
ranges.push(range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,11 +26,11 @@ impl Layout for PlaceNode {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let out_of_flow = self.out_of_flow();
|
let out_of_flow = self.out_of_flow();
|
||||||
|
|
||||||
// The pod is the base area of the region because for absolute
|
// 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 pod = {
|
||||||
let finite = regions.base.map(Length::is_finite);
|
let finite = regions.base.map(Length::is_finite);
|
||||||
let expand = finite & (regions.expand | out_of_flow);
|
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 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
|
// If expansion is off, zero all sizes so that we don't take up any
|
||||||
// space in our parent. Otherwise, respect the expand settings.
|
// space in our parent. Otherwise, respect the expand settings.
|
||||||
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);
|
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)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlaceNode {
|
impl PlaceNode {
|
||||||
/// Whether this node wants to be placed relative to its its parent's base
|
/// 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.
|
/// position.
|
||||||
pub fn out_of_flow(&self) -> bool {
|
pub fn out_of_flow(&self) -> bool {
|
||||||
self.0
|
self.0
|
||||||
|
@ -49,7 +49,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let mut frames;
|
let mut frames;
|
||||||
if let Some(child) = &self.0 {
|
if let Some(child) = &self.0 {
|
||||||
let mut padding = styles.get(Self::PADDING);
|
let mut padding = styles.get(Self::PADDING);
|
||||||
@ -60,48 +60,47 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
|||||||
// Pad the child.
|
// Pad the child.
|
||||||
let child = child.clone().padded(Sides::splat(padding));
|
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)?;
|
frames = child.layout(vm, &pod, styles)?;
|
||||||
|
|
||||||
// Relayout with full expansion into square region to make sure
|
// Relayout with full expansion into square region to make sure
|
||||||
// the result is really a square or circle.
|
// the result is really a square or circle.
|
||||||
if is_quadratic(S) {
|
if is_quadratic(S) {
|
||||||
let length = if regions.expand.x || regions.expand.y {
|
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)
|
target.x.max(target.y)
|
||||||
} else {
|
} else {
|
||||||
let size = frames[0].item.size;
|
let size = frames[0].size;
|
||||||
let desired = size.x.max(size.y);
|
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);
|
pod.expand = Spec::splat(true);
|
||||||
frames = child.layout(vm, &pod, styles)?;
|
frames = child.layout(vm, &pod, styles)?;
|
||||||
frames[0].cts = Constraints::tight(regions);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The default size that a shape takes on if it has no child and
|
// The default size that a shape takes on if it has no child and
|
||||||
// enough space.
|
// enough space.
|
||||||
let mut size =
|
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) {
|
if is_quadratic(S) {
|
||||||
let length = if regions.expand.x || regions.expand.y {
|
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)
|
target.x.max(target.y)
|
||||||
} else {
|
} else {
|
||||||
size.x.min(size.y)
|
size.x.min(size.y)
|
||||||
};
|
};
|
||||||
size = Size::splat(length);
|
size = Size::splat(length);
|
||||||
} else {
|
} 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.
|
// Add fill and/or stroke.
|
||||||
let fill = styles.get(Self::FILL);
|
let fill = styles.get(Self::FILL);
|
||||||
|
@ -31,7 +31,7 @@ impl Layout for StackNode {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let mut layouter = StackLayouter::new(self.dir, regions);
|
let mut layouter = StackLayouter::new(self.dir, regions);
|
||||||
|
|
||||||
// Spacing to insert before the next node.
|
// Spacing to insert before the next node.
|
||||||
@ -106,7 +106,7 @@ pub struct StackLayouter {
|
|||||||
/// fractional spacing.
|
/// fractional spacing.
|
||||||
items: Vec<StackItem>,
|
items: Vec<StackItem>,
|
||||||
/// Finished frames for previous regions.
|
/// Finished frames for previous regions.
|
||||||
finished: Vec<Constrained<Arc<Frame>>>,
|
finished: Vec<Arc<Frame>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A prepared item in a stack layout.
|
/// A prepared item in a stack layout.
|
||||||
@ -124,7 +124,7 @@ impl StackLayouter {
|
|||||||
pub fn new(dir: Dir, regions: &Regions) -> Self {
|
pub fn new(dir: Dir, regions: &Regions) -> Self {
|
||||||
let axis = dir.axis();
|
let axis = dir.axis();
|
||||||
let expand = regions.expand;
|
let expand = regions.expand;
|
||||||
let full = regions.current;
|
let full = regions.first;
|
||||||
|
|
||||||
// Disable expansion along the block axis for children.
|
// Disable expansion along the block axis for children.
|
||||||
let mut regions = regions.clone();
|
let mut regions = regions.clone();
|
||||||
@ -149,7 +149,7 @@ impl StackLayouter {
|
|||||||
SpacingKind::Linear(v) => {
|
SpacingKind::Linear(v) => {
|
||||||
// Resolve the linear and limit it to the remaining space.
|
// Resolve the linear and limit it to the remaining space.
|
||||||
let resolved = v.resolve(self.regions.base.get(self.axis));
|
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);
|
let limited = resolved.min(*remaining);
|
||||||
*remaining -= limited;
|
*remaining -= limited;
|
||||||
self.used.main += limited;
|
self.used.main += limited;
|
||||||
@ -183,11 +183,11 @@ impl StackLayouter {
|
|||||||
let len = frames.len();
|
let len = frames.len();
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, frame) in frames.into_iter().enumerate() {
|
||||||
// Grow our size, shrink the region and save the frame for later.
|
// Grow our size, shrink the region and save the frame for later.
|
||||||
let size = frame.item.size.to_gen(self.axis);
|
let size = frame.size.to_gen(self.axis);
|
||||||
self.used.main += size.main;
|
self.used.main += size.main;
|
||||||
self.used.cross.set_max(size.cross);
|
self.used.cross.set_max(size.cross);
|
||||||
*self.regions.current.get_mut(self.axis) -= size.main;
|
*self.regions.first.get_mut(self.axis) -= size.main;
|
||||||
self.items.push(StackItem::Frame(frame.item, align));
|
self.items.push(StackItem::Frame(frame, align));
|
||||||
|
|
||||||
if i + 1 < len {
|
if i + 1 < len {
|
||||||
self.finish_region();
|
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.
|
// Advance to the next region.
|
||||||
self.regions.next();
|
self.regions.next();
|
||||||
self.full = self.regions.current;
|
self.full = self.regions.first;
|
||||||
self.used = Gen::zero();
|
self.used = Gen::zero();
|
||||||
self.fr = Fractional::zero();
|
self.fr = Fractional::zero();
|
||||||
self.finished.push(output.constrain(cts));
|
self.finished.push(Arc::new(output));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish layouting and return the resulting frames.
|
/// Finish layouting and return the resulting frames.
|
||||||
pub fn finish(mut self) -> Vec<Constrained<Arc<Frame>>> {
|
pub fn finish(mut self) -> Vec<Arc<Frame>> {
|
||||||
self.finish_region();
|
self.finish_region();
|
||||||
self.finished
|
self.finished
|
||||||
}
|
}
|
||||||
|
@ -49,11 +49,11 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
|
|||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Constrained<Arc<Frame>>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
|
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 {
|
for frame in &mut frames {
|
||||||
let Spec { x, y } = origin.zip(frame.size).map(|(o, s)| o.resolve(s));
|
let Spec { x, y } = origin.zip(frame.size).map(|(o, s)| o.resolve(s));
|
||||||
let transform = Transform::translation(x, y)
|
let transform = Transform::translation(x, y)
|
||||||
.pre_concat(self.transform)
|
.pre_concat(self.transform)
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs::{self, File};
|
use std::fs;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use filedescriptor::{FileDescriptor, StdioDescriptor::*};
|
|
||||||
use tiny_skia as sk;
|
use tiny_skia as sk;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
@ -16,7 +15,7 @@ use typst::geom::{Length, RgbaColor};
|
|||||||
use typst::library::{PageNode, TextNode};
|
use typst::library::{PageNode, TextNode};
|
||||||
use typst::loading::FsLoader;
|
use typst::loading::FsLoader;
|
||||||
use typst::parse::Scanner;
|
use typst::parse::Scanner;
|
||||||
use typst::source::{SourceFile, SourceId};
|
use typst::source::SourceFile;
|
||||||
use typst::syntax::Span;
|
use typst::syntax::Span;
|
||||||
use typst::{Context, Vm};
|
use typst::{Context, Vm};
|
||||||
|
|
||||||
@ -269,19 +268,16 @@ fn test_part(
|
|||||||
ok &= test_reparse(ctx.sources.get(id).src(), i, rng);
|
ok &= test_reparse(ctx.sources.get(id).src(), i, rng);
|
||||||
|
|
||||||
let mut vm = Vm::new(ctx);
|
let mut vm = Vm::new(ctx);
|
||||||
let (frames, mut errors) = match vm.typeset(id) {
|
let (mut frames, mut errors) = match vm.typeset(id) {
|
||||||
Ok(mut frames) => {
|
Ok(frames) => (frames, vec![]),
|
||||||
ok &= test_incremental(ctx, i, id, &frames);
|
|
||||||
|
|
||||||
if !compare_ref {
|
|
||||||
frames.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
(frames, vec![])
|
|
||||||
}
|
|
||||||
Err(errors) => (vec![], *errors),
|
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.
|
// TODO: Also handle errors from other files.
|
||||||
errors.retain(|error| error.span.source == id);
|
errors.retain(|error| error.span.source == id);
|
||||||
for error in &mut errors {
|
for error in &mut errors {
|
||||||
@ -467,53 +463,6 @@ fn test_reparse(src: &str, i: usize, rng: &mut LinearShift) -> bool {
|
|||||||
ok
|
ok
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_incremental(
|
|
||||||
ctx: &mut Context,
|
|
||||||
i: usize,
|
|
||||||
id: SourceId,
|
|
||||||
frames: &[Arc<Frame>],
|
|
||||||
) -> 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.
|
/// Draw all frames into one image with padding in between.
|
||||||
fn render(ctx: &mut Context, frames: &[Arc<Frame>]) -> sk::Pixmap {
|
fn render(ctx: &mut Context, frames: &[Arc<Frame>]) -> sk::Pixmap {
|
||||||
let pixel_per_pt = 2.0;
|
let pixel_per_pt = 2.0;
|
||||||
@ -582,21 +531,6 @@ fn render_links(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disable stdout and stderr during execution of `f`.
|
|
||||||
fn silenced<F, T>(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
|
/// This is an Linear-feedback shift register using XOR as its shifting
|
||||||
/// function. It can be used as PRNG.
|
/// function. It can be used as PRNG.
|
||||||
struct LinearShift(u64);
|
struct LinearShift(u64);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user