font/sfnt: parse the glyf table.
Change-Id: Ib7ff75d99d3641f68f621db4ba2279cc1bda8c3a Reviewed-on: https://go-review.googlesource.com/35271 Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
parent
1ff62c9216
commit
83686c5479
|
@ -25,6 +25,7 @@ import (
|
|||
// These constants are not part of the specifications, but are limitations used
|
||||
// by this implementation.
|
||||
const (
|
||||
maxGlyphDataLength = 64 * 1024
|
||||
maxHintBits = 256
|
||||
maxNumTables = 256
|
||||
maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
|
||||
|
@ -40,7 +41,9 @@ var (
|
|||
|
||||
errInvalidBounds = errors.New("sfnt: invalid bounds")
|
||||
errInvalidCFFTable = errors.New("sfnt: invalid CFF table")
|
||||
errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
|
||||
errInvalidHeadTable = errors.New("sfnt: invalid head table")
|
||||
errInvalidLocaTable = errors.New("sfnt: invalid loca table")
|
||||
errInvalidLocationData = errors.New("sfnt: invalid location data")
|
||||
errInvalidMaxpTable = errors.New("sfnt: invalid maxp table")
|
||||
errInvalidNameTable = errors.New("sfnt: invalid name table")
|
||||
|
@ -51,6 +54,8 @@ var (
|
|||
errInvalidVersion = errors.New("sfnt: invalid version")
|
||||
|
||||
errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
|
||||
errUnsupportedCompoundGlyph = errors.New("sfnt: unsupported compound glyph")
|
||||
errUnsupportedGlyphDataLength = errors.New("sfnt: unsupported glyph data length")
|
||||
errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
|
||||
errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints")
|
||||
errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
|
||||
|
@ -281,6 +286,7 @@ type Font struct {
|
|||
// TODO: hdmx, kern, vmtx? Others?
|
||||
|
||||
cached struct {
|
||||
indexToLocFormat bool // false means short, true means long.
|
||||
isPostScript bool
|
||||
unitsPerEm Units
|
||||
|
||||
|
@ -388,6 +394,11 @@ func (f *Font) initialize() error {
|
|||
return errInvalidHeadTable
|
||||
}
|
||||
f.cached.unitsPerEm = Units(u)
|
||||
u, err = f.src.u16(buf, f.head, 50)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.cached.indexToLocFormat = u != 0
|
||||
|
||||
// https://www.microsoft.com/typography/otspec/maxp.htm
|
||||
if f.cached.isPostScript {
|
||||
|
@ -417,8 +428,11 @@ func (f *Font) initialize() error {
|
|||
return err
|
||||
}
|
||||
} else {
|
||||
// TODO: locaParser for TrueType fonts.
|
||||
f.cached.locations = make([]uint32, numGlyphs+1)
|
||||
f.cached.locations, err = parseLoca(
|
||||
&f.src, f.loca, f.glyf.offset, f.cached.indexToLocFormat, numGlyphs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(f.cached.locations) != numGlyphs+1 {
|
||||
return errInvalidLocationData
|
||||
|
@ -436,6 +450,9 @@ func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) ([]byte, error) {
|
|||
}
|
||||
i := f.cached.locations[xx+0]
|
||||
j := f.cached.locations[xx+1]
|
||||
if j-i > maxGlyphDataLength {
|
||||
return nil, errUnsupportedGlyphDataLength
|
||||
}
|
||||
return b.view(&f.src, int(i), int(j-i))
|
||||
}
|
||||
|
||||
|
@ -467,7 +484,11 @@ func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, opts *LoadGlyphOptions) ([]Seg
|
|||
}
|
||||
b.segments = b.psi.type2Charstrings.segments
|
||||
} else {
|
||||
return nil, errors.New("sfnt: TODO: load glyf data")
|
||||
segments, err := appendGlyfSegments(b.segments, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.segments = segments
|
||||
}
|
||||
|
||||
// TODO: look at opts to scale / transform / hint the Buffer.segments.
|
||||
|
|
|
@ -34,6 +34,18 @@ func lineTo(xa, ya int) Segment {
|
|||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -76,16 +88,7 @@ func testTrueType(t *testing.T, f *Font) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPostScript(t *testing.T) {
|
||||
data, err := ioutil.ReadFile(filepath.Join("..", "testdata", "CFFTest.otf"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := Parse(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
func TestPostScriptSegments(t *testing.T) {
|
||||
// wants' vectors correspond 1-to-1 to what's in the CFFTest.sfd file,
|
||||
// although OpenType/CFF and FontForge's SFD have reversed orders.
|
||||
// https://fontforge.github.io/validation.html says that "All paths must be
|
||||
|
@ -96,8 +99,8 @@ func TestPostScript(t *testing.T) {
|
|||
// again when it saves them, of course)."
|
||||
//
|
||||
// The .notdef glyph isn't explicitly in the SFD file, but for some unknown
|
||||
// reason, FontForge generates a .notdef glyph in the OpenType/CFF file.
|
||||
wants := [...][]Segment{{
|
||||
// reason, FontForge generates it in the OpenType/CFF file.
|
||||
wants := [][]Segment{{
|
||||
// .notdef
|
||||
// - contour #0
|
||||
moveTo(50, 0),
|
||||
|
@ -158,8 +161,80 @@ func TestPostScript(t *testing.T) {
|
|||
lineTo(331, 758),
|
||||
lineTo(243, 752),
|
||||
lineTo(235, 562),
|
||||
// TODO: explicitly (not implicitly) close these contours?
|
||||
}}
|
||||
|
||||
testSegments(t, "CFFTest.otf", wants)
|
||||
}
|
||||
|
||||
func TestTrueTypeSegments(t *testing.T) {
|
||||
// wants' vectors correspond 1-to-1 to what's in the glyfTest.sfd file,
|
||||
// although FontForge's SFD format stores quadratic Bézier curves as cubics
|
||||
// with duplicated off-curve points. quadTo(bx, by, cx, cy) is stored as
|
||||
// "bx by bx by cx cy".
|
||||
//
|
||||
// The .notdef, .null and nonmarkingreturn glyphs aren't explicitly in the
|
||||
// SFD file, but for some unknown reason, FontForge generates them in the
|
||||
// TrueType file.
|
||||
wants := [][]Segment{{
|
||||
// .notdef
|
||||
// - contour #0
|
||||
moveTo(68, 0),
|
||||
lineTo(68, 1365),
|
||||
lineTo(612, 1365),
|
||||
lineTo(612, 0),
|
||||
lineTo(68, 0),
|
||||
// - contour #1
|
||||
moveTo(136, 68),
|
||||
lineTo(544, 68),
|
||||
lineTo(544, 1297),
|
||||
lineTo(136, 1297),
|
||||
lineTo(136, 68),
|
||||
}, {
|
||||
// .null
|
||||
// Empty glyph.
|
||||
}, {
|
||||
// nonmarkingreturn
|
||||
// Empty glyph.
|
||||
}, {
|
||||
// zero
|
||||
// - contour #0
|
||||
moveTo(614, 1434),
|
||||
quadTo(369, 1434, 369, 614),
|
||||
quadTo(369, 471, 435, 338),
|
||||
quadTo(502, 205, 614, 205),
|
||||
quadTo(860, 205, 860, 1024),
|
||||
quadTo(860, 1167, 793, 1300),
|
||||
quadTo(727, 1434, 614, 1434),
|
||||
// - contour #1
|
||||
moveTo(614, 1638),
|
||||
quadTo(1024, 1638, 1024, 819),
|
||||
quadTo(1024, 0, 614, 0),
|
||||
quadTo(205, 0, 205, 819),
|
||||
quadTo(205, 1638, 614, 1638),
|
||||
}, {
|
||||
// one
|
||||
// - contour #0
|
||||
moveTo(205, 0),
|
||||
lineTo(205, 1638),
|
||||
lineTo(614, 1638),
|
||||
lineTo(614, 0),
|
||||
lineTo(205, 0),
|
||||
}}
|
||||
|
||||
testSegments(t, "glyfTest.ttf", wants)
|
||||
}
|
||||
|
||||
func testSegments(t *testing.T, filename string, wants [][]Segment) {
|
||||
data, err := ioutil.ReadFile(filepath.Join("..", "testdata", filename))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := Parse(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ng := f.NumGlyphs(); ng != len(wants) {
|
||||
t.Fatalf("NumGlyphs: got %d, want %d", ng, len(wants))
|
||||
}
|
||||
|
@ -191,7 +266,7 @@ loop:
|
|||
name, err := f.Name(nil, NameIDFamily)
|
||||
if err != nil {
|
||||
t.Errorf("Name: %v", err)
|
||||
} else if want := "CFFTest"; name != want {
|
||||
} else if want := filename[:len(filename)-len(".ttf")]; name != want {
|
||||
t.Errorf("Name:\ngot %q\nwant %q", name, want)
|
||||
}
|
||||
}
|
||||
|
|
489
font/sfnt/truetype.go
Normal file
489
font/sfnt/truetype.go
Normal file
|
@ -0,0 +1,489 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// Flags for simple (non-compound) glyphs.
|
||||
//
|
||||
// See https://www.microsoft.com/typography/OTSPEC/glyf.htm
|
||||
const (
|
||||
flagOnCurve = 1 << 0 // 0x0001
|
||||
flagXShortVector = 1 << 1 // 0x0002
|
||||
flagYShortVector = 1 << 2 // 0x0004
|
||||
flagRepeat = 1 << 3 // 0x0008
|
||||
|
||||
// The same flag bits are overloaded to have two meanings, dependent on the
|
||||
// value of the flag{X,Y}ShortVector bits.
|
||||
flagPositiveXShortVector = 1 << 4 // 0x0010
|
||||
flagThisXIsSame = 1 << 4 // 0x0010
|
||||
flagPositiveYShortVector = 1 << 5 // 0x0020
|
||||
flagThisYIsSame = 1 << 5 // 0x0020
|
||||
)
|
||||
|
||||
// Flags for compound glyphs.
|
||||
//
|
||||
// See https://www.microsoft.com/typography/OTSPEC/glyf.htm
|
||||
const (
|
||||
flagArg1And2AreWords = 1 << 0 // 0x0001
|
||||
flagArgsAreXYValues = 1 << 1 // 0x0002
|
||||
flagRoundXYToGrid = 1 << 2 // 0x0004
|
||||
flagWeHaveAScale = 1 << 3 // 0x0008
|
||||
flagReserved4 = 1 << 4 // 0x0010
|
||||
flagMoreComponents = 1 << 5 // 0x0020
|
||||
flagWeHaveAnXAndYScale = 1 << 6 // 0x0040
|
||||
flagWeHaveATwoByTwo = 1 << 7 // 0x0080
|
||||
flagWeHaveInstructions = 1 << 8 // 0x0100
|
||||
flagUseMyMetrics = 1 << 9 // 0x0200
|
||||
flagOverlapCompound = 1 << 10 // 0x0400
|
||||
flagScaledComponentOffset = 1 << 11 // 0x0800
|
||||
flagUnscaledComponentOffset = 1 << 12 // 0x1000
|
||||
)
|
||||
|
||||
func midPoint(p, q fixed.Point26_6) fixed.Point26_6 {
|
||||
return fixed.Point26_6{
|
||||
X: (p.X + q.X) / 2,
|
||||
Y: (p.Y + q.Y) / 2,
|
||||
}
|
||||
}
|
||||
|
||||
func parseLoca(src *source, loca table, glyfOffset uint32, indexToLocFormat bool, numGlyphs int) (locations []uint32, err error) {
|
||||
if indexToLocFormat {
|
||||
if loca.length != 4*uint32(numGlyphs+1) {
|
||||
return nil, errInvalidLocaTable
|
||||
}
|
||||
} else {
|
||||
if loca.length != 2*uint32(numGlyphs+1) {
|
||||
return nil, errInvalidLocaTable
|
||||
}
|
||||
}
|
||||
|
||||
locations = make([]uint32, numGlyphs+1)
|
||||
buf, err := src.view(nil, int(loca.offset), int(loca.length))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if indexToLocFormat {
|
||||
for i := range locations {
|
||||
locations[i] = 1*uint32(u32(buf[4*i:])) + glyfOffset
|
||||
}
|
||||
} else {
|
||||
for i := range locations {
|
||||
locations[i] = 2*uint32(u16(buf[2*i:])) + glyfOffset
|
||||
}
|
||||
}
|
||||
return locations, err
|
||||
}
|
||||
|
||||
// https://www.microsoft.com/typography/OTSPEC/glyf.htm says that "Each
|
||||
// glyph begins with the following [10 byte] header".
|
||||
const glyfHeaderLen = 10
|
||||
|
||||
// appendGlyfSegments appends to dst the segments encoded in the glyf data.
|
||||
func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
|
||||
if len(data) == 0 {
|
||||
return dst, nil
|
||||
}
|
||||
if len(data) < glyfHeaderLen {
|
||||
return nil, errInvalidGlyphData
|
||||
}
|
||||
index := glyfHeaderLen
|
||||
|
||||
numContours, numPoints := int16(u16(data)), 0
|
||||
switch {
|
||||
case numContours == -1:
|
||||
// We have a compound glyph. No-op.
|
||||
case numContours == 0:
|
||||
return dst, nil
|
||||
case numContours > 0:
|
||||
// We have a simple (non-compound) glyph.
|
||||
index += 2 * int(numContours)
|
||||
if index > len(data) {
|
||||
return nil, errInvalidGlyphData
|
||||
}
|
||||
// The +1 for numPoints is because the value in the file format is
|
||||
// inclusive, but Go's slice[:index] semantics are exclusive.
|
||||
numPoints = 1 + int(u16(data[index-2:]))
|
||||
default:
|
||||
return nil, errInvalidGlyphData
|
||||
}
|
||||
|
||||
// Skip the hinting instructions.
|
||||
index += 2
|
||||
if index > len(data) {
|
||||
return nil, errInvalidGlyphData
|
||||
}
|
||||
hintsLength := int(u16(data[index-2:]))
|
||||
index += hintsLength
|
||||
if index > len(data) {
|
||||
return nil, errInvalidGlyphData
|
||||
}
|
||||
|
||||
// TODO: support compound glyphs.
|
||||
if numContours < 0 {
|
||||
return nil, errUnsupportedCompoundGlyph
|
||||
}
|
||||
|
||||
// For simple (non-compound) glyphs, the remainder of the glyf data
|
||||
// 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,
|
||||
// then all the y co-ordinates), not rows, as it compresses better.
|
||||
//
|
||||
// Decoding those points in row order involves two passes. The first pass
|
||||
// determines the indexes (relative to the data slice) of where the flags,
|
||||
// the x co-ordinates and the y co-ordinates each start.
|
||||
flagIndex := int32(index)
|
||||
xIndex, yIndex, ok := findXYIndexes(data, index, numPoints)
|
||||
if !ok {
|
||||
return nil, errInvalidGlyphData
|
||||
}
|
||||
|
||||
// The second pass decodes each (flags, x, y) tuple in row order.
|
||||
g := glyfIter{
|
||||
data: data,
|
||||
flagIndex: flagIndex,
|
||||
xIndex: xIndex,
|
||||
yIndex: yIndex,
|
||||
endIndex: glyfHeaderLen,
|
||||
// The -1 is because the contour-end index in the file format is
|
||||
// inclusive, but Go's slice[:index] semantics are exclusive.
|
||||
prevEnd: -1,
|
||||
numContours: int32(numContours),
|
||||
}
|
||||
for g.nextContour() {
|
||||
for g.nextSegment() {
|
||||
dst = append(dst, g.seg)
|
||||
}
|
||||
}
|
||||
if g.err != nil {
|
||||
return nil, g.err
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
func findXYIndexes(data []byte, index, numPoints int) (xIndex, yIndex int32, ok bool) {
|
||||
xDataLen := 0
|
||||
yDataLen := 0
|
||||
for i := 0; ; {
|
||||
if i > numPoints {
|
||||
return 0, 0, false
|
||||
}
|
||||
if i == numPoints {
|
||||
break
|
||||
}
|
||||
|
||||
repeatCount := 1
|
||||
if index >= len(data) {
|
||||
return 0, 0, false
|
||||
}
|
||||
flag := data[index]
|
||||
index++
|
||||
if flag&flagRepeat != 0 {
|
||||
if index >= len(data) {
|
||||
return 0, 0, false
|
||||
}
|
||||
repeatCount += int(data[index])
|
||||
index++
|
||||
}
|
||||
|
||||
xSize := 0
|
||||
if flag&flagXShortVector != 0 {
|
||||
xSize = 1
|
||||
} else if flag&flagThisXIsSame == 0 {
|
||||
xSize = 2
|
||||
}
|
||||
xDataLen += xSize * repeatCount
|
||||
|
||||
ySize := 0
|
||||
if flag&flagYShortVector != 0 {
|
||||
ySize = 1
|
||||
} else if flag&flagThisYIsSame == 0 {
|
||||
ySize = 2
|
||||
}
|
||||
yDataLen += ySize * repeatCount
|
||||
|
||||
i += repeatCount
|
||||
}
|
||||
if index+xDataLen+yDataLen > len(data) {
|
||||
return 0, 0, false
|
||||
}
|
||||
return int32(index), int32(index + xDataLen), true
|
||||
}
|
||||
|
||||
type glyfIter struct {
|
||||
data []byte
|
||||
err error
|
||||
|
||||
// Various indices into the data slice. See the "Decoding those points in
|
||||
// row order" comment above.
|
||||
flagIndex int32
|
||||
xIndex int32
|
||||
yIndex int32
|
||||
|
||||
// endIndex points to the uint16 that is the inclusive point index of the
|
||||
// current contour's end. prevEnd is the previous contour's end.
|
||||
endIndex int32
|
||||
prevEnd int32
|
||||
|
||||
// c and p count the current contour and point, up to numContours and
|
||||
// numPoints.
|
||||
c, numContours int32
|
||||
p, nPoints int32
|
||||
|
||||
// The next two groups of fields track points and segments. Points are what
|
||||
// the underlying file format provides. Bézier curve segments are what the
|
||||
// rasterizer consumes.
|
||||
//
|
||||
// Points are either on-curve or off-curve. Two consecutive on-curve points
|
||||
// define a linear curve segment between them. N off-curve points between
|
||||
// on-curve points define N quadratic curve segments. The TrueType glyf
|
||||
// format does not use cubic curves. If N is greater than 1, some of these
|
||||
// segment end points are implicit, the midpoint of two off-curve points.
|
||||
// Given the points A, B1, B2, ..., BN, C, where A and C are on-curve and
|
||||
// all the Bs are off-curve, the segments are:
|
||||
//
|
||||
// - A, B1, midpoint(B1, B2)
|
||||
// - midpoint(B1, B2), B2, midpoint(B2, B3)
|
||||
// - midpoint(B2, B3), B3, midpoint(B3, B4)
|
||||
// - ...
|
||||
// - midpoint(BN-1, BN), BN, C
|
||||
//
|
||||
// Note that the sequence of Bs may wrap around from the last point in the
|
||||
// glyf data to the first. A and C may also be the same point (the only
|
||||
// explicit on-curve point), or there may be no explicit on-curve points at
|
||||
// all (but still implicit ones between explicit off-curve points).
|
||||
|
||||
// Points.
|
||||
x, y int16
|
||||
on bool
|
||||
flag uint8
|
||||
repeats uint8
|
||||
|
||||
// Segments.
|
||||
closing bool
|
||||
closed bool
|
||||
firstOnCurveValid bool
|
||||
firstOffCurveValid bool
|
||||
lastOffCurveValid bool
|
||||
firstOnCurve fixed.Point26_6
|
||||
firstOffCurve fixed.Point26_6
|
||||
lastOffCurve fixed.Point26_6
|
||||
seg Segment
|
||||
}
|
||||
|
||||
func (g *glyfIter) nextContour() (ok bool) {
|
||||
if g.c == g.numContours {
|
||||
return false
|
||||
}
|
||||
g.c++
|
||||
|
||||
end := int32(u16(g.data[g.endIndex:]))
|
||||
g.endIndex += 2
|
||||
if end <= g.prevEnd {
|
||||
g.err = errInvalidGlyphData
|
||||
return false
|
||||
}
|
||||
g.nPoints = end - g.prevEnd
|
||||
g.p = 0
|
||||
g.prevEnd = end
|
||||
|
||||
g.closing = false
|
||||
g.closed = false
|
||||
g.firstOnCurveValid = false
|
||||
g.firstOffCurveValid = false
|
||||
g.lastOffCurveValid = false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *glyfIter) close() {
|
||||
switch {
|
||||
case !g.firstOffCurveValid && !g.lastOffCurveValid:
|
||||
g.closed = true
|
||||
g.seg = Segment{
|
||||
Op: SegmentOpLineTo,
|
||||
Args: [6]fixed.Int26_6{
|
||||
g.firstOnCurve.X,
|
||||
g.firstOnCurve.Y,
|
||||
},
|
||||
}
|
||||
case !g.firstOffCurveValid && g.lastOffCurveValid:
|
||||
g.closed = true
|
||||
g.seg = Segment{
|
||||
Op: SegmentOpQuadTo,
|
||||
Args: [6]fixed.Int26_6{
|
||||
g.lastOffCurve.X,
|
||||
g.lastOffCurve.Y,
|
||||
g.firstOnCurve.X,
|
||||
g.firstOnCurve.Y,
|
||||
},
|
||||
}
|
||||
case g.firstOffCurveValid && !g.lastOffCurveValid:
|
||||
g.closed = true
|
||||
g.seg = Segment{
|
||||
Op: SegmentOpQuadTo,
|
||||
Args: [6]fixed.Int26_6{
|
||||
g.firstOffCurve.X,
|
||||
g.firstOffCurve.Y,
|
||||
g.firstOnCurve.X,
|
||||
g.firstOnCurve.Y,
|
||||
},
|
||||
}
|
||||
case g.firstOffCurveValid && g.lastOffCurveValid:
|
||||
mid := midPoint(g.lastOffCurve, g.firstOffCurve)
|
||||
g.lastOffCurveValid = false
|
||||
g.seg = Segment{
|
||||
Op: SegmentOpQuadTo,
|
||||
Args: [6]fixed.Int26_6{
|
||||
g.lastOffCurve.X,
|
||||
g.lastOffCurve.Y,
|
||||
mid.X,
|
||||
mid.Y,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *glyfIter) nextSegment() (ok bool) {
|
||||
for !g.closed {
|
||||
if g.closing || !g.nextPoint() {
|
||||
g.closing = true
|
||||
g.close()
|
||||
return true
|
||||
}
|
||||
|
||||
p := fixed.Point26_6{
|
||||
X: fixed.Int26_6(g.x) << 6,
|
||||
Y: fixed.Int26_6(g.y) << 6,
|
||||
}
|
||||
|
||||
if !g.firstOnCurveValid {
|
||||
if g.on {
|
||||
g.firstOnCurve = p
|
||||
g.firstOnCurveValid = true
|
||||
g.seg = Segment{
|
||||
Op: SegmentOpMoveTo,
|
||||
Args: [6]fixed.Int26_6{
|
||||
p.X,
|
||||
p.Y,
|
||||
},
|
||||
}
|
||||
return true
|
||||
} else if !g.firstOffCurveValid {
|
||||
g.firstOffCurve = p
|
||||
g.firstOffCurveValid = true
|
||||
continue
|
||||
} else {
|
||||
midp := midPoint(g.firstOffCurve, p)
|
||||
g.firstOnCurve = midp
|
||||
g.firstOnCurveValid = true
|
||||
g.lastOffCurve = p
|
||||
g.lastOffCurveValid = true
|
||||
g.seg = Segment{
|
||||
Op: SegmentOpMoveTo,
|
||||
Args: [6]fixed.Int26_6{
|
||||
midp.X,
|
||||
midp.Y,
|
||||
},
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
} else if !g.lastOffCurveValid {
|
||||
if !g.on {
|
||||
g.lastOffCurve = p
|
||||
g.lastOffCurveValid = true
|
||||
continue
|
||||
} else {
|
||||
g.seg = Segment{
|
||||
Op: SegmentOpLineTo,
|
||||
Args: [6]fixed.Int26_6{
|
||||
p.X,
|
||||
p.Y,
|
||||
},
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
} else {
|
||||
if !g.on {
|
||||
midp := midPoint(g.lastOffCurve, p)
|
||||
g.seg = Segment{
|
||||
Op: SegmentOpQuadTo,
|
||||
Args: [6]fixed.Int26_6{
|
||||
g.lastOffCurve.X,
|
||||
g.lastOffCurve.Y,
|
||||
midp.X,
|
||||
midp.Y,
|
||||
},
|
||||
}
|
||||
g.lastOffCurve = p
|
||||
g.lastOffCurveValid = true
|
||||
return true
|
||||
} else {
|
||||
g.seg = Segment{
|
||||
Op: SegmentOpQuadTo,
|
||||
Args: [6]fixed.Int26_6{
|
||||
g.lastOffCurve.X,
|
||||
g.lastOffCurve.Y,
|
||||
p.X,
|
||||
p.Y,
|
||||
},
|
||||
}
|
||||
g.lastOffCurveValid = false
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *glyfIter) nextPoint() (ok bool) {
|
||||
if g.p == g.nPoints {
|
||||
return false
|
||||
}
|
||||
g.p++
|
||||
|
||||
if g.repeats > 0 {
|
||||
g.repeats--
|
||||
} else {
|
||||
g.flag = g.data[g.flagIndex]
|
||||
g.flagIndex++
|
||||
if g.flag&flagRepeat != 0 {
|
||||
g.repeats = g.data[g.flagIndex]
|
||||
g.flagIndex++
|
||||
}
|
||||
}
|
||||
|
||||
if g.flag&flagXShortVector != 0 {
|
||||
if g.flag&flagPositiveXShortVector != 0 {
|
||||
g.x += int16(g.data[g.xIndex])
|
||||
} else {
|
||||
g.x -= int16(g.data[g.xIndex])
|
||||
}
|
||||
g.xIndex += 1
|
||||
} else if g.flag&flagThisXIsSame == 0 {
|
||||
g.x += int16(u16(g.data[g.xIndex:]))
|
||||
g.xIndex += 2
|
||||
}
|
||||
|
||||
if g.flag&flagYShortVector != 0 {
|
||||
if g.flag&flagPositiveYShortVector != 0 {
|
||||
g.y += int16(g.data[g.yIndex])
|
||||
} else {
|
||||
g.y -= int16(g.data[g.yIndex])
|
||||
}
|
||||
g.yIndex += 1
|
||||
} else if g.flag&flagThisYIsSame == 0 {
|
||||
g.y += int16(u16(g.data[g.yIndex:]))
|
||||
g.yIndex += 2
|
||||
}
|
||||
|
||||
g.on = g.flag&flagOnCurve != 0
|
||||
return true
|
||||
}
|
102
font/testdata/glyfTest.sfd
vendored
Normal file
102
font/testdata/glyfTest.sfd
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
SplineFontDB: 3.0
|
||||
FontName: glyfTest
|
||||
FullName: glyfTest
|
||||
FamilyName: glyfTest
|
||||
Weight: Regular
|
||||
Copyright: Copyright 2016 The Go Authors. All rights reserved.\nUse of this font is governed by a BSD-style license that can be found at https://golang.org/LICENSE.
|
||||
Version: 001.000
|
||||
ItalicAngle: -11.25
|
||||
UnderlinePosition: -204
|
||||
UnderlineWidth: 102
|
||||
Ascent: 1638
|
||||
Descent: 410
|
||||
LayerCount: 2
|
||||
Layer: 0 1 "Back" 1
|
||||
Layer: 1 1 "Fore" 0
|
||||
XUID: [1021 367 888937226 7862908]
|
||||
FSType: 8
|
||||
OS2Version: 0
|
||||
OS2_WeightWidthSlopeOnly: 0
|
||||
OS2_UseTypoMetrics: 1
|
||||
CreationTime: 1484386143
|
||||
ModificationTime: 1484386143
|
||||
PfmFamily: 17
|
||||
TTFWeight: 400
|
||||
TTFWidth: 5
|
||||
LineGap: 184
|
||||
VLineGap: 0
|
||||
OS2TypoAscent: 0
|
||||
OS2TypoAOffset: 1
|
||||
OS2TypoDescent: 0
|
||||
OS2TypoDOffset: 1
|
||||
OS2TypoLinegap: 184
|
||||
OS2WinAscent: 0
|
||||
OS2WinAOffset: 1
|
||||
OS2WinDescent: 0
|
||||
OS2WinDOffset: 1
|
||||
HheadAscent: 0
|
||||
HheadAOffset: 1
|
||||
HheadDescent: 0
|
||||
HheadDOffset: 1
|
||||
OS2Vendor: 'PfEd'
|
||||
MarkAttachClasses: 1
|
||||
DEI: 91125
|
||||
LangName: 1033
|
||||
Encoding: UnicodeBmp
|
||||
UnicodeInterp: none
|
||||
NameList: Adobe Glyph List
|
||||
DisplaySize: -24
|
||||
AntiAlias: 1
|
||||
FitToEm: 1
|
||||
WinInfo: 0 32 23
|
||||
BeginPrivate: 0
|
||||
EndPrivate
|
||||
TeXData: 1 0 0 346030 173015 115343 0 -1048576 115343 783286 444596 497025 792723 393216 433062 380633 303038 157286 324010 404750 52429 2506097 1059062 262144
|
||||
BeginChars: 65536 2
|
||||
|
||||
StartChar: zero
|
||||
Encoding: 48 48 0
|
||||
Width: 1228
|
||||
VWidth: 0
|
||||
Flags: W
|
||||
HStem: 0 205<508 700> 1434 205<529 720>
|
||||
VStem: 205 164<500 1088> 860 164<550 1139>
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
614 1434 m 0,0,1
|
||||
369 1434 369 1434 369 614 c 0,2,3
|
||||
369 471 369 471 435 338 c 0,4,5
|
||||
502 205 502 205 614 205 c 0,6,7
|
||||
860 205 860 205 860 1024 c 0,8,9
|
||||
860 1167 860 1167 793 1300 c 0,10,11
|
||||
727 1434 727 1434 614 1434 c 0,0,1
|
||||
614 1638 m 0,12,13
|
||||
1024 1638 1024 1638 1024 819 c 128,-1,14
|
||||
1024 0 1024 0 614 0 c 0,15,16
|
||||
205 0 205 0 205 819 c 128,-1,17
|
||||
205 1638 205 1638 614 1638 c 0,12,13
|
||||
EndSplineSet
|
||||
Validated: 1
|
||||
EndChar
|
||||
|
||||
StartChar: one
|
||||
Encoding: 49 49 1
|
||||
Width: 819
|
||||
VWidth: 0
|
||||
Flags: W
|
||||
HStem: 0 43G<205 614>
|
||||
VStem: 205 410<0 1638>
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
205 0 m 25,0,-1
|
||||
205 1638 l 1,1,-1
|
||||
614 1638 l 1,2,-1
|
||||
614 0 l 1,3,-1
|
||||
205 0 l 25,0,-1
|
||||
EndSplineSet
|
||||
Validated: 1
|
||||
EndChar
|
||||
EndChars
|
||||
EndSplineFont
|
BIN
font/testdata/glyfTest.ttf
vendored
Normal file
BIN
font/testdata/glyfTest.ttf
vendored
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user