diff options
Diffstat (limited to 'vendor/gioui.org/font/opentype/opentype.go')
-rw-r--r-- | vendor/gioui.org/font/opentype/opentype.go | 196 |
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 { |