mirror of
https://github.com/typst/typst
synced 2025-05-19 11:35:27 +08:00
Line break iterating run shaper 🌵
Co-Authored-By: Martin <mhaug@live.de>
This commit is contained in:
parent
8c27dc1010
commit
309b8f20a8
@ -29,6 +29,7 @@ pdf-writer = { path = "../pdf-writer" }
|
|||||||
rustybuzz = "0.3"
|
rustybuzz = "0.3"
|
||||||
ttf-parser = "0.9"
|
ttf-parser = "0.9"
|
||||||
unicode-xid = "0.2"
|
unicode-xid = "0.2"
|
||||||
|
xi-unicode = "0.3"
|
||||||
anyhow = { version = "1", optional = true }
|
anyhow = { version = "1", optional = true }
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|
||||||
|
@ -58,10 +58,7 @@ impl Layout for ParNode {
|
|||||||
for child in &self.children {
|
for child in &self.children {
|
||||||
match *child {
|
match *child {
|
||||||
ParChild::Spacing(amount) => layouter.push_spacing(amount),
|
ParChild::Spacing(amount) => layouter.push_spacing(amount),
|
||||||
ParChild::Text(ref node, align) => {
|
ParChild::Text(ref node, align) => layouter.push_text(ctx, node, align),
|
||||||
let frame = shape(&node.text, &mut ctx.env.fonts, &node.props);
|
|
||||||
layouter.push_frame(frame, align);
|
|
||||||
}
|
|
||||||
ParChild::Any(ref node, align) => {
|
ParChild::Any(ref node, align) => {
|
||||||
for frame in node.layout(ctx, &layouter.areas) {
|
for frame in node.layout(ctx, &layouter.areas) {
|
||||||
layouter.push_frame(frame, align);
|
layouter.push_frame(frame, align);
|
||||||
@ -115,6 +112,51 @@ impl ParLayouter {
|
|||||||
self.line_size.cross = (self.line_size.cross + amount).min(cross_max);
|
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) {
|
fn push_frame(&mut self, frame: Frame, align: Align) {
|
||||||
// When the alignment of the last pushed frame (stored in the "ruler")
|
// 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.
|
// 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.
|
// Find out whether the area still has enough space for this frame.
|
||||||
// Space occupied by previous lines is already removed from
|
if !self.usable().fits(frame.size) {
|
||||||
// `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 {
|
|
||||||
self.finish_line();
|
self.finish_line();
|
||||||
|
|
||||||
// Here, we can directly check whether the frame fits into
|
// Here, we can directly check whether the frame fits into
|
||||||
@ -168,6 +201,15 @@ impl ParLayouter {
|
|||||||
self.line_ruler = align;
|
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) {
|
fn finish_line(&mut self) {
|
||||||
let full_size = {
|
let full_size = {
|
||||||
let expand = self.areas.expand.get(self.cross);
|
let expand = self.areas.expand.get(self.cross);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user