github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/gio/font/opentype/opentype.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 // Package opentype implements text layout and shaping for OpenType 4 // files. 5 package opentype 6 7 import ( 8 "io" 9 10 "unicode" 11 "unicode/utf8" 12 13 "github.com/gop9/olt/gio/f32" 14 "github.com/gop9/olt/gio/op" 15 "github.com/gop9/olt/gio/op/clip" 16 "github.com/gop9/olt/gio/text" 17 "golang.org/x/image/font" 18 "golang.org/x/image/font/sfnt" 19 "golang.org/x/image/math/fixed" 20 ) 21 22 // Font implements text.Face. 23 type Font struct { 24 font *sfnt.Font 25 buf sfnt.Buffer 26 } 27 28 // Collection is a collection of one or more fonts. 29 type Collection struct { 30 coll *sfnt.Collection 31 } 32 33 type opentype struct { 34 Font *sfnt.Font 35 Hinting font.Hinting 36 } 37 38 // NewFont parses an SFNT font, such as TTF or OTF data, from a []byte 39 // data source. 40 func Parse(src []byte) (*Font, error) { 41 fnt, err := sfnt.Parse(src) 42 if err != nil { 43 return nil, err 44 } 45 return &Font{font: fnt}, nil 46 } 47 48 // ParseCollection parses an SFNT font collection, such as TTC or OTC data, 49 // from a []byte data source. 50 // 51 // If passed data for a single font, a TTF or OTF instead of a TTC or OTC, 52 // it will return a collection containing 1 font. 53 func ParseCollection(src []byte) (*Collection, error) { 54 c, err := sfnt.ParseCollection(src) 55 if err != nil { 56 return nil, err 57 } 58 return &Collection{c}, nil 59 } 60 61 // ParseCollectionReaderAt parses an SFNT collection, such as TTC or OTC data, 62 // from an io.ReaderAt data source. 63 // 64 // If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it 65 // will return a collection containing 1 font. 66 func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) { 67 c, err := sfnt.ParseCollectionReaderAt(src) 68 if err != nil { 69 return nil, err 70 } 71 return &Collection{c}, nil 72 } 73 74 // NumFonts returns the number of fonts in the collection. 75 func (c *Collection) NumFonts() int { 76 return c.coll.NumFonts() 77 } 78 79 // Font returns the i'th font in the collection. 80 func (c *Collection) Font(i int) (*Font, error) { 81 fnt, err := c.coll.Font(i) 82 if err != nil { 83 return nil, err 84 } 85 return &Font{font: fnt}, nil 86 } 87 88 func (f *Font) Layout(ppem fixed.Int26_6, str string, opts text.LayoutOptions) *text.Layout { 89 return layoutText(&f.buf, ppem, str, &opentype{Font: f.font, Hinting: font.HintingFull}, opts) 90 } 91 92 func (f *Font) Shape(ppem fixed.Int26_6, str text.String) op.CallOp { 93 return textPath(&f.buf, ppem, &opentype{Font: f.font, Hinting: font.HintingFull}, str) 94 } 95 96 func (f *Font) Metrics(ppem fixed.Int26_6) font.Metrics { 97 o := &opentype{Font: f.font, Hinting: font.HintingFull} 98 return o.Metrics(&f.buf, ppem) 99 } 100 101 func layoutText(buf *sfnt.Buffer, ppem fixed.Int26_6, str string, f *opentype, opts text.LayoutOptions) *text.Layout { 102 m := f.Metrics(buf, ppem) 103 lineTmpl := text.Line{ 104 Ascent: m.Ascent, 105 // m.Height is equal to m.Ascent + m.Descent + linegap. 106 // Compute the descent including the linegap. 107 Descent: m.Height - m.Ascent, 108 Bounds: f.Bounds(buf, ppem), 109 } 110 var lines []text.Line 111 maxDotX := fixed.I(opts.MaxWidth) 112 type state struct { 113 r rune 114 advs []fixed.Int26_6 115 adv fixed.Int26_6 116 x fixed.Int26_6 117 idx int 118 valid bool 119 } 120 var prev, word state 121 endLine := func() { 122 line := lineTmpl 123 line.Text.Advances = prev.advs 124 line.Text.String = str[:prev.idx] 125 line.Width = prev.x + prev.adv 126 line.Bounds.Max.X += prev.x 127 lines = append(lines, line) 128 str = str[prev.idx:] 129 prev = state{} 130 word = state{} 131 } 132 for prev.idx < len(str) { 133 c, s := utf8.DecodeRuneInString(str[prev.idx:]) 134 a, valid := f.GlyphAdvance(buf, ppem, c) 135 next := state{ 136 r: c, 137 advs: prev.advs, 138 idx: prev.idx + s, 139 x: prev.x + prev.adv, 140 adv: a, 141 valid: valid, 142 } 143 if c == '\n' { 144 // The newline is zero width; use the previous 145 // character for line measurements. 146 prev.advs = append(prev.advs, 0) 147 prev.idx = next.idx 148 endLine() 149 continue 150 } 151 var k fixed.Int26_6 152 if prev.valid { 153 k = f.Kern(buf, ppem, prev.r, next.r) 154 } 155 // Break the line if we're out of space. 156 if prev.idx > 0 && next.x+next.adv+k > maxDotX { 157 // If the line contains no word breaks, break off the last rune. 158 if word.idx == 0 { 159 word = prev 160 } 161 next.x -= word.x + word.adv 162 next.idx -= word.idx 163 next.advs = next.advs[len(word.advs):] 164 prev = word 165 endLine() 166 } else if k != 0 { 167 next.advs[len(next.advs)-1] += k 168 next.x += k 169 } 170 next.advs = append(next.advs, next.adv) 171 if unicode.IsSpace(next.r) { 172 word = next 173 } 174 prev = next 175 } 176 endLine() 177 return &text.Layout{Lines: lines} 178 } 179 180 func textPath(buf *sfnt.Buffer, ppem fixed.Int26_6, f *opentype, str text.String) op.CallOp { 181 var lastPos f32.Point 182 var builder clip.Path 183 ops := new(op.Ops) 184 var x fixed.Int26_6 185 var advIdx int 186 builder.Begin(ops) 187 for _, r := range str.String { 188 if !unicode.IsSpace(r) { 189 segs, ok := f.LoadGlyph(buf, ppem, r) 190 if !ok { 191 continue 192 } 193 // Move to glyph position. 194 pos := f32.Point{ 195 X: float32(x) / 64, 196 } 197 builder.Move(pos.Sub(lastPos)) 198 lastPos = pos 199 var lastArg f32.Point 200 // Convert sfnt.Segments to relative segments. 201 for _, fseg := range segs { 202 nargs := 1 203 switch fseg.Op { 204 case sfnt.SegmentOpQuadTo: 205 nargs = 2 206 case sfnt.SegmentOpCubeTo: 207 nargs = 3 208 } 209 var args [3]f32.Point 210 for i := 0; i < nargs; i++ { 211 a := f32.Point{ 212 X: float32(fseg.Args[i].X) / 64, 213 Y: float32(fseg.Args[i].Y) / 64, 214 } 215 args[i] = a.Sub(lastArg) 216 if i == nargs-1 { 217 lastArg = a 218 } 219 } 220 switch fseg.Op { 221 case sfnt.SegmentOpMoveTo: 222 builder.Move(args[0]) 223 case sfnt.SegmentOpLineTo: 224 builder.Line(args[0]) 225 case sfnt.SegmentOpQuadTo: 226 builder.Quad(args[0], args[1]) 227 case sfnt.SegmentOpCubeTo: 228 builder.Cube(args[0], args[1], args[2]) 229 default: 230 panic("unsupported segment op") 231 } 232 } 233 lastPos = lastPos.Add(lastArg) 234 } 235 x += str.Advances[advIdx] 236 advIdx++ 237 } 238 builder.End().Add(ops) 239 return op.CallOp{Ops: ops} 240 } 241 242 func (f *opentype) GlyphAdvance(buf *sfnt.Buffer, ppem fixed.Int26_6, r rune) (advance fixed.Int26_6, ok bool) { 243 g, err := f.Font.GlyphIndex(buf, r) 244 if err != nil { 245 return 0, false 246 } 247 adv, err := f.Font.GlyphAdvance(buf, g, ppem, f.Hinting) 248 return adv, err == nil 249 } 250 251 func (f *opentype) Kern(buf *sfnt.Buffer, ppem fixed.Int26_6, r0, r1 rune) fixed.Int26_6 { 252 g0, err := f.Font.GlyphIndex(buf, r0) 253 if err != nil { 254 return 0 255 } 256 g1, err := f.Font.GlyphIndex(buf, r1) 257 if err != nil { 258 return 0 259 } 260 adv, err := f.Font.Kern(buf, g0, g1, ppem, f.Hinting) 261 if err != nil { 262 return 0 263 } 264 return adv 265 } 266 267 func (f *opentype) Metrics(buf *sfnt.Buffer, ppem fixed.Int26_6) font.Metrics { 268 m, _ := f.Font.Metrics(buf, ppem, f.Hinting) 269 return m 270 } 271 272 func (f *opentype) Bounds(buf *sfnt.Buffer, ppem fixed.Int26_6) fixed.Rectangle26_6 { 273 r, _ := f.Font.Bounds(buf, ppem, f.Hinting) 274 return r 275 } 276 277 func (f *opentype) LoadGlyph(buf *sfnt.Buffer, ppem fixed.Int26_6, r rune) ([]sfnt.Segment, bool) { 278 g, err := f.Font.GlyphIndex(buf, r) 279 if err != nil { 280 return nil, false 281 } 282 segs, err := f.Font.LoadGlyph(buf, g, ppem, nil) 283 if err != nil { 284 return nil, false 285 } 286 return segs, true 287 }