summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-08-16 11:18:45 +0200
committerGitHub <noreply@github.com>2024-08-16 09:18:45 +0000
commitfeb0c913954d385a3f906f9d9d8ec33cbcf2b2d0 (patch)
treedb593c2523c5333512aff7981cbc7dd70af14021 /crates
parent4853726e5b55ea5861566e850db3e6260879d9f8 (diff)
Move paragraph widow and orphan prevention into flow (#4767)
Diffstat (limited to 'crates')
-rw-r--r--crates/typst/src/layout/flow.rs44
-rw-r--r--crates/typst/src/layout/inline/collect.rs6
-rw-r--r--crates/typst/src/layout/inline/finalize.rs36
-rw-r--r--crates/typst/src/layout/inline/prepare.rs3
4 files changed, 48 insertions, 41 deletions
diff --git a/crates/typst/src/layout/flow.rs b/crates/typst/src/layout/flow.rs
index a1f1402e..e15159e1 100644
--- a/crates/typst/src/layout/flow.rs
+++ b/crates/typst/src/layout/flow.rs
@@ -14,10 +14,12 @@ use crate::foundations::{
use crate::introspection::{Locator, SplitLocator, Tag, TagElem};
use crate::layout::{
Abs, AlignElem, Axes, BlockElem, ColbreakElem, FixedAlignment, FlushElem, Fr,
- Fragment, Frame, FrameItem, PlaceElem, Point, Regions, Rel, Size, Spacing, VElem,
+ Fragment, Frame, FrameItem, PlaceElem, Point, Ratio, Regions, Rel, Size, Spacing,
+ VElem,
};
use crate::model::{FootnoteElem, FootnoteEntry, ParElem};
use crate::realize::StyleVec;
+use crate::text::TextElem;
use crate::utils::Numeric;
/// Arranges spacing, paragraphs and block-level elements into a flow.
@@ -278,6 +280,7 @@ impl<'a, 'e> FlowLayouter<'a, 'e> {
// Fetch properties.
let align = AlignElem::alignment_in(styles).resolve(styles);
let leading = ParElem::leading_in(styles);
+ let costs = TextElem::costs_in(styles);
// Layout the paragraph into lines. This only depends on the base size,
// not on the Y position.
@@ -305,12 +308,51 @@ impl<'a, 'e> FlowLayouter<'a, 'e> {
}
}
+ // Determine whether to prevent widow and orphans.
+ let len = lines.len();
+ let prevent_orphans =
+ costs.orphan() > Ratio::zero() && len >= 2 && !lines[1].is_empty();
+ let prevent_widows =
+ costs.widow() > Ratio::zero() && len >= 2 && !lines[len - 2].is_empty();
+ let prevent_all = len == 3 && prevent_orphans && prevent_widows;
+
+ // Store the heights of lines at the edges because we'll potentially
+ // need these later when `lines` is already moved.
+ let height_at = |i| lines.get(i).map(Frame::height).unwrap_or_default();
+ let front_1 = height_at(0);
+ let front_2 = height_at(1);
+ let back_2 = height_at(len.saturating_sub(2));
+ let back_1 = height_at(len.saturating_sub(1));
+
// Layout the lines.
for (i, mut frame) in lines.into_iter().enumerate() {
if i > 0 {
self.handle_item(FlowItem::Absolute(leading, true))?;
}
+ // To prevent widows and orphans, we require enough space for
+ // - all lines if it's just three
+ // - the first two lines if we're at the first line
+ // - the last two lines if we're at the second to last line
+ let needed = if prevent_all && i == 0 {
+ front_1 + leading + front_2 + leading + back_1
+ } else if prevent_orphans && i == 0 {
+ front_1 + leading + front_2
+ } else if prevent_widows && i >= 2 && i + 2 == len {
+ back_2 + leading + back_1
+ } else {
+ frame.height()
+ };
+
+ // If the line(s) don't fit into this region, but they do fit into
+ // the next, then advance.
+ if !self.regions.in_last()
+ && !self.regions.size.y.fits(needed)
+ && self.regions.iter().nth(1).is_some_and(|region| region.y.fits(needed))
+ {
+ self.finish_region(false)?;
+ }
+
self.drain_tag(&mut frame);
self.handle_item(FlowItem::Frame {
frame,
diff --git a/crates/typst/src/layout/inline/collect.rs b/crates/typst/src/layout/inline/collect.rs
index b6a847f5..5021dc55 100644
--- a/crates/typst/src/layout/inline/collect.rs
+++ b/crates/typst/src/layout/inline/collect.rs
@@ -128,11 +128,11 @@ pub fn collect<'a>(
let mut iter = children.chain(styles).peekable();
let mut locator = locator.split();
+ let outer_dir = TextElem::dir_in(*styles);
let first_line_indent = ParElem::first_line_indent_in(*styles);
if !first_line_indent.is_zero()
&& consecutive
- && AlignElem::alignment_in(*styles).resolve(*styles).x
- == TextElem::dir_in(*styles).start().into()
+ && AlignElem::alignment_in(*styles).resolve(*styles).x == outer_dir.start().into()
{
collector.push_item(Item::Absolute(first_line_indent.resolve(*styles), false));
collector.spans.push(1, Span::detached());
@@ -144,8 +144,6 @@ pub fn collect<'a>(
collector.spans.push(1, Span::detached());
}
- let outer_dir = TextElem::dir_in(*styles);
-
while let Some((child, styles)) = iter.next() {
let prev_len = collector.full.len();
diff --git a/crates/typst/src/layout/inline/finalize.rs b/crates/typst/src/layout/inline/finalize.rs
index c8ba4729..03493af5 100644
--- a/crates/typst/src/layout/inline/finalize.rs
+++ b/crates/typst/src/layout/inline/finalize.rs
@@ -1,5 +1,4 @@
use super::*;
-use crate::layout::{Abs, Frame, Point};
use crate::utils::Numeric;
/// Turns the selected lines into frames.
@@ -26,38 +25,9 @@ pub fn finalize(
// Stack the lines into one frame per region.
let shrink = ParElem::shrink_in(styles);
- let mut frames: Vec<Frame> = lines
+ lines
.iter()
.map(|line| commit(engine, p, line, width, region.y, shrink))
- .collect::<SourceResult<_>>()?;
-
- // Positive ratios enable prevention, while zero and negative ratios disable
- // it.
- if p.costs.orphan().get() > 0.0 {
- // Prevent orphans.
- if frames.len() >= 2 && !frames[1].is_empty() {
- let second = frames.remove(1);
- let first = &mut frames[0];
- merge(first, second, p.leading);
- }
- }
- if p.costs.widow().get() > 0.0 {
- // 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, p.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;
+ .collect::<SourceResult<_>>()
+ .map(Fragment::frames)
}
diff --git a/crates/typst/src/layout/inline/prepare.rs b/crates/typst/src/layout/inline/prepare.rs
index 59682b2c..9e73af66 100644
--- a/crates/typst/src/layout/inline/prepare.rs
+++ b/crates/typst/src/layout/inline/prepare.rs
@@ -43,8 +43,6 @@ pub struct Preparation<'a> {
pub cjk_latin_spacing: bool,
/// Whether font fallback is enabled for this paragraph.
pub fallback: bool,
- /// The leading of the paragraph.
- pub leading: Abs,
/// How to determine line breaks.
pub linebreaks: Smart<Linebreaks>,
/// The text size.
@@ -136,7 +134,6 @@ pub fn prepare<'a>(
hang: ParElem::hanging_indent_in(styles),
cjk_latin_spacing,
fallback: TextElem::fallback_in(styles),
- leading: ParElem::leading_in(styles),
linebreaks: ParElem::linebreaks_in(styles),
size: TextElem::size_in(styles),
})