summaryrefslogtreecommitdiff
path: root/font.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 /font.go
Initial commit into mercurial
Diffstat (limited to 'font.go')
-rw-r--r--font.go451
1 files changed, 451 insertions, 0 deletions
diff --git a/font.go b/font.go
new file mode 100644
index 0000000..f5cf8c1
--- /dev/null
+++ b/font.go
@@ -0,0 +1,451 @@
+/*
+ * 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 generate font definition files
+
+// Version: 1.2
+// Date: 2011-06-18
+// Author: Olivier PLATHEY
+// Port to Go: Kurt Jung, 2013-07-15
+
+import (
+ "bufio"
+ "compress/zlib"
+ "encoding/binary"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+)
+
+func baseNoExt(fileStr string) string {
+ str := filepath.Base(fileStr)
+ extLen := len(filepath.Ext(str))
+ if extLen > 0 {
+ str = str[:len(str)-extLen]
+ }
+ return str
+}
+
+func loadMap(encodingFileStr string) (encList encListType, err error) {
+ // printf("Encoding file string [%s]\n", encodingFileStr)
+ var f *os.File
+ // f, err = os.Open(encodingFilepath(encodingFileStr))
+ f, err = os.Open(encodingFileStr)
+ if err == nil {
+ defer f.Close()
+ for j, _ := range encList {
+ encList[j].uv = -1
+ encList[j].name = ".notdef"
+ }
+ scanner := bufio.NewScanner(f)
+ var enc encType
+ var pos int
+ for scanner.Scan() {
+ // "!3F U+003F question"
+ _, err = fmt.Sscanf(scanner.Text(), "!%x U+%x %s", &pos, &enc.uv, &enc.name)
+ if err == nil {
+ if pos < 256 {
+ encList[pos] = enc
+ } else {
+ err = fmt.Errorf("Map position 0x%2X exceeds 0xFF", pos)
+ return
+ }
+ } else {
+ return
+ }
+ }
+ if err = scanner.Err(); err != nil {
+ return
+ }
+ }
+ return
+}
+
+// Return informations from a TrueType font
+func getInfoFromTrueType(fileStr string, msgWriter io.Writer, embed bool, encList encListType) (info fontInfoType, err error) {
+ var ttf TtfType
+ ttf, err = TtfParse(fileStr)
+ if err != nil {
+ return
+ }
+ if embed {
+ if !ttf.Embeddable {
+ err = fmt.Errorf("Font license does not allow embedding")
+ return
+ }
+ info.Data, err = ioutil.ReadFile(fileStr)
+ if err != nil {
+ return
+ }
+ info.OriginalSize = len(info.Data)
+ }
+ k := 1000.0 / float64(ttf.UnitsPerEm)
+ info.FontName = ttf.PostScriptName
+ info.Bold = ttf.Bold
+ info.Desc.ItalicAngle = int(ttf.ItalicAngle)
+ info.IsFixedPitch = ttf.IsFixedPitch
+ info.Desc.Ascent = round(k * float64(ttf.TypoAscender))
+ info.Desc.Descent = round(k * float64(ttf.TypoDescender))
+ info.UnderlineThickness = round(k * float64(ttf.UnderlineThickness))
+ info.UnderlinePosition = round(k * float64(ttf.UnderlinePosition))
+ info.Desc.FontBBox = fontBoxType{
+ round(k * float64(ttf.Xmin)),
+ round(k * float64(ttf.Ymin)),
+ round(k * float64(ttf.Xmax)),
+ round(k * float64(ttf.Ymax)),
+ }
+ // printf("FontBBox\n")
+ // dump(info.Desc.FontBBox)
+ info.Desc.CapHeight = round(k * float64(ttf.CapHeight))
+ info.Desc.MissingWidth = round(k * float64(ttf.Widths[0]))
+ var wd int
+ for j := 0; j < len(info.Widths); j++ {
+ wd = info.Desc.MissingWidth
+ if encList[j].name != ".notdef" {
+ uv := encList[j].uv
+ pos, ok := ttf.Chars[uint16(uv)]
+ if ok {
+ wd = round(k * float64(ttf.Widths[pos]))
+ } else {
+ fmt.Fprintf(msgWriter, "Character %s is missing\n", encList[j].name)
+ }
+ }
+ info.Widths[j] = wd
+ }
+ // printf("getInfoFromTrueType/FontBBox\n")
+ // dump(info.Desc.FontBBox)
+ return
+}
+
+type segmentType struct {
+ marker uint8
+ tp uint8
+ size uint32
+ data []byte
+}
+
+func segmentRead(f *os.File) (s segmentType, err error) {
+ if err = binary.Read(f, binary.LittleEndian, &s.marker); err != nil {
+ return
+ }
+ if s.marker != 128 {
+ err = fmt.Errorf("Font file is not a valid binary Type1")
+ return
+ }
+ if err = binary.Read(f, binary.LittleEndian, &s.tp); err != nil {
+ return
+ }
+ if err = binary.Read(f, binary.LittleEndian, &s.size); err != nil {
+ return
+ }
+ s.data = make([]byte, s.size)
+ _, err = f.Read(s.data)
+ return
+}
+
+// -rw-r--r-- 1 root root 9532 2010-04-22 11:27 /usr/share/fonts/type1/mathml/Symbol.afm
+// -rw-r--r-- 1 root root 37744 2010-04-22 11:27 /usr/share/fonts/type1/mathml/Symbol.pfb
+
+// Return informations from a Type1 font
+func getInfoFromType1(fileStr string, msgWriter io.Writer, embed bool, encList encListType) (info fontInfoType, err error) {
+ if embed {
+ var f *os.File
+ f, err = os.Open(fileStr)
+ if err != nil {
+ return
+ }
+ defer f.Close()
+ // Read first segment
+ var s1, s2 segmentType
+ s1, err = segmentRead(f)
+ if err != nil {
+ return
+ }
+ s2, err = segmentRead(f)
+ if err != nil {
+ return
+ }
+ info.Data = s1.data
+ info.Data = append(info.Data, s2.data...)
+ info.Size1 = s1.size
+ info.Size2 = s2.size
+ }
+ afmFileStr := fileStr[0:len(fileStr)-3] + "afm"
+ size, ok := fileSize(afmFileStr)
+ if !ok {
+ err = fmt.Errorf("AFM font file %s not found", afmFileStr)
+ return
+ } else if size == 0 {
+ err = fmt.Errorf("AFM font file %s empty or not readable", afmFileStr)
+ return
+ }
+ var f *os.File
+ f, err = os.Open(afmFileStr)
+ if err != nil {
+ return
+ }
+ defer f.Close()
+ scanner := bufio.NewScanner(f)
+ var fields []string
+ var wd int
+ var wt, name string
+ wdMap := make(map[string]int)
+ for scanner.Scan() {
+ fields = strings.Fields(strings.TrimSpace(scanner.Text()))
+ // Comment Generated by FontForge 20080203
+ // FontName Symbol
+ // C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+ if len(fields) >= 2 {
+ switch fields[0] {
+ case "C":
+ if wd, err = strconv.Atoi(fields[4]); err == nil {
+ name = fields[7]
+ wdMap[name] = wd
+ }
+ case "FontName":
+ info.FontName = fields[1]
+ case "Weight":
+ wt = strings.ToLower(fields[1])
+ case "ItalicAngle":
+ info.Desc.ItalicAngle, err = strconv.Atoi(fields[1])
+ case "Ascender":
+ info.Desc.Ascent, err = strconv.Atoi(fields[1])
+ case "Descender":
+ info.Desc.Descent, err = strconv.Atoi(fields[1])
+ case "UnderlineThickness":
+ info.UnderlineThickness, err = strconv.Atoi(fields[1])
+ case "UnderlinePosition":
+ info.UnderlinePosition, err = strconv.Atoi(fields[1])
+ case "IsFixedPitch":
+ info.IsFixedPitch = fields[1] == "true"
+ case "FontBBox":
+ if info.Desc.FontBBox.Xmin, err = strconv.Atoi(fields[1]); err == nil {
+ if info.Desc.FontBBox.Ymin, err = strconv.Atoi(fields[2]); err == nil {
+ if info.Desc.FontBBox.Xmax, err = strconv.Atoi(fields[3]); err == nil {
+ info.Desc.FontBBox.Ymax, err = strconv.Atoi(fields[4])
+ }
+ }
+ }
+ case "CapHeight":
+ info.Desc.CapHeight, err = strconv.Atoi(fields[1])
+ case "StdVW":
+ info.Desc.StemV, err = strconv.Atoi(fields[1])
+ }
+ }
+ if err != nil {
+ return
+ }
+ }
+ if err = scanner.Err(); err != nil {
+ return
+ }
+ if info.FontName == "" {
+ err = fmt.Errorf("FontName missing in AFM file %s", afmFileStr)
+ return
+ }
+ info.Bold = wt == "bold" || wt == "black"
+ var missingWd int
+ missingWd, ok = wdMap[".notdef"]
+ if ok {
+ info.Desc.MissingWidth = missingWd
+ }
+ for j := 0; j < len(info.Widths); j++ {
+ info.Widths[j] = info.Desc.MissingWidth
+ }
+ for j := 0; j < len(info.Widths); j++ {
+ name = encList[j].name
+ if name != ".notdef" {
+ wd, ok = wdMap[name]
+ if ok {
+ info.Widths[j] = wd
+ } else {
+ fmt.Fprintf(msgWriter, "Character %s is missing\n", name)
+ }
+ }
+ }
+ // printf("getInfoFromType1/FontBBox\n")
+ // dump(info.Desc.FontBBox)
+ return
+}
+
+func makeFontDescriptor(info *fontInfoType) {
+ if info.Desc.CapHeight == 0 {
+ info.Desc.CapHeight = info.Desc.Ascent
+ }
+ info.Desc.Flags = 1 << 5
+ if info.IsFixedPitch {
+ info.Desc.Flags |= 1
+ }
+ if info.Desc.ItalicAngle != 0 {
+ info.Desc.Flags |= 1 << 6
+ }
+ if info.Desc.StemV == 0 {
+ if info.Bold {
+ info.Desc.StemV = 120
+ } else {
+ info.Desc.StemV = 70
+ }
+ }
+ // printf("makeFontDescriptor/FontBBox\n")
+ // dump(info.Desc.FontBBox)
+}
+
+// Build differences from reference encoding
+func makeFontEncoding(encList encListType, refEncFileStr string) (diffStr string, err error) {
+ var refList encListType
+ if refList, err = loadMap(refEncFileStr); err != nil {
+ return
+ }
+ var buf fmtBuffer
+ last := 0
+ for j := 32; j < 256; j++ {
+ if encList[j].name != refList[j].name {
+ if j != last+1 {
+ buf.printf("%d ", j)
+ }
+ last = j
+ buf.printf("/%s ", encList[j].name)
+ }
+ }
+ diffStr = strings.TrimSpace(buf.String())
+ return
+}
+
+func makeDefinitionFile(fileStr, tpStr, encodingFileStr string, embed bool, encList encListType, info fontInfoType) (err error) {
+ var def fontDefType
+ def.Tp = tpStr
+ def.Name = info.FontName
+ makeFontDescriptor(&info)
+ def.Desc = info.Desc
+ // printf("makeDefinitionFile/FontBBox\n")
+ // dump(def.Desc.FontBBox)
+ def.Up = info.UnderlinePosition
+ def.Ut = info.UnderlineThickness
+ def.Cw = info.Widths
+ def.Enc = baseNoExt(encodingFileStr)
+ // fmt.Printf("encodingFileStr [%s], def.Enc [%s]\n", encodingFileStr, def.Enc)
+ // fmt.Printf("reference [%s]\n", filepath.Join(filepath.Dir(encodingFileStr), "cp1252.map"))
+ def.Diff, err = makeFontEncoding(encList, filepath.Join(filepath.Dir(encodingFileStr), "cp1252.map"))
+ if err != nil {
+ return
+ }
+ def.File = info.File
+ def.Size1 = int(info.Size1)
+ def.Size2 = int(info.Size2)
+ def.OriginalSize = info.OriginalSize
+ // printf("Font definition file [%s]\n", fileStr)
+ var buf []byte
+ buf, err = json.Marshal(def)
+ if err != nil {
+ return
+ }
+ var f *os.File
+ f, err = os.Create(fileStr)
+ if err != nil {
+ return
+ }
+ defer f.Close()
+ f.Write(buf)
+ return
+}
+
+// Generate a font definition file in JSON format. A definition file of this
+// type is required to use non-core fonts in the PDF documents that gofpdf
+// generates. See the makefont utility in the gofpdf package for a command line
+// interface to this function.
+//
+// fontFileStr is the name of the TrueType (or OpenType based on TrueType) or
+// Type1 file from which to generate a definition file.
+//
+// encodingFileStr is the name of the encoding file that corresponds to the
+// font.
+//
+// dstDirStr is the name of the directory in which to save the definition file
+// and, if embed is true, the compressed font file.
+//
+// msgWriter is the writer that is called to display messages throughout the
+// process. Use nil to turn off messages.
+//
+// embed is true if the font is to be embedded in the PDF files.
+func MakeFont(fontFileStr, encodingFileStr, dstDirStr string, msgWriter io.Writer, embed bool) (err error) {
+ if msgWriter == nil {
+ msgWriter = ioutil.Discard
+ }
+ if !fileExist(fontFileStr) {
+ err = fmt.Errorf("Font file not found: %s", fontFileStr)
+ return
+ }
+ extStr := strings.ToLower(fontFileStr[len(fontFileStr)-3:])
+ // printf("Font file extension [%s]\n", extStr)
+ var tpStr string
+ if extStr == "ttf" || extStr == "otf" {
+ tpStr = "TrueType"
+ } else if extStr == "pfb" {
+ tpStr = "Type1"
+ } else {
+ err = fmt.Errorf("Unrecognized font file extension: %s", extStr)
+ return
+ }
+ var encList encListType
+ var info fontInfoType
+ encList, err = loadMap(encodingFileStr)
+ if err != nil {
+ return
+ }
+ // printf("Encoding table\n")
+ // dump(encList)
+ if tpStr == "TrueType" {
+ info, err = getInfoFromTrueType(fontFileStr, msgWriter, embed, encList)
+ if err != nil {
+ return
+ }
+ } else {
+ info, err = getInfoFromType1(fontFileStr, msgWriter, embed, encList)
+ if err != nil {
+ return
+ }
+ }
+ baseStr := baseNoExt(fontFileStr)
+ // fmt.Printf("Base [%s]\n", baseStr)
+ if embed {
+ var f *os.File
+ info.File = baseStr + ".z"
+ zFileStr := filepath.Join(dstDirStr, info.File)
+ f, err = os.Create(zFileStr)
+ if err != nil {
+ return
+ }
+ defer f.Close()
+ cmp := zlib.NewWriter(f)
+ cmp.Write(info.Data)
+ cmp.Close()
+ fmt.Fprintf(msgWriter, "Font file compressed: %s\n", zFileStr)
+ }
+ defFileStr := filepath.Join(dstDirStr, baseStr+".json")
+ err = makeDefinitionFile(defFileStr, tpStr, encodingFileStr, embed, encList, info)
+ if err != nil {
+ return
+ }
+ fmt.Fprintf(msgWriter, "Font definition file successfully generated: %s\n", defFileStr)
+ return
+}