375 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			375 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
 * 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
 | 
						|
}
 |