summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/eval/context.rs4
-rw-r--r--src/eval/mod.rs4
-rw-r--r--src/eval/state.rs6
-rw-r--r--src/geom/length.rs10
-rw-r--r--src/geom/relative.rs7
-rw-r--r--src/geom/size.rs10
-rw-r--r--src/layout/fixed.rs6
-rw-r--r--src/layout/mod.rs63
-rw-r--r--src/layout/pad.rs6
-rw-r--r--src/layout/par.rs21
-rw-r--r--src/layout/stack.rs21
-rw-r--r--src/library/insert.rs20
-rw-r--r--src/library/layout.rs13
13 files changed, 115 insertions, 76 deletions
diff --git a/src/eval/context.rs b/src/eval/context.rs
index 64a8fbbe..1e09aaaf 100644
--- a/src/eval/context.rs
+++ b/src/eval/context.rs
@@ -95,6 +95,7 @@ impl EvalContext {
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,
@@ -124,7 +125,7 @@ impl EvalContext {
child: NodeStack {
dirs: group.dirs,
align: group.align,
- expansion: Gen::uniform(Expansion::Fill),
+ expand: group.expand,
children,
}
.into(),
@@ -281,6 +282,7 @@ pub enum Softness {
#[derive(Debug)]
struct PageGroup {
size: Size,
+ expand: Spec<Expansion>,
padding: Sides<Linear>,
dirs: LayoutDirs,
align: ChildAlign,
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 0e6fa79f..9abc1074 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -19,7 +19,7 @@ use std::rc::Rc;
use crate::color::Color;
use crate::diag::Pass;
use crate::env::SharedEnv;
-use crate::geom::{Angle, Gen, Length, Relative};
+use crate::geom::{Angle, Length, Relative, Spec};
use crate::layout::{self, Expansion, NodeSpacing, NodeStack};
use crate::syntax::*;
@@ -137,7 +137,7 @@ impl Eval for Spanned<&NodeRaw> {
ctx.push(NodeStack {
dirs: ctx.state.dirs,
align: ctx.state.align,
- expansion: Gen::uniform(Expansion::Fit),
+ expand: Spec::uniform(Expansion::Fit),
children,
});
diff --git a/src/eval/state.rs b/src/eval/state.rs
index 2a8ee2f0..ce6bd009 100644
--- a/src/eval/state.rs
+++ b/src/eval/state.rs
@@ -4,8 +4,9 @@ use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, Font
use super::Scope;
use crate::geom::{
- Align, ChildAlign, Dir, LayoutDirs, Length, Linear, Relative, Sides, Size,
+ Align, ChildAlign, Dir, LayoutDirs, Length, Linear, Relative, Sides, Size, Spec,
};
+use crate::layout::Expansion;
use crate::paper::{Paper, PaperClass, PAPER_A4};
/// The evaluation state.
@@ -45,6 +46,8 @@ pub struct StatePage {
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 in the order [left, top, right, bottom]. If a
/// side is set to `None`, the default for the paper class is used.
pub margins: Sides<Option<Linear>>,
@@ -56,6 +59,7 @@ impl StatePage {
Self {
class: paper.class,
size: paper.size(),
+ expand: Spec::uniform(Expansion::Fill),
margins: Sides::uniform(None),
}
}
diff --git a/src/geom/length.rs b/src/geom/length.rs
index f4d8682e..bfb1d668 100644
--- a/src/geom/length.rs
+++ b/src/geom/length.rs
@@ -81,6 +81,16 @@ impl Length {
Self { raw: self.raw.max(other.raw) }
}
+ /// Whether the length is finite.
+ pub fn is_finite(self) -> bool {
+ self.raw.is_finite()
+ }
+
+ /// Whether the length is infinite.
+ pub fn is_infinite(self) -> bool {
+ self.raw.is_infinite()
+ }
+
/// Whether the length is `NaN`.
pub fn is_nan(self) -> bool {
self.raw.is_nan()
diff --git a/src/geom/relative.rs b/src/geom/relative.rs
index 8fd430af..d39ead3a 100644
--- a/src/geom/relative.rs
+++ b/src/geom/relative.rs
@@ -26,7 +26,12 @@ impl Relative {
/// Resolve this relative to the given `length`.
pub fn resolve(self, length: Length) -> Length {
- self.get() * length
+ // Zero wins over infinity.
+ if self.0 == 0.0 {
+ Length::ZERO
+ } else {
+ self.get() * length
+ }
}
}
diff --git a/src/geom/size.rs b/src/geom/size.rs
index 1dfc8b97..caba3d8b 100644
--- a/src/geom/size.rs
+++ b/src/geom/size.rs
@@ -31,6 +31,16 @@ impl Size {
self.width >= other.width && self.height >= other.height
}
+ /// Whether both components are finite.
+ pub fn is_finite(self) -> bool {
+ self.width.is_finite() && self.height.is_finite()
+ }
+
+ /// Whether any of the two components is infinite.
+ pub fn is_infinite(self) -> bool {
+ self.width.is_infinite() || self.height.is_infinite()
+ }
+
/// Whether any of the two components is `NaN`.
pub fn is_nan(self) -> bool {
self.width.is_nan() || self.height.is_nan()
diff --git a/src/layout/fixed.rs b/src/layout/fixed.rs
index cb69b178..60dbe4d6 100644
--- a/src/layout/fixed.rs
+++ b/src/layout/fixed.rs
@@ -14,10 +14,10 @@ pub struct NodeFixed {
impl Layout for NodeFixed {
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
- let Area { rem, full } = areas.current;
+ let Areas { current, full, .. } = areas;
let size = Size::new(
- self.width.map(|w| w.resolve(full.width)).unwrap_or(rem.width),
- self.height.map(|h| h.resolve(full.height)).unwrap_or(rem.height),
+ self.width.map(|w| w.resolve(full.width)).unwrap_or(current.width),
+ self.height.map(|h| h.resolve(full.height)).unwrap_or(current.height),
);
let areas = Areas::once(size);
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 30026b9f..714bac4b 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -71,27 +71,13 @@ pub struct LayoutContext {
pub env: SharedEnv,
}
-/// An area into which content can be laid out.
-#[derive(Debug, Copy, Clone, PartialEq)]
-pub struct Area {
- /// The remaining size of this area.
- pub rem: Size,
- /// The full size this area once had (used for relative sizing).
- pub full: Size,
-}
-
-impl Area {
- /// Create a new area.
- pub fn new(size: Size) -> Self {
- Self { rem: size, full: size }
- }
-}
-
/// A collection of areas to layout into.
#[derive(Debug, Clone, PartialEq)]
pub struct Areas {
- /// The current area.
- pub current: Area,
+ /// The remaining size of the current area.
+ pub current: Size,
+ /// The full size the current area once had (used for relative sizing).
+ pub full: Size,
/// A stack of followup areas (the next area is the last element).
pub backlog: Vec<Size>,
/// The final area that is repeated when the backlog is empty.
@@ -102,7 +88,8 @@ impl Areas {
/// Create a new length-1 sequence of areas with just one `area`.
pub fn once(size: Size) -> Self {
Self {
- current: Area::new(size),
+ current: size,
+ full: size,
backlog: vec![],
last: None,
}
@@ -111,7 +98,8 @@ impl Areas {
/// Create a new sequence of areas that repeats `area` indefinitely.
pub fn repeat(size: Size) -> Self {
Self {
- current: Area::new(size),
+ current: size,
+ full: size,
backlog: vec![],
last: Some(size),
}
@@ -120,7 +108,8 @@ impl Areas {
/// Advance to the next area if there is any.
pub fn next(&mut self) {
if let Some(size) = self.backlog.pop().or(self.last) {
- self.current = Area::new(size);
+ self.current = size;
+ self.full = size;
}
}
@@ -130,11 +119,32 @@ impl Areas {
pub fn in_full_last(&self) -> bool {
self.backlog.is_empty()
&& self.last.map_or(true, |size| {
- self.current.rem.is_nan() || size.is_nan() || self.current.rem == size
+ self.current.is_nan() || size.is_nan() || self.current == size
})
}
}
+/// Whether to expand or shrink a node along an axis.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum Expansion {
+ /// Fit the content.
+ Fit,
+ /// Fill the available space.
+ Fill,
+}
+
+impl Expansion {
+ /// Resolve the expansion to either the `fit` or `fill` length.
+ ///
+ /// Prefers `fit` if `fill` is infinite.
+ pub fn resolve(self, fit: Length, fill: Length) -> Length {
+ match self {
+ Self::Fill if fill.is_finite() => fill,
+ _ => fit,
+ }
+ }
+}
+
/// The result of layouting a node.
#[derive(Debug, Clone, PartialEq)]
pub enum Layouted {
@@ -158,15 +168,6 @@ impl Layouted {
}
}
-/// Whether to expand or shrink a node along an axis.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub enum Expansion {
- /// Fit the content.
- Fit,
- /// Fill the available space.
- Fill,
-}
-
/// A finished layout with elements at fixed positions.
#[derive(Debug, Clone, PartialEq)]
pub struct Frame {
diff --git a/src/layout/pad.rs b/src/layout/pad.rs
index 9e2718c3..f8a623e3 100644
--- a/src/layout/pad.rs
+++ b/src/layout/pad.rs
@@ -39,10 +39,8 @@ impl From<NodePad> for NodeAny {
fn shrink(areas: &Areas, padding: Sides<Linear>) -> Areas {
let shrink = |size| size - padding.resolve(size).size();
Areas {
- current: Area {
- rem: shrink(areas.current.rem),
- full: shrink(areas.current.full),
- },
+ current: shrink(areas.current),
+ full: shrink(areas.full),
backlog: areas.backlog.iter().copied().map(shrink).collect(),
last: areas.last.map(shrink),
}
diff --git a/src/layout/par.rs b/src/layout/par.rs
index 7b2e4e92..2a1ad941 100644
--- a/src/layout/par.rs
+++ b/src/layout/par.rs
@@ -74,7 +74,7 @@ impl<'a> ParLayouter<'a> {
}
fn push_spacing(&mut self, amount: Length) {
- let cross_max = self.areas.current.rem.get(self.cross);
+ let cross_max = self.areas.current.get(self.cross);
self.run_size.cross = (self.run_size.cross + amount).min(cross_max);
}
@@ -84,7 +84,7 @@ impl<'a> ParLayouter<'a> {
}
let fits = {
- let mut usable = self.areas.current.rem;
+ let mut usable = self.areas.current;
*usable.get_mut(self.cross) -= self.run_size.cross;
usable.fits(frame.size)
};
@@ -92,7 +92,7 @@ impl<'a> ParLayouter<'a> {
if !fits {
self.finish_run();
- while !self.areas.current.rem.fits(frame.size) {
+ while !self.areas.current.fits(frame.size) {
if self.areas.in_full_last() {
// TODO: Diagnose once the necessary spans exist.
let _ = warning!("cannot fit frame into any area");
@@ -112,10 +112,15 @@ impl<'a> ParLayouter<'a> {
}
fn finish_run(&mut self) {
- let full_size = Gen::new(self.run_size.main, match self.par.cross_expansion {
- Expansion::Fill => self.areas.current.full.get(self.cross),
- Expansion::Fit => self.run_size.cross,
- });
+ let full_size = {
+ let full = self.areas.full.switch(self.dirs);
+ Gen::new(
+ self.run_size.main,
+ self.par
+ .cross_expansion
+ .resolve(self.run_size.cross.min(full.cross), full.cross),
+ )
+ };
let mut output = Frame::new(full_size.switch(self.dirs).to_size());
@@ -139,7 +144,7 @@ impl<'a> ParLayouter<'a> {
self.lines.push((self.lines_size.main, output, self.run_ruler));
let main_offset = full_size.main + self.par.line_spacing;
- *self.areas.current.rem.get_mut(self.main) -= main_offset;
+ *self.areas.current.get_mut(self.main) -= main_offset;
self.lines_size.main += main_offset;
self.lines_size.cross = self.lines_size.cross.max(full_size.cross);
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index eac631d9..bfb93a94 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -11,7 +11,7 @@ pub struct NodeStack {
/// 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 expansion: Gen<Expansion>,
+ pub expand: Spec<Expansion>,
/// The nodes to be stacked.
pub children: Vec<Node>,
}
@@ -66,7 +66,7 @@ impl<'a> StackLayouter<'a> {
}
fn push_spacing(&mut self, amount: Length) {
- let main_rest = self.areas.current.rem.get_mut(self.main);
+ let main_rest = self.areas.current.get_mut(self.main);
let capped = amount.min(*main_rest);
*main_rest -= capped;
self.used.main += capped;
@@ -77,7 +77,7 @@ impl<'a> StackLayouter<'a> {
self.finish_area();
}
- while !self.areas.current.rem.fits(frame.size) {
+ while !self.areas.current.fits(frame.size) {
if self.areas.in_full_last() {
// TODO: Diagnose once the necessary spans exist.
let _ = warning!("cannot fit frame into any area");
@@ -90,7 +90,7 @@ impl<'a> StackLayouter<'a> {
let size = frame.size.switch(self.dirs);
self.frames.push((self.used.main, frame, align));
- *self.areas.current.rem.get_mut(self.main) -= size.main;
+ *self.areas.current.get_mut(self.main) -= size.main;
self.used.main += size.main;
self.used.cross = self.used.cross.max(size.cross);
self.ruler = align.main;
@@ -98,16 +98,11 @@ impl<'a> StackLayouter<'a> {
fn finish_area(&mut self) {
let full_size = {
- let full = self.areas.current.full.switch(self.dirs);
+ let expand = self.stack.expand.switch(self.dirs);
+ let full = self.areas.full.switch(self.dirs);
Gen::new(
- match self.stack.expansion.main {
- Expansion::Fill => full.main,
- Expansion::Fit => self.used.main.min(full.main),
- },
- match self.stack.expansion.cross {
- Expansion::Fill => full.cross,
- Expansion::Fit => self.used.cross.min(full.cross),
- },
+ expand.main.resolve(self.used.main.min(full.main), full.main),
+ expand.cross.resolve(self.used.cross.min(full.cross), full.cross),
)
};
diff --git a/src/library/insert.rs b/src/library/insert.rs
index 8d60927a..51cbbf52 100644
--- a/src/library/insert.rs
+++ b/src/library/insert.rs
@@ -55,8 +55,11 @@ struct NodeImage {
impl Layout for NodeImage {
fn layout(&self, _: &mut LayoutContext, areas: &Areas) -> Layouted {
- let Area { rem, full } = areas.current;
- let pixel_ratio = (self.dimensions.0 as f64) / (self.dimensions.1 as f64);
+ let Areas { current, full, .. } = areas;
+
+ let pixel_width = self.dimensions.0 as f64;
+ let pixel_height = self.dimensions.1 as f64;
+ let pixel_ratio = pixel_width / pixel_height;
let width = self.width.map(|w| w.resolve(full.width));
let height = self.height.map(|w| w.resolve(full.height));
@@ -66,12 +69,15 @@ impl Layout for NodeImage {
(Some(width), None) => Size::new(width, width / pixel_ratio),
(None, Some(height)) => Size::new(height * pixel_ratio, height),
(None, None) => {
- let ratio = rem.width / rem.height;
- if ratio < pixel_ratio {
- Size::new(rem.width, rem.width / pixel_ratio)
- } else {
+ let ratio = current.width / current.height;
+ if ratio < pixel_ratio && current.width.is_finite() {
+ Size::new(current.width, current.width / pixel_ratio)
+ } else if current.height.is_finite() {
// TODO: Fix issue with line spacing.
- Size::new(rem.height * pixel_ratio, rem.height)
+ Size::new(current.height * pixel_ratio, current.height)
+ } else {
+ // Totally unbounded area, we have to make up something.
+ Size::new(Length::pt(pixel_width), Length::pt(pixel_height))
}
}
};
diff --git a/src/library/layout.rs b/src/library/layout.rs
index 59c1954b..0e04c507 100644
--- a/src/library/layout.rs
+++ b/src/library/layout.rs
@@ -197,13 +197,12 @@ pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value {
let children = ctx.end_content_group();
let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit };
- let expansion =
- Spec::new(fill_if(width.is_some()), fill_if(height.is_some())).switch(dirs);
+ let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some()));
ctx.push(NodeFixed {
width,
height,
- child: NodeStack { dirs, align, expansion, children }.into(),
+ child: NodeStack { dirs, align, expand, children }.into(),
});
ctx.state = snapshot;
@@ -271,6 +270,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value {
if let Some(paper) = Paper::from_name(&name.v) {
ctx.state.page.class = paper.class;
ctx.state.page.size = paper.size();
+ ctx.state.page.expand = Spec::uniform(Expansion::Fill);
} else {
ctx.diag(error!(name.span, "invalid paper name"));
}
@@ -279,11 +279,13 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value {
if let Some(width) = args.get(ctx, "width") {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.width = width;
+ ctx.state.page.expand.horizontal = Expansion::Fill;
}
if let Some(height) = args.get(ctx, "height") {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.height = height;
+ ctx.state.page.expand.vertical = Expansion::Fill;
}
if let Some(margins) = args.get(ctx, "margins") {
@@ -307,8 +309,9 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value {
}
if args.get(ctx, "flip").unwrap_or(false) {
- let size = &mut ctx.state.page.size;
- std::mem::swap(&mut size.width, &mut size.height);
+ 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);
}
let main = args.get(ctx, "main-dir");