text: truncate multi-paragraph text correctly

This commit fixes a subtle problem when trunating text widgets that contain
multiple newline-delimited paragraphs.

Paragraphs are the unit of text shaping, so we divide the text into paragraphs
and then iterate those paragraphs performing shaping and line wrapping. If we
have a maximum number of lines to fill, we stop iterating paragraphs when we
use all of the available lines. Usually, if we fill all of the lines the text
shaper will insert the truncator symbol. However, if we exactly fill all of the
lines with the end of a paragraph, the line wrapper is able to fill the line
quota without actually truncating any of the text in that paragraph. Thus it
doesn't insert a truncator even though subsequent paragraphs were truncated (it
has no way to know).

To fix this, I've taught the line wrapper about an explicit scenario in which
we always want to show the truncator symbol *if* we hit the line limit, even if
all of the text in the current paragraph fit. I've then plumbed support for
that through our text stack.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit is contained in:
Chris Waldon
2023-03-28 13:53:29 -04:00
committed by Elias Naur
parent 7e8c10927b
commit d71f170c29
6 changed files with 100 additions and 14 deletions
+5 -2
View File
@@ -404,6 +404,7 @@ func (s *shaperImpl) shapeText(faces []font.Face, ppem fixed.Int26_6, lc system.
func (s *shaperImpl) shapeAndWrapText(faces []font.Face, params Parameters, txt []rune) (_ []shaping.Line, truncated int) {
wc := shaping.WrapConfig{
TruncateAfterLines: params.MaxLines,
TextContinues: params.forceTruncate,
}
if wc.TruncateAfterLines > 0 {
if len(params.Truncator) == 0 {
@@ -475,7 +476,9 @@ func (s *shaperImpl) LayoutRunes(params Parameters, txt []rune) document {
}
ls, truncated := s.shapeAndWrapText(s.orderer.sortedFacesForStyle(params.Font), params, replaceControlCharacters(txt))
if truncated > 0 && hasNewline {
didTruncate := truncated > 0 || (params.forceTruncate && params.MaxLines == len(ls))
if didTruncate && hasNewline {
// We've truncated the newline, since it was at the end and we've truncated some amount of runes
// before it.
truncated++
@@ -513,7 +516,7 @@ func (s *shaperImpl) LayoutRunes(params Parameters, txt []rune) document {
otLine.runs[finalRunIdx].Glyphs[0] = syntheticGlyph
}
}
if isFinalLine && truncated > 0 {
if isFinalLine && didTruncate {
// If we've truncated the text with a truncator, adjust the rune counts within the
// truncator to make it represent the truncated text.
finalRunIdx := len(otLine.runs) - 1