Line break iterating run shaper 🌵

Co-Authored-By: Martin <mhaug@live.de>
This commit is contained in:
Laurenz 2021-03-28 19:03:04 +02:00
parent 8c27dc1010
commit 309b8f20a8
2 changed files with 57 additions and 14 deletions

View File

@ -29,6 +29,7 @@ pdf-writer = { path = "../pdf-writer" }
rustybuzz = "0.3"
ttf-parser = "0.9"
unicode-xid = "0.2"
xi-unicode = "0.3"
anyhow = { version = "1", optional = true }
serde = { version = "1", features = ["derive"], optional = true }

View File

@ -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);