#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()