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
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user