mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Refactor layouting a bit
Notably: - Handle aspect ratio in fixed node - Inline constraint inflation into pad node
This commit is contained in:
parent
fdab7158c9
commit
a6f260ca39
@ -834,7 +834,6 @@ fn walk_item(ctx: &mut EvalContext, label: EcoString, body: Template) {
|
|||||||
};
|
};
|
||||||
StackNode {
|
StackNode {
|
||||||
dirs: Gen::new(state.dirs.main, state.dirs.cross),
|
dirs: Gen::new(state.dirs.main, state.dirs.cross),
|
||||||
aspect: None,
|
|
||||||
children: vec![
|
children: vec![
|
||||||
StackChild::Any(label.into(), Gen::default()),
|
StackChild::Any(label.into(), Gen::default()),
|
||||||
StackChild::Spacing((state.font.size / 2.0).into()),
|
StackChild::Spacing((state.font.size / 2.0).into()),
|
||||||
|
@ -425,7 +425,7 @@ impl StackBuilder {
|
|||||||
children.extend(last.any());
|
children.extend(last.any());
|
||||||
children.push(par);
|
children.push(par);
|
||||||
}
|
}
|
||||||
StackNode { dirs, aspect: None, children }
|
StackNode { dirs, children }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,14 @@ impl Size {
|
|||||||
Self { width: value, height: value }
|
Self { width: value, height: value }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Limit width and height at that of another size.
|
||||||
|
pub fn cap(self, limit: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
width: self.width.min(limit.width),
|
||||||
|
height: self.height.min(limit.height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the other size fits into this one (smaller width and height).
|
/// Whether the other size fits into this one (smaller width and height).
|
||||||
pub fn fits(self, other: Self) -> bool {
|
pub fn fits(self, other: Self) -> bool {
|
||||||
self.width.fits(other.width) && self.height.fits(other.height)
|
self.width.fits(other.width) && self.height.fits(other.height)
|
||||||
@ -62,13 +70,6 @@ impl Size {
|
|||||||
SpecAxis::Vertical => Gen::new(self.width, self.height),
|
SpecAxis::Vertical => Gen::new(self.width, self.height),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the largest contained size that satisfies the given `aspect` ratio.
|
|
||||||
pub fn with_aspect(self, aspect: f64) -> Self {
|
|
||||||
let width = self.width.min(aspect * self.height);
|
|
||||||
let height = width / aspect;
|
|
||||||
Size::new(width, height)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Get<SpecAxis> for Size {
|
impl Get<SpecAxis> for Size {
|
||||||
|
@ -26,9 +26,8 @@ impl Layout for BackgroundNode {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let mut frames = self.child.layout(ctx, regions);
|
let mut frames = self.child.layout(ctx, regions);
|
||||||
for frame in &mut frames {
|
|
||||||
let mut new = Frame::new(frame.size, frame.baseline);
|
|
||||||
|
|
||||||
|
for Constrained { item: frame, .. } in &mut frames {
|
||||||
let (point, geometry) = match self.shape {
|
let (point, geometry) = match self.shape {
|
||||||
BackgroundShape::Rect => (Point::zero(), Geometry::Rect(frame.size)),
|
BackgroundShape::Rect => (Point::zero(), Geometry::Rect(frame.size)),
|
||||||
BackgroundShape::Ellipse => {
|
BackgroundShape::Ellipse => {
|
||||||
@ -36,11 +35,15 @@ impl Layout for BackgroundNode {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let prev = std::mem::take(&mut frame.item);
|
// Create a new frame with the background geometry and the child's
|
||||||
|
// frame.
|
||||||
|
let empty = Frame::new(frame.size, frame.baseline);
|
||||||
|
let prev = std::mem::replace(frame, Rc::new(empty));
|
||||||
|
let new = Rc::make_mut(frame);
|
||||||
new.push(point, Element::Geometry(geometry, self.fill));
|
new.push(point, Element::Geometry(geometry, self.fill));
|
||||||
new.push_frame(Point::zero(), prev);
|
new.push_frame(Point::zero(), prev);
|
||||||
*Rc::make_mut(&mut frame.item) = new;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
frames
|
frames
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use crate::util::OptionExt;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Carries an item that is only valid in certain regions and the constraints
|
/// Carries an item that is only valid in certain regions and the constraints
|
||||||
@ -61,36 +59,14 @@ impl Constraints {
|
|||||||
&& base.eq_by(&self.base, |x, y| y.map_or(true, |y| x.approx_eq(y)))
|
&& base.eq_by(&self.base, |x, y| y.map_or(true, |y| x.approx_eq(y)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the appropriate base constraints for (relative) width and height
|
/// Set the appropriate base constraints for linear width and height sizing.
|
||||||
/// metrics, respectively.
|
pub fn set_base_if_linear(&mut self, base: Size, sizing: Spec<Option<Linear>>) {
|
||||||
pub fn set_base_using_linears(
|
|
||||||
&mut self,
|
|
||||||
size: Spec<Option<Linear>>,
|
|
||||||
regions: &Regions,
|
|
||||||
) {
|
|
||||||
// The full sizes need to be equal if there is a relative component in the sizes.
|
// The full sizes need to be equal if there is a relative component in the sizes.
|
||||||
if size.horizontal.map_or(false, |l| l.is_relative()) {
|
if sizing.horizontal.map_or(false, |l| l.is_relative()) {
|
||||||
self.base.horizontal = Some(regions.base.width);
|
self.base.horizontal = Some(base.width);
|
||||||
}
|
}
|
||||||
if size.vertical.map_or(false, |l| l.is_relative()) {
|
if sizing.vertical.map_or(false, |l| l.is_relative()) {
|
||||||
self.base.vertical = Some(regions.base.height);
|
self.base.vertical = Some(base.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Changes all constraints by adding the `size` to them if they are `Some`.
|
|
||||||
pub fn inflate(&mut self, size: Size, regions: &Regions) {
|
|
||||||
for spec in [&mut self.min, &mut self.max] {
|
|
||||||
if let Some(horizontal) = spec.horizontal.as_mut() {
|
|
||||||
*horizontal += size.width;
|
|
||||||
}
|
|
||||||
if let Some(vertical) = spec.vertical.as_mut() {
|
|
||||||
*vertical += size.height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.exact.horizontal.and_set(Some(regions.current.width));
|
|
||||||
self.exact.vertical.and_set(Some(regions.current.height));
|
|
||||||
self.base.horizontal.and_set(Some(regions.base.width));
|
|
||||||
self.base.vertical.and_set(Some(regions.base.height));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use decorum::N64;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// A node that can fix its child's width and height.
|
/// A node that can fix its child's width and height.
|
||||||
@ -8,6 +10,10 @@ pub struct FixedNode {
|
|||||||
pub width: Option<Linear>,
|
pub width: Option<Linear>,
|
||||||
/// The fixed height, if any.
|
/// The fixed height, if any.
|
||||||
pub height: Option<Linear>,
|
pub height: Option<Linear>,
|
||||||
|
/// The fixed aspect ratio between width and height.
|
||||||
|
///
|
||||||
|
/// The resulting frame will satisfy `width = aspect * height`.
|
||||||
|
pub aspect: Option<N64>,
|
||||||
/// The child node whose size to fix.
|
/// The child node whose size to fix.
|
||||||
pub child: LayoutNode,
|
pub child: LayoutNode,
|
||||||
}
|
}
|
||||||
@ -16,32 +22,73 @@ impl Layout for FixedNode {
|
|||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
&Regions { current, base, expand, .. }: &Regions,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let Regions { current, base, .. } = regions;
|
// Fill in width or height if aspect ratio and the other is given.
|
||||||
let mut constraints = Constraints::new(regions.expand);
|
let aspect = self.aspect.map(N64::into_inner);
|
||||||
constraints.set_base_using_linears(Spec::new(self.width, self.height), ®ions);
|
let width = self.width.or(self.height.zip(aspect).map(|(h, a)| a * h));
|
||||||
|
let height = self.height.or(self.width.zip(aspect).map(|(w, a)| w / a));
|
||||||
|
|
||||||
let size = Size::new(
|
// Prepare constraints.
|
||||||
self.width.map_or(current.width, |w| w.resolve(base.width)),
|
let mut constraints = Constraints::new(expand);
|
||||||
self.height.map_or(current.height, |h| h.resolve(base.height)),
|
constraints.set_base_if_linear(base, Spec::new(width, height));
|
||||||
);
|
|
||||||
|
|
||||||
// If one dimension was not specified, the `current` size needs to remain static.
|
// If the size for one axis isn't specified, the `current` size along
|
||||||
if self.width.is_none() {
|
// that axis needs to remain the same for the result to be reusable.
|
||||||
|
if width.is_none() {
|
||||||
constraints.exact.horizontal = Some(current.width);
|
constraints.exact.horizontal = Some(current.width);
|
||||||
}
|
}
|
||||||
if self.height.is_none() {
|
|
||||||
|
if height.is_none() {
|
||||||
constraints.exact.vertical = Some(current.height);
|
constraints.exact.vertical = Some(current.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
let expand = Spec::new(self.width.is_some(), self.height.is_some());
|
// Resolve the linears based on the current width and height.
|
||||||
let regions = Regions::one(size, expand);
|
let mut size = Size::new(
|
||||||
|
width.map_or(current.width, |w| w.resolve(base.width)),
|
||||||
|
height.map_or(current.height, |h| h.resolve(base.height)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// If width or height aren't set for an axis, the base should be
|
||||||
|
// inherited from the parent for that axis.
|
||||||
|
let base = Size::new(
|
||||||
|
width.map_or(base.width, |_| size.width),
|
||||||
|
height.map_or(base.height, |_| size.height),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle the aspect ratio.
|
||||||
|
if let Some(aspect) = aspect {
|
||||||
|
constraints.exact = current.to_spec().map(Some);
|
||||||
|
constraints.min = Spec::splat(None);
|
||||||
|
constraints.max = Spec::splat(None);
|
||||||
|
|
||||||
|
let width = size.width.min(aspect * size.height);
|
||||||
|
size = Size::new(width, width / aspect);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If width or height are fixed, the child should fill the available
|
||||||
|
// space along that axis.
|
||||||
|
let expand = Spec::new(width.is_some(), height.is_some());
|
||||||
|
|
||||||
|
// Layout the child.
|
||||||
|
let mut regions = Regions::one(size, base, expand);
|
||||||
let mut frames = self.child.layout(ctx, ®ions);
|
let mut frames = self.child.layout(ctx, ®ions);
|
||||||
|
|
||||||
if let Some(frame) = frames.first_mut() {
|
// If we have an aspect ratio and the child is content-sized, we need to
|
||||||
frame.constraints = constraints;
|
// relayout with expansion.
|
||||||
|
if let Some(aspect) = aspect {
|
||||||
|
if width.is_none() && height.is_none() {
|
||||||
|
let needed = frames[0].size.cap(size);
|
||||||
|
let width = needed.width.max(aspect * needed.height);
|
||||||
|
regions.current = Size::new(width, width / aspect);
|
||||||
|
regions.expand = Spec::splat(true);
|
||||||
|
frames = self.child.layout(ctx, ®ions);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite the child's constraints with ours.
|
||||||
|
frames[0].constraints = constraints;
|
||||||
|
assert_eq!(frames.len(), 1);
|
||||||
|
|
||||||
frames
|
frames
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,13 @@ pub struct Frame {
|
|||||||
children: Vec<(Point, Child)>,
|
children: Vec<(Point, Child)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A frame can contain multiple children: elements or other frames, complete
|
/// A frame can contain two different kinds of children: a leaf element or a
|
||||||
/// with their children.
|
/// nested frame.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
enum Child {
|
enum Child {
|
||||||
|
/// A leaf node in the frame tree.
|
||||||
Element(Element),
|
Element(Element),
|
||||||
|
/// An interior node.
|
||||||
Frame(Rc<Frame>),
|
Frame(Rc<Frame>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,7 +263,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let mut resolved = Length::zero();
|
let mut resolved = Length::zero();
|
||||||
for node in (0 .. self.rows.len()).filter_map(|y| self.cell(x, y)) {
|
for node in (0 .. self.rows.len()).filter_map(|y| self.cell(x, y)) {
|
||||||
let size = Gen::new(available, Length::inf()).to_size(self.main);
|
let size = Gen::new(available, Length::inf()).to_size(self.main);
|
||||||
let regions = Regions::one(size, Spec::splat(false));
|
let regions = Regions::one(size, size, Spec::splat(false));
|
||||||
let frame = node.layout(ctx, ®ions).remove(0);
|
let frame = node.layout(ctx, ®ions).remove(0);
|
||||||
resolved.set_max(frame.size.get(self.cross));
|
resolved.set_max(frame.size.get(self.cross));
|
||||||
}
|
}
|
||||||
@ -405,7 +405,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 size = Gen::new(rcol, length).to_size(self.main);
|
let size = Gen::new(rcol, length).to_size(self.main);
|
||||||
let regions = Regions::one(size, Spec::splat(true));
|
let regions = Regions::one(size, size, Spec::splat(true));
|
||||||
let frame = node.layout(ctx, ®ions).remove(0);
|
let frame = node.layout(ctx, ®ions).remove(0);
|
||||||
output.push_frame(pos.to_point(self.main), frame.item);
|
output.push_frame(pos.to_point(self.main), frame.item);
|
||||||
}
|
}
|
||||||
@ -432,7 +432,8 @@ impl<'a> GridLayouter<'a> {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Prepare regions.
|
// Prepare regions.
|
||||||
let mut regions = Regions::one(self.to_size(first), Spec::splat(true));
|
let size = self.to_size(first);
|
||||||
|
let mut regions = Regions::one(size, size, Spec::splat(true));
|
||||||
regions.backlog = rest.iter().rev().map(|&v| self.to_size(v)).collect();
|
regions.backlog = rest.iter().rev().map(|&v| self.to_size(v)).collect();
|
||||||
|
|
||||||
// Layout the row.
|
// Layout the row.
|
||||||
|
@ -19,11 +19,10 @@ impl Layout for ImageNode {
|
|||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
&Regions { current, base, expand, .. }: &Regions,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let Regions { current, base, .. } = regions;
|
let mut constraints = Constraints::new(expand);
|
||||||
let mut constraints = Constraints::new(regions.expand);
|
constraints.set_base_if_linear(base, Spec::new(self.width, self.height));
|
||||||
constraints.set_base_using_linears(Spec::new(self.width, self.height), regions);
|
|
||||||
|
|
||||||
let width = self.width.map(|w| w.resolve(base.width));
|
let width = self.width.map(|w| w.resolve(base.width));
|
||||||
let height = self.height.map(|w| w.resolve(base.height));
|
let height = self.height.map(|w| w.resolve(base.height));
|
||||||
|
@ -6,7 +6,6 @@ use itertools::Itertools;
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
const CACHE_SIZE: usize = 20;
|
|
||||||
const TEMP_LEN: usize = 5;
|
const TEMP_LEN: usize = 5;
|
||||||
const TEMP_LAST: usize = TEMP_LEN - 1;
|
const TEMP_LAST: usize = TEMP_LEN - 1;
|
||||||
|
|
||||||
@ -23,22 +22,26 @@ pub struct LayoutCache {
|
|||||||
/// In how many compilations this cache has been used.
|
/// In how many compilations this cache has been used.
|
||||||
age: usize,
|
age: usize,
|
||||||
/// What cache eviction policy should be used.
|
/// What cache eviction policy should be used.
|
||||||
policy: EvictionStrategy,
|
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 {
|
impl LayoutCache {
|
||||||
/// Create a new, empty layout cache.
|
/// Create a new, empty layout cache.
|
||||||
pub fn new(policy: EvictionStrategy) -> Self {
|
pub fn new(policy: EvictionPolicy, max_size: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
frames: HashMap::default(),
|
frames: HashMap::default(),
|
||||||
age: 0,
|
age: 0,
|
||||||
policy,
|
policy,
|
||||||
|
max_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the cache is empty.
|
/// Whether the cache is empty.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.len() == 0
|
self.frames.values().all(|entry| entry.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Amount of items in the cache.
|
/// Amount of items in the cache.
|
||||||
@ -108,38 +111,34 @@ impl LayoutCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let last = entry.temperature[TEMP_LAST];
|
let last = entry.temperature[TEMP_LAST];
|
||||||
|
|
||||||
for i in (1 .. TEMP_LEN).rev() {
|
for i in (1 .. TEMP_LEN).rev() {
|
||||||
entry.temperature[i] = entry.temperature[i - 1];
|
entry.temperature[i] = entry.temperature[i - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.temperature[0] = 0;
|
entry.temperature[0] = 0;
|
||||||
entry.temperature[TEMP_LAST] += last;
|
entry.temperature[TEMP_LAST] += last;
|
||||||
|
|
||||||
entry.age += 1;
|
entry.age += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.evict();
|
self.evict();
|
||||||
|
|
||||||
self.frames.retain(|_, v| !v.is_empty());
|
self.frames.retain(|_, v| !v.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Evict the cache according to the policy.
|
||||||
fn evict(&mut self) {
|
fn evict(&mut self) {
|
||||||
let len = self.len();
|
let len = self.len();
|
||||||
if len <= CACHE_SIZE {
|
if len <= self.max_size {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.policy {
|
match self.policy {
|
||||||
EvictionStrategy::LeastRecentlyUsed => {
|
EvictionPolicy::LeastRecentlyUsed => {
|
||||||
// We find the element with the largest cooldown that cannot fit
|
// We find the element with the largest cooldown that cannot fit
|
||||||
// anymore.
|
// anymore.
|
||||||
let threshold = self
|
let threshold = self
|
||||||
.frames
|
.entries()
|
||||||
.values()
|
|
||||||
.flatten()
|
|
||||||
.map(|f| Reverse(f.cooldown()))
|
.map(|f| Reverse(f.cooldown()))
|
||||||
.k_smallest(len - CACHE_SIZE)
|
.k_smallest(len - self.max_size)
|
||||||
.last()
|
.last()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.0;
|
.0;
|
||||||
@ -148,13 +147,11 @@ impl LayoutCache {
|
|||||||
entries.retain(|e| e.cooldown() < threshold);
|
entries.retain(|e| e.cooldown() < threshold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EvictionStrategy::LeastFrequentlyUsed => {
|
EvictionPolicy::LeastFrequentlyUsed => {
|
||||||
let threshold = self
|
let threshold = self
|
||||||
.frames
|
.entries()
|
||||||
.values()
|
|
||||||
.flatten()
|
|
||||||
.map(|f| N32::from(f.hits() as f32 / f.age() as f32))
|
.map(|f| N32::from(f.hits() as f32 / f.age() as f32))
|
||||||
.k_smallest(len - CACHE_SIZE)
|
.k_smallest(len - self.max_size)
|
||||||
.last()
|
.last()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -164,30 +161,23 @@ impl LayoutCache {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EvictionStrategy::Random => {
|
EvictionPolicy::Random => {
|
||||||
// Fraction of items that should be kept.
|
// Fraction of items that should be kept.
|
||||||
let threshold = CACHE_SIZE as f32 / len as f32;
|
let threshold = self.max_size as f32 / len as f32;
|
||||||
for entries in self.frames.values_mut() {
|
for entries in self.frames.values_mut() {
|
||||||
entries.retain(|_| rand::random::<f32>() > threshold);
|
entries.retain(|_| rand::random::<f32>() > threshold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EvictionStrategy::Patterns => {
|
EvictionPolicy::Patterns => {
|
||||||
let kept = self
|
let kept = self.entries().filter(|f| f.properties().must_keep()).count();
|
||||||
.frames
|
|
||||||
.values()
|
|
||||||
.flatten()
|
|
||||||
.filter(|f| f.properties().must_keep())
|
|
||||||
.count();
|
|
||||||
|
|
||||||
let remaining_capacity = CACHE_SIZE - kept.min(CACHE_SIZE);
|
let remaining_capacity = self.max_size - kept.min(self.max_size);
|
||||||
if len - kept <= remaining_capacity {
|
if len - kept <= remaining_capacity {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let threshold = self
|
let threshold = self
|
||||||
.frames
|
.entries()
|
||||||
.values()
|
|
||||||
.flatten()
|
|
||||||
.filter(|f| !f.properties().must_keep())
|
.filter(|f| !f.properties().must_keep())
|
||||||
.map(|f| N32::from(f.hits() as f32 / f.age() as f32))
|
.map(|f| N32::from(f.hits() as f32 / f.age() as f32))
|
||||||
.k_smallest((len - kept) - remaining_capacity)
|
.k_smallest((len - kept) - remaining_capacity)
|
||||||
@ -201,7 +191,7 @@ impl LayoutCache {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EvictionStrategy::None => {}
|
EvictionPolicy::None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -267,6 +257,11 @@ impl FramesEntry {
|
|||||||
self.temperature[0] != 0
|
self.temperature[0] != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the total amount of hits over the lifetime of this item.
|
||||||
|
pub fn hits(&self) -> usize {
|
||||||
|
self.temperature.iter().sum()
|
||||||
|
}
|
||||||
|
|
||||||
/// The amount of consecutive cycles in which this item has not been used.
|
/// The amount of consecutive cycles in which this item has not been used.
|
||||||
pub fn cooldown(&self) -> usize {
|
pub fn cooldown(&self) -> usize {
|
||||||
let mut cycle = 0;
|
let mut cycle = 0;
|
||||||
@ -279,11 +274,7 @@ impl FramesEntry {
|
|||||||
cycle
|
cycle
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the total amount of hits over the lifetime of this item.
|
/// Properties that describe how this entry's temperature evolved.
|
||||||
pub fn hits(&self) -> usize {
|
|
||||||
self.temperature.iter().sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn properties(&self) -> PatternProperties {
|
pub fn properties(&self) -> PatternProperties {
|
||||||
let mut all_zeros = true;
|
let mut all_zeros = true;
|
||||||
let mut multi_use = false;
|
let mut multi_use = false;
|
||||||
@ -332,15 +323,13 @@ impl FramesEntry {
|
|||||||
all_zeros = false;
|
all_zeros = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
decreasing = decreasing && !all_same;
|
|
||||||
|
|
||||||
PatternProperties {
|
PatternProperties {
|
||||||
mature: self.age >= TEMP_LEN,
|
mature: self.age >= TEMP_LEN,
|
||||||
hit: self.temperature[0] >= 1,
|
hit: self.temperature[0] >= 1,
|
||||||
top_level: self.level == 0,
|
top_level: self.level == 0,
|
||||||
all_zeros,
|
all_zeros,
|
||||||
multi_use,
|
multi_use,
|
||||||
decreasing,
|
decreasing: decreasing && !all_same,
|
||||||
sparse,
|
sparse,
|
||||||
abandoned,
|
abandoned,
|
||||||
}
|
}
|
||||||
@ -349,7 +338,7 @@ impl FramesEntry {
|
|||||||
|
|
||||||
/// Cache eviction strategies.
|
/// Cache eviction strategies.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum EvictionStrategy {
|
pub enum EvictionPolicy {
|
||||||
/// Evict the least recently used item.
|
/// Evict the least recently used item.
|
||||||
LeastRecentlyUsed,
|
LeastRecentlyUsed,
|
||||||
/// Evict the least frequently used item.
|
/// Evict the least frequently used item.
|
||||||
@ -362,7 +351,7 @@ pub enum EvictionStrategy {
|
|||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for EvictionStrategy {
|
impl Default for EvictionPolicy {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Patterns
|
Self::Patterns
|
||||||
}
|
}
|
||||||
@ -415,23 +404,23 @@ impl PatternProperties {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn empty_frame() -> Vec<Constrained<Rc<Frame>>> {
|
fn empty_frames() -> Vec<Constrained<Rc<Frame>>> {
|
||||||
vec![Constrained {
|
vec![Constrained {
|
||||||
item: Rc::new(Frame::default()),
|
item: Rc::new(Frame::default()),
|
||||||
constraints: Constraints::new(Spec::splat(false)),
|
constraints: Constraints::new(Spec::splat(false)),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zero_region() -> Regions {
|
fn zero_regions() -> Regions {
|
||||||
Regions::one(Size::zero(), Spec::splat(false))
|
Regions::one(Size::zero(), Size::zero(), Spec::splat(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_temperature() {
|
fn test_incremental_temperature() {
|
||||||
let mut cache = LayoutCache::new(EvictionStrategy::None);
|
let mut cache = LayoutCache::new(EvictionPolicy::None, 20);
|
||||||
let zero_region = zero_region();
|
let regions = zero_regions();
|
||||||
cache.policy = EvictionStrategy::None;
|
cache.policy = EvictionPolicy::None;
|
||||||
cache.insert(0, empty_frame(), 0);
|
cache.insert(0, empty_frames(), 0);
|
||||||
|
|
||||||
let entry = cache.frames.get(&0).unwrap().first().unwrap();
|
let entry = cache.frames.get(&0).unwrap().first().unwrap();
|
||||||
assert_eq!(entry.age(), 1);
|
assert_eq!(entry.age(), 1);
|
||||||
@ -439,7 +428,7 @@ mod tests {
|
|||||||
assert_eq!(entry.used_cycles, 0);
|
assert_eq!(entry.used_cycles, 0);
|
||||||
assert_eq!(entry.level, 0);
|
assert_eq!(entry.level, 0);
|
||||||
|
|
||||||
cache.get(0, &zero_region).unwrap();
|
cache.get(0, ®ions).unwrap();
|
||||||
let entry = cache.frames.get(&0).unwrap().first().unwrap();
|
let entry = cache.frames.get(&0).unwrap().first().unwrap();
|
||||||
assert_eq!(entry.age(), 1);
|
assert_eq!(entry.age(), 1);
|
||||||
assert_eq!(entry.temperature, [1, 0, 0, 0, 0]);
|
assert_eq!(entry.temperature, [1, 0, 0, 0, 0]);
|
||||||
@ -450,7 +439,7 @@ mod tests {
|
|||||||
assert_eq!(entry.temperature, [0, 1, 0, 0, 0]);
|
assert_eq!(entry.temperature, [0, 1, 0, 0, 0]);
|
||||||
assert_eq!(entry.used_cycles, 1);
|
assert_eq!(entry.used_cycles, 1);
|
||||||
|
|
||||||
cache.get(0, &zero_region).unwrap();
|
cache.get(0, ®ions).unwrap();
|
||||||
for _ in 0 .. 4 {
|
for _ in 0 .. 4 {
|
||||||
cache.turnaround();
|
cache.turnaround();
|
||||||
}
|
}
|
||||||
@ -462,10 +451,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_properties() {
|
fn test_incremental_properties() {
|
||||||
let mut cache = LayoutCache::new(EvictionStrategy::None);
|
let mut cache = LayoutCache::new(EvictionPolicy::None, 20);
|
||||||
cache.policy = EvictionStrategy::None;
|
cache.policy = EvictionPolicy::None;
|
||||||
cache.insert(0, empty_frame(), 1);
|
cache.insert(0, empty_frames(), 1);
|
||||||
|
|
||||||
let props = cache.frames.get(&0).unwrap().first().unwrap().properties();
|
let props = cache.frames.get(&0).unwrap().first().unwrap().properties();
|
||||||
assert_eq!(props.top_level, false);
|
assert_eq!(props.top_level, false);
|
||||||
|
@ -10,6 +10,7 @@ mod image;
|
|||||||
mod incremental;
|
mod incremental;
|
||||||
mod pad;
|
mod pad;
|
||||||
mod par;
|
mod par;
|
||||||
|
mod regions;
|
||||||
mod shaping;
|
mod shaping;
|
||||||
mod stack;
|
mod stack;
|
||||||
mod tree;
|
mod tree;
|
||||||
@ -24,13 +25,11 @@ pub use grid::*;
|
|||||||
pub use incremental::*;
|
pub use incremental::*;
|
||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
pub use par::*;
|
pub use par::*;
|
||||||
|
pub use regions::*;
|
||||||
pub use shaping::*;
|
pub use shaping::*;
|
||||||
pub use stack::*;
|
pub use stack::*;
|
||||||
pub use tree::*;
|
pub use tree::*;
|
||||||
|
|
||||||
use std::hash::Hash;
|
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
use std::hash::Hasher;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::font::FontStore;
|
use crate::font::FontStore;
|
||||||
@ -45,16 +44,6 @@ pub fn layout(ctx: &mut Context, tree: &LayoutTree) -> Vec<Rc<Frame>> {
|
|||||||
tree.layout(&mut ctx)
|
tree.layout(&mut ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a node.
|
|
||||||
pub trait Layout {
|
|
||||||
/// Layout the node into the given regions.
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
ctx: &mut LayoutContext,
|
|
||||||
regions: &Regions,
|
|
||||||
) -> Vec<Constrained<Rc<Frame>>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The context for layouting.
|
/// The context for layouting.
|
||||||
pub struct LayoutContext<'a> {
|
pub struct LayoutContext<'a> {
|
||||||
/// Stores parsed font faces.
|
/// Stores parsed font faces.
|
||||||
@ -83,94 +72,12 @@ impl<'a> LayoutContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A sequence of regions to layout into.
|
/// Layout a node.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
pub trait Layout {
|
||||||
pub struct Regions {
|
/// Layout the node into the given regions.
|
||||||
/// The remaining size of the current region.
|
fn layout(
|
||||||
pub current: Size,
|
&self,
|
||||||
/// The base size for relative sizing.
|
ctx: &mut LayoutContext,
|
||||||
pub base: Size,
|
regions: &Regions,
|
||||||
/// A stack of followup regions.
|
) -> Vec<Constrained<Rc<Frame>>>;
|
||||||
///
|
|
||||||
/// Note that this is a stack and not a queue! The size of the next region is
|
|
||||||
/// `backlog.last()`.
|
|
||||||
pub backlog: Vec<Size>,
|
|
||||||
/// The final region that is repeated once the backlog is drained.
|
|
||||||
pub last: Option<Size>,
|
|
||||||
/// Whether nodes should expand to fill the regions instead of shrinking to
|
|
||||||
/// fit the content.
|
|
||||||
///
|
|
||||||
/// This property is only handled by nodes that have the ability to control
|
|
||||||
/// their own size.
|
|
||||||
pub expand: Spec<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Regions {
|
|
||||||
/// Create a new region sequence with exactly one region.
|
|
||||||
pub fn one(size: Size, expand: Spec<bool>) -> Self {
|
|
||||||
Self {
|
|
||||||
current: size,
|
|
||||||
base: size,
|
|
||||||
backlog: vec![],
|
|
||||||
last: None,
|
|
||||||
expand,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new sequence of same-size regions that repeats indefinitely.
|
|
||||||
pub fn repeat(size: Size, expand: Spec<bool>) -> Self {
|
|
||||||
Self {
|
|
||||||
current: size,
|
|
||||||
base: size,
|
|
||||||
backlog: vec![],
|
|
||||||
last: Some(size),
|
|
||||||
expand,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create new regions where all sizes are mapped with `f`.
|
|
||||||
pub fn map<F>(&self, mut f: F) -> Self
|
|
||||||
where
|
|
||||||
F: FnMut(Size) -> Size,
|
|
||||||
{
|
|
||||||
let mut regions = self.clone();
|
|
||||||
regions.mutate(|s| *s = f(*s));
|
|
||||||
regions
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether `current` is a fully sized (untouched) copy of the last region.
|
|
||||||
///
|
|
||||||
/// If this is true, calling `next()` will have no effect.
|
|
||||||
pub fn in_full_last(&self) -> bool {
|
|
||||||
self.backlog.is_empty() && self.last.map_or(true, |size| self.current == size)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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.iter().rev();
|
|
||||||
let last = self.last.iter().cycle();
|
|
||||||
first.chain(backlog.chain(last).map(|&s| (s, s)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Advance to the next region if there is any.
|
|
||||||
pub fn next(&mut self) {
|
|
||||||
if let Some(size) = self.backlog.pop().or(self.last) {
|
|
||||||
self.current = size;
|
|
||||||
self.base = size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mutate all contained sizes in place.
|
|
||||||
pub fn mutate<F>(&mut self, mut f: F)
|
|
||||||
where
|
|
||||||
F: FnMut(&mut Size),
|
|
||||||
{
|
|
||||||
f(&mut self.current);
|
|
||||||
f(&mut self.base);
|
|
||||||
self.last.as_mut().map(|x| f(x));
|
|
||||||
self.backlog.iter_mut().for_each(f);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,49 +16,62 @@ impl Layout for PadNode {
|
|||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let mut regions = regions.clone();
|
|
||||||
let mut frames = self.child.layout(
|
let mut frames = self.child.layout(
|
||||||
ctx,
|
ctx,
|
||||||
®ions.map(|size| size - self.padding.resolve(size).size()),
|
®ions.map(|size| size - self.padding.resolve(size).size()),
|
||||||
);
|
);
|
||||||
|
|
||||||
for frame in &mut frames {
|
for (Constrained { item: frame, constraints }, (current, base)) in
|
||||||
let padded = solve(self.padding, frame.size);
|
frames.iter_mut().zip(regions.iter())
|
||||||
let padding = self.padding.resolve(padded);
|
{
|
||||||
let origin = Point::new(padding.left, padding.top);
|
|
||||||
|
|
||||||
let mut new = Frame::new(padded, frame.baseline + origin.y);
|
|
||||||
let prev = std::mem::take(&mut frame.item);
|
|
||||||
new.push_frame(origin, prev);
|
|
||||||
|
|
||||||
frame.constraints.inflate(padding.size(), ®ions);
|
|
||||||
|
|
||||||
if self.padding.left.is_relative() || self.padding.right.is_relative() {
|
|
||||||
frame.constraints.base.horizontal = Some(regions.base.width);
|
|
||||||
}
|
|
||||||
if self.padding.top.is_relative() || self.padding.bottom.is_relative() {
|
|
||||||
frame.constraints.base.vertical = Some(regions.base.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
regions.next();
|
|
||||||
*Rc::make_mut(&mut frame.item) = new;
|
|
||||||
}
|
|
||||||
|
|
||||||
frames
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Solve for the size `padded` that satisfies (approximately):
|
|
||||||
/// `padded - padding.resolve(padded).size() == size`
|
|
||||||
fn solve(padding: Sides<Linear>, size: Size) -> Size {
|
|
||||||
fn solve_axis(length: Length, padding: Linear) -> Length {
|
fn solve_axis(length: Length, padding: Linear) -> Length {
|
||||||
(length + padding.abs) / (1.0 - padding.rel.get())
|
(length + padding.abs) / (1.0 - padding.rel.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
Size::new(
|
// Solve for the size `padded` that satisfies (approximately):
|
||||||
solve_axis(size.width, padding.left + padding.right),
|
// `padded - padding.resolve(padded).size() == size`
|
||||||
solve_axis(size.height, padding.top + padding.bottom),
|
let padded = Size::new(
|
||||||
)
|
solve_axis(frame.size.width, self.padding.left + self.padding.right),
|
||||||
|
solve_axis(frame.size.height, self.padding.top + self.padding.bottom),
|
||||||
|
);
|
||||||
|
|
||||||
|
let padding = self.padding.resolve(padded);
|
||||||
|
let origin = Point::new(padding.left, padding.top);
|
||||||
|
|
||||||
|
// Inflate min and max contraints by the padding.
|
||||||
|
for spec in [&mut constraints.min, &mut constraints.max] {
|
||||||
|
if let Some(horizontal) = spec.horizontal.as_mut() {
|
||||||
|
*horizontal += padding.size().width;
|
||||||
|
}
|
||||||
|
if let Some(vertical) = spec.vertical.as_mut() {
|
||||||
|
*vertical += padding.size().height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set exact and base constraints if the child had them.
|
||||||
|
constraints.exact.horizontal.and_set(Some(current.width));
|
||||||
|
constraints.exact.vertical.and_set(Some(current.height));
|
||||||
|
constraints.base.horizontal.and_set(Some(base.width));
|
||||||
|
constraints.base.vertical.and_set(Some(base.height));
|
||||||
|
|
||||||
|
// Also set base constraints if the padding is relative.
|
||||||
|
if self.padding.left.is_relative() || self.padding.right.is_relative() {
|
||||||
|
constraints.base.horizontal = Some(base.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.padding.top.is_relative() || self.padding.bottom.is_relative() {
|
||||||
|
constraints.base.vertical = Some(base.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new larger frame and place the child's frame inside it.
|
||||||
|
let empty = Frame::new(padded, frame.baseline + origin.y);
|
||||||
|
let prev = std::mem::replace(frame, Rc::new(empty));
|
||||||
|
let new = Rc::make_mut(frame);
|
||||||
|
new.push_frame(origin, prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
frames
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PadNode> for LayoutNode {
|
impl From<PadNode> for LayoutNode {
|
||||||
|
93
src/layout/regions.rs
Normal file
93
src/layout/regions.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
use crate::geom::{Size, Spec};
|
||||||
|
|
||||||
|
/// A sequence of regions to layout into.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct Regions {
|
||||||
|
/// The remaining size of the current region.
|
||||||
|
pub current: Size,
|
||||||
|
/// The base size for relative sizing.
|
||||||
|
pub base: Size,
|
||||||
|
/// A stack of followup regions.
|
||||||
|
///
|
||||||
|
/// Note that this is a stack and not a queue! The size of the next region is
|
||||||
|
/// `backlog.last()`.
|
||||||
|
pub backlog: Vec<Size>,
|
||||||
|
/// The final region that is repeated once the backlog is drained.
|
||||||
|
pub last: Option<Size>,
|
||||||
|
/// Whether nodes should expand to fill the regions instead of shrinking to
|
||||||
|
/// fit the content.
|
||||||
|
///
|
||||||
|
/// This property is only handled by nodes that have the ability to control
|
||||||
|
/// their own size.
|
||||||
|
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![],
|
||||||
|
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![],
|
||||||
|
last: Some(size),
|
||||||
|
expand,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new regions where all sizes are mapped with `f`.
|
||||||
|
pub fn map<F>(&self, mut f: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnMut(Size) -> Size,
|
||||||
|
{
|
||||||
|
let mut regions = self.clone();
|
||||||
|
regions.mutate(|s| *s = f(*s));
|
||||||
|
regions
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether `current` is a fully sized (untouched) copy of the last region.
|
||||||
|
///
|
||||||
|
/// If this is true, calling `next()` will have no effect.
|
||||||
|
pub fn in_full_last(&self) -> bool {
|
||||||
|
self.backlog.is_empty() && self.last.map_or(true, |size| self.current == size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.iter().rev();
|
||||||
|
let last = self.last.iter().cycle();
|
||||||
|
first.chain(backlog.chain(last).map(|&s| (s, s)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance to the next region if there is any.
|
||||||
|
pub fn next(&mut self) {
|
||||||
|
if let Some(size) = self.backlog.pop().or(self.last) {
|
||||||
|
self.current = size;
|
||||||
|
self.base = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutate all contained sizes in place.
|
||||||
|
pub fn mutate<F>(&mut self, mut f: F)
|
||||||
|
where
|
||||||
|
F: FnMut(&mut Size),
|
||||||
|
{
|
||||||
|
f(&mut self.current);
|
||||||
|
f(&mut self.base);
|
||||||
|
self.last.as_mut().map(|x| f(x));
|
||||||
|
self.backlog.iter_mut().for_each(f);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,3 @@
|
|||||||
use decorum::N64;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// A node that stacks its children.
|
/// A node that stacks its children.
|
||||||
@ -11,10 +9,6 @@ pub struct StackNode {
|
|||||||
/// The children are stacked along the `main` direction. The `cross`
|
/// The children are stacked along the `main` direction. The `cross`
|
||||||
/// direction is required for aligning the children.
|
/// direction is required for aligning the children.
|
||||||
pub dirs: Gen<Dir>,
|
pub dirs: Gen<Dir>,
|
||||||
/// The fixed aspect ratio between width and height, if any.
|
|
||||||
///
|
|
||||||
/// The resulting frames will satisfy `width = aspect * height`.
|
|
||||||
pub aspect: Option<N64>,
|
|
||||||
/// The nodes to be stacked.
|
/// The nodes to be stacked.
|
||||||
pub children: Vec<StackChild>,
|
pub children: Vec<StackChild>,
|
||||||
}
|
}
|
||||||
@ -83,10 +77,6 @@ impl<'a> StackLayouter<'a> {
|
|||||||
// Disable expansion on the main axis for children.
|
// Disable expansion on the main axis for children.
|
||||||
regions.expand.set(main, false);
|
regions.expand.set(main, false);
|
||||||
|
|
||||||
if let Some(aspect) = stack.aspect {
|
|
||||||
regions.current = regions.current.with_aspect(aspect.into_inner());
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
stack,
|
stack,
|
||||||
main,
|
main,
|
||||||
@ -161,6 +151,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
.max
|
.max
|
||||||
.get_mut(self.main)
|
.get_mut(self.main)
|
||||||
.set_min(self.used.main + size.main);
|
.set_min(self.used.main + size.main);
|
||||||
|
|
||||||
self.finish_region();
|
self.finish_region();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +175,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
|
|
||||||
// Determine the stack's size dependening on whether the region is
|
// Determine the stack's size dependening on whether the region is
|
||||||
// fixed.
|
// fixed.
|
||||||
let mut size = Size::new(
|
let size = Size::new(
|
||||||
if expand.horizontal {
|
if expand.horizontal {
|
||||||
self.constraints.exact.horizontal = Some(self.full.width);
|
self.constraints.exact.horizontal = Some(self.full.width);
|
||||||
self.full.width
|
self.full.width
|
||||||
@ -201,20 +192,6 @@ impl<'a> StackLayouter<'a> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Make sure the stack's size satisfies the aspect ratio.
|
|
||||||
if let Some(aspect) = self.stack.aspect {
|
|
||||||
self.constraints.exact = self.full.to_spec().map(Some);
|
|
||||||
self.constraints.min = Spec::splat(None);
|
|
||||||
self.constraints.max = Spec::splat(None);
|
|
||||||
let width = size
|
|
||||||
.width
|
|
||||||
.max(aspect.into_inner() * size.height)
|
|
||||||
.min(self.full.width)
|
|
||||||
.min(aspect.into_inner() * self.full.height);
|
|
||||||
|
|
||||||
size = Size::new(width, width / aspect.into_inner());
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.overflowing {
|
if self.overflowing {
|
||||||
self.constraints.min.vertical = None;
|
self.constraints.min.vertical = None;
|
||||||
self.constraints.max.vertical = None;
|
self.constraints.max.vertical = None;
|
||||||
@ -259,10 +236,6 @@ impl<'a> StackLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.regions.next();
|
self.regions.next();
|
||||||
if let Some(aspect) = self.stack.aspect {
|
|
||||||
self.regions.current = self.regions.current.with_aspect(aspect.into_inner());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.full = self.regions.current;
|
self.full = self.regions.current;
|
||||||
self.used = Gen::zero();
|
self.used = Gen::zero();
|
||||||
self.ruler = Align::Start;
|
self.ruler = Align::Start;
|
||||||
|
@ -3,6 +3,9 @@ use super::*;
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
use fxhash::FxHasher64;
|
use fxhash::FxHasher64;
|
||||||
|
|
||||||
@ -37,7 +40,7 @@ impl PageRun {
|
|||||||
// that axis.
|
// that axis.
|
||||||
let Size { width, height } = self.size;
|
let Size { width, height } = self.size;
|
||||||
let expand = Spec::new(width.is_finite(), height.is_finite());
|
let expand = Spec::new(width.is_finite(), height.is_finite());
|
||||||
let regions = Regions::repeat(self.size, expand);
|
let regions = Regions::repeat(self.size, self.size, expand);
|
||||||
self.child.layout(ctx, ®ions).into_iter().map(|c| c.item).collect()
|
self.child.layout(ctx, ®ions).into_iter().map(|c| c.item).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
36
src/lib.rs
36
src/lib.rs
@ -53,7 +53,7 @@ use crate::eval::{Module, Scope, State};
|
|||||||
use crate::font::FontStore;
|
use crate::font::FontStore;
|
||||||
use crate::image::ImageStore;
|
use crate::image::ImageStore;
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
use crate::layout::{EvictionStrategy, LayoutCache};
|
use crate::layout::{EvictionPolicy, LayoutCache};
|
||||||
use crate::layout::{Frame, LayoutTree};
|
use crate::layout::{Frame, LayoutTree};
|
||||||
use crate::loading::Loader;
|
use crate::loading::Loader;
|
||||||
use crate::source::{SourceId, SourceStore};
|
use crate::source::{SourceId, SourceStore};
|
||||||
@ -137,12 +137,13 @@ impl Context {
|
|||||||
/// A builder for a [`Context`].
|
/// A builder for a [`Context`].
|
||||||
///
|
///
|
||||||
/// This struct is created by [`Context::builder`].
|
/// This struct is created by [`Context::builder`].
|
||||||
#[derive(Default)]
|
|
||||||
pub struct ContextBuilder {
|
pub struct ContextBuilder {
|
||||||
std: Option<Scope>,
|
std: Option<Scope>,
|
||||||
state: Option<State>,
|
state: Option<State>,
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
policy: Option<EvictionStrategy>,
|
policy: EvictionPolicy,
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
max_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextBuilder {
|
impl ContextBuilder {
|
||||||
@ -161,8 +162,18 @@ impl ContextBuilder {
|
|||||||
|
|
||||||
/// The policy for eviction of the layout cache.
|
/// The policy for eviction of the layout cache.
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
pub fn policy(mut self, policy: EvictionStrategy) -> Self {
|
pub fn cache_policy(mut self, policy: EvictionPolicy) -> Self {
|
||||||
self.policy = Some(policy);
|
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].
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
pub fn cache_max_size(mut self, max_size: usize) -> Self {
|
||||||
|
self.max_size = max_size;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,9 +186,22 @@ impl ContextBuilder {
|
|||||||
images: ImageStore::new(Rc::clone(&loader)),
|
images: ImageStore::new(Rc::clone(&loader)),
|
||||||
loader,
|
loader,
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
layouts: LayoutCache::new(self.policy.unwrap_or_default()),
|
layouts: LayoutCache::new(self.policy, self.max_size),
|
||||||
std: self.std.unwrap_or(library::new()),
|
std: self.std.unwrap_or(library::new()),
|
||||||
state: self.state.unwrap_or_default(),
|
state: self.state.unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ContextBuilder {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
std: None,
|
||||||
|
state: None,
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
policy: EvictionPolicy::default(),
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
max_size: 2000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -64,18 +64,19 @@ fn rect_impl(
|
|||||||
body: Template,
|
body: Template,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
Value::Template(Template::from_inline(move |state| {
|
Value::Template(Template::from_inline(move |state| {
|
||||||
let mut stack = body.to_stack(state);
|
let mut node = LayoutNode::new(FixedNode {
|
||||||
stack.aspect = aspect;
|
width,
|
||||||
|
height,
|
||||||
let mut node = FixedNode { width, height, child: stack.into() }.into();
|
aspect,
|
||||||
|
child: body.to_stack(state).into(),
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(fill) = fill {
|
if let Some(fill) = fill {
|
||||||
node = BackgroundNode {
|
node = LayoutNode::new(BackgroundNode {
|
||||||
shape: BackgroundShape::Rect,
|
shape: BackgroundShape::Rect,
|
||||||
fill: Paint::Color(fill),
|
fill: Paint::Color(fill),
|
||||||
child: node,
|
child: node,
|
||||||
}
|
});
|
||||||
.into();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node
|
node
|
||||||
@ -120,27 +121,22 @@ fn ellipse_impl(
|
|||||||
// perfectly into the ellipse.
|
// perfectly into the ellipse.
|
||||||
const PAD: f64 = 0.5 - SQRT_2 / 4.0;
|
const PAD: f64 = 0.5 - SQRT_2 / 4.0;
|
||||||
|
|
||||||
let mut stack = body.to_stack(state);
|
let mut node = LayoutNode::new(FixedNode {
|
||||||
stack.aspect = aspect;
|
|
||||||
|
|
||||||
let mut node = FixedNode {
|
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
child: PadNode {
|
aspect,
|
||||||
|
child: LayoutNode::new(PadNode {
|
||||||
padding: Sides::splat(Relative::new(PAD).into()),
|
padding: Sides::splat(Relative::new(PAD).into()),
|
||||||
child: stack.into(),
|
child: body.to_stack(state).into(),
|
||||||
}
|
}),
|
||||||
.into(),
|
});
|
||||||
}
|
|
||||||
.into();
|
|
||||||
|
|
||||||
if let Some(fill) = fill {
|
if let Some(fill) = fill {
|
||||||
node = BackgroundNode {
|
node = LayoutNode::new(BackgroundNode {
|
||||||
shape: BackgroundShape::Ellipse,
|
shape: BackgroundShape::Ellipse,
|
||||||
fill: Paint::Color(fill),
|
fill: Paint::Color(fill),
|
||||||
child: node,
|
child: node,
|
||||||
}
|
});
|
||||||
.into();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node
|
node
|
||||||
|
@ -145,8 +145,12 @@ pub fn boxed(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
|||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
let body: Template = args.eat().unwrap_or_default();
|
let body: Template = args.eat().unwrap_or_default();
|
||||||
Ok(Value::Template(Template::from_inline(move |state| {
|
Ok(Value::Template(Template::from_inline(move |state| {
|
||||||
let child = body.to_stack(state).into();
|
FixedNode {
|
||||||
FixedNode { width, height, child }
|
width,
|
||||||
|
height,
|
||||||
|
aspect: None,
|
||||||
|
child: body.to_stack(state).into(),
|
||||||
|
}
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,10 +194,7 @@ pub fn stack(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
|||||||
Ok(Value::Template(Template::from_block(move |state| {
|
Ok(Value::Template(Template::from_block(move |state| {
|
||||||
let children = children
|
let children = children
|
||||||
.iter()
|
.iter()
|
||||||
.map(|child| {
|
.map(|child| StackChild::Any(child.to_stack(state).into(), state.aligns))
|
||||||
let child = child.to_stack(state).into();
|
|
||||||
StackChild::Any(child, state.aligns)
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut dirs = Gen::new(None, dir).unwrap_or(state.dirs);
|
let mut dirs = Gen::new(None, dir).unwrap_or(state.dirs);
|
||||||
@ -204,7 +205,7 @@ pub fn stack(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
|||||||
dirs.cross = state.dirs.main;
|
dirs.cross = state.dirs.main;
|
||||||
}
|
}
|
||||||
|
|
||||||
StackNode { dirs, aspect: None, children }
|
StackNode { dirs, children }
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ use crate::diag::TypResult;
|
|||||||
use crate::eval::{Arguments, EvalContext, Scope, Str, Template, Value};
|
use crate::eval::{Arguments, EvalContext, Scope, Str, Template, Value};
|
||||||
use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
|
use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
|
||||||
use crate::geom::*;
|
use crate::geom::*;
|
||||||
|
use crate::layout::LayoutNode;
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
|
|
||||||
/// Construct a scope containing all standard library definitions.
|
/// Construct a scope containing all standard library definitions.
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.1 KiB |
@ -1,12 +1,18 @@
|
|||||||
// Test the `square` function.
|
// Test the `square` function.
|
||||||
|
|
||||||
---
|
---
|
||||||
Auto-sized square. \
|
// Test auto-sized square.
|
||||||
#square(fill: eastern)[
|
#square(fill: eastern)[
|
||||||
#font(fill: white, weight: bold)
|
#font(fill: white, weight: bold)
|
||||||
#align(center)
|
#align(center)
|
||||||
#pad(5pt)[Typst]
|
#pad(5pt)[Typst]
|
||||||
]
|
]
|
||||||
|
---
|
||||||
|
// Test relative-sized child.
|
||||||
|
#square(fill: eastern)[
|
||||||
|
#rect(width: 10pt, height: 5pt, fill: conifer) \
|
||||||
|
#rect(width: 40%, height: 5pt, fill: conifer)
|
||||||
|
]
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test height overflow.
|
// Test height overflow.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user