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
17package 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
26import (
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
40func 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
49func 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
85func 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
141type segmentType struct {
142	marker uint8
143	tp     uint8
144	size   uint32
145	data   []byte
146}
147
148func 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
171func 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
293func 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
316func 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
336func 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.
406func 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}
475