github.com/phpdave11/gofpdf@v1.4.2/font.go (about)

     1  /*
     2   * Copyright (c) 2013 Kurt Jung (Gmail: kurt.w.jung)
     3   *
     4   * Permission to use, copy, modify, and distribute this software for any
     5   * purpose with or without fee is hereby granted, provided that the above
     6   * copyright notice and this permission notice appear in all copies.
     7   *
     8   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     9   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    10   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    11   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    12   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    13   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    14   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    15   */
    16  
    17  package gofpdf
    18  
    19  // Utility to generate font definition files
    20  
    21  // Version: 1.2
    22  // Date:    2011-06-18
    23  // Author:  Olivier PLATHEY
    24  // Port to Go: Kurt Jung, 2013-07-15
    25  
    26  import (
    27  	"bufio"
    28  	"compress/zlib"
    29  	"encoding/binary"
    30  	"encoding/json"
    31  	"fmt"
    32  	"io"
    33  	"io/ioutil"
    34  	"os"
    35  	"path/filepath"
    36  	"strconv"
    37  	"strings"
    38  )
    39  
    40  func baseNoExt(fileStr string) string {
    41  	str := filepath.Base(fileStr)
    42  	extLen := len(filepath.Ext(str))
    43  	if extLen > 0 {
    44  		str = str[:len(str)-extLen]
    45  	}
    46  	return str
    47  }
    48  
    49  func loadMap(encodingFileStr string) (encList encListType, err error) {
    50  	// printf("Encoding file string [%s]\n", encodingFileStr)
    51  	var f *os.File
    52  	// f, err = os.Open(encodingFilepath(encodingFileStr))
    53  	f, err = os.Open(encodingFileStr)
    54  	if err == nil {
    55  		defer f.Close()
    56  		for j := range encList {
    57  			encList[j].uv = -1
    58  			encList[j].name = ".notdef"
    59  		}
    60  		scanner := bufio.NewScanner(f)
    61  		var enc encType
    62  		var pos int
    63  		for scanner.Scan() {
    64  			// "!3F U+003F question"
    65  			_, err = fmt.Sscanf(scanner.Text(), "!%x U+%x %s", &pos, &enc.uv, &enc.name)
    66  			if err == nil {
    67  				if pos < 256 {
    68  					encList[pos] = enc
    69  				} else {
    70  					err = fmt.Errorf("map position 0x%2X exceeds 0xFF", pos)
    71  					return
    72  				}
    73  			} else {
    74  				return
    75  			}
    76  		}
    77  		if err = scanner.Err(); err != nil {
    78  			return
    79  		}
    80  	}
    81  	return
    82  }
    83  
    84  // getInfoFromTrueType returns information from a TrueType font
    85  func getInfoFromTrueType(fileStr string, msgWriter io.Writer, embed bool, encList encListType) (info fontInfoType, err error) {
    86  	info.Widths = make([]int, 256)
    87  	var ttf TtfType
    88  	ttf, err = TtfParse(fileStr)
    89  	if err != nil {
    90  		return
    91  	}
    92  	if embed {
    93  		if !ttf.Embeddable {
    94  			err = fmt.Errorf("font license does not allow embedding")
    95  			return
    96  		}
    97  		info.Data, err = ioutil.ReadFile(fileStr)
    98  		if err != nil {
    99  			return
   100  		}
   101  		info.OriginalSize = len(info.Data)
   102  	}
   103  	k := 1000.0 / float64(ttf.UnitsPerEm)
   104  	info.FontName = ttf.PostScriptName
   105  	info.Bold = ttf.Bold
   106  	info.Desc.ItalicAngle = int(ttf.ItalicAngle)
   107  	info.IsFixedPitch = ttf.IsFixedPitch
   108  	info.Desc.Ascent = round(k * float64(ttf.TypoAscender))
   109  	info.Desc.Descent = round(k * float64(ttf.TypoDescender))
   110  	info.UnderlineThickness = round(k * float64(ttf.UnderlineThickness))
   111  	info.UnderlinePosition = round(k * float64(ttf.UnderlinePosition))
   112  	info.Desc.FontBBox = fontBoxType{
   113  		round(k * float64(ttf.Xmin)),
   114  		round(k * float64(ttf.Ymin)),
   115  		round(k * float64(ttf.Xmax)),
   116  		round(k * float64(ttf.Ymax)),
   117  	}
   118  	// printf("FontBBox\n")
   119  	// dump(info.Desc.FontBBox)
   120  	info.Desc.CapHeight = round(k * float64(ttf.CapHeight))
   121  	info.Desc.MissingWidth = round(k * float64(ttf.Widths[0]))
   122  	var wd int
   123  	for j := 0; j < len(info.Widths); j++ {
   124  		wd = info.Desc.MissingWidth
   125  		if encList[j].name != ".notdef" {
   126  			uv := encList[j].uv
   127  			pos, ok := ttf.Chars[uint16(uv)]
   128  			if ok {
   129  				wd = round(k * float64(ttf.Widths[pos]))
   130  			} else {
   131  				fmt.Fprintf(msgWriter, "Character %s is missing\n", encList[j].name)
   132  			}
   133  		}
   134  		info.Widths[j] = wd
   135  	}
   136  	// printf("getInfoFromTrueType/FontBBox\n")
   137  	// dump(info.Desc.FontBBox)
   138  	return
   139  }
   140  
   141  type segmentType struct {
   142  	marker uint8
   143  	tp     uint8
   144  	size   uint32
   145  	data   []byte
   146  }
   147  
   148  func segmentRead(r io.Reader) (s segmentType, err error) {
   149  	if err = binary.Read(r, binary.LittleEndian, &s.marker); err != nil {
   150  		return
   151  	}
   152  	if s.marker != 128 {
   153  		err = fmt.Errorf("font file is not a valid binary Type1")
   154  		return
   155  	}
   156  	if err = binary.Read(r, binary.LittleEndian, &s.tp); err != nil {
   157  		return
   158  	}
   159  	if err = binary.Read(r, binary.LittleEndian, &s.size); err != nil {
   160  		return
   161  	}
   162  	s.data = make([]byte, s.size)
   163  	_, err = r.Read(s.data)
   164  	return
   165  }
   166  
   167  // -rw-r--r-- 1 root root  9532 2010-04-22 11:27 /usr/share/fonts/type1/mathml/Symbol.afm
   168  // -rw-r--r-- 1 root root 37744 2010-04-22 11:27 /usr/share/fonts/type1/mathml/Symbol.pfb
   169  
   170  // getInfoFromType1 return information from a Type1 font
   171  func getInfoFromType1(fileStr string, msgWriter io.Writer, embed bool, encList encListType) (info fontInfoType, err error) {
   172  	info.Widths = make([]int, 256)
   173  	if embed {
   174  		var f *os.File
   175  		f, err = os.Open(fileStr)
   176  		if err != nil {
   177  			return
   178  		}
   179  		defer f.Close()
   180  		// Read first segment
   181  		var s1, s2 segmentType
   182  		s1, err = segmentRead(f)
   183  		if err != nil {
   184  			return
   185  		}
   186  		s2, err = segmentRead(f)
   187  		if err != nil {
   188  			return
   189  		}
   190  		info.Data = s1.data
   191  		info.Data = append(info.Data, s2.data...)
   192  		info.Size1 = s1.size
   193  		info.Size2 = s2.size
   194  	}
   195  	afmFileStr := fileStr[0:len(fileStr)-3] + "afm"
   196  	size, ok := fileSize(afmFileStr)
   197  	if !ok {
   198  		err = fmt.Errorf("font file (ATM) %s not found", afmFileStr)
   199  		return
   200  	} else if size == 0 {
   201  		err = fmt.Errorf("font file (AFM) %s empty or not readable", afmFileStr)
   202  		return
   203  	}
   204  	var f *os.File
   205  	f, err = os.Open(afmFileStr)
   206  	if err != nil {
   207  		return
   208  	}
   209  	defer f.Close()
   210  	scanner := bufio.NewScanner(f)
   211  	var fields []string
   212  	var wd int
   213  	var wt, name string
   214  	wdMap := make(map[string]int)
   215  	for scanner.Scan() {
   216  		fields = strings.Fields(strings.TrimSpace(scanner.Text()))
   217  		// Comment Generated by FontForge 20080203
   218  		// FontName Symbol
   219  		// C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
   220  		if len(fields) >= 2 {
   221  			switch fields[0] {
   222  			case "C":
   223  				if wd, err = strconv.Atoi(fields[4]); err == nil {
   224  					name = fields[7]
   225  					wdMap[name] = wd
   226  				}
   227  			case "FontName":
   228  				info.FontName = fields[1]
   229  			case "Weight":
   230  				wt = strings.ToLower(fields[1])
   231  			case "ItalicAngle":
   232  				info.Desc.ItalicAngle, err = strconv.Atoi(fields[1])
   233  			case "Ascender":
   234  				info.Desc.Ascent, err = strconv.Atoi(fields[1])
   235  			case "Descender":
   236  				info.Desc.Descent, err = strconv.Atoi(fields[1])
   237  			case "UnderlineThickness":
   238  				info.UnderlineThickness, err = strconv.Atoi(fields[1])
   239  			case "UnderlinePosition":
   240  				info.UnderlinePosition, err = strconv.Atoi(fields[1])
   241  			case "IsFixedPitch":
   242  				info.IsFixedPitch = fields[1] == "true"
   243  			case "FontBBox":
   244  				if info.Desc.FontBBox.Xmin, err = strconv.Atoi(fields[1]); err == nil {
   245  					if info.Desc.FontBBox.Ymin, err = strconv.Atoi(fields[2]); err == nil {
   246  						if info.Desc.FontBBox.Xmax, err = strconv.Atoi(fields[3]); err == nil {
   247  							info.Desc.FontBBox.Ymax, err = strconv.Atoi(fields[4])
   248  						}
   249  					}
   250  				}
   251  			case "CapHeight":
   252  				info.Desc.CapHeight, err = strconv.Atoi(fields[1])
   253  			case "StdVW":
   254  				info.Desc.StemV, err = strconv.Atoi(fields[1])
   255  			}
   256  		}
   257  		if err != nil {
   258  			return
   259  		}
   260  	}
   261  	if err = scanner.Err(); err != nil {
   262  		return
   263  	}
   264  	if info.FontName == "" {
   265  		err = fmt.Errorf("the field FontName missing in AFM file %s", afmFileStr)
   266  		return
   267  	}
   268  	info.Bold = wt == "bold" || wt == "black"
   269  	var missingWd int
   270  	missingWd, ok = wdMap[".notdef"]
   271  	if ok {
   272  		info.Desc.MissingWidth = missingWd
   273  	}
   274  	for j := 0; j < len(info.Widths); j++ {
   275  		info.Widths[j] = info.Desc.MissingWidth
   276  	}
   277  	for j := 0; j < len(info.Widths); j++ {
   278  		name = encList[j].name
   279  		if name != ".notdef" {
   280  			wd, ok = wdMap[name]
   281  			if ok {
   282  				info.Widths[j] = wd
   283  			} else {
   284  				fmt.Fprintf(msgWriter, "Character %s is missing\n", name)
   285  			}
   286  		}
   287  	}
   288  	// printf("getInfoFromType1/FontBBox\n")
   289  	// dump(info.Desc.FontBBox)
   290  	return
   291  }
   292  
   293  func makeFontDescriptor(info *fontInfoType) {
   294  	if info.Desc.CapHeight == 0 {
   295  		info.Desc.CapHeight = info.Desc.Ascent
   296  	}
   297  	info.Desc.Flags = 1 << 5
   298  	if info.IsFixedPitch {
   299  		info.Desc.Flags |= 1
   300  	}
   301  	if info.Desc.ItalicAngle != 0 {
   302  		info.Desc.Flags |= 1 << 6
   303  	}
   304  	if info.Desc.StemV == 0 {
   305  		if info.Bold {
   306  			info.Desc.StemV = 120
   307  		} else {
   308  			info.Desc.StemV = 70
   309  		}
   310  	}
   311  	// printf("makeFontDescriptor/FontBBox\n")
   312  	// dump(info.Desc.FontBBox)
   313  }
   314  
   315  // makeFontEncoding builds differences from reference encoding
   316  func makeFontEncoding(encList encListType, refEncFileStr string) (diffStr string, err error) {
   317  	var refList encListType
   318  	if refList, err = loadMap(refEncFileStr); err != nil {
   319  		return
   320  	}
   321  	var buf fmtBuffer
   322  	last := 0
   323  	for j := 32; j < 256; j++ {
   324  		if encList[j].name != refList[j].name {
   325  			if j != last+1 {
   326  				buf.printf("%d ", j)
   327  			}
   328  			last = j
   329  			buf.printf("/%s ", encList[j].name)
   330  		}
   331  	}
   332  	diffStr = strings.TrimSpace(buf.String())
   333  	return
   334  }
   335  
   336  func makeDefinitionFile(fileStr, tpStr, encodingFileStr string, embed bool, encList encListType, info fontInfoType) error {
   337  	var err error
   338  	var def fontDefType
   339  	def.Tp = tpStr
   340  	def.Name = info.FontName
   341  	makeFontDescriptor(&info)
   342  	def.Desc = info.Desc
   343  	// printf("makeDefinitionFile/FontBBox\n")
   344  	// dump(def.Desc.FontBBox)
   345  	def.Up = info.UnderlinePosition
   346  	def.Ut = info.UnderlineThickness
   347  	def.Cw = info.Widths
   348  	def.Enc = baseNoExt(encodingFileStr)
   349  	// fmt.Printf("encodingFileStr [%s], def.Enc [%s]\n", encodingFileStr, def.Enc)
   350  	// fmt.Printf("reference [%s]\n", filepath.Join(filepath.Dir(encodingFileStr), "cp1252.map"))
   351  	def.Diff, err = makeFontEncoding(encList, filepath.Join(filepath.Dir(encodingFileStr), "cp1252.map"))
   352  	if err != nil {
   353  		return err
   354  	}
   355  	def.File = info.File
   356  	def.Size1 = int(info.Size1)
   357  	def.Size2 = int(info.Size2)
   358  	def.OriginalSize = info.OriginalSize
   359  	// printf("Font definition file [%s]\n", fileStr)
   360  	var buf []byte
   361  	buf, err = json.Marshal(def)
   362  	if err != nil {
   363  		return err
   364  	}
   365  	var f *os.File
   366  	f, err = os.Create(fileStr)
   367  	if err != nil {
   368  		return err
   369  	}
   370  	defer f.Close()
   371  	_, err = f.Write(buf)
   372  	if err != nil {
   373  		return err
   374  	}
   375  	err = f.Close()
   376  	if err != nil {
   377  		return err
   378  	}
   379  
   380  	return err
   381  }
   382  
   383  // MakeFont generates a font definition file in JSON format. A definition file
   384  // of this type is required to use non-core fonts in the PDF documents that
   385  // gofpdf generates. See the makefont utility in the gofpdf package for a
   386  // command line interface to this function.
   387  //
   388  // fontFileStr is the name of the TrueType file (extension .ttf), OpenType file
   389  // (extension .otf) or binary Type1 file (extension .pfb) from which to
   390  // generate a definition file. If an OpenType file is specified, it must be one
   391  // that is based on TrueType outlines, not PostScript outlines; this cannot be
   392  // determined from the file extension alone. If a Type1 file is specified, a
   393  // metric file with the same pathname except with the extension .afm must be
   394  // present.
   395  //
   396  // encodingFileStr is the name of the encoding file that corresponds to the
   397  // font.
   398  //
   399  // dstDirStr is the name of the directory in which to save the definition file
   400  // and, if embed is true, the compressed font file.
   401  //
   402  // msgWriter is the writer that is called to display messages throughout the
   403  // process. Use nil to turn off messages.
   404  //
   405  // embed is true if the font is to be embedded in the PDF files.
   406  func MakeFont(fontFileStr, encodingFileStr, dstDirStr string, msgWriter io.Writer, embed bool) error {
   407  	if msgWriter == nil {
   408  		msgWriter = ioutil.Discard
   409  	}
   410  	if !fileExist(fontFileStr) {
   411  		return fmt.Errorf("font file not found: %s", fontFileStr)
   412  	}
   413  	extStr := strings.ToLower(fontFileStr[len(fontFileStr)-3:])
   414  	// printf("Font file extension [%s]\n", extStr)
   415  	var tpStr string
   416  	switch extStr {
   417  	case "ttf":
   418  		fallthrough
   419  	case "otf":
   420  		tpStr = "TrueType"
   421  	case "pfb":
   422  		tpStr = "Type1"
   423  	default:
   424  		return fmt.Errorf("unrecognized font file extension: %s", extStr)
   425  	}
   426  
   427  	var info fontInfoType
   428  	encList, err := loadMap(encodingFileStr)
   429  	if err != nil {
   430  		return err
   431  	}
   432  	// printf("Encoding table\n")
   433  	// dump(encList)
   434  	if tpStr == "TrueType" {
   435  		info, err = getInfoFromTrueType(fontFileStr, msgWriter, embed, encList)
   436  		if err != nil {
   437  			return err
   438  		}
   439  	} else {
   440  		info, err = getInfoFromType1(fontFileStr, msgWriter, embed, encList)
   441  		if err != nil {
   442  			return err
   443  		}
   444  	}
   445  	baseStr := baseNoExt(fontFileStr)
   446  	// fmt.Printf("Base [%s]\n", baseStr)
   447  	if embed {
   448  		var f *os.File
   449  		info.File = baseStr + ".z"
   450  		zFileStr := filepath.Join(dstDirStr, info.File)
   451  		f, err = os.Create(zFileStr)
   452  		if err != nil {
   453  			return err
   454  		}
   455  		defer f.Close()
   456  		cmp := zlib.NewWriter(f)
   457  		_, err = cmp.Write(info.Data)
   458  		if err != nil {
   459  			return err
   460  		}
   461  		err = cmp.Close()
   462  		if err != nil {
   463  			return err
   464  		}
   465  		fmt.Fprintf(msgWriter, "Font file compressed: %s\n", zFileStr)
   466  	}
   467  	defFileStr := filepath.Join(dstDirStr, baseStr+".json")
   468  	err = makeDefinitionFile(defFileStr, tpStr, encodingFileStr, embed, encList, info)
   469  	if err != nil {
   470  		return err
   471  	}
   472  	fmt.Fprintf(msgWriter, "Font definition file successfully generated: %s\n", defFileStr)
   473  	return nil
   474  }