unipdf/pdf/model/fonts/ttfparser.go

823 lines
23 KiB
Go
Raw Normal View History

/*
* Copyright (c) 2013 Kurt Jung (Gmail: kurt.w.jung)
*
* 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.
*/
/*
* Copyright (c) 2018 FoxyUtils ehf. to modifications of the original.
* Modifications of the original file are subject to the terms and conditions
* defined in file 'LICENSE.md', which is part of this source code package.
*/
package fonts
// Utility to parse TTF font files
// Version: 1.0
// Date: 2011-06-18
// Author: Olivier PLATHEY
// Port to Go: Kurt Jung, 2013-07-15
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"regexp"
"sort"
"strings"
"github.com/unidoc/unidoc/common"
"github.com/unidoc/unidoc/pdf/core"
"github.com/unidoc/unidoc/pdf/internal/cmap"
2018-08-03 21:15:21 +00:00
"github.com/unidoc/unidoc/pdf/internal/textencoding"
)
// MakeEncoder returns an encoder built from the tables in `rec`.
func (rec *TtfType) MakeEncoder() (*textencoding.SimpleEncoder, error) {
2018-11-29 23:24:40 +02:00
encoding := make(map[textencoding.CharCode]GlyphName)
for code := textencoding.CharCode(0); code <= 256; code++ {
r := rune(code) // TODO(dennwc): make sure this conversion is valid
gid, ok := rec.Chars[r]
if !ok {
continue
}
2018-11-29 23:24:40 +02:00
var glyph GlyphName
2018-10-24 01:29:55 +03:00
if int(gid) >= 0 && int(gid) < len(rec.GlyphNames) {
glyph = rec.GlyphNames[gid]
} else {
2018-11-29 23:24:40 +02:00
// TODO(dennwc): shouldn't this be uniXXX?
glyph = GlyphName(rune(gid))
}
encoding[code] = glyph
}
if len(encoding) == 0 {
common.Log.Debug("WARNING: Zero length TrueType enconding. rec=%s Chars=[% 02x]",
rec, rec.Chars)
}
return textencoding.NewCustomSimpleTextEncoder(encoding, nil)
}
// GID is a glyph index.
type GID = textencoding.GID
2018-11-29 23:24:40 +02:00
// GlyphName is a name of a glyph.
type GlyphName = textencoding.GlyphName
// TtfType describes a TrueType font file.
// http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-chapter08
type TtfType struct {
UnitsPerEm uint16
PostScriptName string
Bold bool
ItalicAngle float64
IsFixedPitch bool
TypoAscender int16
TypoDescender int16
UnderlinePosition int16
UnderlineThickness int16
Xmin, Ymin, Xmax, Ymax int16
CapHeight int16
Widths []uint16
// Chars maps rune values (unicode) to the indexes in GlyphNames. i.e. GlyphNames[Chars[r]] is
// the glyph corresponding to rune r.
Chars map[rune]GID
// GlyphNames is a list of glyphs from the "post" section of the TrueType file.
2018-11-29 23:24:40 +02:00
GlyphNames []GlyphName
}
// MakeToUnicode returns a ToUnicode CMap based on the encoding of `ttf`.
2018-09-21 16:43:10 +10:00
// XXX(peterwilliams97): This currently gives a bad text mapping for creator_test.go but leads to an
// otherwise valid PDF file that Adobe Reader displays without error.
func (ttf *TtfType) MakeToUnicode() *cmap.CMap {
codeToUnicode := make(map[cmap.CharCode]rune)
for code, gid := range ttf.Chars {
glyph := ttf.GlyphNames[gid]
// TODO(dennwc): 'code' is already a rune; do we need this extra lookup?
r, ok := textencoding.GlyphToRune(glyph)
if !ok {
common.Log.Debug("No rune. code=0x%04x glyph=%q", code, glyph)
r = textencoding.MissingCodeRune
}
// TODO(dennwc): implies rune <-> charcode identity?
codeToUnicode[cmap.CharCode(code)] = r
}
return cmap.NewToUnicodeCMap(codeToUnicode)
}
// String returns a human readable representation of `ttf`.
func (ttf *TtfType) String() string {
2018-10-24 01:28:48 +03:00
return fmt.Sprintf("FONT_FILE2{%#q UnitsPerEm=%d Bold=%t ItalicAngle=%f "+
"CapHeight=%d Chars=%d GlyphNames=%d}",
2018-10-24 01:28:48 +03:00
ttf.PostScriptName, ttf.UnitsPerEm, ttf.Bold, ttf.ItalicAngle,
ttf.CapHeight, len(ttf.Chars), len(ttf.GlyphNames))
}
// ttfParser contains some state variables used to parse a TrueType file.
type ttfParser struct {
rec TtfType
f io.ReadSeeker
tables map[string]uint32
numberOfHMetrics uint16
numGlyphs uint16
}
// NewFontFile2FromPdfObject returns a TtfType describing the TrueType font file in PdfObject `obj`.
func NewFontFile2FromPdfObject(obj core.PdfObject) (TtfType, error) {
obj = core.TraceToDirectObject(obj)
streamObj, ok := obj.(*core.PdfObjectStream)
if !ok {
common.Log.Debug("ERROR: FontFile2 must be a stream (%T)", obj)
return TtfType{}, core.ErrTypeError
}
data, err := core.DecodeStream(streamObj)
if err != nil {
return TtfType{}, err
}
2018-07-24 21:32:02 +10:00
// Uncomment these lines to see the contents of the font file. For debugging.
// fmt.Println("===============&&&&===============")
// fmt.Printf("%#q", string(data))
// fmt.Println("===============####===============")
t := ttfParser{f: bytes.NewReader(data)}
return t.Parse()
}
// TtfParse returns a TtfType describing the TrueType font file in disk file `fileStr`.
func TtfParse(fileStr string) (TtfType, error) {
f, err := os.Open(fileStr)
if err != nil {
return TtfType{}, err
}
defer f.Close()
t := ttfParser{f: f}
return t.Parse()
}
// Parse returns a TtfType describing the TrueType font file in io.Reader `t`.f.
func (t *ttfParser) Parse() (TtfType, error) {
version, err := t.ReadStr(4)
if err != nil {
return TtfType{}, err
}
if version == "OTTO" {
return TtfType{}, errors.New("fonts based on PostScript outlines are not supported")
}
if version != "\x00\x01\x00\x00" {
// This is not an error. In the font_test.go example axes.txt we see version "true".
common.Log.Debug("Unrecognized TrueType file format. version=%q", version)
}
numTables := int(t.ReadUShort())
t.Skip(3 * 2) // searchRange, entrySelector, rangeShift
t.tables = make(map[string]uint32)
var tag string
for j := 0; j < numTables; j++ {
tag, err = t.ReadStr(4)
if err != nil {
return TtfType{}, err
}
t.Skip(4) // checkSum
offset := t.ReadULong()
t.Skip(4) // length
t.tables[tag] = offset
}
common.Log.Trace(describeTables(t.tables))
if err = t.ParseComponents(); err != nil {
return TtfType{}, err
}
return t.rec, nil
}
// describeTables returns a string describing `tables`, the tables in a TrueType font file.
func describeTables(tables map[string]uint32) string {
var tags []string
for tag := range tables {
tags = append(tags, tag)
}
sort.Slice(tags, func(i, j int) bool { return tables[tags[i]] < tables[tags[j]] })
parts := []string{fmt.Sprintf("TrueType tables: %d", len(tables))}
for _, tag := range tags {
parts = append(parts, fmt.Sprintf("\t%q %5d", tag, tables[tag]))
}
return strings.Join(parts, "\n")
}
// ParseComponents parses the tables in a TrueType font file/
// The standard TrueType tables are
// "head"
// "hhea"
// "loca"
// "maxp"
// "cvt "
// "prep"
// "glyf"
// "hmtx"
// "fpgm"
// "gasp"
func (t *ttfParser) ParseComponents() error {
// Mandatory tables.
if err := t.ParseHead(); err != nil {
return err
}
if err := t.ParseHhea(); err != nil {
return err
}
if err := t.ParseMaxp(); err != nil {
return err
}
if err := t.ParseHmtx(); err != nil {
return err
}
// Optional tables.
if _, ok := t.tables["name"]; ok {
if err := t.ParseName(); err != nil {
return err
}
}
if _, ok := t.tables["OS/2"]; ok {
if err := t.ParseOS2(); err != nil {
return err
}
}
if _, ok := t.tables["post"]; ok {
if err := t.ParsePost(); err != nil {
return err
}
}
if _, ok := t.tables["cmap"]; ok {
if err := t.ParseCmap(); err != nil {
return err
}
}
return nil
}
func (t *ttfParser) ParseHead() error {
if err := t.Seek("head"); err != nil {
return err
}
t.Skip(3 * 4) // version, fontRevision, checkSumAdjustment
magicNumber := t.ReadULong()
if magicNumber != 0x5F0F3CF5 {
return fmt.Errorf("incorrect magic number")
}
t.Skip(2) // flags
t.rec.UnitsPerEm = t.ReadUShort()
t.Skip(2 * 8) // created, modified
t.rec.Xmin = t.ReadShort()
t.rec.Ymin = t.ReadShort()
t.rec.Xmax = t.ReadShort()
t.rec.Ymax = t.ReadShort()
return nil
}
func (t *ttfParser) ParseHhea() error {
if err := t.Seek("hhea"); err != nil {
return err
}
t.Skip(4 + 15*2)
t.numberOfHMetrics = t.ReadUShort()
return nil
}
func (t *ttfParser) ParseMaxp() error {
if err := t.Seek("maxp"); err != nil {
return err
}
t.Skip(4)
t.numGlyphs = t.ReadUShort()
return nil
}
func (t *ttfParser) ParseHmtx() error {
if err := t.Seek("hmtx"); err != nil {
return err
}
t.rec.Widths = make([]uint16, 0, 8)
for j := uint16(0); j < t.numberOfHMetrics; j++ {
t.rec.Widths = append(t.rec.Widths, t.ReadUShort())
t.Skip(2) // lsb
}
if t.numberOfHMetrics < t.numGlyphs {
lastWidth := t.rec.Widths[t.numberOfHMetrics-1]
for j := t.numberOfHMetrics; j < t.numGlyphs; j++ {
t.rec.Widths = append(t.rec.Widths, lastWidth)
}
}
return nil
}
// parseCmapSubtable31 parses information from an (3,1) subtable (Windows Unicode).
func (t *ttfParser) parseCmapSubtable31(offset31 int64) error {
startCount := make([]rune, 0, 8)
endCount := make([]rune, 0, 8)
idDelta := make([]int16, 0, 8)
idRangeOffset := make([]uint16, 0, 8)
t.rec.Chars = make(map[rune]GID)
t.f.Seek(int64(t.tables["cmap"])+offset31, os.SEEK_SET)
format := t.ReadUShort()
if format != 4 {
return fmt.Errorf("unexpected subtable format: %d", format)
}
t.Skip(2 * 2) // length, language
segCount := int(t.ReadUShort() / 2)
t.Skip(3 * 2) // searchRange, entrySelector, rangeShift
for j := 0; j < segCount; j++ {
endCount = append(endCount, rune(t.ReadUShort()))
}
t.Skip(2) // reservedPad
for j := 0; j < segCount; j++ {
startCount = append(startCount, rune(t.ReadUShort()))
}
for j := 0; j < segCount; j++ {
idDelta = append(idDelta, t.ReadShort())
}
offset, _ := t.f.Seek(int64(0), os.SEEK_CUR)
for j := 0; j < segCount; j++ {
idRangeOffset = append(idRangeOffset, t.ReadUShort())
}
for j := 0; j < segCount; j++ {
c1 := startCount[j]
c2 := endCount[j]
d := idDelta[j]
ro := idRangeOffset[j]
if ro > 0 {
t.f.Seek(offset+2*int64(j)+int64(ro), os.SEEK_SET)
}
for c := c1; c <= c2; c++ {
if c == 0xFFFF {
break
}
var gid int32
if ro > 0 {
gid = int32(t.ReadUShort())
if gid > 0 {
gid += int32(d)
}
} else {
gid = int32(c) + int32(d)
}
if gid >= 65536 {
gid -= 65536
}
if gid > 0 {
t.rec.Chars[c] = GID(gid)
}
}
}
return nil
}
// parseCmapSubtable10 parses information from an (1,0) subtable (symbol).
func (t *ttfParser) parseCmapSubtable10(offset10 int64) error {
if t.rec.Chars == nil {
t.rec.Chars = make(map[rune]GID)
}
t.f.Seek(int64(t.tables["cmap"])+offset10, os.SEEK_SET)
var length, language uint32
format := t.ReadUShort()
if format < 8 {
length = uint32(t.ReadUShort())
language = uint32(t.ReadUShort())
} else {
t.ReadUShort()
length = t.ReadULong()
language = t.ReadULong()
}
2018-08-14 21:28:57 +10:00
common.Log.Trace("parseCmapSubtable10: format=%d length=%d language=%d",
format, length, language)
if format != 0 {
return errors.New("unsupported cmap subtable format")
}
dataStr, err := t.ReadStr(256)
if err != nil {
return err
}
data := []byte(dataStr)
for code, gid := range data {
t.rec.Chars[rune(code)] = GID(gid)
if gid != 0 {
fmt.Printf("\t0x%02x ➞ 0x%02x=%c\n", code, gid, rune(gid))
}
}
return nil
}
// ParseCmap parses the cmap table in a TrueType font.
func (t *ttfParser) ParseCmap() error {
var offset int64
if err := t.Seek("cmap"); err != nil {
return err
}
2018-08-14 21:28:57 +10:00
common.Log.Trace("ParseCmap")
2018-07-24 21:32:02 +10:00
t.ReadUShort() // version is ignored.
numTables := int(t.ReadUShort())
offset10 := int64(0)
offset31 := int64(0)
for j := 0; j < numTables; j++ {
platformID := t.ReadUShort()
encodingID := t.ReadUShort()
offset = int64(t.ReadULong())
if platformID == 3 && encodingID == 1 {
// (3,1) subtable. Windows Unicode.
offset31 = offset
} else if platformID == 1 && encodingID == 0 {
offset10 = offset
}
}
// Latin font support based on (3,1) table encoding.
if offset31 != 0 {
if err := t.parseCmapSubtable31(offset31); err != nil {
return err
}
}
// Many non-Latin fonts (including asian fonts) use subtable (1,0).
if offset10 != 0 {
if err := t.parseCmapVersion(offset10); err != nil {
return err
}
}
if offset31 == 0 && offset10 == 0 {
common.Log.Debug("ttfParser.ParseCmap. No 31 or 10 table.")
}
return nil
}
func (t *ttfParser) parseCmapVersion(offset int64) error {
common.Log.Trace("parseCmapVersion: offset=%d", offset)
if t.rec.Chars == nil {
t.rec.Chars = make(map[rune]GID)
}
t.f.Seek(int64(t.tables["cmap"])+offset, os.SEEK_SET)
var length, language uint32
format := t.ReadUShort()
if format < 8 {
length = uint32(t.ReadUShort())
language = uint32(t.ReadUShort())
} else {
t.ReadUShort()
length = t.ReadULong()
language = t.ReadULong()
}
common.Log.Debug("parseCmapVersion: format=%d length=%d language=%d",
format, length, language)
switch format {
case 0:
return t.parseCmapFormat0()
case 6:
return t.parseCmapFormat6()
case 12:
return t.parseCmapFormat12()
default:
common.Log.Debug("ERROR: Unsupported cmap format=%d", format)
2018-09-21 16:43:10 +10:00
return nil // XXX(peterwilliams97): Can't return an error here if creator_test.go is to pass.
}
}
func (t *ttfParser) parseCmapFormat0() error {
dataStr, err := t.ReadStr(256)
if err != nil {
return err
}
data := []byte(dataStr)
common.Log.Trace("parseCmapFormat0: %s\ndataStr=%+q\ndata=[% 02x]", t.rec.String(), dataStr, data)
for code, glyphId := range data {
t.rec.Chars[rune(code)] = GID(glyphId)
}
return nil
}
func (t *ttfParser) parseCmapFormat6() error {
firstCode := int(t.ReadUShort())
entryCount := int(t.ReadUShort())
common.Log.Trace("parseCmapFormat6: %s firstCode=%d entryCount=%d",
t.rec.String(), firstCode, entryCount)
for i := 0; i < entryCount; i++ {
glyphId := GID(t.ReadUShort())
t.rec.Chars[rune(i+firstCode)] = glyphId
}
return nil
}
func (t *ttfParser) parseCmapFormat12() error {
numGroups := t.ReadULong()
common.Log.Trace("parseCmapFormat12: %s numGroups=%d", t.rec.String(), numGroups)
for i := uint32(0); i < numGroups; i++ {
firstCode := t.ReadULong()
endCode := t.ReadULong()
startGlyph := t.ReadULong()
if firstCode > 0x0010FFFF || (0xD800 <= firstCode && firstCode <= 0xDFFF) {
return errors.New("invalid characters codes")
}
if endCode < firstCode || endCode > 0x0010FFFF || (0xD800 <= endCode && endCode <= 0xDFFF) {
return errors.New("invalid characters codes")
}
for j := uint32(0); j <= endCode-firstCode; j++ {
glyphId := startGlyph + j
// if glyphId >= numGlyphs {
// common.Log.Debug("ERROR: Format 12 cmap contains an invalid glyph index")
// break
// }
if firstCode+j > 0x10FFFF {
common.Log.Debug("Format 12 cmap contains character beyond UCS-4")
}
t.rec.Chars[rune(i+firstCode)] = GID(glyphId)
}
}
return nil
}
// ParseName parses the "name" table.
func (t *ttfParser) ParseName() error {
if err := t.Seek("name"); err != nil {
return err
}
tableOffset, _ := t.f.Seek(0, os.SEEK_CUR)
t.rec.PostScriptName = ""
t.Skip(2) // format
count := t.ReadUShort()
stringOffset := t.ReadUShort()
for j := uint16(0); j < count && t.rec.PostScriptName == ""; j++ {
t.Skip(3 * 2) // platformID, encodingID, languageID
nameID := t.ReadUShort()
length := t.ReadUShort()
offset := t.ReadUShort()
if nameID == 6 {
// PostScript name
t.f.Seek(int64(tableOffset)+int64(stringOffset)+int64(offset), os.SEEK_SET)
s, err := t.ReadStr(int(length))
if err != nil {
return err
}
s = strings.Replace(s, "\x00", "", -1)
re, err := regexp.Compile("[(){}<> /%[\\]]")
if err != nil {
return err
}
t.rec.PostScriptName = re.ReplaceAllString(s, "")
}
}
if t.rec.PostScriptName == "" {
common.Log.Debug("ParseName: The name PostScript was not found.")
}
return nil
}
func (t *ttfParser) ParseOS2() error {
if err := t.Seek("OS/2"); err != nil {
return err
}
version := t.ReadUShort()
2018-10-24 01:28:48 +03:00
t.Skip(4 * 2) // xAvgCharWidth, usWeightClass, usWidthClass
t.Skip(11*2 + 10 + 4*4 + 4)
fsSelection := t.ReadUShort()
t.rec.Bold = (fsSelection & 32) != 0
t.Skip(2 * 2) // usFirstCharIndex, usLastCharIndex
t.rec.TypoAscender = t.ReadShort()
t.rec.TypoDescender = t.ReadShort()
if version >= 2 {
t.Skip(3*2 + 2*4 + 2)
t.rec.CapHeight = t.ReadShort()
} else {
t.rec.CapHeight = 0
}
return nil
}
// ParsePost reads the "post" section in a TrueType font table and sets t.rec.GlyphNames.
func (t *ttfParser) ParsePost() error {
if err := t.Seek("post"); err != nil {
return err
}
formatType := t.Read32Fixed()
t.rec.ItalicAngle = t.Read32Fixed()
t.rec.UnderlinePosition = t.ReadShort()
t.rec.UnderlineThickness = t.ReadShort()
t.rec.IsFixedPitch = t.ReadULong() != 0
2018-07-24 21:32:02 +10:00
t.ReadULong() // minMemType42 ignored.
t.ReadULong() // maxMemType42 ignored.
t.ReadULong() // mimMemType1 ignored.
t.ReadULong() // maxMemType1 ignored.
common.Log.Trace("ParsePost: formatType=%f", formatType)
switch formatType {
case 1.0: // This font file contains the standard Macintosh TrueTyp 258 glyphs.
t.rec.GlyphNames = macGlyphNames
case 2.0:
numGlyphs := int(t.ReadUShort())
glyphNameIndex := make([]int, numGlyphs)
2018-11-29 23:24:40 +02:00
t.rec.GlyphNames = make([]GlyphName, numGlyphs)
maxIndex := -1
for i := 0; i < numGlyphs; i++ {
index := int(t.ReadUShort())
glyphNameIndex[i] = index
// Index numbers between 0x7fff and 0xffff are reserved for future use
if index <= 0x7fff && index > maxIndex {
maxIndex = index
}
}
2018-11-29 23:24:40 +02:00
var nameArray []GlyphName
if maxIndex >= len(macGlyphNames) {
2018-11-29 23:24:40 +02:00
nameArray = make([]GlyphName, maxIndex-len(macGlyphNames)+1)
for i := 0; i < maxIndex-len(macGlyphNames)+1; i++ {
numberOfChars := int(t.ReadByte())
names, err := t.ReadStr(numberOfChars)
if err != nil {
return err
}
2018-11-29 23:24:40 +02:00
nameArray[i] = GlyphName(names)
}
}
for i := 0; i < numGlyphs; i++ {
index := glyphNameIndex[i]
if index < len(macGlyphNames) {
t.rec.GlyphNames[i] = macGlyphNames[index]
} else if index >= len(macGlyphNames) && index <= 32767 {
t.rec.GlyphNames[i] = nameArray[index-len(macGlyphNames)]
} else {
t.rec.GlyphNames[i] = ".undefined"
}
}
case 2.5:
2018-07-24 21:32:02 +10:00
glyphNameIndex := make([]int, t.numGlyphs)
for i := 0; i < len(glyphNameIndex); i++ {
offset := int(t.ReadSByte())
glyphNameIndex[i] = i + 1 + offset
}
2018-11-29 23:24:40 +02:00
t.rec.GlyphNames = make([]GlyphName, len(glyphNameIndex))
for i := 0; i < len(t.rec.GlyphNames); i++ {
name := macGlyphNames[glyphNameIndex[i]]
t.rec.GlyphNames[i] = name
}
case 3.0:
2018-07-24 21:32:02 +10:00
// no PostScript information is provided.
common.Log.Debug("No PostScript name information is provided for the font.")
default:
common.Log.Debug("ERROR: Unknown formatType=%f", formatType)
}
return nil
}
// The 258 standard mac glyph names used in 'post' format 1 and 2.
2018-11-29 23:24:40 +02:00
var macGlyphNames = []GlyphName{
".notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl",
"numbersign", "dollar", "percent", "ampersand", "quotesingle",
"parenleft", "parenright", "asterisk", "plus", "comma", "hyphen",
"period", "slash", "zero", "one", "two", "three", "four", "five",
"six", "seven", "eight", "nine", "colon", "semicolon", "less",
"equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F",
"G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S",
"T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash",
"bracketright", "asciicircum", "underscore", "grave", "a", "b",
"c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft",
"bar", "braceright", "asciitilde", "Adieresis", "Aring",
"Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis", "aacute",
"agrave", "acircumflex", "adieresis", "atilde", "aring",
"ccedilla", "eacute", "egrave", "ecircumflex", "edieresis",
"iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute",
"ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave",
"ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling",
"section", "bullet", "paragraph", "germandbls", "registered",
"copyright", "trademark", "acute", "dieresis", "notequal", "AE",
"Oslash", "infinity", "plusminus", "lessequal", "greaterequal",
"yen", "mu", "partialdiff", "summation", "product", "pi",
"integral", "ordfeminine", "ordmasculine", "Omega", "ae", "oslash",
"questiondown", "exclamdown", "logicalnot", "radical", "florin",
"approxequal", "Delta", "guillemotleft", "guillemotright",
"ellipsis", "nonbreakingspace", "Agrave", "Atilde", "Otilde", "OE",
"oe", "endash", "emdash", "quotedblleft", "quotedblright",
"quoteleft", "quoteright", "divide", "lozenge", "ydieresis",
"Ydieresis", "fraction", "currency", "guilsinglleft",
"guilsinglright", "fi", "fl", "daggerdbl", "periodcentered",
"quotesinglbase", "quotedblbase", "perthousand", "Acircumflex",
"Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute",
"Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex",
"apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi",
"circumflex", "tilde", "macron", "breve", "dotaccent", "ring",
"cedilla", "hungarumlaut", "ogonek", "caron", "Lslash", "lslash",
"Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth",
"Yacute", "yacute", "Thorn", "thorn", "minus", "multiply",
"onesuperior", "twosuperior", "threesuperior", "onehalf",
"onequarter", "threequarters", "franc", "Gbreve", "gbreve",
"Idotaccent", "Scedilla", "scedilla", "Cacute", "cacute", "Ccaron",
"ccaron", "dcroat",
}
// Seek moves the file pointer to the table named `tag`.
func (t *ttfParser) Seek(tag string) error {
ofs, ok := t.tables[tag]
if !ok {
return fmt.Errorf("table not found: %s", tag)
}
t.f.Seek(int64(ofs), os.SEEK_SET)
return nil
}
2018-07-24 21:32:02 +10:00
// Skip moves the file point n bytes forward.
func (t *ttfParser) Skip(n int) {
t.f.Seek(int64(n), os.SEEK_CUR)
}
2018-07-24 21:32:02 +10:00
// ReadStr reads `length` bytes from the file and returns them as a string, or an error if there was
// a problem.
func (t *ttfParser) ReadStr(length int) (string, error) {
buf := make([]byte, length)
n, err := t.f.Read(buf)
if err != nil {
return "", err
} else if n != length {
return "", fmt.Errorf("unable to read %d bytes", length)
}
return string(buf), nil
}
2018-07-24 21:32:02 +10:00
// ReadByte reads a byte and returns it as unsigned.
func (t *ttfParser) ReadByte() (val uint8) {
binary.Read(t.f, binary.BigEndian, &val)
return val
}
2018-07-24 21:32:02 +10:00
// ReadSByte reads a byte and returns it as signed.
func (t *ttfParser) ReadSByte() (val int8) {
binary.Read(t.f, binary.BigEndian, &val)
return val
}
2018-07-24 21:32:02 +10:00
// ReadUShort reads 2 bytes and returns them as a big endian unsigned 16 bit integer.
func (t *ttfParser) ReadUShort() (val uint16) {
binary.Read(t.f, binary.BigEndian, &val)
return val
}
2018-07-24 21:32:02 +10:00
// ReadShort reads 2 bytes and returns them as a big endian signed 16 bit integer.
func (t *ttfParser) ReadShort() (val int16) {
binary.Read(t.f, binary.BigEndian, &val)
return val
}
2018-07-24 21:32:02 +10:00
// ReadULong reads 4 bytes and returns them as a big endian unsigned 32 bit integer.
func (t *ttfParser) ReadULong() (val uint32) {
binary.Read(t.f, binary.BigEndian, &val)
return val
}
// Read32Fixed reads 4 bytes and returns them as a float, the first 2 bytes for the whole number and
2018-07-24 21:32:02 +10:00
// the second 2 bytes for the fraction.
func (t *ttfParser) Read32Fixed() float64 {
2018-10-24 01:30:44 +03:00
whole := float64(t.ReadShort())
frac := float64(t.ReadUShort()) / 65536.0
return whole + frac
}