summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-03-28 19:03:04 +0200
committerLaurenz <laurmaedje@gmail.com>2021-03-29 15:02:15 +0200
commit309b8f20a8530b1616c3bfe71329ca883f3a597b (patch)
tree862708739f44a6b1f487d00c6d7a1b2fce151952 /src
parent8c27dc101043a4a24132bd73ad39a592f9c2b2ad (diff)
Line break iterating run shaper 🌵
Co-Authored-By: Martin <mhaug@live.de>
Diffstat (limited to 'src')
-rw-r--r--src/layout/par.rs70
1 files changed, 56 insertions, 14 deletions
diff --git a/src/layout/par.rs b/src/layout/par.rs
index 4077e74b..14fb842a 100644
--- a/src/layout/par.rs
+++ b/src/layout/par.rs
@@ -58,10 +58,7 @@ impl Layout for ParNode {
for child in &self.children {
match *child {
ParChild::Spacing(amount) => layouter.push_spacing(amount),
- ParChild::Text(ref node, align) => {
- let frame = shape(&node.text, &mut ctx.env.fonts, &node.props);
- layouter.push_frame(frame, align);
- }
+ ParChild::Text(ref node, align) => layouter.push_text(ctx, node, align),
ParChild::Any(ref node, align) => {
for frame in node.layout(ctx, &layouter.areas) {
layouter.push_frame(frame, align);
@@ -115,6 +112,51 @@ impl ParLayouter {
self.line_size.cross = (self.line_size.cross + amount).min(cross_max);
}
+ fn push_text(&mut self, ctx: &mut LayoutContext, node: &TextNode, align: Align) {
+ // Text shaped up to the previous line break opportunity that can fit
+ // the current line.
+ let mut last = None;
+
+ // Position in the text at which the current line starts.
+ let mut start = 0;
+
+ let iter = xi_unicode::LineBreakIterator::new(&node.text);
+ for (pos, mandatory) in iter {
+ while start != pos {
+ let part = &node.text[start .. pos].trim_end();
+ let frame = shape(part, &mut ctx.env.fonts, &node.props);
+
+ if self.usable().fits(frame.size) {
+ if mandatory {
+ // We have to break here.
+ self.push_frame(frame, align);
+ self.finish_line();
+ start = pos;
+ last = None;
+ } else {
+ // Still fits into the line.
+ last = Some((frame, pos));
+ }
+ } else {
+ // Doesn't fit into the line. If `last` was `None`, the single
+ // unbreakable piece of text didn't fit, but we can't break it
+ // up further, so we just have to push it.
+ let (frame, pos) = last.take().unwrap_or((frame, pos));
+ self.push_frame(frame, align);
+ self.finish_line();
+ start = pos;
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ if let Some((frame, _)) = last {
+ self.push_frame(frame, align);
+ }
+ }
+
fn push_frame(&mut self, frame: Frame, align: Align) {
// When the alignment of the last pushed frame (stored in the "ruler")
// is further to the end than the new `frame`, we need a line break.
@@ -133,16 +175,7 @@ impl ParLayouter {
}
// Find out whether the area still has enough space for this frame.
- // Space occupied by previous lines is already removed from
- // `areas.current`, but the cross-extent of the current line needs to be
- // subtracted to make sure the frame fits.
- let fits = {
- let mut usable = self.areas.current;
- *usable.get_mut(self.cross) -= self.line_size.cross;
- usable.fits(frame.size)
- };
-
- if !fits {
+ if !self.usable().fits(frame.size) {
self.finish_line();
// Here, we can directly check whether the frame fits into
@@ -168,6 +201,15 @@ impl ParLayouter {
self.line_ruler = align;
}
+ fn usable(&self) -> Size {
+ // Space occupied by previous lines is already removed from
+ // `areas.current`, but the cross-extent of the current line needs to be
+ // subtracted to make sure the frame fits.
+ let mut usable = self.areas.current;
+ *usable.get_mut(self.cross) -= self.line_size.cross;
+ usable
+ }
+
fn finish_line(&mut self) {
let full_size = {
let expand = self.areas.expand.get(self.cross);