src/reportlab/pdfbase/pdfmetrics.py
author robin <robin@reportlab.com>
Tue, 07 Mar 2017 10:00:34 +0000
changeset 4330 617ffa6bbdc8
parent 4252 fe660f227cac
child 4367 9960d82643bf
permissions -rwxr-xr-x
changes for release 3.4.0

#Copyright ReportLab Europe Ltd. 2000-2017
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfbase/pdfmetrics.py
#$Header $
__version__='3.3.0'
__doc__="""This provides a database of font metric information and
efines Font, Encoding and TypeFace classes aimed at end users.

There are counterparts to some of these in pdfbase/pdfdoc.py, but
the latter focus on constructing the right PDF objects.  These
classes are declarative and focus on letting the user construct
and query font objects.

The module maintains a registry of font objects at run time.

It is independent of the canvas or any particular context.  It keeps
a registry of Font, TypeFace and Encoding objects.  Ideally these
would be pre-loaded, but due to a nasty circularity problem we
trap attempts to access them and do it on first access.
"""
import string, os, sys
from reportlab.pdfbase import _fontdata
from reportlab.lib.logger import warnOnce
from reportlab.lib.utils import rl_isfile, rl_glob, rl_isdir, open_and_read, open_and_readlines, findInPaths, isSeq, isStr, isUnicode, isPy3
from reportlab.rl_config import defaultEncoding, T1SearchPath
from reportlab.lib.rl_accel import unicode2T1, instanceStringWidthT1
from reportlab.pdfbase import rl_codecs
_notdefChar = b'n'

rl_codecs.RL_Codecs.register()
standardFonts = _fontdata.standardFonts
standardEncodings = _fontdata.standardEncodings

_typefaces = {}
_encodings = {}
_fonts = {}
_dynFaceNames = {}      #record dynamicFont face names

class FontError(Exception):
    pass
class FontNotFoundError(Exception):
    pass

def parseAFMFile(afmFileName):
    """Quick and dirty - gives back a top-level dictionary
    with top-level items, and a 'widths' key containing
    a dictionary of glyph names and widths.  Just enough
    needed for embedding.  A better parser would accept
    options for what data you wwanted, and preserve the
    order."""

    lines = open_and_readlines(afmFileName, 'r')
    if len(lines)<=1:
        #likely to be a MAC file
        if lines: lines = lines[0].split('\r')
        if len(lines)<=1:
            raise ValueError('AFM file %s hasn\'t enough data' % afmFileName)
    topLevel = {}
    glyphLevel = []

    lines = [l.strip() for l in lines]
    lines = [l for l in lines if not l.lower().startswith('comment')]
    #pass 1 - get the widths
    inMetrics = 0  # os 'TOP', or 'CHARMETRICS'
    for line in lines:
        if line[0:16] == 'StartCharMetrics':
            inMetrics = 1
        elif line[0:14] == 'EndCharMetrics':
            inMetrics = 0
        elif inMetrics:
            chunks = line.split(';')
            chunks = [chunk.strip() for chunk in chunks]
            cidChunk, widthChunk, nameChunk = chunks[0:3]

            # character ID
            l, r = cidChunk.split()
            assert l == 'C', 'bad line in font file %s' % line
            cid = int(r)

            # width
            l, r = widthChunk.split()
            assert l == 'WX', 'bad line in font file %s' % line
            width = int(r)

            # name
            l, r = nameChunk.split()
            assert l == 'N', 'bad line in font file %s' % line
            name = r

            glyphLevel.append((cid, width, name))

    # pass 2 font info
    inHeader = 0
    for line in lines:
        if line[0:16] == 'StartFontMetrics':
            inHeader = 1
        if line[0:16] == 'StartCharMetrics':
            inHeader = 0
        elif inHeader:
            if line[0:7] == 'Comment': pass
            try:
                left, right = line.split(' ',1)
            except:
                raise ValueError("Header information error in afm %s: line='%s'" % (afmFileName, line))
            try:
                right = int(right)
            except:
                pass
            topLevel[left] = right


    return (topLevel, glyphLevel)

class TypeFace:
    def __init__(self, name):
        self.name = name
        self.glyphNames = []
        self.glyphWidths = {}
        self.ascent = 0
        self.descent = 0


        # all typefaces of whatever class should have these 3 attributes.
        # these are the basis for family detection.
        self.familyName = None  # should set on load/construction if possible
        self.bold = 0    # bold faces should set this
        self.italic = 0  #italic faces should set this

        if name == 'ZapfDingbats':
            self.requiredEncoding = 'ZapfDingbatsEncoding'
        elif name == 'Symbol':
            self.requiredEncoding = 'SymbolEncoding'
        else:
            self.requiredEncoding = None
        if name in standardFonts:
            self.builtIn = 1
            self._loadBuiltInData(name)
        else:
            self.builtIn = 0

    def _loadBuiltInData(self, name):
        """Called for the built in 14 fonts.  Gets their glyph data.
        We presume they never change so this can be a shared reference."""
        name = str(name)    #needed for pycanvas&jython/2.1 compatibility
        self.glyphWidths = _fontdata.widthsByFontGlyph[name]
        self.glyphNames = list(self.glyphWidths.keys())
        self.ascent,self.descent = _fontdata.ascent_descent[name]

    def getFontFiles(self):
        "Info function, return list of the font files this depends on."
        return []

    def findT1File(self, ext='.pfb'):
        possible_exts = (ext.lower(), ext.upper())
        if hasattr(self,'pfbFileName'):
            r_basename = os.path.splitext(self.pfbFileName)[0]
            for e in possible_exts:
                if rl_isfile(r_basename + e):
                    return r_basename + e
        try:
            r = _fontdata.findT1File(self.name)
        except:
            afm = bruteForceSearchForAFM(self.name)
            if afm:
                if ext.lower() == '.pfb':
                    for e in possible_exts:
                        pfb = os.path.splitext(afm)[0] + e
                        if rl_isfile(pfb):
                            r = pfb
                        else:
                            r = None
                elif ext.lower() == '.afm':
                    r = afm
            else:
                r = None
        if r is None:
            warnOnce("Can't find %s for face '%s'" % (ext, self.name))
        return r

def bruteForceSearchForFile(fn,searchPath=None):
    if searchPath is None: from reportlab.rl_config import T1SearchPath as searchPath
    if rl_isfile(fn): return fn
    bfn = os.path.basename(fn)
    for dirname in searchPath:
        if not rl_isdir(dirname): continue
        tfn = os.path.join(dirname,bfn)
        if rl_isfile(tfn): return tfn
    return fn

def bruteForceSearchForAFM(faceName):
    """Looks in all AFM files on path for face with given name.

    Returns AFM file name or None.  Ouch!"""
    from reportlab.rl_config import T1SearchPath

    for dirname in T1SearchPath:
        if not rl_isdir(dirname): continue
        possibles = rl_glob(dirname + os.sep + '*.[aA][fF][mM]')
        for possible in possibles:
            try:
                topDict, glyphDict = parseAFMFile(possible)
                if topDict['FontName'] == faceName:
                    return possible
            except:
                t,v,b=sys.exc_info()
                v.args = (' '.join(map(str,v.args))+', while looking for faceName=%r' % faceName,)
                raise 


#for faceName in standardFonts:
#    registerTypeFace(TypeFace(faceName))


class Encoding:
    """Object to help you create and refer to encodings."""
    def __init__(self, name, base=None):
        self.name = name
        self.frozen = 0
        if name in standardEncodings:
            assert base is None, "Can't have a base encoding for a standard encoding"
            self.baseEncodingName = name
            self.vector = _fontdata.encodings[name]
        elif base == None:
            # assume based on the usual one
            self.baseEncodingName = defaultEncoding
            self.vector = _fontdata.encodings[defaultEncoding]
        elif isStr(base):
            baseEnc = getEncoding(base)
            self.baseEncodingName = baseEnc.name
            self.vector = baseEnc.vector[:]
        elif isSeq(base):
            self.baseEncodingName = defaultEncoding
            self.vector = base[:]
        elif isinstance(base, Encoding):
            # accept a vector
            self.baseEncodingName = base.name
            self.vector = base.vector[:]

    def __getitem__(self, index):
        "Return glyph name for that code point, or None"
        # THIS SHOULD BE INLINED FOR SPEED
        return self.vector[index]

    def __setitem__(self, index, value):
        # should fail if they are frozen
        assert self.frozen == 0, 'Cannot modify a frozen encoding'
        if self.vector[index]!=value:
            L = list(self.vector)
            L[index] = value
            self.vector = tuple(L)

    def freeze(self):
        self.vector = tuple(self.vector)
        self.frozen = 1

    def isEqual(self, other):
        return self.name==other.name and tuple(self.vector)==tuple(other.vector)

    def modifyRange(self, base, newNames):
        """Set a group of character names starting at the code point 'base'."""
        assert self.frozen == 0, 'Cannot modify a frozen encoding'
        idx = base
        for name in newNames:
            self.vector[idx] = name
            idx = idx + 1

    def getDifferences(self, otherEnc):
        """
        Return a compact list of the code points differing between two encodings

        This is in the Adobe format: list of
           [[b1, name1, name2, name3],
           [b2, name4]]
           
        where b1...bn is the starting code point, and the glyph names following
        are assigned consecutive code points.
        
        """

        ranges = []
        curRange = None
        for i in range(len(self.vector)):
            glyph = self.vector[i]
            if glyph==otherEnc.vector[i]:
                if curRange:
                    ranges.append(curRange)
                    curRange = []
            else:
                if curRange:
                    curRange.append(glyph)
                elif glyph:
                    curRange = [i, glyph]
        if curRange:
            ranges.append(curRange)
        return ranges

    def makePDFObject(self):
        "Returns a PDF Object representing self"
        # avoid circular imports - this cannot go at module level
        from reportlab.pdfbase import pdfdoc

        D = {}
        baseEnc = getEncoding(self.baseEncodingName)
        differences = self.getDifferences(baseEnc) #[None] * 256)

        # if no differences, we just need the base name
        if differences == []:
            return pdfdoc.PDFName(self.baseEncodingName)
        else:
            #make up a dictionary describing the new encoding
            diffArray = []
            for range in differences:
                diffArray.append(range[0])        # numbers go 'as is'
                for glyphName in range[1:]:
                    if glyphName is not None:
                        # there is no way to 'unset' a character in the base font.
                        diffArray.append('/' + glyphName)

            #print 'diffArray = %s' % diffArray
            D["Differences"] = pdfdoc.PDFArray(diffArray)
            D["BaseEncoding"] = pdfdoc.PDFName(self.baseEncodingName)
            D["Type"] = pdfdoc.PDFName("Encoding")
            PD = pdfdoc.PDFDictionary(D)
            return PD

#for encName in standardEncodings:
#    registerEncoding(Encoding(encName))


standardT1SubstitutionFonts = []
class Font:
    """Represents a font (i.e combination of face and encoding).

    Defines suitable machinery for single byte fonts.  This is
    a concrete class which can handle the basic built-in fonts;
    not clear yet if embedded ones need a new font class or
    just a new typeface class (which would do the job through
    composition)"""

    _multiByte = 0      # do not want our own stringwidth
    _dynamicFont = 0    # do not want dynamic subsetting

    def __init__(self, name, faceName, encName):
        self.fontName = name
        face = self.face = getTypeFace(faceName)
        self.encoding= getEncoding(encName)
        self.encName = encName
        if face.builtIn and face.requiredEncoding is None:
            _ = standardT1SubstitutionFonts
        else:
            _ = []
        self.substitutionFonts = _
        self._calcWidths()
        self._notdefChar = _notdefChar
        self._notdefFont = name=='ZapfDingbats' and self or _notdefFont

    def stringWidth(self, text, size, encoding='utf8'):
        return instanceStringWidthT1(self, text, size, encoding=encoding)

    def __repr__(self):
        return "<%s %s>" % (self.__class__.__name__, self.face.name)

    def _calcWidths(self):
        """Vector of widths for stringWidth function"""
        #synthesize on first request
        w = [0] * 256
        gw = self.face.glyphWidths
        vec = self.encoding.vector
        for i in range(256):
            glyphName = vec[i]
            if glyphName is not None:
                try:
                    width = gw[glyphName]
                    w[i] = width
                except KeyError:
                    import reportlab.rl_config
                    if reportlab.rl_config.warnOnMissingFontGlyphs:
                        print('typeface "%s" does not have a glyph "%s", bad font!' % (self.face.name, glyphName))
                    else:
                        pass
        self.widths = w

    def _formatWidths(self):
        "returns a pretty block in PDF Array format to aid inspection"
        text = b'['
        for i in range(256):
            text = text + b' ' + bytes(str(self.widths[i]),'utf8')
            if i == 255:
                text = text + b' ]'
            if i % 16 == 15:
                text = text + b'\n'
        return text

    def addObjects(self, doc):
        """Makes and returns one or more PDF objects to be added
        to the document.  The caller supplies the internal name
        to be used (typically F1, F2... in sequence) """
        # avoid circular imports - this cannot go at module level
        from reportlab.pdfbase import pdfdoc

        # construct a Type 1 Font internal object
        internalName = 'F' + repr(len(doc.fontMapping)+1)
        pdfFont = pdfdoc.PDFType1Font()
        pdfFont.Name = internalName
        pdfFont.BaseFont = self.face.name
        pdfFont.__Comment__ = 'Font %s' % self.fontName
        pdfFont.Encoding = self.encoding.makePDFObject()

        # is it a built-in one?  if not, need more stuff.
        if not self.face.name in standardFonts:
            pdfFont.FirstChar = 0
            pdfFont.LastChar = 255
            pdfFont.Widths = pdfdoc.PDFArray(self.widths)
            pdfFont.FontDescriptor = self.face.addObjects(doc)
        # now link it in
        ref = doc.Reference(pdfFont, internalName)

        # also refer to it in the BasicFonts dictionary
        fontDict = doc.idToObject['BasicFonts'].dict
        fontDict[internalName] = pdfFont

        # and in the font mappings
        doc.fontMapping[self.fontName] = '/' + internalName

PFB_MARKER=chr(0x80)
PFB_ASCII=chr(1)
PFB_BINARY=chr(2)
PFB_EOF=chr(3)

if isPy3:
    def _pfbCheck(p,d,m,fn):
        if chr(d[p])!=PFB_MARKER or chr(d[p+1])!=m:
            raise ValueError('Bad pfb file\'%s\' expected chr(%d)chr(%d) at char %d, got chr(%d)chr(%d)' % (fn,ord(PFB_MARKER),ord(m),p,d[p],d[p+1]))
        if m==PFB_EOF: return
        p = p + 2
        l = (((((d[p+3])<<8)|(d[p+2])<<8)|(d[p+1]))<<8)|(d[p])
        p = p + 4
        if p+l>len(d):
            raise ValueError('Bad pfb file\'%s\' needed %d+%d bytes have only %d!' % (fn,p,l,len(d)))
        return p, p+l
else:
    def _pfbSegLen(p,d):
        '''compute a pfb style length from the first 4 bytes of string d'''
        return ((((ord(d[p+3])<<8)|ord(d[p+2])<<8)|ord(d[p+1]))<<8)|ord(d[p])

    def _pfbCheck(p,d,m,fn):
        if d[p]!=PFB_MARKER or d[p+1]!=m:
            raise ValueError('Bad pfb file\'%s\' expected chr(%d)chr(%d) at char %d, got chr(%d)chr(%d)' % (fn,ord(PFB_MARKER),ord(m),p,ord(d[p]),ord(d[p+1])))
        if m==PFB_EOF: return
        p = p + 2
        l = _pfbSegLen(p,d)
        p = p + 4
        if p+l>len(d):
            raise ValueError('Bad pfb file\'%s\' needed %d+%d bytes have only %d!' % (fn,p,l,len(d)))
        return p, p+l

class EmbeddedType1Face(TypeFace):
    """A Type 1 font other than one of the basic 14.

    Its glyph data will be embedded in the PDF file."""
    def __init__(self, afmFileName, pfbFileName):
        # ignore afm file for now
        TypeFace.__init__(self, None)
        #None is a hack, name will be supplied by AFM parse lower done
        #in this __init__ method.
        afmFileName = findInPaths(afmFileName,T1SearchPath)
        pfbFileName = findInPaths(pfbFileName,T1SearchPath)
        self.afmFileName = os.path.abspath(afmFileName)
        self.pfbFileName = os.path.abspath(pfbFileName)
        self.requiredEncoding = None
        self._loadGlyphs(pfbFileName)
        self._loadMetrics(afmFileName)

    def getFontFiles(self):
        return [self.afmFileName, self.pfbFileName]

    def _loadGlyphs(self, pfbFileName):
        """Loads in binary glyph data, and finds the four length
        measurements needed for the font descriptor"""
        pfbFileName = bruteForceSearchForFile(pfbFileName)
        assert rl_isfile(pfbFileName), 'file %s not found' % pfbFileName
        d = open_and_read(pfbFileName, 'b')
        s1, l1 = _pfbCheck(0,d,PFB_ASCII,pfbFileName)
        s2, l2 = _pfbCheck(l1,d,PFB_BINARY,pfbFileName)
        s3, l3 = _pfbCheck(l2,d,PFB_ASCII,pfbFileName)
        _pfbCheck(l3,d,PFB_EOF,pfbFileName)
        self._binaryData = d[s1:l1]+d[s2:l2]+d[s3:l3]

        self._length = len(self._binaryData)
        self._length1 = l1-s1
        self._length2 = l2-s2
        self._length3 = l3-s3


    def _loadMetrics(self, afmFileName):
        """Loads in and parses font metrics"""
        #assert os.path.isfile(afmFileName), "AFM file %s not found" % afmFileName
        afmFileName = bruteForceSearchForFile(afmFileName)
        (topLevel, glyphData) = parseAFMFile(afmFileName)

        self.name = topLevel['FontName']
        self.familyName = topLevel['FamilyName']
        self.ascent = topLevel.get('Ascender', 1000)
        self.descent = topLevel.get('Descender', 0)
        self.capHeight = topLevel.get('CapHeight', 1000)
        self.italicAngle = topLevel.get('ItalicAngle', 0)
        self.stemV = topLevel.get('stemV', 0)
        self.xHeight = topLevel.get('XHeight', 1000)

        strBbox = topLevel.get('FontBBox', [0,0,1000,1000])
        tokens = strBbox.split()
        self.bbox = []
        for tok in tokens:
            self.bbox.append(int(tok))

        glyphWidths = {}
        for (cid, width, name) in glyphData:
            glyphWidths[name] = width
        self.glyphWidths = glyphWidths
        self.glyphNames = list(glyphWidths.keys())
        self.glyphNames.sort()

        # for font-specific encodings like Symbol, Dingbats, Carta we
        # need to make a new encoding as well....
        if topLevel.get('EncodingScheme', None) == 'FontSpecific':
            names = [None] * 256
            for (code, width, name) in glyphData:
                if code >=0 and code <=255:
                    names[code] = name
            encName = self.name + 'Encoding'
            self.requiredEncoding = encName
            enc = Encoding(encName, names)
            registerEncoding(enc)

    def addObjects(self, doc):
        """Add whatever needed to PDF file, and return a FontDescriptor reference"""
        from reportlab.pdfbase import pdfdoc

        fontFile = pdfdoc.PDFStream()
        fontFile.content = self._binaryData
        #fontFile.dictionary['Length'] = self._length
        fontFile.dictionary['Length1'] = self._length1
        fontFile.dictionary['Length2'] = self._length2
        fontFile.dictionary['Length3'] = self._length3
        #fontFile.filters = [pdfdoc.PDFZCompress]

        fontFileRef = doc.Reference(fontFile, 'fontFile:' + self.pfbFileName)

        fontDescriptor = pdfdoc.PDFDictionary({
            'Type': '/FontDescriptor',
            'Ascent':self.ascent,
            'CapHeight':self.capHeight,
            'Descent':self.descent,
            'Flags': 34,
            'FontBBox':pdfdoc.PDFArray(self.bbox),
            'FontName':pdfdoc.PDFName(self.name),
            'ItalicAngle':self.italicAngle,
            'StemV':self.stemV,
            'XHeight':self.xHeight,
            'FontFile': fontFileRef,
            })
        fontDescriptorRef = doc.Reference(fontDescriptor, 'fontDescriptor:' + self.name)
        return fontDescriptorRef

def registerTypeFace(face):
    assert isinstance(face, TypeFace), 'Not a TypeFace: %s' % face
    _typefaces[face.name] = face
    if not face.name in standardFonts:
        # HACK - bold/italic do not apply for type 1, so egister
        # all combinations of mappings.
        registerFontFamily(face.name)

def registerEncoding(enc):
    assert isinstance(enc, Encoding), 'Not an Encoding: %s' % enc
    if enc.name in _encodings:
        # already got one, complain if they are not the same
        if enc.isEqual(_encodings[enc.name]):
            enc.freeze()
        else:
            raise FontError('Encoding "%s" already registered with a different name vector!' % enc.name)
    else:
        _encodings[enc.name] = enc
        enc.freeze()
    # have not yet dealt with immutability!

def registerFontFamily(family,normal=None,bold=None,italic=None,boldItalic=None):
    from reportlab.lib import fonts
    if not normal: normal = family
    family = family.lower()
    if not boldItalic: boldItalic = italic or bold or normal
    if not bold: bold = normal
    if not italic: italic = normal
    fonts.addMapping(family, 0, 0, normal)
    fonts.addMapping(family, 1, 0, bold)
    fonts.addMapping(family, 0, 1, italic)
    fonts.addMapping(family, 1, 1, boldItalic)

def registerFont(font):
    "Registers a font, including setting up info for accelerated stringWidth"
    #assert isinstance(font, Font), 'Not a Font: %s' % font
    fontName = font.fontName
    if font._dynamicFont:
        faceName = font.face.name
        if fontName not in _fonts:
            if faceName in _dynFaceNames:
                ofont = _dynFaceNames[faceName]
                if not ofont._dynamicFont:
                    raise ValueError('Attempt to register fonts %r %r for face %r' % (ofont, font, faceName))
                else:
                    _fonts[fontName] = ofont
            else:
                _dynFaceNames[faceName] = _fonts[fontName] = font
    else:
        _fonts[fontName] = font

    if font._multiByte:
        # CID fonts don't need to have typeface registered.
        #need to set mappings so it can go in a paragraph even if within
        # bold tags
        registerFontFamily(font.fontName)

def getTypeFace(faceName):
    """Lazily construct known typefaces if not found"""
    try:
        return _typefaces[faceName]
    except KeyError:
        # not found, construct it if known
        if faceName in standardFonts:
            face = TypeFace(faceName)
            (face.familyName, face.bold, face.italic) = _fontdata.standardFontAttributes[faceName]
            registerTypeFace(face)
##            print 'auto-constructing type face %s with family=%s, bold=%d, italic=%d' % (
##                face.name, face.familyName, face.bold, face.italic)
            return face
        else:
            #try a brute force search
            afm = bruteForceSearchForAFM(faceName)
            if afm:
                for e in ('.pfb', '.PFB'):
                    pfb = os.path.splitext(afm)[0] + e
                    if rl_isfile(pfb): break
                assert rl_isfile(pfb), 'file %s not found!' % pfb
                face = EmbeddedType1Face(afm, pfb)
                registerTypeFace(face)
                return face
            else:
                raise

def getEncoding(encName):
    """Lazily construct known encodings if not found"""
    try:
        return _encodings[encName]
    except KeyError:
        if encName in standardEncodings:
            enc = Encoding(encName)
            registerEncoding(enc)
            #print 'auto-constructing encoding %s' % encName
            return enc
        else:
            raise

def findFontAndRegister(fontName):
    '''search for and register a font given its name'''
    fontName = str(fontName)
    assert type(fontName) is str, 'fontName=%s is not required type str' % ascii(fontName)
    #it might have a font-specific encoding e.g. Symbol
    # or Dingbats.  If not, take the default.
    face = getTypeFace(fontName)
    if face.requiredEncoding:
        font = Font(fontName, fontName, face.requiredEncoding)
    else:
        font = Font(fontName, fontName, defaultEncoding)
    registerFont(font)
    return font

def getFont(fontName):
    """Lazily constructs known fonts if not found.

    Names of form 'face-encoding' will be built if
    face and encoding are known.  Also if the name is
    just one of the standard 14, it will make up a font
    in the default encoding."""
    try:
        return _fonts[fontName]
    except KeyError:
        return findFontAndRegister(fontName)

_notdefFont = getFont('ZapfDingbats')
standardT1SubstitutionFonts.extend([getFont('Symbol'),_notdefFont])

def getAscentDescent(fontName,fontSize=None):
    font = getFont(fontName)
    try:
        ascent = font.ascent
        descent = font.descent
    except:
        ascent = font.face.ascent
        descent = font.face.descent
    if fontSize:
        norm = fontSize/1000.
        return ascent*norm, descent*norm
    else:
        return ascent, descent

def getAscent(fontName,fontSize=None):
    return getAscentDescent(fontName,fontSize)[0]

def getDescent(fontName,fontSize=None):
    return getAscentDescent(fontName,fontSize)[1]

def getRegisteredFontNames():
    "Returns what's in there"
    reg = list(_fonts.keys())
    reg.sort()
    return reg

def stringWidth(text, fontName, fontSize, encoding='utf8'):
    """Compute width of string in points;
    not accelerated as fast enough because of instanceStringWidthT1/TTF"""
    return getFont(fontName).stringWidth(text, fontSize, encoding=encoding)

def dumpFontData():
    print('Registered Encodings:')
    keys = list(_encodings.keys())
    keys.sort()
    for encName in keys:
        print('   ',encName)

    print()
    print('Registered Typefaces:')
    faces = list(_typefaces.keys())
    faces.sort()
    for faceName in faces:
        print('   ',faceName)


    print()
    print('Registered Fonts:')
    k = list(_fonts.keys())
    k.sort()
    for key in k:
        font = _fonts[key]
        print('    %s (%s/%s)' % (font.fontName, font.face.name, font.encoding.name))

def test3widths(texts):
    # checks all 3 algorithms give same answer, note speed
    import time
    for fontName in standardFonts[0:1]:
##        t0 = time.time()
##        for text in texts:
##            l1 = stringWidth(text, fontName, 10)
##        t1 = time.time()
##        print 'fast stringWidth took %0.4f' % (t1 - t0)

        t0 = time.time()
        w = getFont(fontName).widths
        for text in texts:
            l2 = 0
            for ch in text:
                l2 = l2 + w[ord(ch)]
        t1 = time.time()
        print('slow stringWidth took %0.4f' % (t1 - t0))

        t0 = time.time()
        for text in texts:
            l3 = getFont(fontName).stringWidth(text, 10)
        t1 = time.time()
        print('class lookup and stringWidth took %0.4f' % (t1 - t0))
        print()

def testStringWidthAlgorithms():
    rawdata = open('../../rlextra/rml2pdf/doc/rml_user_guide.prep').read()
    print('rawdata length %d' % len(rawdata))
    print('test one huge string...')
    test3widths([rawdata])
    print()
    words = rawdata.split()
    print('test %d shorter strings (average length %0.2f chars)...' % (len(words), 1.0*len(rawdata)/len(words)))
    test3widths(words)


def test():
    helv = TypeFace('Helvetica')
    registerTypeFace(helv)
    print(helv.glyphNames[0:30])

    wombat = TypeFace('Wombat')
    print(wombat.glyphNames)
    registerTypeFace(wombat)

    dumpFontData()

#preserve the initial values here
def _reset(
        initial_dicts = dict(
            _typefaces = _typefaces.copy(),
            _encodings = _encodings.copy(),
            _fonts = _fonts.copy(),
            _dynFaceNames = _dynFaceNames.copy(),
            )
        ):
    for k,v in initial_dicts.items():
        d=globals()[k]
        d.clear()
        d.update(v)

from reportlab.rl_config import register_reset
register_reset(_reset)
del register_reset

if __name__=='__main__':
    test()
    testStringWidthAlgorithms()