codeberg.org/go-pdf/fpdf@v0.11.1/font.go (about)

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