afm.py :  » PDF » PyX » PyX-0.10 » pyx » font » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » PDF » PyX 
PyX » PyX 0.10 » pyx » font » afm.py
# -*- coding: ISO-8859-1 -*-
#
#
# Copyright (C) 2006 Jrg Lehmann <joergl@users.sourceforge.net>
#
# This file is part of PyX (http://pyx.sourceforge.net/).
#
# PyX 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 2 of the License, or
# (at your option) any later version.
#
# PyX 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 PyX; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

import string

class AFMError(Exception):
    pass

# reader states
_READ_START       = 0
_READ_MAIN        = 1
_READ_DIRECTION   = 2
_READ_CHARMETRICS = 3
_READ_KERNDATA    = 4
_READ_TRACKKERN   = 5
_READ_KERNPAIRS   = 6
_READ_COMPOSITES  = 7
_READ_END         = 8

# various parsing functions
def _parseint(s):
    try:
        return int(s)
    except:
        raise AFMError("Expecting int, got '%s'" % s)

def _parsehex(s):
    try:
        if s[0] != "<" or s[-1] != ">":
            raise AFMError()
        return int(s[1:-1], 16)
    except:
        raise AFMError("Expecting hexadecimal int, got '%s'" % s)

def _parsefloat(s):
    try:
        return float(s)
    except:
        raise AFMError("Expecting float, got '%s'" % s)

def _parsefloats(s, nos):
    try:
        numbers = s.split()
        result = map(float, numbers)
        if len(result) != nos:
            raise AFMError()
    except:
        raise AFMError("Expecting list of %d numbers, got '%s'" % (s, nos))
    return result

def _parsestr(s):
    # XXX: check for invalid characters in s
    return s

def _parsebool(s):
    s = s.rstrip()
    if s == "true":
       return 1
    elif s == "false":
       return 0
    else:
        raise AFMError("Expecting boolean, got '%s'" % s)


class AFMcharmetrics:
    def __init__(self, code, widths=None, vvector=None, name=None, bbox=None, ligatures=None):
        self.code = code
        if widths is None:
            self.widths = [None] * 2
        else:
            self.widths = widths
        self.vvector = vvector
        self.name = name
        self.bbox = bbox
        if ligatures is None:
            self.ligatures = []
        else:
            self.ligatures = ligatures


class AFMtrackkern:
    def __init__(self, degree, min_ptsize, min_kern, max_ptsize, max_kern):
        self.degree = degree
        self.min_ptsize = min_ptsize
        self.min_kern = min_kern
        self.max_ptsize = max_ptsize
        self.max_kern = max_kern


class AFMkernpair:
    def __init__(self, name1, name2, x, y):
        self.name1 = name1
        self.name2 = name2
        self.x = x
        self.y = y


class AFMcomposite:
    def __init__(self, name, parts):
        self.name = name
        self.parts = parts


class AFMfile:

    def __init__(self, filename):
       self.filename = filename
       self.metricssets = 0                     # int, optional
       self.fontname = None                     # str, required
       self.fullname = None                     # str, optional
       self.familyname = None                   # str, optional
       self.weight = None                       # str, optional
       self.fontbbox = None                     # 4 floats, required
       self.version = None                      # str, optional
       self.notice = None                       # str, optional
       self.encodingscheme = None               # str, optional
       self.mappingscheme = None                # int, optional (not present in base font programs)
       self.escchar = None                      # int, required if mappingscheme == 3
       self.characterset = None                 # str, optional
       self.characters = None                   # int, optional
       self.isbasefont = 1                      # bool, optional
       self.vvector = None                      # 2 floats, required if metricssets == 2
       self.isfixedv = None                     # bool, default: true if vvector present, false otherwise
       self.capheight = None                    # float, optional
       self.xheight = None                      # float, optional
       self.ascender = None                     # float, optional
       self.descender = None                    # float, optional
       self.underlinepositions = [None] * 2     # int, optional (for each direction)
       self.underlinethicknesses = [None] * 2   # float, optional (for each direction)
       self.italicangles = [None] * 2           # float, optional (for each direction)
       self.charwidths = [None] * 2             # 2 floats, optional (for each direction)
       self.isfixedpitchs = [None] * 2          # bool, optional (for each direction)
       self.charmetrics = None                  # list of character metrics information, optional
       self.trackkerns = None                   # list of track kernings, optional
       self.kernpairs = [None] * 2              # list of list of kerning pairs (for each direction), optional
       self.composites = None                   # list of composite character data sets, optional
       self.parse()
       if self.isfixedv is None:
           self.isfixedv = self.vvector is not None
       # XXX we should check the constraints on some parameters

    # the following methods process a line when the reader is in the corresponding
    # state and return the new state
    def _processline_start(self, line):
        key, args = line.split(None, 1)
        if key != "StartFontMetrics":
            raise AFMError("Expecting StartFontMetrics, no found")
        return _READ_MAIN, None

    def _processline_main(self, line):
        try:
            key, args = line.split(None, 1)
        except ValueError:
            key = line.rstrip()
        if key == "Comment":
            return _READ_MAIN, None
        elif key == "MetricsSets":
            self.metricssets = _parseint(args)
            if direction is not None:
                raise AFMError("MetricsSets not allowed after first (implicit) StartDirection")
        elif key == "FontName":
            self.fontname = _parsestr(args)
        elif key == "FullName":
            self.fullname = _parsestr(args)
        elif key == "FamilyName":
            self.familyname = _parsestr(args)
        elif key == "Weight":
            self.weight = _parsestr(args)
        elif key == "FontBBox":
            self.fontbbox = _parsefloats(args, 4)
        elif key == "Version":
            self.version = _parsestr(args)
        elif key == "Notice":
            self.notice = _parsestr(args)
        elif key == "EncodingScheme":
            self.encodingscheme = _parsestr(args)
        elif key == "MappingScheme":
            self.mappingscheme = _parseint(args)
        elif key == "EscChar":
            self.escchar = _parseint(args)
        elif key == "CharacterSet":
            self.characterset = _parsestr(args)
        elif key == "Characters":
            self.characters = _parseint(args)
        elif key == "IsBaseFont":
            self.isbasefont = _parsebool(args)
        elif key == "VVector":
            self.vvector = _parsefloats(args, 2)
        elif key == "IsFixedV":
            self.isfixedv = _parsebool(args)
        elif key == "CapHeight":
            self.capheight = _parsefloat(args)
        elif key == "XHeight":
            self.xheight = _parsefloat(args)
        elif key == "Ascender":
            self.ascender = _parsefloat(args)
        elif key == "Descender":
            self.descender = _parsefloat(args)
        elif key == "StartDirection":
            direction = _parseint(args)
            if 0 <= direction <= 2:
                return _READ_DIRECTION, direction
            else:
                raise AFMError("Wrong direction number %d" % direction)
        elif (key == "UnderLinePosition" or key == "UnderlineThickness" or key == "ItalicAngle" or
              key == "Charwidth" or key == "IsFixedPitch"):
            # we implicitly entered a direction section, so we should process the line again
            return self._processline_direction(line, 0)
        elif key == "StartCharMetrics":
            if self.charmetrics is not None:
                raise AFMError("Multiple character metrics sections")
            self.charmetrics = [None] * _parseint(args)
            return _READ_CHARMETRICS, 0
        elif key == "StartKernData":
            return _READ_KERNDATA, None
        elif key == "StartComposites":
            if self.composites is not None:
                raise AFMError("Multiple composite character data sections")
            self.composites = [None] * _parseint(args)
            return _READ_COMPOSITES, 0
        elif key == "EndFontMetrics":
            return _READ_END, None
        elif key[0] in string.lowercase:
            # ignoring private commands
            pass
        return _READ_MAIN, None

    def _processline_direction(self, line, direction):
        try:
            key, args = line.split(None, 1)
        except ValueError:
            key = line.rstrip()
        if key == "UnderLinePosition":
            self.underlinepositions[direction] = _parseint(args)
        elif key == "UnderlineThickness":
            self.underlinethicknesses[direction] = _parsefloat(args)
        elif key == "ItalicAngle":
            self.italicangles[direction] = _parsefloat(args)
        elif key == "Charwidth":
            self.charwidths[direction] = _parsefloats(args, 2)
        elif key == "IsFixedPitch":
            self.isfixedpitchs[direction] = _parsebool(args)
        elif key == "EndDirection":
            return _READ_MAIN, None
        else:
            # we assume that we are implicitly leaving the direction section again,
            # so try to reprocess the line in the header reader state
            return self._processline_main(line)
        return _READ_DIRECTION, direction

    def _processline_charmetrics(self, line, charno):
        if line.rstrip() == "EndCharMetrics":
            if charno != len(self.charmetrics):
                raise AFMError("Fewer character metrics than expected")
            return _READ_MAIN, None
        if charno >= len(self.charmetrics):
            raise AFMError("More character metrics than expected")

        char = None
        for s in line.split(";"):
            s = s.strip()
            if not s:
               continue
            key, args = s.split(None, 1)
            if key == "C":
                if char is not None:
                    raise AFMError("Cannot define char code twice")
                char = AFMcharmetrics(_parseint(args))
            elif key == "CH":
                if char is not None:
                    raise AFMError("Cannot define char code twice")
                char = AFMcharmetrics(_parsehex(args))
            elif key == "WX" or key == "W0X":
                char.widths[0] = _parsefloat(args), 0
            elif key == "W1X":
                char.widths[1] = _parsefloat(args), 0
            elif key == "WY" or key == "W0Y":
                char.widths[0] = 0, _parsefloat(args)
            elif key == "W1Y":
                char.widths[1] = 0, _parsefloat(args)
            elif key == "W" or key == "W0":
                char.widths[0] = _parsefloats(args, 2)
            elif key == "W1":
                char.widths[1] = _parsefloats(args, 2)
            elif key == "VV":
                char.vvector = _parsefloats(args, 2)
            elif key == "N":
                # XXX: we should check that name is valid (no whitespcae, etc.)
                char.name = _parsestr(args)
            elif key == "B":
                char.bbox = _parsefloats(args, 4)
            elif key == "L":
                successor, ligature = args.split(None, 1)
                char.ligatures.append((_parsestr(successor), ligature))
            else:
                raise AFMError("Undefined command in character widths specification: '%s'", s)
        if char is None:
            raise AFMError("Character metrics not defined")

        self.charmetrics[charno] = char
        return _READ_CHARMETRICS, charno+1

    def _processline_kerndata(self, line):
        try:
            key, args = line.split(None, 1)
        except ValueError:
            key = line.rstrip()
        if key == "Comment":
            return _READ_KERNDATA, None
        if key == "StartTrackKern":
            if self.trackkerns is not None:
                raise AFMError("Multiple track kernings data sections")
            self.trackkerns = [None] * _parseint(args)
            return _READ_TRACKKERN, 0
        elif key == "StartKernPairs" or key == "StartKernPairs0":
            if self.kernpairs[0] is not None:
                raise AFMError("Multiple kerning pairs data sections for direction 0")
            self.kernpairs[0] = [None] * _parseint(args)
            return _READ_KERNPAIRS, (0, 0)
        elif key == "StartKernPairs1":
            if self.kernpairs[1] is not None:
                raise AFMError("Multiple kerning pairs data sections for direction 0")
            self.kernpairs[1] = [None] * _parseint(args)
            return _READ_KERNPAIRS, (1, 0)
        elif key == "EndKernData":
            return _READ_MAIN, None
        else:
            raise AFMError("Unsupported key %s in kerning data section" % key)

    def _processline_trackkern(self, line, i):
        try:
            key, args = line.split(None, 1)
        except ValueError:
            key = line.rstrip()
        if key == "Comment":
            return _READ_TRACKKERN, i
        elif key == "TrackKern":
            if i >= len(self.trackkerns):
                raise AFMError("More track kerning data sets than expected")
            degrees, args = args.split(None, 1)
            self.trackkerns[i] = AFMtrackkern(int(degrees), *_parsefloats(args, 4))
            return _READ_TRACKKERN, i+1
        elif key == "EndTrackKern":
            if i < len(self.trackkerns):
                raise AFMError("Fewer track kerning data sets than expected")
            return _READ_KERNDATA, None
        else:
            raise AFMError("Unsupported key %s in kerning data section" % key)

    def _processline_kernpairs(self, line, (direction, i)):
        try:
            key, args = line.split(None, 1)
        except ValueError:
            key = line.rstrip()
        if key == "Comment":
            return _READ_KERNPAIRS, (direction, i)
        elif key == "EndKernPairs":
            if i < len(self.kernpairs[direction]):
                raise AFMError("Fewer kerning pairs than expected")
            return _READ_KERNDATA, None
        else:
            if i >= len(self.kernpairs[direction]):
                raise AFMError("More kerning pairs than expected")
            if key == "KP":
                try:
                    name1, name2, x, y = args.split()
                except:
                    raise AFMError("Expecting name1, name2, x, y, got '%s'" % args)
                self.kernpairs[direction][i] = AFMkernpair(name1, name2, _parsefloat(x), _parsefloat(y))
            elif key == "KPH":
                try:
                    hex1, hex2, x, y = args.split()
                except:
                    raise AFMError("Expecting <hex1>, <hex2>, x, y, got '%s'" % args)
                self.kernpairs[direction][i] = AFMkernpair(_parsehex(hex1), _parsehex(hex2),
                                                           _parsefloat(x), _parsefloat(y))
            elif key == "KPX":
                try:
                    name1, name2, x = args.split()
                except:
                    raise AFMError("Expecting name1, name2, x, got '%s'" % args)
                self.kernpairs[direction][i] = AFMkernpair(name1, name2, _parsefloat(x), 0)
            elif key == "KPY":
                try:
                    name1, name2, y = args.split()
                except:
                    raise AFMError("Expecting name1, name2, x, got '%s'" % args)
                self.kernpairs[direction][i] = AFMkernpair(name1, name2, 0, _parsefloat(y))
            else:
                raise AFMError("Unknown key '%s' in kern pair section" % key)
            return _READ_KERNPAIRS, (direction, i+1)

    def _processline_composites(self, line, i):
        if line.rstrip() == "EndComposites":
            if i < len(self.composites):
                raise AFMError("Fewer composite character data sets than expected")
            return _READ_MAIN, None
        if i >= len(self.composites):
            raise AFMError("More composite character data sets than expected")

        name = None
        no = None
        parts = []
        for s in line.split(";"):
            s = s.strip()
            if not s:
               continue
            key, args = s.split(None, 1)
            if key == "CC":
                try:
                    name, no = args.split()
                except:
                    raise AFMError("Expecting name number, got '%s'" % args)
                no = _parseint(no)
            elif key == "PCC":
                try:
                    name1, x, y = args.split()
                except:
                    raise AFMError("Expecting name x y, got '%s'" % args)
                parts.append((name1, _parsefloat(x), _parsefloat(y)))
            else:
                raise AFMError("Unknown key '%s' in composite character data section" % key)
        if len(parts) != no:
            raise AFMError("Wrong number of composite characters")
        self.composites[i] = AFMcomposite(name, parts)
        return _READ_COMPOSITES, i+1

    def parse(self):
        f = open(self.filename, "r")
        try:
             # state of the reader, consisting of 
             #  - the main state, i.e. the type of the section
             #  - a parameter sstate
             state = _READ_START, None
             # Note that we do a line by line processing here, since one
             # of the states (_READ_DIRECTION) can be entered implicitly, i.e.
             # without a corresponding StartDirection section and we thus
             # may need to reprocess a line in the context of the new state
             for line in f:
                line = line[:-1]
                mstate, sstate = state
                if mstate == _READ_START:
                    state = self._processline_start(line)
                else: 
                    # except for the first line, any empty will be ignored
                    if not line.strip():
                       continue
                    if mstate == _READ_MAIN:
                        state = self._processline_main(line)
                    elif mstate == _READ_DIRECTION:
                        state = self._processline_direction(line, sstate)
                    elif mstate == _READ_CHARMETRICS:
                        state = self._processline_charmetrics(line, sstate)
                    elif mstate == _READ_KERNDATA:
                        state = self._processline_kerndata(line)
                    elif mstate == _READ_TRACKKERN:
                        state = self._processline_trackkern(line, sstate)
                    elif mstate == _READ_KERNPAIRS:
                        state = self._processline_kernpairs(line, sstate)
                    elif mstate == _READ_COMPOSITES:
                        state = self._processline_composites(line, sstate)
                    else:
                        raise RuntimeError("Undefined state in AFM reader")
        finally:
            f.close()

if __name__ == "__main__":
    a = AFMfile("/opt/local/share/texmf-dist/fonts/afm/yandy/lucida/lbc.afm")
    print a.charmetrics[0].name
    a = AFMfile("/usr/share/enscript/hv.afm")
    print a.charmetrics[32].name
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.