summaryrefslogtreecommitdiff
path: root/ttfparser.go
diff options
context:
space:
mode:
authorKurt Jung <kurt.w.jung@code.google.com>2013-08-02 14:59:27 -0400
committerKurt Jung <kurt.w.jung@code.google.com>2013-08-02 14:59:27 -0400
commitcaed6a338466079a637af39db2836b5f4b1771a9 (patch)
treed23e03cd5965618d723ab453b19c6156371bf42b /ttfparser.go
Initial commit into mercurial
Diffstat (limited to 'ttfparser.go')
-rw-r--r--ttfparser.go367
1 files changed, 367 insertions, 0 deletions
diff --git a/ttfparser.go b/ttfparser.go
new file mode 100644
index 0000000..323f144
--- /dev/null
+++ b/ttfparser.go
@@ -0,0 +1,367 @@
+/*
+ * 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.
+ */
+
+package gofpdf
+
+// 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 (
+ "encoding/binary"
+ "fmt"
+ "os"
+ "regexp"
+ "strings"
+)
+
+type TtfType struct {
+ Embeddable bool
+ UnitsPerEm uint16
+ PostScriptName string
+ Bold bool
+ ItalicAngle int16
+ IsFixedPitch bool
+ TypoAscender int16
+ TypoDescender int16
+ UnderlinePosition int16
+ UnderlineThickness int16
+ Xmin, Ymin, Xmax, Ymax int16
+ CapHeight int16
+ Widths []uint16
+ Chars map[uint16]uint16
+}
+
+type ttfParser struct {
+ rec TtfType
+ f *os.File
+ tables map[string]uint32
+ numberOfHMetrics uint16
+ numGlyphs uint16
+}
+
+// Extract various metrics from a TrueType font file.
+func TtfParse(fileStr string) (TtfRec TtfType, err error) {
+ var t ttfParser
+ t.f, err = os.Open(fileStr)
+ if err != nil {
+ return
+ }
+ version, err := t.ReadStr(4)
+ if err != nil {
+ return
+ }
+ if version == "OTTO" {
+ err = fmt.Errorf("OpenType fonts based on PostScript outlines are not supported")
+ return
+ }
+ if version != "\x00\x01\x00\x00" {
+ err = fmt.Errorf("Unrecognized file format")
+ return
+ }
+ 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
+ }
+ t.Skip(4) // checkSum
+ offset := t.ReadULong()
+ t.Skip(4) // length
+ t.tables[tag] = offset
+ }
+ if err = t.ParseHead(); err != nil {
+ return
+ }
+ if err = t.ParseHhea(); err != nil {
+ return
+ }
+ if err = t.ParseMaxp(); err != nil {
+ return
+ }
+ if err = t.ParseHmtx(); err != nil {
+ return
+ }
+ if err = t.ParseCmap(); err != nil {
+ return
+ }
+ if err = t.ParseName(); err != nil {
+ return
+ }
+ if err = t.ParseOS2(); err != nil {
+ return
+ }
+ if err = t.ParsePost(); err != nil {
+ return
+ }
+ t.f.Close()
+ TtfRec = t.rec
+ return
+}
+
+func (t *ttfParser) ParseHead() (err error) {
+ err = t.Seek("head")
+ t.Skip(3 * 4) // version, fontRevision, checkSumAdjustment
+ magicNumber := t.ReadULong()
+ if magicNumber != 0x5F0F3CF5 {
+ err = fmt.Errorf("Incorrect magic number")
+ return
+ }
+ 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
+}
+
+func (t *ttfParser) ParseHhea() (err error) {
+ err = t.Seek("hhea")
+ if err == nil {
+ t.Skip(4 + 15*2)
+ t.numberOfHMetrics = t.ReadUShort()
+ }
+ return
+}
+
+func (t *ttfParser) ParseMaxp() (err error) {
+ err = t.Seek("maxp")
+ if err == nil {
+ t.Skip(4)
+ t.numGlyphs = t.ReadUShort()
+ }
+ return
+}
+
+func (t *ttfParser) ParseHmtx() (err error) {
+ err = t.Seek("hmtx")
+ if err == nil {
+ 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
+}
+
+func (t *ttfParser) ParseCmap() (err error) {
+ var offset int64
+ if err = t.Seek("cmap"); err != nil {
+ return
+ }
+ t.Skip(2) // version
+ numTables := int(t.ReadUShort())
+ offset31 := int64(0)
+ for j := 0; j < numTables; j++ {
+ platformID := t.ReadUShort()
+ encodingID := t.ReadUShort()
+ offset = int64(t.ReadULong())
+ if platformID == 3 && encodingID == 1 {
+ offset31 = offset
+ }
+ }
+ if offset31 == 0 {
+ err = fmt.Errorf("No Unicode encoding found")
+ return
+ }
+ startCount := make([]uint16, 0, 8)
+ endCount := make([]uint16, 0, 8)
+ idDelta := make([]int16, 0, 8)
+ idRangeOffset := make([]uint16, 0, 8)
+ t.rec.Chars = make(map[uint16]uint16)
+ t.f.Seek(int64(t.tables["cmap"])+offset31, os.SEEK_SET)
+ format := t.ReadUShort()
+ if format != 4 {
+ err = fmt.Errorf("Unexpected subtable format: %d", format)
+ return
+ }
+ 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, t.ReadUShort())
+ }
+ t.Skip(2) // reservedPad
+ for j := 0; j < segCount; j++ {
+ startCount = append(startCount, 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] = uint16(gid)
+ }
+ }
+ }
+ return
+}
+
+func (t *ttfParser) ParseName() (err error) {
+ err = t.Seek("name")
+ if err == nil {
+ 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)
+ var s string
+ s, err = t.ReadStr(int(length))
+ if err != nil {
+ return
+ }
+ s = strings.Replace(s, "\x00", "", -1)
+ var re *regexp.Regexp
+ if re, err = regexp.Compile("[(){}<> /%[\\]]"); err != nil {
+ return
+ }
+ t.rec.PostScriptName = re.ReplaceAllString(s, "")
+ }
+ }
+ if t.rec.PostScriptName == "" {
+ err = fmt.Errorf("PostScript name not found")
+ }
+ }
+ return
+}
+
+func (t *ttfParser) ParseOS2() (err error) {
+ err = t.Seek("OS/2")
+ if err == nil {
+ version := t.ReadUShort()
+ t.Skip(3 * 2) // xAvgCharWidth, usWeightClass, usWidthClass
+ fsType := t.ReadUShort()
+ t.rec.Embeddable = (fsType != 2) && (fsType&0x200) == 0
+ 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
+}
+
+func (t *ttfParser) ParsePost() (err error) {
+ err = t.Seek("post")
+ if err == nil {
+ t.Skip(4) // version
+ t.rec.ItalicAngle = t.ReadShort()
+ t.Skip(2) // Skip decimal part
+ t.rec.UnderlinePosition = t.ReadShort()
+ t.rec.UnderlineThickness = t.ReadShort()
+ t.rec.IsFixedPitch = t.ReadULong() != 0
+ }
+ return
+}
+
+func (t *ttfParser) Seek(tag string) (err error) {
+ ofs, ok := t.tables[tag]
+ if ok {
+ t.f.Seek(int64(ofs), os.SEEK_SET)
+ } else {
+ err = fmt.Errorf("Table not found: %s", tag)
+ }
+ return
+}
+
+func (t *ttfParser) Skip(n int) {
+ t.f.Seek(int64(n), os.SEEK_CUR)
+}
+
+func (t *ttfParser) ReadStr(length int) (str string, err error) {
+ var n int
+ buf := make([]byte, length)
+ n, err = t.f.Read(buf)
+ if err == nil {
+ if n == length {
+ str = string(buf)
+ } else {
+ err = fmt.Errorf("Unable to read %d bytes", length)
+ }
+ }
+ return
+}
+
+func (t *ttfParser) ReadUShort() (val uint16) {
+ binary.Read(t.f, binary.BigEndian, &val)
+ return
+}
+
+func (t *ttfParser) ReadShort() (val int16) {
+ binary.Read(t.f, binary.BigEndian, &val)
+ return
+}
+
+func (t *ttfParser) ReadULong() (val uint32) {
+ binary.Read(t.f, binary.BigEndian, &val)
+ return
+}