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  }