summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-03-11 10:48:29 +0100
committerLaurenz <laurmaedje@gmail.com>2021-03-11 10:48:29 +0100
commitc1b1dbcc0925ba1730fabbfbca3c8b99831c5561 (patch)
tree6e4cb30753729c699bd899a7f2ec352e276beee8
parent4e5f85aa4ac0d6b51323bb2a6e1fbd3f4f46babb (diff)
Better expansion behaviour 🐪
This makes expansion behaviour inheritable by placing it into the area and passing it down during layouting instead of computing some approximation of what we want during execution.
-rw-r--r--src/exec/context.rs20
-rw-r--r--src/exec/mod.rs19
-rw-r--r--src/exec/state.rs6
-rw-r--r--src/layout/fixed.rs8
-rw-r--r--src/layout/mod.rs15
-rw-r--r--src/layout/pad.rs1
-rw-r--r--src/layout/par.rs21
-rw-r--r--src/layout/stack.rs12
-rw-r--r--src/library/pad.rs3
-rw-r--r--src/library/page.rs4
-rw-r--r--src/library/shapes.rs6
-rw-r--r--tests/ref/expand.pngbin0 -> 1419 bytes
-rw-r--r--tests/typ/expand.typ14
-rw-r--r--tests/typeset.rs5
14 files changed, 67 insertions, 67 deletions
diff --git a/src/exec/context.rs b/src/exec/context.rs
index d491a251..4d2047a6 100644
--- a/src/exec/context.rs
+++ b/src/exec/context.rs
@@ -7,7 +7,7 @@ use super::*;
use crate::diag::{Diag, DiagSet};
use crate::geom::{ChildAlign, Dir, Gen, LayoutDirs, Length, Linear, Sides, Size};
use crate::layout::{
- Expansion, Node, NodePad, NodePages, NodePar, NodeSpacing, NodeStack, NodeText, Tree,
+ Node, NodePad, NodePages, NodePar, NodeSpacing, NodeStack, NodeText, Tree,
};
use crate::parse::is_newline;
@@ -105,18 +105,18 @@ impl<'a> ExecContext<'a> {
}
}
- /// Execute the body of a function and return the result as a stack node.
- pub fn exec_body(&mut self, body: &ValueTemplate, expand: Spec<Expansion>) -> Node {
+ /// Execute a template and return the result as a stack node.
+ pub fn exec(&mut self, template: &ValueTemplate) -> Node {
let dirs = self.state.dirs;
let align = self.state.align;
self.start_group(ContentGroup);
self.start_par_group();
- body.exec(self);
+ template.exec(self);
self.end_par_group();
let children = self.end_group::<ContentGroup>().1;
- NodeStack { dirs, align, expand, children }.into()
+ NodeStack { dirs, align, children }.into()
}
/// Start a page group based on the active page state.
@@ -128,7 +128,6 @@ impl<'a> ExecContext<'a> {
pub fn start_page_group(&mut self, softness: Softness) {
self.start_group(PageGroup {
size: self.state.page.size,
- expand: self.state.page.expand,
padding: self.state.page.margins(),
dirs: self.state.dirs,
align: self.state.align,
@@ -158,7 +157,6 @@ impl<'a> ExecContext<'a> {
child: NodeStack {
dirs: group.dirs,
align: group.align,
- expand: group.expand,
children,
}
.into(),
@@ -186,13 +184,6 @@ impl<'a> ExecContext<'a> {
self.push(NodePar {
dirs: group.dirs,
align: group.align,
- // FIXME: This is a hack and should be superseded by something
- // better.
- cross_expansion: if self.groups.len() <= 1 {
- Expansion::Fill
- } else {
- Expansion::Fit
- },
line_spacing: group.line_spacing,
children,
});
@@ -306,7 +297,6 @@ pub enum Softness {
#[derive(Debug)]
struct PageGroup {
size: Size,
- expand: Spec<Expansion>,
padding: Sides<Linear>,
dirs: LayoutDirs,
align: ChildAlign,
diff --git a/src/exec/mod.rs b/src/exec/mod.rs
index ea2c90f4..79ad81e7 100644
--- a/src/exec/mod.rs
+++ b/src/exec/mod.rs
@@ -11,8 +11,7 @@ use std::rc::Rc;
use crate::diag::Pass;
use crate::env::Env;
use crate::eval::{ExprMap, TemplateFunc, TemplateNode, Value, ValueTemplate};
-use crate::geom::Spec;
-use crate::layout::{self, Expansion, NodeSpacing, NodeStack};
+use crate::layout::{self, NodeFixed, NodeSpacing, NodeStack};
use crate::pretty::pretty;
use crate::syntax::*;
@@ -120,11 +119,17 @@ impl Exec for NodeRaw {
ctx.apply_parbreak();
}
- ctx.push(NodeStack {
- dirs: ctx.state.dirs,
- align: ctx.state.align,
- expand: Spec::uniform(Expansion::Fit),
- children,
+ // This is wrapped in a fixed node to make sure the stack fits to its
+ // content instead of filling the available area.
+ ctx.push(NodeFixed {
+ width: None,
+ height: None,
+ child: NodeStack {
+ dirs: ctx.state.dirs,
+ align: ctx.state.align,
+ children,
+ }
+ .into(),
});
if self.block {
diff --git a/src/exec/state.rs b/src/exec/state.rs
index 416b5d08..3293662a 100644
--- a/src/exec/state.rs
+++ b/src/exec/state.rs
@@ -3,9 +3,8 @@ use std::rc::Rc;
use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight};
use crate::geom::{
- Align, ChildAlign, Dir, LayoutDirs, Length, Linear, Relative, Sides, Size, Spec,
+ Align, ChildAlign, Dir, LayoutDirs, Length, Linear, Relative, Sides, Size,
};
-use crate::layout::Expansion;
use crate::paper::{Paper, PaperClass, PAPER_A4};
/// The evaluation state.
@@ -42,8 +41,6 @@ pub struct PageState {
pub class: PaperClass,
/// The width and height of the page.
pub size: Size,
- /// Whether the expand the pages to the `size` or to fit the content.
- pub expand: Spec<Expansion>,
/// The amount of white space on each side of the page. If a side is set to
/// `None`, the default for the paper class is used.
pub margins: Sides<Option<Linear>>,
@@ -55,7 +52,6 @@ impl PageState {
Self {
class: paper.class,
size: paper.size(),
- expand: Spec::uniform(Expansion::Fill),
margins: Sides::uniform(None),
}
}
diff --git a/src/layout/fixed.rs b/src/layout/fixed.rs
index 60dbe4d6..a9554a07 100644
--- a/src/layout/fixed.rs
+++ b/src/layout/fixed.rs
@@ -20,7 +20,13 @@ impl Layout for NodeFixed {
self.height.map(|h| h.resolve(full.height)).unwrap_or(current.height),
);
- let areas = Areas::once(size);
+ let fill_if = |cond| if cond { Expand::Fill } else { Expand::Fit };
+ let expand = Spec::new(
+ fill_if(self.width.is_some()),
+ fill_if(self.height.is_some()),
+ );
+
+ let areas = Areas::once(size, expand);
self.child.layout(ctx, &areas)
}
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 622b0363..2e6a39ff 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -55,7 +55,7 @@ pub struct NodePages {
impl NodePages {
/// Layout the page run.
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Frame> {
- let areas = Areas::repeat(self.size);
+ let areas = Areas::repeat(self.size, Spec::uniform(Expand::Fill));
let layouted = self.child.layout(ctx, &areas);
layouted.into_frames()
}
@@ -85,26 +85,31 @@ pub struct Areas {
pub backlog: Vec<Size>,
/// The final area that is repeated when the backlog is empty.
pub last: Option<Size>,
+ /// Whether the frames resulting from layouting into this areas should be
+ /// shrunk to fit their content or expanded to fill the area.
+ pub expand: Spec<Expand>,
}
impl Areas {
/// Create a new length-1 sequence of areas with just one `area`.
- pub fn once(size: Size) -> Self {
+ pub fn once(size: Size, expand: Spec<Expand>) -> Self {
Self {
current: size,
full: size,
backlog: vec![],
last: None,
+ expand,
}
}
/// Create a new sequence of areas that repeats `area` indefinitely.
- pub fn repeat(size: Size) -> Self {
+ pub fn repeat(size: Size, expand: Spec<Expand>) -> Self {
Self {
current: size,
full: size,
backlog: vec![],
last: Some(size),
+ expand,
}
}
@@ -129,14 +134,14 @@ impl Areas {
/// Whether to expand or shrink a node along an axis.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub enum Expansion {
+pub enum Expand {
/// Fit the content.
Fit,
/// Fill the available space.
Fill,
}
-impl Expansion {
+impl Expand {
/// Resolve the expansion to either the `fit` or `fill` length.
///
/// Prefers `fit` if `fill` is infinite.
diff --git a/src/layout/pad.rs b/src/layout/pad.rs
index 425fa41b..02adebeb 100644
--- a/src/layout/pad.rs
+++ b/src/layout/pad.rs
@@ -37,6 +37,7 @@ fn shrink(areas: &Areas, padding: Sides<Linear>) -> Areas {
full: shrink(areas.full),
backlog: areas.backlog.iter().copied().map(shrink).collect(),
last: areas.last.map(shrink),
+ expand: areas.expand,
}
}
diff --git a/src/layout/par.rs b/src/layout/par.rs
index 45494dec..9a5d26dd 100644
--- a/src/layout/par.rs
+++ b/src/layout/par.rs
@@ -8,8 +8,6 @@ pub struct NodePar {
/// The children are placed in lines along the `cross` direction. The lines
/// are stacked along the `main` direction.
pub dirs: LayoutDirs,
- /// Whether to expand the cross axis to fill the area or to fit the content.
- pub cross_expansion: Expansion,
/// The spacing to insert after each line.
pub line_spacing: Length,
/// The nodes to be arranged in a paragraph.
@@ -42,11 +40,11 @@ impl From<NodePar> for NodeAny {
}
}
-struct ParLayouter<'a> {
- par: &'a NodePar,
+struct ParLayouter {
main: SpecAxis,
cross: SpecAxis,
dirs: LayoutDirs,
+ line_spacing: Length,
areas: Areas,
finished: Vec<Frame>,
lines: Vec<(Length, Frame, Align)>,
@@ -56,13 +54,13 @@ struct ParLayouter<'a> {
line_ruler: Align,
}
-impl<'a> ParLayouter<'a> {
- fn new(par: &'a NodePar, areas: Areas) -> Self {
+impl ParLayouter {
+ fn new(par: &NodePar, areas: Areas) -> Self {
Self {
- par,
main: par.dirs.main.axis(),
cross: par.dirs.cross.axis(),
dirs: par.dirs,
+ line_spacing: par.line_spacing,
areas,
finished: vec![],
lines: vec![],
@@ -134,13 +132,12 @@ impl<'a> ParLayouter<'a> {
}
fn finish_line(&mut self) {
+ let expand = self.areas.expand.switch(self.dirs);
let full_size = {
let full = self.areas.full.switch(self.dirs);
Gen::new(
self.line_size.main,
- self.par
- .cross_expansion
- .resolve(self.line_size.cross.min(full.cross), full.cross),
+ expand.cross.resolve(self.line_size.cross.min(full.cross), full.cross),
)
};
@@ -165,8 +162,8 @@ impl<'a> ParLayouter<'a> {
// Add line spacing, but only between lines.
if !self.lines.is_empty() {
- self.lines_size.main += self.par.line_spacing;
- *self.areas.current.get_mut(self.main) -= self.par.line_spacing;
+ self.lines_size.main += self.line_spacing;
+ *self.areas.current.get_mut(self.main) -= self.line_spacing;
}
// Update metrics of the whole paragraph.
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index 7d1d7a12..73bd49c1 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -10,8 +10,6 @@ pub struct NodeStack {
pub dirs: LayoutDirs,
/// How to align this stack in _its_ parent.
pub align: ChildAlign,
- /// Whether to expand the axes to fill the area or to fit the content.
- pub expand: Spec<Expansion>,
/// The nodes to be stacked.
pub children: Vec<Node>,
}
@@ -40,8 +38,7 @@ impl From<NodeStack> for NodeAny {
}
}
-struct StackLayouter<'a> {
- stack: &'a NodeStack,
+struct StackLayouter {
main: SpecAxis,
dirs: LayoutDirs,
areas: Areas,
@@ -51,10 +48,9 @@ struct StackLayouter<'a> {
ruler: Align,
}
-impl<'a> StackLayouter<'a> {
- fn new(stack: &'a NodeStack, areas: Areas) -> Self {
+impl StackLayouter {
+ fn new(stack: &NodeStack, areas: Areas) -> Self {
Self {
- stack,
main: stack.dirs.main.axis(),
dirs: stack.dirs,
areas,
@@ -97,7 +93,7 @@ impl<'a> StackLayouter<'a> {
fn finish_area(&mut self) {
let full_size = {
- let expand = self.stack.expand.switch(self.dirs);
+ let expand = self.areas.expand.switch(self.dirs);
let full = self.areas.full.switch(self.dirs);
Gen::new(
expand.main.resolve(self.used.main.min(full.main), full.main),
diff --git a/src/library/pad.rs b/src/library/pad.rs
index f9e4386d..0bf93093 100644
--- a/src/library/pad.rs
+++ b/src/library/pad.rs
@@ -29,8 +29,7 @@ pub fn pad(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
Value::template("pad", move |ctx| {
let snapshot = ctx.state.clone();
- let expand = Spec::uniform(Expansion::Fit);
- let child = ctx.exec_body(&body, expand);
+ let child = ctx.exec(&body);
ctx.push(NodePad { padding, child });
ctx.state = snapshot;
diff --git a/src/library/page.rs b/src/library/page.rs
index 1fdf4d3f..4ab92b31 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -45,19 +45,16 @@ pub fn page(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
if let Some(paper) = paper {
ctx.state.page.class = paper.class;
ctx.state.page.size = paper.size();
- ctx.state.page.expand = Spec::uniform(Expansion::Fill);
}
if let Some(width) = width {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.width = width;
- ctx.state.page.expand.horizontal = Expansion::Fill;
}
if let Some(height) = height {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.height = height;
- ctx.state.page.expand.vertical = Expansion::Fill;
}
if let Some(margins) = margins {
@@ -83,7 +80,6 @@ pub fn page(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
if flip.unwrap_or(false) {
let page = &mut ctx.state.page;
std::mem::swap(&mut page.size.width, &mut page.size.height);
- std::mem::swap(&mut page.expand.horizontal, &mut page.expand.vertical);
}
ctx.set_dirs(Gen::new(main, cross));
diff --git a/src/library/shapes.rs b/src/library/shapes.rs
index 5e638c01..9c466318 100644
--- a/src/library/shapes.rs
+++ b/src/library/shapes.rs
@@ -26,17 +26,13 @@ pub fn box_(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value {
let color = args.get(ctx, "color");
let body = args.find::<ValueTemplate>(ctx).unwrap_or_default();
- let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit };
- let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some()));
-
Value::template("box", move |ctx| {
let snapshot = ctx.state.clone();
ctx.set_dirs(Gen::new(main, cross));
- let child = ctx.exec_body(&body, expand);
+ let child = ctx.exec(&body);
let fixed = NodeFixed { width, height, child };
-
if let Some(color) = color {
ctx.push(NodeBackground {
fill: Fill::Color(color),
diff --git a/tests/ref/expand.png b/tests/ref/expand.png
new file mode 100644
index 00000000..fc1f5de9
--- /dev/null
+++ b/tests/ref/expand.png
Binary files differ
diff --git a/tests/typ/expand.typ b/tests/typ/expand.typ
new file mode 100644
index 00000000..3b242928
--- /dev/null
+++ b/tests/typ/expand.typ
@@ -0,0 +1,14 @@
+// Test fit/fill expansion.
+
+---
+#let right(body) = align(right, body)
+#let pad(body) = pad(left: 10pt, right: 10pt, body)
+
+// Top-level paragraph fills page, boxed paragraph only when width is fixed.
+L #right[R] \
+#box(width: 50pt)[L #right[R]] \
+#box[L #right[R]] \
+
+// Pad inherits expansion behaviour.
+#pad[PL #right[PR]] \
+#box(pad[PL #right[PR]])
diff --git a/tests/typeset.rs b/tests/typeset.rs
index 232dfa5c..ff0e44bc 100644
--- a/tests/typeset.rs
+++ b/tests/typeset.rs
@@ -20,8 +20,8 @@ use typst::eval::{EvalContext, Scope, Value, ValueArgs, ValueFunc};
use typst::exec::State;
use typst::export::pdf;
use typst::font::FsIndexExt;
-use typst::geom::{Length, Point, Sides, Size, Spec};
-use typst::layout::{Element, Expansion, Fill, Frame, Geometry, Image, Shape};
+use typst::geom::{Length, Point, Sides, Size};
+use typst::layout::{Element, Fill, Frame, Geometry, Image, Shape};
use typst::library;
use typst::parse::{LineMap, Scanner};
use typst::shaping::Shaped;
@@ -202,7 +202,6 @@ fn test_part(
// large and fit them to match their content.
let mut state = State::default();
state.page.size = Size::new(Length::pt(120.0), Length::raw(f64::INFINITY));
- state.page.expand = Spec::new(Expansion::Fill, Expansion::Fit);
state.page.margins = Sides::uniform(Some(Length::pt(10.0).into()));
let Pass { output: mut frames, diags } = typeset(env, &src, &scope, state);