summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mkvlib/ass.go35
-rw-r--r--mkvlib/parser/sfnt/cmap.go312
-rw-r--r--mkvlib/parser/sfnt/data.go68
-rw-r--r--mkvlib/parser/sfnt/gpos.go550
-rw-r--r--mkvlib/parser/sfnt/postscript.go1426
-rw-r--r--mkvlib/parser/sfnt/sfnt.go1964
-rw-r--r--mkvlib/parser/sfnt/truetype.go578
-rw-r--r--mkvlib/shared.go2
8 files changed, 4920 insertions, 15 deletions
diff --git a/mkvlib/ass.go b/mkvlib/ass.go
index 7ea99dc..664a45b 100644
--- a/mkvlib/ass.go
+++ b/mkvlib/ass.go
@@ -6,9 +6,8 @@ import (
"encoding/json"
"fmt"
"github.com/KurenaiRyu/MkvAutoSubset/mkvlib/parser"
+ "github.com/KurenaiRyu/MkvAutoSubset/mkvlib/parser/sfnt"
"github.com/antchfx/xmlquery"
- "golang.org/x/image/font/opentype"
- "golang.org/x/image/font/sfnt"
"io"
"io/ioutil"
"os"
@@ -256,17 +255,25 @@ func (self *assProcessor) getFontName(p string) [][]map[string]bool {
id2, _ := _font.Name(nil, sfnt.NameIDSubfamily)
id4, _ := _font.Name(nil, sfnt.NameIDFull)
id6, _ := _font.Name(nil, sfnt.NameIDPostScript)
- if id1 != "" {
- names[id1] = true
+ if id1 != nil {
+ for _, v := range id1 {
+ names[v] = true
+ }
}
- if id4 != "" {
- names[id4] = true
+ if id4 != nil {
+ for _, v := range id4 {
+ names[v] = true
+ }
}
- if id6 != "" {
- names[id6] = true
+ if id6 != nil {
+ for _, v := range id6 {
+ names[v] = true
+ }
}
- if id2 != "" {
- types[id2] = true
+ if id2 != nil {
+ for _, v := range id2 {
+ types[v] = true
+ }
}
return []map[string]bool{names, types}
}
@@ -278,7 +285,7 @@ func (self *assProcessor) getFontName(p string) [][]map[string]bool {
if err == nil {
fonts := make([]*sfnt.Font, 0)
if strings.HasSuffix(strings.ToLower(p), ".ttc") {
- c, err := opentype.ParseCollection(data)
+ c, err := sfnt.ParseCollection(data)
if err == nil {
l := c.NumFonts()
for i := 0; i < l; i++ {
@@ -289,7 +296,7 @@ func (self *assProcessor) getFontName(p string) [][]map[string]bool {
}
}
} else {
- _f, err := opentype.Parse(data)
+ _f, err := sfnt.Parse(data)
if err == nil {
fonts = append(fonts, _f)
}
@@ -333,12 +340,12 @@ func (self *assProcessor) checkFontMissing(f *fontInfo, i int, c bool) bool {
if err == nil {
var _font *sfnt.Font
if strings.HasSuffix(strings.ToLower(f.file), ".ttc") {
- c, err := opentype.ParseCollection(data)
+ c, err := sfnt.ParseCollection(data)
if err == nil {
_font, _ = c.Font(f.index)
}
} else {
- _font, _ = opentype.Parse(data)
+ _font, _ = sfnt.Parse(data)
}
if _font != nil {
for _, r := range f.runes {
diff --git a/mkvlib/parser/sfnt/cmap.go b/mkvlib/parser/sfnt/cmap.go
new file mode 100644
index 0000000..2035ea9
--- /dev/null
+++ b/mkvlib/parser/sfnt/cmap.go
@@ -0,0 +1,312 @@
+// 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/text/encoding/charmap"
+)
+
+// Platform IDs and Platform Specific IDs as per
+// https://www.microsoft.com/typography/otspec/name.htm
+const (
+ pidUnicode = 0
+ pidMacintosh = 1
+ pidWindows = 3
+
+ psidUnicode2BMPOnly = 3
+ psidUnicode2FullRepertoire = 4
+ // Note that FontForge may generate a bogus Platform Specific ID (value 10)
+ // for the Unicode Platform ID (value 0). See
+ // https://github.com/fontforge/fontforge/issues/2728
+
+ psidMacintoshRoman = 0
+
+ psidWindowsSymbol = 0
+ psidWindowsUCS2 = 1
+ psidWindowsUCS4 = 10
+)
+
+// platformEncodingWidth returns the number of bytes per character assumed by
+// the given Platform ID and Platform Specific ID.
+//
+// Very old fonts, from before Unicode was widely adopted, assume only 1 byte
+// per character: a character map.
+//
+// Old fonts, from when Unicode meant the Basic Multilingual Plane (BMP),
+// assume that 2 bytes per character is sufficient.
+//
+// Recent fonts naturally support the full range of Unicode code points, which
+// can take up to 4 bytes per character. Such fonts might still choose one of
+// the legacy encodings if e.g. their repertoire is limited to the BMP, for
+// greater compatibility with older software, or because the resultant file
+// size can be smaller.
+func platformEncodingWidth(pid, psid uint16) int {
+ switch pid {
+ case pidUnicode:
+ switch psid {
+ case psidUnicode2BMPOnly:
+ return 2
+ case psidUnicode2FullRepertoire:
+ return 4
+ }
+
+ case pidMacintosh:
+ switch psid {
+ case psidMacintoshRoman:
+ return 1
+ }
+
+ case pidWindows:
+ switch psid {
+ case psidWindowsSymbol:
+ return 2
+ case psidWindowsUCS2:
+ return 2
+ case psidWindowsUCS4:
+ return 4
+ }
+ }
+ return 0
+}
+
+// The various cmap formats are described at
+// https://www.microsoft.com/typography/otspec/cmap.htm
+
+var supportedCmapFormat = func(format, pid, psid uint16) bool {
+ switch format {
+ case 0:
+ return pid == pidMacintosh && psid == psidMacintoshRoman
+ case 4:
+ return true
+ case 6:
+ return true
+ case 12:
+ return true
+ }
+ return false
+}
+
+func (f *Font) makeCachedGlyphIndex(buf []byte, offset, length uint32, format uint16) ([]byte, glyphIndexFunc, error) {
+ switch format {
+ case 0:
+ return f.makeCachedGlyphIndexFormat0(buf, offset, length)
+ case 4:
+ return f.makeCachedGlyphIndexFormat4(buf, offset, length)
+ case 6:
+ return f.makeCachedGlyphIndexFormat6(buf, offset, length)
+ case 12:
+ return f.makeCachedGlyphIndexFormat12(buf, offset, length)
+ }
+ panic("unreachable")
+}
+
+func (f *Font) makeCachedGlyphIndexFormat0(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) {
+ if length != 6+256 || offset+length > f.cmap.length {
+ return nil, nil, errInvalidCmapTable
+ }
+ var err error
+ buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(length))
+ if err != nil {
+ return nil, nil, err
+ }
+ var table [256]byte
+ copy(table[:], buf[6:])
+ return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
+ x, ok := charmap.Macintosh.EncodeRune(r)
+ if !ok {
+ // The source rune r is not representable in the Macintosh-Roman encoding.
+ return 0, nil
+ }
+ return GlyphIndex(table[x]), nil
+ }, nil
+}
+
+func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) {
+ const headerSize = 14
+ if offset+headerSize > f.cmap.length {
+ return nil, nil, errInvalidCmapTable
+ }
+ var err error
+ buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize)
+ if err != nil {
+ return nil, nil, err
+ }
+ offset += headerSize
+
+ segCount := u16(buf[6:])
+ if segCount&1 != 0 {
+ return nil, nil, errInvalidCmapTable
+ }
+ segCount /= 2
+ if segCount > maxCmapSegments {
+ return nil, nil, errUnsupportedNumberOfCmapSegments
+ }
+
+ eLength := 8*uint32(segCount) + 2
+ if offset+eLength > f.cmap.length {
+ return nil, nil, errInvalidCmapTable
+ }
+ buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength))
+ if err != nil {
+ return nil, nil, err
+ }
+ offset += eLength
+
+ entries := make([]cmapEntry16, segCount)
+ for i := range entries {
+ entries[i] = cmapEntry16{
+ end: u16(buf[0*len(entries)+0+2*i:]),
+ start: u16(buf[2*len(entries)+2+2*i:]),
+ delta: u16(buf[4*len(entries)+2+2*i:]),
+ offset: u16(buf[6*len(entries)+2+2*i:]),
+ }
+ }
+ indexesBase := f.cmap.offset + offset
+ indexesLength := f.cmap.length - offset
+
+ return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
+ if uint32(r) > 0xffff {
+ return 0, nil
+ }
+
+ c := uint16(r)
+ for i, j := 0, len(entries); i < j; {
+ h := i + (j-i)/2
+ entry := &entries[h]
+ if c < entry.start {
+ j = h
+ } else if entry.end < c {
+ i = h + 1
+ } else if entry.offset == 0 {
+ return GlyphIndex(c + entry.delta), nil
+ } else {
+ offset := uint32(entry.offset) + 2*uint32(h-len(entries)+int(c-entry.start))
+ if offset > indexesLength || offset+2 > indexesLength {
+ return 0, errInvalidCmapTable
+ }
+ if b == nil {
+ b = &Buffer{}
+ }
+ x, err := b.view(&f.src, int(indexesBase+offset), 2)
+ if err != nil {
+ return 0, err
+ }
+ return GlyphIndex(u16(x)), nil
+ }
+ }
+ return 0, nil
+ }, nil
+}
+
+func (f *Font) makeCachedGlyphIndexFormat6(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) {
+ const headerSize = 10
+ if offset+headerSize > f.cmap.length {
+ return nil, nil, errInvalidCmapTable
+ }
+ var err error
+ buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize)
+ if err != nil {
+ return nil, nil, err
+ }
+ offset += headerSize
+
+ firstCode := u16(buf[6:])
+ entryCount := u16(buf[8:])
+
+ eLength := 2 * uint32(entryCount)
+ if offset+eLength > f.cmap.length {
+ return nil, nil, errInvalidCmapTable
+ }
+
+ if entryCount != 0 {
+ buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength))
+ if err != nil {
+ return nil, nil, err
+ }
+ offset += eLength
+ }
+
+ entries := make([]uint16, entryCount)
+ for i := range entries {
+ entries[i] = u16(buf[2*i:])
+ }
+
+ return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
+ if uint16(r) < firstCode {
+ return 0, nil
+ }
+
+ c := int(uint16(r) - firstCode)
+ if c >= len(entries) {
+ return 0, nil
+ }
+ return GlyphIndex(entries[c]), nil
+ }, nil
+}
+
+func (f *Font) makeCachedGlyphIndexFormat12(buf []byte, offset, _ uint32) ([]byte, glyphIndexFunc, error) {
+ const headerSize = 16
+ if offset+headerSize > f.cmap.length {
+ return nil, nil, errInvalidCmapTable
+ }
+ var err error
+ buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize)
+ if err != nil {
+ return nil, nil, err
+ }
+ length := u32(buf[4:])
+ if f.cmap.length < offset || length > f.cmap.length-offset {
+ return nil, nil, errInvalidCmapTable
+ }
+ offset += headerSize
+
+ numGroups := u32(buf[12:])
+ if numGroups > maxCmapSegments {
+ return nil, nil, errUnsupportedNumberOfCmapSegments
+ }
+
+ eLength := 12 * numGroups
+ if headerSize+eLength != length {
+ return nil, nil, errInvalidCmapTable
+ }
+ buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength))
+ if err != nil {
+ return nil, nil, err
+ }
+ offset += eLength
+
+ entries := make([]cmapEntry32, numGroups)
+ for i := range entries {
+ entries[i] = cmapEntry32{
+ start: u32(buf[0+12*i:]),
+ end: u32(buf[4+12*i:]),
+ delta: u32(buf[8+12*i:]),
+ }
+ }
+
+ return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
+ c := uint32(r)
+ for i, j := 0, len(entries); i < j; {
+ h := i + (j-i)/2
+ entry := &entries[h]
+ if c < entry.start {
+ j = h
+ } else if entry.end < c {
+ i = h + 1
+ } else {
+ return GlyphIndex(c - entry.start + entry.delta), nil
+ }
+ }
+ return 0, nil
+ }, nil
+}
+
+type cmapEntry16 struct {
+ end, start, delta, offset uint16
+}
+
+type cmapEntry32 struct {
+ start, end, delta uint32
+}
diff --git a/mkvlib/parser/sfnt/data.go b/mkvlib/parser/sfnt/data.go
new file mode 100644
index 0000000..ad0c139
--- /dev/null
+++ b/mkvlib/parser/sfnt/data.go
@@ -0,0 +1,68 @@
+// generated by go run gen.go; DO NOT EDIT
+
+package sfnt
+
+const numBuiltInPostNames = 258
+
+const builtInPostNamesData = "" +
+ ".notdef.nullnonmarkingreturnspaceexclamquotedblnumbersigndollarp" +
+ "ercentampersandquotesingleparenleftparenrightasteriskpluscommahy" +
+ "phenperiodslashzeroonetwothreefourfivesixseveneightninecolonsemi" +
+ "colonlessequalgreaterquestionatABCDEFGHIJKLMNOPQRSTUVWXYZbracket" +
+ "leftbackslashbracketrightasciicircumunderscoregraveabcdefghijklm" +
+ "nopqrstuvwxyzbraceleftbarbracerightasciitildeAdieresisAringCcedi" +
+ "llaEacuteNtildeOdieresisUdieresisaacuteagraveacircumflexadieresi" +
+ "satildearingccedillaeacuteegraveecircumflexedieresisiacuteigrave" +
+ "icircumflexidieresisntildeoacuteograveocircumflexodieresisotilde" +
+ "uacuteugraveucircumflexudieresisdaggerdegreecentsterlingsectionb" +
+ "ulletparagraphgermandblsregisteredcopyrighttrademarkacutedieresi" +
+ "snotequalAEOslashinfinityplusminuslessequalgreaterequalyenmupart" +
+ "ialdiffsummationproductpiintegralordfeminineordmasculineOmegaaeo" +
+ "slashquestiondownexclamdownlogicalnotradicalflorinapproxequalDel" +
+ "taguillemotleftguillemotrightellipsisnonbreakingspaceAgraveAtild" +
+ "eOtildeOEoeendashemdashquotedblleftquotedblrightquoteleftquoteri" +
+ "ghtdividelozengeydieresisYdieresisfractioncurrencyguilsinglleftg" +
+ "uilsinglrightfifldaggerdblperiodcenteredquotesinglbasequotedblba" +
+ "seperthousandAcircumflexEcircumflexAacuteEdieresisEgraveIacuteIc" +
+ "ircumflexIdieresisIgraveOacuteOcircumflexappleOgraveUacuteUcircu" +
+ "mflexUgravedotlessicircumflextildemacronbrevedotaccentringcedill" +
+ "ahungarumlautogonekcaronLslashlslashScaronscaronZcaronzcaronbrok" +
+ "enbarEthethYacuteyacuteThornthornminusmultiplyonesuperiortwosupe" +
+ "riorthreesuperioronehalfonequarterthreequartersfrancGbrevegbreve" +
+ "IdotaccentScedillascedillaCacutecacuteCcaronccarondcroat"
+
+var builtInPostNamesOffsets = [...]uint16{
+ 0x0000, 0x0007, 0x000c, 0x001c, 0x0021, 0x0027, 0x002f, 0x0039,
+ 0x003f, 0x0046, 0x004f, 0x005a, 0x0063, 0x006d, 0x0075, 0x0079,
+ 0x007e, 0x0084, 0x008a, 0x008f, 0x0093, 0x0096, 0x0099, 0x009e,
+ 0x00a2, 0x00a6, 0x00a9, 0x00ae, 0x00b3, 0x00b7, 0x00bc, 0x00c5,
+ 0x00c9, 0x00ce, 0x00d5, 0x00dd, 0x00df, 0x00e0, 0x00e1, 0x00e2,
+ 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea,
+ 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, 0x00f0, 0x00f1, 0x00f2,
+ 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x0104,
+ 0x010d, 0x0119, 0x0124, 0x012e, 0x0133, 0x0134, 0x0135, 0x0136,
+ 0x0137, 0x0138, 0x0139, 0x013a, 0x013b, 0x013c, 0x013d, 0x013e,
+ 0x013f, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146,
+ 0x0147, 0x0148, 0x0149, 0x014a, 0x014b, 0x014c, 0x014d, 0x0156,
+ 0x0159, 0x0163, 0x016d, 0x0176, 0x017b, 0x0183, 0x0189, 0x018f,
+ 0x0198, 0x01a1, 0x01a7, 0x01ad, 0x01b8, 0x01c1, 0x01c7, 0x01cc,
+ 0x01d4, 0x01da, 0x01e0, 0x01eb, 0x01f4, 0x01fa, 0x0200, 0x020b,
+ 0x0214, 0x021a, 0x0220, 0x0226, 0x0231, 0x023a, 0x0240, 0x0246,
+ 0x024c, 0x0257, 0x0260, 0x0266, 0x026c, 0x0270, 0x0278, 0x027f,
+ 0x0285, 0x028e, 0x0298, 0x02a2, 0x02ab, 0x02b4, 0x02b9, 0x02c1,
+ 0x02c9, 0x02cb, 0x02d1, 0x02d9, 0x02e2, 0x02eb, 0x02f7, 0x02fa,
+ 0x02fc, 0x0307, 0x0310, 0x0317, 0x0319, 0x0321, 0x032c, 0x0338,
+ 0x033d, 0x033f, 0x0345, 0x0351, 0x035b, 0x0365, 0x036c, 0x0372,
+ 0x037d, 0x0382, 0x038f, 0x039d, 0x03a5, 0x03b5, 0x03bb, 0x03c1,
+ 0x03c7, 0x03c9, 0x03cb, 0x03d1, 0x03d7, 0x03e3, 0x03f0, 0x03f9,
+ 0x0403, 0x0409, 0x0410, 0x0419, 0x0422, 0x042a, 0x0432, 0x043f,
+ 0x044d, 0x044f, 0x0451, 0x045a, 0x0468, 0x0476, 0x0482, 0x048d,
+ 0x0498, 0x04a3, 0x04a9, 0x04b2, 0x04b8, 0x04be, 0x04c9, 0x04d2,
+ 0x04d8, 0x04de, 0x04e9, 0x04ee, 0x04f4, 0x04fa, 0x0505, 0x050b,
+ 0x0513, 0x051d, 0x0522, 0x0528, 0x052d, 0x0536, 0x053a, 0x0541,
+ 0x054d, 0x0553, 0x0558, 0x055e, 0x0564, 0x056a, 0x0570, 0x0576,
+ 0x057c, 0x0585, 0x0588, 0x058b, 0x0591, 0x0597, 0x059c, 0x05a1,
+ 0x05a6, 0x05ae, 0x05b9, 0x05c4, 0x05d1, 0x05d8, 0x05e2, 0x05ef,
+ 0x05f4, 0x05fa, 0x0600, 0x060a, 0x0612, 0x061a, 0x0620, 0x0626,
+ 0x062c, 0x0632, 0x0638,
+}
diff --git a/mkvlib/parser/sfnt/gpos.go b/mkvlib/parser/sfnt/gpos.go
new file mode 100644
index 0000000..e0e339c
--- /dev/null
+++ b/mkvlib/parser/sfnt/gpos.go
@@ -0,0 +1,550 @@
+// Copyright 2019 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 (
+ "sort"
+)
+
+const (
+ hexScriptLatn = uint32(0x6c61746e) // latn
+ hexScriptDFLT = uint32(0x44464c54) // DFLT
+ hexFeatureKern = uint32(0x6b65726e) // kern
+)
+
+// kernFunc returns the unscaled kerning value for kerning pair a+b.
+// Returns ErrNotFound if no kerning is specified for this pair.
+type kernFunc func(a, b GlyphIndex) (int16, error)
+
+func (f *Font) parseGPOSKern(buf []byte) ([]byte, []kernFunc, error) {
+ // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos
+
+ if f.gpos.length == 0 {
+ return buf, nil, nil
+ }
+ const headerSize = 10 // GPOS header v1.1 is 14 bytes, but we don't support FeatureVariations
+ if f.gpos.length < headerSize {
+ return buf, nil, errInvalidGPOSTable
+ }
+
+ buf, err := f.src.view(buf, int(f.gpos.offset), headerSize)
+ if err != nil {
+ return buf, nil, err
+ }
+
+ // check for version 1.0/1.1
+ if u16(buf) != 1 || u16(buf[2:]) > 1 {
+ return buf, nil, errUnsupportedGPOSTable
+ }
+ scriptListOffset := u16(buf[4:])
+ featureListOffset := u16(buf[6:])
+ lookupListOffset := u16(buf[8:])
+
+ // get all feature indices for latn script
+ buf, featureIdxs, err := f.parseGPOSScriptFeatures(buf, int(f.gpos.offset)+int(scriptListOffset), hexScriptLatn)
+ if err != nil {
+ return buf, nil, err
+ }
+ if len(featureIdxs) == 0 {
+ // get all feature indices for DFLT script
+ buf, featureIdxs, err = f.parseGPOSScriptFeatures(buf, int(f.gpos.offset)+int(scriptListOffset), hexScriptDFLT)
+ if err != nil {
+ return buf, nil, err
+ }
+ if len(featureIdxs) == 0 {
+ return buf, nil, nil
+ }
+ }
+
+ // get all lookup indices for kern features
+ buf, lookupIdx, err := f.parseGPOSFeaturesLookup(buf, int(f.gpos.offset)+int(featureListOffset), featureIdxs, hexFeatureKern)
+ if err != nil {
+ return buf, nil, err
+ }
+
+ // LookupTableList: lookupCount,[]lookups
+ buf, numLookupTables, err := f.src.varLenView(buf, int(f.gpos.offset)+int(lookupListOffset), 2, 0, 2)
+ if err != nil {
+ return buf, nil, err
+ }
+
+ var kernFuncs []kernFunc
+
+lookupTables:
+ for _, n := range lookupIdx {
+ if n > numLookupTables {
+ return buf, nil, errInvalidGPOSTable
+ }
+ tableOffset := int(f.gpos.offset) + int(lookupListOffset) + int(u16(buf[2+n*2:]))
+
+ // LookupTable: lookupType, lookupFlag, subTableCount, []subtableOffsets, markFilteringSet
+ buf, numSubTables, err := f.src.varLenView(buf, tableOffset, 8, 4, 2)
+ if err != nil {
+ return buf, nil, err
+ }
+
+ flags := u16(buf[2:])
+
+ subTableOffsets := make([]int, numSubTables)
+ for i := 0; i < int(numSubTables); i++ {
+ subTableOffsets[i] = int(tableOffset) + int(u16(buf[6+i*2:]))
+ }
+
+ switch lookupType := u16(buf); lookupType {
+ case 2: // PairPos table
+ case 9:
+ // Extension Positioning table defines an additional u32 offset
+ // to allow subtables to exceed the 16-bit limit.
+ for i := range subTableOffsets {
+ buf, err = f.src.view(buf, subTableOffsets[i], 8)
+ if err != nil {
+ return buf, nil, err
+ }
+ if format := u16(buf); format != 1 {
+ return buf, nil, errUnsupportedExtensionPosFormat
+ }
+ if lookupType := u16(buf[2:]); lookupType != 2 {
+ continue lookupTables
+ }
+ subTableOffsets[i] += int(u32(buf[4:]))
+ }
+ default: // other types are not supported
+ continue
+ }
+
+ if flags&0x0010 > 0 {
+ // useMarkFilteringSet enabled, skip as it is not supported
+ continue
+ }
+
+ for _, subTableOffset := range subTableOffsets {
+ buf, err = f.src.view(buf, int(subTableOffset), 4)
+ if err != nil {
+ return buf, nil, err
+ }
+ format := u16(buf)
+
+ var lookupIndex indexLookupFunc
+ buf, lookupIndex, err = f.makeCachedCoverageLookup(buf, subTableOffset+int(u16(buf[2:])))
+ if err != nil {
+ return buf, nil, err
+ }
+
+ switch format {
+ case 1: // Adjustments for Glyph Pairs
+ buf, kern, err := f.parsePairPosFormat1(buf, subTableOffset, lookupIndex)
+ if err != nil {
+ return buf, nil, err
+ }
+ if kern != nil {
+ kernFuncs = append(kernFuncs, kern)
+ }
+ case 2: // Class Pair Adjustment
+ buf, kern, err := f.parsePairPosFormat2(buf, subTableOffset, lookupIndex)
+ if err != nil {
+ return buf, nil, err
+ }
+ if kern != nil {
+ kernFuncs = append(kernFuncs, kern)
+ }
+ }
+ }
+ }
+
+ return buf, kernFuncs, nil
+}
+
+func (f *Font) parsePairPosFormat1(buf []byte, offset int, lookupIndex indexLookupFunc) ([]byte, kernFunc, error) {
+ // PairPos Format 1: posFormat, coverageOffset, valueFormat1,
+ // valueFormat2, pairSetCount, []pairSetOffsets
+ var err error
+ var nPairs int
+ buf, nPairs, err = f.src.varLenView(buf, offset, 10, 8, 2)
+ if err != nil {
+ return buf, nil, err
+ }
+ // check valueFormat1 and valueFormat2 flags
+ if u16(buf[4:]) != 0x04 || u16(buf[6:]) != 0x00 {
+ // we only support kerning with X_ADVANCE for first glyph
+ return buf, nil, nil
+ }
+
+ // PairPos table contains an array of offsets to PairSet
+ // tables, which contains an array of PairValueRecords.
+ // Calculate length of complete PairPos table by jumping to
+ // last PairSet.
+ // We need to iterate all offsets to find the last pair as
+ // offsets are not sorted and can be repeated.
+ var lastPairSetOffset int
+ for n := 0; n < nPairs; n++ {
+ pairOffset := int(u16(buf[10+n*2:]))
+ if pairOffset > lastPairSetOffset {
+ lastPairSetOffset = pairOffset
+ }
+ }
+ buf, err = f.src.view(buf, offset+lastPairSetOffset, 2)
+ if err != nil {
+ return buf, nil, err
+ }
+
+ pairValueCount := int(u16(buf))
+ // Each PairSet contains the secondGlyph (u16) and one or more value records (all u16).
+ // We only support lookup tables with one value record (X_ADVANCE, see valueFormat1/2 above).
+ lastPairSetLength := 2 + pairValueCount*4
+
+ length := lastPairSetOffset + lastPairSetLength
+ buf, err = f.src.view(buf, offset, length)
+ if err != nil {
+ return buf, nil, err
+ }
+
+ kern := makeCachedPairPosGlyph(lookupIndex, nPairs, buf)
+ return buf, kern, nil
+}
+
+func (f *Font) parsePairPosFormat2(buf []byte, offset int, lookupIndex indexLookupFunc) ([]byte, kernFunc, error) {
+ // PairPos Format 2:
+ // posFormat, coverageOffset, valueFormat1, valueFormat2,
+ // classDef1Offset, classDef2Offset, class1Count, class2Count,
+ // []class1Records
+ var err error
+ buf, err = f.src.view(buf, offset, 16)
+ if err != nil {
+ return buf, nil, err
+ }
+ // check valueFormat1 and valueFormat2 flags
+ if u16(buf[4:]) != 0x04 || u16(buf[6:]) != 0x00 {
+ // we only support kerning with X_ADVANCE for first glyph
+ return buf, nil, nil
+ }
+ numClass1 := int(u16(buf[12:]))
+ numClass2 := int(u16(buf[14:]))
+ cdef1Offset := offset + int(u16(buf[8:]))
+ cdef2Offset := offset + int(u16(buf[10:]))
+ var cdef1, cdef2 classLookupFunc
+ buf, cdef1, err = f.makeCachedClassLookup(buf, cdef1Offset)
+ if err != nil {
+ return buf, nil, err
+ }
+ buf, cdef2, err = f.makeCachedClassLookup(buf, cdef2Offset)
+ if err != nil {
+ return buf, nil, err
+ }
+
+ buf, err = f.src.view(buf, offset+16, numClass1*numClass2*2)
+ if err != nil {
+ return buf, nil, err
+ }
+ kern := makeCachedPairPosClass(
+ lookupIndex,
+ numClass1,
+ numClass2,
+ cdef1,
+ cdef2,
+ buf,
+ )
+
+ return buf, kern, nil
+}
+
+// parseGPOSScriptFeatures returns all indices of features in FeatureTable that
+// are valid for the given script.
+// Returns features from DefaultLangSys, different languages are not supported.
+// However, all observed fonts either do not use different languages or use the
+// same features as DefaultLangSys.
+func (f *Font) parseGPOSScriptFeatures(buf []byte, offset int, script uint32) ([]byte, []int, error) {
+ // ScriptList table: scriptCount, []scriptRecords{scriptTag, scriptOffset}
+ buf, numScriptTables, err := f.src.varLenView(buf, offset, 2, 0, 6)
+ if err != nil {
+ return buf, nil, err
+ }
+
+ // Search ScriptTables for script
+ var scriptTableOffset uint16
+ for i := 0; i < numScriptTables; i++ {
+ scriptTag := u32(buf[2+i*6:])
+ if scriptTag == script {
+ scriptTableOffset = u16(buf[2+i*6+4:])
+ break
+ }
+ }
+ if scriptTableOffset == 0 {
+ return buf, nil, nil
+ }
+
+ // Script table: defaultLangSys, langSysCount, []langSysRecords{langSysTag, langSysOffset}
+ buf, err = f.src.view(buf, offset+int(scriptTableOffset), 2)
+ if err != nil {
+ return buf, nil, err
+ }
+ defaultLangSysOffset := u16(buf)
+
+ if defaultLangSysOffset == 0 {
+ return buf, nil, nil
+ }
+
+ // LangSys table: lookupOrder (reserved), requiredFeatureIndex, featureIndexCount, []featureIndices
+ buf, numFeatures, err := f.src.varLenView(buf, offset+int(scriptTableOffset)+int(defaultLangSysOffset), 6, 4, 2)
+ if err != nil {
+ return buf, nil, err
+ }
+
+ featureIdxs := make([]int, numFeatures)
+ for i := range featureIdxs {
+ featureIdxs[i] = int(u16(buf[6+i*2:]))
+ }
+ return buf, featureIdxs, nil
+}
+
+func (f *Font) parseGPOSFeaturesLookup(buf []byte, offset int, featureIdxs []int, feature uint32) ([]byte, []int, error) {
+ // FeatureList table: featureCount, []featureRecords{featureTag, featureOffset}
+ buf, numFeatureTables, err := f.src.varLenView(buf, offset, 2, 0, 6)
+ if err != nil {
+ return buf, nil, err
+ }
+
+ lookupIdx := make([]int, 0, 4)
+
+ for _, fidx := range featureIdxs {
+ if fidx > numFeatureTables {
+ return buf, nil, errInvalidGPOSTable
+ }
+ featureTag := u32(buf[2+fidx*6:])
+ if featureTag != feature {
+ continue
+ }
+ featureOffset := u16(buf[2+fidx*6+4:])
+
+ buf, numLookups, err := f.src.varLenView(nil, offset+int(featureOffset), 4, 2, 2)
+ if err != nil {
+ return buf, nil, err
+ }
+
+ for i := 0; i < numLookups; i++ {
+ lookupIdx = append(lookupIdx, int(u16(buf[4+i*2:])))
+ }
+ }
+
+ return buf, lookupIdx, nil
+}
+
+func makeCachedPairPosGlyph(cov indexLookupFunc, num int, buf []byte) kernFunc {
+ glyphs := make([]byte, len(buf))
+ copy(glyphs, buf)
+ return func(a, b GlyphIndex) (int16, error) {
+ idx, found := cov(a)
+ if !found {
+ return 0, ErrNotFound
+ }
+ if idx >= num {
+ return 0, ErrNotFound
+ }
+ offset := int(u16(glyphs[10+idx*2:]))
+ if offset+1 >= len(glyphs) {
+ return 0, errInvalidGPOSTable
+ }
+
+ count := int(u16(glyphs[offset:]))
+ for i := 0; i < count; i++ {
+ secondGlyphIndex := GlyphIndex(int(u16(glyphs[offset+2+i*4:])))
+ if secondGlyphIndex == b {
+ return int16(u16(glyphs[offset+2+i*4+2:])), nil
+ }
+ if secondGlyphIndex > b {
+ return 0, ErrNotFound
+ }
+ }
+
+ return 0, ErrNotFound
+ }
+}
+
+func makeCachedPairPosClass(cov indexLookupFunc, num1, num2 int, cdef1, cdef2 classLookupFunc, buf []byte) kernFunc {
+ glyphs := make([]byte, len(buf))
+ copy(glyphs, buf)
+ return func(a, b GlyphIndex) (int16, error) {
+ // check coverage to avoid selection of default class 0
+ _, found := cov(a)
+ if !found {
+ return 0, ErrNotFound
+ }
+ idxa := cdef1(a)
+ idxb := cdef2(b)
+ return int16(u16(glyphs[(idxb+idxa*num2)*2:])), nil
+ }
+}
+
+// indexLookupFunc returns the index into a PairPos table for the provided glyph.
+// Returns false if the glyph is not covered by this lookup.
+type indexLookupFunc func(GlyphIndex) (int, bool)
+
+func (f *Font) makeCachedCoverageLookup(buf []byte, offset int) ([]byte, indexLookupFunc, error) {
+ var err error
+ buf, err = f.src.view(buf, offset, 2)
+ if err != nil {
+ return buf, nil, err
+ }
+ switch u16(buf) {
+ case 1:
+ // Coverage Format 1: coverageFormat, glyphCount, []glyphArray
+ buf, _, err = f.src.varLenView(buf, offset, 4, 2, 2)
+ if err != nil {
+ return buf, nil, err
+ }
+ return buf, makeCachedCoverageList(buf[2:]), nil
+ case 2:
+ // Coverage Format 2: coverageFormat, rangeCount, []rangeRecords{startGlyphID, endGlyphID, startCoverageIndex}
+ buf, _, err = f.src.varLenView(buf, offset, 4, 2, 6)
+ if err != nil {
+ return buf, nil, err
+ }
+ return buf, makeCachedCoverageRange(buf[2:]), nil
+ default:
+ return buf, nil, errUnsupportedCoverageFormat
+ }
+}
+
+func makeCachedCoverageList(buf []byte) indexLookupFunc {
+ num := int(u16(buf))
+ list := make([]byte, len(buf)-2)
+ copy(list, buf[2:])
+ return func(gi GlyphIndex) (int, bool) {
+ idx := sort.Search(num, func(i int) bool {
+ return gi <= GlyphIndex(u16(list[i*2:]))
+ })
+ if idx < num && GlyphIndex(u16(list[idx*2:])) == gi {
+ return idx, true
+ }
+
+ return 0, false
+ }
+}
+
+func makeCachedCoverageRange(buf []byte) indexLookupFunc {
+ num := int(u16(buf))
+ ranges := make([]byte, len(buf)-2)
+ copy(ranges, buf[2:])
+ return func(gi GlyphIndex) (int, bool) {
+ if num == 0 {
+ return 0, false
+ }
+
+ // ranges is an array of startGlyphID, endGlyphID and startCoverageIndex
+ // Ranges are non-overlapping.
+ // The following GlyphIDs/index pairs are stored as follows:
+ // pairs: 130=0, 131=1, 132=2, 133=3, 134=4, 135=5, 137=6
+ // ranges: 130, 135, 0 137, 137, 6
+ // startCoverageIndex is used to calculate the index without counting
+ // the length of the preceding ranges
+
+ idx := sort.Search(num, func(i int) bool {
+ return gi <= GlyphIndex(u16(ranges[i*6:]))
+ })
+ // idx either points to a matching start, or to the next range (or idx==num)
+ // e.g. with the range example from above: 130 points to 130-135 range, 133 points to 137-137 range
+
+ // check if gi is the start of a range, but only if sort.Search returned a valid result
+ if idx < num {
+ if start := u16(ranges[idx*6:]); gi == GlyphIndex(start) {
+ return int(u16(ranges[idx*6+4:])), true
+ }
+ }
+ // check if gi is in previous range
+ if idx > 0 {
+ idx--
+ start, end := u16(ranges[idx*6:]), u16(ranges[idx*6+2:])
+ if gi >= GlyphIndex(start) && gi <= GlyphIndex(end) {
+ return int(u16(ranges[idx*6+4:]) + uint16(gi) - start), true
+ }
+ }
+
+ return 0, false
+ }
+}
+
+// classLookupFunc returns the class ID for the provided glyph. Returns 0
+// (default class) for glyphs not covered by this lookup.
+type classLookupFunc func(GlyphIndex) int
+
+func (f *Font) makeCachedClassLookup(buf []byte, offset int) ([]byte, classLookupFunc, error) {
+ var err error
+ buf, err = f.src.view(buf, offset, 2)
+ if err != nil {
+ return buf, nil, err
+ }
+ switch u16(buf) {
+ case 1:
+ // ClassDefFormat 1: classFormat, startGlyphID, glyphCount, []classValueArray
+ buf, _, err = f.src.varLenView(buf, offset, 6, 4, 2)
+ if err != nil {
+ return buf, nil, err
+ }
+ return buf, makeCachedClassLookupFormat1(buf), nil
+ case 2:
+ // ClassDefFormat 2: classFormat, classRangeCount, []classRangeRecords
+ buf, _, err = f.src.varLenView(buf, offset, 4, 2, 6)
+ if err != nil {
+ return buf, nil, err
+ }
+ return buf, makeCachedClassLookupFormat2(buf), nil
+ default:
+ return buf, nil, errUnsupportedClassDefFormat
+ }
+}
+
+func makeCachedClassLookupFormat1(buf []byte) classLookupFunc {
+ startGI := u16(buf[2:])
+ num := u16(buf[4:])
+ classIDs := make([]byte, len(buf)-4)
+ copy(classIDs, buf[6:])
+
+ return func(gi GlyphIndex) int {
+ // classIDs is an array of target class IDs. gi is the index into that array (minus startGI).
+ if gi < GlyphIndex(startGI) || gi >= GlyphIndex(startGI+num) {
+ // default to class 0
+ return 0
+ }
+ return int(u16(classIDs[(int(gi)-int(startGI))*2:]))
+ }
+}
+
+func makeCachedClassLookupFormat2(buf []byte) classLookupFunc {
+ num := int(u16(buf[2:]))
+ classRanges := make([]byte, len(buf)-2)
+ copy(classRanges, buf[4:])
+
+ return func(gi GlyphIndex) int {
+ if num == 0 {
+ return 0 // default to class 0
+ }
+
+ // classRange is an array of startGlyphID, endGlyphID and target class ID.
+ // Ranges are non-overlapping.
+ // E.g. 130, 135, 1 137, 137, 5 etc
+
+ idx := sort.Search(num, func(i int) bool {
+ return gi <= GlyphIndex(u16(classRanges[i*6:]))
+ })
+ // idx either points to a matching start, or to the next range (or idx==num)
+ // e.g. with the range example from above: 130 points to 130-135 range, 133 points to 137-137 range
+
+ // check if gi is the start of a range, but only if sort.Search returned a valid result
+ if idx < num {
+ if start := u16(classRanges[idx*6:]); gi == GlyphIndex(start) {
+ return int(u16(classRanges[idx*6+4:]))
+ }
+ }
+ // check if gi is in previous range
+ if idx > 0 {
+ idx--
+ start, end := u16(classRanges[idx*6:]), u16(classRanges[idx*6+2:])
+ if gi >= GlyphIndex(start) && gi <= GlyphIndex(end) {
+ return int(u16(classRanges[idx*6+4:]))
+ }
+ }
+ // default to class 0
+ return 0
+ }
+}
diff --git a/mkvlib/parser/sfnt/postscript.go b/mkvlib/parser/sfnt/postscript.go
new file mode 100644
index 0000000..2a21997
--- /dev/null
+++ b/mkvlib/parser/sfnt/postscript.go
@@ -0,0 +1,1426 @@
+// Copyright 2016 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
+
+// Compact Font Format (CFF) fonts are written in PostScript, a stack-based
+// programming language.
+//
+// A fundamental concept is a DICT, or a key-value map, expressed in reverse
+// Polish notation. For example, this sequence of operations:
+// - push the number 379
+// - version operator
+// - push the number 392
+// - Notice operator
+// - etc
+// - push the number 100
+// - push the number 0
+// - push the number 500
+// - push the number 800
+// - FontBBox operator
+// - etc
+// defines a DICT that maps "version" to the String ID (SID) 379, "Notice" to
+// the SID 392, "FontBBox" to the four numbers [100, 0, 500, 800], etc.
+//
+// The first 391 String IDs (starting at 0) are predefined as per the CFF spec
+// Appendix A, in 5176.CFF.pdf referenced below. For example, 379 means
+// "001.000". String ID 392 is not predefined, and is mapped by a separate
+// structure, the "String INDEX", inside the CFF data. (String ID 391 is also
+// not predefined. Specifically for ../testdata/CFFTest.otf, 391 means
+// "uni4E2D", as this font contains a glyph for U+4E2D).
+//
+// The actual glyph vectors are similarly encoded (in PostScript), in a format
+// called Type 2 Charstrings. The wire encoding is similar to but not exactly
+// the same as CFF's. For example, the byte 0x05 means FontBBox for CFF DICTs,
+// but means rlineto (relative line-to) for Type 2 Charstrings. See
+// 5176.CFF.pdf Appendix H and 5177.Type2.pdf Appendix A in the PDF files
+// referenced below.
+//
+// CFF is a stand-alone format, but CFF as used in SFNT fonts have further
+// restrictions. For example, a stand-alone CFF can contain multiple fonts, but
+// https://www.microsoft.com/typography/OTSPEC/cff.htm says that "The Name
+// INDEX in the CFF must contain only one entry; that is, there must be only
+// one font in the CFF FontSet".
+//
+// The relevant specifications are:
+// - http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5176.CFF.pdf
+// - http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5177.Type2.pdf
+
+import (
+ "fmt"
+ "math"
+ "strconv"
+
+ "golang.org/x/image/math/fixed"
+)
+
+const (
+ // psArgStackSize is the argument stack size for a PostScript interpreter.
+ // 5176.CFF.pdf section 4 "DICT Data" says that "An operator may be
+ // preceded by up to a maximum of 48 operands". 5177.Type2.pdf Appendix B
+ // "Type 2 Charstring Implementation Limits" says that "Argument stack 48".
+ psArgStackSize = 48
+
+ // Similarly, Appendix B says "Subr nesting, stack limit 10".
+ psCallStackSize = 10
+)
+
+func bigEndian(b []byte) uint32 {
+ switch len(b) {
+ case 1:
+ return uint32(b[0])
+ case 2:
+ return uint32(b[0])<<8 | uint32(b[1])
+ case 3:
+ return uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2])
+ case 4:
+ return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
+ }
+ panic("unreachable")
+}
+
+// fdSelect holds a CFF font's Font Dict Select data.
+type fdSelect struct {
+ format uint8
+ numRanges uint16
+ offset int32
+}
+
+func (t *fdSelect) lookup(f *Font, b *Buffer, x GlyphIndex) (int, error) {
+ switch t.format {
+ case 0:
+ buf, err := b.view(&f.src, int(t.offset)+int(x), 1)
+ if err != nil {
+ return 0, err
+ }
+ return int(buf[0]), nil
+ case 3:
+ lo, hi := 0, int(t.numRanges)
+ for lo < hi {
+ i := (lo + hi) / 2
+ buf, err := b.view(&f.src, int(t.offset)+3*i, 3+2)
+ if err != nil {
+ return 0, err
+ }
+ // buf holds the range [xlo, xhi).
+ if xlo := GlyphIndex(u16(buf[0:])); x < xlo {
+ hi = i
+ continue
+ }
+ if xhi := GlyphIndex(u16(buf[3:])); xhi <= x {
+ lo = i + 1
+ continue
+ }
+ return int(buf[2]), nil
+ }
+ }
+ return 0, ErrNotFound
+}
+
+// cffParser parses the CFF table from an SFNT font.
+type cffParser struct {
+ src *source
+ base int
+ offset int
+ end int
+ err error
+
+ buf []byte
+ locBuf [2]uint32
+
+ psi psInterpreter
+}
+
+func (p *cffParser) parse(numGlyphs int32) (ret glyphData, err error) {
+ // Parse the header.
+ {
+ if !p.read(4) {
+ return glyphData{}, p.err
+ }
+ if p.buf[0] != 1 || p.buf[1] != 0 || p.buf[2] != 4 {
+ return glyphData{}, errUnsupportedCFFVersion
+ }
+ }
+
+ // Parse the Name INDEX.
+ {
+ count, offSize, ok := p.parseIndexHeader()
+ if !ok {
+ return glyphData{}, p.err
+ }
+ // https://www.microsoft.com/typography/OTSPEC/cff.htm says that "The
+ // Name INDEX in the CFF must contain only one entry".
+ if count != 1 {
+ return glyphData{}, errInvalidCFFTable
+ }
+ if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
+ return glyphData{}, p.err
+ }
+ p.offset = int(p.locBuf[1])
+ }
+
+ // Parse the Top DICT INDEX.
+ p.psi.topDict.initialize()
+ {
+ count, offSize, ok := p.parseIndexHeader()
+ if !ok {
+ return glyphData{}, p.err
+ }
+ // 5176.CFF.pdf section 8 "Top DICT INDEX" says that the count here
+ // should match the count of the Name INDEX, which is 1.
+ if count != 1 {
+ return glyphData{}, errInvalidCFFTable
+ }
+ if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
+ return glyphData{}, p.err
+ }
+ if !p.read(int(p.locBuf[1] - p.locBuf[0])) {
+ return glyphData{}, p.err
+ }
+ if p.err = p.psi.run(psContextTopDict, p.buf, 0, 0); p.err != nil {
+ return glyphData{}, p.err
+ }
+ }
+
+ // Skip the String INDEX.
+ {
+ count, offSize, ok := p.parseIndexHeader()
+ if !ok {
+ return glyphData{}, p.err
+ }
+ if count != 0 {
+ // Read the last location. Locations are off by 1 byte. See the
+ // comment in parseIndexLocations.
+ if !p.skip(int(count * offSize)) {
+ return glyphData{}, p.err
+ }
+ if !p.read(int(offSize)) {
+ return glyphData{}, p.err
+ }
+ loc := bigEndian(p.buf) - 1
+ // Check that locations are in bounds.
+ if uint32(p.end-p.offset) < loc {
+ return glyphData{}, errInvalidCFFTable
+ }
+ // Skip the index data.
+ if !p.skip(int(loc)) {
+ return glyphData{}, p.err
+ }
+ }
+ }
+
+ // Parse the Global Subrs [Subroutines] INDEX.
+ {
+ count, offSize, ok := p.parseIndexHeader()
+ if !ok {
+ return glyphData{}, p.err
+ }
+ if count != 0 {
+ if count > maxNumSubroutines {
+ return glyphData{}, errUnsupportedNumberOfSubroutines
+ }
+ ret.gsubrs = make([]uint32, count+1)
+ if !p.parseIndexLocations(ret.gsubrs, count, offSize) {
+ return glyphData{}, p.err
+ }
+ }
+ }
+
+ // Parse the CharStrings INDEX, whose location was found in the Top DICT.
+ {
+ if !p.seekFromBase(p.psi.topDict.charStringsOffset) {
+ return glyphData{}, errInvalidCFFTable
+ }
+ count, offSize, ok := p.parseIndexHeader()
+ if !ok {
+ return glyphData{}, p.err
+ }
+ if count == 0 || int32(count) != numGlyphs {
+ return glyphData{}, errInvalidCFFTable
+ }
+ ret.locations = make([]uint32, count+1)
+ if !p.parseIndexLocations(ret.locations, count, offSize) {
+ return glyphData{}, p.err
+ }
+ }
+
+ if !p.psi.topDict.isCIDFont {
+ // Parse the Private DICT, whose location was found in the Top DICT.
+ ret.singleSubrs, err = p.parsePrivateDICT(
+ p.psi.topDict.privateDictOffset,
+ p.psi.topDict.privateDictLength,
+ )
+ if err != nil {
+ return glyphData{}, err
+ }
+
+ } else {
+ // Parse the Font Dict Select data, whose location was found in the Top
+ // DICT.
+ ret.fdSelect, err = p.parseFDSelect(p.psi.topDict.fdSelect, numGlyphs)
+ if err != nil {
+ return glyphData{}, err
+ }
+
+ // Parse the Font Dicts. Each one contains its own Private DICT.
+ if !p.seekFromBase(p.psi.topDict.fdArray) {
+ return glyphData{}, errInvalidCFFTable
+ }
+
+ count, offSize, ok := p.parseIndexHeader()
+ if !ok {
+ return glyphData{}, p.err
+ }
+ if count > maxNumFontDicts {
+ return glyphData{}, errUnsupportedNumberOfFontDicts
+ }
+
+ fdLocations := make([]uint32, count+1)
+ if !p.parseIndexLocations(fdLocations, count, offSize) {
+ return glyphData{}, p.err
+ }
+
+ privateDicts := make([]struct {
+ offset, length int32
+ }, count)
+
+ for i := range privateDicts {
+ length := fdLocations[i+1] - fdLocations[i]
+ if !p.read(int(length)) {
+ return glyphData{}, errInvalidCFFTable
+ }
+ p.psi.topDict.initialize()
+ if p.err = p.psi.run(psContextTopDict, p.buf, 0, 0); p.err != nil {
+ return glyphData{}, p.err
+ }
+ privateDicts[i].offset = p.psi.topDict.privateDictOffset
+ privateDicts[i].length = p.psi.topDict.privateDictLength
+ }
+
+ ret.multiSubrs = make([][]uint32, count)
+ for i, pd := range privateDicts {
+ ret.multiSubrs[i], err = p.parsePrivateDICT(pd.offset, pd.length)
+ if err != nil {
+ return glyphData{}, err
+ }
+ }
+ }
+
+ return ret, err
+}
+
+// parseFDSelect parses the Font Dict Select data as per 5176.CFF.pdf section
+// 19 "FDSelect".
+func (p *cffParser) parseFDSelect(offset int32, numGlyphs int32) (ret fdSelect, err error) {
+ if !p.seekFromBase(p.psi.topDict.fdSelect) {
+ return fdSelect{}, errInvalidCFFTable
+ }
+ if !p.read(1) {
+ return fdSelect{}, p.err
+ }
+ ret.format = p.buf[0]
+ switch ret.format {
+ case 0:
+ if p.end-p.offset < int(numGlyphs) {
+ return fdSelect{}, errInvalidCFFTable
+ }
+ ret.offset = int32(p.offset)
+ return ret, nil
+ case 3:
+ if !p.read(2) {
+ return fdSelect{}, p.err
+ }
+ ret.numRanges = u16(p.buf)
+ if p.end-p.offset < 3*int(ret.numRanges)+2 {
+ return fdSelect{}, errInvalidCFFTable
+ }
+ ret.offset = int32(p.offset)
+ return ret, nil
+ }
+ return fdSelect{}, errUnsupportedCFFFDSelectTable
+}
+
+func (p *cffParser) parsePrivateDICT(offset, length int32) (subrs []uint32, err error) {
+ p.psi.privateDict.initialize()
+ if length != 0 {
+ fullLength := int32(p.end - p.base)
+ if offset <= 0 || fullLength < offset || fullLength-offset < length || length < 0 {
+ return nil, errInvalidCFFTable
+ }
+ p.offset = p.base + int(offset)
+ if !p.read(int(length)) {
+ return nil, p.err
+ }
+ if p.err = p.psi.run(psContextPrivateDict, p.buf, 0, 0); p.err != nil {
+ return nil, p.err
+ }
+ }
+
+ // Parse the Local Subrs [Subroutines] INDEX, whose location was found in
+ // the Private DICT.
+ if p.psi.privateDict.subrsOffset != 0 {
+ if !p.seekFromBase(offset + p.psi.privateDict.subrsOffset) {
+ return nil, errInvalidCFFTable
+ }
+ count, offSize, ok := p.parseIndexHeader()
+ if !ok {
+ return nil, p.err
+ }
+ if count != 0 {
+ if count > maxNumSubroutines {
+ return nil, errUnsupportedNumberOfSubroutines
+ }
+ subrs = make([]uint32, count+1)
+ if !p.parseIndexLocations(subrs, count, offSize) {
+ return nil, p.err
+ }
+ }
+ }
+
+ return subrs, err
+}
+
+// read sets p.buf to view the n bytes from p.offset to p.offset+n. It also
+// advances p.offset by n.
+//
+// As per the source.view method, the caller should not modify the contents of
+// p.buf after read returns, other than by calling read again.
+//
+// The caller should also avoid modifying the pointer / length / capacity of
+// the p.buf slice, not just avoid modifying the slice's contents, in order to
+// maximize the opportunity to re-use p.buf's allocated memory when viewing the
+// underlying source data for subsequent read calls.
+func (p *cffParser) read(n int) (ok bool) {
+ if n < 0 || p.end-p.offset < n {
+ p.err = errInvalidCFFTable
+ return false
+ }
+ p.buf, p.err = p.src.view(p.buf, p.offset, n)
+ // TODO: if p.err == io.EOF, change that to a different error??
+ p.offset += n
+ return p.err == nil
+}
+
+func (p *cffParser) skip(n int) (ok bool) {
+ if p.end-p.offset < n {
+ p.err = errInvalidCFFTable
+ return false
+ }
+ p.offset += n
+ return true
+}
+
+func (p *cffParser) seekFromBase(offset int32) (ok bool) {
+ if offset < 0 || int32(p.end-p.base) < offset {
+ return false
+ }
+ p.offset = p.base + int(offset)
+ return true
+}
+
+func (p *cffParser) parseIndexHeader() (count, offSize int32, ok bool) {
+ if !p.read(2) {
+ return 0, 0, false
+ }
+ count = int32(u16(p.buf[:2]))
+ // 5176.CFF.pdf section 5 "INDEX Data" says that "An empty INDEX is
+ // represented by a count field with a 0 value and no additional fields.
+ // Thus, the total size of an empty INDEX is 2 bytes".
+ if count == 0 {
+ return count, 0, true
+ }
+ if !p.read(1) {
+ return 0, 0, false
+ }
+ offSize = int32(p.buf[0])
+ if offSize < 1 || 4 < offSize {
+ p.err = errInvalidCFFTable
+ return 0, 0, false
+ }
+ return count, offSize, true
+}
+
+func (p *cffParser) parseIndexLocations(dst []uint32, count, offSize int32) (ok bool) {
+ if count == 0 {
+ return true
+ }
+ if len(dst) != int(count+1) {
+ panic("unreachable")
+ }
+ if !p.read(len(dst) * int(offSize)) {
+ return false
+ }
+
+ buf, prev := p.buf, uint32(0)
+ for i := range dst {
+ loc := bigEndian(buf[:offSize])
+ buf = buf[offSize:]
+
+ // Locations are off by 1 byte. 5176.CFF.pdf section 5 "INDEX Data"
+ // says that "Offsets in the offset array are relative to the byte that
+ // precedes the object data... This ensures that every object has a
+ // corresponding offset which is always nonzero".
+ if loc == 0 {
+ p.err = errInvalidCFFTable
+ return false
+ }
+ loc--
+
+ // In the same paragraph, "Therefore the first element of the offset
+ // array is always 1" before correcting for the off-by-1.
+ if i == 0 {
+ if loc != 0 {
+ p.err = errInvalidCFFTable
+ break
+ }
+ } else if loc <= prev { // Check that locations are increasing.
+ p.err = errInvalidCFFTable
+ break
+ }
+
+ // Check that locations are in bounds.
+ if uint32(p.end-p.offset) < loc {
+ p.err = errInvalidCFFTable
+ break
+ }
+
+ dst[i] = uint32(p.offset) + loc
+ prev = loc
+ }
+ return p.err == nil
+}
+
+type psCallStackEntry struct {
+ offset, length uint32
+}
+
+type psContext uint32
+
+const (
+ psContextTopDict psContext = iota
+ psContextPrivateDict
+ psContextType2Charstring
+)
+
+// psTopDictData contains fields specific to the Top DICT context.
+type psTopDictData struct {
+ charStringsOffset int32
+ fdArray int32
+ fdSelect int32
+ isCIDFont bool
+ privateDictOffset int32
+ privateDictLength int32
+}
+
+func (d *psTopDictData) initialize() {
+ *d = psTopDictData{}
+}
+
+// psPrivateDictData contains fields specific to the Private DICT context.
+type psPrivateDictData struct {
+ subrsOffset int32
+}
+
+func (d *psPrivateDictData) initialize() {
+ *d = psPrivateDictData{}
+}
+
+// psType2CharstringsData contains fields specific to the Type 2 Charstrings
+// context.
+type psType2CharstringsData struct {
+ f *Font
+ b *Buffer
+ x int32
+ y int32
+ firstX int32
+ firstY int32
+ hintBits int32
+ seenWidth bool
+ ended bool
+ glyphIndex GlyphIndex
+ // fdSelectIndexPlusOne is the result of the Font Dict Select lookup, plus
+ // one. That plus one lets us use the zero value to denote either unused
+ // (for CFF fonts with a single Font Dict) or lazily evaluated.
+ fdSelectIndexPlusOne int32
+}
+
+func (d *psType2CharstringsData) initialize(f *Font, b *Buffer, glyphIndex GlyphIndex) {
+ *d = psType2CharstringsData{
+ f: f,
+ b: b,
+ glyphIndex: glyphIndex,
+ }
+}
+
+func (d *psType2CharstringsData) closePath() {
+ if d.x != d.firstX || d.y != d.firstY {
+ d.b.segments = append(d.b.segments, Segment{
+ Op: SegmentOpLineTo,
+ Args: [3]fixed.Point26_6{{
+ X: fixed.Int26_6(d.firstX),
+ Y: fixed.Int26_6(d.firstY),
+ }},
+ })
+ }
+}
+
+func (d *psType2CharstringsData) moveTo(dx, dy int32) {
+ d.closePath()
+ d.x += dx
+ d.y += dy
+ d.b.segments = append(d.b.segments, Segment{
+ Op: SegmentOpMoveTo,
+ Args: [3]fixed.Point26_6{{
+ X: fixed.Int26_6(d.x),
+ Y: fixed.Int26_6(d.y),
+ }},
+ })
+ d.firstX = d.x
+ d.firstY = d.y
+}
+
+func (d *psType2CharstringsData) lineTo(dx, dy int32) {
+ d.x += dx
+ d.y += dy
+ d.b.segments = append(d.b.segments, Segment{
+ Op: SegmentOpLineTo,
+ Args: [3]fixed.Point26_6{{
+ X: fixed.Int26_6(d.x),
+ Y: fixed.Int26_6(d.y),
+ }},
+ })
+}
+
+func (d *psType2CharstringsData) cubeTo(dxa, dya, dxb, dyb, dxc, dyc int32) {
+ d.x += dxa
+ d.y += dya
+ xa := fixed.Int26_6(d.x)
+ ya := fixed.Int26_6(d.y)
+ d.x += dxb
+ d.y += dyb
+ xb := fixed.Int26_6(d.x)
+ yb := fixed.Int26_6(d.y)
+ d.x += dxc
+ d.y += dyc
+ xc := fixed.Int26_6(d.x)
+ yc := fixed.Int26_6(d.y)
+ d.b.segments = append(d.b.segments, Segment{
+ Op: SegmentOpCubeTo,
+ Args: [3]fixed.Point26_6{{X: xa, Y: ya}, {X: xb, Y: yb}, {X: xc, Y: yc}},
+ })
+}
+
+// psInterpreter is a PostScript interpreter.
+type psInterpreter struct {
+ ctx psContext
+ instructions []byte
+ instrOffset uint32
+ instrLength uint32
+ argStack struct {
+ a [psArgStackSize]int32
+ top int32
+ }
+ callStack struct {
+ a [psCallStackSize]psCallStackEntry
+ top int32
+ }
+ parseNumberBuf [maxRealNumberStrLen]byte
+
+ topDict psTopDictData
+ privateDict psPrivateDictData
+ type2Charstrings psType2CharstringsData
+}
+
+func (p *psInterpreter) hasMoreInstructions() bool {
+ if len(p.instructions) != 0 {
+ return true
+ }
+ for i := int32(0); i < p.callStack.top; i++ {
+ if p.callStack.a[i].length != 0 {
+ return true
+ }
+ }
+ return false
+}
+
+// run runs the instructions in the given PostScript context. For the
+// psContextType2Charstring context, offset and length give the location of the
+// instructions in p.type2Charstrings.f.src.
+func (p *psInterpreter) run(ctx psContext, instructions []byte, offset, length uint32) error {
+ p.ctx = ctx
+ p.instructions = instructions
+ p.instrOffset = offset
+ p.instrLength = length
+ p.argStack.top = 0
+ p.callStack.top = 0
+
+loop:
+ for len(p.instructions) > 0 {
+ // Push a numeric operand on the stack, if applicable.
+ if hasResult, err := p.parseNumber(); hasResult {
+ if err != nil {
+ return err
+ }
+ continue
+ }
+
+ // Otherwise, execute an operator.
+ b := p.instructions[0]
+ p.instructions = p.instructions[1:]
+
+ for escaped, ops := false, psOperators[ctx][0]; ; {
+ if b == escapeByte && !escaped {
+ if len(p.instructions) <= 0 {
+ return errInvalidCFFTable
+ }
+ b = p.instructions[0]
+ p.instructions = p.instructions[1:]
+ escaped = true
+ ops = psOperators[ctx][1]
+ continue
+ }
+
+ if int(b) < len(ops) {
+ if op := ops[b]; op.name != "" {
+ if p.argStack.top < op.numPop {
+ return errInvalidCFFTable
+ }
+ if op.run != nil {
+ if err := op.run(p); err != nil {
+ return err
+ }
+ }
+ if op.numPop < 0 {
+ p.argStack.top = 0
+ } else {
+ p.argStack.top -= op.numPop
+ }
+ continue loop
+ }
+ }
+
+ if escaped {
+ return fmt.Errorf("sfnt: unrecognized CFF 2-byte operator (12 %d)", b)
+ } else {
+ return fmt.Errorf("sfnt: unrecognized CFF 1-byte operator (%d)", b)
+ }
+ }
+ }
+ return nil
+}
+
+// See 5176.CFF.pdf section 4 "DICT Data".
+func (p *psInterpreter) parseNumber() (hasResult bool, err error) {
+ number := int32(0)
+ switch b := p.instructions[0]; {
+ case b == 28:
+ if len(p.instructions) < 3 {
+ return true, errInvalidCFFTable
+ }
+ number, hasResult = int32(int16(u16(p.instructions[1:]))), true
+ p.instructions = p.instructions[3:]
+
+ case b == 29 && p.ctx != psContextType2Charstring:
+ if len(p.instructions) < 5 {
+ return true, errInvalidCFFTable
+ }
+ number, hasResult = int32(u32(p.instructions[1:])), true
+ p.instructions = p.instructions[5:]
+
+ case b == 30 && p.ctx != psContextType2Charstring:
+ // Parse a real number. This isn't listed in 5176.CFF.pdf Table 3
+ // "Operand Encoding" but that table lists integer encodings. Further
+ // down the page it says "A real number operand is provided in addition
+ // to integer operands. This operand begins with a byte value of 30
+ // followed by a variable-length sequence of bytes."
+
+ s := p.parseNumberBuf[:0]
+ p.instructions = p.instructions[1:]
+ loop:
+ for {
+ if len(p.instructions) == 0 {
+ return true, errInvalidCFFTable
+ }
+ b := p.instructions[0]
+ p.instructions = p.instructions[1:]
+ // Process b's two nibbles, high then low.
+ for i := 0; i < 2; i++ {
+ nib := b >> 4
+ b = b << 4
+ if nib == 0x0f {
+ f, err := strconv.ParseFloat(string(s), 32)
+ if err != nil {
+ return true, errInvalidCFFTable
+ }
+ number, hasResult = int32(math.Float32bits(float32(f))), true
+ break loop
+ }
+ if nib == 0x0d {
+ return true, errInvalidCFFTable
+ }
+ if len(s)+maxNibbleDefsLength > len(p.parseNumberBuf) {
+ return true, errUnsupportedRealNumberEncoding
+ }
+ s = append(s, nibbleDefs[nib]...)
+ }
+ }
+
+ case b < 32:
+ // No-op.
+
+ case b < 247:
+ p.instructions = p.instructions[1:]
+ number, hasResult = int32(b)-139, true
+
+ case b < 251:
+ if len(p.instructions) < 2 {
+ return true, errInvalidCFFTable
+ }
+ b1 := p.instructions[1]
+ p.instructions = p.instructions[2:]
+ number, hasResult = +int32(b-247)*256+int32(b1)+108, true
+
+ case b < 255:
+ if len(p.instructions) < 2 {
+ return true, errInvalidCFFTable
+ }
+ b1 := p.instructions[1]
+ p.instructions = p.instructions[2:]
+ number, hasResult = -int32(b-251)*256-int32(b1)-108, true
+
+ case b == 255 && p.ctx == psContextType2Charstring:
+ if len(p.instructions) < 5 {
+ return true, errInvalidCFFTable
+ }
+ number, hasResult = int32(u32(p.instructions[1:])), true
+ p.instructions = p.instructions[5:]
+ // 5177.Type2.pdf section 3.2 "Charstring Number Encoding" says "If the
+ // charstring byte contains the value 255... [this] number is
+ // interpreted as a Fixed; that is, a signed number with 16 bits of
+ // fraction".
+ //
+ // TODO: change the psType2CharstringsData.b.segments and
+ // psInterpreter.argStack data structures to optionally hold fixed
+ // point values, not just integer values. That's a substantial
+ // re-design, though. Until then, just round the 16.16 fixed point
+ // number to the closest integer value. This isn't just "number =
+ // ((number + 0x8000) >> 16)" because of potential overflow.
+ number = (number >> 16) + (1 & (number >> 15))
+ }
+
+ if hasResult {
+ if p.argStack.top == psArgStackSize {
+ return true, errInvalidCFFTable
+ }
+ p.argStack.a[p.argStack.top] = number
+ p.argStack.top++
+ }
+ return hasResult, nil
+}
+
+const maxNibbleDefsLength = len("E-")
+
+// nibbleDefs encodes 5176.CFF.pdf Table 5 "Nibble Definitions".
+var nibbleDefs = [16]string{
+ 0x00: "0",
+ 0x01: "1",
+ 0x02: "2",
+ 0x03: "3",
+ 0x04: "4",
+ 0x05: "5",
+ 0x06: "6",
+ 0x07: "7",
+ 0x08: "8",
+ 0x09: "9",
+ 0x0a: ".",
+ 0x0b: "E",
+ 0x0c: "E-",
+ 0x0d: "",
+ 0x0e: "-",
+ 0x0f: "",
+}
+
+type psOperator struct {
+ // numPop is the number of stack values to pop. -1 means "array" and -2
+ // means "delta" as per 5176.CFF.pdf Table 6 "Operand Types".
+ numPop int32
+ // name is the operator name. An empty name (i.e. the zero value for the
+ // struct overall) means an unrecognized 1-byte operator.
+ name string
+ // run is the function that implements the operator. Nil means that we
+ // ignore the operator, other than popping its arguments off the stack.
+ run func(*psInterpreter) error
+}
+
+// psOperators holds the 1-byte and 2-byte operators for PostScript interpreter
+// contexts.
+var psOperators = [...][2][]psOperator{
+ // The Top DICT operators are defined by 5176.CFF.pdf Table 9 "Top DICT
+ // Operator Entries" and Table 10 "CIDFont Operator Extensions".
+ psContextTopDict: {{
+ // 1-byte operators.
+ 0: {+1, "version", nil},
+ 1: {+1, "Notice", nil},
+ 2: {+1, "FullName", nil},
+ 3: {+1, "FamilyName", nil},
+ 4: {+1, "Weight", nil},
+ 5: {-1, "FontBBox", nil},
+ 13: {+1, "UniqueID", nil},
+ 14: {-1, "XUID", nil},
+ 15: {+1, "charset", nil},
+ 16: {+1, "Encoding", nil},
+ 17: {+1, "CharStrings", func(p *psInterpreter) error {
+ p.topDict.charStringsOffset = p.argStack.a[p.argStack.top-1]
+ return nil
+ }},
+ 18: {+2, "Private", func(p *psInterpreter) error {
+ p.topDict.privateDictLength = p.argStack.a[p.argStack.top-2]
+ p.topDict.privateDictOffset = p.argStack.a[p.argStack.top-1]
+ return nil
+ }},
+ }, {
+ // 2-byte operators. The first byte is the escape byte.
+ 0: {+1, "Copyright", nil},
+ 1: {+1, "isFixedPitch", nil},
+ 2: {+1, "ItalicAngle", nil},
+ 3: {+1, "UnderlinePosition", nil},
+ 4: {+1, "UnderlineThickness", nil},
+ 5: {+1, "PaintType", nil},
+ 6: {+1, "CharstringType", nil},
+ 7: {-1, "FontMatrix", nil},
+ 8: {+1, "StrokeWidth", nil},
+ 20: {+1, "SyntheticBase", nil},
+ 21: {+1, "PostScript", nil},
+ 22: {+1, "BaseFontName", nil},
+ 23: {-2, "BaseFontBlend", nil},
+ 30: {+3, "ROS", func(p *psInterpreter) error {
+ p.topDict.isCIDFont = true
+ return nil
+ }},
+ 31: {+1, "CIDFontVersion", nil},
+ 32: {+1, "CIDFontRevision", nil},
+ 33: {+1, "CIDFontType", nil},
+ 34: {+1, "CIDCount", nil},
+ 35: {+1, "UIDBase", nil},
+ 36: {+1, "FDArray", func(p *psInterpreter) error {
+ p.topDict.fdArray = p.argStack.a[p.argStack.top-1]
+ return nil
+ }},
+ 37: {+1, "FDSelect", func(p *psInterpreter) error {
+ p.topDict.fdSelect = p.argStack.a[p.argStack.top-1]
+ return nil
+ }},
+ 38: {+1, "FontName", nil},
+ }},
+
+ // The Private DICT operators are defined by 5176.CFF.pdf Table 23 "Private
+ // DICT Operators".
+ psContextPrivateDict: {{
+ // 1-byte operators.
+ 6: {-2, "BlueValues", nil},
+ 7: {-2, "OtherBlues", nil},
+ 8: {-2, "FamilyBlues", nil},
+ 9: {-2, "FamilyOtherBlues", nil},
+ 10: {+1, "StdHW", nil},
+ 11: {+1, "StdVW", nil},
+ 19: {+1, "Subrs", func(p *psInterpreter) error {
+ p.privateDict.subrsOffset = p.argStack.a[p.argStack.top-1]
+ return nil
+ }},
+ 20: {+1, "defaultWidthX", nil},
+ 21: {+1, "nominalWidthX", nil},
+ }, {
+ // 2-byte operators. The first byte is the escape byte.
+ 9: {+1, "BlueScale", nil},
+ 10: {+1, "BlueShift", nil},
+ 11: {+1, "BlueFuzz", nil},
+ 12: {-2, "StemSnapH", nil},
+ 13: {-2, "StemSnapV", nil},
+ 14: {+1, "ForceBold", nil},
+ 17: {+1, "LanguageGroup", nil},
+ 18: {+1, "ExpansionFactor", nil},
+ 19: {+1, "initialRandomSeed", nil},
+ }},
+
+ // The Type 2 Charstring operators are defined by 5177.Type2.pdf Appendix A
+ // "Type 2 Charstring Command Codes".
+ psContextType2Charstring: {{
+ // 1-byte operators.
+ 0: {}, // Reserved.
+ 1: {-1, "hstem", t2CStem},
+ 2: {}, // Reserved.
+ 3: {-1, "vstem", t2CStem},
+ 4: {-1, "vmoveto", t2CVmoveto},
+ 5: {-1, "rlineto", t2CRlineto},
+ 6: {-1, "hlineto", t2CHlineto},
+ 7: {-1, "vlineto", t2CVlineto},
+ 8: {-1, "rrcurveto", t2CRrcurveto},
+ 9: {}, // Reserved.
+ 10: {+1, "callsubr", t2CCallsubr},
+ 11: {+0, "return", t2CReturn},
+ 12: {}, // escape.
+ 13: {}, // Reserved.
+ 14: {-1, "endchar", t2CEndchar},
+ 15: {}, // Reserved.
+ 16: {}, // Reserved.
+ 17: {}, // Reserved.
+ 18: {-1, "hstemhm", t2CStem},
+ 19: {-1, "hintmask", t2CMask},
+ 20: {-1, "cntrmask", t2CMask},
+ 21: {-1, "rmoveto", t2CRmoveto},
+ 22: {-1, "hmoveto", t2CHmoveto},
+ 23: {-1, "vstemhm", t2CStem},
+ 24: {-1, "rcurveline", t2CRcurveline},
+ 25: {-1, "rlinecurve", t2CRlinecurve},
+ 26: {-1, "vvcurveto", t2CVvcurveto},
+ 27: {-1, "hhcurveto", t2CHhcurveto},
+ 28: {}, // shortint.
+ 29: {+1, "callgsubr", t2CCallgsubr},
+ 30: {-1, "vhcurveto", t2CVhcurveto},
+ 31: {-1, "hvcurveto", t2CHvcurveto},
+ }, {
+ // 2-byte operators. The first byte is the escape byte.
+ 34: {+7, "hflex", t2CHflex},
+ 36: {+9, "hflex1", t2CHflex1},
+ // TODO: more operators.
+ }},
+}
+
+// 5176.CFF.pdf section 4 "DICT Data" says that "Two-byte operators have an
+// initial escape byte of 12".
+const escapeByte = 12
+
+// t2CReadWidth reads the optional width adjustment. If present, it is on the
+// bottom of the arg stack. nArgs is the expected number of arguments on the
+// stack. A negative nArgs means a multiple of 2.
+//
+// 5177.Type2.pdf page 16 Note 4 says: "The first stack-clearing operator,
+// which must be one of hstem, hstemhm, vstem, vstemhm, cntrmask, hintmask,
+// hmoveto, vmoveto, rmoveto, or endchar, takes an additional argument — the
+// width... which may be expressed as zero or one numeric argument."
+func t2CReadWidth(p *psInterpreter, nArgs int32) {
+ if p.type2Charstrings.seenWidth {
+ return
+ }
+ p.type2Charstrings.seenWidth = true
+ if nArgs >= 0 {
+ if p.argStack.top != nArgs+1 {
+ return
+ }
+ } else if p.argStack.top&1 == 0 {
+ return
+ }
+ // When parsing a standalone CFF, we'd save the value of p.argStack.a[0]
+ // here as it defines the glyph's width (horizontal advance). Specifically,
+ // if present, it is a delta to the font-global nominalWidthX value found
+ // in the Private DICT. If absent, the glyph's width is the defaultWidthX
+ // value in that dict. See 5176.CFF.pdf section 15 "Private DICT Data".
+ //
+ // For a CFF embedded in an SFNT font (i.e. an OpenType font), glyph widths
+ // are already stored in the hmtx table, separate to the CFF table, and it
+ // is simpler to parse that table for all OpenType fonts (PostScript and
+ // TrueType). We therefore ignore the width value here, and just remove it
+ // from the bottom of the argStack.
+ copy(p.argStack.a[:p.argStack.top-1], p.argStack.a[1:p.argStack.top])
+ p.argStack.top--
+}
+
+func t2CStem(p *psInterpreter) error {
+ t2CReadWidth(p, -1)
+ if p.argStack.top%2 != 0 {
+ return errInvalidCFFTable
+ }
+ // We update the number of hintBits need to parse hintmask and cntrmask
+ // instructions, but this Type 2 Charstring implementation otherwise
+ // ignores the stem hints.
+ p.type2Charstrings.hintBits += p.argStack.top / 2
+ if p.type2Charstrings.hintBits > maxHintBits {
+ return errUnsupportedNumberOfHints
+ }
+ return nil
+}
+
+func t2CMask(p *psInterpreter) error {
+ // 5176.CFF.pdf section 4.3 "Hint Operators" says that "If hstem and vstem
+ // hints are both declared at the beginning of a charstring, and this
+ // sequence is followed directly by the hintmask or cntrmask operators, the
+ // vstem hint operator need not be included."
+ //
+ // What we implement here is more permissive (but the same as what the
+ // FreeType implementation does, and simpler than tracking the previous
+ // operator and other hinting state): if a hintmask is given any arguments
+ // (i.e. the argStack is non-empty), we run an implicit vstem operator.
+ //
+ // Note that the vstem operator consumes from p.argStack, but the hintmask
+ // or cntrmask operators consume from p.instructions.
+ if p.argStack.top != 0 {
+ if err := t2CStem(p); err != nil {
+ return err
+ }
+ } else if !p.type2Charstrings.seenWidth {
+ p.type2Charstrings.seenWidth = true
+ }
+
+ hintBytes := (p.type2Charstrings.hintBits + 7) / 8
+ if len(p.instructions) < int(hintBytes) {
+ return errInvalidCFFTable
+ }
+ p.instructions = p.instructions[hintBytes:]
+ return nil
+}
+
+func t2CHmoveto(p *psInterpreter) error {
+ t2CReadWidth(p, 1)
+ if p.argStack.top != 1 {
+ return errInvalidCFFTable
+ }
+ p.type2Charstrings.moveTo(p.argStack.a[0], 0)
+ return nil
+}
+
+func t2CVmoveto(p *psInterpreter) error {
+ t2CReadWidth(p, 1)
+ if p.argStack.top != 1 {
+ return errInvalidCFFTable
+ }
+ p.type2Charstrings.moveTo(0, p.argStack.a[0])
+ return nil
+}
+
+func t2CRmoveto(p *psInterpreter) error {
+ t2CReadWidth(p, 2)
+ if p.argStack.top != 2 {
+ return errInvalidCFFTable
+ }
+ p.type2Charstrings.moveTo(p.argStack.a[0], p.argStack.a[1])
+ return nil
+}
+
+func t2CHlineto(p *psInterpreter) error { return t2CLineto(p, false) }
+func t2CVlineto(p *psInterpreter) error { return t2CLineto(p, true) }
+
+func t2CLineto(p *psInterpreter, vertical bool) error {
+ if !p.type2Charstrings.seenWidth || p.argStack.top < 1 {
+ return errInvalidCFFTable
+ }
+ for i := int32(0); i < p.argStack.top; i, vertical = i+1, !vertical {
+ dx, dy := p.argStack.a[i], int32(0)
+ if vertical {
+ dx, dy = dy, dx
+ }
+ p.type2Charstrings.lineTo(dx, dy)
+ }
+ return nil
+}
+
+func t2CRlineto(p *psInterpreter) error {
+ if !p.type2Charstrings.seenWidth || p.argStack.top < 2 || p.argStack.top%2 != 0 {
+ return errInvalidCFFTable
+ }
+ for i := int32(0); i < p.argStack.top; i += 2 {
+ p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1])
+ }
+ return nil
+}
+
+// As per 5177.Type2.pdf section 4.1 "Path Construction Operators",
+//
+// rcurveline is:
+// - {dxa dya dxb dyb dxc dyc}+ dxd dyd
+//
+// rlinecurve is:
+// - {dxa dya}+ dxb dyb dxc dyc dxd dyd
+
+func t2CRcurveline(p *psInterpreter) error {
+ if !p.type2Charstrings.seenWidth || p.argStack.top < 8 || p.argStack.top%6 != 2 {
+ return errInvalidCFFTable
+ }
+ i := int32(0)
+ for iMax := p.argStack.top - 2; i < iMax; i += 6 {
+ p.type2Charstrings.cubeTo(
+ p.argStack.a[i+0],
+ p.argStack.a[i+1],
+ p.argStack.a[i+2],
+ p.argStack.a[i+3],
+ p.argStack.a[i+4],
+ p.argStack.a[i+5],
+ )
+ }
+ p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1])
+ return nil
+}
+
+func t2CRlinecurve(p *psInterpreter) error {
+ if !p.type2Charstrings.seenWidth || p.argStack.top < 8 || p.argStack.top%2 != 0 {
+ return errInvalidCFFTable
+ }
+ i := int32(0)
+ for iMax := p.argStack.top - 6; i < iMax; i += 2 {
+ p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1])
+ }
+ p.type2Charstrings.cubeTo(
+ p.argStack.a[i+0],
+ p.argStack.a[i+1],
+ p.argStack.a[i+2],
+ p.argStack.a[i+3],
+ p.argStack.a[i+4],
+ p.argStack.a[i+5],
+ )
+ return nil
+}
+
+// As per 5177.Type2.pdf section 4.1 "Path Construction Operators",
+//
+// hhcurveto is:
+// - dy1 {dxa dxb dyb dxc}+
+//
+// vvcurveto is:
+// - dx1 {dya dxb dyb dyc}+
+//
+// hvcurveto is one of:
+// - dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf?
+// - {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf?
+//
+// vhcurveto is one of:
+// - dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf?
+// - {dya dxb dyb dxc dxd dxe dye dyf}+ dxf?
+
+func t2CHhcurveto(p *psInterpreter) error { return t2CCurveto(p, false, false) }
+func t2CVvcurveto(p *psInterpreter) error { return t2CCurveto(p, false, true) }
+func t2CHvcurveto(p *psInterpreter) error { return t2CCurveto(p, true, false) }
+func t2CVhcurveto(p *psInterpreter) error { return t2CCurveto(p, true, true) }
+
+// t2CCurveto implements the hh / vv / hv / vh xxcurveto operators. N relative
+// cubic curve requires 6*N control points, but only 4*N+0 or 4*N+1 are used
+// here: all (or all but one) of the piecewise cubic curve's tangents are
+// implicitly horizontal or vertical.
+//
+// swap is whether that implicit horizontal / vertical constraint swaps as you
+// move along the piecewise cubic curve. If swap is false, the constraints are
+// either all horizontal or all vertical. If swap is true, it alternates.
+//
+// vertical is whether the first implicit constraint is vertical.
+func t2CCurveto(p *psInterpreter, swap, vertical bool) error {
+ if !p.type2Charstrings.seenWidth || p.argStack.top < 4 {
+ return errInvalidCFFTable
+ }
+
+ i := int32(0)
+ switch p.argStack.top & 3 {
+ case 0:
+ // No-op.
+ case 1:
+ if swap {
+ break
+ }
+ i = 1
+ if vertical {
+ p.type2Charstrings.x += p.argStack.a[0]
+ } else {
+ p.type2Charstrings.y += p.argStack.a[0]
+ }
+ default:
+ return errInvalidCFFTable
+ }
+
+ for i != p.argStack.top {
+ i = t2CCurveto4(p, swap, vertical, i)
+ if i < 0 {
+ return errInvalidCFFTable
+ }
+ if swap {
+ vertical = !vertical
+ }
+ }
+ return nil
+}
+
+func t2CCurveto4(p *psInterpreter, swap bool, vertical bool, i int32) (j int32) {
+ if i+4 > p.argStack.top {
+ return -1
+ }
+ dxa := p.argStack.a[i+0]
+ dya := int32(0)
+ dxb := p.argStack.a[i+1]
+ dyb := p.argStack.a[i+2]
+ dxc := p.argStack.a[i+3]
+ dyc := int32(0)
+ i += 4
+
+ if vertical {
+ dxa, dya = dya, dxa
+ }
+
+ if swap {
+ if i+1 == p.argStack.top {
+ dyc = p.argStack.a[i]
+ i++
+ }
+ }
+
+ if swap != vertical {
+ dxc, dyc = dyc, dxc
+ }
+
+ p.type2Charstrings.cubeTo(dxa, dya, dxb, dyb, dxc, dyc)
+ return i
+}
+
+func t2CRrcurveto(p *psInterpreter) error {
+ if !p.type2Charstrings.seenWidth || p.argStack.top < 6 || p.argStack.top%6 != 0 {
+ return errInvalidCFFTable
+ }
+ for i := int32(0); i != p.argStack.top; i += 6 {
+ p.type2Charstrings.cubeTo(
+ p.argStack.a[i+0],
+ p.argStack.a[i+1],
+ p.argStack.a[i+2],
+ p.argStack.a[i+3],
+ p.argStack.a[i+4],
+ p.argStack.a[i+5],
+ )
+ }
+ return nil
+}
+
+// For the flex operators, we ignore the flex depth and always produce cubic
+// segments, not linear segments. It's not obvious why the Type 2 Charstring
+// format cares about switching behavior based on a metric in pixels, not in
+// ideal font units. The Go vector rasterizer has no problems with almost
+// linear cubic segments.
+
+func t2CHflex(p *psInterpreter) error {
+ p.type2Charstrings.cubeTo(
+ p.argStack.a[0], 0,
+ p.argStack.a[1], +p.argStack.a[2],
+ p.argStack.a[3], 0,
+ )
+ p.type2Charstrings.cubeTo(
+ p.argStack.a[4], 0,
+ p.argStack.a[5], -p.argStack.a[2],
+ p.argStack.a[6], 0,
+ )
+ return nil
+}
+
+func t2CHflex1(p *psInterpreter) error {
+ dy1 := p.argStack.a[1]
+ dy2 := p.argStack.a[3]
+ dy5 := p.argStack.a[7]
+ dy6 := -dy1 - dy2 - dy5
+ p.type2Charstrings.cubeTo(
+ p.argStack.a[0], dy1,
+ p.argStack.a[2], dy2,
+ p.argStack.a[4], 0,
+ )
+ p.type2Charstrings.cubeTo(
+ p.argStack.a[5], 0,
+ p.argStack.a[6], dy5,
+ p.argStack.a[8], dy6,
+ )
+ return nil
+}
+
+// subrBias returns the subroutine index bias as per 5177.Type2.pdf section 4.7
+// "Subroutine Operators".
+func subrBias(numSubroutines int) int32 {
+ if numSubroutines < 1240 {
+ return 107
+ }
+ if numSubroutines < 33900 {
+ return 1131
+ }
+ return 32768
+}
+
+func t2CCallgsubr(p *psInterpreter) error {
+ return t2CCall(p, p.type2Charstrings.f.cached.glyphData.gsubrs)
+}
+
+func t2CCallsubr(p *psInterpreter) error {
+ t := &p.type2Charstrings
+ d := &t.f.cached.glyphData
+ subrs := d.singleSubrs
+ if d.multiSubrs != nil {
+ if t.fdSelectIndexPlusOne == 0 {
+ index, err := d.fdSelect.lookup(t.f, t.b, t.glyphIndex)
+ if err != nil {
+ return err
+ }
+ if index < 0 || len(d.multiSubrs) <= index {
+ return errInvalidCFFTable
+ }
+ t.fdSelectIndexPlusOne = int32(index + 1)
+ }
+ subrs = d.multiSubrs[t.fdSelectIndexPlusOne-1]
+ }
+ return t2CCall(p, subrs)
+}
+
+func t2CCall(p *psInterpreter, subrs []uint32) error {
+ if p.callStack.top == psCallStackSize || len(subrs) == 0 {
+ return errInvalidCFFTable
+ }
+ length := uint32(len(p.instructions))
+ p.callStack.a[p.callStack.top] = psCallStackEntry{
+ offset: p.instrOffset + p.instrLength - length,
+ length: length,
+ }
+ p.callStack.top++
+
+ subrIndex := p.argStack.a[p.argStack.top-1] + subrBias(len(subrs)-1)
+ if subrIndex < 0 || int32(len(subrs)-1) <= subrIndex {
+ return errInvalidCFFTable
+ }
+ i := subrs[subrIndex+0]
+ j := subrs[subrIndex+1]
+ if j < i {
+ return errInvalidCFFTable
+ }
+ if j-i > maxGlyphDataLength {
+ return errUnsupportedGlyphDataLength
+ }
+ buf, err := p.type2Charstrings.b.view(&p.type2Charstrings.f.src, int(i), int(j-i))
+ if err != nil {
+ return err
+ }
+
+ p.instructions = buf
+ p.instrOffset = i
+ p.instrLength = j - i
+ return nil
+}
+
+func t2CReturn(p *psInterpreter) error {
+ if p.callStack.top <= 0 {
+ return errInvalidCFFTable
+ }
+ p.callStack.top--
+ o := p.callStack.a[p.callStack.top].offset
+ n := p.callStack.a[p.callStack.top].length
+ buf, err := p.type2Charstrings.b.view(&p.type2Charstrings.f.src, int(o), int(n))
+ if err != nil {
+ return err
+ }
+
+ p.instructions = buf
+ p.instrOffset = o
+ p.instrLength = n
+ return nil
+}
+
+func t2CEndchar(p *psInterpreter) error {
+ t2CReadWidth(p, 0)
+ if p.argStack.top != 0 || p.hasMoreInstructions() {
+ if p.argStack.top == 4 {
+ // TODO: process the implicit "seac" command as per 5177.Type2.pdf
+ // Appendix C "Compatibility and Deprecated Operators".
+ return errUnsupportedType2Charstring
+ }
+ return errInvalidCFFTable
+ }
+ p.type2Charstrings.closePath()
+ p.type2Charstrings.ended = true
+ return nil
+}
diff --git a/mkvlib/parser/sfnt/sfnt.go b/mkvlib/parser/sfnt/sfnt.go
new file mode 100644
index 0000000..fcc2b66
--- /dev/null
+++ b/mkvlib/parser/sfnt/sfnt.go
@@ -0,0 +1,1964 @@
+package sfnt
+
+import (
+ "errors"
+ "image"
+ "io"
+
+ "golang.org/x/image/font"
+ "golang.org/x/image/math/fixed"
+ "golang.org/x/text/encoding/charmap"
+)
+
+// These constants are not part of the specifications, but are limitations used
+// by this implementation.
+const (
+ // This value is arbitrary, but defends against parsing malicious font
+ // files causing excessive memory allocations. For reference, Adobe's
+ // SourceHanSansSC-Regular.otf has 65535 glyphs and:
+ // - its format-4 cmap table has 1581 segments.
+ // - its format-12 cmap table has 16498 segments.
+ //
+ // TODO: eliminate this constraint? If the cmap table is very large, load
+ // some or all of it lazily (at the time Font.GlyphIndex is called) instead
+ // of all of it eagerly (at the time Font.initialize is called), while
+ // keeping an upper bound on the memory used? This will make the code in
+ // cmap.go more complicated, considering that all of the Font methods are
+ // safe to call concurrently, as long as each call has a different *Buffer.
+ maxCmapSegments = 20000
+
+ // TODO: similarly, load subroutine locations lazily. Adobe's
+ // SourceHanSansSC-Regular.otf has up to 30000 subroutines.
+ maxNumSubroutines = 40000
+
+ maxCompoundRecursionDepth = 8
+ maxCompoundStackSize = 64
+ maxGlyphDataLength = 64 * 1024
+ maxHintBits = 256
+ maxNumFontDicts = 256
+ maxNumFonts = 256
+ maxNumTables = 256
+ maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
+
+ // (maxTableOffset + maxTableLength) will not overflow an int32.
+ maxTableLength = 1 << 29
+ maxTableOffset = 1 << 29
+)
+
+var (
+ // ErrColoredGlyph indicates that the requested glyph is not a monochrome
+ // vector glyph, such as a colored (bitmap or vector) emoji glyph.
+ ErrColoredGlyph = errors.New("sfnt: colored glyph")
+ // ErrNotFound indicates that the requested value was not found.
+ ErrNotFound = errors.New("sfnt: not found")
+
+ errInvalidBounds = errors.New("sfnt: invalid bounds")
+ errInvalidCFFTable = errors.New("sfnt: invalid CFF table")
+ errInvalidCmapTable = errors.New("sfnt: invalid cmap table")
+ errInvalidDfont = errors.New("sfnt: invalid dfont")
+ errInvalidFont = errors.New("sfnt: invalid font")
+ errInvalidFontCollection = errors.New("sfnt: invalid font collection")
+ errInvalidGPOSTable = errors.New("sfnt: invalid GPOS table")
+ errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
+ errInvalidGlyphDataLength = errors.New("sfnt: invalid glyph data length")
+ errInvalidHeadTable = errors.New("sfnt: invalid head table")
+ errInvalidHheaTable = errors.New("sfnt: invalid hhea table")
+ errInvalidHmtxTable = errors.New("sfnt: invalid hmtx table")
+ errInvalidKernTable = errors.New("sfnt: invalid kern 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")
+ errInvalidOS2Table = errors.New("sfnt: invalid OS/2 table")
+ errInvalidPostTable = errors.New("sfnt: invalid post table")
+ errInvalidSingleFont = errors.New("sfnt: invalid single font (data is a font collection)")
+ errInvalidSourceData = errors.New("sfnt: invalid source data")
+ errInvalidTableOffset = errors.New("sfnt: invalid table offset")
+ errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order")
+ errInvalidUCS2String = errors.New("sfnt: invalid UCS-2 string")
+
+ errUnsupportedCFFFDSelectTable = errors.New("sfnt: unsupported CFF FDSelect table")
+ errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
+ errUnsupportedClassDefFormat = errors.New("sfnt: unsupported class definition format")
+ errUnsupportedCmapEncodings = errors.New("sfnt: unsupported cmap encodings")
+ errUnsupportedCollection = errors.New("sfnt: unsupported collection")
+ errUnsupportedCompoundGlyph = errors.New("sfnt: unsupported compound glyph")
+ errUnsupportedCoverageFormat = errors.New("sfnt: unsupported coverage format")
+ errUnsupportedExtensionPosFormat = errors.New("sfnt: unsupported extension positioning format")
+ errUnsupportedGPOSTable = errors.New("sfnt: unsupported GPOS table")
+ errUnsupportedGlyphDataLength = errors.New("sfnt: unsupported glyph data length")
+ errUnsupportedKernTable = errors.New("sfnt: unsupported kern table")
+ errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments")
+ errUnsupportedNumberOfFontDicts = errors.New("sfnt: unsupported number of font dicts")
+ errUnsupportedNumberOfFonts = errors.New("sfnt: unsupported number of fonts")
+ errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints")
+ errUnsupportedNumberOfSubroutines = errors.New("sfnt: unsupported number of subroutines")
+ errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
+ errUnsupportedPlatformEncoding = errors.New("sfnt: unsupported platform encoding")
+ errUnsupportedPostTable = errors.New("sfnt: unsupported post table")
+ errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
+ errUnsupportedTableOffsetLength = errors.New("sfnt: unsupported table offset or length")
+ errUnsupportedType2Charstring = errors.New("sfnt: unsupported Type 2 Charstring")
+)
+
+// GlyphIndex is a glyph index in a Font.
+type GlyphIndex uint16
+
+// NameID identifies a name table entry.
+//
+// See the "Name IDs" section of
+// https://www.microsoft.com/typography/otspec/name.htm
+type NameID uint16
+
+const (
+ NameIDCopyright NameID = 0
+ NameIDFamily = 1
+ NameIDSubfamily = 2
+ NameIDUniqueIdentifier = 3
+ NameIDFull = 4
+ NameIDVersion = 5
+ NameIDPostScript = 6
+ NameIDTrademark = 7
+ NameIDManufacturer = 8
+ NameIDDesigner = 9
+ NameIDDescription = 10
+ NameIDVendorURL = 11
+ NameIDDesignerURL = 12
+ NameIDLicense = 13
+ NameIDLicenseURL = 14
+ NameIDTypographicFamily = 16
+ NameIDTypographicSubfamily = 17
+ NameIDCompatibleFull = 18
+ NameIDSampleText = 19
+ NameIDPostScriptCID = 20
+ NameIDWWSFamily = 21
+ NameIDWWSSubfamily = 22
+ NameIDLightBackgroundPalette = 23
+ NameIDDarkBackgroundPalette = 24
+ NameIDVariationsPostScriptPrefix = 25
+)
+
+// Units are an integral number of abstract, scalable "font units". The em
+// square is typically 1000 or 2048 "font units". This would map to a certain
+// number (e.g. 30 pixels) of physical pixels, depending on things like the
+// display resolution (DPI) and font size (e.g. a 12 point font).
+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 {
+ _ = b[1] // Bounds check hint to compiler.
+ return uint16(b[0])<<8 | uint16(b[1])<<0
+}
+
+func u32(b []byte) uint32 {
+ _ = b[3] // Bounds check hint to compiler.
+ return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])<<0
+}
+
+// source is a source of byte data. Conceptually, it is like an io.ReaderAt,
+// except that a common source of SFNT font data is in-memory instead of
+// on-disk: a []byte containing the entire data, either as a global variable
+// (e.g. "goregular.TTF") or the result of an ioutil.ReadFile call. In such
+// cases, as an optimization, we skip the io.Reader / io.ReaderAt model of
+// copying from the source to a caller-supplied buffer, and instead provide
+// direct access to the underlying []byte data.
+type source struct {
+ b []byte
+ r io.ReaderAt
+
+ // TODO: add a caching layer, if we're using the io.ReaderAt? Note that
+ // this might make a source no longer safe to use concurrently.
+}
+
+// valid returns whether exactly one of s.b and s.r is nil.
+func (s *source) valid() bool {
+ return (s.b == nil) != (s.r == nil)
+}
+
+// viewBufferWritable returns whether the []byte returned by source.view can be
+// written to by the caller, including by passing it to the same method
+// (source.view) on other receivers (i.e. different sources).
+//
+// In other words, it returns whether the source's underlying data is an
+// io.ReaderAt, not a []byte.
+func (s *source) viewBufferWritable() bool {
+ return s.b == nil
+}
+
+// view returns the length bytes at the given offset. buf is an optional
+// scratch buffer to reduce allocations when calling view multiple times. A nil
+// buf is valid. The []byte returned may be a sub-slice of buf[:cap(buf)], or
+// it may be an unrelated slice. In any case, the caller should not modify the
+// contents of the returned []byte, other than passing that []byte back to this
+// method on the same source s.
+func (s *source) view(buf []byte, offset, length int) ([]byte, error) {
+ if 0 > offset || offset > offset+length {
+ return nil, errInvalidBounds
+ }
+
+ // Try reading from the []byte.
+ if s.b != nil {
+ if offset+length > len(s.b) {
+ return nil, errInvalidBounds
+ }
+ return s.b[offset : offset+length], nil
+ }
+
+ // Read from the io.ReaderAt.
+ if length <= cap(buf) {
+ buf = buf[:length]
+ } else {
+ // Round length up to the nearest KiB. The slack can lead to fewer
+ // allocations if the buffer is re-used for multiple source.view calls.
+ n := length
+ n += 1023
+ n &^= 1023
+ buf = make([]byte, length, n)
+ }
+ if n, err := s.r.ReadAt(buf, int64(offset)); n != length {
+ return nil, err
+ }
+ return buf, nil
+}
+
+// varLenView returns bytes from the given offset for sub-tables with varying
+// length. The length of bytes is determined by staticLength plus n*itemLength,
+// where n is read as uint16 from countOffset (relative to offset). buf is an
+// optional scratch buffer (see source.view())
+func (s *source) varLenView(buf []byte, offset, staticLength, countOffset, itemLength int) ([]byte, int, error) {
+ if 0 > offset || offset > offset+staticLength {
+ return nil, 0, errInvalidBounds
+ }
+ if 0 > countOffset || countOffset+1 >= staticLength {
+ return nil, 0, errInvalidBounds
+ }
+
+ // read static part which contains our count
+ buf, err := s.view(buf, offset, staticLength)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ count := int(u16(buf[countOffset:]))
+ buf, err = s.view(buf, offset, staticLength+count*itemLength)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ return buf, count, nil
+}
+
+// u16 returns the uint16 in the table t at the relative offset i.
+//
+// buf is an optional scratch buffer as per the source.view method.
+func (s *source) u16(buf []byte, t table, i int) (uint16, error) {
+ if i < 0 || uint(t.length) < uint(i+2) {
+ return 0, errInvalidBounds
+ }
+ buf, err := s.view(buf, int(t.offset)+i, 2)
+ if err != nil {
+ return 0, err
+ }
+ return u16(buf), nil
+}
+
+// u32 returns the uint32 in the table t at the relative offset i.
+//
+// buf is an optional scratch buffer as per the source.view method.
+func (s *source) u32(buf []byte, t table, i int) (uint32, error) {
+ if i < 0 || uint(t.length) < uint(i+4) {
+ return 0, errInvalidBounds
+ }
+ buf, err := s.view(buf, int(t.offset)+i, 4)
+ if err != nil {
+ return 0, err
+ }
+ return u32(buf), nil
+}
+
+// table is a section of the font data.
+type table struct {
+ offset, length uint32
+}
+
+// ParseCollection parses an SFNT font collection, such as TTC or OTC data,
+// from a []byte data source.
+//
+// If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it
+// will return a collection containing 1 font.
+//
+// The caller should not modify src while the Collection or its Fonts remain in
+// use. See the package documentation for details.
+func ParseCollection(src []byte) (*Collection, error) {
+ c := &Collection{src: source{b: src}}
+ if err := c.initialize(); err != nil {
+ return nil, err
+ }
+ return c, nil
+}
+
+// ParseCollectionReaderAt parses an SFNT collection, such as TTC or OTC data,
+// from an io.ReaderAt data source.
+//
+// If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it
+// will return a collection containing 1 font.
+//
+// The caller should not modify or close src while the Collection or its Fonts
+// remain in use. See the package documentation for details.
+func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) {
+ c := &Collection{src: source{r: src}}
+ if err := c.initialize(); err != nil {
+ return nil, err
+ }
+ return c, nil
+}
+
+// Collection is a collection of one or more fonts.
+//
+// All of the Collection methods are safe to call concurrently.
+type Collection struct {
+ src source
+ offsets []uint32
+ isDfont bool
+}
+
+// NumFonts returns the number of fonts in the collection.
+func (c *Collection) NumFonts() int { return len(c.offsets) }
+
+func (c *Collection) initialize() error {
+ // The https://www.microsoft.com/typography/otspec/otff.htm "Font
+ // Collections" section describes the TTC header.
+ //
+ // https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
+ // describes the dfont header.
+ //
+ // 16 is the maximum of sizeof(TTCHeader) and sizeof(DfontHeader).
+ buf, err := c.src.view(nil, 0, 16)
+ if err != nil {
+ return err
+ }
+ // These cases match the switch statement in Font.initializeTables.
+ switch u32(buf) {
+ default:
+ return errInvalidFontCollection
+ case dfontResourceDataOffset:
+ return c.parseDfont(buf, u32(buf[4:]), u32(buf[12:]))
+ case 0x00010000, 0x4f54544f, 0x74727565: // 0x10000, "OTTO", "true"
+ // Try parsing it as a single font instead of a collection.
+ c.offsets = []uint32{0}
+ case 0x74746366: // "ttcf".
+ numFonts := u32(buf[8:])
+ if numFonts == 0 || numFonts > maxNumFonts {
+ return errUnsupportedNumberOfFonts
+ }
+ buf, err = c.src.view(nil, 12, int(4*numFonts))
+ if err != nil {
+ return err
+ }
+ c.offsets = make([]uint32, numFonts)
+ for i := range c.offsets {
+ o := u32(buf[4*i:])
+ if o > maxTableOffset {
+ return errUnsupportedTableOffsetLength
+ }
+ c.offsets[i] = o
+ }
+ }
+ return nil
+}
+
+// dfontResourceDataOffset is the assumed value of a dfont file's resource data
+// offset.
+//
+// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
+// says that "A Mac OS resource file... [starts with an] offset from start of
+// file to start of resource data section... [usually] 0x0100". In theory,
+// 0x00000100 isn't always a magic number for identifying dfont files. In
+// practice, it seems to work.
+const dfontResourceDataOffset = 0x00000100
+
+// parseDfont parses a dfont resource map, as per
+// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
+//
+// That unofficial wiki page lists all of its fields as *signed* integers,
+// which looks unusual. The actual file format might use *unsigned* integers in
+// various places, but until we have either an official specification or an
+// actual dfont file where this matters, we'll use signed integers and treat
+// negative values as invalid.
+func (c *Collection) parseDfont(buf []byte, resourceMapOffset, resourceMapLength uint32) error {
+ if resourceMapOffset > maxTableOffset || resourceMapLength > maxTableLength {
+ return errUnsupportedTableOffsetLength
+ }
+
+ const headerSize = 28
+ if resourceMapLength < headerSize {
+ return errInvalidDfont
+ }
+ buf, err := c.src.view(buf, int(resourceMapOffset+24), 2)
+ if err != nil {
+ return err
+ }
+ typeListOffset := int(int16(u16(buf)))
+
+ if typeListOffset < headerSize || resourceMapLength < uint32(typeListOffset)+2 {
+ return errInvalidDfont
+ }
+ buf, err = c.src.view(buf, int(resourceMapOffset)+typeListOffset, 2)
+ if err != nil {
+ return err
+ }
+ typeCount := int(int16(u16(buf)))
+
+ const tSize = 8
+ if typeCount < 0 || tSize*uint32(typeCount) > resourceMapLength-uint32(typeListOffset)-2 {
+ return errInvalidDfont
+ }
+ buf, err = c.src.view(buf, int(resourceMapOffset)+typeListOffset+2, tSize*typeCount)
+ if err != nil {
+ return err
+ }
+ resourceCount, resourceListOffset := 0, 0
+ for i := 0; i < typeCount; i++ {
+ if u32(buf[tSize*i:]) != 0x73666e74 { // "sfnt".
+ continue
+ }
+
+ resourceCount = int(int16(u16(buf[tSize*i+4:])))
+ if resourceCount < 0 {
+ return errInvalidDfont
+ }
+ // https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
+ // says that the value in the wire format is "the number of
+ // resources of this type, minus one."
+ resourceCount++
+
+ resourceListOffset = int(int16(u16(buf[tSize*i+6:])))
+ if resourceListOffset < 0 {
+ return errInvalidDfont
+ }
+ break
+ }
+ if resourceCount == 0 {
+ return errInvalidDfont
+ }
+ if resourceCount > maxNumFonts {
+ return errUnsupportedNumberOfFonts
+ }
+
+ const rSize = 12
+ if o, n := uint32(typeListOffset+resourceListOffset), rSize*uint32(resourceCount); o > resourceMapLength || n > resourceMapLength-o {
+ return errInvalidDfont
+ } else {
+ buf, err = c.src.view(buf, int(resourceMapOffset+o), int(n))
+ if err != nil {
+ return err
+ }
+ }
+ c.offsets = make([]uint32, resourceCount)
+ for i := range c.offsets {
+ o := 0xffffff & u32(buf[rSize*i+4:])
+ // Offsets are relative to the resource data start, not the file start.
+ // A particular resource's data also starts with a 4-byte length, which
+ // we skip.
+ o += dfontResourceDataOffset + 4
+ if o > maxTableOffset {
+ return errUnsupportedTableOffsetLength
+ }
+ c.offsets[i] = o
+ }
+ c.isDfont = true
+ return nil
+}
+
+// Font returns the i'th font in the collection.
+func (c *Collection) Font(i int) (*Font, error) {
+ if i < 0 || len(c.offsets) <= i {
+ return nil, ErrNotFound
+ }
+ f := &Font{src: c.src}
+ if err := f.initialize(int(c.offsets[i]), c.isDfont); err != nil {
+ return nil, err
+ }
+ return f, nil
+}
+
+// Parse parses an SFNT font, such as TTF or OTF data, from a []byte data
+// source.
+//
+// The caller should not modify src while the Font remains in use. See the
+// package documentation for details.
+func Parse(src []byte) (*Font, error) {
+ f := &Font{src: source{b: src}}
+ if err := f.initialize(0, false); err != nil {
+ return nil, err
+ }
+ return f, nil
+}
+
+// ParseReaderAt parses an SFNT font, such as TTF or OTF data, from an
+// io.ReaderAt data source.
+//
+// The caller should not modify or close src while the Font remains in use. See
+// the package documentation for details.
+func ParseReaderAt(src io.ReaderAt) (*Font, error) {
+ f := &Font{src: source{r: src}}
+ if err := f.initialize(0, false); err != nil {
+ return nil, err
+ }
+ return f, nil
+}
+
+// Font is an SFNT font.
+//
+// Many of its methods take a *Buffer argument, as re-using buffers can reduce
+// the total memory allocation of repeated Font method calls, such as measuring
+// and rasterizing every unique glyph in a string of text. If efficiency is not
+// a concern, passing a nil *Buffer is valid, and implies using a temporary
+// buffer for a single call.
+//
+// It is valid to re-use a *Buffer with multiple Font method calls, even with
+// different *Font receivers, as long as they are not concurrent calls.
+//
+// All of the Font methods are safe to call concurrently, as long as each call
+// has a different *Buffer (or nil).
+//
+// The Font methods that don't take a *Buffer argument are always safe to call
+// concurrently.
+//
+// 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
+// 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).
+//
+// 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 {
+ src source
+
+ // initialOffset is the file offset of the start of the font. This may be
+ // non-zero for fonts within a font collection.
+ initialOffset int32
+
+ // https://www.microsoft.com/typography/otspec/otff.htm#otttables
+ // "Required Tables".
+ cmap table
+ head table
+ hhea table
+ hmtx table
+ maxp table
+ name table
+ os2 table
+ post table
+
+ // https://www.microsoft.com/typography/otspec/otff.htm#otttables
+ // "Tables Related to TrueType Outlines".
+ //
+ // This implementation does not support hinting, so it does not read the
+ // cvt, fpgm gasp or prep tables.
+ glyf table
+ loca table
+
+ // https://www.microsoft.com/typography/otspec/otff.htm#otttables
+ // "Tables Related to PostScript Outlines".
+ //
+ // TODO: cff2, vorg?
+ cff table
+
+ // https://www.microsoft.com/typography/otspec/otff.htm#otttables
+ // "Tables Related to Bitmap Glyphs".
+ //
+ // TODO: Others?
+ cblc table
+
+ // https://www.microsoft.com/typography/otspec/otff.htm#otttables
+ // "Advanced Typographic Tables".
+ //
+ // TODO: base, gdef, gsub, jstf, math?
+ gpos table
+
+ // https://www.microsoft.com/typography/otspec/otff.htm#otttables
+ // "Other OpenType Tables".
+ //
+ // TODO: hdmx, vmtx? Others?
+ kern table
+
+ cached struct {
+ ascent int32
+ capHeight int32
+ finalTableOffset int32
+ glyphData glyphData
+ glyphIndex glyphIndexFunc
+ bounds [4]int16
+ descent int32
+ indexToLocFormat bool // false means short, true means long.
+ isColorBitmap bool
+ isPostScript bool
+ kernNumPairs int32
+ kernOffset int32
+ kernFuncs []kernFunc
+ lineGap int32
+ numHMetrics int32
+ post *PostTable
+ slope [2]int32
+ unitsPerEm Units
+ xHeight int32
+ }
+}
+
+// NumGlyphs returns the number of glyphs in f.
+func (f *Font) NumGlyphs() int { return len(f.cached.glyphData.locations) - 1 }
+
+// UnitsPerEm returns the number of units per em for f.
+func (f *Font) UnitsPerEm() Units { return f.cached.unitsPerEm }
+
+func (f *Font) initialize(offset int, isDfont bool) error {
+ if !f.src.valid() {
+ return errInvalidSourceData
+ }
+ buf, finalTableOffset, isPostScript, err := f.initializeTables(offset, isDfont)
+ if err != nil {
+ return err
+ }
+
+ // The order of these parseXxx calls matters. Later calls may depend on
+ // information parsed by earlier calls, such as the maxp table's numGlyphs.
+ // To enforce these dependencies, such information is passed and returned
+ // explicitly, and the f.cached fields are only set afterwards.
+ //
+ // When implementing new parseXxx methods, take care not to call methods
+ // such as Font.NumGlyphs that implicitly depend on f.cached fields.
+
+ buf, bounds, indexToLocFormat, unitsPerEm, err := f.parseHead(buf)
+ if err != nil {
+ return err
+ }
+ buf, numGlyphs, err := f.parseMaxp(buf, isPostScript)
+ if err != nil {
+ return err
+ }
+ buf, glyphData, isColorBitmap, err := f.parseGlyphData(buf, numGlyphs, indexToLocFormat, isPostScript)
+ if err != nil {
+ return err
+ }
+ buf, glyphIndex, err := f.parseCmap(buf)
+ if err != nil {
+ return err
+ }
+ buf, kernNumPairs, kernOffset, err := f.parseKern(buf)
+ if err != nil {
+ return err
+ }
+ buf, kernFuncs, err := f.parseGPOSKern(buf)
+ if err != nil {
+ return err
+ }
+ buf, ascent, descent, lineGap, run, rise, numHMetrics, err := f.parseHhea(buf, numGlyphs)
+ if err != nil {
+ return err
+ }
+ buf, err = f.parseHmtx(buf, numGlyphs, numHMetrics)
+ if err != nil {
+ return err
+ }
+ buf, hasXHeightCapHeight, xHeight, capHeight, err := f.parseOS2(buf)
+ if err != nil {
+ return err
+ }
+ buf, post, err := f.parsePost(buf, numGlyphs)
+ if err != nil {
+ return err
+ }
+
+ f.cached.ascent = ascent
+ f.cached.capHeight = capHeight
+ f.cached.finalTableOffset = finalTableOffset
+ f.cached.glyphData = glyphData
+ f.cached.glyphIndex = glyphIndex
+ f.cached.bounds = bounds
+ f.cached.descent = descent
+ f.cached.indexToLocFormat = indexToLocFormat
+ f.cached.isColorBitmap = isColorBitmap
+ f.cached.isPostScript = isPostScript
+ f.cached.kernNumPairs = kernNumPairs
+ f.cached.kernOffset = kernOffset
+ f.cached.kernFuncs = kernFuncs
+ f.cached.lineGap = lineGap
+ f.cached.numHMetrics = numHMetrics
+ f.cached.post = post
+ f.cached.slope = [2]int32{run, rise}
+ f.cached.unitsPerEm = unitsPerEm
+ f.cached.xHeight = xHeight
+
+ if !hasXHeightCapHeight {
+ xh, ch, err := f.initOS2Version1()
+ if err != nil {
+ return err
+ }
+ f.cached.xHeight = xh
+ f.cached.capHeight = ch
+ }
+
+ return nil
+}
+
+func (f *Font) initializeTables(offset int, isDfont bool) (buf1 []byte, finalTableOffset int32, isPostScript bool, err error) {
+ f.initialOffset = int32(offset)
+ if int(f.initialOffset) != offset {
+ return nil, 0, false, errUnsupportedTableOffsetLength
+ }
+ // https://www.microsoft.com/typography/otspec/otff.htm "Organization of an
+ // OpenType Font" says that "The OpenType font starts with the Offset
+ // Table", which is 12 bytes.
+ buf, err := f.src.view(nil, offset, 12)
+ if err != nil {
+ return nil, 0, false, err
+ }
+ // When updating the cases in this switch statement, also update the
+ // Collection.initialize method.
+ switch u32(buf) {
+ default:
+ return nil, 0, false, errInvalidFont
+ case dfontResourceDataOffset:
+ return nil, 0, false, errInvalidSingleFont
+ case 0x00010000:
+ // No-op.
+ case 0x4f54544f: // "OTTO".
+ isPostScript = true
+ case 0x74727565: // "true"
+ // No-op.
+ case 0x74746366: // "ttcf".
+ return nil, 0, false, errInvalidSingleFont
+ }
+ numTables := int(u16(buf[4:]))
+ if numTables > maxNumTables {
+ return nil, 0, false, errUnsupportedNumberOfTables
+ }
+
+ // "The Offset Table is followed immediately by the Table Record entries...
+ // sorted in ascending order by tag", 16 bytes each.
+ buf, err = f.src.view(buf, offset+12, 16*numTables)
+ if err != nil {
+ return nil, 0, false, err
+ }
+ for b, first, prevTag := buf, true, uint32(0); len(b) > 0; b = b[16:] {
+ tag := u32(b)
+ if first {
+ first = false
+ } else if tag <= prevTag {
+ return nil, 0, false, errInvalidTableTagOrder
+ }
+ prevTag = tag
+
+ o, n := u32(b[8:12]), u32(b[12:16])
+ // For dfont files, the offset is relative to the resource, not the
+ // file.
+ if isDfont {
+ origO := o
+ o += uint32(offset)
+ if o < origO {
+ return nil, 0, false, errUnsupportedTableOffsetLength
+ }
+ }
+ if o > maxTableOffset || n > maxTableLength {
+ return nil, 0, false, errUnsupportedTableOffsetLength
+ }
+ // We ignore the checksums, but "all tables must begin on four byte
+ // boundries [sic]".
+ if o&3 != 0 {
+ //return nil, 0, false, errInvalidTableOffset
+ }
+ if finalTableOffset < int32(o+n) {
+ finalTableOffset = int32(o + n)
+ }
+
+ // Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32.
+ switch tag {
+ case 0x43424c43:
+ f.cblc = table{o, n}
+ case 0x43464620:
+ f.cff = table{o, n}
+ case 0x4f532f32:
+ f.os2 = table{o, n}
+ case 0x636d6170:
+ f.cmap = table{o, n}
+ case 0x676c7966:
+ f.glyf = table{o, n}
+ case 0x47504f53:
+ f.gpos = table{o, n}
+ case 0x68656164:
+ f.head = table{o, n}
+ case 0x68686561:
+ f.hhea = table{o, n}
+ case 0x686d7478:
+ f.hmtx = table{o, n}
+ case 0x6b65726e:
+ f.kern = table{o, n}
+ case 0x6c6f6361:
+ f.loca = table{o, n}
+ case 0x6d617870:
+ f.maxp = table{o, n}
+ case 0x6e616d65:
+ f.name = table{o, n}
+ case 0x706f7374:
+ f.post = table{o, n}
+ }
+ }
+
+ if (f.src.b != nil) && (int(finalTableOffset) > len(f.src.b)) {
+ return nil, 0, false, errInvalidSourceData
+ }
+ return buf, finalTableOffset, isPostScript, nil
+}
+
+func (f *Font) parseCmap(buf []byte) (buf1 []byte, glyphIndex glyphIndexFunc, err error) {
+ // https://www.microsoft.com/typography/OTSPEC/cmap.htm
+
+ const headerSize, entrySize = 4, 8
+ if f.cmap.length < headerSize {
+ return nil, nil, errInvalidCmapTable
+ }
+ u, err := f.src.u16(buf, f.cmap, 2)
+ if err != nil {
+ return nil, nil, err
+ }
+ numSubtables := int(u)
+ if f.cmap.length < headerSize+entrySize*uint32(numSubtables) {
+ return nil, nil, errInvalidCmapTable
+ }
+
+ var (
+ bestWidth int
+ bestOffset uint32
+ bestLength uint32
+ bestFormat uint16
+ )
+
+ // Scan all of the subtables, picking the widest supported one. See the
+ // platformEncodingWidth comment for more discussion of width.
+ for i := 0; i < numSubtables; i++ {
+ buf, err = f.src.view(buf, int(f.cmap.offset)+headerSize+entrySize*i, entrySize)
+ if err != nil {
+ return nil, nil, err
+ }
+ pid := u16(buf)
+ psid := u16(buf[2:])
+ width := platformEncodingWidth(pid, psid)
+ if width <= bestWidth {
+ continue
+ }
+ offset := u32(buf[4:])
+
+ if offset > f.cmap.length-4 {
+ return nil, nil, errInvalidCmapTable
+ }
+ buf, err = f.src.view(buf, int(f.cmap.offset+offset), 4)
+ if err != nil {
+ return nil, nil, err
+ }
+ format := u16(buf)
+ if !supportedCmapFormat(format, pid, psid) {
+ continue
+ }
+ length := uint32(u16(buf[2:]))
+
+ bestWidth = width
+ bestOffset = offset
+ bestLength = length
+ bestFormat = format
+ }
+
+ if bestWidth == 0 {
+ return nil, nil, errUnsupportedCmapEncodings
+ }
+ return f.makeCachedGlyphIndex(buf, bestOffset, bestLength, bestFormat)
+}
+
+func (f *Font) parseHead(buf []byte) (buf1 []byte, bounds [4]int16, indexToLocFormat bool, unitsPerEm Units, err error) {
+ // https://www.microsoft.com/typography/otspec/head.htm
+
+ if f.head.length != 54 {
+ return nil, [4]int16{}, false, 0, errInvalidHeadTable
+ }
+
+ u, err := f.src.u16(buf, f.head, 18)
+ if err != nil {
+ return nil, [4]int16{}, false, 0, err
+ }
+ if u == 0 {
+ return nil, [4]int16{}, false, 0, errInvalidHeadTable
+ }
+ unitsPerEm = Units(u)
+
+ for i := range bounds {
+ u, err := f.src.u16(buf, f.head, 36+2*i)
+ if err != nil {
+ return nil, [4]int16{}, false, 0, err
+ }
+ bounds[i] = int16(u)
+ }
+
+ u, err = f.src.u16(buf, f.head, 50)
+ if err != nil {
+ return nil, [4]int16{}, false, 0, err
+ }
+ indexToLocFormat = u != 0
+ return buf, bounds, indexToLocFormat, unitsPerEm, nil
+}
+
+func (f *Font) parseHhea(buf []byte, numGlyphs int32) (buf1 []byte, ascent, descent, lineGap, run, rise, numHMetrics int32, err error) {
+ // https://www.microsoft.com/typography/OTSPEC/hhea.htm
+
+ if f.hhea.length != 36 {
+ return nil, 0, 0, 0, 0, 0, 0, errInvalidHheaTable
+ }
+ u, err := f.src.u16(buf, f.hhea, 34)
+ if err != nil {
+ return nil, 0, 0, 0, 0, 0, 0, err
+ }
+ if int32(u) > numGlyphs || u == 0 {
+ return nil, 0, 0, 0, 0, 0, 0, errInvalidHheaTable
+ }
+ a, err := f.src.u16(buf, f.hhea, 4)
+ if err != nil {
+ return nil, 0, 0, 0, 0, 0, 0, err
+ }
+ d, err := f.src.u16(buf, f.hhea, 6)
+ if err != nil {
+ return nil, 0, 0, 0, 0, 0, 0, err
+ }
+ l, err := f.src.u16(buf, f.hhea, 8)
+ if err != nil {
+ return nil, 0, 0, 0, 0, 0, 0, err
+ }
+ ru, err := f.src.u16(buf, f.hhea, 20)
+ if err != nil {
+ return nil, 0, 0, 0, 0, 0, 0, err
+ }
+ ri, err := f.src.u16(buf, f.hhea, 18)
+ if err != nil {
+ return nil, 0, 0, 0, 0, 0, 0, err
+ }
+ return buf, int32(int16(a)), int32(int16(d)), int32(int16(l)), int32(int16(ru)), int32(int16(ri)), int32(u), nil
+}
+
+func (f *Font) parseHmtx(buf []byte, numGlyphs, numHMetrics int32) (buf1 []byte, err error) {
+ // https://www.microsoft.com/typography/OTSPEC/hmtx.htm
+
+ // The spec says that the hmtx table's length should be
+ // "4*numHMetrics+2*(numGlyphs-numHMetrics)". However, some fonts seen in the
+ // wild omit the "2*(nG-nHM)". See https://github.com/golang/go/issues/28379
+ if f.hmtx.length != uint32(4*numHMetrics) && f.hmtx.length != uint32(4*numHMetrics+2*(numGlyphs-numHMetrics)) {
+ return nil, errInvalidHmtxTable
+ }
+ return buf, nil
+}
+
+func (f *Font) parseKern(buf []byte) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
+ // https://www.microsoft.com/typography/otspec/kern.htm
+
+ if f.kern.length == 0 {
+ return buf, 0, 0, nil
+ }
+ const headerSize = 4
+ if f.kern.length < headerSize {
+ return nil, 0, 0, errInvalidKernTable
+ }
+ buf, err = f.src.view(buf, int(f.kern.offset), headerSize)
+ if err != nil {
+ return nil, 0, 0, err
+ }
+ offset := int(f.kern.offset) + headerSize
+ length := int(f.kern.length) - headerSize
+
+ switch version := u16(buf); version {
+ case 0:
+ if numTables := int(u16(buf[2:])); numTables == 0 {
+ return buf, 0, 0, nil
+ } else if numTables > 1 {
+ // TODO: support multiple subtables. For now, fall through and use
+ // only the first one.
+ }
+ return f.parseKernVersion0(buf, offset, length)
+ case 1:
+ if buf[2] != 0 || buf[3] != 0 {
+ return nil, 0, 0, errUnsupportedKernTable
+ }
+ // Microsoft's https://www.microsoft.com/typography/otspec/kern.htm
+ // says that "Apple has extended the definition of the 'kern' table to
+ // provide additional functionality. The Apple extensions are not
+ // supported on Windows."
+ //
+ // The format is relatively complicated, including encoding a state
+ // machine, but rarely seen. We follow Microsoft's and FreeType's
+ // behavior and simply ignore it. Theoretically, we could follow
+ // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html
+ // but it doesn't seem worth the effort.
+ return buf, 0, 0, nil
+ }
+ return nil, 0, 0, errUnsupportedKernTable
+}
+
+func (f *Font) parseKernVersion0(buf []byte, offset, length int) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
+ const headerSize = 6
+ if length < headerSize {
+ return nil, 0, 0, errInvalidKernTable
+ }
+ buf, err = f.src.view(buf, offset, headerSize)
+ if err != nil {
+ return nil, 0, 0, err
+ }
+ if version := u16(buf); version != 0 {
+ return nil, 0, 0, errUnsupportedKernTable
+ }
+ subtableLengthU16 := u16(buf[2:])
+ if int(subtableLengthU16) < headerSize || length < int(subtableLengthU16) {
+ return nil, 0, 0, errInvalidKernTable
+ }
+ if coverageBits := buf[5]; coverageBits != 0x01 {
+ // We only support horizontal kerning.
+ return nil, 0, 0, errUnsupportedKernTable
+ }
+ offset += headerSize
+ length -= headerSize
+ subtableLengthU16 -= headerSize
+
+ switch format := buf[4]; format {
+ case 0:
+ return f.parseKernFormat0(buf, offset, length, subtableLengthU16)
+ case 2:
+ // If we could find such a font, we could write code to support it, but
+ // a comment in the equivalent FreeType code (sfnt/ttkern.c) says that
+ // they've never seen such a font.
+ }
+ return nil, 0, 0, errUnsupportedKernTable
+}
+
+func (f *Font) parseKernFormat0(buf []byte, offset, length int, subtableLengthU16 uint16) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
+ const headerSize, entrySize = 8, 6
+ if length < headerSize {
+ return nil, 0, 0, errInvalidKernTable
+ }
+ buf, err = f.src.view(buf, offset, headerSize)
+ if err != nil {
+ return nil, 0, 0, err
+ }
+ kernNumPairs = int32(u16(buf))
+
+ // The subtable length from the kern table is only uint16. Fonts like
+ // Cambria, Calibri or Corbel have more then 10k kerning pairs and the
+ // actual subtable size is truncated to uint16. Compare size with KERN
+ // length and truncated size with subtable length.
+ n := headerSize + entrySize*int(kernNumPairs)
+ if (length < n) || (subtableLengthU16 != uint16(n)) {
+ return nil, 0, 0, errInvalidKernTable
+ }
+ return buf, kernNumPairs, int32(offset) + headerSize, nil
+}
+
+func (f *Font) parseMaxp(buf []byte, isPostScript bool) (buf1 []byte, numGlyphs int32, err error) {
+ // https://www.microsoft.com/typography/otspec/maxp.htm
+
+ if isPostScript {
+ if f.maxp.length != 6 {
+ return nil, 0, errInvalidMaxpTable
+ }
+ } else {
+ if f.maxp.length != 32 {
+ return nil, 0, errInvalidMaxpTable
+ }
+ }
+ u, err := f.src.u16(buf, f.maxp, 4)
+ if err != nil {
+ return nil, 0, err
+ }
+ return buf, int32(u), nil
+}
+
+type glyphData struct {
+ // The glyph data for the i'th glyph index is in
+ // src[locations[i+0]:locations[i+1]].
+ //
+ // The slice length equals 1 plus the number of glyphs.
+ locations []uint32
+
+ // For PostScript fonts, the bytecode for the i'th global or local
+ // subroutine is in src[x[i+0]:x[i+1]].
+ //
+ // The []uint32 slice length equals 1 plus the number of subroutines
+ gsubrs []uint32
+ singleSubrs []uint32
+ multiSubrs [][]uint32
+
+ fdSelect fdSelect
+}
+
+func (f *Font) parseGlyphData(buf []byte, numGlyphs int32, indexToLocFormat, isPostScript bool) (buf1 []byte, ret glyphData, isColorBitmap bool, err error) {
+ if isPostScript {
+ p := cffParser{
+ src: &f.src,
+ base: int(f.cff.offset),
+ offset: int(f.cff.offset),
+ end: int(f.cff.offset + f.cff.length),
+ }
+ ret, err = p.parse(numGlyphs)
+ if err != nil {
+ return nil, glyphData{}, false, err
+ }
+ } else if f.loca.length != 0 {
+ ret.locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs)
+ if err != nil {
+ return nil, glyphData{}, false, err
+ }
+ } else if f.cblc.length != 0 {
+ isColorBitmap = true
+ // TODO: parse the CBLC (and CBDT) tables. For now, we return a font
+ // with empty glyphs.
+ ret.locations = make([]uint32, numGlyphs+1)
+ }
+
+ if len(ret.locations) != int(numGlyphs+1) {
+ return nil, glyphData{}, false, errInvalidLocationData
+ }
+
+ return buf, ret, isColorBitmap, nil
+}
+
+func (f *Font) glyphTopOS2(b *Buffer, ppem fixed.Int26_6, r rune) (int32, error) {
+ ind, err := f.GlyphIndex(b, r)
+ if err != nil && err != ErrNotFound {
+ return 0, err
+ } else if ind == 0 {
+ return 0, nil
+ }
+ // Y axis points down
+ var min fixed.Int26_6
+ seg, err := f.LoadGlyph(b, ind, ppem, nil)
+ if err != nil {
+ return 0, err
+ }
+ for _, s := range seg {
+ for _, p := range s.Args {
+ if p.Y < min {
+ min = p.Y
+ }
+ }
+ }
+ return int32(min), nil
+}
+
+func (f *Font) initOS2Version1() (xHeight, capHeight int32, err error) {
+ ppem := fixed.Int26_6(f.UnitsPerEm())
+ var b Buffer
+
+ // sxHeight equal to the top of the unscaled and unhinted glyph bounding box
+ // of the glyph encoded at U+0078 (LATIN SMALL LETTER X).
+ xh, err := f.glyphTopOS2(&b, ppem, 'x')
+ if err != nil {
+ return 0, 0, err
+ }
+
+ // sCapHeight may be set equal to the top of the unscaled and unhinted glyph
+ // bounding box of the glyph encoded at U+0048 (LATIN CAPITAL LETTER H).
+ ch, err := f.glyphTopOS2(&b, ppem, 'H')
+ if err != nil {
+ return 0, 0, err
+ }
+
+ return int32(xh), int32(ch), nil
+}
+
+func (f *Font) parseOS2(buf []byte) (buf1 []byte, hasXHeightCapHeight bool, xHeight, capHeight int32, err error) {
+ // https://docs.microsoft.com/da-dk/typography/opentype/spec/os2
+
+ if f.os2.length == 0 {
+ // Apple TrueType fonts might omit the OS/2 table.
+ return buf, false, 0, 0, nil
+ } else if f.os2.length < 2 {
+ return nil, false, 0, 0, errInvalidOS2Table
+ }
+ vers, err := f.src.u16(buf, f.os2, 0)
+ if err != nil {
+ return nil, false, 0, 0, err
+ }
+ if vers <= 1 {
+ const headerSize = 86
+ if f.os2.length < headerSize {
+ return nil, false, 0, 0, errInvalidOS2Table
+ }
+ // Will resolve xHeight and capHeight later, see initOS2Version1.
+ return buf, false, 0, 0, nil
+ }
+ const headerSize = 96
+ if f.os2.length < headerSize {
+ return nil, false, 0, 0, errInvalidOS2Table
+ }
+ xh, err := f.src.u16(buf, f.os2, 86)
+ if err != nil {
+ return nil, false, 0, 0, err
+ }
+ ch, err := f.src.u16(buf, f.os2, 88)
+ if err != nil {
+ return nil, false, 0, 0, err
+ }
+ return buf, true, int32(int16(xh)), int32(int16(ch)), nil
+}
+
+// PostTable represents an information stored in the PostScript font section.
+type PostTable struct {
+ // Version of the version tag of the "post" table.
+ Version uint32
+ // ItalicAngle in counter-clockwise degrees from the vertical. Zero for
+ // upright text, negative for text that leans to the right (forward).
+ ItalicAngle float64
+ // UnderlinePosition is the suggested distance of the top of the
+ // underline from the baseline (negative values indicate below baseline).
+ UnderlinePosition int16
+ // Suggested values for the underline thickness.
+ UnderlineThickness int16
+ // IsFixedPitch indicates that the font is not proportionally spaced
+ // (i.e. monospaced).
+ IsFixedPitch bool
+}
+
+// PostTable returns the information from the font's "post" table. It can
+// return nil, if the font doesn't have such a table.
+//
+// See https://docs.microsoft.com/en-us/typography/opentype/spec/post
+func (f *Font) PostTable() *PostTable {
+ return f.cached.post
+}
+
+func (f *Font) parsePost(buf []byte, numGlyphs int32) (buf1 []byte, post *PostTable, err error) {
+ // https://www.microsoft.com/typography/otspec/post.htm
+
+ const headerSize = 32
+ if f.post.length < headerSize {
+ return nil, nil, errInvalidPostTable
+ }
+ u, err := f.src.u32(buf, f.post, 0)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ switch u {
+ case 0x10000:
+ // No-op.
+ case 0x20000:
+ if f.post.length < headerSize+2+2*uint32(numGlyphs) {
+ return nil, nil, errInvalidPostTable
+ }
+ case 0x30000:
+ // No-op.
+ default:
+ return nil, nil, errUnsupportedPostTable
+ }
+
+ ang, err := f.src.u32(buf, f.post, 4)
+ if err != nil {
+ return nil, nil, err
+ }
+ up, err := f.src.u16(buf, f.post, 8)
+ if err != nil {
+ return nil, nil, err
+ }
+ ut, err := f.src.u16(buf, f.post, 10)
+ if err != nil {
+ return nil, nil, err
+ }
+ fp, err := f.src.u32(buf, f.post, 12)
+ if err != nil {
+ return nil, nil, err
+ }
+ post = &PostTable{
+ Version: u,
+ ItalicAngle: float64(int32(ang)) / 0x10000,
+ UnderlinePosition: int16(up),
+ UnderlineThickness: int16(ut),
+ IsFixedPitch: fp != 0,
+ }
+ return buf, post, nil
+}
+
+// Bounds returns the union of a Font's glyphs' bounds.
+//
+// In the returned Rectangle26_6's (x, y) coordinates, the Y axis increases
+// down.
+func (f *Font) Bounds(b *Buffer, ppem fixed.Int26_6, h font.Hinting) (fixed.Rectangle26_6, error) {
+ // The 0, 3, 2, 1 indices are to flip the Y coordinates. OpenType's Y axis
+ // increases up. Go's standard graphics libraries' Y axis increases down.
+ r := fixed.Rectangle26_6{
+ Min: fixed.Point26_6{
+ X: +scale(fixed.Int26_6(f.cached.bounds[0])*ppem, f.cached.unitsPerEm),
+ Y: -scale(fixed.Int26_6(f.cached.bounds[3])*ppem, f.cached.unitsPerEm),
+ },
+ Max: fixed.Point26_6{
+ X: +scale(fixed.Int26_6(f.cached.bounds[2])*ppem, f.cached.unitsPerEm),
+ Y: -scale(fixed.Int26_6(f.cached.bounds[1])*ppem, f.cached.unitsPerEm),
+ },
+ }
+ if h == font.HintingFull {
+ // Quantize the Min down and Max up to a whole pixel.
+ r.Min.X = (r.Min.X + 0) &^ 63
+ r.Min.Y = (r.Min.Y + 0) &^ 63
+ r.Max.X = (r.Max.X + 63) &^ 63
+ r.Max.Y = (r.Max.Y + 63) &^ 63
+ }
+ return r, nil
+}
+
+// TODO: API for looking up glyph variants?? For example, some fonts may
+// provide both slashed and dotted zero glyphs ('0'), or regular and 'old
+// style' numerals, and users can direct software to choose a variant.
+
+type glyphIndexFunc func(f *Font, b *Buffer, r rune) (GlyphIndex, error)
+
+// GlyphIndex returns the glyph index for the given rune.
+//
+// It returns (0, nil) if there is no glyph for r.
+// https://www.microsoft.com/typography/OTSPEC/cmap.htm says that "Character
+// codes that do not correspond to any glyph in the font should be mapped to
+// glyph index 0. The glyph at this location must be a special glyph
+// representing a missing character, commonly known as .notdef."
+func (f *Font) GlyphIndex(b *Buffer, r rune) (GlyphIndex, error) {
+ return f.cached.glyphIndex(f, b, r)
+}
+
+func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) (buf []byte, offset, length uint32, err error) {
+ xx := int(x)
+ if f.NumGlyphs() <= xx {
+ return nil, 0, 0, ErrNotFound
+ }
+ i := f.cached.glyphData.locations[xx+0]
+ j := f.cached.glyphData.locations[xx+1]
+ if j < i {
+ return nil, 0, 0, errInvalidGlyphDataLength
+ }
+ if j-i > maxGlyphDataLength {
+ return nil, 0, 0, errUnsupportedGlyphDataLength
+ }
+ buf, err = b.view(&f.src, int(i), int(j-i))
+ return buf, i, j - i, err
+}
+
+// LoadGlyphOptions are the options to the Font.LoadGlyph method.
+type LoadGlyphOptions struct {
+ // TODO: transform / hinting.
+}
+
+// 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.
+//
+// In the returned Segments' (x, y) coordinates, the Y axis increases down.
+//
+// It returns ErrNotFound if the glyph index is out of range. It returns
+// ErrColoredGlyph if the glyph is not a monochrome vector glyph, such as a
+// colored (bitmap or vector) emoji glyph.
+func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *LoadGlyphOptions) (Segments, error) {
+ if b == nil {
+ b = &Buffer{}
+ }
+
+ b.segments = b.segments[:0]
+ if f.cached.isColorBitmap {
+ return nil, ErrColoredGlyph
+ }
+ if f.cached.isPostScript {
+ buf, offset, length, err := f.viewGlyphData(b, x)
+ if err != nil {
+ return nil, err
+ }
+ b.psi.type2Charstrings.initialize(f, b, x)
+ if err := b.psi.run(psContextType2Charstring, buf, offset, length); err != nil {
+ return nil, err
+ }
+ if !b.psi.type2Charstrings.ended {
+ return nil, errInvalidCFFTable
+ }
+ } else if err := loadGlyf(f, b, x, 0, 0); err != nil {
+ return nil, err
+ }
+
+ // 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.
+ //
+ // We also flip the Y coordinates. OpenType's Y axis increases up. Go's
+ // standard graphics libraries' Y axis increases down.
+ for i := range b.segments {
+ a := &b.segments[i].Args
+ for j := range a {
+ a[j].X = +scale(a[j].X*ppem, f.cached.unitsPerEm)
+ a[j].Y = -scale(a[j].Y*ppem, f.cached.unitsPerEm)
+ }
+ }
+
+ // TODO: look at opts to transform / hint the Buffer.segments.
+
+ return b.segments, nil
+}
+
+func (f *Font) glyphNameFormat10(x GlyphIndex) (string, error) {
+ if x >= numBuiltInPostNames {
+ return "", ErrNotFound
+ }
+ // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
+ i := builtInPostNamesOffsets[x+0]
+ j := builtInPostNamesOffsets[x+1]
+ return builtInPostNamesData[i:j], nil
+}
+
+func (f *Font) glyphNameFormat20(b *Buffer, x GlyphIndex) (string, error) {
+ if b == nil {
+ b = &Buffer{}
+ }
+ // The wire format for a Version 2 post table is documented at:
+ // https://www.microsoft.com/typography/otspec/post.htm
+ const glyphNameIndexOffset = 34
+
+ buf, err := b.view(&f.src, int(f.post.offset)+glyphNameIndexOffset+2*int(x), 2)
+ if err != nil {
+ return "", err
+ }
+ u := u16(buf)
+ if u < numBuiltInPostNames {
+ i := builtInPostNamesOffsets[u+0]
+ j := builtInPostNamesOffsets[u+1]
+ return builtInPostNamesData[i:j], nil
+ }
+ // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
+ // says that "32768 through 65535 are reserved for future use".
+ if u > 32767 {
+ return "", errUnsupportedPostTable
+ }
+ u -= numBuiltInPostNames
+
+ // Iterate through the list of Pascal-formatted strings. A linear scan is
+ // clearly O(u), which isn't great (as the obvious loop, calling
+ // Font.GlyphName, to get all of the glyph names in a font has quadratic
+ // complexity), but the wire format doesn't suggest a better alternative.
+
+ offset := glyphNameIndexOffset + 2*f.NumGlyphs()
+ buf, err = b.view(&f.src, int(f.post.offset)+offset, int(f.post.length)-offset)
+ if err != nil {
+ return "", err
+ }
+
+ for {
+ if len(buf) == 0 {
+ return "", errInvalidPostTable
+ }
+ n := 1 + int(buf[0])
+ if len(buf) < n {
+ return "", errInvalidPostTable
+ }
+ if u == 0 {
+ return string(buf[1:n]), nil
+ }
+ buf = buf[n:]
+ u--
+ }
+}
+
+// GlyphName returns the name of the x'th glyph.
+//
+// Not every font contains glyph names. If not present, GlyphName will return
+// ("", nil).
+//
+// If present, the glyph name, provided by the font, is assumed to follow the
+// Adobe Glyph List Specification:
+// https://github.com/adobe-type-tools/agl-specification/blob/master/README.md
+//
+// This is also known as the "Adobe Glyph Naming convention", the "Adobe
+// document [for] Unicode and Glyph Names" or "PostScript glyph names".
+//
+// It returns ErrNotFound if the glyph index is out of range.
+func (f *Font) GlyphName(b *Buffer, x GlyphIndex) (string, error) {
+ if int(x) >= f.NumGlyphs() {
+ return "", ErrNotFound
+ }
+ if f.cached.post == nil {
+ return "", nil
+ }
+ switch f.cached.post.Version {
+ case 0x10000:
+ return f.glyphNameFormat10(x)
+ case 0x20000:
+ return f.glyphNameFormat20(b, x)
+ default:
+ return "", nil
+ }
+}
+
+// GlyphBounds returns the bounding box of the x'th glyph, drawn at a dot equal
+// to the origin, and that glyph's advance width. ppem is the number of pixels
+// in 1 em.
+//
+// It returns ErrNotFound if the glyph index is out of range.
+//
+// The glyph's ascent and descent are equal to -bounds.Min.Y and +bounds.Max.Y.
+// The glyph's left-side and right-side bearings are equal to bounds.Min.X and
+// advance-bounds.Max.X. A visual depiction of what these metrics are is at
+// https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyphterms_2x.png
+func (f *Font) GlyphBounds(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, err error) {
+ if int(x) >= f.NumGlyphs() {
+ return fixed.Rectangle26_6{}, 0, ErrNotFound
+ }
+ if b == nil {
+ b = &Buffer{}
+ }
+
+ // https://www.microsoft.com/typography/OTSPEC/hmtx.htm says that "As an
+ // optimization, the number of records can be less than the number of
+ // glyphs, in which case the advance width value of the last record applies
+ // to all remaining glyph IDs."
+ metricIndex := x
+ if n := GlyphIndex(f.cached.numHMetrics - 1); x > n {
+ metricIndex = n
+ }
+
+ buf, err := b.view(&f.src, int(f.hmtx.offset)+4*int(metricIndex), 2)
+ if err != nil {
+ return fixed.Rectangle26_6{}, 0, err
+ }
+ advance = fixed.Int26_6(u16(buf))
+ advance = scale(advance*ppem, f.cached.unitsPerEm)
+ if h == font.HintingFull {
+ // Quantize the fixed.Int26_6 value to the nearest pixel.
+ advance = (advance + 32) &^ 63
+ }
+
+ // Ignore the hmtx LSB entries and the glyf bounding boxes. Instead, always
+ // calculate bounds from the segments. OpenType does contain the bounds for
+ // each glyph in the glyf table, but the bounds are not available for
+ // compound glyphs. CFF/PostScript also have no explicit bounds and must be
+ // obtained from the segments.
+
+ segments, err := f.LoadGlyph(b, x, ppem, &LoadGlyphOptions{
+ // TODO: pass h, the font.Hinting.
+ })
+ if err != nil {
+ return fixed.Rectangle26_6{}, 0, err
+ }
+ return segments.Bounds(), advance, nil
+}
+
+// GlyphAdvance returns the advance width for the x'th glyph. ppem is the
+// number of pixels in 1 em.
+//
+// It returns ErrNotFound if the glyph index is out of range.
+func (f *Font) GlyphAdvance(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (fixed.Int26_6, error) {
+ if int(x) >= f.NumGlyphs() {
+ return 0, ErrNotFound
+ }
+ if b == nil {
+ b = &Buffer{}
+ }
+
+ // https://www.microsoft.com/typography/OTSPEC/hmtx.htm says that "As an
+ // optimization, the number of records can be less than the number of
+ // glyphs, in which case the advance width value of the last record applies
+ // to all remaining glyph IDs."
+ if n := GlyphIndex(f.cached.numHMetrics - 1); x > n {
+ x = n
+ }
+
+ buf, err := b.view(&f.src, int(f.hmtx.offset)+4*int(x), 2)
+ if err != nil {
+ return 0, err
+ }
+ adv := fixed.Int26_6(u16(buf))
+ adv = scale(adv*ppem, f.cached.unitsPerEm)
+ if h == font.HintingFull {
+ // Quantize the fixed.Int26_6 value to the nearest pixel.
+ adv = (adv + 32) &^ 63
+ }
+ return adv, nil
+}
+
+// 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) {
+
+ // Use GPOS kern tables if available.
+ if f.cached.kernFuncs != nil {
+ for _, kf := range f.cached.kernFuncs {
+ adv, err := kf(x0, x1)
+ if err == ErrNotFound {
+ continue
+ }
+ if err != nil {
+ return 0, err
+ }
+ kern := fixed.Int26_6(adv)
+ 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, ErrNotFound
+ }
+
+ // Fallback to kern table.
+
+ // TODO: Convert kern table handling into kernFunc and decide in Parse if
+ // GPOS or kern should be used.
+
+ if n := f.NumGlyphs(); int(x0) >= n || int(x1) >= n {
+ return 0, ErrNotFound
+ }
+ // Not every font has a kern table. If it doesn't, or if that table is
+ // ignored, there's no need to allocate a Buffer.
+ if f.cached.kernNumPairs == 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
+}
+
+// Metrics returns the metrics of this font.
+func (f *Font) Metrics(b *Buffer, ppem fixed.Int26_6, h font.Hinting) (font.Metrics, error) {
+ m := font.Metrics{
+ Height: scale(fixed.Int26_6(f.cached.ascent-f.cached.descent+f.cached.lineGap)*ppem, f.cached.unitsPerEm),
+ Ascent: +scale(fixed.Int26_6(f.cached.ascent)*ppem, f.cached.unitsPerEm),
+ Descent: -scale(fixed.Int26_6(f.cached.descent)*ppem, f.cached.unitsPerEm),
+ XHeight: scale(fixed.Int26_6(f.cached.xHeight)*ppem, f.cached.unitsPerEm),
+ CapHeight: scale(fixed.Int26_6(f.cached.capHeight)*ppem, f.cached.unitsPerEm),
+ CaretSlope: image.Point{X: int(f.cached.slope[0]), Y: int(f.cached.slope[1])},
+ }
+ if h == font.HintingFull {
+ // Quantize up to a whole pixel.
+ m.Height = (m.Height + 63) &^ 63
+ m.Ascent = (m.Ascent + 63) &^ 63
+ m.Descent = (m.Descent + 63) &^ 63
+ m.XHeight = (m.XHeight + 63) &^ 63
+ m.CapHeight = (m.CapHeight + 63) &^ 63
+ }
+ return m, nil
+}
+
+// WriteSourceTo writes the source data (the []byte or io.ReaderAt passed to
+// Parse or ParseReaderAt) to w.
+//
+// It returns the number of bytes written. On success, this is the final offset
+// of the furthest SFNT table in the source. This may be less than the length
+// of the []byte or io.ReaderAt originally passed.
+func (f *Font) WriteSourceTo(b *Buffer, w io.Writer) (int64, error) {
+ if f.initialOffset != 0 {
+ // TODO: when extracting a single font (i.e. TTF) out of a font
+ // collection (i.e. TTC), write only the i'th font and not the (i-1)
+ // previous fonts. Subtly, in the file format, table offsets may be
+ // relative to the start of the resource (for dfont collections) or the
+ // start of the file (otherwise). If we were to extract a single font
+ // here, we might need to dynamically patch the table offsets, bearing
+ // in mind that f.src.b is conceptually a 'read-only' slice of bytes.
+ return 0, errUnsupportedCollection
+ }
+
+ if f.src.b != nil {
+ n, err := w.Write(f.src.b[:f.cached.finalTableOffset])
+ return int64(n), err
+ }
+
+ // We have an io.ReaderAt source, not a []byte. It is tempting to see if
+ // the io.ReaderAt optionally implements the io.WriterTo interface, but we
+ // don't for two reasons:
+ // - We want to write exactly f.cached.finalTableOffset bytes, even if the
+ // underlying 'file' is larger, to be consistent with the []byte flavor.
+ // - We document that "Font methods are safe to call concurrently" and
+ // while io.ReaderAt is stateless (the offset is an argument), the
+ // io.Reader / io.Writer abstractions are stateful (the current position
+ // is a field) and mutable state generally isn't concurrent-safe.
+
+ if b == nil {
+ b = &Buffer{}
+ }
+ finalTableOffset := int(f.cached.finalTableOffset)
+ numBytesWritten := int64(0)
+ for offset := 0; offset < finalTableOffset; {
+ length := finalTableOffset - offset
+ if length > 4096 {
+ length = 4096
+ }
+ view, err := b.view(&f.src, offset, length)
+ if err != nil {
+ return numBytesWritten, err
+ }
+ n, err := w.Write(view)
+ numBytesWritten += int64(n)
+ if err != nil {
+ return numBytesWritten, err
+ }
+ offset += length
+ }
+ return numBytesWritten, nil
+}
+
+// Name returns the name value keyed by the given NameID.
+//
+// It returns ErrNotFound if there is no value for that key.
+func (f *Font) Name(b *Buffer, id NameID) ([]string, error) {
+ if b == nil {
+ b = &Buffer{}
+ }
+
+ const headerSize, entrySize = 6, 12
+ if f.name.length < headerSize {
+ return nil, errInvalidNameTable
+ }
+ buf, err := b.view(&f.src, int(f.name.offset), headerSize)
+ if err != nil {
+ return nil, err
+ }
+ numSubtables := u16(buf[2:])
+ if f.name.length < headerSize+entrySize*uint32(numSubtables) {
+ return nil, errInvalidNameTable
+ }
+ stringOffset := u16(buf[4:])
+
+ names := make([]string, 0)
+ for i, n := 0, int(numSubtables); i < n; i++ {
+ buf, err := b.view(&f.src, int(f.name.offset)+headerSize+entrySize*i, entrySize)
+ if err != nil {
+ return nil, err
+ }
+ if u16(buf[6:]) != uint16(id) {
+ continue
+ }
+
+ var stringify func([]byte) (string, error)
+ switch u32(buf) {
+ default:
+ continue
+ case pidMacintosh<<16 | psidMacintoshRoman:
+ stringify = stringifyMacintosh
+ case pidWindows<<16 | psidWindowsUCS2:
+ stringify = stringifyUCS2
+ }
+
+ nameLength := u16(buf[8:])
+ nameOffset := u16(buf[10:])
+ buf, err = b.view(&f.src, int(f.name.offset)+int(nameOffset)+int(stringOffset), int(nameLength))
+ if err != nil {
+ return nil, err
+ }
+ name, err := stringify(buf)
+ if err == nil {
+ names = append(names, name)
+ continue
+ } else {
+ return nil, err
+ }
+ }
+
+ if len(names) > 0 {
+ return names, nil
+ }
+ return nil, ErrNotFound
+}
+
+func stringifyMacintosh(b []byte) (string, error) {
+ for _, c := range b {
+ if c >= 0x80 {
+ // b contains some non-ASCII bytes.
+ s, _ := charmap.Macintosh.NewDecoder().Bytes(b)
+ return string(s), nil
+ }
+ }
+ // b contains only ASCII bytes.
+ return string(b), nil
+}
+
+func stringifyUCS2(b []byte) (string, error) {
+ if len(b)&1 != 0 {
+ return "", errInvalidUCS2String
+ }
+ r := make([]rune, len(b)/2)
+ for i := range r {
+ r[i] = rune(u16(b))
+ b = b[2:]
+ }
+ return string(r), nil
+}
+
+// Buffer holds re-usable buffers that can reduce the total memory allocation
+// of repeated Font method calls.
+//
+// See the Font type's documentation comment for more details.
+type Buffer struct {
+ // buf is a byte buffer for when a Font's source is an io.ReaderAt.
+ buf []byte
+ // segments holds glyph vector path segments.
+ segments Segments
+ // compoundStack holds the components of a TrueType compound glyph.
+ compoundStack [maxCompoundStackSize]struct {
+ glyphIndex GlyphIndex
+ dx, dy int16
+ hasTransform bool
+ transformXX int16
+ transformXY int16
+ transformYX int16
+ transformYY int16
+ }
+ // psi is a PostScript interpreter for when the Font is an OpenType/CFF
+ // font.
+ psi psInterpreter
+}
+
+func (b *Buffer) view(src *source, offset, length int) ([]byte, error) {
+ buf, err := src.view(b.buf, offset, length)
+ if err != nil {
+ return nil, err
+ }
+ // Only update b.buf if it is safe to re-use buf.
+ if src.viewBufferWritable() {
+ b.buf = buf
+ }
+ return buf, nil
+}
+
+// Segment is a segment of a vector path.
+type Segment struct {
+ // Op is the operator.
+ Op SegmentOp
+ // Args is up to three (x, y) coordinates. The Y axis increases down.
+ Args [3]fixed.Point26_6
+}
+
+// SegmentOp is a vector path segment's operator.
+type SegmentOp uint32
+
+const (
+ SegmentOpMoveTo SegmentOp = iota
+ SegmentOpLineTo
+ SegmentOpQuadTo
+ SegmentOpCubeTo
+)
+
+// Segments is a slice of Segment.
+type Segments []Segment
+
+// Bounds returns s' bounding box. It returns an empty rectangle if s is empty.
+func (s Segments) Bounds() (bounds fixed.Rectangle26_6) {
+ if len(s) == 0 {
+ return fixed.Rectangle26_6{}
+ }
+
+ bounds.Min.X = fixed.Int26_6(+(1 << 31) - 1)
+ bounds.Min.Y = fixed.Int26_6(+(1 << 31) - 1)
+ bounds.Max.X = fixed.Int26_6(-(1 << 31) + 0)
+ bounds.Max.Y = fixed.Int26_6(-(1 << 31) + 0)
+
+ for _, seg := range s {
+ n := 1
+ switch seg.Op {
+ case SegmentOpQuadTo:
+ n = 2
+ case SegmentOpCubeTo:
+ n = 3
+ }
+ for i := 0; i < n; i++ {
+ if bounds.Max.X < seg.Args[i].X {
+ bounds.Max.X = seg.Args[i].X
+ }
+ if bounds.Min.X > seg.Args[i].X {
+ bounds.Min.X = seg.Args[i].X
+ }
+ if bounds.Max.Y < seg.Args[i].Y {
+ bounds.Max.Y = seg.Args[i].Y
+ }
+ if bounds.Min.Y > seg.Args[i].Y {
+ bounds.Min.Y = seg.Args[i].Y
+ }
+ }
+ }
+
+ return bounds
+}
+
+// translateArgs applies a translation to args.
+func translateArgs(args *[3]fixed.Point26_6, dx, dy fixed.Int26_6) {
+ args[0].X += dx
+ args[0].Y += dy
+ args[1].X += dx
+ args[1].Y += dy
+ args[2].X += dx
+ args[2].Y += dy
+}
+
+// transformArgs applies an affine transformation to args. The t?? arguments
+// are 2.14 fixed point values.
+func transformArgs(args *[3]fixed.Point26_6, txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6) {
+ args[0] = tform(txx, txy, tyx, tyy, dx, dy, args[0])
+ args[1] = tform(txx, txy, tyx, tyy, dx, dy, args[1])
+ args[2] = tform(txx, txy, tyx, tyy, dx, dy, args[2])
+}
+
+func tform(txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6, p fixed.Point26_6) fixed.Point26_6 {
+ const half = 1 << 13
+ return fixed.Point26_6{
+ X: dx +
+ fixed.Int26_6((int64(p.X)*int64(txx)+half)>>14) +
+ fixed.Int26_6((int64(p.Y)*int64(tyx)+half)>>14),
+ Y: dy +
+ fixed.Int26_6((int64(p.X)*int64(txy)+half)>>14) +
+ fixed.Int26_6((int64(p.Y)*int64(tyy)+half)>>14),
+ }
+}
diff --git a/mkvlib/parser/sfnt/truetype.go b/mkvlib/parser/sfnt/truetype.go
new file mode 100644
index 0000000..0d0b309
--- /dev/null
+++ b/mkvlib/parser/sfnt/truetype.go
@@ -0,0 +1,578 @@
+// 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 int32) (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
+
+func loadGlyf(f *Font, b *Buffer, x GlyphIndex, stackBottom, recursionDepth uint32) error {
+ data, _, _, err := f.viewGlyphData(b, x)
+ if err != nil {
+ return err
+ }
+ if len(data) == 0 {
+ return nil
+ }
+ if len(data) < glyfHeaderLen {
+ return errInvalidGlyphData
+ }
+ index := glyfHeaderLen
+
+ numContours, numPoints := int16(u16(data)), 0
+ switch {
+ case numContours == -1:
+ // We have a compound glyph. No-op.
+ case numContours == 0:
+ return nil
+ case numContours > 0:
+ // We have a simple (non-compound) glyph.
+ index += 2 * int(numContours)
+ if index > len(data) {
+ return 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 errInvalidGlyphData
+ }
+
+ if numContours < 0 {
+ return loadCompoundGlyf(f, b, data[glyfHeaderLen:], stackBottom, recursionDepth)
+ }
+
+ // Skip the hinting instructions.
+ index += 2
+ if index > len(data) {
+ return errInvalidGlyphData
+ }
+ hintsLength := int(u16(data[index-2:]))
+ index += hintsLength
+ if index > len(data) {
+ return errInvalidGlyphData
+ }
+
+ // 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 coordinates, then
+ // all the y coordinates), 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 coordinates and the y coordinates each start.
+ flagIndex := int32(index)
+ xIndex, yIndex, ok := findXYIndexes(data, index, numPoints)
+ if !ok {
+ return 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 on prevEnd and finalEnd are because the contour-end index in
+ // the file format is inclusive, but Go's slice[:index] is exclusive.
+ prevEnd: -1,
+ finalEnd: int32(numPoints - 1),
+ numContours: int32(numContours),
+ }
+ for g.nextContour() {
+ for g.nextSegment() {
+ b.segments = append(b.segments, g.seg)
+ }
+ }
+ return g.err
+}
+
+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
+}
+
+func loadCompoundGlyf(f *Font, b *Buffer, data []byte, stackBottom, recursionDepth uint32) error {
+ if recursionDepth++; recursionDepth == maxCompoundRecursionDepth {
+ return errUnsupportedCompoundGlyph
+ }
+
+ // Read and process the compound glyph's components. They are two separate
+ // for loops, since reading parses the elements of the data slice, and
+ // processing can overwrite the backing array.
+
+ stackTop := stackBottom
+ for {
+ if stackTop >= maxCompoundStackSize {
+ return errUnsupportedCompoundGlyph
+ }
+ elem := &b.compoundStack[stackTop]
+ stackTop++
+
+ if len(data) < 4 {
+ return errInvalidGlyphData
+ }
+ flags := u16(data)
+ elem.glyphIndex = GlyphIndex(u16(data[2:]))
+ if flags&flagArg1And2AreWords == 0 {
+ if len(data) < 6 {
+ return errInvalidGlyphData
+ }
+ elem.dx = int16(int8(data[4]))
+ elem.dy = int16(int8(data[5]))
+ data = data[6:]
+ } else {
+ if len(data) < 8 {
+ return errInvalidGlyphData
+ }
+ elem.dx = int16(u16(data[4:]))
+ elem.dy = int16(u16(data[6:]))
+ data = data[8:]
+ }
+
+ if flags&flagArgsAreXYValues == 0 {
+ return errUnsupportedCompoundGlyph
+ }
+ elem.hasTransform = flags&(flagWeHaveAScale|flagWeHaveAnXAndYScale|flagWeHaveATwoByTwo) != 0
+ if elem.hasTransform {
+ switch {
+ case flags&flagWeHaveAScale != 0:
+ if len(data) < 2 {
+ return errInvalidGlyphData
+ }
+ elem.transformXX = int16(u16(data))
+ elem.transformXY = 0
+ elem.transformYX = 0
+ elem.transformYY = elem.transformXX
+ data = data[2:]
+ case flags&flagWeHaveAnXAndYScale != 0:
+ if len(data) < 4 {
+ return errInvalidGlyphData
+ }
+ elem.transformXX = int16(u16(data[0:]))
+ elem.transformXY = 0
+ elem.transformYX = 0
+ elem.transformYY = int16(u16(data[2:]))
+ data = data[4:]
+ case flags&flagWeHaveATwoByTwo != 0:
+ if len(data) < 8 {
+ return errInvalidGlyphData
+ }
+ elem.transformXX = int16(u16(data[0:]))
+ elem.transformXY = int16(u16(data[2:]))
+ elem.transformYX = int16(u16(data[4:]))
+ elem.transformYY = int16(u16(data[6:]))
+ data = data[8:]
+ }
+ }
+
+ if flags&flagMoreComponents == 0 {
+ break
+ }
+ }
+
+ // To support hinting, we'd have to save the remaining bytes in data here
+ // and interpret them after the for loop below, since that for loop's
+ // loadGlyf calls can overwrite the backing array.
+
+ for i := stackBottom; i < stackTop; i++ {
+ elem := &b.compoundStack[i]
+ base := len(b.segments)
+ if err := loadGlyf(f, b, elem.glyphIndex, stackTop, recursionDepth); err != nil {
+ return err
+ }
+ dx, dy := fixed.Int26_6(elem.dx), fixed.Int26_6(elem.dy)
+ segments := b.segments[base:]
+ if elem.hasTransform {
+ txx := elem.transformXX
+ txy := elem.transformXY
+ tyx := elem.transformYX
+ tyy := elem.transformYY
+ for j := range segments {
+ transformArgs(&segments[j].Args, txx, txy, tyx, tyy, dx, dy)
+ }
+ } else {
+ for j := range segments {
+ translateArgs(&segments[j].Args, dx, dy)
+ }
+ }
+ }
+
+ return nil
+}
+
+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. finalEnd
+ // should match the final contour's end.
+ endIndex int32
+ prevEnd int32
+ finalEnd 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 {
+ if g.prevEnd != g.finalEnd {
+ g.err = errInvalidGlyphData
+ }
+ return false
+ }
+ g.c++
+
+ end := int32(u16(g.data[g.endIndex:]))
+ g.endIndex += 2
+ if (end <= g.prevEnd) || (g.finalEnd < end) {
+ 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: [3]fixed.Point26_6{g.firstOnCurve},
+ }
+ case !g.firstOffCurveValid && g.lastOffCurveValid:
+ g.closed = true
+ g.seg = Segment{
+ Op: SegmentOpQuadTo,
+ Args: [3]fixed.Point26_6{g.lastOffCurve, g.firstOnCurve},
+ }
+ case g.firstOffCurveValid && !g.lastOffCurveValid:
+ g.closed = true
+ g.seg = Segment{
+ Op: SegmentOpQuadTo,
+ Args: [3]fixed.Point26_6{g.firstOffCurve, g.firstOnCurve},
+ }
+ case g.firstOffCurveValid && g.lastOffCurveValid:
+ g.lastOffCurveValid = false
+ g.seg = Segment{
+ Op: SegmentOpQuadTo,
+ Args: [3]fixed.Point26_6{
+ g.lastOffCurve,
+ midPoint(g.lastOffCurve, g.firstOffCurve),
+ },
+ }
+ }
+}
+
+func (g *glyfIter) nextSegment() (ok bool) {
+ for !g.closed {
+ if g.closing || !g.nextPoint() {
+ g.closing = true
+ g.close()
+ 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{
+ X: fixed.Int26_6(g.x),
+ Y: fixed.Int26_6(g.y),
+ }
+
+ if !g.firstOnCurveValid {
+ if g.on {
+ g.firstOnCurve = p
+ g.firstOnCurveValid = true
+ g.seg = Segment{
+ Op: SegmentOpMoveTo,
+ Args: [3]fixed.Point26_6{p},
+ }
+ return true
+ } else if !g.firstOffCurveValid {
+ g.firstOffCurve = p
+ g.firstOffCurveValid = true
+ continue
+ } else {
+ g.firstOnCurve = midPoint(g.firstOffCurve, p)
+ g.firstOnCurveValid = true
+ g.lastOffCurve = p
+ g.lastOffCurveValid = true
+ g.seg = Segment{
+ Op: SegmentOpMoveTo,
+ Args: [3]fixed.Point26_6{g.firstOnCurve},
+ }
+ return true
+ }
+
+ } else if !g.lastOffCurveValid {
+ if !g.on {
+ g.lastOffCurve = p
+ g.lastOffCurveValid = true
+ continue
+ } else {
+ g.seg = Segment{
+ Op: SegmentOpLineTo,
+ Args: [3]fixed.Point26_6{p},
+ }
+ return true
+ }
+
+ } else {
+ if !g.on {
+ g.seg = Segment{
+ Op: SegmentOpQuadTo,
+ Args: [3]fixed.Point26_6{
+ g.lastOffCurve,
+ midPoint(g.lastOffCurve, p),
+ },
+ }
+ g.lastOffCurve = p
+ g.lastOffCurveValid = true
+ return true
+ } else {
+ g.seg = Segment{
+ Op: SegmentOpQuadTo,
+ Args: [3]fixed.Point26_6{g.lastOffCurve, p},
+ }
+ 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
+}
diff --git a/mkvlib/shared.go b/mkvlib/shared.go
index 7675b57..5dadbce 100644
--- a/mkvlib/shared.go
+++ b/mkvlib/shared.go
@@ -11,7 +11,7 @@ import (
)
const libName = "mkvlib"
-const libVer = "v1.7.1"
+const libVer = "v1.7.2"
const LibFName = libName + " " + libVer