123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827 |
- #Copyright ReportLab Europe Ltd. 2000-2017
- #see license.txt for license details
- #history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/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, encodings
- 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
- 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
- try:
- width = int(r)
- except ValueError:
- width = float(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 = {}
- baseEncodingName = self.baseEncodingName
- baseEnc = getEncoding(baseEncodingName)
- differences = self.getDifferences(baseEnc) #[None] * 256)
- # if no differences, we just need the base name
- if differences == []:
- return pdfdoc.PDFName(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)
- if baseEncodingName in ('MacRomanEncoding','MacExpertEncoding','WinAnsiEncoding'):
- #https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf page 263
- D["BaseEncoding"] = pdfdoc.PDFName(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, substitutionFonts=None):
- self.fontName = name
- face = self.face = getTypeFace(faceName)
- self.encoding= getEncoding(encName)
- self.encName = encName
- self.substitutionFonts = (standardT1SubstitutionFonts
- if face.builtIn and face.requiredEncoding is None
- else substitutionFonts or [])
- 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
- e = self.encoding.makePDFObject()
- if not isStr(e) or e in ('/MacRomanEncoding','/MacExpertEncoding','/WinAnsiEncoding'):
- #https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf page 255
- pdfFont.Encoding = e
- # 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)
- 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
- _postScriptNames2Unicode = None
- 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':
- global _postScriptNames2Unicode
- if _postScriptNames2Unicode is None:
- try:
- from reportlab.pdfbase._glyphlist import _glyphname2unicode
- _postScriptNames2Unicode = _glyphname2unicode
- del _glyphname2unicode
- except:
- _postScriptNames2Unicode = {}
- raise ValueError(
- "cannot import module reportlab.pdfbase._glyphlist module\n"
- "you can obtain a version from here\n"
- "https://www.reportlab.com/ftp/_glyphlist.py\n"
- )
- names = [None] * 256
- ex = {}
- rex = {}
- for (code, width, name) in glyphData:
- if 0<=code<=255:
- names[code] = name
- u = _postScriptNames2Unicode.get(name,None)
- if u is not None:
- rex[code] = u
- ex[u] = code
- encName = encodings.normalize_encoding('rl-dynamic-%s-encoding' % self.name)
- rl_codecs.RL_Codecs.add_dynamic_codec(encName,ex,rex)
- 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)
- rl_codecs.RL_Codecs.reset_dynamic_codecs()
- from reportlab.rl_config import register_reset
- register_reset(_reset)
- del register_reset
- if __name__=='__main__':
- test()
- testStringWidthAlgorithms()
|