unipdf/model/internal/fonts/testdata/afms/export_metrics.go

543 lines
13 KiB
Go
Raw Permalink Normal View History

// +build unidev
// Parse character metrics from an AFM file to convert into a static go code declaration.
package main
import (
"bufio"
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"flag"
pdfcommon "github.com/unidoc/unipdf/v3/common"
"github.com/unidoc/unipdf/v3/internal/fonts"
"github.com/unidoc/unipdf/v3/model"
)
func main() {
filepath := flag.String("file", "", "AFM input file")
method := flag.String("method", "charmetrics", "charmetrics/charcodes/glyph-to-charcode")
flag.Parse()
if len(*filepath) == 0 && *method != "descriptor" {
fmt.Println("Please specify an input file. Run with -h to get options.")
return
}
var err error
switch *method {
case "charmetrics":
err = runCharmetricsOnFile(*filepath)
case "charcodes":
err = runCharcodeToGlyphRetrievalOnFile(*filepath)
case "glyph-to-charcode":
err = runGlyphToCharcodeRetrievalOnFile(*filepath)
case "descriptor":
err = runFontDescriptorOnFiles()
}
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
// --charmetrics to get char metric data.
// --charcodes to get charcode to glyph data
}
// Generate a glyph to charmetrics (width and height) map.
func runCharmetricsOnFile(path string) error {
metrics, err := GetCharmetricsFromAfmFile(path)
if err != nil {
return err
}
keys := []string{}
2017-07-31 14:54:14 +00:00
for key := range metrics {
keys = append(keys, key)
}
sort.Strings(keys)
fmt.Printf("var xxfontCharMetrics map[string]CharMetrics = map[string]CharMetrics{\n")
for _, key := range keys {
metric := metrics[key]
fmt.Printf("\t\"%s\":\t{Wx:%f, Wy:%f},\n", key, metric.Wx, metric.Wy)
}
fmt.Printf("}\n")
return nil
}
func runCharcodeToGlyphRetrievalOnFile(afmpath string) error {
charcodeToGlyphMap, err := GetCharcodeToGlyphEncodingFromAfmFile(afmpath)
if err != nil {
return err
}
keys := []int{}
2017-07-31 14:54:14 +00:00
for key := range charcodeToGlyphMap {
keys = append(keys, int(key))
}
sort.Ints(keys)
fmt.Printf("var xxfontCharcodeToGlyphMap map[byte]string = map[byte]string{\n")
for _, key := range keys {
fmt.Printf("\t%d: \"%s\",\n", key, charcodeToGlyphMap[byte(key)])
}
fmt.Printf("}\n")
return nil
}
func runGlyphToCharcodeRetrievalOnFile(afmpath string) error {
charcodeToGlyphMap, err := GetCharcodeToGlyphEncodingFromAfmFile(afmpath)
if err != nil {
return err
}
keys := []int{}
2017-07-31 14:54:14 +00:00
for key := range charcodeToGlyphMap {
keys = append(keys, int(key))
}
sort.Ints(keys)
fmt.Printf("var xxfontGlyphToCharcodeMap map[string]byte = map[string]byte ={\n")
for _, key := range keys {
fmt.Printf("\t\"%s\":\t%d,\n", charcodeToGlyphMap[byte(key)], key)
}
fmt.Printf("}\n")
return nil
}
func GetCharmetricsFromAfmFile(filename string) (map[string]model.CharMetrics, error) {
glyphMetricsMap := map[string]model.CharMetrics{}
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
readingCharMetrics := false
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Split(line, " ")
if len(parts) < 1 {
continue
}
if !readingCharMetrics && parts[0] == "StartCharMetrics" {
readingCharMetrics = true
continue
}
if readingCharMetrics && parts[0] == "EndCharMetrics" {
break
}
if !readingCharMetrics {
continue
}
if parts[0] != "C" {
continue
}
parts = strings.Split(line, ";")
var metrics model.CharMetrics
glyphName := ""
for _, part := range parts {
cmd := strings.TrimSpace(part)
if len(cmd) == 0 {
continue
}
args := strings.Split(cmd, " ")
if len(args) < 1 {
continue
}
switch args[0] {
case "N":
if len(args) != 2 {
pdfcommon.Log.Debug("Failed C line: ", line)
2018-12-08 19:16:52 +02:00
return nil, errors.New("invalid C line")
}
glyphName = strings.TrimSpace(args[1])
case "WX":
if len(args) != 2 {
pdfcommon.Log.Debug("WX: Invalid number of args != 1 (%s)\n", line)
2018-12-08 19:16:52 +02:00
return nil, errors.New("invalid range")
}
wx, err := strconv.ParseFloat(args[1], 64)
if err != nil {
return nil, err
}
metrics.Wx = wx
case "WY":
if len(args) != 2 {
pdfcommon.Log.Debug("WY: Invalid number of args != 1 (%s)\n", line)
2018-12-08 19:16:52 +02:00
return nil, errors.New("invalid range")
}
wy, err := strconv.ParseFloat(args[1], 64)
if err != nil {
return nil, err
}
metrics.Wy = wy
case "W":
if len(args) != 2 {
pdfcommon.Log.Debug("W: Invalid number of args != 1 (%s)\n", line)
2018-12-08 19:16:52 +02:00
return nil, errors.New("invalid range")
}
w, err := strconv.ParseFloat(args[1], 64)
if err != nil {
return nil, err
}
metrics.Wy = w
metrics.Wx = w
}
}
if len(glyphName) > 0 {
glyphMetricsMap[glyphName] = metrics
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return glyphMetricsMap, nil
}
func GetCharcodeToGlyphEncodingFromAfmFile(filename string) (map[byte]string, error) {
charcodeToGlypMap := map[byte]string{}
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
readingCharMetrics := false
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Split(line, " ")
if len(parts) < 1 {
continue
}
if !readingCharMetrics && parts[0] == "StartCharMetrics" {
readingCharMetrics = true
continue
}
if readingCharMetrics && parts[0] == "EndCharMetrics" {
break
}
if !readingCharMetrics {
continue
}
if parts[0] != "C" {
continue
}
parts = strings.Split(line, ";")
var charcode int64
var glyph string
for _, part := range parts {
cmd := strings.TrimSpace(part)
if len(cmd) == 0 {
continue
}
args := strings.Split(cmd, " ")
if len(args) < 1 {
continue
}
switch args[0] {
case "C":
if len(args) != 2 {
pdfcommon.Log.Debug("Failed C line: %s", line)
2018-12-08 19:16:52 +02:00
return nil, errors.New("invalid C line")
}
charcode, err = strconv.ParseInt(strings.TrimSpace(args[1]), 10, 64)
if err != nil {
return nil, err
}
case "N":
if len(args) != 2 {
pdfcommon.Log.Debug("Failed C line: %s", line)
2018-12-08 19:16:52 +02:00
return nil, errors.New("invalid C line")
}
glyph = strings.TrimSpace(args[1])
if charcode >= 0 && charcode <= 255 {
charcodeToGlypMap[byte(charcode)] = glyph
} else {
fmt.Printf("NOT included: %d -> %s\n", charcode, glyph)
}
}
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return charcodeToGlypMap, nil
}
func runFontDescriptorOnFiles() error {
parts := []string{}
for _, name := range standard14Names {
path := filepath.Join(".", fmt.Sprintf("%s.afm", name))
text, err := createFontDescriptor(name, path)
if err != nil {
return err
}
parts = append(parts, text)
}
fmt.Println("\n// =========================================================\n")
fmt.Println(" Standard14Descriptors = map[string]DescriptorLiteral {")
for _, text := range parts {
fmt.Printf("%s,\n", text)
}
fmt.Println("}")
return nil
}
var standard14Names = []string{
"Courier",
"Courier-Bold",
"Courier-BoldOblique",
"Courier-Oblique",
"Helvetica",
"Helvetica-Bold",
"Helvetica-BoldOblique",
"Helvetica-Oblique",
"Times-Roman",
"Times-Bold",
"Times-BoldItalic",
"Times-Italic",
"Symbol",
"ZapfDingbats",
}
// createFontDescriptor creates a literal DescriptorLiteral that can be copied into
// pdf/internal/fonts/const.go.
func createFontDescriptor(name, filename string) (string, error) {
fmt.Println("----------------------------------------------------")
fmt.Printf("createFontDescriptor: name=%#q filename=%q\n", name, filename)
nameVal, err := parseAfmFile(filename)
if err != nil {
return "", err
}
descriptor, err := parseNameVal(nameVal)
if err != nil {
fmt.Printf("ERROR: createFontDescriptor failed. name=%q err=%v\n", name, err)
return "", err
}
return writeDescriptorLiteral(name, descriptor), nil
}
func parseAfmFile(filename string) (map[string]string, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
nameVal := map[string]string{}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
k, v, ok := asKeyValue(line)
if !ok {
continue
}
if k == "StartCharMetrics" {
break
}
nameVal[k] = v
}
if err := scanner.Err(); err != nil {
return nil, err
}
return nameVal, nil
}
// firstWord returns the first word in `line`.
func asKeyValue(line string) (string, string, bool) {
line = strings.Trim(line, "\r\n\t ")
i := strings.IndexByte(line, ' ')
if i < 0 {
return "", "", false
}
return line[:i], line[i+1:], true
}
func parseNameVal(nameVal map[string]string) (fonts.DescriptorLiteral, error) {
fmt.Printf("nameVal=%q\n", nameVal)
descriptor := fonts.DescriptorLiteral{}
descriptor.FontName = nameVal["FontName"]
descriptor.FontFamily = nameVal["FamilyName"]
weight, ok := textWeight[nameVal["Weight"]]
if !ok {
return descriptor, fmt.Errorf("Invalid Weight=%#q. nameVal=%q",
nameVal["Weight"], nameVal)
}
descriptor.FontWeight = weight
symbolic := descriptor.FontFamily == "Symbol" || descriptor.FontFamily == "ZapfDingbats"
descriptor.FontBBox = toBBox(nameVal["FontBBox"])
descriptor.ItalicAngle = toFloat(nameVal, false, "ItalicAngle")
descriptor.CapHeight = toFloat(nameVal, symbolic, "CapHeight")
descriptor.XHeight = toFloat(nameVal, symbolic, "XHeight")
descriptor.Ascent = toFloat(nameVal, symbolic, "Ascender")
descriptor.Descent = toFloat(nameVal, symbolic, "Descender")
descriptor.StemH = toFloat(nameVal, symbolic, "StdHW")
descriptor.StemV = toFloat(nameVal, symbolic, "StdVW")
fixedPitch := toBool(nameVal, "IsFixedPitch")
italic := descriptor.ItalicAngle != 0
descriptor.Flags = getFlags(descriptor.FontName, fixedPitch, symbolic, italic)
return descriptor, nil
}
func getFlags(name string, fixedPitch, symbolic, italic bool) uint {
flags := uint(0)
if fixedPitch {
flags |= fontFlagFixedPitch
}
if symbolic {
flags |= fontFlagSymbolic
} else {
flags |= fontFlagNonsymbolic
}
if italic {
flags |= fontFlagItalic
}
return flags
}
// These consts are copied from model/font.go
// 9.8.2 Font Descriptor Flags (page 283)
const (
fontFlagFixedPitch = 0x00001
fontFlagSerif = 0x00002
fontFlagSymbolic = 0x00004
fontFlagScript = 0x00008
fontFlagNonsymbolic = 0x00020
fontFlagItalic = 0x00040
fontFlagAllCap = 0x10000
fontFlagSmallCap = 0x20000
fontFlagForceBold = 0x40000
)
func toInt(keyVal map[string]string, key string) uint {
text, ok := keyVal[key]
if !ok {
panic(fmt.Sprintf("No %#q key", key))
}
x, err := strconv.Atoi(text)
if err != nil {
panic(err)
}
return uint(x)
}
func toBool(keyVal map[string]string, key string) bool {
text, ok := keyVal[key]
if !ok {
panic(fmt.Sprintf("No %#q key", key))
}
switch text {
case "true":
return true
case "false":
return false
default:
panic(fmt.Sprintf("Bad bool val %q. key=%#q", text, key))
}
}
func toFloat(keyVal map[string]string, optional bool, key string) float64 {
text, ok := keyVal[key]
if !ok {
// Symbol and ZapfDingbats don't have XHeight and key == "CapHeight" entries
if optional {
return 0.0
}
panic(fmt.Sprintf("No %#q key", key))
}
x, err := strconv.ParseFloat(text, 64)
if err != nil {
panic(err)
}
return x
}
func toBBox(text string) [4]float64 {
fmt.Printf("toBBox: text=%q\n", text)
parts := strings.Split(text, " ")
vals := [4]float64{}
for i, p := range parts {
x, err := strconv.ParseFloat(p, 64)
if err != nil {
panic(err)
}
vals[i] = x
}
return vals
}
var textWeight = map[string]fonts.FontWeight{
"Medium": fonts.FontWeightMedium,
"Bold": fonts.FontWeightBold,
"Roman": fonts.FontWeightRoman,
}
var weightVarname = map[fonts.FontWeight]string{
fonts.FontWeightMedium: "FontWeightMedium",
fonts.FontWeightBold: "FontWeightBold",
fonts.FontWeightRoman: "FontWeightRoman",
}
func writeDescriptorLiteral(name string, descriptor fonts.DescriptorLiteral) string {
var b bytes.Buffer
fmt.Fprintf(&b, "%q: DescriptorLiteral {\n", name)
fmt.Fprintf(&b, "\tFontName: %q,\n", descriptor.FontName)
fmt.Fprintf(&b, "\tFontFamily: %q,\n", descriptor.FontFamily)
fmt.Fprintf(&b, "\tFontWeight: %s,\n", weightVarname[descriptor.FontWeight])
fmt.Fprintf(&b, "\tFlags: 0x%04x,\n", descriptor.Flags)
fmt.Fprintf(&b, "\tFontBBox: %#v,\n", descriptor.FontBBox)
fmt.Fprintf(&b, "\tItalicAngle: %g,\n", descriptor.ItalicAngle)
fmt.Fprintf(&b, "\tAscent: %g,\n", descriptor.Ascent)
fmt.Fprintf(&b, "\tDescent: %g,\n", descriptor.Descent)
fmt.Fprintf(&b, "\tCapHeight: %g,\n", descriptor.CapHeight)
fmt.Fprintf(&b, "\tXHeight: %g,\n", descriptor.XHeight)
fmt.Fprintf(&b, "\tStemV: %g,\n", descriptor.StemV)
fmt.Fprintf(&b, "\tStemH: %g,\n", descriptor.StemH)
fmt.Fprintf(&b, "}")
return b.String()
}