/* * Copyright (c) 2019 Arteom Korotkiy (Gmail: arteomkorotkiy) * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ package gofpdf import ( "bytes" "encoding/binary" "fmt" "math" "sort" ) // flags const symbolWords = 1 << 0 const symbolScale = 1 << 3 const symbolContinue = 1 << 5 const symbolAllScale = 1 << 6 const symbol2x2 = 1 << 7 // CID map Init const toUnicode = "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo\n<> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000> \nendcodespacerange\n1 beginbfrange\n<0000> <0000>\nendbfrange\nendcmap\nCMapName currentdict /CMap defineresource pop\nend\nend" type utf8FontFile struct { fileReader *fileReader LastRune int tableDescriptions map[string]*tableDescription outTablesData map[string][]byte symbolPosition []int charSymbolDictionary map[int]int Ascent int Descent int fontElementSize int Bbox fontBoxType CapHeight int StemV int ItalicAngle int Flags int UnderlinePosition float64 UnderlineThickness float64 CharWidths []int DefaultWidth float64 symbolData map[int]map[string][]int CodeSymbolDictionary map[int]int } type tableDescription struct { name string checksum []int position int size int } type fileReader struct { readerPosition int64 array []byte } func (fr *fileReader) Read(s int) []byte { b := fr.array[fr.readerPosition : fr.readerPosition+int64(s)] fr.readerPosition += int64(s) return b } func (fr *fileReader) seek(shift int64, flag int) (int64, error) { if flag == 0 { fr.readerPosition = shift } else if flag == 1 { fr.readerPosition += shift } else if flag == 2 { fr.readerPosition = int64(len(fr.array)) - shift } return int64(fr.readerPosition), nil } func newUTF8Font(reader *fileReader) *utf8FontFile { utf := utf8FontFile{ fileReader: reader, } return &utf } func (utf *utf8FontFile) parseFile() error { utf.fileReader.readerPosition = 0 utf.symbolPosition = make([]int, 0) utf.charSymbolDictionary = make(map[int]int) utf.tableDescriptions = make(map[string]*tableDescription) utf.outTablesData = make(map[string][]byte) utf.Ascent = 0 utf.Descent = 0 codeType := uint32(utf.readUint32()) if codeType == 0x4F54544F { return fmt.Errorf("not supported\n ") } if codeType == 0x74746366 { return fmt.Errorf("not supported\n ") } if codeType != 0x00010000 && codeType != 0x74727565 { return fmt.Errorf("Not a TrueType font: codeType=%v\n ", codeType) } utf.generateTableDescriptions() utf.parseTables() return nil } func (utf *utf8FontFile) generateTableDescriptions() { tablesCount := utf.readUint16() _ = utf.readUint16() _ = utf.readUint16() _ = utf.readUint16() utf.tableDescriptions = make(map[string]*tableDescription) for i := 0; i < tablesCount; i++ { record := tableDescription{ name: utf.readTableName(), checksum: []int{utf.readUint16(), utf.readUint16()}, position: utf.readUint32(), size: utf.readUint32(), } utf.tableDescriptions[record.name] = &record } } func (utf *utf8FontFile) readTableName() string { return string(utf.fileReader.Read(4)) } func (utf *utf8FontFile) readUint16() int { s := utf.fileReader.Read(2) return (int(s[0]) << 8) + int(s[1]) } func (utf *utf8FontFile) readUint32() int { s := utf.fileReader.Read(4) return (int(s[0]) * 16777216) + (int(s[1]) << 16) + (int(s[2]) << 8) + int(s[3]) // 16777216 = 1<<24 } func (utf *utf8FontFile) calcInt32(x, y []int) []int { answer := make([]int, 2) if y[1] > x[1] { x[1] += 1 << 16 x[0]++ } answer[1] = x[1] - y[1] if y[0] > x[0] { x[0] += 1 << 16 } answer[0] = x[0] - y[0] answer[0] = answer[0] & 0xFFFF return answer } func (utf *utf8FontFile) generateChecksum(data []byte) []int { if (len(data) % 4) != 0 { for i := 0; (len(data) % 4) != 0; i++ { data = append(data, 0) } } answer := []int{0x0000, 0x0000} for i := 0; i < len(data); i += 4 { answer[0] += (int(data[i]) << 8) + int(data[i+1]) answer[1] += (int(data[i+2]) << 8) + int(data[i+3]) answer[0] += answer[1] >> 16 answer[1] = answer[1] & 0xFFFF answer[0] = answer[0] & 0xFFFF } return answer } func (utf *utf8FontFile) seek(shift int) { _, _ = utf.fileReader.seek(int64(shift), 0) } func (utf *utf8FontFile) skip(delta int) { _, _ = utf.fileReader.seek(int64(delta), 1) } //SeekTable position func (utf *utf8FontFile) SeekTable(name string) int { return utf.seekTable(name, 0) } func (utf *utf8FontFile) seekTable(name string, offsetInTable int) int { _, _ = utf.fileReader.seek(int64(utf.tableDescriptions[name].position+offsetInTable), 0) return int(utf.fileReader.readerPosition) } func (utf *utf8FontFile) readInt16() int16 { s := utf.fileReader.Read(2) a := (int16(s[0]) << 8) + int16(s[1]) if (int(a) & (1 << 15)) == 0 { a = int16(int(a) - (1 << 16)) } return a } func (utf *utf8FontFile) getUint16(pos int) int { _, _ = utf.fileReader.seek(int64(pos), 0) s := utf.fileReader.Read(2) return (int(s[0]) << 8) + int(s[1]) } func (utf *utf8FontFile) splice(stream []byte, offset int, value []byte) []byte { stream = append([]byte{}, stream...) return append(append(stream[:offset], value...), stream[offset+len(value):]...) } func (utf *utf8FontFile) insertUint16(stream []byte, offset int, value int) []byte { up := make([]byte, 2) binary.BigEndian.PutUint16(up, uint16(value)) return utf.splice(stream, offset, up) } func (utf *utf8FontFile) getRange(pos, length int) []byte { _, _ = utf.fileReader.seek(int64(pos), 0) if length < 1 { return make([]byte, 0) } s := utf.fileReader.Read(length) return s } func (utf *utf8FontFile) getTableData(name string) []byte { desckrip := utf.tableDescriptions[name] if desckrip == nil { return nil } if desckrip.size == 0 { return nil } _, _ = utf.fileReader.seek(int64(desckrip.position), 0) s := utf.fileReader.Read(desckrip.size) return s } func (utf *utf8FontFile) setOutTable(name string, data []byte) { if data == nil { return } if name == "head" { data = utf.splice(data, 8, []byte{0, 0, 0, 0}) } utf.outTablesData[name] = data } func arrayKeys(arr map[int]string) []int { answer := make([]int, len(arr)) i := 0 for key := range arr { answer[i] = key i++ } return answer } func inArray(s int, arr []int) bool { for _, i := range arr { if s == i { return true } } return false } func (utf *utf8FontFile) parseNAMETable() int { namePosition := utf.SeekTable("name") format := utf.readUint16() if format != 0 { fmt.Printf("Illegal format %d\n", format) return format } nameCount := utf.readUint16() stringDataPosition := namePosition + utf.readUint16() names := map[int]string{1: "", 2: "", 3: "", 4: "", 6: ""} keys := arrayKeys(names) counter := len(names) for i := 0; i < nameCount; i++ { system := utf.readUint16() code := utf.readUint16() local := utf.readUint16() nameID := utf.readUint16() size := utf.readUint16() position := utf.readUint16() if !inArray(nameID, keys) { continue } currentName := "" if system == 3 && code == 1 && local == 0x409 { oldPos := utf.fileReader.readerPosition utf.seek(stringDataPosition + position) if size%2 != 0 { fmt.Printf("name is not binar byte format\n") return format } size /= 2 currentName = "" for size > 0 { char := utf.readUint16() currentName += string(rune(char)) size-- } utf.fileReader.readerPosition = oldPos utf.seek(int(oldPos)) } else if system == 1 && code == 0 && local == 0 { oldPos := utf.fileReader.readerPosition currentName = string(utf.getRange(stringDataPosition+position, size)) utf.fileReader.readerPosition = oldPos utf.seek(int(oldPos)) } if currentName != "" && names[nameID] == "" { names[nameID] = currentName counter-- if counter == 0 { break } } } return format } func (utf *utf8FontFile) parseHEADTable() { utf.SeekTable("head") utf.skip(18) utf.fontElementSize = utf.readUint16() scale := 1000.0 / float64(utf.fontElementSize) utf.skip(16) xMin := utf.readInt16() yMin := utf.readInt16() xMax := utf.readInt16() yMax := utf.readInt16() utf.Bbox = fontBoxType{int(float64(xMin) * scale), int(float64(yMin) * scale), int(float64(xMax) * scale), int(float64(yMax) * scale)} utf.skip(3 * 2) _ = utf.readUint16() symbolDataFormat := utf.readUint16() if symbolDataFormat != 0 { fmt.Printf("Unknown symbol data format %d\n", symbolDataFormat) return } } func (utf *utf8FontFile) parseHHEATable() int { metricsCount := 0 if _, OK := utf.tableDescriptions["hhea"]; OK { scale := 1000.0 / float64(utf.fontElementSize) utf.SeekTable("hhea") utf.skip(4) hheaAscender := utf.readInt16() hheaDescender := utf.readInt16() utf.Ascent = int(float64(hheaAscender) * scale) utf.Descent = int(float64(hheaDescender) * scale) utf.skip(24) metricDataFormat := utf.readUint16() if metricDataFormat != 0 { fmt.Printf("Unknown horizontal metric data format %d\n", metricDataFormat) return 0 } metricsCount = utf.readUint16() if metricsCount == 0 { fmt.Printf("Number of horizontal metrics is 0\n") return 0 } } return metricsCount } func (utf *utf8FontFile) parseOS2Table() int { var weightType int scale := 1000.0 / float64(utf.fontElementSize) if _, OK := utf.tableDescriptions["OS/2"]; OK { utf.SeekTable("OS/2") version := utf.readUint16() utf.skip(2) weightType = utf.readUint16() utf.skip(2) fsType := utf.readUint16() if fsType == 0x0002 || (fsType&0x0300) != 0 { fmt.Printf("ERROR - copyright restrictions.\n") return 0 } utf.skip(20) _ = utf.readInt16() utf.skip(36) sTypoAscender := utf.readInt16() sTypoDescender := utf.readInt16() if utf.Ascent == 0 { utf.Ascent = int(float64(sTypoAscender) * scale) } if utf.Descent == 0 { utf.Descent = int(float64(sTypoDescender) * scale) } if version > 1 { utf.skip(16) sCapHeight := utf.readInt16() utf.CapHeight = int(float64(sCapHeight) * scale) } else { utf.CapHeight = utf.Ascent } } else { weightType = 500 if utf.Ascent == 0 { utf.Ascent = int(float64(utf.Bbox.Ymax) * scale) } if utf.Descent == 0 { utf.Descent = int(float64(utf.Bbox.Ymin) * scale) } utf.CapHeight = utf.Ascent } utf.StemV = 50 + int(math.Pow(float64(weightType)/65.0, 2)) return weightType } func (utf *utf8FontFile) parsePOSTTable(weight int) { utf.SeekTable("post") utf.skip(4) utf.ItalicAngle = int(utf.readInt16()) + utf.readUint16()/65536.0 scale := 1000.0 / float64(utf.fontElementSize) utf.UnderlinePosition = float64(utf.readInt16()) * scale utf.UnderlineThickness = float64(utf.readInt16()) * scale fixed := utf.readUint32() utf.Flags = 4 if utf.ItalicAngle != 0 { utf.Flags = utf.Flags | 64 } if weight >= 600 { utf.Flags = utf.Flags | 262144 } if fixed != 0 { utf.Flags = utf.Flags | 1 } } func (utf *utf8FontFile) parseCMAPTable(format int) int { cmapPosition := utf.SeekTable("cmap") utf.skip(2) cmapTableCount := utf.readUint16() cidCMAPPosition := 0 for i := 0; i < cmapTableCount; i++ { system := utf.readUint16() coded := utf.readUint16() position := utf.readUint32() oldReaderPosition := utf.fileReader.readerPosition if (system == 3 && coded == 1) || system == 0 { // Microsoft, Unicode format = utf.getUint16(cmapPosition + position) if format == 4 { if cidCMAPPosition == 0 { cidCMAPPosition = cmapPosition + position } break } } utf.seek(int(oldReaderPosition)) } if cidCMAPPosition == 0 { fmt.Printf("Font does not have cmap for Unicode\n") return cidCMAPPosition } return cidCMAPPosition } func (utf *utf8FontFile) parseTables() { f := utf.parseNAMETable() utf.parseHEADTable() n := utf.parseHHEATable() w := utf.parseOS2Table() utf.parsePOSTTable(w) runeCMAPPosition := utf.parseCMAPTable(f) utf.SeekTable("maxp") utf.skip(4) numSymbols := utf.readUint16() symbolCharDictionary := make(map[int][]int) charSymbolDictionary := make(map[int]int) utf.generateSCCSDictionaries(runeCMAPPosition, symbolCharDictionary, charSymbolDictionary) scale := 1000.0 / float64(utf.fontElementSize) utf.parseHMTXTable(n, numSymbols, symbolCharDictionary, scale) } func (utf *utf8FontFile) generateCMAP() map[int][]int { cmapPosition := utf.SeekTable("cmap") utf.skip(2) cmapTableCount := utf.readUint16() runeCmapPosition := 0 for i := 0; i < cmapTableCount; i++ { system := utf.readUint16() coder := utf.readUint16() position := utf.readUint32() oldPosition := utf.fileReader.readerPosition if (system == 3 && coder == 1) || system == 0 { format := utf.getUint16(cmapPosition + position) if format == 4 { runeCmapPosition = cmapPosition + position break } } utf.seek(int(oldPosition)) } if runeCmapPosition == 0 { fmt.Printf("Font does not have cmap for Unicode\n") return nil } symbolCharDictionary := make(map[int][]int) charSymbolDictionary := make(map[int]int) utf.generateSCCSDictionaries(runeCmapPosition, symbolCharDictionary, charSymbolDictionary) utf.charSymbolDictionary = charSymbolDictionary return symbolCharDictionary } func (utf *utf8FontFile) parseSymbols(usedRunes map[int]int) (map[int]int, map[int]int, map[int]int, []int) { symbolCollection := map[int]int{0: 0} charSymbolPairCollection := make(map[int]int) for _, char := range usedRunes { if _, OK := utf.charSymbolDictionary[char]; OK { symbolCollection[utf.charSymbolDictionary[char]] = char charSymbolPairCollection[char] = utf.charSymbolDictionary[char] } utf.LastRune = max(utf.LastRune, char) } begin := utf.tableDescriptions["glyf"].position symbolArray := make(map[int]int) symbolCollectionKeys := keySortInt(symbolCollection) symbolCounter := 0 maxRune := 0 for _, oldSymbolIndex := range symbolCollectionKeys { maxRune = max(maxRune, symbolCollection[oldSymbolIndex]) symbolArray[oldSymbolIndex] = symbolCounter symbolCounter++ } charSymbolPairCollectionKeys := keySortInt(charSymbolPairCollection) runeSymbolPairCollection := make(map[int]int) for _, runa := range charSymbolPairCollectionKeys { runeSymbolPairCollection[runa] = symbolArray[charSymbolPairCollection[runa]] } utf.CodeSymbolDictionary = runeSymbolPairCollection symbolCollectionKeys = keySortInt(symbolCollection) for _, oldSymbolIndex := range symbolCollectionKeys { _, symbolArray, symbolCollection, symbolCollectionKeys = utf.getSymbols(oldSymbolIndex, &begin, symbolArray, symbolCollection, symbolCollectionKeys) } return runeSymbolPairCollection, symbolArray, symbolCollection, symbolCollectionKeys } func (utf *utf8FontFile) generateCMAPTable(cidSymbolPairCollection map[int]int, numSymbols int) []byte { cidSymbolPairCollectionKeys := keySortInt(cidSymbolPairCollection) cidID := 0 cidArray := make(map[int][]int) prevCid := -2 prevSymbol := -1 for _, cid := range cidSymbolPairCollectionKeys { if cid == (prevCid+1) && cidSymbolPairCollection[cid] == (prevSymbol+1) { if n, OK := cidArray[cidID]; !OK || n == nil { cidArray[cidID] = make([]int, 0) } cidArray[cidID] = append(cidArray[cidID], cidSymbolPairCollection[cid]) } else { cidID = cid cidArray[cidID] = make([]int, 0) cidArray[cidID] = append(cidArray[cidID], cidSymbolPairCollection[cid]) } prevCid = cid prevSymbol = cidSymbolPairCollection[cid] } cidArrayKeys := keySortArrayRangeMap(cidArray) segCount := len(cidArray) + 1 searchRange := 1 entrySelector := 0 for searchRange*2 <= segCount { searchRange = searchRange * 2 entrySelector = entrySelector + 1 } searchRange = searchRange * 2 rangeShift := segCount*2 - searchRange length := 16 + (8 * segCount) + (numSymbols + 1) cmap := []int{0, 1, 3, 1, 0, 12, 4, length, 0, segCount * 2, searchRange, entrySelector, rangeShift} for _, start := range cidArrayKeys { endCode := start + (len(cidArray[start]) - 1) cmap = append(cmap, endCode) } cmap = append(cmap, 0xFFFF) cmap = append(cmap, 0) for _, cidKey := range cidArrayKeys { cmap = append(cmap, cidKey) } cmap = append(cmap, 0xFFFF) for _, cidKey := range cidArrayKeys { idDelta := -(cidKey - cidArray[cidKey][0]) cmap = append(cmap, idDelta) } cmap = append(cmap, 1) for range cidArray { cmap = append(cmap, 0) } cmap = append(cmap, 0) for _, start := range cidArrayKeys { for _, glidx := range cidArray[start] { cmap = append(cmap, glidx) } } cmap = append(cmap, 0) cmapstr := make([]byte, 0) for _, cm := range cmap { cmapstr = append(cmapstr, packUint16(cm)...) } return cmapstr } //GenerateCutFont fill utf8FontFile from .utf file, only with runes from usedRunes func (utf *utf8FontFile) GenerateCutFont(usedRunes map[int]int) []byte { utf.fileReader.readerPosition = 0 utf.symbolPosition = make([]int, 0) utf.charSymbolDictionary = make(map[int]int) utf.tableDescriptions = make(map[string]*tableDescription) utf.outTablesData = make(map[string][]byte) utf.Ascent = 0 utf.Descent = 0 utf.skip(4) utf.LastRune = 0 utf.generateTableDescriptions() utf.SeekTable("head") utf.skip(50) LocaFormat := utf.readUint16() utf.SeekTable("hhea") utf.skip(34) metricsCount := utf.readUint16() oldMetrics := metricsCount utf.SeekTable("maxp") utf.skip(4) numSymbols := utf.readUint16() symbolCharDictionary := utf.generateCMAP() if symbolCharDictionary == nil { return nil } utf.parseHMTXTable(metricsCount, numSymbols, symbolCharDictionary, 1.0) utf.parseLOCATable(LocaFormat, numSymbols) cidSymbolPairCollection, symbolArray, symbolCollection, symbolCollectionKeys := utf.parseSymbols(usedRunes) metricsCount = len(symbolCollection) numSymbols = metricsCount utf.setOutTable("name", utf.getTableData("name")) utf.setOutTable("cvt ", utf.getTableData("cvt ")) utf.setOutTable("fpgm", utf.getTableData("fpgm")) utf.setOutTable("prep", utf.getTableData("prep")) utf.setOutTable("gasp", utf.getTableData("gasp")) postTable := utf.getTableData("post") postTable = append(append([]byte{0x00, 0x03, 0x00, 0x00}, postTable[4:16]...), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...) utf.setOutTable("post", postTable) delete(cidSymbolPairCollection, 0) utf.setOutTable("cmap", utf.generateCMAPTable(cidSymbolPairCollection, numSymbols)) symbolData := utf.getTableData("glyf") offsets := make([]int, 0) glyfData := make([]byte, 0) pos := 0 hmtxData := make([]byte, 0) utf.symbolData = make(map[int]map[string][]int, 0) for _, originalSymbolIdx := range symbolCollectionKeys { hm := utf.getMetrics(oldMetrics, originalSymbolIdx) hmtxData = append(hmtxData, hm...) offsets = append(offsets, pos) symbolPos := utf.symbolPosition[originalSymbolIdx] symbolLen := utf.symbolPosition[originalSymbolIdx+1] - symbolPos data := symbolData[symbolPos : symbolPos+symbolLen] var up int if symbolLen > 0 { up = unpackUint16(data[0:2]) } if symbolLen > 2 && (up&(1<<15)) != 0 { posInSymbol := 10 flags := symbolContinue nComponentElements := 0 for (flags & symbolContinue) != 0 { nComponentElements++ up = unpackUint16(data[posInSymbol : posInSymbol+2]) flags = up up = unpackUint16(data[posInSymbol+2 : posInSymbol+4]) symbolIdx := up if _, OK := utf.symbolData[originalSymbolIdx]; !OK { utf.symbolData[originalSymbolIdx] = make(map[string][]int) } if _, OK := utf.symbolData[originalSymbolIdx]["compSymbols"]; !OK { utf.symbolData[originalSymbolIdx]["compSymbols"] = make([]int, 0) } utf.symbolData[originalSymbolIdx]["compSymbols"] = append(utf.symbolData[originalSymbolIdx]["compSymbols"], symbolIdx) data = utf.insertUint16(data, posInSymbol+2, symbolArray[symbolIdx]) posInSymbol += 4 if (flags & symbolWords) != 0 { posInSymbol += 4 } else { posInSymbol += 2 } if (flags & symbolScale) != 0 { posInSymbol += 2 } else if (flags & symbolAllScale) != 0 { posInSymbol += 4 } else if (flags & symbol2x2) != 0 { posInSymbol += 8 } } } glyfData = append(glyfData, data...) pos += symbolLen if pos%4 != 0 { padding := 4 - (pos % 4) glyfData = append(glyfData, make([]byte, padding)...) pos += padding } } offsets = append(offsets, pos) utf.setOutTable("glyf", glyfData) utf.setOutTable("hmtx", hmtxData) locaData := make([]byte, 0) if ((pos + 1) >> 1) > 0xFFFF { LocaFormat = 1 for _, offset := range offsets { locaData = append(locaData, packUint32(offset)...) } } else { LocaFormat = 0 for _, offset := range offsets { locaData = append(locaData, packUint16(offset/2)...) } } utf.setOutTable("loca", locaData) headData := utf.getTableData("head") headData = utf.insertUint16(headData, 50, LocaFormat) utf.setOutTable("head", headData) hheaData := utf.getTableData("hhea") hheaData = utf.insertUint16(hheaData, 34, metricsCount) utf.setOutTable("hhea", hheaData) maxp := utf.getTableData("maxp") maxp = utf.insertUint16(maxp, 4, numSymbols) utf.setOutTable("maxp", maxp) os2Data := utf.getTableData("OS/2") utf.setOutTable("OS/2", os2Data) return utf.assembleTables() } func (utf *utf8FontFile) getSymbols(originalSymbolIdx int, start *int, symbolSet map[int]int, SymbolsCollection map[int]int, SymbolsCollectionKeys []int) (*int, map[int]int, map[int]int, []int) { symbolPos := utf.symbolPosition[originalSymbolIdx] symbolSize := utf.symbolPosition[originalSymbolIdx+1] - symbolPos if symbolSize == 0 { return start, symbolSet, SymbolsCollection, SymbolsCollectionKeys } utf.seek(*start + symbolPos) lineCount := utf.readInt16() if lineCount < 0 { utf.skip(8) flags := symbolContinue for flags&symbolContinue != 0 { flags = utf.readUint16() symbolIndex := utf.readUint16() if _, OK := symbolSet[symbolIndex]; !OK { symbolSet[symbolIndex] = len(SymbolsCollection) SymbolsCollection[symbolIndex] = 1 SymbolsCollectionKeys = append(SymbolsCollectionKeys, symbolIndex) } oldPosition, _ := utf.fileReader.seek(0, 1) _, _, _, SymbolsCollectionKeys = utf.getSymbols(symbolIndex, start, symbolSet, SymbolsCollection, SymbolsCollectionKeys) utf.seek(int(oldPosition)) if flags&symbolWords != 0 { utf.skip(4) } else { utf.skip(2) } if flags&symbolScale != 0 { utf.skip(2) } else if flags&symbolAllScale != 0 { utf.skip(4) } else if flags&symbol2x2 != 0 { utf.skip(8) } } } return start, symbolSet, SymbolsCollection, SymbolsCollectionKeys } func (utf *utf8FontFile) parseHMTXTable(numberOfHMetrics, numSymbols int, symbolToChar map[int][]int, scale float64) { var widths int start := utf.SeekTable("hmtx") arrayWidths := 0 var arr []int utf.CharWidths = make([]int, 256*256) charCount := 0 arr = unpackUint16Array(utf.getRange(start, numberOfHMetrics*4)) for symbol := 0; symbol < numberOfHMetrics; symbol++ { arrayWidths = arr[(symbol*2)+1] if _, OK := symbolToChar[symbol]; OK || symbol == 0 { if arrayWidths >= (1 << 15) { arrayWidths = 0 } if symbol == 0 { utf.DefaultWidth = scale * float64(arrayWidths) continue } for _, char := range symbolToChar[symbol] { if char != 0 && char != 65535 { widths = int(math.Round(scale * float64(arrayWidths))) if widths == 0 { widths = 65535 } if char < 196608 { utf.CharWidths[char] = widths charCount++ } } } } } diff := numSymbols - numberOfHMetrics for pos := 0; pos < diff; pos++ { symbol := pos + numberOfHMetrics if _, OK := symbolToChar[symbol]; OK { for _, char := range symbolToChar[symbol] { if char != 0 && char != 65535 { widths = int(math.Round(scale * float64(arrayWidths))) if widths == 0 { widths = 65535 } if char < 196608 { utf.CharWidths[char] = widths charCount++ } } } } } utf.CharWidths[0] = charCount } func (utf *utf8FontFile) getMetrics(metricCount, gid int) []byte { start := utf.SeekTable("hmtx") var metrics []byte if gid < metricCount { utf.seek(start + (gid * 4)) metrics = utf.fileReader.Read(4) } else { utf.seek(start + ((metricCount - 1) * 4)) metrics = utf.fileReader.Read(2) utf.seek(start + (metricCount * 2) + (gid * 2)) metrics = append(metrics, utf.fileReader.Read(2)...) } return metrics } func (utf *utf8FontFile) parseLOCATable(format, numSymbols int) { start := utf.SeekTable("loca") utf.symbolPosition = make([]int, 0) if format == 0 { data := utf.getRange(start, (numSymbols*2)+2) arr := unpackUint16Array(data) for n := 0; n <= numSymbols; n++ { utf.symbolPosition = append(utf.symbolPosition, arr[n+1]*2) } } else if format == 1 { data := utf.getRange(start, (numSymbols*4)+4) arr := unpackUint32Array(data) for n := 0; n <= numSymbols; n++ { utf.symbolPosition = append(utf.symbolPosition, arr[n+1]) } } else { fmt.Printf("Unknown loca table format %d\n", format) return } } func (utf *utf8FontFile) generateSCCSDictionaries(runeCmapPosition int, symbolCharDictionary map[int][]int, charSymbolDictionary map[int]int) { maxRune := 0 utf.seek(runeCmapPosition + 2) size := utf.readUint16() rim := runeCmapPosition + size utf.skip(2) segmentSize := utf.readUint16() / 2 utf.skip(6) completers := make([]int, 0) for i := 0; i < segmentSize; i++ { completers = append(completers, utf.readUint16()) } utf.skip(2) beginners := make([]int, 0) for i := 0; i < segmentSize; i++ { beginners = append(beginners, utf.readUint16()) } sizes := make([]int, 0) for i := 0; i < segmentSize; i++ { sizes = append(sizes, int(utf.readInt16())) } readerPositionStart := utf.fileReader.readerPosition positions := make([]int, 0) for i := 0; i < segmentSize; i++ { positions = append(positions, utf.readUint16()) } var symbol int for n := 0; n < segmentSize; n++ { completePosition := completers[n] + 1 for char := beginners[n]; char < completePosition; char++ { if positions[n] == 0 { symbol = (char + sizes[n]) & 0xFFFF } else { position := (char-beginners[n])*2 + positions[n] position = int(readerPositionStart) + 2*n + position if position >= rim { symbol = 0 } else { symbol = utf.getUint16(position) if symbol != 0 { symbol = (symbol + sizes[n]) & 0xFFFF } } } charSymbolDictionary[char] = symbol if char < 196608 { maxRune = max(char, maxRune) } symbolCharDictionary[symbol] = append(symbolCharDictionary[symbol], char) } } } func max(i, n int) int { if n > i { return n } return i } func (utf *utf8FontFile) assembleTables() []byte { answer := make([]byte, 0) tablesCount := len(utf.outTablesData) findSize := 1 writer := 0 for findSize*2 <= tablesCount { findSize = findSize * 2 writer = writer + 1 } findSize = findSize * 16 rOffset := tablesCount*16 - findSize answer = append(answer, packHeader(0x00010000, tablesCount, findSize, writer, rOffset)...) tables := utf.outTablesData tablesNames := keySortStrings(tables) offset := 12 + tablesCount*16 begin := 0 for _, name := range tablesNames { if name == "head" { begin = offset } answer = append(answer, []byte(name)...) checksum := utf.generateChecksum(tables[name]) answer = append(answer, pack2Uint16(checksum[0], checksum[1])...) answer = append(answer, pack2Uint32(offset, len(tables[name]))...) paddedLength := (len(tables[name]) + 3) &^ 3 offset = offset + paddedLength } for _, key := range tablesNames { data := append([]byte{}, tables[key]...) data = append(data, []byte{0, 0, 0}...) answer = append(answer, data[:(len(data)&^3)]...) } checksum := utf.generateChecksum([]byte(answer)) checksum = utf.calcInt32([]int{0xB1B0, 0xAFBA}, checksum) answer = utf.splice(answer, (begin + 8), pack2Uint16(checksum[0], checksum[1])) return answer } func unpackUint16Array(data []byte) []int { answer := make([]int, 1) r := bytes.NewReader(data) bs := make([]byte, 2) var e error var c int c, e = r.Read(bs) for e == nil && c > 0 { answer = append(answer, int(binary.BigEndian.Uint16(bs))) c, e = r.Read(bs) } return answer } func unpackUint32Array(data []byte) []int { answer := make([]int, 1) r := bytes.NewReader(data) bs := make([]byte, 4) var e error var c int c, e = r.Read(bs) for e == nil && c > 0 { answer = append(answer, int(binary.BigEndian.Uint32(bs))) c, e = r.Read(bs) } return answer } func unpackUint16(data []byte) int { return int(binary.BigEndian.Uint16(data)) } func packHeader(N uint32, n1, n2, n3, n4 int) []byte { answer := make([]byte, 0) bs4 := make([]byte, 4) binary.BigEndian.PutUint32(bs4, N) answer = append(answer, bs4...) bs := make([]byte, 2) binary.BigEndian.PutUint16(bs, uint16(n1)) answer = append(answer, bs...) binary.BigEndian.PutUint16(bs, uint16(n2)) answer = append(answer, bs...) binary.BigEndian.PutUint16(bs, uint16(n3)) answer = append(answer, bs...) binary.BigEndian.PutUint16(bs, uint16(n4)) answer = append(answer, bs...) return answer } func pack2Uint16(n1, n2 int) []byte { answer := make([]byte, 0) bs := make([]byte, 2) binary.BigEndian.PutUint16(bs, uint16(n1)) answer = append(answer, bs...) binary.BigEndian.PutUint16(bs, uint16(n2)) answer = append(answer, bs...) return answer } func pack2Uint32(n1, n2 int) []byte { answer := make([]byte, 0) bs := make([]byte, 4) binary.BigEndian.PutUint32(bs, uint32(n1)) answer = append(answer, bs...) binary.BigEndian.PutUint32(bs, uint32(n2)) answer = append(answer, bs...) return answer } func packUint32(n1 int) []byte { bs := make([]byte, 4) binary.BigEndian.PutUint32(bs, uint32(n1)) return bs } func packUint16(n1 int) []byte { bs := make([]byte, 2) binary.BigEndian.PutUint16(bs, uint16(n1)) return bs } func keySortStrings(s map[string][]byte) []string { keys := make([]string, len(s)) i := 0 for key := range s { keys[i] = key i++ } sort.Strings(keys) return keys } func keySortInt(s map[int]int) []int { keys := make([]int, len(s)) i := 0 for key := range s { keys[i] = key i++ } sort.Ints(keys) return keys } func keySortArrayRangeMap(s map[int][]int) []int { keys := make([]int, len(s)) i := 0 for key := range s { keys[i] = key i++ } sort.Ints(keys) return keys } // UTF8CutFont is a utility function that generates a TrueType font composed // only of the runes included in cutset. The rune glyphs are copied from This // function is demonstrated in ExampleUTF8CutFont(). func UTF8CutFont(inBuf []byte, cutset string) (outBuf []byte) { f := newUTF8Font(&fileReader{readerPosition: 0, array: inBuf}) runes := map[int]int{} for i, r := range cutset { runes[i] = int(r) } outBuf = f.GenerateCutFont(runes) return }