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  }