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 --- font.go | 451 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 451 insertions(+) create mode 100644 font.go (limited to 'font.go') 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 +} -- cgit v1.2.1-24-ge1ad