summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/eval/template.rs12
-rw-r--r--src/frame.rs54
-rw-r--r--src/geom/gen.rs69
-rw-r--r--src/geom/point.rs4
-rw-r--r--src/geom/size.rs4
-rw-r--r--src/geom/spec.rs4
-rw-r--r--src/layout/constraints.rs12
-rw-r--r--src/layout/regions.rs30
-rw-r--r--src/library/align.rs25
-rw-r--r--src/library/flow.rs19
-rw-r--r--src/library/grid.rs36
-rw-r--r--src/library/image.rs37
-rw-r--r--src/library/par.rs16
-rw-r--r--src/library/shape.rs11
-rw-r--r--src/library/sized.rs8
-rw-r--r--src/library/spacing.rs4
-rw-r--r--src/library/stack.rs38
-rw-r--r--src/library/text.rs3
-rw-r--r--src/library/transform.rs8
-rw-r--r--tests/typ/code/let.typ2
-rw-r--r--tests/typ/code/target.typ2
-rw-r--r--tests/typ/elements/circle.typ2
-rw-r--r--tests/typ/elements/rect.typ2
-rw-r--r--tests/typ/layout/grid-1.typ2
24 files changed, 228 insertions, 176 deletions
diff --git a/src/eval/template.rs b/src/eval/template.rs
index 307bff2a..e9cbbfcf 100644
--- a/src/eval/template.rs
+++ b/src/eval/template.rs
@@ -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));
}
}
diff --git a/src/frame.rs b/src/frame.rs
index 5b1c36ce..ec10fe96 100644
--- a/src/frame.rs
+++ b/src/frame.rs
@@ -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.
diff --git a/src/geom/gen.rs b/src/geom/gen.rs
index e770f80d..5232139b 100644
--- a/src/geom/gen.rs
+++ b/src/geom/gen.rs
@@ -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,
}
}
}
diff --git a/src/geom/point.rs b/src/geom/point.rs
index 49e3018a..30e3f9b6 100644
--- a/src/geom/point.rs
+++ b/src/geom/point.rs
@@ -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),
}
diff --git a/src/geom/size.rs b/src/geom/size.rs
index 12cb0ad2..1c049425 100644
--- a/src/geom/size.rs
+++ b/src/geom/size.rs
@@ -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),
}
diff --git a/src/geom/spec.rs b/src/geom/spec.rs
index 02263481..608643d8 100644
--- a/src/geom/spec.rs
+++ b/src/geom/spec.rs
@@ -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),
}
diff --git a/src/layout/constraints.rs b/src/layout/constraints.rs
index 36cfa582..b72254d7 100644
--- a/src/layout/constraints.rs
+++ b/src/layout/constraints.rs
@@ -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 {
diff --git a/src/layout/regions.rs b/src/layout/regions.rs
index 39297914..0ef92543 100644
--- a/src/layout/regions.rs
+++ b/src/layout/regions.rs
@@ -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
diff --git a/src/library/align.rs b/src/library/align.rs
index f788b325..6f079b7b 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -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
diff --git a/src/library/flow.rs b/src/library/flow.rs
index 44ce6304..d30bce09 100644
--- a/src/library/flow.rs
+++ b/src/library/flow.rs
@@ -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;
diff --git a/src/library/grid.rs b/src/library/grid.rs
index b48856eb..09bb3b3b 100644
--- a/src/library/grid.rs
+++ b/src/library/grid.rs
@@ -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.
diff --git a/src/library/image.rs b/src/library/image.rs
index a421af60..185d033a 100644
--- a/src/library/image.rs
+++ b/src/library/image.rs
@@ -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))]
}
}
diff --git a/src/library/par.rs b/src/library/par.rs
index 7f3f1088..e09e4ad2 100644
--- a/src/library/par.rs
+++ b/src/library/par.rs
@@ -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;
diff --git a/src/library/shape.rs b/src/library/shape.rs
index 5d3504d0..36e25b3c 100644
--- a/src/library/shape.rs
+++ b/src/library/shape.rs
@@ -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))]
}
}
diff --git a/src/library/sized.rs b/src/library/sized.rs
index 3e27ff2f..df150143 100644
--- a/src/library/sized.rs
+++ b/src/library/sized.rs
@@ -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(
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
index 26f1bee1..59911dc7 100644
--- a/src/library/spacing.rs
+++ b/src/library/spacing.rs
@@ -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))
}
diff --git a/src/library/stack.rs b/src/library/stack.rs
index d0d5225e..a6878bd6 100644
--- a/src/library/stack.rs
+++ b/src/library/stack.rs
@@ -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);
diff --git a/src/library/text.rs b/src/library/text.rs
index f1cd2b4d..aac99e8a 100644
--- a/src/library/text.rs
+++ b/src/library/text.rs
@@ -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);
diff --git a/src/library/transform.rs b/src/library/transform.rs
index 75df8067..207a8098 100644
--- a/src/library/transform.rs
+++ b/src/library/transform.rs
@@ -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
diff --git a/tests/typ/code/let.typ b/tests/typ/code/let.typ
index cd7531b7..7fd6e0da 100644
--- a/tests/typ/code/let.typ
+++ b/tests/typ/code/let.typ
@@ -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!]
---
diff --git a/tests/typ/code/target.typ b/tests/typ/code/target.typ
index 12a5ff8b..73516817 100644
--- a/tests/typ/code/target.typ
+++ b/tests/typ/code/target.typ
@@ -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.
diff --git a/tests/typ/elements/circle.typ b/tests/typ/elements/circle.typ
index 2e97e985..ad9d3a4e 100644
--- a/tests/typ/elements/circle.typ
+++ b/tests/typ/elements/circle.typ
@@ -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!]
)
)
diff --git a/tests/typ/elements/rect.typ b/tests/typ/elements/rect.typ
index bb666fb2..b3d4d286 100644
--- a/tests/typ/elements/rect.typ
+++ b/tests/typ/elements/rect.typ
@@ -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]
diff --git a/tests/typ/layout/grid-1.typ b/tests/typ/layout/grid-1.typ
index 2d13452b..647e366f 100644
--- a/tests/typ/layout/grid-1.typ
+++ b/tests/typ/layout/grid-1.typ
@@ -20,8 +20,6 @@
cell(100%, rgb("00ff00")),
)
-#grid()
-
---
#grid(
columns: (auto, auto, 40%),