aboutsummaryrefslogtreecommitdiff
path: root/vendor/gioui.org/font/opentype/opentype.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gioui.org/font/opentype/opentype.go')
-rw-r--r--vendor/gioui.org/font/opentype/opentype.go196
1 files changed, 142 insertions, 54 deletions
diff --git a/vendor/gioui.org/font/opentype/opentype.go b/vendor/gioui.org/font/opentype/opentype.go
index 27afea4..1f42330 100644
--- a/vendor/gioui.org/font/opentype/opentype.go
+++ b/vendor/gioui.org/font/opentype/opentype.go
@@ -5,29 +5,32 @@
package opentype
import (
+ "bytes"
"io"
-
"unicode"
"unicode/utf8"
+ "golang.org/x/image/font"
+ "golang.org/x/image/font/sfnt"
+ "golang.org/x/image/math/fixed"
+
"gioui.org/f32"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/text"
- "golang.org/x/image/font"
- "golang.org/x/image/font/sfnt"
- "golang.org/x/image/math/fixed"
)
-// Font implements text.Face.
+// Font implements text.Face. Its methods are safe to use
+// concurrently.
type Font struct {
font *sfnt.Font
- buf sfnt.Buffer
}
-// Collection is a collection of one or more fonts.
+// Collection is a collection of one or more fonts. When used as a text.Face,
+// each rune will be assigned a glyph from the first font in the collection
+// that supports it.
type Collection struct {
- coll *sfnt.Collection
+ fonts []*opentype
}
type opentype struct {
@@ -35,6 +38,13 @@ type opentype struct {
Hinting font.Hinting
}
+// a glyph represents a rune and its advance according to a Font.
+// TODO: remove this type and work on io.Readers directly.
+type glyph struct {
+ Rune rune
+ Advance fixed.Int26_6
+}
+
// NewFont parses an SFNT font, such as TTF or OTF data, from a []byte
// data source.
func Parse(src []byte) (*Font, error) {
@@ -55,7 +65,7 @@ func ParseCollection(src []byte) (*Collection, error) {
if err != nil {
return nil, err
}
- return &Collection{c}, nil
+ return newCollectionFrom(c)
}
// ParseCollectionReaderAt parses an SFNT collection, such as TTC or OTC data,
@@ -68,21 +78,35 @@ func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) {
if err != nil {
return nil, err
}
- return &Collection{c}, nil
+ return newCollectionFrom(c)
+}
+
+func newCollectionFrom(coll *sfnt.Collection) (*Collection, error) {
+ fonts := make([]*opentype, coll.NumFonts())
+ for i := range fonts {
+ fnt, err := coll.Font(i)
+ if err != nil {
+ return nil, err
+ }
+ fonts[i] = &opentype{
+ Font: fnt,
+ Hinting: font.HintingFull,
+ }
+ }
+ return &Collection{fonts: fonts}, nil
}
// NumFonts returns the number of fonts in the collection.
func (c *Collection) NumFonts() int {
- return c.coll.NumFonts()
+ return len(c.fonts)
}
// Font returns the i'th font in the collection.
func (c *Collection) Font(i int) (*Font, error) {
- fnt, err := c.coll.Font(i)
- if err != nil {
- return nil, err
+ if i < 0 || len(c.fonts) <= i {
+ return nil, sfnt.ErrNotFound
}
- return &Font{font: fnt}, nil
+ return &Font{font: c.fonts[i].Font}, nil
}
func (f *Font) Layout(ppem fixed.Int26_6, maxWidth int, txt io.Reader) ([]text.Line, error) {
@@ -90,31 +114,69 @@ func (f *Font) Layout(ppem fixed.Int26_6, maxWidth int, txt io.Reader) ([]text.L
if err != nil {
return nil, err
}
- return layoutText(&f.buf, ppem, maxWidth, &opentype{Font: f.font, Hinting: font.HintingFull}, glyphs)
+ fonts := []*opentype{{Font: f.font, Hinting: font.HintingFull}}
+ var buf sfnt.Buffer
+ return layoutText(&buf, ppem, maxWidth, fonts, glyphs)
}
-func (f *Font) Shape(ppem fixed.Int26_6, str []text.Glyph) op.CallOp {
- return textPath(&f.buf, ppem, &opentype{Font: f.font, Hinting: font.HintingFull}, str)
+func (f *Font) Shape(ppem fixed.Int26_6, str text.Layout) clip.PathSpec {
+ var buf sfnt.Buffer
+ return textPath(&buf, ppem, []*opentype{{Font: f.font, Hinting: font.HintingFull}}, str)
}
func (f *Font) Metrics(ppem fixed.Int26_6) font.Metrics {
o := &opentype{Font: f.font, Hinting: font.HintingFull}
- return o.Metrics(&f.buf, ppem)
+ var buf sfnt.Buffer
+ return o.Metrics(&buf, ppem)
+}
+
+func (c *Collection) Layout(ppem fixed.Int26_6, maxWidth int, txt io.Reader) ([]text.Line, error) {
+ glyphs, err := readGlyphs(txt)
+ if err != nil {
+ return nil, err
+ }
+ var buf sfnt.Buffer
+ return layoutText(&buf, ppem, maxWidth, c.fonts, glyphs)
+}
+
+func (c *Collection) Shape(ppem fixed.Int26_6, str text.Layout) clip.PathSpec {
+ var buf sfnt.Buffer
+ return textPath(&buf, ppem, c.fonts, str)
+}
+
+func fontForGlyph(buf *sfnt.Buffer, fonts []*opentype, r rune) *opentype {
+ if len(fonts) < 1 {
+ return nil
+ }
+ for _, f := range fonts {
+ if f.HasGlyph(buf, r) {
+ return f
+ }
+ }
+ return fonts[0] // Use replacement character from the first font if necessary
}
-func layoutText(sbuf *sfnt.Buffer, ppem fixed.Int26_6, maxWidth int, f *opentype, glyphs []text.Glyph) ([]text.Line, error) {
- m := f.Metrics(sbuf, ppem)
- lineTmpl := text.Line{
- Ascent: m.Ascent,
+func layoutText(sbuf *sfnt.Buffer, ppem fixed.Int26_6, maxWidth int, fonts []*opentype, glyphs []glyph) ([]text.Line, error) {
+ var lines []text.Line
+ var nextLine text.Line
+ updateBounds := func(f *opentype) {
+ m := f.Metrics(sbuf, ppem)
+ if m.Ascent > nextLine.Ascent {
+ nextLine.Ascent = m.Ascent
+ }
// m.Height is equal to m.Ascent + m.Descent + linegap.
// Compute the descent including the linegap.
- Descent: m.Height - m.Ascent,
- Bounds: f.Bounds(sbuf, ppem),
+ descent := m.Height - m.Ascent
+ if descent > nextLine.Descent {
+ nextLine.Descent = descent
+ }
+ b := f.Bounds(sbuf, ppem)
+ nextLine.Bounds = nextLine.Bounds.Union(b)
}
- var lines []text.Line
maxDotX := fixed.I(maxWidth)
type state struct {
r rune
+ f *opentype
adv fixed.Int26_6
x fixed.Int26_6
idx int
@@ -123,26 +185,33 @@ func layoutText(sbuf *sfnt.Buffer, ppem fixed.Int26_6, maxWidth int, f *opentype
}
var prev, word state
endLine := func() {
- line := lineTmpl
- line.Layout = glyphs[:prev.idx:prev.idx]
- line.Len = prev.len
- line.Width = prev.x + prev.adv
- line.Bounds.Max.X += prev.x
- lines = append(lines, line)
+ if prev.f == nil && len(fonts) > 0 {
+ prev.f = fonts[0]
+ }
+ updateBounds(prev.f)
+ nextLine.Layout = toLayout(glyphs[:prev.idx:prev.idx])
+ nextLine.Width = prev.x + prev.adv
+ nextLine.Bounds.Max.X += prev.x
+ lines = append(lines, nextLine)
glyphs = glyphs[prev.idx:]
+ nextLine = text.Line{}
prev = state{}
word = state{}
}
for prev.idx < len(glyphs) {
g := &glyphs[prev.idx]
- a, valid := f.GlyphAdvance(sbuf, ppem, g.Rune)
next := state{
- r: g.Rune,
- idx: prev.idx + 1,
- len: prev.len + utf8.RuneLen(g.Rune),
- x: prev.x + prev.adv,
- adv: a,
- valid: valid,
+ r: g.Rune,
+ f: fontForGlyph(sbuf, fonts, g.Rune),
+ idx: prev.idx + 1,
+ len: prev.len + utf8.RuneLen(g.Rune),
+ x: prev.x + prev.adv,
+ }
+ if next.f != nil {
+ if next.f != prev.f {
+ updateBounds(next.f)
+ }
+ next.adv, next.valid = next.f.GlyphAdvance(sbuf, ppem, g.Rune)
}
if g.Rune == '\n' {
// The newline is zero width; use the previous
@@ -153,8 +222,8 @@ func layoutText(sbuf *sfnt.Buffer, ppem fixed.Int26_6, maxWidth int, f *opentype
continue
}
var k fixed.Int26_6
- if prev.valid {
- k = f.Kern(sbuf, ppem, prev.r, next.r)
+ if prev.valid && next.f != nil {
+ k = next.f.Kern(sbuf, ppem, prev.r, next.r)
}
// Break the line if we're out of space.
if prev.idx > 0 && next.x+next.adv+k > maxDotX {
@@ -181,16 +250,30 @@ func layoutText(sbuf *sfnt.Buffer, ppem fixed.Int26_6, maxWidth int, f *opentype
return lines, nil
}
-func textPath(buf *sfnt.Buffer, ppem fixed.Int26_6, f *opentype, str []text.Glyph) op.CallOp {
+// toLayout converts a slice of glyphs to a text.Layout.
+func toLayout(glyphs []glyph) text.Layout {
+ var buf bytes.Buffer
+ advs := make([]fixed.Int26_6, len(glyphs))
+ for i, g := range glyphs {
+ buf.WriteRune(g.Rune)
+ advs[i] = glyphs[i].Advance
+ }
+ return text.Layout{Text: buf.String(), Advances: advs}
+}
+
+func textPath(buf *sfnt.Buffer, ppem fixed.Int26_6, fonts []*opentype, str text.Layout) clip.PathSpec {
var lastPos f32.Point
var builder clip.Path
- ops := new(op.Ops)
- m := op.Record(ops)
var x fixed.Int26_6
- builder.Begin(ops)
- for _, g := range str {
- if !unicode.IsSpace(g.Rune) {
- segs, ok := f.LoadGlyph(buf, ppem, g.Rune)
+ builder.Begin(new(op.Ops))
+ rune := 0
+ for _, r := range str.Text {
+ if !unicode.IsSpace(r) {
+ f := fontForGlyph(buf, fonts, r)
+ if f == nil {
+ continue
+ }
+ segs, ok := f.LoadGlyph(buf, ppem, r)
if !ok {
continue
}
@@ -236,14 +319,14 @@ func textPath(buf *sfnt.Buffer, ppem fixed.Int26_6, f *opentype, str []text.Glyp
}
lastPos = lastPos.Add(lastArg)
}
- x += g.Advance
+ x += str.Advances[rune]
+ rune++
}
- builder.End().Add(ops)
- return m.Stop()
+ return builder.End()
}
-func readGlyphs(r io.Reader) ([]text.Glyph, error) {
- var glyphs []text.Glyph
+func readGlyphs(r io.Reader) ([]glyph, error) {
+ var glyphs []glyph
buf := make([]byte, 0, 1024)
for {
n, err := r.Read(buf[len(buf):cap(buf)])
@@ -257,7 +340,7 @@ func readGlyphs(r io.Reader) ([]text.Glyph, error) {
for i < lim {
c, s := utf8.DecodeRune(buf[i:])
i += s
- glyphs = append(glyphs, text.Glyph{Rune: c})
+ glyphs = append(glyphs, glyph{Rune: c})
}
n = copy(buf, buf[i:])
buf = buf[:n]
@@ -271,6 +354,11 @@ func readGlyphs(r io.Reader) ([]text.Glyph, error) {
return glyphs, nil
}
+func (f *opentype) HasGlyph(buf *sfnt.Buffer, r rune) bool {
+ g, err := f.Font.GlyphIndex(buf, r)
+ return g != 0 && err == nil
+}
+
func (f *opentype) GlyphAdvance(buf *sfnt.Buffer, ppem fixed.Int26_6, r rune) (advance fixed.Int26_6, ok bool) {
g, err := f.Font.GlyphIndex(buf, r)
if err != nil {