font/sfnt: add a ppem arg to Font.LoadGlyph.
This lets us load a glyph at e.g. 12 pixels per em. Change-Id: I048b3db89af8670782953a8361afe0e6373df9b0 Reviewed-on: https://go-review.googlesource.com/37175 Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
parent
791b615328
commit
ed91dc314e
125
font/sfnt/example_test.go
Normal file
125
font/sfnt/example_test.go
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package sfnt_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/draw"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/image/font/gofont/goregular"
|
||||||
|
"golang.org/x/image/font/sfnt"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
"golang.org/x/image/vector"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleRasterizeGlyph() {
|
||||||
|
const (
|
||||||
|
ppem = 32
|
||||||
|
width = 24
|
||||||
|
height = 32
|
||||||
|
originX = 0
|
||||||
|
originY = 28
|
||||||
|
)
|
||||||
|
|
||||||
|
f, err := sfnt.Parse(goregular.TTF)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Parse: %v", err)
|
||||||
|
}
|
||||||
|
var b sfnt.Buffer
|
||||||
|
x, err := f.GlyphIndex(&b, 'G')
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("GlyphIndex: %v", err)
|
||||||
|
}
|
||||||
|
if x == 0 {
|
||||||
|
log.Fatalf("GlyphIndex: no glyph index found for the rune 'G'")
|
||||||
|
}
|
||||||
|
segments, err := f.LoadGlyph(&b, x, fixed.I(ppem), nil)
|
||||||
|
|
||||||
|
r := vector.NewRasterizer(width, height)
|
||||||
|
r.DrawOp = draw.Src
|
||||||
|
for _, seg := range segments {
|
||||||
|
// The divisions by 64 below is because the seg.Args values have type
|
||||||
|
// fixed.Int26_6, a 26.6 fixed point number, and 1<<6 == 64.
|
||||||
|
switch seg.Op {
|
||||||
|
case sfnt.SegmentOpMoveTo:
|
||||||
|
r.MoveTo(
|
||||||
|
originX+float32(seg.Args[0])/64,
|
||||||
|
originY-float32(seg.Args[1])/64,
|
||||||
|
)
|
||||||
|
case sfnt.SegmentOpLineTo:
|
||||||
|
r.LineTo(
|
||||||
|
originX+float32(seg.Args[0])/64,
|
||||||
|
originY-float32(seg.Args[1])/64,
|
||||||
|
)
|
||||||
|
case sfnt.SegmentOpQuadTo:
|
||||||
|
r.QuadTo(
|
||||||
|
originX+float32(seg.Args[0])/64,
|
||||||
|
originY-float32(seg.Args[1])/64,
|
||||||
|
originX+float32(seg.Args[2])/64,
|
||||||
|
originY-float32(seg.Args[3])/64,
|
||||||
|
)
|
||||||
|
case sfnt.SegmentOpCubeTo:
|
||||||
|
r.CubeTo(
|
||||||
|
originX+float32(seg.Args[0])/64,
|
||||||
|
originY-float32(seg.Args[1])/64,
|
||||||
|
originX+float32(seg.Args[2])/64,
|
||||||
|
originY-float32(seg.Args[3])/64,
|
||||||
|
originX+float32(seg.Args[4])/64,
|
||||||
|
originY-float32(seg.Args[5])/64,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: call ClosePath? Once overall or once per contour (i.e. MoveTo)?
|
||||||
|
|
||||||
|
dst := image.NewAlpha(image.Rect(0, 0, width, height))
|
||||||
|
r.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
|
||||||
|
|
||||||
|
const asciiArt = ".++8"
|
||||||
|
buf := make([]byte, 0, height*(width+1))
|
||||||
|
for y := 0; y < height; y++ {
|
||||||
|
for x := 0; x < width; x++ {
|
||||||
|
a := dst.AlphaAt(x, y).A
|
||||||
|
buf = append(buf, asciiArt[a>>6])
|
||||||
|
}
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
}
|
||||||
|
os.Stdout.Write(buf)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// ........................
|
||||||
|
// ........................
|
||||||
|
// ........................
|
||||||
|
// ........................
|
||||||
|
// ..........+++++++++.....
|
||||||
|
// .......+8888888888888+..
|
||||||
|
// ......8888888888888888..
|
||||||
|
// ....+8888+........++88..
|
||||||
|
// ....8888................
|
||||||
|
// ...8888.................
|
||||||
|
// ..+888+.................
|
||||||
|
// ..+888..................
|
||||||
|
// ..888+..................
|
||||||
|
// .+888+..................
|
||||||
|
// .+888...................
|
||||||
|
// .+888...................
|
||||||
|
// .+888...................
|
||||||
|
// .+888..........+++++++..
|
||||||
|
// .+888..........8888888..
|
||||||
|
// .+888+.........+++8888..
|
||||||
|
// ..888+............+888..
|
||||||
|
// ..8888............+888..
|
||||||
|
// ..+888+...........+888..
|
||||||
|
// ...8888+..........+888..
|
||||||
|
// ...+8888+.........+888..
|
||||||
|
// ....+88888+.......+888..
|
||||||
|
// .....+8888888888888888..
|
||||||
|
// .......+888888888888++..
|
||||||
|
// ..........++++++++......
|
||||||
|
// ........................
|
||||||
|
// ........................
|
||||||
|
// ........................
|
||||||
|
}
|
|
@ -653,8 +653,8 @@ func t2CAppendMoveto(p *psInterpreter) {
|
||||||
p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
|
p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
|
||||||
Op: SegmentOpMoveTo,
|
Op: SegmentOpMoveTo,
|
||||||
Args: [6]fixed.Int26_6{
|
Args: [6]fixed.Int26_6{
|
||||||
0: fixed.Int26_6(p.type2Charstrings.x) << 6,
|
0: fixed.Int26_6(p.type2Charstrings.x),
|
||||||
1: fixed.Int26_6(p.type2Charstrings.y) << 6,
|
1: fixed.Int26_6(p.type2Charstrings.y),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -663,8 +663,8 @@ func t2CAppendLineto(p *psInterpreter) {
|
||||||
p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
|
p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
|
||||||
Op: SegmentOpLineTo,
|
Op: SegmentOpLineTo,
|
||||||
Args: [6]fixed.Int26_6{
|
Args: [6]fixed.Int26_6{
|
||||||
0: fixed.Int26_6(p.type2Charstrings.x) << 6,
|
0: fixed.Int26_6(p.type2Charstrings.x),
|
||||||
1: fixed.Int26_6(p.type2Charstrings.y) << 6,
|
1: fixed.Int26_6(p.type2Charstrings.y),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -685,12 +685,12 @@ func t2CAppendCubeto(p *psInterpreter, dxa, dya, dxb, dyb, dxc, dyc int32) {
|
||||||
p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
|
p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
|
||||||
Op: SegmentOpCubeTo,
|
Op: SegmentOpCubeTo,
|
||||||
Args: [6]fixed.Int26_6{
|
Args: [6]fixed.Int26_6{
|
||||||
0: fixed.Int26_6(xa) << 6,
|
0: fixed.Int26_6(xa),
|
||||||
1: fixed.Int26_6(ya) << 6,
|
1: fixed.Int26_6(ya),
|
||||||
2: fixed.Int26_6(xb) << 6,
|
2: fixed.Int26_6(xb),
|
||||||
3: fixed.Int26_6(yb) << 6,
|
3: fixed.Int26_6(yb),
|
||||||
4: fixed.Int26_6(xc) << 6,
|
4: fixed.Int26_6(xc),
|
||||||
5: fixed.Int26_6(yc) << 6,
|
5: fixed.Int26_6(yc),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,8 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -132,6 +134,7 @@ func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, fi
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Parse: %v", err)
|
t.Fatalf("Parse: %v", err)
|
||||||
}
|
}
|
||||||
|
ppem := fixed.Int26_6(f.UnitsPerEm()) << 6
|
||||||
|
|
||||||
numGlyphs := f.NumGlyphs()
|
numGlyphs := f.NumGlyphs()
|
||||||
if numGlyphs < minNumGlyphs {
|
if numGlyphs < minNumGlyphs {
|
||||||
|
@ -144,7 +147,7 @@ func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, fi
|
||||||
iMax = firstUnsupportedGlyph
|
iMax = firstUnsupportedGlyph
|
||||||
}
|
}
|
||||||
for i, numErrors := 0, 0; i < iMax; i++ {
|
for i, numErrors := 0, 0; i < iMax; i++ {
|
||||||
if _, err := f.LoadGlyph(&buf, GlyphIndex(i), nil); err != nil {
|
if _, err := f.LoadGlyph(&buf, GlyphIndex(i), ppem, nil); err != nil {
|
||||||
t.Errorf("LoadGlyph(%d): %v", i, err)
|
t.Errorf("LoadGlyph(%d): %v", i, err)
|
||||||
numErrors++
|
numErrors++
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,6 +131,17 @@ const (
|
||||||
// display resolution (DPI) and font size (e.g. a 12 point font).
|
// display resolution (DPI) and font size (e.g. a 12 point font).
|
||||||
type Units int32
|
type Units int32
|
||||||
|
|
||||||
|
// scale returns x divided by unitsPerEm, rounded to the nearest fixed.Int26_6
|
||||||
|
// value (1/64th of a pixel).
|
||||||
|
func scale(x fixed.Int26_6, unitsPerEm Units) fixed.Int26_6 {
|
||||||
|
if x >= 0 {
|
||||||
|
x += fixed.Int26_6(unitsPerEm) / 2
|
||||||
|
} else {
|
||||||
|
x -= fixed.Int26_6(unitsPerEm) / 2
|
||||||
|
}
|
||||||
|
return x / fixed.Int26_6(unitsPerEm)
|
||||||
|
}
|
||||||
|
|
||||||
func u16(b []byte) uint16 {
|
func u16(b []byte) uint16 {
|
||||||
_ = b[1] // Bounds check hint to compiler.
|
_ = b[1] // Bounds check hint to compiler.
|
||||||
return uint16(b[0])<<8 | uint16(b[1])<<0
|
return uint16(b[0])<<8 | uint16(b[1])<<0
|
||||||
|
@ -274,6 +285,12 @@ func ParseReaderAt(src io.ReaderAt) (*Font, error) {
|
||||||
//
|
//
|
||||||
// The Font methods that don't take a *Buffer argument are always safe to call
|
// The Font methods that don't take a *Buffer argument are always safe to call
|
||||||
// concurrently.
|
// concurrently.
|
||||||
|
//
|
||||||
|
// Some methods provide lengths or co-ordinates, e.g. bounds, font metrics and
|
||||||
|
// control points. All of these methods take a ppem parameter, which is the
|
||||||
|
// number of pixels in 1 em, expressed as a 26.6 fixed point value. For
|
||||||
|
// example, if 1 em is 10 pixels then ppem is fixed.I(10), which equals
|
||||||
|
// fixed.Int26_6(10 << 6).
|
||||||
type Font struct {
|
type Font struct {
|
||||||
src source
|
src source
|
||||||
|
|
||||||
|
@ -623,15 +640,16 @@ func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) ([]byte, error) {
|
||||||
|
|
||||||
// LoadGlyphOptions are the options to the Font.LoadGlyph method.
|
// LoadGlyphOptions are the options to the Font.LoadGlyph method.
|
||||||
type LoadGlyphOptions struct {
|
type LoadGlyphOptions struct {
|
||||||
// TODO: scale / transform / hinting.
|
// TODO: transform / hinting.
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadGlyph returns the vector segments for the x'th glyph.
|
// LoadGlyph returns the vector segments for the x'th glyph. ppem is the number
|
||||||
|
// of pixels in 1 em.
|
||||||
//
|
//
|
||||||
// If b is non-nil, the segments become invalid to use once b is re-used.
|
// If b is non-nil, the segments become invalid to use once b is re-used.
|
||||||
//
|
//
|
||||||
// It returns ErrNotFound if the glyph index is out of range.
|
// It returns ErrNotFound if the glyph index is out of range.
|
||||||
func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, opts *LoadGlyphOptions) ([]Segment, error) {
|
func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *LoadGlyphOptions) ([]Segment, error) {
|
||||||
if b == nil {
|
if b == nil {
|
||||||
b = &Buffer{}
|
b = &Buffer{}
|
||||||
}
|
}
|
||||||
|
@ -656,7 +674,19 @@ func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, opts *LoadGlyphOptions) ([]Seg
|
||||||
b.segments = segments
|
b.segments = segments
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: look at opts to scale / transform / hint the Buffer.segments.
|
// Scale the segments. If we want to support hinting, we'll have to push
|
||||||
|
// the scaling computation into the PostScript / TrueType specific glyph
|
||||||
|
// loading code, such as the appendGlyfSegments body, since TrueType
|
||||||
|
// hinting bytecode works on the scaled glyph vectors. For now, though,
|
||||||
|
// it's simpler to scale as a post-processing step.
|
||||||
|
for i := range b.segments {
|
||||||
|
s := &b.segments[i]
|
||||||
|
for j := range s.Args {
|
||||||
|
s.Args[j] = scale(s.Args[j]*ppem, f.cached.unitsPerEm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: look at opts to transform / hint the Buffer.segments.
|
||||||
|
|
||||||
return b.segments, nil
|
return b.segments, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ package sfnt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -24,6 +25,16 @@ func moveTo(xa, ya int) Segment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func moveTo26_6(xa, ya fixed.Int26_6) Segment {
|
||||||
|
return Segment{
|
||||||
|
Op: SegmentOpMoveTo,
|
||||||
|
Args: [6]fixed.Int26_6{
|
||||||
|
0: xa,
|
||||||
|
1: ya,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func lineTo(xa, ya int) Segment {
|
func lineTo(xa, ya int) Segment {
|
||||||
return Segment{
|
return Segment{
|
||||||
Op: SegmentOpLineTo,
|
Op: SegmentOpLineTo,
|
||||||
|
@ -34,6 +45,16 @@ func lineTo(xa, ya int) Segment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lineTo26_6(xa, ya fixed.Int26_6) Segment {
|
||||||
|
return Segment{
|
||||||
|
Op: SegmentOpLineTo,
|
||||||
|
Args: [6]fixed.Int26_6{
|
||||||
|
0: xa,
|
||||||
|
1: ya,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func quadTo(xa, ya, xb, yb int) Segment {
|
func quadTo(xa, ya, xb, yb int) Segment {
|
||||||
return Segment{
|
return Segment{
|
||||||
Op: SegmentOpQuadTo,
|
Op: SegmentOpQuadTo,
|
||||||
|
@ -60,6 +81,20 @@ func cubeTo(xa, ya, xb, yb, xc, yc int) Segment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkSegmentsEqual(got, want []Segment) error {
|
||||||
|
if len(got) != len(want) {
|
||||||
|
return fmt.Errorf("got %d elements, want %d\noverall:\ngot %v\nwant %v",
|
||||||
|
len(got), len(want), got, want)
|
||||||
|
}
|
||||||
|
for i, g := range got {
|
||||||
|
if w := want[i]; g != w {
|
||||||
|
return fmt.Errorf("element %d:\ngot %v\nwant %v\noverall:\ngot %v\nwant %v",
|
||||||
|
i, g, w, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestTrueTypeParse(t *testing.T) {
|
func TestTrueTypeParse(t *testing.T) {
|
||||||
f, err := Parse(goregular.TTF)
|
f, err := Parse(goregular.TTF)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -389,38 +424,30 @@ func TestTrueTypeSegments(t *testing.T) {
|
||||||
func testSegments(t *testing.T, filename string, wants [][]Segment) {
|
func testSegments(t *testing.T, filename string, wants [][]Segment) {
|
||||||
data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/" + filename))
|
data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/" + filename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatalf("ReadFile: %v", err)
|
||||||
}
|
}
|
||||||
f, err := Parse(data)
|
f, err := Parse(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatalf("Parse: %v", err)
|
||||||
}
|
}
|
||||||
|
ppem := fixed.Int26_6(f.UnitsPerEm()) << 6
|
||||||
|
|
||||||
if ng := f.NumGlyphs(); ng != len(wants) {
|
if ng := f.NumGlyphs(); ng != len(wants) {
|
||||||
t.Fatalf("NumGlyphs: got %d, want %d", ng, len(wants))
|
t.Fatalf("NumGlyphs: got %d, want %d", ng, len(wants))
|
||||||
}
|
}
|
||||||
var b Buffer
|
var b Buffer
|
||||||
loop:
|
|
||||||
for i, want := range wants {
|
for i, want := range wants {
|
||||||
got, err := f.LoadGlyph(&b, GlyphIndex(i), nil)
|
got, err := f.LoadGlyph(&b, GlyphIndex(i), ppem, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("i=%d: LoadGlyph: %v", i, err)
|
t.Errorf("i=%d: LoadGlyph: %v", i, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(got) != len(want) {
|
if err := checkSegmentsEqual(got, want); err != nil {
|
||||||
t.Errorf("i=%d: got %d elements, want %d\noverall:\ngot %v\nwant %v",
|
t.Errorf("i=%d: %v", i, err)
|
||||||
i, len(got), len(want), got, want)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for j, g := range got {
|
|
||||||
if w := want[j]; g != w {
|
|
||||||
t.Errorf("i=%d: element %d:\ngot %v\nwant %v\noverall:\ngot %v\nwant %v",
|
|
||||||
i, j, g, w, got, want)
|
|
||||||
continue loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if _, err := f.LoadGlyph(nil, 0xffff, nil); err != ErrNotFound {
|
if _, err := f.LoadGlyph(nil, 0xffff, ppem, nil); err != ErrNotFound {
|
||||||
t.Errorf("LoadGlyph(..., 0xffff, ...):\ngot %v\nwant %v", err, ErrNotFound)
|
t.Errorf("LoadGlyph(..., 0xffff, ...):\ngot %v\nwant %v", err, ErrNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,6 +459,60 @@ loop:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPPEM(t *testing.T) {
|
||||||
|
data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/glyfTest.ttf"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadFile: %v", err)
|
||||||
|
}
|
||||||
|
f, err := Parse(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse: %v", err)
|
||||||
|
}
|
||||||
|
var b Buffer
|
||||||
|
x, err := f.GlyphIndex(&b, '1')
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GlyphIndex: %v", err)
|
||||||
|
}
|
||||||
|
if x == 0 {
|
||||||
|
t.Fatalf("GlyphIndex: no glyph index found for the rune '1'")
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
ppem fixed.Int26_6
|
||||||
|
want []Segment
|
||||||
|
}{{
|
||||||
|
ppem: fixed.I(12),
|
||||||
|
want: []Segment{
|
||||||
|
moveTo26_6(77, 0),
|
||||||
|
lineTo26_6(77, 614),
|
||||||
|
lineTo26_6(230, 614),
|
||||||
|
lineTo26_6(230, 0),
|
||||||
|
lineTo26_6(77, 0),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
ppem: fixed.I(2048),
|
||||||
|
want: []Segment{
|
||||||
|
moveTo(205, 0),
|
||||||
|
lineTo(205, 1638),
|
||||||
|
lineTo(614, 1638),
|
||||||
|
lineTo(614, 0),
|
||||||
|
lineTo(205, 0),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
got, err := f.LoadGlyph(&b, x, tc.ppem, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("i=%d: LoadGlyph: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := checkSegmentsEqual(got, tc.want); err != nil {
|
||||||
|
t.Errorf("i=%d: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGlyphName(t *testing.T) {
|
func TestGlyphName(t *testing.T) {
|
||||||
f, err := Parse(goregular.TTF)
|
f, err := Parse(goregular.TTF)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -357,9 +357,18 @@ func (g *glyfIter) nextSegment() (ok bool) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert the tuple (g.x, g.y) to a fixed.Point26_6, since the latter
|
||||||
|
// is what's held in a Segment. The input (g.x, g.y) is a pair of int16
|
||||||
|
// values, measured in font units, since that is what the underlying
|
||||||
|
// format provides. The output is a pair of fixed.Int26_6 values. A
|
||||||
|
// fixed.Int26_6 usually represents a 26.6 fixed number of pixels, but
|
||||||
|
// this here is just a straight numerical conversion, with no scaling
|
||||||
|
// factor. A later step scales the Segment.Args values by such a factor
|
||||||
|
// to convert e.g. 1792 font units to 10.5 pixels at 2048 font units
|
||||||
|
// per em and 12 ppem (pixels per em).
|
||||||
p := fixed.Point26_6{
|
p := fixed.Point26_6{
|
||||||
X: fixed.Int26_6(g.x) << 6,
|
X: fixed.Int26_6(g.x),
|
||||||
Y: fixed.Int26_6(g.y) << 6,
|
Y: fixed.Int26_6(g.y),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !g.firstOnCurveValid {
|
if !g.firstOnCurveValid {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user