123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- /*
- * 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"
- )
- // TtfType contains metrics of a TrueType font.
- 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
- }
- // TtfParse extracts 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("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
- }
- err = t.ParseComponents()
- if err != nil {
- return
- }
- t.f.Close()
- TtfRec = t.rec
- return
- }
- func (t *ttfParser) ParseComponents() (err error) {
- err = t.ParseHead()
- if err == nil {
- err = t.ParseHhea()
- if err == nil {
- err = t.ParseMaxp()
- if err == nil {
- err = t.ParseHmtx()
- if err == nil {
- err = t.ParseCmap()
- if err == nil {
- err = t.ParseName()
- if err == nil {
- err = t.ParseOS2()
- if err == nil {
- err = t.ParsePost()
- }
- }
- }
- }
- }
- }
- }
- 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("the name PostScript was 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
- }
|