Source code for PyDvi.Font.AfmParser

####################################################################################################
# 
# PyDvi - A Python Library to Process DVI Stream
# Copyright (C) 2014 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# 
####################################################################################################


"""Reference: Adobe Font Metrics File Format Specification, Version 4.1, 7 October 1998

All measurements in AFM files are given in terms of units equal to 1/1000 of the scale factor (point
size) of the font being used. To compute actual sizes in a document (in points; with 72 points = 1
inch), these amounts should be multiplied by (scale factor of font) / 1000.

"""

####################################################################################################

# __all__ = []

####################################################################################################

import logging

####################################################################################################

_module_logger = logging.getLogger(__name__)

####################################################################################################

[docs]def boolean(x): if x == 'false': return False elif x == 'true': return True else: raise ValueError("Can't convert {} to boolean".format(x))
####################################################################################################
[docs]def hex(x): if x.startswith('<') and x.endswith('>'): return int(x[1:-1], base=16) else: raise ValueError('Wrong hexadecimal number {}'.format(x))
#################################################################################################### global_font_information_keys = { 'MetricsSets': int, 'FontName': str, 'FullName': str, 'FamilyName': str, 'Weight': str, 'FontBBox': (float, float, float, float), 'Version': str, 'Notice': str, 'EncodingScheme': str, 'MappingScheme': int, 'EscChar': int, 'CharacterSet': str, 'Characters': int, 'IsBaseFont': boolean, 'VVector': (float, float), 'IsFixedV': boolean, 'CapHeight': float, 'XHeight': float, 'Ascender': float, 'Descender': float, # ??? 'IsCIDFont': boolean, } direction_keys = { 'UnderlinePosition': float, 'UnderlineThickness': float, 'ItalicAngle': float, 'CharWidth': (float, float), 'IsFixedPitch': boolean, } # if omitted, StartDirection 0 is implied. global_font_information_keys.update(direction_keys) character_metric_keys = { 'C': int, 'CH': hex, 'WX': float, 'W0X': float, 'W1X': float, 'WY': float, 'W0Y': float, 'W1Y': float, 'W': (float, float), 'W0': (float, float), 'W1': (float, float), 'VV': (float, float), 'N': str, 'B': (float, float, float, float), 'L': (str, str), } track_kern_keys = { 'TrackKern': (int, float, float, float, float), } kern_keys = { 'KP': (str, str, float, float), 'KPH': (hex, hex, float, float), 'KPX': (str, str, float), 'KPY': (str, str, float), } composite_keys = { 'CC': (str, int), 'PCC': (str, float, float), } # afm_sections = { # 'FontMetrics': (str, global_font_information_keys, { # 'Direction': (int, direction_keys, None), # 'CharMetrics': (int, character_metric_keys, None), # 'KernData': (int, None, { # 'KernPairs': (int, kern_keys, None), # 'KernPairs0': (int, kern_keys, None), # 'KernPairs1': (int, kern_keys, None), # }), # 'Composites': (int, composite_keys, None), # }), # } afm_sections = { 'FontMetrics': (str, global_font_information_keys), 'Direction': (int, direction_keys), 'CharMetrics': (int, character_metric_keys), 'KernData': (int, None), 'TrackKern': (int, track_kern_keys), 'KernPairs': (int, kern_keys), 'KernPairs0': (int, kern_keys), 'KernPairs1': (int, kern_keys), 'Composites': (int, composite_keys), } section_hiearachy = {'FontMetrics': { 'Direction': None, 'CharMetrics': None, 'KernData': { 'TrackKern': None, 'KernPairs': { 'KernPairs0': None, 'KernPairs1': None, 'Composites': None, }, }, 'Composites': None, }} ####################################################################################################
[docs]class BadAfmFile(NameError): pass
####################################################################################################
[docs]class AfmParser(object): _logger = _module_logger.getChild('AfmParser') ############################################## @staticmethod
[docs] def parse(filename): afm_parser = AfmParser(filename) return afm_parser
############################################## def __init__(self, filename): with open(filename) as f: self._parse(f) ##############################################
[docs] def _parse(self, stream): self._section_stack = [] self._stack = [] self._section = None self._keys = None self._sections = section_hiearachy for line in stream: line = line.strip() # self._logger.info('\n' + line) if not line or line.startswith('Comment'): continue elif line.startswith('Start'): values = self._parse_start(line) elif line.startswith('End'): self._parse_end(line) else: if self._section in ('CharMetrics', 'Composites'): properties = self._parse_key_values_list(self._keys, line) elif self._keys is not None: key, values = self._parse_key_values(self._keys, line) else: raise BadAfmFile("This file doesn't look like an AFM file")
##############################################
[docs] def _get_values(self, types, line): line = line.strip() if isinstance(types, tuple): start = 0 values = [] for data_type in types: stop = line.find(' ', start) if stop == -1: value_string = line[start:] else: value_string = line[start:stop] value = data_type(value_string) values.append(value) start = stop + 1 return values else: return types(line)
##############################################
[docs] def _parse_start(self, line): index = line.find(' ') if index == -1: section = line[5:] else: section = line[5:index] if self._section is None and section != 'FontMetrics': raise BadAfmFile('File start with section {} '.format(section)) elif section not in self._sections: raise BadAfmFile('Wrong section order: {} -> {}'.format(self._section_stack, section)) self._section_stack.append(section) self._stack.append((self._keys, self._sections)) self._section = section data_types, self._keys = afm_sections[section] self._sections = self._sections[section] if index == -1: values = None else: values = self._get_values(data_types, line[index:]) self._logger.info('Enter section {}: {}'.format(section, values)) return values
##############################################
[docs] def _parse_end(self, line): section = line[3:] if self._section == section: if len(self._stack) == 1: self._section = None self._keys = None self._sections = None else: self._section = self._section_stack[-2] self._keys, self._sections = self._stack[-1] del self._section_stack[-1] del self._stack[-1] self._logger.info('Leave section {}'.format(section)) else: raise BadAfmFile("Misplaced end of section {}".format(section))
##############################################
[docs] def _parse_key_values(self, keys, line): index = line.find(' ') if index != -1: key = line[:index] if key in keys: values = self._get_values(keys[key], line[index:]) self._logger.info('key {}: {}'.format(key, values)) return key, values else: # raise NameError() self._logger.warning("Unknown key {} in section {}".format(key, self._section)) return None, None else: raise NameError("Bad line")
##############################################
[docs] def _parse_key_values_list(self, keys, line): self._logger.info('') properties = {} for pair in line.split(';'): pair = pair.strip() if pair: key, values = self._parse_key_values(keys, pair) if key is not None: properties[key] = values return properties
#################################################################################################### # # End # ####################################################################################################