mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
go.*,text: implement shaper-driven line truncation
This commit pushes limiting the maximum number of lines of text into the shaper implementation. This is more efficient than doing it in widgets, and also opens the door for future use of the shaper to insert ellipsis and other truncating characters as appropriate. I realized that we lost the implementation of limiting the number of lines of text in my text stack overhaul, so this fixes a regression from that work. Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit is contained in:
+5
-3
@@ -384,9 +384,11 @@ func (s *shaperImpl) shapeText(faces []font.Face, ppem fixed.Int26_6, lc system.
|
||||
}
|
||||
|
||||
// shapeAndWrapText invokes the text shaper and returns wrapped lines in the shaper's native format.
|
||||
func (s *shaperImpl) shapeAndWrapText(faces []font.Face, ppem fixed.Int26_6, maxWidth int, lc system.Locale, txt []rune) []shaping.Line {
|
||||
func (s *shaperImpl) shapeAndWrapText(faces []font.Face, params Parameters, maxWidth int, lc system.Locale, txt []rune) []shaping.Line {
|
||||
// Wrap outputs into lines.
|
||||
return s.wrapper.WrapParagraph(maxWidth, txt, s.shapeText(faces, ppem, lc, txt)...)
|
||||
return s.wrapper.WrapParagraph(shaping.WrapConfig{
|
||||
TruncateAfterLines: params.MaxLines,
|
||||
}, maxWidth, txt, s.shapeText(faces, params.PxPerEm, lc, txt)...)
|
||||
}
|
||||
|
||||
// replaceControlCharacters replaces problematic unicode
|
||||
@@ -445,7 +447,7 @@ func (s *shaperImpl) LayoutRunes(params Parameters, minWidth, maxWidth int, lc s
|
||||
if hasNewline {
|
||||
txt = txt[:len(txt)-1]
|
||||
}
|
||||
ls := s.shapeAndWrapText(s.orderer.sortedFacesForStyle(params.Font), params.PxPerEm, maxWidth, lc, replaceControlCharacters(txt))
|
||||
ls := s.shapeAndWrapText(s.orderer.sortedFacesForStyle(params.Font), params, maxWidth, lc, replaceControlCharacters(txt))
|
||||
// Convert to Lines.
|
||||
textLines := make([]line, len(ls))
|
||||
for i := range ls {
|
||||
|
||||
+2
-2
@@ -193,8 +193,8 @@ func makeTestText(shaper *shaperImpl, primaryDir system.TextDirection, fontSize,
|
||||
rtlSource = string(complexRunes[:runeLimit])
|
||||
}
|
||||
}
|
||||
simpleText := shaper.shapeAndWrapText(shaper.orderer.sortedFacesForStyle(Font{}), fixed.I(fontSize), lineWidth, locale, []rune(simpleSource))
|
||||
complexText := shaper.shapeAndWrapText(shaper.orderer.sortedFacesForStyle(Font{}), fixed.I(fontSize), lineWidth, locale, []rune(complexSource))
|
||||
simpleText := shaper.shapeAndWrapText(shaper.orderer.sortedFacesForStyle(Font{}), Parameters{PxPerEm: fixed.I(fontSize)}, lineWidth, locale, []rune(simpleSource))
|
||||
complexText := shaper.shapeAndWrapText(shaper.orderer.sortedFacesForStyle(Font{}), Parameters{PxPerEm: fixed.I(fontSize)}, lineWidth, locale, []rune(complexSource))
|
||||
shaper = testShaper(rtlFace, ltrFace)
|
||||
return simpleText, complexText
|
||||
}
|
||||
|
||||
@@ -192,6 +192,8 @@ func (l *Shaper) layoutText(params Parameters, minWidth, maxWidth int, lc system
|
||||
l.txt.append(l.layoutParagraph(params, minWidth, maxWidth, lc, "", nil))
|
||||
return
|
||||
}
|
||||
truncating := params.MaxLines > 0
|
||||
maxLines := params.MaxLines
|
||||
var done bool
|
||||
var startByte int
|
||||
var endByte int
|
||||
@@ -222,6 +224,12 @@ func (l *Shaper) layoutText(params Parameters, minWidth, maxWidth int, lc system
|
||||
done = endByte == len(str)
|
||||
}
|
||||
l.txt.append(l.layoutParagraph(params, minWidth, maxWidth, lc, str[startByte:endByte], l.paragraph))
|
||||
if truncating {
|
||||
params.MaxLines = maxLines - len(l.txt.lines)
|
||||
if params.MaxLines == 0 {
|
||||
done = true
|
||||
}
|
||||
}
|
||||
if done {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -10,6 +10,36 @@ import (
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// TestWrappingTruncation checks that the line wrapper's truncation features
|
||||
// behave as expected.
|
||||
func TestWrappingTruncation(t *testing.T) {
|
||||
// Use a test string containing multiple newlines to ensure that they are shaped
|
||||
// as separate paragraphs.
|
||||
textInput := "Lorem ipsum dolor sit amet, consectetur adipiscing elit,\nsed do eiusmod tempor incididunt ut labore et\ndolore magna aliqua."
|
||||
ltrFace, _ := opentype.Parse(goregular.TTF)
|
||||
collection := []FontFace{{Face: ltrFace}}
|
||||
cache := NewShaper(collection)
|
||||
cache.LayoutString(Parameters{
|
||||
Alignment: Middle,
|
||||
PxPerEm: fixed.I(10),
|
||||
}, 200, 200, english, textInput)
|
||||
untruncatedCount := len(cache.txt.lines)
|
||||
|
||||
for i := untruncatedCount + 1; i > 0; i-- {
|
||||
cache.LayoutString(Parameters{
|
||||
Alignment: Middle,
|
||||
PxPerEm: fixed.I(10),
|
||||
MaxLines: i,
|
||||
}, 200, 200, english, textInput)
|
||||
lineCount := len(cache.txt.lines)
|
||||
if i <= untruncatedCount && lineCount != i {
|
||||
t.Errorf("expected %d lines, got %d", i, lineCount)
|
||||
} else if i > untruncatedCount && lineCount != untruncatedCount {
|
||||
t.Errorf("expected %d lines, got %d", untruncatedCount, lineCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCacheEmptyString ensures that shaping the empty string returns a
|
||||
// single synthetic glyph with ascent/descent info.
|
||||
func TestCacheEmptyString(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user