codeberg.org/go-pdf/fpdf@v0.11.1/font_afm.go (about) 1 // Copyright ©2025 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 package fpdf 6 7 import ( 8 "bufio" 9 "fmt" 10 "io" 11 "math" 12 "strconv" 13 "strings" 14 ) 15 16 type afmParser struct { 17 s *bufio.Scanner 18 err error 19 line int 20 toks []string 21 22 wdmap map[string]int 23 } 24 25 func newAFMParser(r io.Reader) *afmParser { 26 return &afmParser{ 27 s: bufio.NewScanner(r), 28 wdmap: make(map[string]int), 29 } 30 } 31 32 func (p *afmParser) parse(fnt *fontInfoType) error { 33 loop: 34 for p.scan() { 35 switch p.toks[0] { 36 case "StartFontMetrics": 37 // ok. 38 case "FontName": 39 fnt.FontName = p.readStr(1) 40 case "Weight": 41 weight := strings.ToLower(p.readStr(1)) 42 fnt.Bold = weight == "bold" || weight == "black" 43 case "FontBBox": 44 fnt.Desc.FontBBox.Xmin = p.readFixed(1) 45 fnt.Desc.FontBBox.Ymin = p.readFixed(2) 46 fnt.Desc.FontBBox.Xmax = p.readFixed(3) 47 fnt.Desc.FontBBox.Ymax = p.readFixed(4) 48 case "CapHeight": 49 fnt.Desc.CapHeight = p.readFixed(1) 50 case "Ascender": 51 fnt.Desc.Ascent = p.readFixed(1) 52 case "Descender": 53 fnt.Desc.Descent = p.readFixed(1) 54 case "StdVW": 55 fnt.Desc.StemV = p.readFixed(1) 56 case "UnderlinePosition": 57 fnt.UnderlinePosition = p.readFixed(1) 58 case "UnderlineThickness": 59 fnt.UnderlineThickness = p.readFixed(1) 60 case "ItalicAngle": 61 fnt.Desc.ItalicAngle = p.readFixed(1) 62 case "StartCharMetrics": 63 err := p.parseCharMetrics(fnt, p.readInt(1)) 64 if err != nil { 65 return fmt.Errorf("could not scan AFM CharMetrics section: %w", err) 66 } 67 case "EndFontMetrics": 68 break loop 69 default: 70 // log.Printf("invalid FontMetrics token %q (line=%d)", p.toks[0], p.line) 71 } 72 } 73 74 if p.err != nil { 75 return fmt.Errorf("could not parse AFM file: %w", p.err) 76 } 77 78 if err := p.s.Err(); err != nil { 79 return fmt.Errorf("could not parse AFM file: %w", p.err) 80 } 81 82 return nil 83 } 84 85 func (p *afmParser) scan() bool { 86 if p.err != nil { 87 return false 88 } 89 p.line++ 90 ok := p.s.Scan() 91 p.toks = strings.Fields(strings.TrimSpace(p.s.Text())) 92 if ok && len(p.toks) == 0 { 93 // skip empty lines. 94 return p.scan() 95 } 96 p.err = p.s.Err() 97 return ok 98 } 99 100 func (p *afmParser) parseCharMetrics(fnt *fontInfoType, n int) error { 101 for p.scan() { 102 switch p.toks[0] { 103 case "EndCharMetrics": 104 return nil 105 case "Comment": 106 // ignore. 107 case "C": 108 err := p.parseCharMetric(fnt) 109 if err != nil { 110 return fmt.Errorf("could not parse CharMetric entry: %w", err) 111 } 112 case "CH", 113 "WX", "W0X", "W1X", 114 "WY", "W0Y", "W1Y", 115 "W", "W0", "W1", 116 "VV", 117 "N", 118 "B", 119 "L": 120 // ignore. 121 default: 122 return fmt.Errorf("invalid CharMetrics token %q", p.toks[0]) 123 } 124 } 125 return p.err 126 } 127 128 func (p *afmParser) parseCharMetric(fnt *fontInfoType) error { 129 type metric struct { 130 Name string 131 Wd int 132 } 133 var ch metric 134 for _, v := range strings.Split(p.s.Text(), ";") { 135 v = strings.TrimSpace(v) 136 if v == "" { 137 continue 138 } 139 p.toks = strings.Fields(v) 140 switch p.toks[0] { 141 case "C": 142 // ignore. 143 case "CH": 144 // ignore. 145 case "WX", "W0X": 146 ch.Wd = p.readFixed(1) 147 case "W1X": 148 // ignore. 149 case "WY", "W0Y": 150 // ignore. 151 case "W1Y": 152 // ignore. 153 case "W", "W0": 154 // ignore. 155 case "W1": 156 // ignore. 157 case "VV": 158 // ignore. 159 case "N": 160 ch.Name = p.readStr(1) 161 case "B": 162 // ignore. 163 case "L": 164 // ignore. 165 } 166 } 167 p.wdmap[ch.Name] = ch.Wd 168 return p.err 169 } 170 171 func (p *afmParser) readStr(i int) string { 172 if len(p.toks) <= i { 173 return "" 174 } 175 return p.toks[i] 176 } 177 178 func (p *afmParser) readInt(i int) int { 179 if len(p.toks) <= i { 180 return 0 181 } 182 return atoi(p.toks[i]) 183 } 184 185 func (p *afmParser) readFixed(i int) int { 186 if len(p.toks) <= i { 187 return 0 188 } 189 return fixedFrom(p.toks[i]) 190 } 191 192 func fixedFrom(v string) int { 193 v = strings.Replace(v, ",", ".", 1) 194 o, err := parseInt16_16(v) 195 if err != nil { 196 panic(err) 197 } 198 // FIXME(sbinet): keep digits and precision. 199 return int(math.Round(o)) 200 } 201 202 func atoi(s string) int { 203 v, err := strconv.Atoi(s) 204 if err != nil { 205 panic(err) 206 } 207 return v 208 } 209 210 func parseInt16_16(s string) (float64, error) { 211 f, err := strconv.ParseFloat(s, 32) 212 if err != nil { 213 return 0, err 214 } 215 //return int16_16(int32(f * (1 << 16))), nil 216 return f, nil 217 }