Constraints (#31)

This commit is contained in:
Martin 2021-06-17 14:18:43 +02:00 committed by GitHub
parent e2cdda67dc
commit e14e804789
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 426 additions and 85 deletions

View File

@ -23,6 +23,14 @@ impl<T> Gen<T> {
Self { cross: value.clone(), main: value } Self { cross: value.clone(), main: value }
} }
/// Maps the individual fields with `f`.
pub fn map<F, U>(self, mut f: F) -> Gen<U>
where
F: FnMut(T) -> U,
{
Gen { cross: f(self.cross), main: f(self.main) }
}
/// Convert to the specific representation. /// Convert to the specific representation.
pub fn to_spec(self, main: SpecAxis) -> Spec<T> { pub fn to_spec(self, main: SpecAxis) -> Spec<T> {
match main { match main {

View File

@ -40,6 +40,11 @@ impl Linear {
pub fn is_zero(self) -> bool { pub fn is_zero(self) -> bool {
self.rel.is_zero() && self.abs.is_zero() self.rel.is_zero() && self.abs.is_zero()
} }
/// Whether there is a linear component.
pub fn is_relative(&self) -> bool {
!self.rel.is_zero()
}
} }
impl Display for Linear { impl Display for Linear {

View File

@ -50,6 +50,11 @@ impl Size {
Point::new(self.width, self.height) Point::new(self.width, self.height)
} }
/// Convert to a Spec.
pub fn to_spec(self) -> Spec<Length> {
Spec::new(self.width, self.height)
}
/// Convert to the generic representation. /// Convert to the generic representation.
pub fn to_gen(self, main: SpecAxis) -> Gen<Length> { pub fn to_gen(self, main: SpecAxis) -> Gen<Length> {
match main { match main {

View File

@ -26,6 +26,17 @@ impl<T> Spec<T> {
} }
} }
/// Maps the individual fields with `f`.
pub fn map<F, U>(self, mut f: F) -> Spec<U>
where
F: FnMut(T) -> U,
{
Spec {
horizontal: f(self.horizontal),
vertical: f(self.vertical),
}
}
/// Convert to the generic representation. /// Convert to the generic representation.
pub fn to_gen(self, main: SpecAxis) -> Gen<T> { pub fn to_gen(self, main: SpecAxis) -> Gen<T> {
match main { match main {
@ -33,6 +44,15 @@ impl<T> Spec<T> {
SpecAxis::Vertical => Gen::new(self.horizontal, self.vertical), SpecAxis::Vertical => Gen::new(self.horizontal, self.vertical),
} }
} }
/// Compare to whether two instances are equal when compared field-by-field
/// with `f`.
pub fn eq_by<U, F>(&self, other: &Spec<U>, eq: F) -> bool
where
F: Fn(&T, &U) -> bool,
{
eq(&self.vertical, &other.vertical) && eq(&self.horizontal, &other.horizontal)
}
} }
impl Spec<Length> { impl Spec<Length> {

View File

@ -19,7 +19,11 @@ pub enum BackgroundShape {
} }
impl Layout for BackgroundNode { impl Layout for BackgroundNode {
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> { fn layout(
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>> {
let mut frames = self.child.layout(ctx, regions); let mut frames = self.child.layout(ctx, regions);
for frame in &mut frames { for frame in &mut frames {
@ -31,7 +35,7 @@ impl Layout for BackgroundNode {
}; };
let element = Element::Geometry(shape, self.fill); let element = Element::Geometry(shape, self.fill);
frame.elements.insert(0, (point, element)); frame.item.elements.insert(0, (point, element));
} }
frames frames

View File

@ -12,16 +12,37 @@ pub struct FixedNode {
} }
impl Layout for FixedNode { impl Layout for FixedNode {
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> { fn layout(
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>> {
let Regions { current, base, .. } = regions; let Regions { current, base, .. } = regions;
let mut constraints = Constraints::new(regions.expand);
constraints.set_base_using_linears(Spec::new(self.width, self.height), &regions);
let size = Size::new( let size = Size::new(
self.width.map_or(current.width, |w| w.resolve(base.width)), self.width.map_or(current.width, |w| w.resolve(base.width)),
self.height.map_or(current.height, |h| h.resolve(base.height)), self.height.map_or(current.height, |h| h.resolve(base.height)),
); );
// If one dimension was not specified, the `current` size needs to remain static.
if self.width.is_none() {
constraints.exact.horizontal = Some(current.width);
}
if self.height.is_none() {
constraints.exact.vertical = Some(current.height);
}
let expand = Spec::new(self.width.is_some(), self.height.is_some()); let expand = Spec::new(self.width.is_some(), self.height.is_some());
let regions = Regions::one(size, expand); let regions = Regions::one(size, expand);
self.child.layout(ctx, &regions) let mut frames = self.child.layout(ctx, &regions);
if let Some(frame) = frames.first_mut() {
frame.constraints = constraints;
}
frames
} }
} }

View File

@ -1,10 +1,11 @@
use serde::{Deserialize, Serialize};
use super::{Constrained, Constraints};
use crate::color::Color; use crate::color::Color;
use crate::font::FaceId; use crate::font::FaceId;
use crate::geom::{Length, Path, Point, Size}; use crate::geom::{Length, Path, Point, Size};
use crate::image::ImageId; use crate::image::ImageId;
use serde::{Deserialize, Serialize};
/// A finished layout with elements at fixed positions. /// A finished layout with elements at fixed positions.
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Frame { pub struct Frame {
@ -39,6 +40,11 @@ impl Frame {
} }
} }
} }
/// Wraps the frame with constraints.
pub fn constrain(self, constraints: Constraints) -> Constrained<Self> {
Constrained { item: self, constraints }
}
} }
/// The building block frames are composed of. /// The building block frames are composed of.

View File

@ -28,7 +28,11 @@ pub enum TrackSizing {
} }
impl Layout for GridNode { impl Layout for GridNode {
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> { fn layout(
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>> {
// Prepare grid layout by unifying content and gutter tracks. // Prepare grid layout by unifying content and gutter tracks.
let mut layouter = GridLayouter::new(self, regions.clone()); let mut layouter = GridLayouter::new(self, regions.clone());
@ -71,8 +75,10 @@ struct GridLayouter<'a> {
fr: Fractional, fr: Fractional,
/// Rows in the current region. /// Rows in the current region.
lrows: Vec<Row>, lrows: Vec<Row>,
/// Constraints for the active region.
constraints: Constraints,
/// Frames for finished regions. /// Frames for finished regions.
finished: Vec<Frame>, finished: Vec<Constrained<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,
@ -138,6 +144,7 @@ impl<'a> GridLayouter<'a> {
cols, cols,
rows, rows,
children: &grid.children, children: &grid.children,
constraints: Constraints::new(regions.expand),
regions, regions,
rcols, rcols,
lrows: vec![], lrows: vec![],
@ -150,6 +157,16 @@ impl<'a> GridLayouter<'a> {
/// Determine all column sizes. /// Determine all column sizes.
fn measure_columns(&mut self, ctx: &mut LayoutContext) { fn measure_columns(&mut self, ctx: &mut LayoutContext) {
enum Case {
PurelyLinear,
Fitting,
Overflowing,
Exact,
}
// 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();
@ -164,13 +181,20 @@ 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(base.cross); let resolved = v.resolve(base.cross);
*rcol = resolved; *rcol = resolved;
linear += resolved; linear += resolved;
*self.constraints.base.get_mut(self.cross) =
Some(self.regions.base.get(self.cross));
}
TrackSizing::Fractional(v) => {
case = Case::Fitting;
fr += v;
} }
TrackSizing::Fractional(v) => fr += v,
} }
} }
@ -184,13 +208,33 @@ impl<'a> GridLayouter<'a> {
// otherwise shrink auto columns. // otherwise shrink auto columns.
let remaining = available - auto; let remaining = available - auto;
if remaining >= Length::zero() { if remaining >= Length::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 let Case::Fitting = case {
case = Case::Overflowing;
} }
self.used.cross = self.rcols.iter().sum(); self.used.cross = self.rcols.iter().sum();
match case {
Case::PurelyLinear => {}
Case::Fitting => {
*self.constraints.min.get_mut(self.cross) = Some(self.used.cross);
}
Case::Overflowing => {
*self.constraints.max.get_mut(self.cross) = Some(linear);
}
Case::Exact => {
*self.constraints.exact.get_mut(self.cross) =
Some(self.regions.current.get(self.cross));
}
}
} }
/// Measure the size that is available to auto columns. /// Measure the size that is available to auto columns.
@ -268,7 +312,7 @@ impl<'a> GridLayouter<'a> {
} }
/// Layout the grid row-by-row. /// Layout the grid row-by-row.
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Frame> { fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Frame>> {
for y in 0 .. self.rows.len() { for y in 0 .. self.rows.len() {
match self.rows[y] { match self.rows[y] {
TrackSizing::Auto => { TrackSizing::Auto => {
@ -276,12 +320,16 @@ impl<'a> GridLayouter<'a> {
} }
TrackSizing::Linear(v) => { TrackSizing::Linear(v) => {
let base = self.regions.base.get(self.main); let base = self.regions.base.get(self.main);
if v.is_relative() {
*self.constraints.base.get_mut(self.main) = Some(base);
}
let resolved = v.resolve(base); let resolved = v.resolve(base);
let frame = self.layout_single_row(ctx, resolved, y); let frame = self.layout_single_row(ctx, resolved, y);
self.push_row(ctx, frame); self.push_row(ctx, frame);
} }
TrackSizing::Fractional(v) => { TrackSizing::Fractional(v) => {
self.fr += v; self.fr += v;
*self.constraints.exact.get_mut(self.main) = Some(self.full);
self.lrows.push(Row::Fr(v, y)); self.lrows.push(Row::Fr(v, y));
} }
} }
@ -326,7 +374,11 @@ impl<'a> GridLayouter<'a> {
self.push_row(ctx, frame); self.push_row(ctx, frame);
} else { } else {
let frames = self.layout_multi_row(ctx, first, &rest, y); let frames = self.layout_multi_row(ctx, first, &rest, y);
for frame in frames { let len = frames.len();
for (i, frame) in frames.into_iter().enumerate() {
if i + 1 != len {
*self.constraints.exact.get_mut(self.main) = Some(self.full);
}
self.push_row(ctx, frame); self.push_row(ctx, frame);
} }
} }
@ -348,7 +400,7 @@ impl<'a> GridLayouter<'a> {
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, Spec::splat(true));
let frame = node.layout(ctx, &regions).remove(0); let frame = node.layout(ctx, &regions).remove(0);
output.push_frame(pos.to_point(self.main), frame); output.push_frame(pos.to_point(self.main), frame.item);
} }
pos.cross += rcol; pos.cross += rcol;
@ -385,7 +437,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(ctx, &regions); let frames = node.layout(ctx, &regions);
for (output, frame) in outputs.iter_mut().zip(frames) { for (output, frame) in outputs.iter_mut().zip(frames) {
output.push_frame(pos.to_point(self.main), frame); output.push_frame(pos.to_point(self.main), frame.item);
} }
} }
@ -404,6 +456,7 @@ impl<'a> GridLayouter<'a> {
while !self.regions.current.get(self.main).fits(length) while !self.regions.current.get(self.main).fits(length)
&& !self.regions.in_full_last() && !self.regions.in_full_last()
{ {
*self.constraints.max.get_mut(self.main) = Some(self.used.main + length);
self.finish_region(ctx); self.finish_region(ctx);
} }
@ -417,6 +470,7 @@ impl<'a> GridLayouter<'a> {
// Determine the size of the region. // Determine the size of the region.
let length = if self.fr.is_zero() { self.used.main } else { self.full }; let length = if self.fr.is_zero() { self.used.main } else { self.full };
let size = self.to_size(length); let size = self.to_size(length);
*self.constraints.min.get_mut(self.main) = Some(length);
// The frame for the region. // The frame for the region.
let mut output = Frame::new(size, size.height); let mut output = Frame::new(size, size.height);
@ -449,7 +503,8 @@ impl<'a> GridLayouter<'a> {
self.full = self.regions.current.get(self.main); self.full = self.regions.current.get(self.main);
self.used.main = Length::zero(); self.used.main = Length::zero();
self.fr = Fractional::zero(); self.fr = Fractional::zero();
self.finished.push(output); self.finished.push(output.constrain(self.constraints));
self.constraints = Constraints::new(self.regions.expand);
} }
/// Get the node in the cell in column `x` and row `y`. /// Get the node in the cell in column `x` and row `y`.

147
src/layout/incremental.rs Normal file
View File

@ -0,0 +1,147 @@
use std::{collections::HashMap, ops::Deref};
use super::*;
/// Caches layouting artifacts.
#[derive(Default, Debug, Clone)]
pub struct LayoutCache {
/// Maps from node hashes to the resulting frames and regions in which the
/// frames are valid.
pub frames: HashMap<u64, FramesEntry>,
}
impl LayoutCache {
/// Create a new, empty layout cache.
pub fn new() -> Self {
Self { frames: HashMap::new() }
}
/// Clear the cache.
pub fn clear(&mut self) {
self.frames.clear();
}
}
#[derive(Debug, Clone)]
/// Cached frames from past layouting.
pub struct FramesEntry {
/// The cached frames for a node.
pub frames: Vec<Constrained<Frame>>,
}
impl FramesEntry {
/// Checks if the cached [`Frame`] is valid for the given regions.
pub fn check(&self, mut regions: Regions) -> Option<Vec<Constrained<Frame>>> {
for (i, frame) in self.frames.iter().enumerate() {
if (i != 0 && !regions.next()) || !frame.constraints.check(&regions) {
return None;
}
}
Some(self.frames.clone())
}
}
#[derive(Debug, Copy, Clone, 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,
}
}
/// Set the appropriate base constraints for (relative) width and height
/// metrics, respectively.
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.
if size.horizontal.map_or(false, |l| l.is_relative()) {
self.base.horizontal = Some(regions.base.width);
}
if size.vertical.map_or(false, |l| l.is_relative()) {
self.base.vertical = Some(regions.base.height);
}
}
fn check(&self, regions: &Regions) -> bool {
if self.expand != regions.expand {
return false;
}
let base = regions.base.to_spec();
let current = regions.current.to_spec();
current.eq_by(&self.min, |x, y| y.map_or(true, |y| x >= &y))
&& current.eq_by(&self.max, |x, y| y.map_or(true, |y| x < &y))
&& current.eq_by(&self.exact, |x, y| y.map_or(true, |y| x == &y))
&& base.eq_by(&self.base, |x, y| y.map_or(true, |y| x == &y))
}
/// Changes all constraints by adding the argument to them if they are set.
pub fn mutate(&mut self, size: Size) {
for x in &mut [self.min, self.max, self.exact, self.base] {
if let Some(horizontal) = x.horizontal.as_mut() {
*horizontal += size.width;
}
if let Some(vertical) = x.vertical.as_mut() {
*vertical += size.height;
}
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Constrained<T> {
pub item: T,
pub constraints: Constraints,
}
impl<T> Deref for Constrained<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.item
}
}
pub trait OptionExt {
fn set_min(&mut self, other: Length);
fn set_max(&mut self, other: Length);
}
impl OptionExt for Option<Length> {
fn set_min(&mut self, other: Length) {
match self {
Some(x) => x.set_min(other),
None => *self = Some(other),
}
}
fn set_max(&mut self, other: Length) {
match self {
Some(x) => x.set_max(other),
None => *self = Some(other),
}
}
}

View File

@ -4,6 +4,7 @@ mod background;
mod fixed; mod fixed;
mod frame; mod frame;
mod grid; mod grid;
mod incremental;
mod pad; mod pad;
mod par; mod par;
mod shaping; mod shaping;
@ -13,16 +14,18 @@ pub use background::*;
pub use fixed::*; pub use fixed::*;
pub use frame::*; pub use frame::*;
pub use grid::*; pub use grid::*;
pub use incremental::*;
pub use pad::*; pub use pad::*;
pub use par::*; pub use par::*;
pub use shaping::*; pub use shaping::*;
pub use stack::*; pub use stack::*;
use std::any::Any; use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use fxhash::FxHasher64;
use crate::cache::Cache; use crate::cache::Cache;
use crate::geom::*; use crate::geom::*;
use crate::loading::Loader; use crate::loading::Loader;
@ -64,7 +67,7 @@ impl PageRun {
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, expand);
self.child.layout(ctx, &regions) self.child.layout(ctx, &regions).into_iter().map(|c| c.item).collect()
} }
} }
@ -80,26 +83,34 @@ impl AnyNode {
where where
T: Layout + Debug + Clone + PartialEq + Hash + 'static, T: Layout + Debug + Clone + PartialEq + Hash + 'static,
{ {
let hash = fxhash::hash64(&node); let mut state = FxHasher64::default();
node.type_id().hash(&mut state);
node.hash(&mut state);
let hash = state.finish();
Self { node: Box::new(node), hash } Self { node: Box::new(node), hash }
} }
} }
impl Layout for AnyNode { impl Layout for AnyNode {
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> { fn layout(
if let Some(hit) = ctx.cache.layout.frames.get(&self.hash) { &self,
if &hit.regions == regions { ctx: &mut LayoutContext,
return hit.frames.clone(); regions: &Regions,
} ) -> Vec<Constrained<Frame>> {
} ctx.cache
.layout
.frames
.get(&self.hash)
.and_then(|x| x.check(regions.clone()))
.unwrap_or_else(|| {
let frames = self.node.layout(ctx, regions); let frames = self.node.layout(ctx, regions);
ctx.cache.layout.frames.insert(self.hash, FramesEntry { ctx.cache
regions: regions.clone(), .layout
frames: frames.clone(), .frames
}); .insert(self.hash, FramesEntry { frames: frames.clone() });
frames frames
})
} }
} }
@ -160,7 +171,11 @@ where
/// Layout a node. /// Layout a node.
pub trait Layout { pub trait Layout {
/// Layout the node into the given regions. /// Layout the node into the given regions.
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame>; fn layout(
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>>;
} }
/// The context for layouting. /// The context for layouting.
@ -171,33 +186,6 @@ pub struct LayoutContext<'a> {
pub cache: &'a mut Cache, pub cache: &'a mut Cache,
} }
/// Caches layouting artifacts.
pub struct LayoutCache {
/// Maps from node hashes to the resulting frames and regions in which the
/// frames are valid.
pub frames: HashMap<u64, FramesEntry>,
}
impl LayoutCache {
/// Create a new, empty layout cache.
pub fn new() -> Self {
Self { frames: HashMap::new() }
}
/// Clear the cache.
pub fn clear(&mut self) {
self.frames.clear();
}
}
/// Cached frames from past layouting.
pub struct FramesEntry {
/// The regions in which these frames are valid.
pub regions: Regions,
/// The cached frames for a node.
pub frames: Vec<Frame>,
}
/// A sequence of regions to layout into. /// A sequence of regions to layout into.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Regions { pub struct Regions {
@ -261,10 +249,13 @@ impl Regions {
} }
/// Advance to the next region if there is any. /// Advance to the next region if there is any.
pub fn next(&mut self) { pub fn next(&mut self) -> bool {
if let Some(size) = self.backlog.pop().or(self.last) { if let Some(size) = self.backlog.pop().or(self.last) {
self.current = size; self.current = size;
self.base = size; self.base = size;
true
} else {
false
} }
} }

View File

@ -10,8 +10,12 @@ pub struct PadNode {
} }
impl Layout for PadNode { impl Layout for PadNode {
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> { fn layout(
let regions = regions.map(|size| size - self.padding.resolve(size).size()); &self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>> {
let mut regions = regions.map(|size| size - self.padding.resolve(size).size());
let mut frames = self.child.layout(ctx, &regions); let mut frames = self.child.layout(ctx, &regions);
for frame in &mut frames { for frame in &mut frames {
@ -19,12 +23,23 @@ impl Layout for PadNode {
let padding = self.padding.resolve(padded); let padding = self.padding.resolve(padded);
let origin = Point::new(padding.left, padding.top); let origin = Point::new(padding.left, padding.top);
frame.size = padded; frame.item.size = padded;
frame.baseline += origin.y; frame.item.baseline += origin.y;
for (point, _) in &mut frame.elements { for (point, _) in &mut frame.item.elements {
*point += origin; *point += origin;
} }
frame.constraints.mutate(padding.size() * -1.0);
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();
} }
frames frames

View File

@ -33,7 +33,11 @@ pub enum ParChild {
} }
impl Layout for ParNode { impl Layout for ParNode {
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> { fn layout(
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<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();
@ -145,7 +149,7 @@ impl<'a> ParLayouter<'a> {
} }
ParChild::Any(ref node, align) => { ParChild::Any(ref node, align) => {
let frame = node.layout(ctx, regions).remove(0); let frame = node.layout(ctx, regions).remove(0);
items.push(ParItem::Frame(frame, align)); items.push(ParItem::Frame(frame.item, align));
ranges.push(range); ranges.push(range);
} }
} }
@ -161,7 +165,11 @@ impl<'a> ParLayouter<'a> {
} }
/// Find first-fit line breaks and build the paragraph. /// Find first-fit line breaks and build the paragraph.
fn layout(self, ctx: &mut LayoutContext, regions: Regions) -> Vec<Frame> { fn layout(
self,
ctx: &mut LayoutContext,
regions: Regions,
) -> Vec<Constrained<Frame>> {
let mut stack = LineStack::new(self.line_spacing, regions); let mut stack = LineStack::new(self.line_spacing, regions);
// The current line attempt. // The current line attempt.
@ -182,7 +190,20 @@ impl<'a> ParLayouter<'a> {
// line cannot be broken up further. // line cannot be broken up further.
if !stack.regions.current.fits(line.size) { if !stack.regions.current.fits(line.size) {
if let Some((last_line, last_end)) = last.take() { if let Some((last_line, last_end)) = last.take() {
if !stack.regions.current.width.fits(line.size.width) {
stack.constraints.max.horizontal.set_min(line.size.width);
}
if !stack.regions.current.height.fits(line.size.height) {
stack
.constraints
.max
.vertical
.set_min(stack.size.height + line.size.height);
}
stack.push(last_line); stack.push(last_line);
stack.constraints.min.vertical = Some(stack.size.height);
start = last_end; start = last_end;
line = LineLayout::new(ctx, &self, start .. end); line = LineLayout::new(ctx, &self, start .. end);
} }
@ -192,6 +213,7 @@ impl<'a> ParLayouter<'a> {
while !stack.regions.current.height.fits(line.size.height) while !stack.regions.current.height.fits(line.size.height)
&& !stack.regions.in_full_last() && !stack.regions.in_full_last()
{ {
stack.constraints.max.vertical.set_min(line.size.height);
stack.finish_region(ctx); stack.finish_region(ctx);
} }
@ -203,20 +225,25 @@ impl<'a> ParLayouter<'a> {
start = end; start = end;
last = None; last = None;
stack.constraints.min.vertical = Some(stack.size.height);
// If there is a trailing line break at the end of the // If there is a trailing line break at the end of the
// paragraph, we want to force an empty line. // paragraph, we want to force an empty line.
if mandatory && end == self.bidi.text.len() { if mandatory && end == self.bidi.text.len() {
stack.push(LineLayout::new(ctx, &self, end .. end)); stack.push(LineLayout::new(ctx, &self, end .. end));
stack.constraints.min.vertical = Some(stack.size.height);
} }
} else { } else {
// Otherwise, the line fits both horizontally and vertically // Otherwise, the line fits both horizontally and vertically
// and we remember it. // and we remember it.
stack.constraints.min.horizontal.set_max(line.size.width);
last = Some((line, end)); last = Some((line, end));
} }
} }
if let Some((line, _)) = last { if let Some((line, _)) = last {
stack.push(line); stack.push(line);
stack.constraints.min.vertical = Some(stack.size.height);
} }
stack.finish(ctx) stack.finish(ctx)
@ -279,7 +306,8 @@ struct LineStack<'a> {
regions: Regions, regions: Regions,
size: Size, size: Size,
lines: Vec<LineLayout<'a>>, lines: Vec<LineLayout<'a>>,
finished: Vec<Frame>, finished: Vec<Constrained<Frame>>,
constraints: Constraints,
} }
impl<'a> LineStack<'a> { impl<'a> LineStack<'a> {
@ -287,6 +315,7 @@ impl<'a> LineStack<'a> {
fn new(line_spacing: Length, regions: Regions) -> Self { fn new(line_spacing: Length, regions: Regions) -> Self {
Self { Self {
line_spacing, line_spacing,
constraints: Constraints::new(regions.expand),
regions, regions,
size: Size::zero(), size: Size::zero(),
lines: vec![], lines: vec![],
@ -311,6 +340,7 @@ impl<'a> LineStack<'a> {
fn finish_region(&mut self, ctx: &LayoutContext) { fn finish_region(&mut self, ctx: &LayoutContext) {
if self.regions.expand.horizontal { if self.regions.expand.horizontal {
self.size.width = self.regions.current.width; self.size.width = self.regions.current.width;
self.constraints.exact.horizontal = Some(self.regions.current.width);
} }
let mut output = Frame::new(self.size, self.size.height); let mut output = Frame::new(self.size, self.size.height);
@ -330,13 +360,14 @@ impl<'a> LineStack<'a> {
output.push_frame(pos, frame); output.push_frame(pos, frame);
} }
self.finished.push(output.constrain(self.constraints));
self.regions.next(); self.regions.next();
self.constraints = Constraints::new(self.regions.expand);
self.size = Size::zero(); self.size = Size::zero();
self.finished.push(output);
} }
/// Finish the last region and return the built frames. /// Finish the last region and return the built frames.
fn finish(mut self, ctx: &LayoutContext) -> Vec<Frame> { fn finish(mut self, ctx: &LayoutContext) -> Vec<Constrained<Frame>> {
self.finish_region(ctx); self.finish_region(ctx);
self.finished self.finished
} }

View File

@ -28,7 +28,11 @@ pub enum StackChild {
} }
impl Layout for StackNode { impl Layout for StackNode {
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> { fn layout(
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>> {
StackLayouter::new(self, regions.clone()).layout(ctx) StackLayouter::new(self, regions.clone()).layout(ctx)
} }
} }
@ -56,11 +60,13 @@ struct StackLayouter<'a> {
used: Gen<Length>, used: Gen<Length>,
/// The alignment ruler for the current region. /// The alignment ruler for the current region.
ruler: Align, ruler: Align,
/// The constraints for the current region.
constraints: Constraints,
/// Offset, alignment and frame for all children that fit into the current /// Offset, alignment and frame for all children that fit into the current
/// region. The exact positions are not known yet. /// region. The exact positions are not known yet.
frames: Vec<(Length, Gen<Align>, Frame)>, frames: Vec<(Length, Gen<Align>, Frame)>,
/// Finished frames for previous regions. /// Finished frames for previous regions.
finished: Vec<Frame>, finished: Vec<Constrained<Frame>>,
} }
impl<'a> StackLayouter<'a> { impl<'a> StackLayouter<'a> {
@ -81,6 +87,7 @@ impl<'a> StackLayouter<'a> {
stack, stack,
main, main,
expand, expand,
constraints: Constraints::new(regions.expand),
regions, regions,
full, full,
used: Gen::zero(), used: Gen::zero(),
@ -91,13 +98,18 @@ impl<'a> StackLayouter<'a> {
} }
/// Layout all children. /// Layout all children.
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Frame> { fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Frame>> {
for child in &self.stack.children { for child in &self.stack.children {
match *child { match *child {
StackChild::Spacing(amount) => self.space(amount), StackChild::Spacing(amount) => self.space(amount),
StackChild::Any(ref node, aligns) => { StackChild::Any(ref node, aligns) => {
for frame in node.layout(ctx, &self.regions) { let nodes = node.layout(ctx, &self.regions);
self.push_frame(frame, aligns); let len = nodes.len();
for (i, frame) in nodes.into_iter().enumerate() {
if i + 1 != len {
self.constraints.exact = self.full.to_spec().map(Some);
}
self.push_frame(frame.item, aligns);
} }
} }
} }
@ -109,7 +121,8 @@ impl<'a> StackLayouter<'a> {
/// Add main-axis spacing into the current region. /// Add main-axis spacing into the current region.
fn space(&mut self, amount: Length) { fn space(&mut self, amount: Length) {
// Cap the spacing to the remaining available space. // Cap the spacing to the remaining available space. This action does
// not directly affect the constraints because of the cap.
let remaining = self.regions.current.get_mut(self.main); let remaining = self.regions.current.get_mut(self.main);
let capped = amount.min(*remaining); let capped = amount.min(*remaining);
@ -132,6 +145,7 @@ impl<'a> StackLayouter<'a> {
while !self.regions.current.get(self.main).fits(size.main) while !self.regions.current.get(self.main).fits(size.main)
&& !self.regions.in_full_last() && !self.regions.in_full_last()
{ {
self.constraints.max.get_mut(self.main).set_min(size.main);
self.finish_region(); self.finish_region();
} }
@ -156,12 +170,25 @@ 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 mut size = Size::new(
if expand.horizontal { self.full.width } else { used.width }, if expand.horizontal {
if expand.vertical { self.full.height } else { used.height }, self.constraints.exact.horizontal = Some(self.full.width);
self.full.width
} else {
self.constraints.min.horizontal = Some(used.width);
used.width
},
if expand.vertical {
self.constraints.exact.vertical = Some(self.full.height);
self.full.height
} else {
self.constraints.min.vertical = Some(used.height);
used.height
},
); );
// Make sure the stack's size satisfies the aspect ratio. // Make sure the stack's size satisfies the aspect ratio.
if let Some(aspect) = self.stack.aspect { if let Some(aspect) = self.stack.aspect {
self.constraints.exact = self.regions.current.to_spec().map(Some);
let width = size let width = size
.width .width
.max(aspect.into_inner() * size.height) .max(aspect.into_inner() * size.height)
@ -216,6 +243,7 @@ impl<'a> StackLayouter<'a> {
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;
self.finished.push(output); self.finished.push(output.constrain(self.constraints));
self.constraints = Constraints::new(self.regions.expand);
} }
} }

View File

@ -2,7 +2,7 @@ use ::image::GenericImageView;
use super::*; use super::*;
use crate::image::ImageId; use crate::image::ImageId;
use crate::layout::{AnyNode, Element, Frame, Layout, LayoutContext, Regions}; use crate::layout::{AnyNode, Constrained, Constraints, Element, Frame, Layout, LayoutContext, Regions};
/// `image`: An image. /// `image`: An image.
/// ///
@ -52,8 +52,11 @@ struct ImageNode {
} }
impl Layout for ImageNode { impl Layout for ImageNode {
fn layout(&self, _: &mut LayoutContext, regions: &Regions) -> Vec<Frame> { fn layout(&self, _: &mut LayoutContext, regions: &Regions) -> Vec<Constrained<Frame>> {
let Regions { current, base, .. } = regions; let Regions { current, base, .. } = regions;
let mut constraints = Constraints::new(regions.expand);
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));
@ -66,6 +69,8 @@ impl Layout for ImageNode {
(Some(width), None) => Size::new(width, width / pixel_ratio), (Some(width), None) => Size::new(width, width / pixel_ratio),
(None, Some(height)) => Size::new(height * pixel_ratio, height), (None, Some(height)) => Size::new(height * pixel_ratio, height),
(None, None) => { (None, None) => {
constraints.exact = current.to_spec().map(Some);
let ratio = current.width / current.height; let ratio = current.width / current.height;
if ratio < pixel_ratio && current.width.is_finite() { if ratio < pixel_ratio && current.width.is_finite() {
Size::new(current.width, current.width / pixel_ratio) Size::new(current.width, current.width / pixel_ratio)
@ -81,7 +86,7 @@ impl Layout for ImageNode {
let mut frame = Frame::new(size, size.height); let mut frame = Frame::new(size, size.height);
frame.push(Point::zero(), Element::Image(self.id, size)); frame.push(Point::zero(), Element::Image(self.id, size));
vec![frame] vec![frame.constrain(constraints)]
} }
} }