github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/model/fonts/afms/export_metrics.go (about) 1 // +build unidev 2 3 // Parse character metrics from an AFM file to convert into a static go code declaration. 4 5 package main 6 7 import ( 8 "bufio" 9 "errors" 10 "fmt" 11 "os" 12 "sort" 13 "strconv" 14 "strings" 15 16 "flag" 17 18 pdfcommon "github.com/unidoc/unidoc/common" 19 "github.com/unidoc/unidoc/pdf/model/fonts" 20 ) 21 22 func main() { 23 filepath := flag.String("file", "", "AFM input file") 24 method := flag.String("method", "charmetrics", "charmetrics/charcodes/glyph-to-charcode") 25 26 flag.Parse() 27 28 if len(*filepath) == 0 { 29 fmt.Println("Please specify an input file. Run with -h to get options.") 30 return 31 } 32 33 var err error 34 switch *method { 35 case "charmetrics": 36 err = runCharmetricsOnFile(*filepath) 37 case "charcodes": 38 err = runCharcodeToGlyphRetrievalOnFile(*filepath) 39 case "glyph-to-charcode": 40 err = runGlyphToCharcodeRetrievalOnFile(*filepath) 41 } 42 43 if err != nil { 44 fmt.Printf("Error: %v\n", err) 45 os.Exit(1) 46 } 47 48 // --charmetrics to get char metric data. 49 // --charcodes to get charcode to glyph data 50 51 } 52 53 // Generate a glyph to charmetrics (width and height) map. 54 func runCharmetricsOnFile(path string) error { 55 metrics, err := GetCharmetricsFromAfmFile(path) 56 if err != nil { 57 return err 58 } 59 60 keys := []string{} 61 for key := range metrics { 62 keys = append(keys, key) 63 } 64 65 sort.Strings(keys) 66 fmt.Printf("var xxfontCharMetrics map[string]CharMetrics = map[string]CharMetrics{\n") 67 for _, key := range keys { 68 metric := metrics[key] 69 fmt.Printf("\t\"%s\":\t{GlyphName:\"%s\", Wx:%f, Wy:%f},\n", key, metric.GlyphName, metric.Wx, metric.Wy) 70 } 71 fmt.Printf("}\n") 72 return nil 73 } 74 75 func runCharcodeToGlyphRetrievalOnFile(afmpath string) error { 76 charcodeToGlyphMap, err := GetCharcodeToGlyphEncodingFromAfmFile(afmpath) 77 if err != nil { 78 return err 79 } 80 81 keys := []int{} 82 for key := range charcodeToGlyphMap { 83 keys = append(keys, int(key)) 84 } 85 sort.Ints(keys) 86 87 fmt.Printf("var xxfontCharcodeToGlyphMap map[byte]string = map[byte]string{\n") 88 for _, key := range keys { 89 fmt.Printf("\t%d: \"%s\",\n", key, charcodeToGlyphMap[byte(key)]) 90 } 91 fmt.Printf("}\n") 92 93 return nil 94 } 95 96 func runGlyphToCharcodeRetrievalOnFile(afmpath string) error { 97 charcodeToGlyphMap, err := GetCharcodeToGlyphEncodingFromAfmFile(afmpath) 98 if err != nil { 99 return err 100 } 101 102 keys := []int{} 103 for key := range charcodeToGlyphMap { 104 keys = append(keys, int(key)) 105 } 106 sort.Ints(keys) 107 108 fmt.Printf("var xxfontGlyphToCharcodeMap map[string]byte = map[string]byte ={\n") 109 for _, key := range keys { 110 fmt.Printf("\t\"%s\":\t%d,\n", charcodeToGlyphMap[byte(key)], key) 111 } 112 fmt.Printf("}\n") 113 114 return nil 115 } 116 117 func GetCharmetricsFromAfmFile(filename string) (map[string]fonts.CharMetrics, error) { 118 glyphMetricsMap := map[string]fonts.CharMetrics{} 119 120 f, err := os.Open(filename) 121 if err != nil { 122 return nil, err 123 } 124 defer f.Close() 125 126 readingCharMetrics := false 127 128 scanner := bufio.NewScanner(f) 129 for scanner.Scan() { 130 line := scanner.Text() 131 132 parts := strings.Split(line, " ") 133 if len(parts) < 1 { 134 continue 135 } 136 if !readingCharMetrics && parts[0] == "StartCharMetrics" { 137 readingCharMetrics = true 138 continue 139 } 140 if readingCharMetrics && parts[0] == "EndCharMetrics" { 141 break 142 } 143 if !readingCharMetrics { 144 continue 145 } 146 if parts[0] != "C" { 147 continue 148 } 149 150 parts = strings.Split(line, ";") 151 metrics := fonts.CharMetrics{} 152 metrics.GlyphName = "" 153 for _, part := range parts { 154 cmd := strings.TrimSpace(part) 155 if len(cmd) == 0 { 156 continue 157 } 158 args := strings.Split(cmd, " ") 159 if len(args) < 1 { 160 continue 161 } 162 163 switch args[0] { 164 case "N": 165 if len(args) != 2 { 166 pdfcommon.Log.Debug("Failed C line: ", line) 167 return nil, errors.New("Invalid C line") 168 } 169 metrics.GlyphName = strings.TrimSpace(args[1]) 170 case "WX": 171 if len(args) != 2 { 172 pdfcommon.Log.Debug("WX: Invalid number of args != 1 (%s)\n", line) 173 return nil, errors.New("Invalid range") 174 } 175 wx, err := strconv.ParseFloat(args[1], 64) 176 if err != nil { 177 return nil, err 178 } 179 metrics.Wx = wx 180 case "WY": 181 if len(args) != 2 { 182 pdfcommon.Log.Debug("WY: Invalid number of args != 1 (%s)\n", line) 183 return nil, errors.New("Invalid range") 184 } 185 wy, err := strconv.ParseFloat(args[1], 64) 186 if err != nil { 187 return nil, err 188 } 189 metrics.Wy = wy 190 case "W": 191 if len(args) != 2 { 192 pdfcommon.Log.Debug("W: Invalid number of args != 1 (%s)\n", line) 193 return nil, errors.New("Invalid range") 194 } 195 w, err := strconv.ParseFloat(args[1], 64) 196 if err != nil { 197 return nil, err 198 } 199 metrics.Wy = w 200 metrics.Wx = w 201 } 202 } 203 204 if len(metrics.GlyphName) > 0 { 205 glyphMetricsMap[metrics.GlyphName] = metrics 206 } 207 } 208 209 if err := scanner.Err(); err != nil { 210 return nil, err 211 } 212 213 return glyphMetricsMap, nil 214 } 215 216 func GetCharcodeToGlyphEncodingFromAfmFile(filename string) (map[byte]string, error) { 217 charcodeToGlypMap := map[byte]string{} 218 219 f, err := os.Open(filename) 220 if err != nil { 221 return nil, err 222 } 223 defer f.Close() 224 225 readingCharMetrics := false 226 227 scanner := bufio.NewScanner(f) 228 for scanner.Scan() { 229 line := scanner.Text() 230 231 parts := strings.Split(line, " ") 232 if len(parts) < 1 { 233 continue 234 } 235 if !readingCharMetrics && parts[0] == "StartCharMetrics" { 236 readingCharMetrics = true 237 continue 238 } 239 if readingCharMetrics && parts[0] == "EndCharMetrics" { 240 break 241 } 242 if !readingCharMetrics { 243 continue 244 } 245 if parts[0] != "C" { 246 continue 247 } 248 249 parts = strings.Split(line, ";") 250 var charcode int64 251 var glyph string 252 253 for _, part := range parts { 254 cmd := strings.TrimSpace(part) 255 if len(cmd) == 0 { 256 continue 257 } 258 args := strings.Split(cmd, " ") 259 if len(args) < 1 { 260 continue 261 } 262 263 switch args[0] { 264 case "C": 265 if len(args) != 2 { 266 pdfcommon.Log.Debug("Failed C line: %s", line) 267 return nil, errors.New("Invalid C line") 268 } 269 charcode, err = strconv.ParseInt(strings.TrimSpace(args[1]), 10, 64) 270 if err != nil { 271 return nil, err 272 } 273 case "N": 274 if len(args) != 2 { 275 pdfcommon.Log.Debug("Failed C line: %s", line) 276 return nil, errors.New("Invalid C line") 277 } 278 279 glyph = strings.TrimSpace(args[1]) 280 if charcode >= 0 && charcode <= 255 { 281 charcodeToGlypMap[byte(charcode)] = glyph 282 } else { 283 fmt.Printf("NOT included: %d -> %s\n", charcode, glyph) 284 } 285 } 286 } 287 288 } 289 290 if err := scanner.Err(); err != nil { 291 return nil, err 292 } 293 294 return charcodeToGlypMap, nil 295 }