mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
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:
+29
-33
@@ -172,9 +172,9 @@ type Shaper struct {
|
||||
pathCache pathCache
|
||||
bitmapShapeCache bitmapShapeCache
|
||||
layoutCache layoutCache
|
||||
paragraph []rune
|
||||
|
||||
reader strings.Reader
|
||||
reader *bufio.Reader
|
||||
paragraph []byte
|
||||
|
||||
// Iterator state.
|
||||
brokeParagraph bool
|
||||
@@ -198,13 +198,14 @@ func NewShaper(collection []FontFace) *Shaper {
|
||||
l.shaper.Load(f)
|
||||
}
|
||||
l.shaper.shaper.SetFontCacheSize(32)
|
||||
l.reader = bufio.NewReader(nil)
|
||||
return l
|
||||
}
|
||||
|
||||
// Layout text from an io.Reader according to a set of options. Results can be retrieved by
|
||||
// iteratively calling NextGlyph.
|
||||
func (l *Shaper) Layout(params Parameters, txt io.Reader) {
|
||||
l.layoutText(params, bufio.NewReader(txt), "")
|
||||
l.layoutText(params, txt, "")
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
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)
|
||||
if txt == nil && len(str) == 0 {
|
||||
l.txt.append(l.layoutParagraph(params, "", nil))
|
||||
return
|
||||
}
|
||||
l.reader.Reset(txt)
|
||||
truncating := params.MaxLines > 0
|
||||
var done bool
|
||||
var startByte int
|
||||
var endByte int
|
||||
for !done {
|
||||
var runes int
|
||||
l.paragraph = l.paragraph[:0]
|
||||
if txt != nil {
|
||||
for r, _, re := txt.ReadRune(); !done; r, _, re = txt.ReadRune() {
|
||||
if re != nil {
|
||||
for {
|
||||
b, err := l.reader.ReadByte()
|
||||
if err != nil {
|
||||
// EOF or any other error ends processing here.
|
||||
done = true
|
||||
continue
|
||||
break
|
||||
}
|
||||
l.paragraph = append(l.paragraph, r)
|
||||
runes++
|
||||
if r == '\n' {
|
||||
l.paragraph = append(l.paragraph, b)
|
||||
if b == '\n' {
|
||||
break
|
||||
}
|
||||
}
|
||||
_, _, re := txt.ReadRune()
|
||||
_, re := l.reader.ReadByte()
|
||||
done = re != nil
|
||||
_ = txt.UnreadRune()
|
||||
_ = l.reader.UnreadByte()
|
||||
} else {
|
||||
for endByte = startByte; endByte < len(str); {
|
||||
r, width := utf8.DecodeRuneInString(str[endByte:])
|
||||
endByte += width
|
||||
runes++
|
||||
if r == '\n' {
|
||||
break
|
||||
}
|
||||
idx := strings.IndexByte(str, '\n')
|
||||
if idx == -1 {
|
||||
done = true
|
||||
endByte = len(str)
|
||||
} else {
|
||||
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
|
||||
lines := l.layoutParagraph(params, str[startByte:endByte], l.paragraph)
|
||||
lines := l.layoutParagraph(params, str[:endByte], l.paragraph)
|
||||
if truncating {
|
||||
params.MaxLines -= len(lines.lines)
|
||||
if params.MaxLines == 0 {
|
||||
@@ -275,7 +274,7 @@ func (l *Shaper) layoutText(params Parameters, txt *bufio.Reader, str string) {
|
||||
unreadRunes = utf8.RuneCountInString(str[endByte:])
|
||||
} else {
|
||||
for {
|
||||
_, _, e := txt.ReadRune()
|
||||
_, _, e := l.reader.ReadRune()
|
||||
if e != nil {
|
||||
break
|
||||
}
|
||||
@@ -294,19 +293,19 @@ func (l *Shaper) layoutText(params Parameters, txt *bufio.Reader, str string) {
|
||||
if done {
|
||||
return
|
||||
}
|
||||
startByte = endByte
|
||||
str = str[endByte:]
|
||||
}
|
||||
}
|
||||
|
||||
// layoutParagraph shapes and wraps a paragraph using the provided parameters.
|
||||
// It accepts the paragraph data in either string or rune format, preferring the
|
||||
// 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 {
|
||||
return document{}
|
||||
}
|
||||
if len(asStr) == 0 && len(asRunes) > 0 {
|
||||
asStr = string(asRunes)
|
||||
if len(asStr) == 0 && len(asBytes) > 0 {
|
||||
asStr = string(asBytes)
|
||||
}
|
||||
// Alignment is not part of the cache key because changing it does not impact shaping.
|
||||
lk := layoutKey{
|
||||
@@ -323,10 +322,7 @@ func (l *Shaper) layoutParagraph(params Parameters, asStr string, asRunes []rune
|
||||
if l, ok := l.layoutCache.Get(lk); ok {
|
||||
return l
|
||||
}
|
||||
if len(asRunes) == 0 && len(asStr) > 0 {
|
||||
asRunes = []rune(asStr)
|
||||
}
|
||||
lines := l.shaper.LayoutRunes(params, asRunes)
|
||||
lines := l.shaper.LayoutRunes(params, []rune(asStr))
|
||||
l.layoutCache.Put(lk, lines)
|
||||
return lines
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user