Layout improvements

This commit is contained in:
Laurenz 2021-11-25 20:51:16 +01:00
parent 304d9dd110
commit 393d74f9bb
24 changed files with 228 additions and 176 deletions

View File

@ -6,7 +6,7 @@ use std::ops::{Add, AddAssign};
use std::rc::Rc; use std::rc::Rc;
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::geom::{Align, Dir, GenAxis, Length, Linear, Paint, Sides, Size}; use crate::geom::{Align, Dir, Length, Linear, Paint, Sides, Size, SpecAxis};
use crate::layout::{Layout, PackedNode}; use crate::layout::{Layout, PackedNode};
use crate::library::{ use crate::library::{
Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode,
@ -33,7 +33,7 @@ enum TemplateNode {
/// Plain text. /// Plain text.
Text(EcoString), Text(EcoString),
/// Spacing. /// Spacing.
Spacing(GenAxis, Spacing), Spacing(SpecAxis, Spacing),
/// A decorated template. /// A decorated template.
Decorated(Decoration, Template), Decorated(Decoration, Template),
/// An inline node builder. /// An inline node builder.
@ -108,7 +108,7 @@ impl Template {
} }
/// Add spacing along an axis. /// Add spacing along an axis.
pub fn spacing(&mut self, axis: GenAxis, spacing: Spacing) { pub fn spacing(&mut self, axis: SpecAxis, spacing: Spacing) {
self.make_mut().push(TemplateNode::Spacing(axis, spacing)); self.make_mut().push(TemplateNode::Spacing(axis, spacing));
} }
@ -349,13 +349,13 @@ impl Builder {
} }
/// Push spacing into the active paragraph or flow depending on the `axis`. /// Push spacing into the active paragraph or flow depending on the `axis`.
fn spacing(&mut self, axis: GenAxis, spacing: Spacing) { fn spacing(&mut self, axis: SpecAxis, spacing: Spacing) {
match axis { match axis {
GenAxis::Block => { SpecAxis::Vertical => {
self.flow.finish_par(&self.style); self.flow.finish_par(&self.style);
self.flow.push_hard(FlowChild::Spacing(spacing)); self.flow.push_hard(FlowChild::Spacing(spacing));
} }
GenAxis::Inline => { SpecAxis::Horizontal => {
self.flow.par.push_hard(ParChild::Spacing(spacing)); self.flow.par.push_hard(ParChild::Spacing(spacing));
} }
} }

View File

@ -6,7 +6,7 @@ use std::rc::Rc;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::font::FaceId; use crate::font::FaceId;
use crate::geom::{Em, Length, Paint, Path, Point, Size, Transform}; use crate::geom::{Align, Em, Length, Paint, Path, Point, Size, Spec, Transform};
use crate::image::ImageId; use crate::image::ImageId;
/// A finished layout with elements at fixed positions. /// A finished layout with elements at fixed positions.
@ -23,9 +23,9 @@ pub struct Frame {
impl Frame { impl Frame {
/// Create a new, empty frame. /// Create a new, empty frame.
#[track_caller] #[track_caller]
pub fn new(size: Size, baseline: Length) -> Self { pub fn new(size: Size) -> Self {
assert!(size.is_finite()); assert!(size.is_finite());
Self { size, baseline, elements: vec![] } Self { size, baseline: size.h, elements: vec![] }
} }
/// Add an element at a position in the background. /// Add an element at a position in the background.
@ -55,12 +55,46 @@ impl Frame {
} }
} }
/// Move all elements in the frame by an offset. /// Resize the frame to a new size, distributing new space according to the
/// given alignments.
pub fn resize(&mut self, new: Size, aligns: Spec<Align>) {
let offset = Point::new(
aligns.x.resolve(new.w - self.size.w),
aligns.y.resolve(new.h - self.size.h),
);
self.size = new;
self.baseline += offset.y;
self.translate(offset);
}
/// Move the contents of the frame by an offset.
pub fn translate(&mut self, offset: Point) { pub fn translate(&mut self, offset: Point) {
for (point, _) in &mut self.elements { for (point, _) in &mut self.elements {
*point += offset; *point += offset;
} }
} }
/// Arbitrarily transform the contents of the frame.
pub fn transform(&mut self, transform: Transform) {
self.group(|g| g.transform = transform);
}
/// Clip the contents of a frame to its size.
pub fn clip(&mut self) {
self.group(|g| g.clips = true);
}
/// Wrap the frame's contents in a group and modify that group with `f`.
pub fn group<F>(&mut self, f: F)
where
F: FnOnce(&mut Group),
{
let mut wrapper = Frame { elements: vec![], ..*self };
let mut group = Group::new(Rc::new(std::mem::take(self)));
f(&mut group);
wrapper.push(Point::zero(), Element::Group(group));
*self = wrapper;
}
} }
impl Debug for Frame { impl Debug for Frame {
@ -116,18 +150,6 @@ impl Group {
clips: false, clips: false,
} }
} }
/// Set the group's transform.
pub fn transform(mut self, transform: Transform) -> Self {
self.transform = transform;
self
}
/// Set whether the group should be a clipping boundary.
pub fn clips(mut self, clips: bool) -> Self {
self.clips = clips;
self
}
} }
/// A run of shaped text. /// A run of shaped text.

View File

@ -1,18 +1,18 @@
use super::*; use super::*;
/// A container with an inline and a block component. /// A container with a main and cross component.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Gen<T> { pub struct Gen<T> {
/// The inline component. /// The main component.
pub inline: T, pub cross: T,
/// The block component. /// The cross component.
pub block: T, pub main: T,
} }
impl<T> Gen<T> { impl<T> Gen<T> {
/// Create a new instance from the two components. /// Create a new instance from the two components.
pub const fn new(inline: T, block: T) -> Self { pub const fn new(cross: T, main: T) -> Self {
Self { inline, block } Self { cross, main }
} }
/// Create a new instance with two equal components. /// Create a new instance with two equal components.
@ -20,7 +20,7 @@ impl<T> Gen<T> {
where where
T: Clone, T: Clone,
{ {
Self { inline: value.clone(), block: value } Self { cross: value.clone(), main: value }
} }
/// Maps the individual fields with `f`. /// Maps the individual fields with `f`.
@ -28,17 +28,14 @@ impl<T> Gen<T> {
where where
F: FnMut(T) -> U, F: FnMut(T) -> U,
{ {
Gen { Gen { cross: f(self.cross), main: f(self.main) }
inline: f(self.inline),
block: f(self.block),
}
} }
/// Convert to the specific representation, given the current block axis. /// Convert to the specific representation, given the current block axis.
pub fn to_spec(self, block: SpecAxis) -> Spec<T> { pub fn to_spec(self, main: SpecAxis) -> Spec<T> {
match block { match main {
SpecAxis::Horizontal => Spec::new(self.block, self.inline), SpecAxis::Horizontal => Spec::new(self.main, self.cross),
SpecAxis::Vertical => Spec::new(self.inline, self.block), SpecAxis::Vertical => Spec::new(self.cross, self.main),
} }
} }
} }
@ -47,19 +44,19 @@ impl Gen<Length> {
/// The zero value. /// The zero value.
pub fn zero() -> Self { pub fn zero() -> Self {
Self { Self {
inline: Length::zero(), cross: Length::zero(),
block: Length::zero(), main: Length::zero(),
} }
} }
/// Convert to a point. /// Convert to a point.
pub fn to_point(self, block: SpecAxis) -> Point { pub fn to_point(self, main: SpecAxis) -> Point {
self.to_spec(block).to_point() self.to_spec(main).to_point()
} }
/// Convert to a size. /// Convert to a size.
pub fn to_size(self, block: SpecAxis) -> Size { pub fn to_size(self, main: SpecAxis) -> Size {
self.to_spec(block).to_size() self.to_spec(main).to_size()
} }
} }
@ -67,8 +64,8 @@ impl<T> Gen<Option<T>> {
/// Unwrap the individual fields. /// Unwrap the individual fields.
pub fn unwrap_or(self, other: Gen<T>) -> Gen<T> { pub fn unwrap_or(self, other: Gen<T>) -> Gen<T> {
Gen { Gen {
inline: self.inline.unwrap_or(other.inline), cross: self.cross.unwrap_or(other.cross),
block: self.block.unwrap_or(other.block), main: self.main.unwrap_or(other.main),
} }
} }
} }
@ -78,40 +75,40 @@ impl<T> Get<GenAxis> for Gen<T> {
fn get(self, axis: GenAxis) -> T { fn get(self, axis: GenAxis) -> T {
match axis { match axis {
GenAxis::Inline => self.inline, GenAxis::Cross => self.cross,
GenAxis::Block => self.block, GenAxis::Main => self.main,
} }
} }
fn get_mut(&mut self, axis: GenAxis) -> &mut T { fn get_mut(&mut self, axis: GenAxis) -> &mut T {
match axis { match axis {
GenAxis::Inline => &mut self.inline, GenAxis::Cross => &mut self.cross,
GenAxis::Block => &mut self.block, GenAxis::Main => &mut self.main,
} }
} }
} }
impl<T: Debug> Debug for Gen<T> { impl<T: Debug> Debug for Gen<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Gen({:?}, {:?})", self.inline, self.block) write!(f, "Gen({:?}, {:?})", self.cross, self.main)
} }
} }
/// The two generic layouting axes. /// Two generic axes of a container.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum GenAxis { pub enum GenAxis {
/// The axis words and lines are set along. /// The minor axis.
Inline, Cross,
/// The axis paragraphs and pages are set along. /// The major axis.
Block, Main,
} }
impl GenAxis { impl GenAxis {
/// The other axis. /// The other axis.
pub fn other(self) -> Self { pub fn other(self) -> Self {
match self { match self {
Self::Inline => Self::Block, Self::Cross => Self::Main,
Self::Block => Self::Inline, Self::Main => Self::Cross,
} }
} }
} }

View File

@ -36,8 +36,8 @@ impl Point {
} }
/// Convert to the generic representation. /// Convert to the generic representation.
pub const fn to_gen(self, block: SpecAxis) -> Gen<Length> { pub const fn to_gen(self, main: SpecAxis) -> Gen<Length> {
match block { match main {
SpecAxis::Horizontal => Gen::new(self.y, self.x), SpecAxis::Horizontal => Gen::new(self.y, self.x),
SpecAxis::Vertical => Gen::new(self.x, self.y), SpecAxis::Vertical => Gen::new(self.x, self.y),
} }

View File

@ -51,8 +51,8 @@ impl Size {
} }
/// Convert to the generic representation. /// Convert to the generic representation.
pub const fn to_gen(self, block: SpecAxis) -> Gen<Length> { pub const fn to_gen(self, main: SpecAxis) -> Gen<Length> {
match block { match main {
SpecAxis::Horizontal => Gen::new(self.h, self.w), SpecAxis::Horizontal => Gen::new(self.h, self.w),
SpecAxis::Vertical => Gen::new(self.w, self.h), SpecAxis::Vertical => Gen::new(self.w, self.h),
} }

View File

@ -57,8 +57,8 @@ impl<T> Spec<T> {
} }
/// Convert to the generic representation. /// Convert to the generic representation.
pub fn to_gen(self, block: SpecAxis) -> Gen<T> { pub fn to_gen(self, main: SpecAxis) -> Gen<T> {
match block { match main {
SpecAxis::Horizontal => Gen::new(self.y, self.x), SpecAxis::Horizontal => Gen::new(self.y, self.x),
SpecAxis::Vertical => Gen::new(self.x, self.y), SpecAxis::Vertical => Gen::new(self.x, self.y),
} }

View File

@ -1,5 +1,6 @@
use std::rc::Rc; use std::rc::Rc;
use super::Regions;
use crate::frame::Frame; use crate::frame::Frame;
use crate::geom::{Length, Linear, Size, Spec}; use crate::geom::{Length, Linear, Size, Spec};
@ -59,6 +60,17 @@ impl Constraints {
} }
} }
/// Create tight constraints for a region.
pub fn tight(regions: &Regions) -> Self {
Self {
min: Spec::default(),
max: Spec::default(),
exact: regions.current.to_spec().map(Some),
base: regions.base.to_spec().map(Some),
expand: regions.expand,
}
}
/// Check whether the constraints are fullfilled in a region with the given /// Check whether the constraints are fullfilled in a region with the given
/// properties. /// properties.
pub fn check(&self, current: Size, base: Size, expand: Spec<bool>) -> bool { pub fn check(&self, current: Size, base: Size, expand: Spec<bool>) -> bool {

View File

@ -1,4 +1,4 @@
use crate::geom::{Size, Spec}; use crate::geom::{Length, Size, Spec};
/// A sequence of regions to layout into. /// A sequence of regions to layout into.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -13,9 +13,6 @@ pub struct Regions {
pub last: Option<Size>, pub last: Option<Size>,
/// Whether nodes should expand to fill the regions instead of shrinking to /// Whether nodes should expand to fill the regions instead of shrinking to
/// fit the content. /// fit the content.
///
/// This property is only handled by nodes that have the ability to control
/// their own size.
pub expand: Spec<bool>, pub expand: Spec<bool>,
} }
@ -52,13 +49,26 @@ impl Regions {
regions regions
} }
/// Whether `current` is a fully sized (untouched) copy of the last region. /// Whether the current region is full and a region break is called for.
pub fn is_full(&self) -> bool {
Length::zero().fits(self.current.h) && !self.in_last()
}
/// Whether `current` is the last usable region.
/// ///
/// If this is true, calling `next()` will have no effect. /// If this is true, calling `next()` will have no effect.
pub fn in_full_last(&self) -> bool { pub fn in_last(&self) -> bool {
self.backlog.len() == 0 && self.last.map_or(true, |size| self.current == size) self.backlog.len() == 0 && self.last.map_or(true, |size| self.current == size)
} }
/// Advance to the next region if there is any.
pub fn next(&mut self) {
if let Some(size) = self.backlog.next().or(self.last) {
self.current = size;
self.base = size;
}
}
/// An iterator that returns pairs of `(current, base)` that are equivalent /// An iterator that returns pairs of `(current, base)` that are equivalent
/// to what would be produced by calling [`next()`](Self::next) repeatedly /// to what would be produced by calling [`next()`](Self::next) repeatedly
/// until all regions are exhausted. /// until all regions are exhausted.
@ -69,14 +79,6 @@ impl Regions {
first.chain(backlog.chain(last).map(|&s| (s, s))) 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.next().or(self.last) {
self.current = size;
self.base = size;
}
}
/// Mutate all contained sizes in place. /// Mutate all contained sizes in place.
pub fn mutate<F>(&mut self, mut f: F) pub fn mutate<F>(&mut self, mut f: F)
where where

View File

@ -34,28 +34,29 @@ impl Layout for AlignNode {
pod.expand.x &= self.aligns.x.is_none(); pod.expand.x &= self.aligns.x.is_none();
pod.expand.y &= self.aligns.y.is_none(); pod.expand.y &= self.aligns.y.is_none();
// Layout the child.
let mut frames = self.child.layout(ctx, &pod); let mut frames = self.child.layout(ctx, &pod);
for (Constrained { item: frame, cts }, (current, _)) in
for (Constrained { item: frame, cts }, (current, base)) in
frames.iter_mut().zip(regions.iter()) frames.iter_mut().zip(regions.iter())
{ {
let canvas = Size::new( // The possibly larger size in which we align the frame.
let new = Size::new(
if regions.expand.x { current.w } else { frame.size.w }, if regions.expand.x { current.w } else { frame.size.w },
if regions.expand.y { current.h } else { frame.size.h }, if regions.expand.y { current.h } else { frame.size.h },
); );
let aligns = self.aligns.unwrap_or(Spec::new(Align::Left, Align::Top)); let aligns = self.aligns.unwrap_or(Spec::new(Align::Left, Align::Top));
let offset = Point::new( Rc::make_mut(frame).resize(new, aligns);
aligns.x.resolve(canvas.w - frame.size.w),
aligns.y.resolve(canvas.h - frame.size.h),
);
let frame = Rc::make_mut(frame);
frame.size = canvas;
frame.baseline += offset.y;
frame.translate(offset);
// Set constraints.
cts.expand = regions.expand; cts.expand = regions.expand;
cts.exact = current.to_spec().map(Some); cts.base.x.and_set(Some(base.w));
cts.base.y.and_set(Some(base.h));
cts.exact = Spec::new(
regions.expand.x.then(|| current.w),
regions.expand.y.then(|| current.h),
);
} }
frames frames

View File

@ -52,7 +52,7 @@ impl Layout for FlowNode {
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
regions: &Regions, regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> { ) -> Vec<Constrained<Rc<Frame>>> {
FlowLayouter::new(self, regions.clone()).layout(ctx) FlowLayouter::new(self, regions).layout(ctx)
} }
} }
@ -80,7 +80,7 @@ struct FlowLayouter<'a> {
children: &'a [FlowChild], children: &'a [FlowChild],
/// 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 region to layout into. /// The regions to layout children into.
regions: Regions, regions: Regions,
/// The full size of `regions.current` that was available before we started /// The full size of `regions.current` that was available before we started
/// subtracting. /// subtracting.
@ -109,15 +109,18 @@ enum FlowItem {
impl<'a> FlowLayouter<'a> { impl<'a> FlowLayouter<'a> {
/// Create a new flow layouter. /// Create a new flow layouter.
fn new(flow: &'a FlowNode, mut regions: Regions) -> Self { fn new(flow: &'a FlowNode, regions: &Regions) -> Self {
// Disable vertical expansion for children.
let expand = regions.expand; let expand = regions.expand;
let full = regions.current;
// Disable vertical expansion for children.
let mut regions = regions.clone();
regions.expand.y = false; regions.expand.y = false;
Self { Self {
children: &flow.children, children: &flow.children,
expand, expand,
full: regions.current, full,
regions, regions,
used: Size::zero(), used: Size::zero(),
fr: Fractional::zero(), fr: Fractional::zero(),
@ -129,6 +132,10 @@ impl<'a> FlowLayouter<'a> {
/// Layout all children. /// Layout all children.
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> { fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
for child in self.children { for child in self.children {
if self.regions.is_full() {
self.finish_region();
}
match *child { match *child {
FlowChild::Spacing(Spacing::Linear(v)) => { FlowChild::Spacing(Spacing::Linear(v)) => {
self.layout_absolute(v); self.layout_absolute(v);
@ -215,7 +222,7 @@ impl<'a> FlowLayouter<'a> {
size.h = self.full.h; size.h = self.full.h;
} }
let mut output = Frame::new(size, size.h); let mut output = Frame::new(size);
let mut before = Length::zero(); let mut before = Length::zero();
let mut ruler = Align::Top; let mut ruler = Align::Top;
let mut first = true; let mut first = true;

View File

@ -99,11 +99,11 @@ struct GridLayouter<'a> {
cols: Vec<TrackSizing>, cols: Vec<TrackSizing>,
/// The row tracks including gutter tracks. /// The row tracks including gutter tracks.
rows: Vec<TrackSizing>, rows: Vec<TrackSizing>,
/// The regions to layout into. /// The regions to layout children into.
regions: Regions, regions: Regions,
/// Resolved column sizes. /// Resolved column sizes.
rcols: Vec<Length>, rcols: Vec<Length>,
/// The full block size 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
/// determined once after columns are resolved and not touched again. /// determined once after columns are resolved and not touched again.
@ -353,6 +353,12 @@ impl<'a> GridLayouter<'a> {
/// Layout the grid row-by-row. /// Layout the grid row-by-row.
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> { fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
for y in 0 .. self.rows.len() { for y in 0 .. self.rows.len() {
// Skip to next region if current one is full, but only for content
// rows, not for gutter rows.
if y % 2 == 0 && self.regions.is_full() {
self.finish_region(ctx);
}
match self.rows[y] { match self.rows[y] {
TrackSizing::Auto => self.layout_auto_row(ctx, y), TrackSizing::Auto => self.layout_auto_row(ctx, y),
TrackSizing::Linear(v) => self.layout_linear_row(ctx, v, y), TrackSizing::Linear(v) => self.layout_linear_row(ctx, v, y),
@ -368,8 +374,8 @@ impl<'a> GridLayouter<'a> {
self.finished self.finished
} }
/// Layout a row with automatic size along the block axis. Such a row may /// Layout a row with automatic height. Such a row may break across multiple
/// break across multiple regions. /// regions.
fn layout_auto_row(&mut self, ctx: &mut LayoutContext, y: usize) { fn layout_auto_row(&mut self, ctx: &mut LayoutContext, y: usize) {
let mut resolved: Vec<Length> = vec![]; let mut resolved: Vec<Length> = vec![];
@ -388,10 +394,14 @@ impl<'a> GridLayouter<'a> {
let mut sizes = let mut sizes =
node.layout(ctx, &pod).into_iter().map(|frame| frame.item.size.h); node.layout(ctx, &pod).into_iter().map(|frame| frame.item.size.h);
// For each region, we want to know the maximum height any
// column requires.
for (target, size) in resolved.iter_mut().zip(&mut sizes) { for (target, size) in resolved.iter_mut().zip(&mut sizes) {
target.set_max(size); target.set_max(size);
} }
// New heights are maximal by virtue of being new. Note that
// this extend only uses the rest of the sizes iterator.
resolved.extend(sizes); resolved.extend(sizes);
} }
} }
@ -431,16 +441,16 @@ impl<'a> GridLayouter<'a> {
} }
} }
/// Layout a row with linear sizing along the block axis. Such a row cannot /// Layout a row with linear height. Such a row cannot break across multiple
/// break across multiple regions, but it may force a region break. /// regions, but it may force a region break.
fn layout_linear_row(&mut self, ctx: &mut LayoutContext, v: Linear, y: usize) { fn layout_linear_row(&mut self, ctx: &mut LayoutContext, v: Linear, y: usize) {
let resolved = v.resolve(self.regions.base.h); let resolved = v.resolve(self.regions.base.h);
let frame = self.layout_single_row(ctx, resolved, y); let frame = self.layout_single_row(ctx, resolved, y);
// Skip to fitting region. // Skip to fitting region.
let length = frame.size.h; let height = frame.size.h;
while !self.regions.current.h.fits(length) && !self.regions.in_full_last() { while !self.regions.current.h.fits(height) && !self.regions.in_last() {
self.cts.max.y = Some(self.used.h + length); self.cts.max.y = Some(self.used.h + height);
self.finish_region(ctx); self.finish_region(ctx);
// 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.
@ -452,14 +462,14 @@ impl<'a> GridLayouter<'a> {
self.push_row(frame); self.push_row(frame);
} }
/// Layout a row with a fixed size along the block axis and return its frame. /// Layout a row with fixed height and return its frame.
fn layout_single_row( fn layout_single_row(
&self, &self,
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
height: Length, height: Length,
y: usize, y: usize,
) -> Frame { ) -> Frame {
let mut output = Frame::new(Size::new(self.used.w, height), height); let mut output = Frame::new(Size::new(self.used.w, height));
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() {
@ -496,7 +506,7 @@ impl<'a> GridLayouter<'a> {
// Prepare frames. // Prepare frames.
let mut outputs: Vec<_> = heights let mut outputs: Vec<_> = heights
.iter() .iter()
.map(|&h| Frame::new(Size::new(self.used.w, h), h)) .map(|&h| Frame::new(Size::new(self.used.w, h)))
.collect(); .collect();
// Prepare regions. // Prepare regions.
@ -553,7 +563,7 @@ impl<'a> GridLayouter<'a> {
} }
// The frame for the region. // The frame for the region.
let mut output = Frame::new(size, size.h); let mut output = Frame::new(size);
let mut pos = Point::zero(); let mut pos = Point::zero();
// Place finished rows and layout fractional rows. // Place finished rows and layout fractional rows.

View File

@ -43,12 +43,12 @@ impl Layout for ImageNode {
let &Regions { current, expand, .. } = regions; let &Regions { current, expand, .. } = regions;
let img = ctx.images.get(self.id); let img = ctx.images.get(self.id);
let pixel_w = img.width() as f64; let pxw = img.width() as f64;
let pixel_h = img.height() as f64; let pxh = img.height() as f64;
let region_ratio = current.w / current.h; let pixel_ratio = pxw / pxh;
let pixel_ratio = pixel_w / pixel_h; let current_ratio = current.w / current.h;
let wide = region_ratio < pixel_ratio; let wide = pixel_ratio > current_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 canvas = if expand.x && expand.y { let canvas = if expand.x && expand.y {
@ -58,7 +58,7 @@ impl Layout for ImageNode {
} else if current.h.is_finite() { } else if current.h.is_finite() {
Size::new(current.w.min(current.h * pixel_ratio), current.h) Size::new(current.w.min(current.h * pixel_ratio), current.h)
} else { } else {
Size::new(Length::pt(pixel_w), Length::pt(pixel_h)) Size::new(Length::pt(pxw), Length::pt(pxh))
}; };
// The actual size of the fitted image. // The actual size of the fitted image.
@ -73,26 +73,19 @@ impl Layout for ImageNode {
ImageFit::Stretch => canvas, ImageFit::Stretch => canvas,
}; };
// Position the image so that it is centered in the canvas. // First, place the image in a frame of exactly its size and then resize
let mut frame = Frame::new(canvas, canvas.h); // the frame to the canvas size, center aligning the image in the
frame.push( // process.
(canvas - size).to_point() / 2.0, let mut frame = Frame::new(size);
Element::Image(self.id, size), frame.push(Point::zero(), Element::Image(self.id, size));
); frame.resize(canvas, Spec::new(Align::Center, Align::Horizon));
// Create a clipping group if the image mode is `cover`. // Create a clipping group if the fit mode is "cover".
if self.fit == ImageFit::Cover { if self.fit == ImageFit::Cover {
let mut wrapper = Frame::new(canvas, canvas.h); frame.clip();
wrapper.push(
Point::zero(),
Element::Group(Group::new(Rc::new(frame)).clips(true)),
);
frame = wrapper;
} }
let mut cts = Constraints::new(regions.expand); vec![frame.constrain(Constraints::tight(regions))]
cts.exact = regions.current.to_spec().map(Some);
vec![frame.constrain(cts)]
} }
} }

View File

@ -241,12 +241,17 @@ impl<'a> ParLayouter<'a> {
starts.push((range.start, deco)); starts.push((range.start, deco));
} }
ParChild::Undecorate => { ParChild::Undecorate => {
let (start, deco) = starts.pop().unwrap(); if let Some((start, deco)) = starts.pop() {
decos.push((start .. range.end, deco)); decos.push((start .. range.end, deco));
}
} }
} }
} }
for (start, deco) in starts {
decos.push((start .. bidi.text.len(), deco));
}
Self { Self {
align: par.align, align: par.align,
leading: par.leading, leading: par.leading,
@ -307,7 +312,7 @@ impl<'a> ParLayouter<'a> {
// If the line does not fit vertically, we start a new region. // If the line does not fit vertically, we start a new region.
while !stack.regions.current.h.fits(line.size.h) { while !stack.regions.current.h.fits(line.size.h) {
if stack.regions.in_full_last() { if stack.regions.in_last() {
stack.overflowing = true; stack.overflowing = true;
break; break;
} }
@ -487,8 +492,9 @@ impl<'a> LineLayout<'a> {
let size = Size::new(self.size.w.max(width), self.size.h); let size = Size::new(self.size.w.max(width), self.size.h);
let remaining = size.w - self.size.w; let remaining = size.w - self.size.w;
let mut output = Frame::new(size, self.baseline); let mut output = Frame::new(size);
let mut offset = Length::zero(); let mut offset = Length::zero();
output.baseline = self.baseline;
for (range, item) in self.reordered() { for (range, item) in self.reordered() {
let mut position = |mut frame: Frame| { let mut position = |mut frame: Frame| {
@ -621,7 +627,7 @@ impl<'a> LineStack<'a> {
self.cts.exact = self.full.to_spec().map(Some); self.cts.exact = self.full.to_spec().map(Some);
} }
let mut output = Frame::new(self.size, self.size.h); let mut output = Frame::new(self.size);
let mut offset = Length::zero(); let mut offset = Length::zero();
let mut first = true; let mut first = true;

View File

@ -165,12 +165,16 @@ impl Layout for ShapeNode {
if regions.expand.y { regions.current.h } else { default }, if regions.expand.y { regions.current.h } else { default },
); );
// Don't overflow the region.
size.w = size.w.min(regions.current.w);
size.h = size.h.min(regions.current.h);
if matches!(self.kind, ShapeKind::Square | ShapeKind::Circle) { if matches!(self.kind, ShapeKind::Square | ShapeKind::Circle) {
size.w = size.w.min(size.h); size.w = size.w.min(size.h);
size.h = size.w; size.h = size.w;
} }
Frame::new(size, size.h) Frame::new(size)
}; };
// Add fill and/or stroke. // Add fill and/or stroke.
@ -197,9 +201,6 @@ impl Layout for ShapeNode {
); );
// Return tight constraints for now. // Return tight constraints for now.
let mut cts = Constraints::new(regions.expand); vec![frame.constrain(Constraints::tight(regions))]
cts.exact = regions.current.to_spec().map(Some);
cts.base = regions.base.to_spec().map(Some);
vec![frame.constrain(cts)]
} }
} }

View File

@ -35,10 +35,6 @@ impl Layout for SizedNode {
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
regions: &Regions, regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> { ) -> Vec<Constrained<Rc<Frame>>> {
// Resolve width and height relative to the region's base.
let width = self.sizing.x.map(|w| w.resolve(regions.base.w));
let height = self.sizing.y.map(|h| h.resolve(regions.base.h));
// Generate constraints. // Generate constraints.
let mut cts = Constraints::new(regions.expand); let mut cts = Constraints::new(regions.expand);
cts.set_base_if_linear(regions.base, self.sizing); cts.set_base_if_linear(regions.base, self.sizing);
@ -56,6 +52,10 @@ impl Layout for SizedNode {
cts.base.y = Some(regions.base.h); cts.base.y = Some(regions.base.h);
} }
// Resolve width and height relative to the region's base.
let width = self.sizing.x.map(|w| w.resolve(regions.base.w));
let height = self.sizing.y.map(|h| h.resolve(regions.base.h));
// 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 = {
let size = Size::new( let size = Size::new(

View File

@ -3,14 +3,14 @@ use super::prelude::*;
/// `h`: Horizontal spacing. /// `h`: Horizontal spacing.
pub fn h(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub fn h(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let mut template = Template::new(); let mut template = Template::new();
template.spacing(GenAxis::Inline, args.expect("spacing")?); template.spacing(SpecAxis::Horizontal, args.expect("spacing")?);
Ok(Value::Template(template)) Ok(Value::Template(template))
} }
/// `v`: Vertical spacing. /// `v`: Vertical spacing.
pub fn v(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub fn v(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let mut template = Template::new(); let mut template = Template::new();
template.spacing(GenAxis::Block, args.expect("spacing")?); template.spacing(SpecAxis::Vertical, args.expect("spacing")?);
Ok(Value::Template(template)) Ok(Value::Template(template))
} }

View File

@ -66,7 +66,7 @@ impl Layout for StackNode {
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
regions: &Regions, regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> { ) -> Vec<Constrained<Rc<Frame>>> {
StackLayouter::new(self, regions.clone()).layout(ctx) StackLayouter::new(self, regions).layout(ctx)
} }
} }
@ -96,7 +96,7 @@ struct StackLayouter<'a> {
axis: SpecAxis, axis: SpecAxis,
/// Whether the stack should expand to fill the region. /// Whether the stack should expand to fill the region.
expand: Spec<bool>, expand: Spec<bool>,
/// The region to layout into. /// The regions to layout children into.
regions: Regions, regions: Regions,
/// The full size of `regions.current` that was available before we started /// The full size of `regions.current` that was available before we started
/// subtracting. /// subtracting.
@ -123,17 +123,21 @@ enum StackItem {
impl<'a> StackLayouter<'a> { impl<'a> StackLayouter<'a> {
/// Create a new stack layouter. /// Create a new stack layouter.
fn new(stack: &'a StackNode, mut regions: Regions) -> Self { fn new(stack: &'a StackNode, regions: &Regions) -> Self {
// Disable expansion along the block axis for children.
let axis = stack.dir.axis(); let axis = stack.dir.axis();
let expand = regions.expand; let expand = regions.expand;
let full = regions.current;
// Disable expansion along the block axis for children.
let mut regions = regions.clone();
regions.expand.set(axis, false); regions.expand.set(axis, false);
Self { Self {
stack, stack,
axis, axis,
expand, expand,
full: regions.current, full,
regions, regions,
used: Gen::zero(), used: Gen::zero(),
fr: Fractional::zero(), fr: Fractional::zero(),
@ -145,6 +149,10 @@ impl<'a> StackLayouter<'a> {
/// Layout all children. /// Layout all children.
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> { fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
for child in &self.stack.children { for child in &self.stack.children {
if self.regions.is_full() {
self.finish_region();
}
match *child { match *child {
StackChild::Spacing(Spacing::Linear(v)) => { StackChild::Spacing(Spacing::Linear(v)) => {
self.layout_absolute(v); self.layout_absolute(v);
@ -167,10 +175,10 @@ impl<'a> StackLayouter<'a> {
fn layout_absolute(&mut self, amount: Linear) { fn layout_absolute(&mut self, amount: Linear) {
// Resolve the linear, limiting it to the remaining available space. // Resolve the linear, limiting it to the remaining available space.
let remaining = self.regions.current.get_mut(self.axis); let remaining = self.regions.current.get_mut(self.axis);
let resolved = amount.resolve(self.full.get(self.axis)); let resolved = amount.resolve(self.regions.base.get(self.axis));
let limited = resolved.min(*remaining); let limited = resolved.min(*remaining);
*remaining -= limited; *remaining -= limited;
self.used.block += limited; self.used.main += limited;
self.items.push(StackItem::Absolute(resolved)); self.items.push(StackItem::Absolute(resolved));
} }
@ -187,9 +195,9 @@ impl<'a> StackLayouter<'a> {
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.item.size.to_gen(self.axis);
self.used.block += size.block; self.used.main += size.main;
self.used.inline.set_max(size.inline); self.used.cross.set_max(size.cross);
*self.regions.current.get_mut(self.axis) -= size.block; *self.regions.current.get_mut(self.axis) -= size.main;
self.items.push(StackItem::Frame(frame.item, align)); self.items.push(StackItem::Frame(frame.item, align));
if i + 1 < len { if i + 1 < len {
@ -210,13 +218,13 @@ impl<'a> StackLayouter<'a> {
// Expand fully if there are fr spacings. // Expand fully if there are fr spacings.
let full = self.full.get(self.axis); let full = self.full.get(self.axis);
let remaining = full - self.used.block; let remaining = full - self.used.main;
if self.fr.get() > 0.0 && full.is_finite() { if self.fr.get() > 0.0 && full.is_finite() {
self.used.block = full; self.used.main = full;
size.set(self.axis, full); size.set(self.axis, full);
} }
let mut output = Frame::new(size, size.h); let mut output = Frame::new(size);
let mut before = Length::zero(); let mut before = Length::zero();
let mut ruler: Align = self.stack.dir.start().into(); let mut ruler: Align = self.stack.dir.start().into();
@ -239,11 +247,11 @@ impl<'a> StackLayouter<'a> {
// Align along the block axis. // Align along the block axis.
let parent = size.get(self.axis); let parent = size.get(self.axis);
let child = frame.size.get(self.axis); let child = frame.size.get(self.axis);
let block = ruler.resolve(parent - self.used.block) let block = ruler.resolve(parent - self.used.main)
+ if self.stack.dir.is_positive() { + if self.stack.dir.is_positive() {
before before
} else { } else {
self.used.block - child - before self.used.main - child - before
}; };
let pos = Gen::new(Length::zero(), block).to_point(self.axis); let pos = Gen::new(Length::zero(), block).to_point(self.axis);

View File

@ -305,8 +305,9 @@ pub struct ShapedGlyph {
impl<'a> ShapedText<'a> { impl<'a> ShapedText<'a> {
/// Build the shaped text's frame. /// Build the shaped text's frame.
pub fn build(&self) -> Frame { pub fn build(&self) -> Frame {
let mut frame = Frame::new(self.size, self.baseline); let mut frame = Frame::new(self.size);
let mut offset = Length::zero(); let mut offset = Length::zero();
frame.baseline = self.baseline;
for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) { for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) {
let pos = Point::new(offset, self.baseline); let pos = Point::new(offset, self.baseline);

View File

@ -62,13 +62,7 @@ impl Layout for TransformNode {
let transform = Transform::translation(x, y) let transform = Transform::translation(x, y)
.pre_concat(self.transform) .pre_concat(self.transform)
.pre_concat(Transform::translation(-x, -y)); .pre_concat(Transform::translation(-x, -y));
Rc::make_mut(frame).transform(transform);
let mut wrapper = Frame::new(frame.size, frame.baseline);
wrapper.push(
Point::zero(),
Element::Group(Group::new(std::mem::take(frame)).transform(transform)),
);
*frame = Rc::new(wrapper);
} }
frames frames

View File

@ -11,7 +11,7 @@
// Syntax sugar for function definitions. // Syntax sugar for function definitions.
#let fill = conifer #let fill = conifer
#let rect(body) = rect(width: 2cm, fill: fill, pad(5pt, body)) #let rect(body) = rect(width: 2cm, fill: fill, padding: 5pt, body)
#rect[Hi!] #rect[Hi!]
--- ---

View File

@ -7,6 +7,6 @@
#let d = 3 #let d = 3
#let value = [hi] #let value = [hi]
#let item(a, b) = a + b #let item(a, b) = a + b
#let fn(body) = rect(fill: conifer, pad(5pt, body)) #let fn = rect with (fill: conifer, padding: 5pt)
Some _includable_ text. Some _includable_ text.

View File

@ -16,7 +16,7 @@ Auto-sized circle. \
Center-aligned rect in auto-sized circle. Center-aligned rect in auto-sized circle.
#circle(fill: forest, stroke: conifer, #circle(fill: forest, stroke: conifer,
align(center + horizon, align(center + horizon,
rect(fill: conifer, pad(5pt)[But, soft!]) rect(fill: conifer, padding: 5pt)[But, soft!]
) )
) )

View File

@ -19,7 +19,7 @@
)) ))
// Fixed width, text height. // Fixed width, text height.
#rect(width: 2cm, fill: rgb("9650d6"), pad(5pt)[Fixed and padded]) #rect(width: 2cm, fill: rgb("9650d6"), padding: 5pt)[Fixed and padded]
// Page width, fixed height. // Page width, fixed height.
#rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft] #rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft]

View File

@ -20,8 +20,6 @@
cell(100%, rgb("00ff00")), cell(100%, rgb("00ff00")),
) )
#grid()
--- ---
#grid( #grid(
columns: (auto, auto, 40%), columns: (auto, auto, 40%),