// SPDX-License-Identifier: Unlicense OR BSD-3-Clausepackage cff// code is adapted from golang.org/x/image/font/sfntimport (ps)var errUnsupportedCFFVersion = errors.New("unsupported CFF version")// CFF represents a parsed CFF font, as found in the 'CFF ' Opentype table.typeCFFstruct { userStrings userStrings fdSelect fdSelect// only valid for CIDFonts charset []uint16// indexed by glyph ID cidFontName string// Charstrings contains the actual glyph definition. // It has a length of numGlyphs and is indexed by glyph ID. // See `LoadGlyph` for a way to intepret the glyph data. Charstrings [][]byte fontName []byte// name from the Name INDEX globalSubrs [][]byte// array of length 1 for non CIDFonts // For CIDFonts, it can be safely indexed by `fdSelect` output localSubrs [][][]byte}// Parse parses a .cff font file.// Although CFF enables multiple font or CIDFont programs to be bundled together in a// single file, embedded CFF font file in PDF or in TrueType/OpenType fonts// shall consist of exactly one font or CIDFont. Thus, this function// returns an error if the file contains more than one font.func ( []byte) (*CFF, error) {// read 4 bytes to check if its a supported CFF fileif := len(); < 4 {returnnil, fmt.Errorf("EOF: expected length: %d, got %d", 4, ) }if [0] != 1 || [1] != 0 || [2] != 4 {returnnil, errUnsupportedCFFVersion } := cffParser{src: , offset: 4} , := .parse()if != nil {returnnil, }iflen() > 1 {returnnil, errors.New("only one font is allowed CFF table") }return &[0], nil}// GlyphName returns the name of the glyph or an empty string if not found.func ( *CFF) ( opentype.GID) string {if .fdSelect != nil || int() >= len(.charset) {return"" } , := .userStrings.getString(.charset[])return}// since SID = 0 means .notdef, we use a reserved value// to mean unsetconst unsetSID = uint16(0xFFFF)type userStrings [][]byte// return either the predefined string or the user defined onefunc ( userStrings) ( uint16) (string, error) {if == unsetSID {return"", nil }if < 391 {returnstdStrings[], nil } -= 391ifint() >= len() {return"", fmt.Errorf("invalid glyph index %d", ) }returnstring([]), nil}// 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 go-opentype-testdata/data/toys/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.//// 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.pdftype cffParser struct { src []byte// whole input offset int// current position}func ( *cffParser) () ([]CFF, error) {// header was checked prior to this call// Parse the Name INDEX. , := .parseNames()if != nil {returnnil, } , := .parseTopDicts()if != nil {returnnil, }// 5176.CFF.pdf section 8 "Top DICT INDEX" says that the count here // should match the count of the Name INDEXiflen() != len() {returnnil, fmt.Errorf("top DICT length doest not match Names (%d, %d)", len(),len()) }// parse the String INDEX. , := .parseUserStrings()if != nil {returnnil, } := make([]CFF, len())// use the strings to fetch the PSInfofor , := range { [].fontName = [] [].userStrings = // skip PSInfo, and cidFontName [].cidFontName, = .getString(.cidFontName)if != nil {returnnil, } }// Parse the Global Subrs [Subroutines] INDEX, // shared among all fonts. , := .parseIndex()if != nil {returnnil, }for , := range { [].globalSubrs = // Parse the CharStrings INDEX, whose location was found in the Top DICT.if = .seek(.charStringsOffset); != nil {returnnil, } [].Charstrings, = .parseIndex()if != nil {returnnil, } := uint16(len([].Charstrings)) [].charset, = .parseCharset(.charsetOffset, )if != nil {returnnil, }// skip encodingif !.isCIDFont {// Parse the Private DICT, whose location was found in the Top DICT.var [][]byte , = .parsePrivateDICT(.privateDictOffset, .privateDictLength)if != nil {returnnil, } [].localSubrs = [][][]byte{} } else {// Parse the Font Dict Select data, whose location was found in the Top // DICT. [].fdSelect, = .parseFDSelect(.fdSelect, )if != nil {returnnil, } := [].fdSelect.extent()// Parse the Font Dicts. Each one contains its own Private DICT.if = .seek(.fdArray); != nil {returnnil, } , := .parseTopDicts()if != nil {returnnil, }iflen() < {returnnil, fmt.Errorf("invalid number of font dicts: %d (for %d)",len(), ) } := make([][][]byte, len())for , := range { [], = .parsePrivateDICT(.privateDictOffset, .privateDictLength)if != nil {returnnil, } } [].localSubrs = } }return , nil}func ( *cffParser) () ([]topDict, error) {// Parse the Top DICT INDEX. , := .parseIndex()if != nil {returnnil, } := make([]topDict, len()) // guarded by uint16 max sizevarps.Machinefor , := range { := &[]// set default value before parsing .underlinePosition = -100 .underlineThickness = 50 .version = unsetSID .notice = unsetSID .fullName = unsetSID .familyName = unsetSID .weight = unsetSID .cidFontName = unsetSIDif = .Run(, nil, nil, ); != nil {returnnil, } }return , nil}// src does NOT includes header, but starts at the array offset// also returns the length read from 'src'func parseIndexContent( []byte, indexStart) ([][]byte, int, error) {if .count == 0 {returnnil, 0, nil } := int(.offSize) := int(.count+1) * if := len(); < {returnnil, 0, fmt.Errorf("reading INDEX offsets: EOF: expected length: %d, got %d", , ) } := make([][]byte, .count) := [:] := 0for := range {// In the same paragraph, "Therefore the first element of the offset // array is always 1" before correcting for the off-by-1. := int(bigEndian([(+1)* : (+2)*]))// 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 == 0 {returnnil, 0, errors.New("invalid INDEX locations (0)") } --if < { // Check that locations are increasingreturnnil, 0, errors.New("invalid INDEX locations (not increasing)") }// Check that locations are in bounds, that is offsetsLength + loc <= len(src)ifint() > len() {returnnil, 0, errors.New("invalid INDEX locations (out of bounds)") } [] = [:] = }return , + , nil}// parse the general form of an indexfunc ( *cffParser) () ([][]byte, error) { , , := .parseIndexHeader()if != nil {returnnil, } , , := parseIndexContent(.src[.offset:], indexStart{count: uint32(), offSize: }) .offset += return , }// parse the Name INDEXfunc ( *cffParser) () ([][]byte, error) {return .parseIndex()}// parse the String INDEXfunc ( *cffParser) () (userStrings, error) { , := .parseIndex()returnuserStrings(), }// Parse the charset data, whose location was found in the Top DICT.func ( *cffParser) ( int32, uint16) ([]uint16, error) {// Predefined charset may have offset of 0 to 2 // Table 22var []uint16switch {case0: // ISOAdobe = charsetISOAdobe[:]case1: // Expert = charsetExpert[:]case2: // ExpertSubset = charsetExpertSubset[:]default: // customif := .seek(); != nil {returnnil, } , := .read(1)if != nil {returnnil, } = make([]uint16, )switch [0] { // formatcase0: , = .read(2 * (int() - 1)) // ".notdef" is omited, and has an implicit SID of 0if != nil {returnnil, }for := uint16(1); < ; ++ { [] = binary.BigEndian.Uint16([2*-2:]) }case1:for := uint16(1); < ; { , = .read(3)if != nil {returnnil, } , := binary.BigEndian.Uint16(), uint16([2])for := uint16(0); <= && < ; ++ { [] = + ++ } }case2:for := uint16(1); < ; { , = .read(4)if != nil {returnnil, } , := binary.BigEndian.Uint16(), binary.BigEndian.Uint16([2:])for := uint16(0); <= && < ; ++ { [] = + ++ } }default:returnnil, fmt.Errorf("invalid custom charset format %d", [0]) } }return , nil}// parseFDSelect parses the Font Dict Select data as per 5176.CFF.pdf section// 19 "FDSelect".func ( *cffParser) ( int32, uint16) (fdSelect, error) {if := .seek(); != nil {returnnil, } , , := parseFdSelect(.src[:], int())if != nil {returnnil, }return , }// Parse Private DICT and the Local Subrs [Subroutines] INDEXfunc ( *cffParser) (, int32) ([][]byte, error) {if == 0 {returnnil, nil }if := .seek(); != nil {returnnil, } , := .read(int())if != nil {returnnil, }var (ps.MachineprivateDict )if = .Run(, nil, nil, &); != nil {returnnil, }if .subrsOffset == 0 {returnnil, nil }// "The local subrs offset is relative to the beginning of the Private DICT data"if = .seek( + .subrsOffset); != nil {returnnil, errors.New("invalid local subroutines offset") } , := .parseIndex()if != nil {returnnil, }return , nil}// read returns the n bytes from p.offset and advances p.offset by n.func ( *cffParser) ( int) ([]byte, error) {if < 0 || len(.src) < .offset+ {returnnil, errors.New("invalid CFF font file (EOF)") } := .src[.offset : .offset+] .offset += return , nil}func ( *cffParser) ( int32) error {if < 0 || len(.src) < int() {returnerrors.New("invalid CFF font file (EOF)") } .offset = int()returnnil}func bigEndian( []byte) uint32 {switchlen() {case1:returnuint32([0])case2:returnuint32([0])<<8 | uint32([1])case3:returnuint32([0])<<16 | uint32([1])<<8 | uint32([2])case4:returnuint32([0])<<24 | uint32([1])<<16 | uint32([2])<<8 | uint32([3]) }panic("unreachable")}func ( *cffParser) () ( uint16, uint8, error) { , := .read(2)if != nil {return0, 0, } = binary.BigEndian.Uint16()// 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 == 0 {return , 0, nil } , = .read(1)if != nil {return0, 0, } = [0]if < 1 || 4 < {return0, 0, fmt.Errorf("invalid offset size %d", ) }return , , nil}// topDict contains fields specific to the Top DICT context.type topDict struct {// SIDs, to be decoded using the string index version, notice, fullName, familyName, weight uint16 isFixedPitch bool italicAngle, underlinePosition, underlineThickness float32 charsetOffset int32 encodingOffset int32 charStringsOffset int32 fdArray int32 fdSelect int32 isCIDFont bool cidFontName uint16 privateDictOffset int32 privateDictLength int32}func ( *topDict) () ps.Context { returnps.TopDict }func ( *topDict) ( *ps.Machine, ps.Operator) error { := topDictOperators[0]if .IsEscaped { = topDictOperators[1] }ifint(.Operator) >= len() {returnfmt.Errorf("invalid operator %s in Top Dict", ) } := [.Operator]if .run == nil {returnfmt.Errorf("invalid operator %s in Top Dict", ) }if .ArgStack.Top < .numPop {returnfmt.Errorf("invalid number of arguments for operator %s in Top Dict", ) } := .run(, )if != nil {return } = .ArgStack.PopN(.numPop)return}// The Top DICT operators are defined by 5176.CFF.pdf Table 9 "Top DICT// Operator Entries" and Table 10 "CIDFont Operator Extensions".type topDictOperator struct {// 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(*topDict, *ps.Machine) error// 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}func topDictNoOp(*topDict, *ps.Machine) error { returnnil }var topDictOperators = [2][]topDictOperator{// 1-byte operators. {0: {func( *topDict, *ps.Machine) error { .version = .ArgStack.Uint16()returnnil }, +1/*version*/},1: {func( *topDict, *ps.Machine) error { .notice = .ArgStack.Uint16()returnnil }, +1/*Notice*/},2: {func( *topDict, *ps.Machine) error { .fullName = .ArgStack.Uint16()returnnil }, +1/*FullName*/},3: {func( *topDict, *ps.Machine) error { .familyName = .ArgStack.Uint16()returnnil }, +1/*FamilyName*/},4: {func( *topDict, *ps.Machine) error { .weight = .ArgStack.Uint16()returnnil }, +1/*Weight*/},5: {topDictNoOp, -1/*FontBBox*/},13: {topDictNoOp, +1/*UniqueID*/},14: {topDictNoOp, -1/*XUID*/},15: {func( *topDict, *ps.Machine) error { .charsetOffset = int32(.ArgStack.Vals[.ArgStack.Top-1])returnnil }, +1/*charset*/},16: {func( *topDict, *ps.Machine) error { .encodingOffset = int32(.ArgStack.Vals[.ArgStack.Top-1])returnnil }, +1/*Encoding*/},17: {func( *topDict, *ps.Machine) error { .charStringsOffset = int32(.ArgStack.Vals[.ArgStack.Top-1])returnnil }, +1/*CharStrings*/},18: {func( *topDict, *ps.Machine) error { .privateDictLength = int32(.ArgStack.Vals[.ArgStack.Top-2]) .privateDictOffset = int32(.ArgStack.Vals[.ArgStack.Top-1])returnnil }, +2/*Private*/}, },// 2-byte operators. The first byte is the escape byte. {0: {topDictNoOp, +1/*Copyright*/},1: {func( *topDict, *ps.Machine) error { .isFixedPitch = .ArgStack.Vals[.ArgStack.Top-1] == 1returnnil }, +1/*isFixedPitch*/},2: {func( *topDict, *ps.Machine) error { .italicAngle = float32(.ArgStack.Vals[.ArgStack.Top-1])returnnil }, +1/*ItalicAngle*/},3: {func( *topDict, *ps.Machine) error { .underlinePosition = float32(.ArgStack.Vals[.ArgStack.Top-1])returnnil }, +1/*UnderlinePosition*/},4: {func( *topDict, *ps.Machine) error { .underlineThickness = float32(.ArgStack.Vals[.ArgStack.Top-1])returnnil }, +1/*UnderlineThickness*/},5: {topDictNoOp, +1/*PaintType*/},6: {func( *topDict, *ps.Machine) error {if := int(.ArgStack.Vals[.ArgStack.Top-1]); != 2 {returnfmt.Errorf("charstring type %d not supported", ) }returnnil }, +1/*CharstringType*/},7: {topDictNoOp, -1/*FontMatrix*/},8: {topDictNoOp, +1/*StrokeWidth*/},20: {topDictNoOp, +1/*SyntheticBase*/},21: {topDictNoOp, +1/*PostScript*/},22: {topDictNoOp, +1/*BaseFontName*/},23: {topDictNoOp, -2/*BaseFontBlend*/},30: {func( *topDict, *ps.Machine) error { .isCIDFont = truereturnnil }, +3/*ROS*/},31: {topDictNoOp, +1/*CIDFontVersion*/},32: {topDictNoOp, +1/*CIDFontRevision*/},33: {topDictNoOp, +1/*CIDFontType*/},34: {topDictNoOp, +1/*CIDCount*/},35: {topDictNoOp, +1/*UIDBase*/},36: {func( *topDict, *ps.Machine) error { .fdArray = int32(.ArgStack.Vals[.ArgStack.Top-1])returnnil }, +1/*FDArray*/},37: {func( *topDict, *ps.Machine) error { .fdSelect = int32(.ArgStack.Vals[.ArgStack.Top-1])returnnil }, +1/*FDSelect*/},38: {func( *topDict, *ps.Machine) error { .cidFontName = .ArgStack.Uint16()returnnil }, +1/*FontName*/}, },}// privateDict contains fields specific to the Private DICT context.type privateDict struct { subrsOffset int32 defaultWidthX, nominalWidthX float64}func (privateDict) () ps.Context { returnps.PrivateDict }// The Private DICT operators are defined by 5176.CFF.pdf Table 23 "Private// DICT Operators".func ( *privateDict) ( *ps.Machine, ps.Operator) error {if !.IsEscaped { // 1-byte operators.switch .Operator {case6, 7, 8, 9: // "BlueValues" "OtherBlues" "FamilyBlues" "FamilyOtherBlues"return .ArgStack.PopN(-2)case10, 11: // "StdHW" "StdVW"return .ArgStack.PopN(1)case20: // "defaultWidthX"if .ArgStack.Top < 1 {returnerrors.New("invalid stack size for 'defaultWidthX' in private Dict charstring") } .defaultWidthX = .ArgStack.Vals[.ArgStack.Top-1]return .ArgStack.PopN(1)case21: // "nominalWidthX"if .ArgStack.Top < 1 {returnerrors.New("invalid stack size for 'nominalWidthX' in private Dict charstring") } .nominalWidthX = .ArgStack.Vals[.ArgStack.Top-1]return .ArgStack.PopN(1)case19: // "Subrs" pop 1if .ArgStack.Top < 1 {returnerrors.New("invalid stack size for 'subrs' in private Dict charstring") } .subrsOffset = int32(.ArgStack.Vals[.ArgStack.Top-1])return .ArgStack.PopN(1) } } else { // 2-byte operators. The first byte is the escape byte.switch .Operator {case9, 10, 11, 14, 17, 18, 19: // "BlueScale" "BlueShift" "BlueFuzz" "ForceBold" "LanguageGroup" "ExpansionFactor" "initialRandomSeed"return .ArgStack.PopN(1)case12, 13: // "StemSnapH" "StemSnapV"return .ArgStack.PopN(-2) } }returnerrors.New("invalid operand in private Dict charstring")}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.