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 }