text: optimize shaper paragraph decoding

This commit removes some inefficiencies from the pre-shaper-cache processing of
text. The text is no longer decoded into runes prior to being tested against the
cache, and the search for newlines uses slightly more efficient iteration operations
now.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit is contained in:
Chris Waldon
2023-04-20 14:24:48 -04:00
committed by Elias Naur
parent 880cd27f59
commit bba91263b0
+29 -33
View File
@@ -172,9 +172,9 @@ type Shaper struct {
pathCache pathCache pathCache pathCache
bitmapShapeCache bitmapShapeCache bitmapShapeCache bitmapShapeCache
layoutCache layoutCache layoutCache layoutCache
paragraph []rune
reader strings.Reader reader *bufio.Reader
paragraph []byte
// Iterator state. // Iterator state.
brokeParagraph bool brokeParagraph bool
@@ -198,13 +198,14 @@ func NewShaper(collection []FontFace) *Shaper {
l.shaper.Load(f) l.shaper.Load(f)
} }
l.shaper.shaper.SetFontCacheSize(32) l.shaper.shaper.SetFontCacheSize(32)
l.reader = bufio.NewReader(nil)
return l return l
} }
// Layout text from an io.Reader according to a set of options. Results can be retrieved by // Layout text from an io.Reader according to a set of options. Results can be retrieved by
// iteratively calling NextGlyph. // iteratively calling NextGlyph.
func (l *Shaper) Layout(params Parameters, txt io.Reader) { func (l *Shaper) Layout(params Parameters, txt io.Reader) {
l.layoutText(params, bufio.NewReader(txt), "") l.layoutText(params, txt, "")
} }
// LayoutString is Layout for strings. // LayoutString is Layout for strings.
@@ -222,48 +223,46 @@ func (l *Shaper) reset(align Alignment) {
// layoutText lays out a large text document by breaking it into paragraphs and laying // layoutText lays out a large text document by breaking it into paragraphs and laying
// out each of them separately. This allows the shaping results to be cached independently // out each of them separately. This allows the shaping results to be cached independently
// by paragraph. Only one of txt and str should be provided. // by paragraph. Only one of txt and str should be provided.
func (l *Shaper) layoutText(params Parameters, txt *bufio.Reader, str string) { func (l *Shaper) layoutText(params Parameters, txt io.Reader, str string) {
l.reset(params.Alignment) l.reset(params.Alignment)
if txt == nil && len(str) == 0 { if txt == nil && len(str) == 0 {
l.txt.append(l.layoutParagraph(params, "", nil)) l.txt.append(l.layoutParagraph(params, "", nil))
return return
} }
l.reader.Reset(txt)
truncating := params.MaxLines > 0 truncating := params.MaxLines > 0
var done bool var done bool
var startByte int
var endByte int var endByte int
for !done { for !done {
var runes int
l.paragraph = l.paragraph[:0] l.paragraph = l.paragraph[:0]
if txt != nil { if txt != nil {
for r, _, re := txt.ReadRune(); !done; r, _, re = txt.ReadRune() { for {
if re != nil { b, err := l.reader.ReadByte()
if err != nil {
// EOF or any other error ends processing here.
done = true done = true
continue break
} }
l.paragraph = append(l.paragraph, r) l.paragraph = append(l.paragraph, b)
runes++ if b == '\n' {
if r == '\n' {
break break
} }
} }
_, _, re := txt.ReadRune() _, re := l.reader.ReadByte()
done = re != nil done = re != nil
_ = txt.UnreadRune() _ = l.reader.UnreadByte()
} else { } else {
for endByte = startByte; endByte < len(str); { idx := strings.IndexByte(str, '\n')
r, width := utf8.DecodeRuneInString(str[endByte:]) if idx == -1 {
endByte += width done = true
runes++ endByte = len(str)
if r == '\n' { } else {
break endByte = idx + 1
}
} }
done = endByte == len(str)
} }
if startByte != endByte || (len(l.paragraph) > 0 || len(l.txt.lines) == 0) { if len(str[:endByte]) > 0 || (len(l.paragraph) > 0 || len(l.txt.lines) == 0) {
params.forceTruncate = truncating && !done params.forceTruncate = truncating && !done
lines := l.layoutParagraph(params, str[startByte:endByte], l.paragraph) lines := l.layoutParagraph(params, str[:endByte], l.paragraph)
if truncating { if truncating {
params.MaxLines -= len(lines.lines) params.MaxLines -= len(lines.lines)
if params.MaxLines == 0 { if params.MaxLines == 0 {
@@ -275,7 +274,7 @@ func (l *Shaper) layoutText(params Parameters, txt *bufio.Reader, str string) {
unreadRunes = utf8.RuneCountInString(str[endByte:]) unreadRunes = utf8.RuneCountInString(str[endByte:])
} else { } else {
for { for {
_, _, e := txt.ReadRune() _, _, e := l.reader.ReadRune()
if e != nil { if e != nil {
break break
} }
@@ -294,19 +293,19 @@ func (l *Shaper) layoutText(params Parameters, txt *bufio.Reader, str string) {
if done { if done {
return return
} }
startByte = endByte str = str[endByte:]
} }
} }
// layoutParagraph shapes and wraps a paragraph using the provided parameters. // layoutParagraph shapes and wraps a paragraph using the provided parameters.
// It accepts the paragraph data in either string or rune format, preferring the // It accepts the paragraph data in either string or rune format, preferring the
// string in order to hit the shaper cache more quickly. // string in order to hit the shaper cache more quickly.
func (l *Shaper) layoutParagraph(params Parameters, asStr string, asRunes []rune) document { func (l *Shaper) layoutParagraph(params Parameters, asStr string, asBytes []byte) document {
if l == nil { if l == nil {
return document{} return document{}
} }
if len(asStr) == 0 && len(asRunes) > 0 { if len(asStr) == 0 && len(asBytes) > 0 {
asStr = string(asRunes) asStr = string(asBytes)
} }
// Alignment is not part of the cache key because changing it does not impact shaping. // Alignment is not part of the cache key because changing it does not impact shaping.
lk := layoutKey{ lk := layoutKey{
@@ -323,10 +322,7 @@ func (l *Shaper) layoutParagraph(params Parameters, asStr string, asRunes []rune
if l, ok := l.layoutCache.Get(lk); ok { if l, ok := l.layoutCache.Get(lk); ok {
return l return l
} }
if len(asRunes) == 0 && len(asStr) > 0 { lines := l.shaper.LayoutRunes(params, []rune(asStr))
asRunes = []rune(asStr)
}
lines := l.shaper.LayoutRunes(params, asRunes)
l.layoutCache.Put(lk, lines) l.layoutCache.Put(lk, lines)
return lines return lines
} }