summaryrefslogtreecommitdiff
path: root/utf8fontfile.go
diff options
context:
space:
mode:
Diffstat (limited to 'utf8fontfile.go')
-rw-r--r--utf8fontfile.go1142
1 files changed, 1142 insertions, 0 deletions
diff --git a/utf8fontfile.go b/utf8fontfile.go
new file mode 100644
index 0000000..964e919
--- /dev/null
+++ b/utf8fontfile.go
@@ -0,0 +1,1142 @@
+/*
+ * 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<</Registry (Adobe)\n/Ordering (UCS)\n/Supplement 0\n>> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000> <FFFF>\nendcodespacerange\n1 beginbfrange\n<0000> <FFFF> <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 {
+ 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 {
+ weightType := 0
+ 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
+}
+
+//GenerateСutFont fill utf8FontFile from .utf file, only with runes from usedRunes
+func (utf *utf8FontFile) GenerateСutFont(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) {
+ start := utf.SeekTable("hmtx")
+ arrayWidths := 0
+ widths := 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++
+ }
+ }
+ }
+ }
+ }
+ data := utf.getRange(start+numberOfHMetrics*4, numSymbols*2)
+ arr = unpackUint16Array(data)
+ 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())
+ }
+ symbol := 0
+ 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 := 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
+}