From caed6a338466079a637af39db2836b5f4b1771a9 Mon Sep 17 00:00:00 2001 From: Kurt Jung Date: Fri, 2 Aug 2013 14:59:27 -0400 Subject: Initial commit into mercurial --- ttfparser.go | 367 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 367 insertions(+) create mode 100644 ttfparser.go (limited to 'ttfparser.go') 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 +} -- cgit v1.2.1-24-ge1ad