codeberg.org/go-pdf/fpdf@v0.11.1/ttfparser.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 parse TTF font files 24 // Version: 1.0 25 // Date: 2011-06-18 26 // Author: Olivier PLATHEY 27 // Port to Go: Kurt Jung, 2013-07-15 28 29 import ( 30 "encoding/binary" 31 "fmt" 32 "io" 33 "os" 34 "regexp" 35 "strings" 36 ) 37 38 // TtfType contains metrics of a TrueType font. 39 type TtfType struct { 40 Embeddable bool 41 UnitsPerEm uint16 42 PostScriptName string 43 Bold bool 44 ItalicAngle int16 45 IsFixedPitch bool 46 TypoAscender int16 47 TypoDescender int16 48 UnderlinePosition int16 49 UnderlineThickness int16 50 Xmin, Ymin, Xmax, Ymax int16 51 CapHeight int16 52 Widths []uint16 53 Chars map[uint16]uint16 54 } 55 56 type ttfParser struct { 57 rec TtfType 58 f *os.File 59 tables map[string]uint32 60 numberOfHMetrics uint16 61 numGlyphs uint16 62 } 63 64 // TtfParse extracts various metrics from a TrueType font file. 65 func TtfParse(fileStr string) (TtfRec TtfType, err error) { 66 var t ttfParser 67 t.f, err = os.Open(fileStr) 68 if err != nil { 69 return 70 } 71 version, err := t.ReadStr(4) 72 if err != nil { 73 return 74 } 75 if version == "OTTO" { 76 err = fmt.Errorf("fonts based on PostScript outlines are not supported") 77 return 78 } 79 if version != "\x00\x01\x00\x00" { 80 err = fmt.Errorf("unrecognized file format") 81 return 82 } 83 numTables := int(t.ReadUShort()) 84 t.Skip(3 * 2) // searchRange, entrySelector, rangeShift 85 t.tables = make(map[string]uint32) 86 var tag string 87 for j := 0; j < numTables; j++ { 88 tag, err = t.ReadStr(4) 89 if err != nil { 90 return 91 } 92 t.Skip(4) // checkSum 93 offset := t.ReadULong() 94 t.Skip(4) // length 95 t.tables[tag] = offset 96 } 97 err = t.ParseComponents() 98 if err != nil { 99 return 100 } 101 t.f.Close() 102 TtfRec = t.rec 103 return 104 } 105 106 func (t *ttfParser) ParseComponents() (err error) { 107 err = t.ParseHead() 108 if err == nil { 109 err = t.ParseHhea() 110 if err == nil { 111 err = t.ParseMaxp() 112 if err == nil { 113 err = t.ParseHmtx() 114 if err == nil { 115 err = t.ParseCmap() 116 if err == nil { 117 err = t.ParseName() 118 if err == nil { 119 err = t.ParseOS2() 120 if err == nil { 121 err = t.ParsePost() 122 } 123 } 124 } 125 } 126 } 127 } 128 } 129 return 130 } 131 132 func (t *ttfParser) ParseHead() (err error) { 133 err = t.Seek("head") 134 t.Skip(3 * 4) // version, fontRevision, checkSumAdjustment 135 magicNumber := t.ReadULong() 136 if magicNumber != 0x5F0F3CF5 { 137 err = fmt.Errorf("incorrect magic number") 138 return 139 } 140 t.Skip(2) // flags 141 t.rec.UnitsPerEm = t.ReadUShort() 142 t.Skip(2 * 8) // created, modified 143 t.rec.Xmin = t.ReadShort() 144 t.rec.Ymin = t.ReadShort() 145 t.rec.Xmax = t.ReadShort() 146 t.rec.Ymax = t.ReadShort() 147 return 148 } 149 150 func (t *ttfParser) ParseHhea() (err error) { 151 err = t.Seek("hhea") 152 if err == nil { 153 t.Skip(4 + 15*2) 154 t.numberOfHMetrics = t.ReadUShort() 155 } 156 return 157 } 158 159 func (t *ttfParser) ParseMaxp() (err error) { 160 err = t.Seek("maxp") 161 if err == nil { 162 t.Skip(4) 163 t.numGlyphs = t.ReadUShort() 164 } 165 return 166 } 167 168 func (t *ttfParser) ParseHmtx() (err error) { 169 err = t.Seek("hmtx") 170 if err == nil { 171 t.rec.Widths = make([]uint16, 0, 8) 172 for j := uint16(0); j < t.numberOfHMetrics; j++ { 173 t.rec.Widths = append(t.rec.Widths, t.ReadUShort()) 174 t.Skip(2) // lsb 175 } 176 if t.numberOfHMetrics < t.numGlyphs { 177 lastWidth := t.rec.Widths[t.numberOfHMetrics-1] 178 for j := t.numberOfHMetrics; j < t.numGlyphs; j++ { 179 t.rec.Widths = append(t.rec.Widths, lastWidth) 180 } 181 } 182 } 183 return 184 } 185 186 func (t *ttfParser) ParseCmap() (err error) { 187 var offset int64 188 if err = t.Seek("cmap"); err != nil { 189 return 190 } 191 t.Skip(2) // version 192 numTables := int(t.ReadUShort()) 193 offset31 := int64(0) 194 for j := 0; j < numTables; j++ { 195 platformID := t.ReadUShort() 196 encodingID := t.ReadUShort() 197 offset = int64(t.ReadULong()) 198 if platformID == 3 && encodingID == 1 { 199 offset31 = offset 200 } 201 } 202 if offset31 == 0 { 203 err = fmt.Errorf("no Unicode encoding found") 204 return 205 } 206 startCount := make([]uint16, 0, 8) 207 endCount := make([]uint16, 0, 8) 208 idDelta := make([]int16, 0, 8) 209 idRangeOffset := make([]uint16, 0, 8) 210 t.rec.Chars = make(map[uint16]uint16) 211 _, err = t.f.Seek(int64(t.tables["cmap"])+offset31, io.SeekStart) 212 if err != nil { 213 err = fmt.Errorf("could not seek to cmap table: %w", err) 214 return 215 } 216 format := t.ReadUShort() 217 if format != 4 { 218 err = fmt.Errorf("unexpected subtable format: %d", format) 219 return 220 } 221 t.Skip(2 * 2) // length, language 222 segCount := int(t.ReadUShort() / 2) 223 t.Skip(3 * 2) // searchRange, entrySelector, rangeShift 224 for j := 0; j < segCount; j++ { 225 endCount = append(endCount, t.ReadUShort()) 226 } 227 t.Skip(2) // reservedPad 228 for j := 0; j < segCount; j++ { 229 startCount = append(startCount, t.ReadUShort()) 230 } 231 for j := 0; j < segCount; j++ { 232 idDelta = append(idDelta, t.ReadShort()) 233 } 234 offset, _ = t.f.Seek(int64(0), io.SeekCurrent) 235 for j := 0; j < segCount; j++ { 236 idRangeOffset = append(idRangeOffset, t.ReadUShort()) 237 } 238 for j := 0; j < segCount; j++ { 239 c1 := startCount[j] 240 c2 := endCount[j] 241 d := idDelta[j] 242 ro := idRangeOffset[j] 243 if ro > 0 { 244 _, err = t.f.Seek(offset+2*int64(j)+int64(ro), io.SeekStart) 245 if err != nil { 246 return fmt.Errorf("could not seek to id range offset: %w", err) 247 } 248 } 249 for c := c1; c <= c2; c++ { 250 if c == 0xFFFF { 251 break 252 } 253 var gid int32 254 if ro > 0 { 255 gid = int32(t.ReadUShort()) 256 if gid > 0 { 257 gid += int32(d) 258 } 259 } else { 260 gid = int32(c) + int32(d) 261 } 262 if gid >= 65536 { 263 gid -= 65536 264 } 265 if gid > 0 { 266 t.rec.Chars[c] = uint16(gid) 267 } 268 } 269 } 270 return 271 } 272 273 func (t *ttfParser) ParseName() (err error) { 274 err = t.Seek("name") 275 if err == nil { 276 tableOffset, _ := t.f.Seek(0, io.SeekCurrent) 277 t.rec.PostScriptName = "" 278 t.Skip(2) // format 279 count := t.ReadUShort() 280 stringOffset := t.ReadUShort() 281 for j := uint16(0); j < count && t.rec.PostScriptName == ""; j++ { 282 t.Skip(3 * 2) // platformID, encodingID, languageID 283 nameID := t.ReadUShort() 284 length := t.ReadUShort() 285 offset := t.ReadUShort() 286 if nameID == 6 { 287 // PostScript name 288 _, err = t.f.Seek(int64(tableOffset)+int64(stringOffset)+int64(offset), io.SeekStart) 289 if err != nil { 290 return 291 } 292 var s string 293 s, err = t.ReadStr(int(length)) 294 if err != nil { 295 return 296 } 297 s = strings.Replace(s, "\x00", "", -1) 298 var re *regexp.Regexp 299 if re, err = regexp.Compile(`[(){}<> /%[\]]`); err != nil { 300 return 301 } 302 t.rec.PostScriptName = re.ReplaceAllString(s, "") 303 } 304 } 305 if t.rec.PostScriptName == "" { 306 err = fmt.Errorf("the name PostScript was not found") 307 } 308 } 309 return 310 } 311 312 func (t *ttfParser) ParseOS2() (err error) { 313 err = t.Seek("OS/2") 314 if err == nil { 315 version := t.ReadUShort() 316 t.Skip(3 * 2) // xAvgCharWidth, usWeightClass, usWidthClass 317 fsType := t.ReadUShort() 318 t.rec.Embeddable = (fsType != 2) && (fsType&0x200) == 0 319 t.Skip(11*2 + 10 + 4*4 + 4) 320 fsSelection := t.ReadUShort() 321 t.rec.Bold = (fsSelection & 32) != 0 322 t.Skip(2 * 2) // usFirstCharIndex, usLastCharIndex 323 t.rec.TypoAscender = t.ReadShort() 324 t.rec.TypoDescender = t.ReadShort() 325 if version >= 2 { 326 t.Skip(3*2 + 2*4 + 2) 327 t.rec.CapHeight = t.ReadShort() 328 } else { 329 t.rec.CapHeight = 0 330 } 331 } 332 return 333 } 334 335 func (t *ttfParser) ParsePost() (err error) { 336 err = t.Seek("post") 337 if err == nil { 338 t.Skip(4) // version 339 t.rec.ItalicAngle = t.ReadShort() 340 t.Skip(2) // Skip decimal part 341 t.rec.UnderlinePosition = t.ReadShort() 342 t.rec.UnderlineThickness = t.ReadShort() 343 t.rec.IsFixedPitch = t.ReadULong() != 0 344 } 345 return 346 } 347 348 func (t *ttfParser) Seek(tag string) (err error) { 349 ofs, ok := t.tables[tag] 350 if !ok { 351 return fmt.Errorf("table not found: %s", tag) 352 } 353 354 _, err = t.f.Seek(int64(ofs), io.SeekStart) 355 if err != nil { 356 return fmt.Errorf("could not seek to table %q: %w", tag, err) 357 } 358 return 359 } 360 361 func (t *ttfParser) Skip(n int) { 362 _, err := t.f.Seek(int64(n), io.SeekCurrent) 363 if err != nil { 364 panic(fmt.Errorf("could not skip %d bytes: %w", n, err)) 365 } 366 } 367 368 func (t *ttfParser) ReadStr(length int) (str string, err error) { 369 var n int 370 buf := make([]byte, length) 371 n, err = t.f.Read(buf) 372 if err == nil { 373 if n == length { 374 str = string(buf) 375 } else { 376 err = fmt.Errorf("unable to read %d bytes", length) 377 } 378 } 379 return 380 } 381 382 func (t *ttfParser) ReadUShort() (val uint16) { 383 err := binary.Read(t.f, binary.BigEndian, &val) 384 if err != nil { 385 panic(fmt.Errorf("could not read u16: %w", err)) 386 } 387 return 388 } 389 390 func (t *ttfParser) ReadShort() (val int16) { 391 err := binary.Read(t.f, binary.BigEndian, &val) 392 if err != nil { 393 panic(fmt.Errorf("could not read i16: %w", err)) 394 } 395 return 396 } 397 398 func (t *ttfParser) ReadULong() (val uint32) { 399 err := binary.Read(t.f, binary.BigEndian, &val) 400 if err != nil { 401 panic(fmt.Errorf("could not read u32: %w", err)) 402 } 403 return 404 }