summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/eval/template.rs14
-rw-r--r--src/eval/walk.rs8
-rw-r--r--src/geom/gen.rs2
-rw-r--r--src/geom/sides.rs2
-rw-r--r--src/geom/spec.rs2
-rw-r--r--src/layout/grid.rs57
-rw-r--r--src/layout/image.rs10
-rw-r--r--src/layout/mod.rs2
-rw-r--r--src/layout/shape.rs3
-rw-r--r--src/layout/shaping.rs3
-rw-r--r--src/layout/spacing.rs50
-rw-r--r--src/layout/stack.rs160
-rw-r--r--src/layout/tree.rs1
-rw-r--r--src/library/layout.rs10
14 files changed, 164 insertions, 160 deletions
diff --git a/src/eval/template.rs b/src/eval/template.rs
index 59fe293a..63916339 100644
--- a/src/eval/template.rs
+++ b/src/eval/template.rs
@@ -6,7 +6,7 @@ use std::rc::Rc;
use super::Str;
use crate::diag::StrResult;
-use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size};
+use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size, SpecAxis};
use crate::layout::{
Decoration, LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild,
StackNode,
@@ -309,7 +309,7 @@ impl Builder {
fn parbreak(&mut self) {
let amount = self.style.par_spacing();
self.stack.finish_par(&self.style);
- self.stack.push_soft(StackChild::Spacing(amount.into()));
+ self.stack.push_soft(StackChild::spacing(amount, SpecAxis::Vertical));
}
/// Apply a forced page break.
@@ -335,7 +335,7 @@ impl Builder {
/// Push a block node into the active stack, finishing the active paragraph.
fn block(&mut self, node: impl Into<LayoutNode>) {
self.parbreak();
- self.stack.push(StackChild::Any(node.into(), self.style.aligns.block));
+ self.stack.push(StackChild::new(node, self.style.aligns.block));
self.parbreak();
}
@@ -344,7 +344,7 @@ impl Builder {
match axis {
GenAxis::Block => {
self.stack.finish_par(&self.style);
- self.stack.push_hard(StackChild::Spacing(amount));
+ self.stack.push_hard(StackChild::spacing(amount, SpecAxis::Vertical));
}
GenAxis::Inline => {
self.stack.par.push_hard(ParChild::Spacing(amount));
@@ -508,10 +508,8 @@ impl ParBuilder {
fn build(self) -> Option<StackChild> {
let Self { align, dir, line_spacing, children, .. } = self;
- (!children.is_empty()).then(|| {
- let node = ParNode { dir, line_spacing, children };
- StackChild::Any(node.into(), align)
- })
+ (!children.is_empty())
+ .then(|| StackChild::new(ParNode { dir, line_spacing, children }, align))
}
}
diff --git a/src/eval/walk.rs b/src/eval/walk.rs
index b76300ec..24284e4e 100644
--- a/src/eval/walk.rs
+++ b/src/eval/walk.rs
@@ -2,7 +2,7 @@ use std::rc::Rc;
use super::{Eval, EvalContext, Str, Template, Value};
use crate::diag::TypResult;
-use crate::geom::Align;
+use crate::geom::{Align, SpecAxis};
use crate::layout::{ParChild, ParNode, StackChild, StackNode};
use crate::syntax::*;
use crate::util::BoolExt;
@@ -119,9 +119,9 @@ fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) {
StackNode {
dir: style.dir,
children: vec![
- StackChild::Any(label.into(), Align::Start),
- StackChild::Spacing((style.text.size / 2.0).into()),
- StackChild::Any(body.to_stack(&style).into(), Align::Start),
+ StackChild::new(label, Align::Start),
+ StackChild::spacing(style.text.size / 2.0, SpecAxis::Horizontal),
+ StackChild::new(body.to_stack(&style), Align::Start),
],
}
});
diff --git a/src/geom/gen.rs b/src/geom/gen.rs
index 18801460..ff8c881a 100644
--- a/src/geom/gen.rs
+++ b/src/geom/gen.rs
@@ -98,7 +98,7 @@ impl<T: Debug> Debug for Gen<T> {
}
/// The two generic layouting axes.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum GenAxis {
/// The axis words and lines are set along.
Inline,
diff --git a/src/geom/sides.rs b/src/geom/sides.rs
index fc7fb3f4..d1fcf9b7 100644
--- a/src/geom/sides.rs
+++ b/src/geom/sides.rs
@@ -76,7 +76,7 @@ impl<T> Get<Side> for Sides<T> {
}
/// The four sides of objects.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Side {
/// The left side.
Left,
diff --git a/src/geom/spec.rs b/src/geom/spec.rs
index a6235548..a257214b 100644
--- a/src/geom/spec.rs
+++ b/src/geom/spec.rs
@@ -101,7 +101,7 @@ impl<T: Debug> Debug for Spec<T> {
}
/// The two specific layouting axes.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum SpecAxis {
/// The horizontal layouting axis.
Horizontal,
diff --git a/src/layout/grid.rs b/src/layout/grid.rs
index 7ed5dd7a..ddda27c0 100644
--- a/src/layout/grid.rs
+++ b/src/layout/grid.rs
@@ -330,18 +330,10 @@ impl<'a> GridLayouter<'a> {
/// Layout the grid row-by-row.
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
- let base = self.regions.base.get(self.block);
-
for y in 0 .. self.rows.len() {
match self.rows[y] {
- TrackSizing::Auto => {
- self.layout_auto_row(ctx, y);
- }
- TrackSizing::Linear(v) => {
- let resolved = v.resolve(base);
- let frame = self.layout_single_row(ctx, resolved, y);
- self.push_row(ctx, frame);
- }
+ TrackSizing::Auto => self.layout_auto_row(ctx, y),
+ TrackSizing::Linear(v) => self.layout_linear_row(ctx, v, y),
TrackSizing::Fractional(v) => {
self.fr += v;
self.constraints.exact.set(self.block, Some(self.full));
@@ -395,7 +387,7 @@ impl<'a> GridLayouter<'a> {
// Layout into a single region.
if let &[first] = resolved.as_slice() {
let frame = self.layout_single_row(ctx, first, y);
- self.push_row(ctx, frame);
+ self.push_row(frame);
return;
}
@@ -414,14 +406,39 @@ impl<'a> GridLayouter<'a> {
let frames = self.layout_multi_row(ctx, &resolved, y);
let len = frames.len();
for (i, frame) in frames.into_iter().enumerate() {
+ self.push_row(frame);
if i + 1 < len {
self.constraints.exact.set(self.block, Some(self.full));
+ self.finish_region(ctx);
}
- self.push_row(ctx, frame);
}
}
- /// Layout a row with a fixed size along the block axis.
+ /// 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.
+ fn layout_linear_row(&mut self, ctx: &mut LayoutContext, v: Linear, y: usize) {
+ let base = self.regions.base.get(self.block);
+ let resolved = v.resolve(base);
+ let frame = self.layout_single_row(ctx, resolved, y);
+
+ // Skip to fitting region.
+ let length = frame.size.get(self.block);
+ while !self.regions.current.get(self.block).fits(length)
+ && !self.regions.in_full_last()
+ {
+ self.constraints.max.set(self.block, Some(self.used.block + length));
+ self.finish_region(ctx);
+
+ // Don't skip multiple regions for gutter and don't push a row.
+ if y % 2 == 1 {
+ return;
+ }
+ }
+
+ self.push_row(frame);
+ }
+
+ /// Layout a row with a fixed size along the block axis and return its frame.
fn layout_single_row(
&self,
ctx: &mut LayoutContext,
@@ -508,19 +525,9 @@ impl<'a> GridLayouter<'a> {
outputs
}
- /// Push a row frame into the current or next fitting region, finishing
- /// regions (including layouting fractional rows) if necessary.
- fn push_row(&mut self, ctx: &mut LayoutContext, frame: Frame) {
+ /// Push a row frame into the current region.
+ fn push_row(&mut self, frame: Frame) {
let length = frame.size.get(self.block);
-
- // Skip to fitting region.
- while !self.regions.current.get(self.block).fits(length)
- && !self.regions.in_full_last()
- {
- self.constraints.max.set(self.block, Some(self.used.block + length));
- self.finish_region(ctx);
- }
-
*self.regions.current.get_mut(self.block) -= length;
self.used.block += length;
self.lrows.push(Row::Frame(frame));
diff --git a/src/layout/image.rs b/src/layout/image.rs
index 4327375b..eb2fc221 100644
--- a/src/layout/image.rs
+++ b/src/layout/image.rs
@@ -23,18 +23,18 @@ impl Layout for ImageNode {
let pixel_size = Spec::new(img.width() as f64, img.height() as f64);
let pixel_ratio = pixel_size.x / pixel_size.y;
- let mut constraints = Constraints::new(regions.expand);
- constraints.set_base_if_linear(regions.base, Spec::new(self.width, self.height));
-
let width = self.width.map(|w| w.resolve(regions.base.w));
let height = self.height.map(|w| w.resolve(regions.base.h));
+ let mut cts = Constraints::new(regions.expand);
+ cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height));
+
let size = match (width, height) {
(Some(width), Some(height)) => Size::new(width, height),
(Some(width), None) => Size::new(width, width / pixel_ratio),
(None, Some(height)) => Size::new(height * pixel_ratio, height),
(None, None) => {
- constraints.exact.x = Some(regions.current.w);
+ cts.exact.x = Some(regions.current.w);
if regions.current.w.is_finite() {
// Fit to width.
Size::new(regions.current.w, regions.current.w / pixel_ratio)
@@ -48,7 +48,7 @@ impl Layout for ImageNode {
let mut frame = Frame::new(size, size.h);
frame.push(Point::zero(), Element::Image(self.id, size));
- vec![frame.constrain(constraints)]
+ vec![frame.constrain(cts)]
}
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index cd59e3d2..ee74ff6f 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -11,6 +11,7 @@ mod par;
mod regions;
mod shape;
mod shaping;
+mod spacing;
mod stack;
mod tree;
@@ -25,6 +26,7 @@ pub use par::*;
pub use regions::*;
pub use shape::*;
pub use shaping::*;
+pub use spacing::*;
pub use stack::*;
pub use tree::*;
diff --git a/src/layout/shape.rs b/src/layout/shape.rs
index aa90707c..3469f660 100644
--- a/src/layout/shape.rs
+++ b/src/layout/shape.rs
@@ -46,7 +46,8 @@ impl Layout for ShapeNode {
let mut cts = Constraints::new(regions.expand);
cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height));
- // Set exact and base constraint if child is automatically sized.
+ // Set tight exact and base constraints if the child is
+ // automatically sized since we don't know what the child might do.
if self.width.is_none() {
cts.exact.x = Some(regions.current.w);
cts.base.x = Some(regions.base.w);
diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs
index 4580ebf5..7bd2646d 100644
--- a/src/layout/shaping.rs
+++ b/src/layout/shaping.rs
@@ -48,6 +48,7 @@ pub fn shape<'a>(
/// This type contains owned or borrowed shaped text runs, which can be
/// measured, used to reshape substrings more quickly and converted into a
/// frame.
+#[derive(Debug, Clone)]
pub struct ShapedText<'a> {
/// The text that was shaped.
pub text: &'a str,
@@ -64,7 +65,7 @@ pub struct ShapedText<'a> {
}
/// A single glyph resulting from shaping.
-#[derive(Copy, Clone)]
+#[derive(Debug, Copy, Clone)]
pub struct ShapedGlyph {
/// The font face the glyph is contained in.
pub face_id: FaceId,
diff --git a/src/layout/spacing.rs b/src/layout/spacing.rs
new file mode 100644
index 00000000..68fab3e6
--- /dev/null
+++ b/src/layout/spacing.rs
@@ -0,0 +1,50 @@
+use super::*;
+
+/// Spacing between other nodes.
+#[derive(Debug)]
+#[cfg_attr(feature = "layout-cache", derive(Hash))]
+pub struct SpacingNode {
+ /// Which axis to space on.
+ pub axis: SpecAxis,
+ /// How much spacing to add.
+ pub amount: Linear,
+}
+
+impl Layout for SpacingNode {
+ fn layout(
+ &self,
+ _: &mut LayoutContext,
+ regions: &Regions,
+ ) -> Vec<Constrained<Rc<Frame>>> {
+ let base = regions.base.get(self.axis);
+ let resolved = self.amount.resolve(base);
+ let limit = regions.current.get(self.axis);
+
+ // Generate constraints.
+ let mut cts = Constraints::new(regions.expand);
+ if self.amount.is_relative() {
+ cts.base.set(self.axis, Some(base));
+ }
+
+ // If the spacing fits into the region, any larger region would also do.
+ // If it was limited though, any change it region size might lead to
+ // different results.
+ if resolved < limit {
+ cts.min.set(self.axis, Some(resolved));
+ } else {
+ cts.exact.set(self.axis, Some(limit));
+ }
+
+ // Create frame with limited spacing size along spacing axis and zero
+ // extent along the other axis.
+ let mut size = Size::zero();
+ size.set(self.axis, resolved.min(limit));
+ vec![Frame::new(size, size.h).constrain(cts)]
+ }
+}
+
+impl From<SpacingNode> for LayoutNode {
+ fn from(spacing: SpacingNode) -> Self {
+ Self::new(spacing)
+ }
+}
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index 765fb7ab..4b9328a7 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -12,15 +12,6 @@ pub struct StackNode {
pub children: Vec<StackChild>,
}
-/// A child of a stack node.
-#[cfg_attr(feature = "layout-cache", derive(Hash))]
-pub enum StackChild {
- /// Spacing between other nodes.
- Spacing(Linear),
- /// Any child node and how to align it in the stack.
- Any(LayoutNode, Align),
-}
-
impl Layout for StackNode {
fn layout(
&self,
@@ -37,12 +28,31 @@ impl From<StackNode> for LayoutNode {
}
}
+/// A child of a stack node.
+#[cfg_attr(feature = "layout-cache", derive(Hash))]
+pub struct StackChild {
+ /// The node itself.
+ pub node: LayoutNode,
+ /// How to align the node along the block axis.
+ pub align: Align,
+}
+
+impl StackChild {
+ /// Create a new stack child.
+ pub fn new(node: impl Into<LayoutNode>, align: Align) -> Self {
+ Self { node: node.into(), align }
+ }
+
+ /// Create a spacing stack child.
+ pub fn spacing(amount: impl Into<Linear>, axis: SpecAxis) -> Self {
+ Self::new(SpacingNode { amount: amount.into(), axis }, Align::Start)
+ }
+}
+
impl Debug for StackChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::Spacing(v) => write!(f, "Spacing({:?})", v),
- Self::Any(node, _) => node.fmt(f),
- }
+ write!(f, "{:?}: ", self.align)?;
+ self.node.fmt(f)
}
}
@@ -51,7 +61,7 @@ struct StackLayouter<'a> {
/// The stack node to layout.
stack: &'a StackNode,
/// The axis of the block direction.
- block: SpecAxis,
+ axis: SpecAxis,
/// Whether the stack should expand to fill the region.
expand: Spec<bool>,
/// The region to layout into.
@@ -63,10 +73,6 @@ struct StackLayouter<'a> {
used: Gen<Length>,
/// The alignment ruler for the current region.
ruler: Align,
- /// The constraints for the current region.
- constraints: Constraints,
- /// Whether the last region can fit all the remaining content.
- overflowing: bool,
/// Offset, alignment and frame for all children that fit into the current
/// region. The exact positions are not known yet.
frames: Vec<(Length, Align, Rc<Frame>)>,
@@ -77,23 +83,21 @@ struct StackLayouter<'a> {
impl<'a> StackLayouter<'a> {
/// Create a new stack layouter.
fn new(stack: &'a StackNode, mut regions: Regions) -> Self {
- let block = stack.dir.axis();
+ let axis = stack.dir.axis();
let full = regions.current;
let expand = regions.expand;
// Disable expansion along the block axis for children.
- regions.expand.set(block, false);
+ regions.expand.set(axis, false);
Self {
stack,
- block,
+ axis,
expand,
regions,
full,
used: Gen::zero(),
ruler: Align::Start,
- constraints: Constraints::new(expand),
- overflowing: false,
frames: vec![],
finished: vec![],
}
@@ -102,17 +106,12 @@ impl<'a> StackLayouter<'a> {
/// Layout all children.
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
for child in &self.stack.children {
- match *child {
- StackChild::Spacing(amount) => self.space(amount),
- StackChild::Any(ref node, align) => {
- let nodes = node.layout(ctx, &self.regions);
- let len = nodes.len();
- for (i, frame) in nodes.into_iter().enumerate() {
- if i + 1 < len {
- self.constraints.exact = self.full.to_spec().map(Some);
- }
- self.push_frame(frame.item, align);
- }
+ let frames = child.node.layout(ctx, &self.regions);
+ let len = frames.len();
+ for (i, frame) in frames.into_iter().enumerate() {
+ self.push_frame(frame.item, child.align);
+ if i + 1 < len {
+ self.finish_region();
}
}
}
@@ -121,96 +120,37 @@ impl<'a> StackLayouter<'a> {
self.finished
}
- /// Add block-axis spacing into the current region.
- fn space(&mut self, amount: Linear) {
- // Resolve the linear.
- let full = self.full.get(self.block);
- let resolved = amount.resolve(full);
-
- // Cap the spacing to the remaining available space. This action does
- // not directly affect the constraints because of the cap.
- let remaining = self.regions.current.get_mut(self.block);
- let capped = resolved.min(*remaining);
-
- // Grow our size and shrink the available space in the region.
- self.used.block += capped;
- *remaining -= capped;
- }
-
- /// Push a frame into the current or next fitting region, finishing regions
- /// if necessary.
+ /// Push a frame into the current region.
fn push_frame(&mut self, frame: Rc<Frame>, align: Align) {
- let size = frame.size.to_gen(self.block);
-
- // Don't allow `Start` after `End` in the same region.
- if align < self.ruler {
- self.finish_region();
- }
-
- // Find a fitting region.
- while !self.regions.current.get(self.block).fits(size.block) {
- if self.regions.in_full_last() {
- self.overflowing = true;
- break;
- }
-
- self.constraints
- .max
- .get_mut(self.block)
- .set_min(self.used.block + size.block);
-
- self.finish_region();
- }
-
- // Shrink available space in the region.
- *self.regions.current.get_mut(self.block) -= size.block;
-
// Grow our size.
let offset = self.used.block;
+ let size = frame.size.to_gen(self.axis);
self.used.block += size.block;
self.used.inline.set_max(size.inline);
- self.ruler = align;
+ self.ruler = self.ruler.max(align);
- // Remember the frame with offset and alignment.
- self.frames.push((offset, align, frame));
+ // Remember the frame and shrink available space in the region for the
+ // following children.
+ self.frames.push((offset, self.ruler, frame));
+ *self.regions.current.get_mut(self.axis) -= size.block;
}
/// Finish the frame for one region.
fn finish_region(&mut self) {
- let expand = self.expand;
- let used = self.used.to_size(self.block);
-
// Determine the stack's size dependening on whether the region expands.
+ let used = self.used.to_size(self.axis);
let size = Size::new(
- if expand.x {
- self.constraints.exact.x = Some(self.full.w);
- self.full.w
- } else {
- self.constraints.min.x = Some(used.w.min(self.full.w));
- used.w
- },
- if expand.y {
- self.constraints.exact.y = Some(self.full.h);
- self.full.h
- } else {
- self.constraints.min.y = Some(used.h.min(self.full.h));
- used.h
- },
+ if self.expand.x { self.full.w } else { used.w },
+ if self.expand.y { self.full.h } else { used.h },
);
- if self.overflowing {
- self.constraints.min.y = None;
- self.constraints.max.y = None;
- self.constraints.exact = self.full.to_spec().map(Some);
- }
-
let mut output = Frame::new(size, size.h);
let mut first = true;
// Place all frames.
for (offset, align, frame) in self.frames.drain(..) {
- let stack_size = size.to_gen(self.block);
- let child_size = frame.size.to_gen(self.block);
+ let stack_size = size.to_gen(self.axis);
+ let child_size = frame.size.to_gen(self.axis);
// Align along the block axis.
let block = align.resolve(
@@ -224,7 +164,7 @@ impl<'a> StackLayouter<'a> {
},
);
- let pos = Gen::new(Length::zero(), block).to_point(self.block);
+ let pos = Gen::new(Length::zero(), block).to_point(self.axis);
// The baseline of the stack is that of the first frame.
if first {
@@ -235,11 +175,15 @@ impl<'a> StackLayouter<'a> {
output.push_frame(pos, frame);
}
+ // Generate tight constraints for now.
+ let mut cts = Constraints::new(self.expand);
+ cts.exact = self.full.to_spec().map(Some);
+ cts.base = self.regions.base.to_spec().map(Some);
+
self.regions.next();
self.full = self.regions.current;
self.used = Gen::zero();
self.ruler = Align::Start;
- self.finished.push(output.constrain(self.constraints));
- self.constraints = Constraints::new(expand);
+ self.finished.push(output.constrain(cts));
}
}
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
index 900eb1a9..181bb611 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -103,6 +103,7 @@ impl Layout for LayoutNode {
#[cfg(debug_assertions)]
if !entry.check(regions) {
+ eprintln!("node: {:#?}", self.node);
eprintln!("regions: {:#?}", regions);
eprintln!(
"constraints: {:#?}",
diff --git a/src/library/layout.rs b/src/library/layout.rs
index b98b08f1..ffe8bc69 100644
--- a/src/library/layout.rs
+++ b/src/library/layout.rs
@@ -209,7 +209,7 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
}
let dir = args.named("dir")?.unwrap_or(Dir::TTB);
- let spacing = args.named("spacing")?;
+ let spacing = args.named::<Linear>("spacing")?;
let list: Vec<Child> = args.all().collect();
Ok(Value::Template(Template::from_block(move |style| {
@@ -220,16 +220,16 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
for child in &list {
match child {
Child::Spacing(v) => {
- children.push(StackChild::Spacing(*v));
+ children.push(StackChild::spacing(*v, dir.axis()));
delayed = None;
}
Child::Any(template) => {
if let Some(v) = delayed {
- children.push(StackChild::Spacing(v));
+ children.push(StackChild::spacing(v, dir.axis()));
}
- let node = template.to_stack(style).into();
- children.push(StackChild::Any(node, style.aligns.block));
+ let node = template.to_stack(style);
+ children.push(StackChild::new(node, style.aligns.block));
delayed = spacing;
}
}