Skip to content

Commit

Permalink
Misc. inline box layout fixes (#207)
Browse files Browse the repository at this point in the history
See individual commits:

- Fix whitespace collapsing when first item in a span is an inline box
- `<p><img src="..." /> label</p>` *should* have a space between the
image and "label".
- Fix wrapping when layout contains only inline boxes
- Test case for layout with only inline boxes
- Prevent inline boxes from being duplicated when the line ends with an
inline box.
- Fix trailing whitespace computation:
- If the last item on a line is an inline box, then it does not have any
trailing whitespace.

---------

Signed-off-by: Nico Burns <[email protected]>
  • Loading branch information
nicoburns authored Dec 18, 2024
1 parent 3a7ce13 commit 93dccce
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 15 deletions.
1 change: 1 addition & 0 deletions parley/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ impl<B: Brush> TreeBuilder<'_, B> {

pub fn push_inline_box(&mut self, mut inline_box: InlineBox) {
self.lcx.tree_style_builder.push_uncommitted_text(false);
self.lcx.tree_style_builder.set_is_span_first(false);
// TODO: arrange type better here to factor out the index
inline_box.index = self.lcx.tree_style_builder.current_text_len();
self.lcx.inline_boxes.push(inline_box);
Expand Down
43 changes: 28 additions & 15 deletions parley/src/layout/line/greedy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,12 @@ impl<'a, B: Brush> BreakLines<'a, B> {

// HACK: ignore max_advance for empty layouts
// Prevents crash when width is too small (https://github.com/linebender/parley/issues/186)
let max_advance = if self.layout.data.text_len == 0 {
f32::MAX
} else {
max_advance
};
let max_advance =
if self.layout.data.text_len == 0 && self.layout.data.inline_boxes.is_empty() {
f32::MAX
} else {
max_advance
};

// This macro simply calls the `commit_line` with the provided arguments and some parts of self.
// It exists solely to cut down on the boilerplate for accessing the self variables while
Expand Down Expand Up @@ -528,14 +529,10 @@ impl<'a, B: Brush> BreakLines<'a, B> {
line.metrics.trailing_whitespace = 0.0;
if !line.item_range.is_empty() {
// Note: there may not be a "last run" if there are no runs in the line
let last_run = &self
.lines
.line_items
.iter()
.rfind(|item| item.is_text_run());
if let Some(last_run) = last_run {
if !last_run.cluster_range.is_empty() {
let cluster = &self.layout.data.clusters[last_run.cluster_range.end - 1];
let last_item = &self.lines.line_items.last();
if let Some(last_item) = last_item {
if last_item.is_text_run() && !last_item.cluster_range.is_empty() {
let cluster = &self.layout.data.clusters[last_item.cluster_range.end - 1];
if cluster.info.whitespace().is_space_or_nbsp() {
line.metrics.trailing_whitespace = cluster.advance;
}
Expand Down Expand Up @@ -726,6 +723,7 @@ fn try_commit_line<B: Brush>(

// Iterate over the items to commit
// println!("\nCOMMIT LINE");
let mut last_item_kind = LayoutItemKind::TextRun;
for (i, item) in items_to_commit.iter().enumerate() {
// println!("i = {} index = {} {:?}", i, item.index, item.kind);

Expand All @@ -745,6 +743,8 @@ fn try_commit_line<B: Brush>(
cluster_range: 0..0,
text_range: 0..0,
});

last_item_kind = item.kind;
}
LayoutItemKind::TextRun => {
let run_data = &layout.data.runs[item.index];
Expand All @@ -768,6 +768,8 @@ fn try_commit_line<B: Brush>(
continue;
}

last_item_kind = item.kind;

// Push run to line
let run = Run::new(layout, 0, 0, run_data, None);
let text_range = if run_data.cluster_range.is_empty() {
Expand Down Expand Up @@ -825,10 +827,21 @@ fn try_commit_line<B: Brush>(
});

// Reset state for the new line
state.num_spaces = 0;
state.clusters.start = state.clusters.end;
state.clusters.end += 1;
state.items.start = state.items.end.saturating_sub(1);
state.num_spaces = 0;

state.items.start = match last_item_kind {
// For text runs, the first item of line N+1 needs to be the SAME as
// the last item for line N. This is because the item (if it a text run
// may be split across the two lines with some clusters in line N and some
// in line N+1). The item is later filtered out (see `continue` in loop above)
// if there are not actually any clusters in line N+1.
LayoutItemKind::TextRun => state.items.end.saturating_sub(1),
// Inline boxes cannot be spread across multiple lines, so we should set
// the first item of line N+1 to be the item AFTER the last item in line N.
LayoutItemKind::InlineBox => state.items.end,
};

true
}
Expand Down
5 changes: 5 additions & 0 deletions parley/src/resolve/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ impl<B: Brush> TreeStyleBuilder<B> {
self.white_space_collapse = white_space_collapse;
}

pub(crate) fn set_is_span_first(&mut self, is_span_first: bool) {
self.is_span_first = is_span_first;
}

pub(crate) fn push_uncommitted_text(&mut self, is_span_last: bool) {
let span_text: Cow<'_, str> = match self.white_space_collapse {
WhiteSpaceCollapse::Preserve => Cow::from(&self.uncommitted_text),
Expand Down Expand Up @@ -109,6 +113,7 @@ impl<B: Brush> TreeStyleBuilder<B> {

// Nothing to do if there is no uncommitted text.
if span_text.is_empty() {
self.uncommitted_text.clear();
return;
}

Expand Down
21 changes: 21 additions & 0 deletions parley/src/tests/test_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,24 @@ fn placing_inboxes() {
env.with_name(test_case_name).check_layout_snapshot(&layout);
}
}

#[test]
fn only_inboxes_wrap() {
let mut env = testenv!();

let text = "";
let mut builder = env.builder(text);
for id in 0..10 {
builder.push_inline_box(InlineBox {
id,
index: 0,
width: 10.0,
height: 10.0,
});
}
let mut layout = builder.build(text);
layout.break_all_lines(Some(40.0));
layout.align(None, Alignment::Middle);

env.check_layout_snapshot(&layout);
}
Binary file added parley/tests/snapshots/only_inboxes_wrap-0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 93dccce

Please sign in to comment.