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 }