font/sfnt: implement Font.Kern.
Change-Id: I9ffb93dd3ee08b8871dadf5bc36578710b800199 Reviewed-on: https://go-review.googlesource.com/37410 Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
parent
b952c941a6
commit
c8ae36c95e
|
@ -31,11 +31,13 @@ go test golang.org/x/image/font/sfnt -test.run=ProprietaryMicrosoft -args -propr
|
||||||
// TODO: enable Apple/Microsoft tests by default on Darwin/Windows?
|
// TODO: enable Apple/Microsoft tests by default on Darwin/Windows?
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/image/font"
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -134,7 +136,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
|
ppem := fixed.Int26_6(f.UnitsPerEm())
|
||||||
qualifiedFilename := proprietor + "/" + filename
|
qualifiedFilename := proprietor + "/" + filename
|
||||||
var buf Buffer
|
var buf Buffer
|
||||||
|
|
||||||
|
@ -183,6 +185,33 @@ func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, fi
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kernLoop:
|
||||||
|
for _, tc := range proprietaryKernTestCases[qualifiedFilename] {
|
||||||
|
var indexes [2]GlyphIndex
|
||||||
|
for i := range indexes {
|
||||||
|
x, err := f.GlyphIndex(&buf, tc.runes[i])
|
||||||
|
if x == 0 && err == nil {
|
||||||
|
err = errors.New("no glyph index found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("GlyphIndex(%q): %v", tc.runes[0], err)
|
||||||
|
continue kernLoop
|
||||||
|
}
|
||||||
|
indexes[i] = x
|
||||||
|
}
|
||||||
|
kern, err := f.Kern(&buf, indexes[0], indexes[1], tc.ppem, tc.hinting)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Kern(%q, %q, ppem=%d, hinting=%v): %v",
|
||||||
|
tc.runes[0], tc.runes[1], tc.ppem, tc.hinting, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := Units(kern); got != tc.want {
|
||||||
|
t.Errorf("Kern(%q, %q, ppem=%d, hinting=%v): got %d, want %d",
|
||||||
|
tc.runes[0], tc.runes[1], tc.ppem, tc.hinting, got, tc.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// proprietaryVersions holds the expected version string of each proprietary
|
// proprietaryVersions holds the expected version string of each proprietary
|
||||||
|
@ -282,3 +311,45 @@ var proprietaryGlyphIndexTestCases = map[string]map[rune]GlyphIndex{
|
||||||
'\uf042': 37, // PRIVATE USE AREA
|
'\uf042': 37, // PRIVATE USE AREA
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type kernTestCase struct {
|
||||||
|
ppem fixed.Int26_6
|
||||||
|
hinting font.Hinting
|
||||||
|
runes [2]rune
|
||||||
|
want Units
|
||||||
|
}
|
||||||
|
|
||||||
|
// proprietaryKernTestCases hold a sample of each font's kerning pairs. The
|
||||||
|
// numerical values can be verified by running the ttx tool.
|
||||||
|
var proprietaryKernTestCases = map[string][]kernTestCase{
|
||||||
|
"microsoft/Arial.ttf": {
|
||||||
|
{2048, font.HintingNone, [2]rune{'A', 'V'}, -152},
|
||||||
|
// U+03B8 GREEK SMALL LETTER THETA
|
||||||
|
// U+03BB GREEK SMALL LETTER LAMDA
|
||||||
|
{2048, font.HintingNone, [2]rune{'\u03b8', '\u03bb'}, -39},
|
||||||
|
{2048, font.HintingNone, [2]rune{'\u03bb', '\u03b8'}, -0},
|
||||||
|
},
|
||||||
|
"microsoft/Comic_Sans_MS.ttf": {
|
||||||
|
{2048, font.HintingNone, [2]rune{'A', 'V'}, 0},
|
||||||
|
},
|
||||||
|
"microsoft/Times_New_Roman.ttf": {
|
||||||
|
{768, font.HintingNone, [2]rune{'A', 'V'}, -99},
|
||||||
|
{768, font.HintingFull, [2]rune{'A', 'V'}, -128},
|
||||||
|
{2048, font.HintingNone, [2]rune{'A', 'A'}, 0},
|
||||||
|
{2048, font.HintingNone, [2]rune{'A', 'T'}, -227},
|
||||||
|
{2048, font.HintingNone, [2]rune{'A', 'V'}, -264},
|
||||||
|
{2048, font.HintingNone, [2]rune{'T', 'A'}, -164},
|
||||||
|
{2048, font.HintingNone, [2]rune{'T', 'T'}, 0},
|
||||||
|
{2048, font.HintingNone, [2]rune{'T', 'V'}, 0},
|
||||||
|
{2048, font.HintingNone, [2]rune{'V', 'A'}, -264},
|
||||||
|
{2048, font.HintingNone, [2]rune{'V', 'T'}, 0},
|
||||||
|
{2048, font.HintingNone, [2]rune{'V', 'V'}, 0},
|
||||||
|
// U+0390 GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
|
||||||
|
// U+0393 GREEK CAPITAL LETTER GAMMA
|
||||||
|
{2048, font.HintingNone, [2]rune{'\u0390', '\u0393'}, 0},
|
||||||
|
{2048, font.HintingNone, [2]rune{'\u0393', '\u0390'}, 76},
|
||||||
|
},
|
||||||
|
"microsoft/Webdings.ttf": {
|
||||||
|
{2048, font.HintingNone, [2]rune{'\uf041', '\uf042'}, 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"golang.org/x/image/font"
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
"golang.org/x/text/encoding/charmap"
|
"golang.org/x/text/encoding/charmap"
|
||||||
)
|
)
|
||||||
|
@ -63,6 +64,7 @@ var (
|
||||||
errInvalidCmapTable = errors.New("sfnt: invalid cmap table")
|
errInvalidCmapTable = errors.New("sfnt: invalid cmap table")
|
||||||
errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
|
errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
|
||||||
errInvalidHeadTable = errors.New("sfnt: invalid head table")
|
errInvalidHeadTable = errors.New("sfnt: invalid head table")
|
||||||
|
errInvalidKernTable = errors.New("sfnt: invalid kern table")
|
||||||
errInvalidLocaTable = errors.New("sfnt: invalid loca table")
|
errInvalidLocaTable = errors.New("sfnt: invalid loca table")
|
||||||
errInvalidLocationData = errors.New("sfnt: invalid location data")
|
errInvalidLocationData = errors.New("sfnt: invalid location data")
|
||||||
errInvalidMaxpTable = errors.New("sfnt: invalid maxp table")
|
errInvalidMaxpTable = errors.New("sfnt: invalid maxp table")
|
||||||
|
@ -78,6 +80,7 @@ var (
|
||||||
errUnsupportedCmapEncodings = errors.New("sfnt: unsupported cmap encodings")
|
errUnsupportedCmapEncodings = errors.New("sfnt: unsupported cmap encodings")
|
||||||
errUnsupportedCompoundGlyph = errors.New("sfnt: unsupported compound glyph")
|
errUnsupportedCompoundGlyph = errors.New("sfnt: unsupported compound glyph")
|
||||||
errUnsupportedGlyphDataLength = errors.New("sfnt: unsupported glyph data length")
|
errUnsupportedGlyphDataLength = errors.New("sfnt: unsupported glyph data length")
|
||||||
|
errUnsupportedKernTable = errors.New("sfnt: unsupported kern table")
|
||||||
errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
|
errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
|
||||||
errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments")
|
errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments")
|
||||||
errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints")
|
errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints")
|
||||||
|
@ -286,11 +289,17 @@ 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
|
// Some methods provide lengths or coordinates, e.g. bounds, font metrics and
|
||||||
// control points. All of these methods take a ppem parameter, which is the
|
// 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
|
// 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
|
// example, if 1 em is 10 pixels then ppem is fixed.I(10), which equals
|
||||||
// fixed.Int26_6(10 << 6).
|
// fixed.Int26_6(10 << 6).
|
||||||
|
//
|
||||||
|
// To get those lengths or coordinates in terms of font units instead of
|
||||||
|
// pixels, use ppem = fixed.Int26_6(f.UnitsPerEm()) and if those methods take a
|
||||||
|
// font.Hinting parameter, use font.HintingNone. The return values will have
|
||||||
|
// type fixed.Int26_6, but those numbers can be converted back to Units with no
|
||||||
|
// further scaling necessary.
|
||||||
type Font struct {
|
type Font struct {
|
||||||
src source
|
src source
|
||||||
|
|
||||||
|
@ -327,12 +336,15 @@ type Font struct {
|
||||||
// https://www.microsoft.com/typography/otspec/otff.htm#otttables
|
// https://www.microsoft.com/typography/otspec/otff.htm#otttables
|
||||||
// "Other OpenType Tables".
|
// "Other OpenType Tables".
|
||||||
//
|
//
|
||||||
// TODO: hdmx, kern, vmtx? Others?
|
// TODO: hdmx, vmtx? Others?
|
||||||
|
kern table
|
||||||
|
|
||||||
cached struct {
|
cached struct {
|
||||||
glyphIndex func(f *Font, b *Buffer, r rune) (GlyphIndex, error)
|
glyphIndex func(f *Font, b *Buffer, r rune) (GlyphIndex, error)
|
||||||
indexToLocFormat bool // false means short, true means long.
|
indexToLocFormat bool // false means short, true means long.
|
||||||
isPostScript bool
|
isPostScript bool
|
||||||
|
kernNumPairs int32
|
||||||
|
kernOffset int32
|
||||||
postTableVersion uint32
|
postTableVersion uint32
|
||||||
unitsPerEm Units
|
unitsPerEm Units
|
||||||
|
|
||||||
|
@ -375,6 +387,10 @@ func (f *Font) initialize() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
buf, err = f.parseKern(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
buf, err = f.parsePost(buf)
|
buf, err = f.parsePost(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -444,6 +460,8 @@ func (f *Font) initializeTables(buf []byte) ([]byte, error) {
|
||||||
f.hhea = table{o, n}
|
f.hhea = table{o, n}
|
||||||
case 0x686d7478:
|
case 0x686d7478:
|
||||||
f.hmtx = table{o, n}
|
f.hmtx = table{o, n}
|
||||||
|
case 0x6b65726e:
|
||||||
|
f.kern = table{o, n}
|
||||||
case 0x6c6f6361:
|
case 0x6c6f6361:
|
||||||
f.loca = table{o, n}
|
f.loca = table{o, n}
|
||||||
case 0x6d617870:
|
case 0x6d617870:
|
||||||
|
@ -542,6 +560,90 @@ func (f *Font) parseHead(buf []byte) ([]byte, error) {
|
||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Font) parseKern(buf []byte) ([]byte, error) {
|
||||||
|
// https://www.microsoft.com/typography/otspec/kern.htm
|
||||||
|
|
||||||
|
if f.kern.length == 0 {
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
const headerSize = 4
|
||||||
|
if f.kern.length < headerSize {
|
||||||
|
return nil, errInvalidKernTable
|
||||||
|
}
|
||||||
|
buf, err := f.src.view(buf, int(f.kern.offset), headerSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
offset := int(f.kern.offset) + headerSize
|
||||||
|
length := int(f.kern.length) - headerSize
|
||||||
|
|
||||||
|
switch version := u16(buf); version {
|
||||||
|
case 0:
|
||||||
|
// TODO: support numTables != 1. Testing that requires finding such a font.
|
||||||
|
if numTables := int(u16(buf[2:])); numTables != 1 {
|
||||||
|
return nil, errUnsupportedKernTable
|
||||||
|
}
|
||||||
|
return f.parseKernVersion0(buf, offset, length)
|
||||||
|
case 1:
|
||||||
|
// TODO: find such a (proprietary?) font, and support it. Both of
|
||||||
|
// https://www.microsoft.com/typography/otspec/kern.htm
|
||||||
|
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html
|
||||||
|
// say that such fonts work on Mac OS but not on Windows.
|
||||||
|
}
|
||||||
|
return nil, errUnsupportedKernTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Font) parseKernVersion0(buf []byte, offset, length int) ([]byte, error) {
|
||||||
|
const headerSize = 6
|
||||||
|
if length < headerSize {
|
||||||
|
return nil, errInvalidKernTable
|
||||||
|
}
|
||||||
|
buf, err := f.src.view(buf, offset, headerSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if version := u16(buf); version != 0 {
|
||||||
|
return nil, errUnsupportedKernTable
|
||||||
|
}
|
||||||
|
subtableLength := int(u16(buf[2:]))
|
||||||
|
if subtableLength < headerSize || length < subtableLength {
|
||||||
|
return nil, errInvalidKernTable
|
||||||
|
}
|
||||||
|
if coverageBits := buf[5]; coverageBits != 0x01 {
|
||||||
|
// We only support horizontal kerning.
|
||||||
|
return nil, errUnsupportedKernTable
|
||||||
|
}
|
||||||
|
offset += headerSize
|
||||||
|
length -= headerSize
|
||||||
|
subtableLength -= headerSize
|
||||||
|
|
||||||
|
switch format := buf[4]; format {
|
||||||
|
case 0:
|
||||||
|
return f.parseKernFormat0(buf, offset, subtableLength)
|
||||||
|
case 2:
|
||||||
|
// TODO: find such a (proprietary?) font, and support it.
|
||||||
|
}
|
||||||
|
return nil, errUnsupportedKernTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Font) parseKernFormat0(buf []byte, offset, length int) ([]byte, error) {
|
||||||
|
const headerSize, entrySize = 8, 6
|
||||||
|
if length < headerSize {
|
||||||
|
return nil, errInvalidKernTable
|
||||||
|
}
|
||||||
|
buf, err := f.src.view(buf, offset, headerSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
numPairs := u16(buf)
|
||||||
|
if length != headerSize+entrySize*int(numPairs) {
|
||||||
|
return nil, errInvalidKernTable
|
||||||
|
}
|
||||||
|
f.cached.kernNumPairs = int32(numPairs)
|
||||||
|
f.cached.kernOffset = int32(offset) + headerSize
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Font) parseMaxp(buf []byte) ([]byte, error) {
|
func (f *Font) parseMaxp(buf []byte) ([]byte, error) {
|
||||||
// https://www.microsoft.com/typography/otspec/maxp.htm
|
// https://www.microsoft.com/typography/otspec/maxp.htm
|
||||||
|
|
||||||
|
@ -763,6 +865,64 @@ func (f *Font) GlyphName(b *Buffer, x GlyphIndex) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kern returns the horizontal adjustment for the kerning pair (x0, x1). A
|
||||||
|
// positive kern means to move the glyphs further apart. ppem is the number of
|
||||||
|
// pixels in 1 em.
|
||||||
|
//
|
||||||
|
// It returns ErrNotFound if either glyph index is out of range.
|
||||||
|
func (f *Font) Kern(b *Buffer, x0, x1 GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (fixed.Int26_6, error) {
|
||||||
|
// TODO: how should this work with the GPOS table and CFF fonts?
|
||||||
|
// https://www.microsoft.com/typography/otspec/kern.htm says that
|
||||||
|
// "OpenType™ fonts containing CFF outlines are not supported by the 'kern'
|
||||||
|
// table and must use the 'GPOS' OpenType Layout table."
|
||||||
|
|
||||||
|
if n := f.NumGlyphs(); int(x0) >= n || int(x1) >= n {
|
||||||
|
return 0, ErrNotFound
|
||||||
|
}
|
||||||
|
// Not every font has a kern table. If it doesn't, there's no need to
|
||||||
|
// allocate a Buffer.
|
||||||
|
if f.kern.length == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if b == nil {
|
||||||
|
b = &Buffer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
key := uint32(x0)<<16 | uint32(x1)
|
||||||
|
lo, hi := int32(0), f.cached.kernNumPairs
|
||||||
|
for lo < hi {
|
||||||
|
i := (lo + hi) / 2
|
||||||
|
|
||||||
|
// TODO: this view call inside the inner loop can lead to many small
|
||||||
|
// reads instead of fewer larger reads, which can be expensive. We
|
||||||
|
// should be able to do better, although we don't want to make (one)
|
||||||
|
// arbitrarily large read. Perhaps we should round up reads to 4K or 8K
|
||||||
|
// chunks. For reference, Arial.ttf's kern table is 5472 bytes.
|
||||||
|
// Times_New_Roman.ttf's kern table is 5220 bytes.
|
||||||
|
const entrySize = 6
|
||||||
|
buf, err := b.view(&f.src, int(f.cached.kernOffset+i*entrySize), entrySize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
k := u32(buf)
|
||||||
|
if k < key {
|
||||||
|
lo = i + 1
|
||||||
|
} else if k > key {
|
||||||
|
hi = i
|
||||||
|
} else {
|
||||||
|
kern := fixed.Int26_6(int16(u16(buf[4:])))
|
||||||
|
kern = scale(kern*ppem, f.cached.unitsPerEm)
|
||||||
|
if h == font.HintingFull {
|
||||||
|
// Quantize the fixed.Int26_6 value to the nearest pixel.
|
||||||
|
kern = (kern + 32) &^ 63
|
||||||
|
}
|
||||||
|
return kern, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Name returns the name value keyed by the given NameID.
|
// Name returns the name value keyed by the given NameID.
|
||||||
//
|
//
|
||||||
// It returns ErrNotFound if there is no value for that key.
|
// It returns ErrNotFound if there is no value for that key.
|
||||||
|
|
|
@ -15,69 +15,31 @@ import (
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
|
||||||
func moveTo(xa, ya int) Segment {
|
func moveTo(xa, ya fixed.Int26_6) Segment {
|
||||||
return Segment{
|
return Segment{
|
||||||
Op: SegmentOpMoveTo,
|
Op: SegmentOpMoveTo,
|
||||||
Args: [6]fixed.Int26_6{
|
Args: [6]fixed.Int26_6{xa, ya},
|
||||||
0: fixed.I(xa),
|
|
||||||
1: fixed.I(ya),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveTo26_6(xa, ya fixed.Int26_6) Segment {
|
func lineTo(xa, ya fixed.Int26_6) Segment {
|
||||||
return Segment{
|
return Segment{
|
||||||
Op: SegmentOpMoveTo,
|
Op: SegmentOpLineTo,
|
||||||
Args: [6]fixed.Int26_6{
|
Args: [6]fixed.Int26_6{xa, ya},
|
||||||
0: xa,
|
|
||||||
1: ya,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lineTo(xa, ya int) Segment {
|
func quadTo(xa, ya, xb, yb fixed.Int26_6) Segment {
|
||||||
return Segment{
|
return Segment{
|
||||||
Op: SegmentOpLineTo,
|
Op: SegmentOpQuadTo,
|
||||||
Args: [6]fixed.Int26_6{
|
Args: [6]fixed.Int26_6{xa, ya, xb, yb},
|
||||||
0: fixed.I(xa),
|
|
||||||
1: fixed.I(ya),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lineTo26_6(xa, ya fixed.Int26_6) Segment {
|
func cubeTo(xa, ya, xb, yb, xc, yc fixed.Int26_6) Segment {
|
||||||
return Segment{
|
return Segment{
|
||||||
Op: SegmentOpLineTo,
|
Op: SegmentOpCubeTo,
|
||||||
Args: [6]fixed.Int26_6{
|
Args: [6]fixed.Int26_6{xa, ya, xb, yb, xc, yc},
|
||||||
0: xa,
|
|
||||||
1: ya,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func quadTo(xa, ya, xb, yb int) Segment {
|
|
||||||
return Segment{
|
|
||||||
Op: SegmentOpQuadTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
0: fixed.I(xa),
|
|
||||||
1: fixed.I(ya),
|
|
||||||
2: fixed.I(xb),
|
|
||||||
3: fixed.I(yb),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cubeTo(xa, ya, xb, yb, xc, yc int) Segment {
|
|
||||||
return Segment{
|
|
||||||
Op: SegmentOpCubeTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
0: fixed.I(xa),
|
|
||||||
1: fixed.I(ya),
|
|
||||||
2: fixed.I(xb),
|
|
||||||
3: fixed.I(yb),
|
|
||||||
4: fixed.I(xc),
|
|
||||||
5: fixed.I(yc),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,7 +392,7 @@ func testSegments(t *testing.T, filename string, wants [][]Segment) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Parse: %v", err)
|
t.Fatalf("Parse: %v", err)
|
||||||
}
|
}
|
||||||
ppem := fixed.Int26_6(f.UnitsPerEm()) << 6
|
ppem := fixed.Int26_6(f.UnitsPerEm())
|
||||||
|
|
||||||
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))
|
||||||
|
@ -481,16 +443,16 @@ func TestPPEM(t *testing.T) {
|
||||||
ppem fixed.Int26_6
|
ppem fixed.Int26_6
|
||||||
want []Segment
|
want []Segment
|
||||||
}{{
|
}{{
|
||||||
ppem: fixed.I(12),
|
ppem: fixed.Int26_6(12 << 6),
|
||||||
want: []Segment{
|
want: []Segment{
|
||||||
moveTo26_6(77, 0),
|
moveTo(77, 0),
|
||||||
lineTo26_6(77, 614),
|
lineTo(77, 614),
|
||||||
lineTo26_6(230, 614),
|
lineTo(230, 614),
|
||||||
lineTo26_6(230, 0),
|
lineTo(230, 0),
|
||||||
lineTo26_6(77, 0),
|
lineTo(77, 0),
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
ppem: fixed.I(2048),
|
ppem: fixed.Int26_6(2048),
|
||||||
want: []Segment{
|
want: []Segment{
|
||||||
moveTo(205, 0),
|
moveTo(205, 0),
|
||||||
lineTo(205, 1638),
|
lineTo(205, 1638),
|
||||||
|
|
|
@ -131,12 +131,12 @@ func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
|
||||||
|
|
||||||
// For simple (non-compound) glyphs, the remainder of the glyf data
|
// For simple (non-compound) glyphs, the remainder of the glyf data
|
||||||
// consists of (flags, x, y) points: the Bézier curve segments. These are
|
// consists of (flags, x, y) points: the Bézier curve segments. These are
|
||||||
// stored in columns (all the flags first, then all the x co-ordinates,
|
// stored in columns (all the flags first, then all the x coordinates, then
|
||||||
// then all the y co-ordinates), not rows, as it compresses better.
|
// all the y coordinates), not rows, as it compresses better.
|
||||||
//
|
//
|
||||||
// Decoding those points in row order involves two passes. The first pass
|
// Decoding those points in row order involves two passes. The first pass
|
||||||
// determines the indexes (relative to the data slice) of where the flags,
|
// determines the indexes (relative to the data slice) of where the flags,
|
||||||
// the x co-ordinates and the y co-ordinates each start.
|
// the x coordinates and the y coordinates each start.
|
||||||
flagIndex := int32(index)
|
flagIndex := int32(index)
|
||||||
xIndex, yIndex, ok := findXYIndexes(data, index, numPoints)
|
xIndex, yIndex, ok := findXYIndexes(data, index, numPoints)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user