Kurt Jungs Auslieferungszustand
This commit is contained in:
474
font.go
Normal file
474
font.go
Normal file
@@ -0,0 +1,474 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
|
||||
// getInfoFromTrueType returns information from a TrueType font
|
||||
func getInfoFromTrueType(fileStr string, msgWriter io.Writer, embed bool, encList encListType) (info fontInfoType, err error) {
|
||||
info.Widths = make([]int, 256)
|
||||
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(r io.Reader) (s segmentType, err error) {
|
||||
if err = binary.Read(r, 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(r, binary.LittleEndian, &s.tp); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Read(r, binary.LittleEndian, &s.size); err != nil {
|
||||
return
|
||||
}
|
||||
s.data = make([]byte, s.size)
|
||||
_, err = r.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
|
||||
|
||||
// getInfoFromType1 return information from a Type1 font
|
||||
func getInfoFromType1(fileStr string, msgWriter io.Writer, embed bool, encList encListType) (info fontInfoType, err error) {
|
||||
info.Widths = make([]int, 256)
|
||||
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("font file (ATM) %s not found", afmFileStr)
|
||||
return
|
||||
} else if size == 0 {
|
||||
err = fmt.Errorf("font file (AFM) %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("the field 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)
|
||||
}
|
||||
|
||||
// makeFontEncoding builds 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) error {
|
||||
var 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 err
|
||||
}
|
||||
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 err
|
||||
}
|
||||
var f *os.File
|
||||
f, err = os.Create(fileStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.Write(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// MakeFont generates 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 file (extension .ttf), OpenType file
|
||||
// (extension .otf) or binary Type1 file (extension .pfb) from which to
|
||||
// generate a definition file. If an OpenType file is specified, it must be one
|
||||
// that is based on TrueType outlines, not PostScript outlines; this cannot be
|
||||
// determined from the file extension alone. If a Type1 file is specified, a
|
||||
// metric file with the same pathname except with the extension .afm must be
|
||||
// present.
|
||||
//
|
||||
// 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) error {
|
||||
if msgWriter == nil {
|
||||
msgWriter = ioutil.Discard
|
||||
}
|
||||
if !fileExist(fontFileStr) {
|
||||
return fmt.Errorf("font file not found: %s", fontFileStr)
|
||||
}
|
||||
extStr := strings.ToLower(fontFileStr[len(fontFileStr)-3:])
|
||||
// printf("Font file extension [%s]\n", extStr)
|
||||
var tpStr string
|
||||
switch extStr {
|
||||
case "ttf":
|
||||
fallthrough
|
||||
case "otf":
|
||||
tpStr = "TrueType"
|
||||
case "pfb":
|
||||
tpStr = "Type1"
|
||||
default:
|
||||
return fmt.Errorf("unrecognized font file extension: %s", extStr)
|
||||
}
|
||||
|
||||
var info fontInfoType
|
||||
encList, err := loadMap(encodingFileStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// printf("Encoding table\n")
|
||||
// dump(encList)
|
||||
if tpStr == "TrueType" {
|
||||
info, err = getInfoFromTrueType(fontFileStr, msgWriter, embed, encList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
info, err = getInfoFromType1(fontFileStr, msgWriter, embed, encList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
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 err
|
||||
}
|
||||
defer f.Close()
|
||||
cmp := zlib.NewWriter(f)
|
||||
_, err = cmp.Write(info.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = cmp.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 err
|
||||
}
|
||||
fmt.Fprintf(msgWriter, "Font definition file successfully generated: %s\n", defFileStr)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user