forked from joejulian/gio
a34e239c04
With this change, the Shape function returns a clip.PathSpec instead of a clip.Outline op. It is then possible to create a clip.Outline or clip.Stroke op to fill the text path or draw its stroke. Signed-off-by: Christophe Meessen <meessen@cppm.in2p3.fr>
161 lines
3.8 KiB
Go
161 lines
3.8 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package text
|
|
|
|
import (
|
|
"io"
|
|
"strings"
|
|
|
|
"golang.org/x/image/math/fixed"
|
|
|
|
"gioui.org/op/clip"
|
|
)
|
|
|
|
// Shaper implements layout and shaping of text.
|
|
type Shaper interface {
|
|
// Layout a text according to a set of options.
|
|
Layout(font Font, size fixed.Int26_6, maxWidth int, txt io.Reader) ([]Line, error)
|
|
// LayoutString is Layout for strings.
|
|
LayoutString(font Font, size fixed.Int26_6, maxWidth int, str string) []Line
|
|
// Shape a line of text and return a clipping operation for its outline.
|
|
Shape(font Font, size fixed.Int26_6, layout Layout) clip.PathSpec
|
|
}
|
|
|
|
// A FontFace is a Font and a matching Face.
|
|
type FontFace struct {
|
|
Font Font
|
|
Face Face
|
|
}
|
|
|
|
// Cache implements cached layout and shaping of text from a set of
|
|
// registered fonts.
|
|
//
|
|
// If a font matches no registered shape, Cache falls back to the
|
|
// first registered face.
|
|
//
|
|
// The LayoutString and ShapeString results are cached and re-used if
|
|
// possible.
|
|
type Cache struct {
|
|
def Typeface
|
|
faces map[Font]*faceCache
|
|
}
|
|
|
|
type faceCache struct {
|
|
face Face
|
|
layoutCache layoutCache
|
|
pathCache pathCache
|
|
}
|
|
|
|
func (c *Cache) lookup(font Font) *faceCache {
|
|
f := c.faceForStyle(font)
|
|
if f == nil {
|
|
font.Typeface = c.def
|
|
f = c.faceForStyle(font)
|
|
}
|
|
return f
|
|
}
|
|
|
|
func (c *Cache) faceForStyle(font Font) *faceCache {
|
|
if closest, ok := c.closestFont(font); ok {
|
|
return c.faces[closest]
|
|
}
|
|
font.Style = Regular
|
|
if closest, ok := c.closestFont(font); ok {
|
|
return c.faces[closest]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// closestFont returns the closest Font by weight, in case of equality the
|
|
// lighter weight will be returned.
|
|
func (c *Cache) closestFont(lookup Font) (Font, bool) {
|
|
if c.faces[lookup] != nil {
|
|
return lookup, true
|
|
}
|
|
found := false
|
|
var match Font
|
|
for cf := range c.faces {
|
|
if cf.Typeface != lookup.Typeface || cf.Variant != lookup.Variant || cf.Style != lookup.Style {
|
|
continue
|
|
}
|
|
if !found {
|
|
found = true
|
|
match = cf
|
|
continue
|
|
}
|
|
cDist := weightDistance(lookup.Weight, cf.Weight)
|
|
mDist := weightDistance(lookup.Weight, match.Weight)
|
|
if cDist < mDist {
|
|
match = cf
|
|
} else if cDist == mDist && cf.Weight < match.Weight {
|
|
match = cf
|
|
}
|
|
}
|
|
return match, found
|
|
}
|
|
|
|
func NewCache(collection []FontFace) *Cache {
|
|
c := &Cache{
|
|
faces: make(map[Font]*faceCache),
|
|
}
|
|
for i, ff := range collection {
|
|
if i == 0 {
|
|
c.def = ff.Font.Typeface
|
|
}
|
|
c.faces[ff.Font] = &faceCache{face: ff.Face}
|
|
}
|
|
return c
|
|
}
|
|
|
|
// Layout implements the Shaper interface.
|
|
func (c *Cache) Layout(font Font, size fixed.Int26_6, maxWidth int, txt io.Reader) ([]Line, error) {
|
|
cache := c.lookup(font)
|
|
return cache.face.Layout(size, maxWidth, txt)
|
|
}
|
|
|
|
// LayoutString is a caching implementation of the Shaper interface.
|
|
func (c *Cache) LayoutString(font Font, size fixed.Int26_6, maxWidth int, str string) []Line {
|
|
cache := c.lookup(font)
|
|
return cache.layout(size, maxWidth, str)
|
|
}
|
|
|
|
// Shape is a caching implementation of the Shaper interface. Shape assumes that the layout
|
|
// argument is unchanged from a call to Layout or LayoutString.
|
|
func (c *Cache) Shape(font Font, size fixed.Int26_6, layout Layout) clip.PathSpec {
|
|
cache := c.lookup(font)
|
|
return cache.shape(size, layout)
|
|
}
|
|
|
|
func (f *faceCache) layout(ppem fixed.Int26_6, maxWidth int, str string) []Line {
|
|
if f == nil {
|
|
return nil
|
|
}
|
|
lk := layoutKey{
|
|
ppem: ppem,
|
|
maxWidth: maxWidth,
|
|
str: str,
|
|
}
|
|
if l, ok := f.layoutCache.Get(lk); ok {
|
|
return l
|
|
}
|
|
l, _ := f.face.Layout(ppem, maxWidth, strings.NewReader(str))
|
|
f.layoutCache.Put(lk, l)
|
|
return l
|
|
}
|
|
|
|
func (f *faceCache) shape(ppem fixed.Int26_6, layout Layout) clip.PathSpec {
|
|
if f == nil {
|
|
return clip.PathSpec{}
|
|
}
|
|
pk := pathKey{
|
|
ppem: ppem,
|
|
str: layout.Text,
|
|
}
|
|
if clip, ok := f.pathCache.Get(pk); ok {
|
|
return clip
|
|
}
|
|
clip := f.face.Shape(ppem, layout)
|
|
f.pathCache.Put(pk, clip)
|
|
return clip
|
|
}
|