123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- /*
- * 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
- }
|