core: Add empty boxes for empty lines *without* breaking multiple-format lines.

This commit is contained in:
David Wendt 2022-10-16 20:32:14 -04:00 committed by kmeisthax
parent 6590f58efe
commit 83db53202e
2 changed files with 67 additions and 17 deletions

View File

@ -217,11 +217,17 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
/// The `final_line_of_para` parameter should be flagged if this the final /// The `final_line_of_para` parameter should be flagged if this the final
/// line in the paragraph or layout operation (e.g. it wasn't caused by an /// line in the paragraph or layout operation (e.g. it wasn't caused by an
/// automatic newline and no more text is to be expected). /// automatic newline and no more text is to be expected).
///
/// The `text` parameter, if provided, consists of the master text slice,
/// the current read index into that slice, and the current text span we
/// are formatting. This is for empty line insertion; omitting this
/// parameter will result in no empty lines being added.
fn fixup_line( fn fixup_line(
&mut self, &mut self,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
only_line: bool, only_line: bool,
final_line_of_para: bool, final_line_of_para: bool,
text: Option<(&'a WStr, usize, &TextSpan)>,
) { ) {
if self.boxes.get_mut(self.current_line..).is_none() { if self.boxes.get_mut(self.current_line..).is_none() {
return; return;
@ -302,6 +308,12 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
box_count += 1; box_count += 1;
} }
if let Some((text, end, span)) = text {
if box_count == 0 {
self.append_text(&text[end..end], end, end, span);
}
}
self.append_underlines(); self.append_underlines();
line_bounds += line_bounds +=
@ -322,8 +334,18 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
/// ///
/// This function will also adjust any layout boxes on the current line to /// This function will also adjust any layout boxes on the current line to
/// their correct alignment and indentation. /// their correct alignment and indentation.
fn explicit_newline(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) { ///
self.fixup_line(context, false, true); /// The `text`, `end`, and `span` parameters are for empty line insertion.
/// `text` should be the text we are laying out, while `end` and `span` are
/// the current positions into the text and format spans we are laying out.
fn explicit_newline(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
text: &'a WStr,
end: usize,
span: &TextSpan,
) {
self.fixup_line(context, false, true, Some((text, end, span)));
self.cursor.set_x(Twips::from_pixels(0.0)); self.cursor.set_x(Twips::from_pixels(0.0));
self.cursor += ( self.cursor += (
@ -340,8 +362,18 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
/// ///
/// This function will also adjust any layout boxes on the current line to /// This function will also adjust any layout boxes on the current line to
/// their correct alignment and indentation. /// their correct alignment and indentation.
fn newline(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) { ///
self.fixup_line(context, false, false); /// The `text`, `end`, and `span` parameters are for empty line insertion.
/// `text` should be the text we are laying out, while `end` and `span` are
/// the current positions into the text and format spans we are laying out.
fn newline(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
text: &'a WStr,
end: usize,
span: &TextSpan,
) {
self.fixup_line(context, false, false, Some((text, end, span)));
self.cursor.set_x(Twips::from_pixels(0.0)); self.cursor.set_x(Twips::from_pixels(0.0));
self.cursor += ( self.cursor += (
@ -537,8 +569,15 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
fn end_layout( fn end_layout(
mut self, mut self,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
fs: &'a FormatSpans,
) -> (Vec<LayoutBox<'gc>>, BoxBounds<Twips>) { ) -> (Vec<LayoutBox<'gc>>, BoxBounds<Twips>) {
self.fixup_line(context, !self.has_line_break, true); self.fixup_line(
context,
!self.has_line_break,
true,
fs.last_span()
.map(|ls| (fs.displayed_text(), fs.displayed_text().len(), ls)),
);
(self.boxes, self.exterior_bounds.unwrap_or_default()) (self.boxes, self.exterior_bounds.unwrap_or_default())
} }
@ -694,7 +733,9 @@ impl<'gc> LayoutBox<'gc> {
}; };
match delimiter { match delimiter {
Some(b'\n' | b'\r') => layout_context.explicit_newline(context), Some(b'\n' | b'\r') => {
layout_context.explicit_newline(context, text, 0, span)
}
Some(b'\t') => layout_context.tab(), Some(b'\t') => layout_context.tab(),
_ => {} _ => {}
} }
@ -713,6 +754,13 @@ impl<'gc> LayoutBox<'gc> {
offset, offset,
layout_context.is_start_of_line(), layout_context.is_start_of_line(),
) { ) {
// This ensures that the space causing the line break
// is included in the line it broke.
let next_breakpoint = string_utils::next_char_boundary(
text,
last_breakpoint + breakpoint,
);
// If text doesn't fit at the start of a line, it // If text doesn't fit at the start of a line, it
// won't fit on the next either, abort and put the // won't fit on the next either, abort and put the
// whole text on the line (will be cut-off). This // whole text on the line (will be cut-off). This
@ -721,7 +769,7 @@ impl<'gc> LayoutBox<'gc> {
if breakpoint == 0 && layout_context.is_start_of_line() { if breakpoint == 0 && layout_context.is_start_of_line() {
break; break;
} else if breakpoint == 0 { } else if breakpoint == 0 {
layout_context.newline(context); layout_context.newline(context, text, next_breakpoint, span);
let next_dim = layout_context.wrap_dimensions(span); let next_dim = layout_context.wrap_dimensions(span);
@ -735,13 +783,6 @@ impl<'gc> LayoutBox<'gc> {
} }
} }
// This ensures that the space causing the line break
// is included in the line it broke.
let next_breakpoint = string_utils::next_char_boundary(
text,
last_breakpoint + breakpoint,
);
layout_context.append_text( layout_context.append_text(
&text[last_breakpoint..next_breakpoint], &text[last_breakpoint..next_breakpoint],
start + last_breakpoint, start + last_breakpoint,
@ -754,7 +795,7 @@ impl<'gc> LayoutBox<'gc> {
break; break;
} }
layout_context.newline(context); layout_context.newline(context, text, next_breakpoint, span);
let next_dim = layout_context.wrap_dimensions(span); let next_dim = layout_context.wrap_dimensions(span);
width = next_dim.0; width = next_dim.0;
@ -764,7 +805,7 @@ impl<'gc> LayoutBox<'gc> {
let span_end = text.len(); let span_end = text.len();
if last_breakpoint <= span_end { if last_breakpoint < span_end {
layout_context.append_text( layout_context.append_text(
&text[last_breakpoint..span_end], &text[last_breakpoint..span_end],
start + last_breakpoint, start + last_breakpoint,
@ -776,7 +817,7 @@ impl<'gc> LayoutBox<'gc> {
} }
} }
layout_context.end_layout(context) layout_context.end_layout(context, fs)
} }
pub fn bounds(&self) -> BoxBounds<Twips> { pub fn bounds(&self) -> BoxBounds<Twips> {

View File

@ -856,6 +856,15 @@ impl FormatSpans {
self.spans.get(index) self.spans.get(index)
} }
/// Retrieve the last text span.
///
/// Text span indices are ephemeral and can change arbitrarily any time the
/// `FormatSpans` are mutated. You should not use this method directly; the
/// `iter_spans` method will yield the string and span data directly.
pub fn last_span(&self) -> Option<&TextSpan> {
self.spans.last()
}
/// Find the index of the span that covers a given search position. /// Find the index of the span that covers a given search position.
/// ///
/// This function returns both the index of the span which covers the /// This function returns both the index of the span which covers the