opentype.go raw

   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  	"bytes"
   9  	"io"
  10  	"unicode"
  11  	"unicode/utf8"
  12  
  13  	"golang.org/x/image/font"
  14  	"golang.org/x/image/font/sfnt"
  15  	"golang.org/x/image/math/fixed"
  16  
  17  	"github.com/p9c/p9/pkg/gel/gio/f32"
  18  	"github.com/p9c/p9/pkg/gel/gio/op"
  19  	"github.com/p9c/p9/pkg/gel/gio/op/clip"
  20  	"github.com/p9c/p9/pkg/gel/gio/text"
  21  )
  22  
  23  // Font implements text.Face. Its methods are safe to use
  24  // concurrently.
  25  type Font struct {
  26  	font *sfnt.Font
  27  }
  28  
  29  // Collection is a collection of one or more fonts. When used as a text.Face,
  30  // each rune will be assigned a glyph from the first font in the collection
  31  // that supports it.
  32  type Collection struct {
  33  	fonts []*opentype
  34  }
  35  
  36  type opentype struct {
  37  	Font    *sfnt.Font
  38  	Hinting font.Hinting
  39  }
  40  
  41  // a glyph represents a rune and its advance according to a Font.
  42  // TODO: remove this type and work on io.Readers directly.
  43  type glyph struct {
  44  	Rune    rune
  45  	Advance fixed.Int26_6
  46  }
  47  
  48  // NewFont parses an SFNT font, such as TTF or OTF data, from a []byte
  49  // data source.
  50  func Parse(src []byte) (*Font, error) {
  51  	fnt, err := sfnt.Parse(src)
  52  	if err != nil {
  53  		return nil, err
  54  	}
  55  	return &Font{font: fnt}, nil
  56  }
  57  
  58  // ParseCollection parses an SFNT font collection, such as TTC or OTC data,
  59  // from a []byte data source.
  60  //
  61  // If passed data for a single font, a TTF or OTF instead of a TTC or OTC,
  62  // it will return a collection containing 1 font.
  63  func ParseCollection(src []byte) (*Collection, error) {
  64  	c, err := sfnt.ParseCollection(src)
  65  	if err != nil {
  66  		return nil, err
  67  	}
  68  	return newCollectionFrom(c)
  69  }
  70  
  71  // ParseCollectionReaderAt parses an SFNT collection, such as TTC or OTC data,
  72  // from an io.ReaderAt data source.
  73  //
  74  // If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it
  75  // will return a collection containing 1 font.
  76  func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) {
  77  	c, err := sfnt.ParseCollectionReaderAt(src)
  78  	if err != nil {
  79  		return nil, err
  80  	}
  81  	return newCollectionFrom(c)
  82  }
  83  
  84  func newCollectionFrom(coll *sfnt.Collection) (*Collection, error) {
  85  	fonts := make([]*opentype, coll.NumFonts())
  86  	for i := range fonts {
  87  		fnt, err := coll.Font(i)
  88  		if err != nil {
  89  			return nil, err
  90  		}
  91  		fonts[i] = &opentype{
  92  			Font:    fnt,
  93  			Hinting: font.HintingFull,
  94  		}
  95  	}
  96  	return &Collection{fonts: fonts}, nil
  97  }
  98  
  99  // NumFonts returns the number of fonts in the collection.
 100  func (c *Collection) NumFonts() int {
 101  	return len(c.fonts)
 102  }
 103  
 104  // Font returns the i'th font in the collection.
 105  func (c *Collection) Font(i int) (*Font, error) {
 106  	if i < 0 || len(c.fonts) <= i {
 107  		return nil, sfnt.ErrNotFound
 108  	}
 109  	return &Font{font: c.fonts[i].Font}, nil
 110  }
 111  
 112  func (f *Font) Layout(ppem fixed.Int26_6, maxWidth int, txt io.Reader) ([]text.Line, error) {
 113  	glyphs, err := readGlyphs(txt)
 114  	if err != nil {
 115  		return nil, err
 116  	}
 117  	fonts := []*opentype{{Font: f.font, Hinting: font.HintingFull}}
 118  	var buf sfnt.Buffer
 119  	return layoutText(&buf, ppem, maxWidth, fonts, glyphs)
 120  }
 121  
 122  func (f *Font) Shape(ppem fixed.Int26_6, str text.Layout) op.CallOp {
 123  	var buf sfnt.Buffer
 124  	return textPath(&buf, ppem, []*opentype{{Font: f.font, Hinting: font.HintingFull}}, str)
 125  }
 126  
 127  func (f *Font) Metrics(ppem fixed.Int26_6) font.Metrics {
 128  	o := &opentype{Font: f.font, Hinting: font.HintingFull}
 129  	var buf sfnt.Buffer
 130  	return o.Metrics(&buf, ppem)
 131  }
 132  
 133  func (c *Collection) Layout(ppem fixed.Int26_6, maxWidth int, txt io.Reader) ([]text.Line, error) {
 134  	glyphs, err := readGlyphs(txt)
 135  	if err != nil {
 136  		return nil, err
 137  	}
 138  	var buf sfnt.Buffer
 139  	return layoutText(&buf, ppem, maxWidth, c.fonts, glyphs)
 140  }
 141  
 142  func (c *Collection) Shape(ppem fixed.Int26_6, str text.Layout) op.CallOp {
 143  	var buf sfnt.Buffer
 144  	return textPath(&buf, ppem, c.fonts, str)
 145  }
 146  
 147  func fontForGlyph(buf *sfnt.Buffer, fonts []*opentype, r rune) *opentype {
 148  	if len(fonts) < 1 {
 149  		return nil
 150  	}
 151  	for _, f := range fonts {
 152  		if f.HasGlyph(buf, r) {
 153  			return f
 154  		}
 155  	}
 156  	return fonts[0] // Use replacement character from the first font if necessary
 157  }
 158  
 159  func layoutText(sbuf *sfnt.Buffer, ppem fixed.Int26_6, maxWidth int, fonts []*opentype, glyphs []glyph) ([]text.Line, error) {
 160  	var lines []text.Line
 161  	var nextLine text.Line
 162  	updateBounds := func(f *opentype) {
 163  		m := f.Metrics(sbuf, ppem)
 164  		if m.Ascent > nextLine.Ascent {
 165  			nextLine.Ascent = m.Ascent
 166  		}
 167  		// m.Height is equal to m.Ascent + m.Descent + linegap.
 168  		// Compute the descent including the linegap.
 169  		descent := m.Height - m.Ascent
 170  		if descent > nextLine.Descent {
 171  			nextLine.Descent = descent
 172  		}
 173  		b := f.Bounds(sbuf, ppem)
 174  		nextLine.Bounds = nextLine.Bounds.Union(b)
 175  	}
 176  	maxDotX := fixed.I(maxWidth)
 177  	type state struct {
 178  		r     rune
 179  		f     *opentype
 180  		adv   fixed.Int26_6
 181  		x     fixed.Int26_6
 182  		idx   int
 183  		len   int
 184  		valid bool
 185  	}
 186  	var prev, word state
 187  	endLine := func() {
 188  		if prev.f == nil && len(fonts) > 0 {
 189  			prev.f = fonts[0]
 190  		}
 191  		updateBounds(prev.f)
 192  		nextLine.Layout = toLayout(glyphs[:prev.idx:prev.idx])
 193  		nextLine.Width = prev.x + prev.adv
 194  		nextLine.Bounds.Max.X += prev.x
 195  		lines = append(lines, nextLine)
 196  		glyphs = glyphs[prev.idx:]
 197  		nextLine = text.Line{}
 198  		prev = state{}
 199  		word = state{}
 200  	}
 201  	for prev.idx < len(glyphs) {
 202  		g := &glyphs[prev.idx]
 203  		next := state{
 204  			r:   g.Rune,
 205  			f:   fontForGlyph(sbuf, fonts, g.Rune),
 206  			idx: prev.idx + 1,
 207  			len: prev.len + utf8.RuneLen(g.Rune),
 208  			x:   prev.x + prev.adv,
 209  		}
 210  		if next.f != nil {
 211  			if next.f != prev.f {
 212  				updateBounds(next.f)
 213  			}
 214  			next.adv, next.valid = next.f.GlyphAdvance(sbuf, ppem, g.Rune)
 215  		}
 216  		if g.Rune == '\n' {
 217  			// The newline is zero width; use the previous
 218  			// character for line measurements.
 219  			prev.idx = next.idx
 220  			prev.len = next.len
 221  			endLine()
 222  			continue
 223  		}
 224  		var k fixed.Int26_6
 225  		if prev.valid && next.f != nil {
 226  			k = next.f.Kern(sbuf, ppem, prev.r, next.r)
 227  		}
 228  		// Break the line if we're out of space.
 229  		if prev.idx > 0 && next.x+next.adv+k > maxDotX {
 230  			// If the line contains no word breaks, break off the last rune.
 231  			if word.idx == 0 {
 232  				word = prev
 233  			}
 234  			next.x -= word.x + word.adv
 235  			next.idx -= word.idx
 236  			next.len -= word.len
 237  			prev = word
 238  			endLine()
 239  		} else if k != 0 {
 240  			glyphs[prev.idx-1].Advance += k
 241  			next.x += k
 242  		}
 243  		g.Advance = next.adv
 244  		if unicode.IsSpace(g.Rune) {
 245  			word = next
 246  		}
 247  		prev = next
 248  	}
 249  	endLine()
 250  	return lines, nil
 251  }
 252  
 253  // toLayout converts a slice of glyphs to a text.Layout.
 254  func toLayout(glyphs []glyph) text.Layout {
 255  	var buf bytes.Buffer
 256  	advs := make([]fixed.Int26_6, len(glyphs))
 257  	for i, g := range glyphs {
 258  		buf.WriteRune(g.Rune)
 259  		advs[i] = glyphs[i].Advance
 260  	}
 261  	return text.Layout{Text: buf.String(), Advances: advs}
 262  }
 263  
 264  func textPath(buf *sfnt.Buffer, ppem fixed.Int26_6, fonts []*opentype, str text.Layout) op.CallOp {
 265  	var lastPos f32.Point
 266  	var builder clip.Path
 267  	ops := new(op.Ops)
 268  	m := op.Record(ops)
 269  	var x fixed.Int26_6
 270  	builder.Begin(ops)
 271  	rune := 0
 272  	for _, r := range str.Text {
 273  		if !unicode.IsSpace(r) {
 274  			f := fontForGlyph(buf, fonts, r)
 275  			if f == nil {
 276  				continue
 277  			}
 278  			segs, ok := f.LoadGlyph(buf, ppem, r)
 279  			if !ok {
 280  				continue
 281  			}
 282  			// Move to glyph position.
 283  			pos := f32.Point{
 284  				X: float32(x) / 64,
 285  			}
 286  			builder.Move(pos.Sub(lastPos))
 287  			lastPos = pos
 288  			var lastArg f32.Point
 289  			// Convert sfnt.Segments to relative segments.
 290  			for _, fseg := range segs {
 291  				nargs := 1
 292  				switch fseg.Op {
 293  				case sfnt.SegmentOpQuadTo:
 294  					nargs = 2
 295  				case sfnt.SegmentOpCubeTo:
 296  					nargs = 3
 297  				}
 298  				var args [3]f32.Point
 299  				for i := 0; i < nargs; i++ {
 300  					a := f32.Point{
 301  						X: float32(fseg.Args[i].X) / 64,
 302  						Y: float32(fseg.Args[i].Y) / 64,
 303  					}
 304  					args[i] = a.Sub(lastArg)
 305  					if i == nargs-1 {
 306  						lastArg = a
 307  					}
 308  				}
 309  				switch fseg.Op {
 310  				case sfnt.SegmentOpMoveTo:
 311  					builder.Move(args[0])
 312  				case sfnt.SegmentOpLineTo:
 313  					builder.Line(args[0])
 314  				case sfnt.SegmentOpQuadTo:
 315  					builder.Quad(args[0], args[1])
 316  				case sfnt.SegmentOpCubeTo:
 317  					builder.Cube(args[0], args[1], args[2])
 318  				default:
 319  					panic("unsupported segment op")
 320  				}
 321  			}
 322  			lastPos = lastPos.Add(lastArg)
 323  		}
 324  		x += str.Advances[rune]
 325  		rune++
 326  	}
 327  	clip.Outline{
 328  		Path: builder.End(),
 329  	}.Op().Add(ops)
 330  	return m.Stop()
 331  }
 332  
 333  func readGlyphs(r io.Reader) ([]glyph, error) {
 334  	var glyphs []glyph
 335  	buf := make([]byte, 0, 1024)
 336  	for {
 337  		n, err := r.Read(buf[len(buf):cap(buf)])
 338  		buf = buf[:len(buf)+n]
 339  		lim := len(buf)
 340  		// Read full runes if possible.
 341  		if err != io.EOF {
 342  			lim -= utf8.UTFMax - 1
 343  		}
 344  		i := 0
 345  		for i < lim {
 346  			c, s := utf8.DecodeRune(buf[i:])
 347  			i += s
 348  			glyphs = append(glyphs, glyph{Rune: c})
 349  		}
 350  		n = copy(buf, buf[i:])
 351  		buf = buf[:n]
 352  		if err != nil {
 353  			if err == io.EOF {
 354  				break
 355  			}
 356  			return nil, err
 357  		}
 358  	}
 359  	return glyphs, nil
 360  }
 361  
 362  func (f *opentype) HasGlyph(buf *sfnt.Buffer, r rune) bool {
 363  	g, err := f.Font.GlyphIndex(buf, r)
 364  	return g != 0 && err == nil
 365  }
 366  
 367  func (f *opentype) GlyphAdvance(buf *sfnt.Buffer, ppem fixed.Int26_6, r rune) (advance fixed.Int26_6, ok bool) {
 368  	g, err := f.Font.GlyphIndex(buf, r)
 369  	if err != nil {
 370  		return 0, false
 371  	}
 372  	adv, err := f.Font.GlyphAdvance(buf, g, ppem, f.Hinting)
 373  	return adv, err == nil
 374  }
 375  
 376  func (f *opentype) Kern(buf *sfnt.Buffer, ppem fixed.Int26_6, r0, r1 rune) fixed.Int26_6 {
 377  	g0, err := f.Font.GlyphIndex(buf, r0)
 378  	if err != nil {
 379  		return 0
 380  	}
 381  	g1, err := f.Font.GlyphIndex(buf, r1)
 382  	if err != nil {
 383  		return 0
 384  	}
 385  	adv, err := f.Font.Kern(buf, g0, g1, ppem, f.Hinting)
 386  	if err != nil {
 387  		return 0
 388  	}
 389  	return adv
 390  }
 391  
 392  func (f *opentype) Metrics(buf *sfnt.Buffer, ppem fixed.Int26_6) font.Metrics {
 393  	m, _ := f.Font.Metrics(buf, ppem, f.Hinting)
 394  	return m
 395  }
 396  
 397  func (f *opentype) Bounds(buf *sfnt.Buffer, ppem fixed.Int26_6) fixed.Rectangle26_6 {
 398  	r, _ := f.Font.Bounds(buf, ppem, f.Hinting)
 399  	return r
 400  }
 401  
 402  func (f *opentype) LoadGlyph(buf *sfnt.Buffer, ppem fixed.Int26_6, r rune) ([]sfnt.Segment, bool) {
 403  	g, err := f.Font.GlyphIndex(buf, r)
 404  	if err != nil {
 405  		return nil, false
 406  	}
 407  	segs, err := f.Font.LoadGlyph(buf, g, ppem, nil)
 408  	if err != nil {
 409  		return nil, false
 410  	}
 411  	return segs, true
 412  }
 413