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 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::library::{
Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode,
@ -33,7 +33,7 @@ enum TemplateNode {
/// Plain text.
Text(EcoString),
/// Spacing.
Spacing(GenAxis, Spacing),
Spacing(SpecAxis, Spacing),
/// A decorated template.
Decorated(Decoration, Template),
/// An inline node builder.
@ -108,7 +108,7 @@ impl Template {
}
/// 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));
}
@ -349,13 +349,13 @@ impl Builder {
}
/// 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 {
GenAxis::Block => {
SpecAxis::Vertical => {
self.flow.finish_par(&self.style);
self.flow.push_hard(FlowChild::Spacing(spacing));
}
GenAxis::Inline => {
SpecAxis::Horizontal => {
self.flow.par.push_hard(ParChild::Spacing(spacing));
}
}

View File

@ -6,7 +6,7 @@ use std::rc::Rc;
use serde::{Deserialize, Serialize};
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;
/// A finished layout with elements at fixed positions.
@ -23,9 +23,9 @@ pub struct Frame {
impl Frame {
/// Create a new, empty frame.
#[track_caller]
pub fn new(size: Size, baseline: Length) -> Self {
pub fn new(size: Size) -> Self {
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.
@ -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) {
for (point, _) in &mut self.elements {
*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 {
@ -116,18 +150,6 @@ impl Group {
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.

View File

@ -1,18 +1,18 @@
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)]
pub struct Gen<T> {
/// The inline component.
pub inline: T,
/// The block component.
pub block: T,
/// The main component.
pub cross: T,
/// The cross component.
pub main: T,
}
impl<T> Gen<T> {
/// Create a new instance from the two components.
pub const fn new(inline: T, block: T) -> Self {
Self { inline, block }
pub const fn new(cross: T, main: T) -> Self {
Self { cross, main }
}
/// Create a new instance with two equal components.
@ -20,7 +20,7 @@ impl<T> Gen<T> {
where
T: Clone,
{
Self { inline: value.clone(), block: value }
Self { cross: value.clone(), main: value }
}
/// Maps the individual fields with `f`.
@ -28,17 +28,14 @@ impl<T> Gen<T> {
where
F: FnMut(T) -> U,
{
Gen {
inline: f(self.inline),
block: f(self.block),
}
Gen { cross: f(self.cross), main: f(self.main) }
}
/// Convert to the specific representation, given the current block axis.
pub fn to_spec(self, block: SpecAxis) -> Spec<T> {
match block {
SpecAxis::Horizontal => Spec::new(self.block, self.inline),
SpecAxis::Vertical => Spec::new(self.inline, self.block),
pub fn to_spec(self, main: SpecAxis) -> Spec<T> {
match main {
SpecAxis::Horizontal => Spec::new(self.main, self.cross),
SpecAxis::Vertical => Spec::new(self.cross, self.main),
}
}
}
@ -47,19 +44,19 @@ impl Gen<Length> {
/// The zero value.
pub fn zero() -> Self {
Self {
inline: Length::zero(),
block: Length::zero(),
cross: Length::zero(),
main: Length::zero(),
}
}
/// Convert to a point.
pub fn to_point(self, block: SpecAxis) -> Point {
self.to_spec(block).to_point()
pub fn to_point(self, main: SpecAxis) -> Point {
self.to_spec(main).to_point()
}
/// Convert to a size.
pub fn to_size(self, block: SpecAxis) -> Size {
self.to_spec(block).to_size()
pub fn to_size(self, main: SpecAxis) -> Size {
self.to_spec(main).to_size()
}
}
@ -67,8 +64,8 @@ impl<T> Gen<Option<T>> {
/// Unwrap the individual fields.
pub fn unwrap_or(self, other: Gen<T>) -> Gen<T> {
Gen {
inline: self.inline.unwrap_or(other.inline),
block: self.block.unwrap_or(other.block),
cross: self.cross.unwrap_or(other.cross),
main: self.main.unwrap_or(other.main),
}
}
}
@ -78,40 +75,40 @@ impl<T> Get<GenAxis> for Gen<T> {
fn get(self, axis: GenAxis) -> T {
match axis {
GenAxis::Inline => self.inline,
GenAxis::Block => self.block,
GenAxis::Cross => self.cross,
GenAxis::Main => self.main,
}
}
fn get_mut(&mut self, axis: GenAxis) -> &mut T {
match axis {
GenAxis::Inline => &mut self.inline,
GenAxis::Block => &mut self.block,
GenAxis::Cross => &mut self.cross,
GenAxis::Main => &mut self.main,
}
}
}
impl<T: Debug> Debug for Gen<T> {
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)]
pub enum GenAxis {
/// The axis words and lines are set along.
Inline,
/// The axis paragraphs and pages are set along.
Block,
/// The minor axis.
Cross,
/// The major axis.
Main,
}
impl GenAxis {
/// The other axis.
pub fn other(self) -> Self {
match self {
Self::Inline => Self::Block,
Self::Block => Self::Inline,
Self::Cross => Self::Main,
Self::Main => Self::Cross,
}
}
}

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
use std::rc::Rc;
use super::Regions;
use crate::frame::Frame;
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
/// properties.
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.
#[derive(Debug, Clone)]
@ -13,9 +13,6 @@ pub struct Regions {
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>,
}
@ -52,13 +49,26 @@ impl 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.
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)
}
/// 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
/// to what would be produced by calling [`next()`](Self::next) repeatedly
/// until all regions are exhausted.
@ -69,14 +79,6 @@ impl Regions {
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.
pub fn mutate<F>(&mut self, mut f: F)
where

View File

@ -34,28 +34,29 @@ impl Layout for AlignNode {
pod.expand.x &= self.aligns.x.is_none();
pod.expand.y &= self.aligns.y.is_none();
// Layout the child.
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())
{
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.y { current.h } else { frame.size.h },
);
let aligns = self.aligns.unwrap_or(Spec::new(Align::Left, Align::Top));
let offset = Point::new(
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);
Rc::make_mut(frame).resize(new, aligns);
// Set constraints.
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

View File

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

View File

@ -99,11 +99,11 @@ struct GridLayouter<'a> {
cols: Vec<TrackSizing>,
/// The row tracks including gutter tracks.
rows: Vec<TrackSizing>,
/// The regions to layout into.
/// The regions to layout children into.
regions: Regions,
/// Resolved column sizes.
rcols: Vec<Length>,
/// The full block size of the current region.
/// The full height of the current region.
full: Length,
/// The used-up size of the current region. The horizontal size is
/// determined once after columns are resolved and not touched again.
@ -353,6 +353,12 @@ impl<'a> GridLayouter<'a> {
/// Layout the grid row-by-row.
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
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] {
TrackSizing::Auto => self.layout_auto_row(ctx, y),
TrackSizing::Linear(v) => self.layout_linear_row(ctx, v, y),
@ -368,8 +374,8 @@ impl<'a> GridLayouter<'a> {
self.finished
}
/// Layout a row with automatic size along the block axis. Such a row may
/// break across multiple regions.
/// Layout a row with automatic height. Such a row may break across multiple
/// regions.
fn layout_auto_row(&mut self, ctx: &mut LayoutContext, y: usize) {
let mut resolved: Vec<Length> = vec![];
@ -388,10 +394,14 @@ impl<'a> GridLayouter<'a> {
let mut sizes =
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) {
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);
}
}
@ -431,16 +441,16 @@ impl<'a> GridLayouter<'a> {
}
}
/// Layout a row with linear sizing along the block axis. Such a row cannot
/// break across multiple regions, but it may force a region break.
/// Layout a row with linear height. Such a row cannot break across multiple
/// regions, but it may force a region break.
fn layout_linear_row(&mut self, ctx: &mut LayoutContext, v: Linear, y: usize) {
let resolved = v.resolve(self.regions.base.h);
let frame = self.layout_single_row(ctx, resolved, y);
// Skip to fitting region.
let length = frame.size.h;
while !self.regions.current.h.fits(length) && !self.regions.in_full_last() {
self.cts.max.y = Some(self.used.h + length);
let height = frame.size.h;
while !self.regions.current.h.fits(height) && !self.regions.in_last() {
self.cts.max.y = Some(self.used.h + height);
self.finish_region(ctx);
// 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);
}
/// 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(
&self,
ctx: &mut LayoutContext,
height: Length,
y: usize,
) -> 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();
for (x, &rcol) in self.rcols.iter().enumerate() {
@ -496,7 +506,7 @@ impl<'a> GridLayouter<'a> {
// Prepare frames.
let mut outputs: Vec<_> = heights
.iter()
.map(|&h| Frame::new(Size::new(self.used.w, h), h))
.map(|&h| Frame::new(Size::new(self.used.w, h)))
.collect();
// Prepare regions.
@ -553,7 +563,7 @@ impl<'a> GridLayouter<'a> {
}
// The frame for the region.
let mut output = Frame::new(size, size.h);
let mut output = Frame::new(size);
let mut pos = Point::zero();
// Place finished rows and layout fractional rows.

View File

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

View File

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

View File

@ -165,12 +165,16 @@ impl Layout for ShapeNode {
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) {
size.w = size.w.min(size.h);
size.h = size.w;
}
Frame::new(size, size.h)
Frame::new(size)
};
// Add fill and/or stroke.
@ -197,9 +201,6 @@ impl Layout for ShapeNode {
);
// Return tight constraints for now.
let mut cts = Constraints::new(regions.expand);
cts.exact = regions.current.to_spec().map(Some);
cts.base = regions.base.to_spec().map(Some);
vec![frame.constrain(cts)]
vec![frame.constrain(Constraints::tight(regions))]
}
}

View File

@ -35,10 +35,6 @@ impl Layout for SizedNode {
ctx: &mut LayoutContext,
regions: &Regions,
) -> 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.
let mut cts = Constraints::new(regions.expand);
cts.set_base_if_linear(regions.base, self.sizing);
@ -56,6 +52,10 @@ impl Layout for SizedNode {
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.
let pod = {
let size = Size::new(

View File

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

View File

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

View File

@ -305,8 +305,9 @@ pub struct ShapedGlyph {
impl<'a> ShapedText<'a> {
/// Build the shaped text's 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();
frame.baseline = self.baseline;
for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) {
let pos = Point::new(offset, self.baseline);

View File

@ -62,13 +62,7 @@ impl Layout for TransformNode {
let transform = Transform::translation(x, y)
.pre_concat(self.transform)
.pre_concat(Transform::translation(-x, -y));
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);
Rc::make_mut(frame).transform(transform);
}
frames

View File

@ -11,7 +11,7 @@
// Syntax sugar for function definitions.
#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!]
---

View File

@ -7,6 +7,6 @@
#let d = 3
#let value = [hi]
#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.

View File

@ -16,7 +16,7 @@ Auto-sized circle. \
Center-aligned rect in auto-sized circle.
#circle(fill: forest, stroke: conifer,
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.
#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.
#rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft]

View File

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