summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--library/src/layout/flow.rs131
-rw-r--r--library/src/layout/mod.rs26
-rw-r--r--library/src/layout/par.rs31
-rw-r--r--src/doc.rs1
-rw-r--r--tests/ref/bugs/columns-1.pngbin0 -> 1322 bytes
-rw-r--r--tests/ref/bugs/flow-1.pngbin0 -> 9726 bytes
-rw-r--r--tests/ref/bugs/flow-2.pngbin0 -> 5600 bytes
-rw-r--r--tests/ref/bugs/flow-3.pngbin0 -> 1600 bytes
-rw-r--r--tests/ref/layout/grid-3.pngbin44418 -> 44308 bytes
-rw-r--r--tests/ref/layout/grid-5.pngbin3699 -> 3673 bytes
-rw-r--r--tests/typ/bugs/columns-1.typ12
-rw-r--r--tests/typ/bugs/flow-1.typ11
-rw-r--r--tests/typ/bugs/flow-2.typ10
-rw-r--r--tests/typ/bugs/flow-3.typ12
-rw-r--r--tests/typ/layout/flow-orphan.typ2
-rw-r--r--tests/typ/layout/grid-5.typ1
16 files changed, 162 insertions, 75 deletions
diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs
index 4a8bed85..61188f9c 100644
--- a/library/src/layout/flow.rs
+++ b/library/src/layout/flow.rs
@@ -9,7 +9,7 @@ use crate::prelude::*;
/// the contents of boxes.
#[capable(Layout)]
#[derive(Hash)]
-pub struct FlowNode(pub StyleVec<Content>, pub bool);
+pub struct FlowNode(pub StyleVec<Content>);
#[node]
impl FlowNode {}
@@ -21,12 +21,12 @@ impl Layout for FlowNode {
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
- let mut layouter = FlowLayouter::new(regions, self.1);
+ let mut layouter = FlowLayouter::new(regions);
for (child, map) in self.0.iter() {
let styles = styles.chain(&map);
if let Some(&node) = child.to::<VNode>() {
- layouter.layout_spacing(node.amount, styles);
+ layouter.layout_spacing(node, styles);
} else if let Some(node) = child.to::<ParNode>() {
let barrier = Style::Barrier(child.id());
let styles = styles.chain_one(&barrier);
@@ -34,7 +34,7 @@ impl Layout for FlowNode {
} else if child.has::<dyn Layout>() {
layouter.layout_block(vt, child, styles)?;
} else if child.is::<ColbreakNode>() {
- layouter.finish_region(false);
+ layouter.finish_region();
} else {
panic!("unexpected flow child: {child:?}");
}
@@ -53,8 +53,6 @@ impl Debug for FlowNode {
/// Performs flow layout.
struct FlowLayouter<'a> {
- /// Whether this is a root page-level flow.
- root: bool,
/// The regions to layout children into.
regions: Regions<'a>,
/// Whether the flow should expand to fill the region.
@@ -73,13 +71,11 @@ struct FlowLayouter<'a> {
/// A prepared item in a flow layout.
#[derive(Debug)]
enum FlowItem {
- /// Absolute spacing between other items.
- Absolute(Abs),
- /// Leading between paragraph lines.
- Leading(Abs),
+ /// Spacing between other items and whether it is weak.
+ Absolute(Abs, bool),
/// Fractional spacing between other items.
Fractional(Fr),
- /// A frame for a layouted block and how to align it.
+ /// A frame for a layouted block, how to align it, and whether it is sticky.
Frame(Frame, Axes<Align>, bool),
/// An absolutely placed frame.
Placed(Frame),
@@ -87,7 +83,7 @@ enum FlowItem {
impl<'a> FlowLayouter<'a> {
/// Create a new flow layouter.
- fn new(mut regions: Regions<'a>, root: bool) -> Self {
+ fn new(mut regions: Regions<'a>) -> Self {
let expand = regions.expand;
let full = regions.first;
@@ -95,7 +91,6 @@ impl<'a> FlowLayouter<'a> {
regions.expand.y = false;
Self {
- root,
regions,
expand,
full,
@@ -106,11 +101,12 @@ impl<'a> FlowLayouter<'a> {
}
/// Layout vertical spacing.
- fn layout_spacing(&mut self, spacing: Spacing, styles: StyleChain) {
- self.layout_item(match spacing {
- Spacing::Relative(v) => {
- FlowItem::Absolute(v.resolve(styles).relative_to(self.full.y))
- }
+ fn layout_spacing(&mut self, node: VNode, styles: StyleChain) {
+ self.layout_item(match node.amount {
+ Spacing::Relative(v) => FlowItem::Absolute(
+ v.resolve(styles).relative_to(self.full.y),
+ node.weakness > 0,
+ ),
Spacing::Fractional(v) => FlowItem::Fractional(v),
});
}
@@ -125,25 +121,42 @@ impl<'a> FlowLayouter<'a> {
let aligns = styles.get(AlignNode::ALIGNS).resolve(styles);
let leading = styles.get(ParNode::LEADING);
let consecutive = self.last_was_par;
- let fragment = par.layout(
- vt,
- styles,
- consecutive,
- self.regions.first.x,
- self.regions.base,
- self.regions.expand.x,
- )?;
-
- let len = fragment.len();
- for (i, frame) in fragment.into_iter().enumerate() {
+ let frames = par
+ .layout(
+ vt,
+ styles,
+ consecutive,
+ self.regions.first.x,
+ self.regions.base,
+ self.regions.expand.x,
+ )?
+ .into_frames();
+
+ let mut sticky = self.items.len();
+ for (i, item) in self.items.iter().enumerate().rev() {
+ match *item {
+ FlowItem::Absolute(_, _) => {}
+ FlowItem::Frame(.., true) => sticky = i,
+ _ => break,
+ }
+ }
+
+ if let [first, ..] = frames.as_slice() {
+ if !self.regions.first.y.fits(first.height()) && !self.regions.in_last() {
+ let carry: Vec<_> = self.items.drain(sticky..).collect();
+ self.finish_region();
+ for item in carry {
+ self.layout_item(item);
+ }
+ }
+ }
+
+ for (i, frame) in frames.into_iter().enumerate() {
if i > 0 {
- self.layout_item(FlowItem::Leading(leading));
+ self.layout_item(FlowItem::Absolute(leading, true));
}
- // Prevent widows and orphans.
- let border = (i == 0 && len >= 2) || i + 2 == len;
- let sticky = self.root && !frame.is_empty() && border;
- self.layout_item(FlowItem::Frame(frame, aligns, sticky));
+ self.layout_item(FlowItem::Frame(frame, aligns, false));
}
self.last_was_par = true;
@@ -186,15 +199,12 @@ impl<'a> FlowLayouter<'a> {
/// Layout a finished frame.
fn layout_item(&mut self, item: FlowItem) {
match item {
- FlowItem::Absolute(v) | FlowItem::Leading(v) => self.regions.first.y -= v,
+ FlowItem::Absolute(v, _) => self.regions.first.y -= v,
FlowItem::Fractional(_) => {}
FlowItem::Frame(ref frame, ..) => {
let size = frame.size();
- if !self.regions.first.y.fits(size.y)
- && !self.regions.in_last()
- && self.items.iter().any(|item| !matches!(item, FlowItem::Leading(_)))
- {
- self.finish_region(true);
+ if !self.regions.first.y.fits(size.y) && !self.regions.in_last() {
+ self.finish_region();
}
self.regions.first.y -= size.y;
@@ -206,34 +216,22 @@ impl<'a> FlowLayouter<'a> {
}
/// Finish the frame for one region.
- fn finish_region(&mut self, something_follows: bool) {
- let mut end = self.items.len();
- if something_follows {
- for (i, item) in self.items.iter().enumerate().rev() {
- match *item {
- FlowItem::Absolute(_)
- | FlowItem::Leading(_)
- | FlowItem::Fractional(_) => {}
- FlowItem::Frame(.., true) => end = i,
- _ => break,
- }
- }
- if end == 0 {
- return;
- }
- }
-
- let carry: Vec<_> = self.items.drain(end..).collect();
-
- while let Some(FlowItem::Leading(_)) = self.items.last() {
+ fn finish_region(&mut self) {
+ // Trim weak spacing.
+ while self
+ .items
+ .last()
+ .map_or(false, |item| matches!(item, FlowItem::Absolute(_, true)))
+ {
self.items.pop();
}
+ // Determine the used size.
let mut fr = Fr::zero();
let mut used = Size::zero();
for item in &self.items {
match *item {
- FlowItem::Absolute(v) | FlowItem::Leading(v) => used.y += v,
+ FlowItem::Absolute(v, _) => used.y += v,
FlowItem::Fractional(v) => fr += v,
FlowItem::Frame(ref frame, ..) => {
let size = frame.size();
@@ -247,6 +245,7 @@ impl<'a> FlowLayouter<'a> {
// Determine the size of the flow in this region depending on whether
// the region expands.
let mut size = self.expand.select(self.full, used);
+ size.y.set_min(self.full.y);
// Account for fractional spacing in the size calculation.
let remaining = self.full.y - used.y;
@@ -262,7 +261,7 @@ impl<'a> FlowLayouter<'a> {
// Place all frames.
for item in self.items.drain(..) {
match item {
- FlowItem::Absolute(v) | FlowItem::Leading(v) => {
+ FlowItem::Absolute(v, _) => {
offset += v;
}
FlowItem::Fractional(v) => {
@@ -286,21 +285,17 @@ impl<'a> FlowLayouter<'a> {
self.finished.push(output);
self.regions.next();
self.full = self.regions.first;
-
- for item in carry {
- self.layout_item(item);
- }
}
/// Finish layouting and return the resulting fragment.
fn finish(mut self) -> Fragment {
if self.expand.y {
while !self.regions.backlog.is_empty() {
- self.finish_region(false);
+ self.finish_region();
}
}
- self.finish_region(false);
+ self.finish_region();
Fragment::frames(self.finished)
}
}
diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs
index 61fcfd44..7f395545 100644
--- a/library/src/layout/mod.rs
+++ b/library/src/layout/mod.rs
@@ -148,7 +148,7 @@ impl Layout for Content {
pub trait Inline: Layout {}
/// A sequence of regions to layout into.
-#[derive(Debug, Copy, Clone, Hash)]
+#[derive(Copy, Clone, Hash)]
pub struct Regions<'a> {
/// The (remaining) size of the first region.
pub first: Size,
@@ -247,6 +247,26 @@ impl<'a> Regions<'a> {
}
}
+impl Debug for Regions<'_> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ f.write_str("Regions ")?;
+ let mut list = f.debug_list();
+ let mut prev = self.first.y;
+ list.entry(&self.first);
+ for &height in self.backlog {
+ list.entry(&Size::new(self.first.x, height));
+ prev = height;
+ }
+ if let Some(last) = self.last {
+ if last != prev {
+ list.entry(&Size::new(self.first.x, last));
+ }
+ list.entry(&(..));
+ }
+ list.finish()
+ }
+}
+
/// Realize into a node that is capable of root-level layout.
fn realize_root<'a>(
vt: &mut Vt,
@@ -280,7 +300,7 @@ fn realize_block<'a>(
builder.accept(content, styles)?;
builder.interrupt_par()?;
let (children, shared) = builder.flow.0.finish();
- Ok((FlowNode(children, false).pack(), shared))
+ Ok((FlowNode(children).pack(), shared))
}
/// Builds a document or a flow node from content.
@@ -468,7 +488,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
let (flow, shared) = mem::take(&mut self.flow).0.finish();
let styles =
if shared == StyleChain::default() { styles.unwrap() } else { shared };
- let page = PageNode(FlowNode(flow, true).pack()).pack();
+ let page = PageNode(FlowNode(flow).pack()).pack();
let stored = self.scratch.content.alloc(page);
self.accept(stored, styles)?;
}
diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs
index a629853a..dbf2c936 100644
--- a/library/src/layout/par.rs
+++ b/library/src/layout/par.rs
@@ -1127,11 +1127,36 @@ fn finalize(
}
// Stack the lines into one frame per region.
- lines
+ let mut frames: Vec<Frame> = lines
.iter()
.map(|line| commit(vt, p, line, base, width))
- .collect::<SourceResult<_>>()
- .map(Fragment::frames)
+ .collect::<SourceResult<_>>()?;
+
+ // Prevent orphans.
+ let leading = p.styles.get(ParNode::LEADING);
+ if frames.len() >= 2 && !frames[1].is_empty() {
+ let second = frames.remove(1);
+ let first = &mut frames[0];
+ merge(first, second, leading);
+ }
+
+ // Prevent widows.
+ let len = frames.len();
+ if len >= 2 && !frames[len - 2].is_empty() {
+ let second = frames.pop().unwrap();
+ let first = frames.last_mut().unwrap();
+ merge(first, second, leading);
+ }
+
+ Ok(Fragment::frames(frames))
+}
+
+/// Merge two line frames
+fn merge(first: &mut Frame, second: Frame, leading: Abs) {
+ let offset = first.height() + leading;
+ let total = offset + second.height();
+ first.push_frame(Point::with_y(offset), second);
+ first.size_mut().y = total;
}
/// Commit to a line and build its frame.
diff --git a/src/doc.rs b/src/doc.rs
index d399162f..988520c5 100644
--- a/src/doc.rs
+++ b/src/doc.rs
@@ -347,6 +347,7 @@ impl Frame {
impl Debug for Frame {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.write_str("Frame ")?;
f.debug_list()
.entries(self.elements.iter().map(|(_, element)| element))
.finish()
diff --git a/tests/ref/bugs/columns-1.png b/tests/ref/bugs/columns-1.png
new file mode 100644
index 00000000..ecb3d417
--- /dev/null
+++ b/tests/ref/bugs/columns-1.png
Binary files differ
diff --git a/tests/ref/bugs/flow-1.png b/tests/ref/bugs/flow-1.png
new file mode 100644
index 00000000..2c5013c4
--- /dev/null
+++ b/tests/ref/bugs/flow-1.png
Binary files differ
diff --git a/tests/ref/bugs/flow-2.png b/tests/ref/bugs/flow-2.png
new file mode 100644
index 00000000..7661cf8f
--- /dev/null
+++ b/tests/ref/bugs/flow-2.png
Binary files differ
diff --git a/tests/ref/bugs/flow-3.png b/tests/ref/bugs/flow-3.png
new file mode 100644
index 00000000..e12d5e12
--- /dev/null
+++ b/tests/ref/bugs/flow-3.png
Binary files differ
diff --git a/tests/ref/layout/grid-3.png b/tests/ref/layout/grid-3.png
index 1bb76292..d9562e9a 100644
--- a/tests/ref/layout/grid-3.png
+++ b/tests/ref/layout/grid-3.png
Binary files differ
diff --git a/tests/ref/layout/grid-5.png b/tests/ref/layout/grid-5.png
index 532700b1..2e9d1705 100644
--- a/tests/ref/layout/grid-5.png
+++ b/tests/ref/layout/grid-5.png
Binary files differ
diff --git a/tests/typ/bugs/columns-1.typ b/tests/typ/bugs/columns-1.typ
new file mode 100644
index 00000000..96a4d0e5
--- /dev/null
+++ b/tests/typ/bugs/columns-1.typ
@@ -0,0 +1,12 @@
+// The well-known columns bug.
+
+---
+#set page(height: 70pt)
+
+Hallo
+#columns(2)[
+ = A
+ Text
+ = B
+ Text
+]
diff --git a/tests/typ/bugs/flow-1.typ b/tests/typ/bugs/flow-1.typ
new file mode 100644
index 00000000..425a0ce8
--- /dev/null
+++ b/tests/typ/bugs/flow-1.typ
@@ -0,0 +1,11 @@
+// In this bug, the first line of the second paragraph was on its page alone an
+// the rest moved down. The reason was that the second block resulted in
+// overlarge frames because the region wasn't finished properly.
+
+---
+#set page(height: 70pt)
+#block[This file tests a bug where an almost empty page occurs.]
+#block[
+ The text in this second block was torn apart and split up for
+ some reason beyond my knowledge.
+]
diff --git a/tests/typ/bugs/flow-2.typ b/tests/typ/bugs/flow-2.typ
new file mode 100644
index 00000000..5ffffd58
--- /dev/null
+++ b/tests/typ/bugs/flow-2.typ
@@ -0,0 +1,10 @@
+// In this bug, the first part of the paragraph moved down to the second page
+// because trailing leading wasn't trimmed, resulting in an overlarge frame.
+
+---
+#set page(height: 60pt)
+#v(19pt)
+#block[
+ But, soft! what light through yonder window breaks?
+ It is the east, and Juliet is the sun.
+]
diff --git a/tests/typ/bugs/flow-3.typ b/tests/typ/bugs/flow-3.typ
new file mode 100644
index 00000000..71af1914
--- /dev/null
+++ b/tests/typ/bugs/flow-3.typ
@@ -0,0 +1,12 @@
+// In this bug, there was a bit of space below the heading because weak spacing
+// directly before a layout-induced column or page break wasn't trimmed.
+
+---
+#set page(height: 60pt)
+#rect(inset: 0pt, columns(2)[
+ Text
+ #v(12pt)
+ Hi
+ #v(10pt, weak: true)
+ At column break.
+])
diff --git a/tests/typ/layout/flow-orphan.typ b/tests/typ/layout/flow-orphan.typ
index a51da2b2..482fd145 100644
--- a/tests/typ/layout/flow-orphan.typ
+++ b/tests/typ/layout/flow-orphan.typ
@@ -1,4 +1,4 @@
-// Test that a heading doesn't become an orphan.
+// Test that lines and headings doesn't become orphan.
---
#set page(height: 100pt)
diff --git a/tests/typ/layout/grid-5.typ b/tests/typ/layout/grid-5.typ
index db7c525a..64385f61 100644
--- a/tests/typ/layout/grid-5.typ
+++ b/tests/typ/layout/grid-5.typ
@@ -22,6 +22,7 @@
#align(bottom)[
Bottom \
Bottom \
+ #v(0pt)
Top
]
],