12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313 |
- #Copyright ReportLab Europe Ltd. 2000-2017
- #see license.txt for license details
- __version__ = '$Id$'
- __doc__="""TrueType font support
- This defines classes to represent TrueType fonts. They know how to calculate
- their own width and how to write themselves into PDF files. They support
- subsetting and embedding and can represent all 16-bit Unicode characters.
- Note on dynamic fonts
- ---------------------
- Usually a Font in ReportLab corresponds to a fixed set of PDF objects (Font,
- FontDescriptor, Encoding). But with dynamic font subsetting a single TTFont
- will result in a number of Font/FontDescriptor/Encoding object sets, and the
- contents of those will depend on the actual characters used for printing.
- To support dynamic font subsetting a concept of "dynamic font" was introduced.
- Dynamic Fonts have a _dynamicFont attribute set to 1.
- Dynamic fonts have the following additional functions::
- def splitString(self, text, doc):
- '''Splits text into a number of chunks, each of which belongs to a
- single subset. Returns a list of tuples (subset, string). Use
- subset numbers with getSubsetInternalName. Doc is used to identify
- a document so that different documents may have different dynamically
- constructed subsets.'''
- def getSubsetInternalName(self, subset, doc):
- '''Returns the name of a PDF Font object corresponding to a given
- subset of this dynamic font. Use this function instead of
- PDFDocument.getInternalFontName.'''
- You must never call PDFDocument.getInternalFontName for dynamic fonts.
- If you have a traditional static font, mapping to PDF text output operators
- is simple::
- '%s 14 Tf (%s) Tj' % (getInternalFontName(psfontname), text)
- If you have a dynamic font, use this instead::
- for subset, chunk in font.splitString(text, doc):
- '%s 14 Tf (%s) Tj' % (font.getSubsetInternalName(subset, doc), chunk)
- (Tf is a font setting operator and Tj is a text ouput operator. You should
- also escape invalid characters in Tj argument, see TextObject._formatText.
- Oh, and that 14 up there is font size.)
- Canvas and TextObject have special support for dynamic fonts.
- """
- from struct import pack, unpack, error as structError
- from reportlab.lib.utils import getBytesIO, bytestr, isUnicode, char2int, bytesT, isStr, isBytes
- from reportlab.pdfbase import pdfmetrics, pdfdoc
- from reportlab import rl_config
- from reportlab.lib.rl_accel import hex32, add32, calcChecksum, instanceStringWidthTTF
- from collections import namedtuple
- import os, time
- class TTFError(pdfdoc.PDFError):
- "TrueType font exception"
- pass
- def SUBSETN(n,table=bytes.maketrans(b'0123456789',b'ABCDEFGIJK')):
- return bytes('%6.6d'%n,'ASCII').translate(table)
- #
- # Helpers
- #
- def makeToUnicodeCMap(fontname, subset):
- """Creates a ToUnicode CMap for a given subset. See Adobe
- _PDF_Reference (ISBN 0-201-75839-3) for more information."""
- cmap = [
- "/CIDInit /ProcSet findresource begin",
- "12 dict begin",
- "begincmap",
- "/CIDSystemInfo",
- "<< /Registry (%s)" % fontname,
- "/Ordering (%s)" % fontname,
- "/Supplement 0",
- ">> def",
- "/CMapName /%s def" % fontname,
- "/CMapType 2 def",
- "1 begincodespacerange",
- "<00> <%02X>" % (len(subset) - 1),
- "endcodespacerange",
- "%d beginbfchar" % len(subset)
- ] + ["<%02X> <%04X>" % (i,v) for i,v in enumerate(subset)] + [
- "endbfchar",
- "endcmap",
- "CMapName currentdict /CMap defineresource pop",
- "end",
- "end"
- ]
- return '\n'.join(cmap)
- def splice(stream, offset, value):
- """Splices the given value into stream at the given offset and
- returns the resulting stream (the original is unchanged)"""
- return stream[:offset] + value + stream[offset + len(value):]
- def _set_ushort(stream, offset, value):
- """Writes the given unsigned short value into stream at the given
- offset and returns the resulting stream (the original is unchanged)"""
- return splice(stream, offset, pack(">H", value))
- #
- # TrueType font handling
- #
- GF_ARG_1_AND_2_ARE_WORDS = 1 << 0
- GF_ARGS_ARE_XY_VALUES = 1 << 1
- GF_ROUND_XY_TO_GRID = 1 << 2
- GF_WE_HAVE_A_SCALE = 1 << 3
- GF_RESERVED = 1 << 4
- GF_MORE_COMPONENTS = 1 << 5
- GF_WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6
- GF_WE_HAVE_A_TWO_BY_TWO = 1 << 7
- GF_WE_HAVE_INSTRUCTIONS = 1 << 8
- GF_USE_MY_METRICS = 1 << 9
- GF_OVERLAP_COMPOUND = 1 << 10
- GF_SCALED_COMPONENT_OFFSET = 1 << 11
- GF_UNSCALED_COMPONENT_OFFSET = 1 << 12
- _cached_ttf_dirs={}
- def _ttf_dirs(*roots):
- R = _cached_ttf_dirs.get(roots,None)
- if R is None:
- join = os.path.join
- realpath = os.path.realpath
- R = []
- aR = R.append
- for root in roots:
- for r, d, f in os.walk(root,followlinks=True):
- s = realpath(r)
- if s not in R: aR(s)
- for s in d:
- s = realpath(join(r,s))
- if s not in R: aR(s)
- _cached_ttf_dirs[roots] = R
- return R
- def TTFOpenFile(fn):
- '''Opens a TTF file possibly after searching TTFSearchPath
- returns (filename,file)
- '''
- from reportlab.lib.utils import rl_isfile, open_for_read
- try:
- f = open_for_read(fn,'rb')
- return fn, f
- except IOError:
- import os
- if not os.path.isabs(fn):
- for D in _ttf_dirs(*rl_config.TTFSearchPath):
- tfn = os.path.join(D,fn)
- if rl_isfile(tfn):
- f = open_for_read(tfn,'rb')
- return tfn, f
- raise TTFError('Can\'t open file "%s"' % fn)
- class TTFontParser:
- "Basic TTF file parser"
- ttfVersions = (0x00010000,0x74727565,0x74746366)
- ttcVersions = (0x00010000,0x00020000)
- fileKind='TTF'
- def __init__(self, file, validate=0,subfontIndex=0):
- """Loads and parses a TrueType font file. file can be a filename or a
- file object. If validate is set to a false values, skips checksum
- validation. This can save time, especially if the font is large.
- """
- self.validate = validate
- self.readFile(file)
- isCollection = self.readHeader()
- if isCollection:
- self.readTTCHeader()
- self.getSubfont(subfontIndex)
- else:
- if self.validate: self.checksumFile()
- self.readTableDirectory()
- self.subfontNameX = b''
- def readTTCHeader(self):
- self.ttcVersion = self.read_ulong()
- self.fileKind = 'TTC'
- self.ttfVersions = self.ttfVersions[:-1]
- if self.ttcVersion not in self.ttcVersions:
- raise TTFError('"%s" is not a %s file: can\'t read version 0x%8.8x' %(self.filename,self.fileKind,self.ttcVersion))
- self.numSubfonts = self.read_ulong()
- self.subfontOffsets = []
- a = self.subfontOffsets.append
- for i in range(self.numSubfonts):
- a(self.read_ulong())
- def getSubfont(self,subfontIndex):
- if self.fileKind!='TTC':
- raise TTFError('"%s" is not a TTC file: use this method' % (self.filename,self.fileKind))
- try:
- pos = self.subfontOffsets[subfontIndex]
- except IndexError:
- raise TTFError('TTC file "%s": bad subfontIndex %s not in [0,%d]' % (self.filename,subfontIndex,self.numSubfonts-1))
- self.seek(pos)
- self.readHeader()
- self.readTableDirectory()
- self.subfontNameX = bytestr('-'+str(subfontIndex))
- def readTableDirectory(self):
- try:
- self.numTables = self.read_ushort()
- self.searchRange = self.read_ushort()
- self.entrySelector = self.read_ushort()
- self.rangeShift = self.read_ushort()
- # Read table directory
- self.table = {}
- self.tables = []
- for n in range(self.numTables):
- record = {}
- record['tag'] = self.read_tag()
- record['checksum'] = self.read_ulong()
- record['offset'] = self.read_ulong()
- record['length'] = self.read_ulong()
- self.tables.append(record)
- self.table[record['tag']] = record
- except:
- raise TTFError('Corrupt %s file "%s" cannot read Table Directory' % (self.fileKind, self.filename))
- if self.validate: self.checksumTables()
- def readHeader(self):
- '''read the sfnt header at the current position'''
- try:
- self.version = version = self.read_ulong()
- except:
- raise TTFError('"%s" is not a %s file: can\'t read version' %(self.filename,self.fileKind))
- if version==0x4F54544F:
- raise TTFError('%s file "%s": postscript outlines are not supported'%(self.fileKind,self.filename))
- if version not in self.ttfVersions:
- raise TTFError('Not a recognized TrueType font: version=0x%8.8X' % version)
- return version==self.ttfVersions[-1]
- def readFile(self,f):
- if not hasattr(self,'_ttf_data'):
- if hasattr(f,'read'):
- self.filename = getattr(f,'name','(ttf)') #good idea Marius
- self._ttf_data = f.read()
- else:
- self.filename, f = TTFOpenFile(f)
- self._ttf_data = f.read()
- f.close()
- self._pos = 0
- def checksumTables(self):
- # Check the checksums for all tables
- for t in self.tables:
- table = self.get_chunk(t['offset'], t['length'])
- checksum = calcChecksum(table)
- if t['tag'] == 'head':
- adjustment = unpack('>l', table[8:8+4])[0]
- checksum = add32(checksum, -adjustment)
- xchecksum = t['checksum']
- if xchecksum != checksum:
- raise TTFError('TTF file "%s": invalid checksum %s table: %s (expected %s)' % (self.filename,hex32(checksum),t['tag'],hex32(xchecksum)))
- def checksumFile(self):
- # Check the checksums for the whole file
- checksum = calcChecksum(self._ttf_data)
- if 0xB1B0AFBA!=checksum:
- raise TTFError('TTF file "%s": invalid checksum %s (expected 0xB1B0AFBA) len: %d &3: %d' % (self.filename,hex32(checksum),len(self._ttf_data),(len(self._ttf_data)&3)))
- def get_table_pos(self, tag):
- "Returns the offset and size of a given TTF table."
- offset = self.table[tag]['offset']
- length = self.table[tag]['length']
- return (offset, length)
- def seek(self, pos):
- "Moves read pointer to a given offset in file."
- self._pos = pos
- def skip(self, delta):
- "Skip the given number of bytes."
- self._pos = self._pos + delta
- def seek_table(self, tag, offset_in_table = 0):
- """Moves read pointer to the given offset within a given table and
- returns absolute offset of that position in the file."""
- self._pos = self.get_table_pos(tag)[0] + offset_in_table
- return self._pos
- def read_tag(self):
- "Read a 4-character tag"
- self._pos += 4
- return str(self._ttf_data[self._pos - 4:self._pos],'utf8')
- def get_chunk(self, pos, length):
- "Return a chunk of raw data at given position"
- return bytes(self._ttf_data[pos:pos+length])
- def read_uint8(self):
- self._pos += 1
- return int(self._ttf_data[self._pos-1])
- def read_ushort(self):
- "Reads an unsigned short"
- self._pos += 2
- return unpack('>H',self._ttf_data[self._pos-2:self._pos])[0]
- def read_ulong(self):
- "Reads an unsigned long"
- self._pos += 4
- return unpack('>L',self._ttf_data[self._pos - 4:self._pos])[0]
- def read_short(self):
- "Reads a signed short"
- self._pos += 2
- try:
- return unpack('>h',self._ttf_data[self._pos-2:self._pos])[0]
- except structError as error:
- raise TTFError(error)
- def get_ushort(self, pos):
- "Return an unsigned short at given position"
- return unpack('>H',self._ttf_data[pos:pos+2])[0]
- def get_ulong(self, pos):
- "Return an unsigned long at given position"
- return unpack('>L',self._ttf_data[pos:pos+4])[0]
- def get_table(self, tag):
- "Return the given TTF table"
- pos, length = self.get_table_pos(tag)
- return self._ttf_data[pos:pos+length]
- class TTFontMaker:
- "Basic TTF file generator"
- def __init__(self):
- "Initializes the generator."
- self.tables = {}
- def add(self, tag, data):
- "Adds a table to the TTF file."
- if tag == 'head':
- data = splice(data, 8, b'\0\0\0\0')
- self.tables[tag] = data
- def makeStream(self):
- "Finishes the generation and returns the TTF file as a string"
- stm = getBytesIO()
- write = stm.write
- tables = self.tables
- numTables = len(tables)
- searchRange = 1
- entrySelector = 0
- while searchRange * 2 <= numTables:
- searchRange = searchRange * 2
- entrySelector = entrySelector + 1
- searchRange = searchRange * 16
- rangeShift = numTables * 16 - searchRange
- # Header
- write(pack(">lHHHH", 0x00010000, numTables, searchRange,
- entrySelector, rangeShift))
- # Table directory
- offset = 12 + numTables * 16
- wStr = lambda x:write(bytes(tag,'latin1'))
- tables_items = list(sorted(tables.items()))
- for tag, data in tables_items:
- if tag == 'head':
- head_start = offset
- checksum = calcChecksum(data)
- wStr(tag)
- write(pack(">LLL", checksum, offset, len(data)))
- paddedLength = (len(data)+3)&~3
- offset = offset + paddedLength
- # Table data
- for tag, data in tables_items:
- data += b"\0\0\0"
- write(data[:len(data)&~3])
- checksum = calcChecksum(stm.getvalue())
- checksum = add32(0xB1B0AFBA, -checksum)
- stm.seek(head_start + 8)
- write(pack('>L', checksum))
- return stm.getvalue()
- #this is used in the cmap encoding fmt==2 case
- CMapFmt2SubHeader = namedtuple('CMapFmt2SubHeader', 'firstCode entryCount idDelta idRangeOffset')
- class TTFNameBytes(bytesT):
- '''class used to return named strings'''
- def __new__(cls,b,enc='utf8'):
- try:
- ustr = b.decode(enc)
- except:
- ustr = b.decode('latin1')
- self = bytesT.__new__(cls,ustr.encode('utf8'))
- self.ustr = ustr
- return self
-
- class TTFontFile(TTFontParser):
- "TTF file parser and generator"
- _agfnc = 0
- _agfnm = {}
- def __init__(self, file, charInfo=1, validate=0,subfontIndex=0):
- """Loads and parses a TrueType font file.
- file can be a filename or a file object. If validate is set to a false
- values, skips checksum validation. This can save time, especially if
- the font is large. See TTFontFile.extractInfo for more information.
- """
- if isStr(subfontIndex): #bytes or unicode
- sfi = 0
- __dict__ = self.__dict__.copy()
- while True:
- TTFontParser.__init__(self, file, validate=validate,subfontIndex=sfi)
- numSubfonts = self.numSubfonts = self.read_ulong()
- self.extractInfo(charInfo)
- if (isBytes(subfontIndex) and subfontIndex==self.name
- or subfontIndex==self.name.ustr): #we found it
- return
- if not sfi:
- __dict__.update(dict(_ttf_data=self._ttf_data, filename=self.filename))
- sfi += 1
- if sfi>=numSubfonts:
- raise ValueError('cannot find %r subfont %r' % (self.filename, subfontIndex))
- self.__dict__.clear()
- self.__dict__.update(__dict__)
- else:
- TTFontParser.__init__(self, file, validate=validate,subfontIndex=subfontIndex)
- self.extractInfo(charInfo)
- def extractInfo(self, charInfo=1):
- """
- Extract typographic information from the loaded font file.
- The following attributes will be set::
-
- name PostScript font name
- flags Font flags
- ascent Typographic ascender in 1/1000ths of a point
- descent Typographic descender in 1/1000ths of a point
- capHeight Cap height in 1/1000ths of a point (0 if not available)
- bbox Glyph bounding box [l,t,r,b] in 1/1000ths of a point
- _bbox Glyph bounding box [l,t,r,b] in unitsPerEm
- unitsPerEm Glyph units per em
- italicAngle Italic angle in degrees ccw
- stemV stem weight in 1/1000ths of a point (approximate)
-
- If charInfo is true, the following will also be set::
-
- defaultWidth default glyph width in 1/1000ths of a point
- charWidths dictionary of character widths for every supported UCS character
- code
-
- This will only work if the font has a Unicode cmap (platform 3,
- encoding 1, format 4 or platform 0 any encoding format 4). Setting
- charInfo to false avoids this requirement
-
- """
- # name - Naming table
- name_offset = self.seek_table("name")
- format = self.read_ushort()
- if format != 0:
- raise TTFError("Unknown name table format (%d)" % format)
- numRecords = self.read_ushort()
- string_data_offset = name_offset + self.read_ushort()
- names = {1:None,2:None,3:None,4:None,6:None}
- K = list(names.keys())
- nameCount = len(names)
- for i in range(numRecords):
- platformId = self.read_ushort()
- encodingId = self.read_ushort()
- languageId = self.read_ushort()
- nameId = self.read_ushort()
- length = self.read_ushort()
- offset = self.read_ushort()
- if nameId not in K: continue
- N = None
- if platformId == 3 and encodingId == 1 and languageId == 0x409: # Microsoft, Unicode, US English, PS Name
- opos = self._pos
- try:
- self.seek(string_data_offset + offset)
- if length % 2 != 0:
- raise TTFError("PostScript name is UTF-16BE string of odd length")
- N = TTFNameBytes(self.get_chunk(string_data_offset + offset, length),'utf_16_be')
- finally:
- self._pos = opos
- elif platformId == 1 and encodingId == 0 and languageId == 0: # Macintosh, Roman, English, PS Name
- # According to OpenType spec, if PS name exists, it must exist
- # both in MS Unicode and Macintosh Roman formats. Apparently,
- # you can find live TTF fonts which only have Macintosh format.
- N = TTFNameBytes(self.get_chunk(string_data_offset + offset, length),'mac_roman')
- if N and names[nameId]==None:
- names[nameId] = N
- nameCount -= 1
- if nameCount==0: break
- if names[6] is not None:
- psName = names[6]
- elif names[4] is not None:
- psName = names[4]
- # Fine, one last try before we bail.
- elif names[1] is not None:
- psName = names[1]
- else:
- psName = None
- # Don't just assume, check for None since some shoddy fonts cause crashes here...
- if not psName:
- if rl_config.autoGenerateTTFMissingTTFName:
- fn = self.filename
- if fn:
- bfn = os.path.splitext(os.path.basename(fn))[0]
- if not fn:
- psName = bytestr('_RL_%s_%s_TTF' % (time.time(), self.__class__._agfnc))
- self.__class__._agfnc += 1
- else:
- psName = self._agfnm.get(fn,'')
- if not psName:
- if bfn:
- psName = bytestr('_RL_%s_TTF' % bfn)
- else:
- psName = bytestr('_RL_%s_%s_TTF' % (time.time(), self.__class__._agfnc))
- self.__class__._agfnc += 1
- self._agfnm[fn] = psName
- else:
- raise TTFError("Could not find PostScript font name")
- psName = psName.__class__(psName.replace(b" ", b"-")) #Dinu Gherman's fix for font names with spaces
- for c in psName:
- if char2int(c)>126 or c in b' [](){}<>/%':
- raise TTFError("psName=%r contains invalid character %s" % (psName,ascii(c)))
- self.name = psName
- self.familyName = names[1] or psName
- self.styleName = names[2] or 'Regular'
- self.fullName = names[4] or psName
- self.uniqueFontID = names[3] or psName
- # head - Font header table
- try:
- self.seek_table("head")
- except:
- raise TTFError('head table not found ttf name=%s' % self.name)
- ver_maj, ver_min = self.read_ushort(), self.read_ushort()
- if ver_maj != 1:
- raise TTFError('Unknown head table version %d.%04x' % (ver_maj, ver_min))
- self.fontRevision = self.read_ushort(), self.read_ushort()
- self.skip(4)
- magic = self.read_ulong()
- if magic != 0x5F0F3CF5:
- raise TTFError('Invalid head table magic %04x' % magic)
- self.skip(2)
- self.unitsPerEm = unitsPerEm = self.read_ushort()
- scale = lambda x, unitsPerEm=unitsPerEm: x * 1000. / unitsPerEm
- self.skip(16)
- xMin = self.read_short()
- yMin = self.read_short()
- xMax = self.read_short()
- yMax = self.read_short()
- self.bbox = list(map(scale, [xMin, yMin, xMax, yMax]))
- self.skip(3*2)
- indexToLocFormat = self.read_ushort()
- glyphDataFormat = self.read_ushort()
- # OS/2 - OS/2 and Windows metrics table
- # (needs data from head table)
- subsettingAllowed = True
- if "OS/2" in self.table:
- self.seek_table("OS/2")
- version = self.read_ushort()
- self.skip(2)
- usWeightClass = self.read_ushort()
- self.skip(2)
- fsType = self.read_ushort()
- if fsType==0x0002 or (fsType & 0x0300):
- subsettingAllowed = os.path.basename(self.filename) not in rl_config.allowTTFSubsetting
- self.skip(58) #11*2 + 10 + 4*4 + 4 + 3*2
- sTypoAscender = self.read_short()
- sTypoDescender = self.read_short()
- self.ascent = scale(sTypoAscender) # XXX: for some reason it needs to be multiplied by 1.24--1.28
- self.descent = scale(sTypoDescender)
- if version > 1:
- self.skip(16) #3*2 + 2*4 + 2
- sCapHeight = self.read_short()
- self.capHeight = scale(sCapHeight)
- else:
- self.capHeight = self.ascent
- else:
- # Microsoft TTFs require an OS/2 table; Apple ones do not. Try to
- # cope. The data is not very important anyway.
- usWeightClass = 500
- self.ascent = scale(yMax)
- self.descent = scale(yMin)
- self.capHeight = self.ascent
- # There's no way to get stemV from a TTF file short of analyzing actual outline data
- # This fuzzy formula is taken from pdflib sources, but we could just use 0 here
- self.stemV = 50 + int((usWeightClass / 65.0) ** 2)
- # post - PostScript table
- # (needs data from OS/2 table)
- self.seek_table("post")
- ver_maj, ver_min = self.read_ushort(), self.read_ushort()
- if ver_maj not in (1, 2, 3, 4):
- # Adobe/MS documents 1, 2, 2.5, 3; Apple also has 4.
- # From Apple docs it seems that we do not need to care
- # about the exact version, so if you get this error, you can
- # try to remove this check altogether.
- raise TTFError('Unknown post table version %d.%04x' % (ver_maj, ver_min))
- self.italicAngle = self.read_short() + self.read_ushort() / 65536.0
- self.underlinePosition = self.read_short()
- self.underlineThickness = self.read_short()
- isFixedPitch = self.read_ulong()
- self.flags = FF_SYMBOLIC # All fonts that contain characters
- # outside the original Adobe character
- # set are considered "symbolic".
- if self.italicAngle!= 0:
- self.flags = self.flags | FF_ITALIC
- if usWeightClass >= 600: # FW_REGULAR == 500, FW_SEMIBOLD == 600
- self.flags = self.flags | FF_FORCEBOLD
- if isFixedPitch:
- self.flags = self.flags | FF_FIXED
- # XXX: FF_SERIF? FF_SCRIPT? FF_ALLCAP? FF_SMALLCAP?
- # hhea - Horizontal header table
- self.seek_table("hhea")
- ver_maj, ver_min = self.read_ushort(), self.read_ushort()
- if ver_maj != 1:
- raise TTFError('Unknown hhea table version %d.%04x' % (ver_maj, ver_min))
- self.skip(28)
- metricDataFormat = self.read_ushort()
- if metricDataFormat != 0:
- raise TTFError('Unknown horizontal metric data format (%d)' % metricDataFormat)
- numberOfHMetrics = self.read_ushort()
- if numberOfHMetrics == 0:
- raise TTFError('Number of horizontal metrics is 0')
- # maxp - Maximum profile table
- self.seek_table("maxp")
- ver_maj, ver_min = self.read_ushort(), self.read_ushort()
- if ver_maj != 1:
- raise TTFError('Unknown maxp table version %d.%04x' % (ver_maj, ver_min))
- self.numGlyphs = numGlyphs = self.read_ushort()
- if not subsettingAllowed:
- if self.numGlyphs>0xFF:
- raise TTFError('Font does not allow subsetting/embedding (%04X)' % fsType)
- else:
- self._full_font = True
- else:
- self._full_font = False
- if not charInfo:
- self.charToGlyph = None
- self.defaultWidth = None
- self.charWidths = None
- return
- if glyphDataFormat != 0:
- raise TTFError('Unknown glyph data format (%d)' % glyphDataFormat)
- # cmap - Character to glyph index mapping table
- cmap_offset = self.seek_table("cmap")
- cmapVersion = self.read_ushort()
- cmapTableCount = self.read_ushort()
- if cmapTableCount==0 and cmapVersion!=0:
- cmapTableCount, cmapVersion = cmapVersion, cmapTableCount
- encoffs = None
- enc = 0
- for n in range(cmapTableCount):
- platform = self.read_ushort()
- encoding = self.read_ushort()
- offset = self.read_ulong()
- if platform==3:
- enc = 1
- encoffs = offset
- elif platform==1 and encoding==0 and enc!=1:
- enc = 2
- encoffs = offset
- elif platform==1 and encoding==1:
- enc = 1
- encoffs = offset
- elif platform==0 and encoding!=5:
- enc = 1
- encoffs = offset
- if encoffs is None:
- raise TTFError('could not find a suitable cmap encoding')
- encoffs += cmap_offset
- self.seek(encoffs)
- fmt = self.read_ushort()
- self.charToGlyph = charToGlyph = {}
- glyphToChar = {}
- if fmt in (13,12,10,8):
- self.skip(2) #padding
- length = self.read_ulong()
- lang = self.read_ulong()
- else:
- length = self.read_ushort()
- lang = self.read_ushort()
- if fmt==0:
- T = [self.read_uint8() for i in range(length-6)]
- for unichar in range(min(256,self.numGlyphs,len(table))):
- glyph = T[glyph]
- charToGlyph[unichar] = glyph
- glyphToChar.setdefault(glyph,[]).append(unichar)
- elif fmt==4:
- limit = encoffs + length
- segCount = int(self.read_ushort() / 2.0)
- self.skip(6)
- endCount = [self.read_ushort() for _ in range(segCount)]
- self.skip(2)
- startCount = [self.read_ushort() for _ in range(segCount)]
- idDelta = [self.read_short() for _ in range(segCount)]
- idRangeOffset_start = self._pos
- idRangeOffset = [self.read_ushort() for _ in range(segCount)]
- # Now it gets tricky.
- for n in range(segCount):
- for unichar in range(startCount[n], endCount[n] + 1):
- if idRangeOffset[n] == 0:
- glyph = (unichar + idDelta[n]) & 0xFFFF
- else:
- offset = (unichar - startCount[n]) * 2 + idRangeOffset[n]
- offset = idRangeOffset_start + 2 * n + offset
- if offset >= limit:
- # workaround for broken fonts (like Thryomanes)
- glyph = 0
- else:
- glyph = self.get_ushort(offset)
- if glyph != 0:
- glyph = (glyph + idDelta[n]) & 0xFFFF
- charToGlyph[unichar] = glyph
- glyphToChar.setdefault(glyph,[]).append(unichar)
- elif fmt==6:
- first = self.read_ushort()
- count = self.read_ushort()
- for glyph in range(first,first+count):
- unichar = self.read_ushort()
- charToGlyph[unichar] = glyph
- glyphToChar.setdefault(glyph,[]).append(unichar)
- elif fmt==10:
- first = self.read_ulong()
- count = self.read_ulong()
- for glyph in range(first,first+count):
- unichar = self.read_ushort()
- charToGlyph[unichar] = glyph
- glyphToChar.setdefault(glyph,[]).append(unichar)
- elif fmt==12:
- segCount = self.read_ulong()
- for n in range(segCount):
- start = self.read_ulong()
- end = self.read_ulong()
- inc = self.read_ulong() - start
- for unichar in range(start,end+1):
- glyph = unichar + inc
- charToGlyph[unichar] = glyph
- glyphToChar.setdefault(glyph,[]).append(unichar)
- elif fmt==13:
- segCount = self.read_ulong()
- for n in range(segCount):
- start = self.read_ulong()
- end = self.read_ulong()
- gid = self.read_ulong()
- for unichar in range(start,end+1):
- charToGlyph[unichar] = gid
- glyphToChar.setdefault(gid,[]).append(unichar)
- elif fmt==2:
- T = [self.read_ushort() for i in range(256)] #subheader keys
- maxSHK = max(T)
- SH = []
- for i in range(maxSHK+1):
- firstCode = self.read_ushort()
- entryCount = self.read_ushort()
- idDelta = self.read_ushort()
- idRangeOffset = (self.read_ushort()-(maxSHK-i)*8-2)>>1
- SH.append(CMapFmt2SubHeader(firstCode,entryCount,idDelta,idRangeOffset))
- #number of glyph indexes to read. it is the length of the entire subtable minus that bit we've read so far
- entryCount = (length-(self._pos-(cmap_offset+encoffs)))>>1
- glyphs = [self.read_short() for i in range(entryCount)]
- last = -1
- for unichar in range(256):
- if T[unichar]==0:
- #Special case, single byte encoding entry, look unichar up in subhead
- if last!=-1:
- glyph = 0
- elif (unichar<SH[0].firstCode or unichar>=SH[0].firstCode+SH[0].entryCount or
- SH[0].idRangeOffset+(unichar-SH[0].firstCode)>=entryCount):
- glyph = 0
- else:
- glyph = glyphs[SH[0].idRangeOffset+(unichar-SH[0].firstCode)]
- if glyph!=0:
- glyph += SH[0].idDelta
- #assume the single byte codes are ascii
- if glyph!=0 and glyph<self.numGlyphs:
- charToGlyph[unichar] = glyph
- glyphToChar.setdefault(glyph,[]).append(unichar)
- else:
- k = T[unichar]
- for j in range(SH[k].entryCount):
- if SH[k].idRangeOffset+j>=entryCount:
- glyph = 0
- else:
- glyph = glyphs[SH[k].idRangeOffset+j]
- if glyph!= 0:
- glyph += SH[k].idDelta
- if glyph!=0 and glyph<self.numGlyphs:
- enc = (unichar<<8)|(j+SH[k].firstCode)
- charToGlyph[enc] = glyph
- glyphToChar.setdefault(glyph,[]).append(enc)
- if last==-1:
- last = unichar
- else:
- raise ValueError('Unsupported cmap encoding format %d' % fmt)
- # hmtx - Horizontal metrics table
- # (needs data from hhea, maxp, and cmap tables)
- self.seek_table("hmtx")
- aw = None
- self.charWidths = charWidths = {}
- self.hmetrics = []
- for glyph in range(numberOfHMetrics):
- # advance width and left side bearing. lsb is actually signed
- # short, but we don't need it anyway (except for subsetting)
- aw, lsb = self.read_ushort(), self.read_ushort()
- self.hmetrics.append((aw, lsb))
- aw = scale(aw)
- if glyph == 0:
- self.defaultWidth = aw
- if glyph in glyphToChar:
- for char in glyphToChar[glyph]:
- charWidths[char] = aw
- for glyph in range(numberOfHMetrics, numGlyphs):
- # the rest of the table only lists advance left side bearings.
- # so we reuse aw set by the last iteration of the previous loop
- lsb = self.read_ushort()
- self.hmetrics.append((aw, lsb))
- if glyph in glyphToChar:
- for char in glyphToChar[glyph]:
- charWidths[char] = aw
- # loca - Index to location
- if 'loca' not in self.table: raise TTFError('missing location table')
- self.seek_table('loca')
- self.glyphPos = []
- if indexToLocFormat == 0:
- for n in range(numGlyphs + 1):
- self.glyphPos.append(self.read_ushort() << 1)
- elif indexToLocFormat == 1:
- for n in range(numGlyphs + 1):
- self.glyphPos.append(self.read_ulong())
- else:
- raise TTFError('Unknown location table format (%d)' % indexToLocFormat)
- if 0x20 in charToGlyph:
- charToGlyph[0xa0] = charToGlyph[0x20]
- charWidths[0xa0] = charWidths[0x20]
- elif 0xa0 in charToGlyph:
- charToGlyph[0x20] = charToGlyph[0xa0]
- charWidths[0x20] = charWidths[0xa0]
- # Subsetting
- def makeSubset(self, subset):
- """Create a subset of a TrueType font"""
- output = TTFontMaker()
- # Build a mapping of glyphs in the subset to glyph numbers in
- # the original font. Also build a mapping of UCS codes to
- # glyph values in the new font.
- # Start with 0 -> 0: "missing character"
- glyphMap = [0] # new glyph index -> old glyph index
- glyphSet = {0:0} # old glyph index -> new glyph index
- codeToGlyph = {} # unicode -> new glyph index
- for code in subset:
- if code in self.charToGlyph:
- originalGlyphIdx = self.charToGlyph[code]
- else:
- originalGlyphIdx = 0
- if originalGlyphIdx not in glyphSet:
- glyphSet[originalGlyphIdx] = len(glyphMap)
- glyphMap.append(originalGlyphIdx)
- codeToGlyph[code] = glyphSet[originalGlyphIdx]
- # Also include glyphs that are parts of composite glyphs
- start = self.get_table_pos('glyf')[0]
- n = 0
- while n < len(glyphMap):
- originalGlyphIdx = glyphMap[n]
- glyphPos = self.glyphPos[originalGlyphIdx]
- glyphLen = self.glyphPos[originalGlyphIdx + 1] - glyphPos
- n += 1
- if not glyphLen: continue
- self.seek(start + glyphPos)
- numberOfContours = self.read_short()
- if numberOfContours < 0:
- # composite glyph
- self.skip(8)
- flags = GF_MORE_COMPONENTS
- while flags & GF_MORE_COMPONENTS:
- flags = self.read_ushort()
- glyphIdx = self.read_ushort()
- if glyphIdx not in glyphSet:
- glyphSet[glyphIdx] = len(glyphMap)
- glyphMap.append(glyphIdx)
- if flags & GF_ARG_1_AND_2_ARE_WORDS:
- self.skip(4)
- else:
- self.skip(2)
- if flags & GF_WE_HAVE_A_SCALE:
- self.skip(2)
- elif flags & GF_WE_HAVE_AN_X_AND_Y_SCALE:
- self.skip(4)
- elif flags & GF_WE_HAVE_A_TWO_BY_TWO:
- self.skip(8)
- # The following tables are simply copied from the original
- for tag in ('name', 'OS/2', 'cvt ', 'fpgm', 'prep'):
- try:
- output.add(tag, self.get_table(tag))
- except KeyError:
- # Apparently some of the tables are optional (cvt, fpgm, prep).
- # The lack of the required ones (name, OS/2) would have already
- # been caught before.
- pass
- # post - PostScript
- post = b"\x00\x03\x00\x00" + self.get_table('post')[4:16] + b"\x00" * 16
- output.add('post', post)
- numGlyphs = len(glyphMap)
- # hmtx - Horizontal Metrics
- hmtx = []
- for n in range(numGlyphs):
- aw, lsb = self.hmetrics[glyphMap[n]]
- hmtx.append(int(aw))
- hmtx.append(int(lsb))
- #work out n as 0 or first aw that's the start of a run
- n = len(hmtx)-2
- while n and hmtx[n]==hmtx[n-2]:
- n -= 2
- if not n: n = 2 #need at least one pair
- numberOfHMetrics = n>>1 #number of full H Metric pairs
- hmtx = hmtx[:n] + hmtx[n+1::2] #full pairs + all the trailing lsb's
- hmtx = pack(*([">%dH" % len(hmtx)] + hmtx))
- output.add('hmtx', hmtx)
- # hhea - Horizontal Header
- hhea = self.get_table('hhea')
- hhea = _set_ushort(hhea, 34, numberOfHMetrics)
- output.add('hhea', hhea)
- # maxp - Maximum Profile
- maxp = self.get_table('maxp')
- maxp = _set_ushort(maxp, 4, numGlyphs)
- output.add('maxp', maxp)
- # cmap - Character to glyph mapping
- # XXX maybe use format 0 if possible, not 6?
- entryCount = len(subset)
- length = 10 + entryCount * 2
- cmap = [0, 1, # version, number of tables
- 1, 0, 0,12, # platform, encoding, offset (hi,lo)
- 6, length, 0, # format, length, language
- 0,
- entryCount] + \
- list(map(codeToGlyph.get, subset))
- cmap = pack(*([">%dH" % len(cmap)] + cmap))
- output.add('cmap', cmap)
- # glyf - Glyph data
- glyphData = self.get_table('glyf')
- offsets = []
- glyf = []
- pos = 0
- for n in range(numGlyphs):
- offsets.append(pos)
- originalGlyphIdx = glyphMap[n]
- glyphPos = self.glyphPos[originalGlyphIdx]
- glyphLen = self.glyphPos[originalGlyphIdx + 1] - glyphPos
- data = glyphData[glyphPos:glyphPos+glyphLen]
- # Fix references in composite glyphs
- if glyphLen > 2 and unpack(">h", data[:2])[0] < 0:
- # composite glyph
- pos_in_glyph = 10
- flags = GF_MORE_COMPONENTS
- while flags & GF_MORE_COMPONENTS:
- flags = unpack(">H", data[pos_in_glyph:pos_in_glyph+2])[0]
- glyphIdx = unpack(">H", data[pos_in_glyph+2:pos_in_glyph+4])[0]
- data = _set_ushort(data, pos_in_glyph + 2, glyphSet[glyphIdx])
- pos_in_glyph = pos_in_glyph + 4
- if flags & GF_ARG_1_AND_2_ARE_WORDS:
- pos_in_glyph = pos_in_glyph + 4
- else:
- pos_in_glyph = pos_in_glyph + 2
- if flags & GF_WE_HAVE_A_SCALE:
- pos_in_glyph = pos_in_glyph + 2
- elif flags & GF_WE_HAVE_AN_X_AND_Y_SCALE:
- pos_in_glyph = pos_in_glyph + 4
- elif flags & GF_WE_HAVE_A_TWO_BY_TWO:
- pos_in_glyph = pos_in_glyph + 8
- glyf.append(data)
- pos = pos + glyphLen
- if pos % 4 != 0:
- padding = 4 - pos % 4
- glyf.append(b'\0' * padding)
- pos = pos + padding
- offsets.append(pos)
- output.add('glyf', b''.join(glyf))
- # loca - Index to location
- loca = []
- if (pos + 1) >> 1 > 0xFFFF:
- indexToLocFormat = 1 # long format
- for offset in offsets:
- loca.append(offset)
- loca = pack(*([">%dL" % len(loca)] + loca))
- else:
- indexToLocFormat = 0 # short format
- for offset in offsets:
- loca.append(offset >> 1)
- loca = pack(*([">%dH" % len(loca)] + loca))
- output.add('loca', loca)
- # head - Font header
- head = self.get_table('head')
- head = _set_ushort(head, 50, indexToLocFormat)
- output.add('head', head)
- return output.makeStream()
- #
- # TrueType font embedding
- #
- # PDF font flags (see PDF Reference Guide table 5.19)
- FF_FIXED = 1 << 1-1
- FF_SERIF = 1 << 2-1
- FF_SYMBOLIC = 1 << 3-1
- FF_SCRIPT = 1 << 4-1
- FF_NONSYMBOLIC = 1 << 6-1
- FF_ITALIC = 1 << 7-1
- FF_ALLCAP = 1 << 17-1
- FF_SMALLCAP = 1 << 18-1
- FF_FORCEBOLD = 1 << 19-1
- class TTFontFace(TTFontFile, pdfmetrics.TypeFace):
- """TrueType typeface.
- Conceptually similar to a single byte typeface, but the glyphs are
- identified by UCS character codes instead of glyph names."""
- def __init__(self, filename, validate=0, subfontIndex=0):
- "Loads a TrueType font from filename."
- pdfmetrics.TypeFace.__init__(self, None)
- TTFontFile.__init__(self, filename, validate=validate, subfontIndex=subfontIndex)
- def getCharWidth(self, code):
- "Returns the width of character U+<code>"
- return self.charWidths.get(code, self.defaultWidth)
- def addSubsetObjects(self, doc, fontname, subset):
- """Generate a TrueType font subset and add it to the PDF document.
- Returns a PDFReference to the new FontDescriptor object."""
- fontFile = pdfdoc.PDFStream()
- fontFile.content = self.makeSubset(subset)
- fontFile.dictionary['Length1'] = len(fontFile.content)
- if doc.compression:
- fontFile.filters = [pdfdoc.PDFZCompress]
- fontFileRef = doc.Reference(fontFile, 'fontFile:%s(%s)' % (self.filename, fontname))
- flags = self.flags & ~ FF_NONSYMBOLIC
- flags = flags | FF_SYMBOLIC
- fontDescriptor = pdfdoc.PDFDictionary({
- 'Type': '/FontDescriptor',
- 'Ascent': self.ascent,
- 'CapHeight': self.capHeight,
- 'Descent': self.descent,
- 'Flags': flags,
- 'FontBBox': pdfdoc.PDFArray(self.bbox),
- 'FontName': pdfdoc.PDFName(fontname),
- 'ItalicAngle': self.italicAngle,
- 'StemV': self.stemV,
- 'FontFile2': fontFileRef,
- })
- return doc.Reference(fontDescriptor, 'fontDescriptor:' + fontname)
- class TTEncoding:
- """Encoding for TrueType fonts (always UTF-8).
- TTEncoding does not directly participate in PDF object creation, since
- we need a number of different 8-bit encodings for every generated font
- subset. TTFont itself cares about that."""
- def __init__(self):
- self.name = "UTF-8"
- class TTFont:
- """Represents a TrueType font.
- Its encoding is always UTF-8.
- Note: you cannot use the same TTFont object for different documents
- at the same time.
- Example of usage:
- font = ttfonts.TTFont('PostScriptFontName', '/path/to/font.ttf')
- pdfmetrics.registerFont(font)
- canvas.setFont('PostScriptFontName', size)
- canvas.drawString(x, y, "Some text encoded in UTF-8")
- """
- class State:
- namePrefix = 'F'
- def __init__(self,asciiReadable=None,ttf=None):
- A = self.assignments = {}
- self.nextCode = 0
- self.internalName = None
- self.frozen = 0
- if getattr(getattr(ttf,'face',None),'_full_font',None):
- C = set(self.charToGlyph.keys())
- if 0xa0 in C: C.remove(0xa0)
- for n in range(256):
- if n in C:
- A[n] = n
- C.remove(n)
- for n in C:
- A[n] = n
- self.subsets = [[n for n in A]]
- self.frozen = True
- return
- if asciiReadable is None:
- asciiReadable = rl_config.ttfAsciiReadable
- if asciiReadable:
- # Let's add the first 128 unicodes to the 0th subset, so ' '
- # always has code 32 (for word spacing to work) and the ASCII
- # output is readable
- subset0 = list(range(128))
- self.subsets = [subset0]
- for n in subset0:
- A[n] = n
- self.nextCode = 128
- else:
- self.subsets = [[32]*33]
- A[32] = 32
- _multiByte = 1 # We want our own stringwidth
- _dynamicFont = 1 # We want dynamic subsetting
- def __init__(self, name, filename, validate=0, subfontIndex=0,asciiReadable=None):
- """Loads a TrueType font from filename.
- If validate is set to a false values, skips checksum validation. This
- can save time, especially if the font is large.
- """
- self.fontName = name
- self.face = TTFontFace(filename, validate=validate, subfontIndex=subfontIndex)
- self.encoding = TTEncoding()
- from weakref import WeakKeyDictionary
- self.state = WeakKeyDictionary()
- if asciiReadable is None:
- asciiReadable = rl_config.ttfAsciiReadable
- self._asciiReadable = asciiReadable
- def stringWidth(self,text,size,encoding='utf8'):
- return instanceStringWidthTTF(self,text,size,encoding)
- def _assignState(self,doc,asciiReadable=None,namePrefix=None):
- '''convenience function for those wishing to roll their own state properties'''
- if asciiReadable is None:
- asciiReadable = self._asciiReadable
- try:
- state = self.state[doc]
- except KeyError:
- state = self.state[doc] = TTFont.State(asciiReadable,self)
- if namePrefix is not None:
- state.namePrefix = namePrefix
- return state
- def splitString(self, text, doc, encoding='utf-8'):
- """Splits text into a number of chunks, each of which belongs to a
- single subset. Returns a list of tuples (subset, string). Use subset
- numbers with getSubsetInternalName. Doc is needed for distinguishing
- subsets when building different documents at the same time."""
- asciiReadable = self._asciiReadable
- try: state = self.state[doc]
- except KeyError: state = self.state[doc] = TTFont.State(asciiReadable,self)
- curSet = -1
- cur = []
- results = []
- if not isUnicode(text):
- text = text.decode('utf-8') # encoding defaults to utf-8
- assignments = state.assignments
- subsets = state.subsets
- reserveTTFNotdef = rl_config.reserveTTFNotdef
- for code in map(ord,text):
- if code==0xa0: code = 32 #map nbsp into space
- if code in assignments:
- n = assignments[code]
- else:
- if state.frozen:
- raise pdfdoc.PDFError("Font %s is already frozen, cannot add new character U+%04X" % (self.fontName, code))
- n = state.nextCode
- if n&0xFF==32:
- # make code 32 always be a space character
- if n!=32: subsets[n >> 8].append(32)
- state.nextCode += 1
- n = state.nextCode
- if n>32:
- if not(n&0xFF):
- if reserveTTFNotdef:
- subsets.append([0]) #force code 0 in as notdef
- state.nextCode += 1
- n = state.nextCode
- else:
- subsets.append([])
- subsets[n >> 8].append(code)
- else:
- subsets[0][n] = code
- state.nextCode += 1
- assignments[code] = n
- #subsets[n>>8].append(code)
- if (n >> 8) != curSet:
- if cur:
- results.append((curSet,bytes(cur)))
- curSet = (n >> 8)
- cur = []
- cur.append(n & 0xFF)
- if cur:
- results.append((curSet,bytes(cur)))
- return results
- def getSubsetInternalName(self, subset, doc):
- """Returns the name of a PDF Font object corresponding to a given
- subset of this dynamic font. Use this function instead of
- PDFDocument.getInternalFontName."""
- try: state = self.state[doc]
- except KeyError: state = self.state[doc] = TTFont.State(self._asciiReadable)
- if subset < 0 or subset >= len(state.subsets):
- raise IndexError('Subset %d does not exist in font %s' % (subset, self.fontName))
- if state.internalName is None:
- state.internalName = state.namePrefix +repr(len(doc.fontMapping) + 1)
- doc.fontMapping[self.fontName] = '/' + state.internalName
- doc.delayedFonts.append(self)
- return '/%s+%d' % (state.internalName, subset)
- def addObjects(self, doc):
- """Makes 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).
- This method creates a number of Font and FontDescriptor objects. Every
- FontDescriptor is a (no more than) 256 character subset of the original
- TrueType font."""
- try: state = self.state[doc]
- except KeyError: state = self.state[doc] = TTFont.State(self._asciiReadable)
- state.frozen = 1
- for n,subset in enumerate(state.subsets):
- internalName = self.getSubsetInternalName(n, doc)[1:]
- baseFontName = (b''.join((SUBSETN(n),b'+',self.face.name,self.face.subfontNameX))).decode('pdfdoc')
- pdfFont = pdfdoc.PDFTrueTypeFont()
- pdfFont.__Comment__ = 'Font %s subset %d' % (self.fontName, n)
- pdfFont.Name = internalName
- pdfFont.BaseFont = baseFontName
- pdfFont.FirstChar = 0
- pdfFont.LastChar = len(subset) - 1
- widths = list(map(self.face.getCharWidth, subset))
- pdfFont.Widths = pdfdoc.PDFArray(widths)
- cmapStream = pdfdoc.PDFStream()
- cmapStream.content = makeToUnicodeCMap(baseFontName, subset)
- if doc.compression:
- cmapStream.filters = [pdfdoc.PDFZCompress]
- pdfFont.ToUnicode = doc.Reference(cmapStream, 'toUnicodeCMap:' + baseFontName)
- pdfFont.FontDescriptor = self.face.addSubsetObjects(doc, baseFontName, subset)
- # link it in
- ref = doc.Reference(pdfFont, internalName)
- fontDict = doc.idToObject['BasicFonts'].dict
- fontDict[internalName] = pdfFont
- del self.state[doc]
- #preserve the initial values here
- def _reset():
- _cached_ttf_dirs.clear()
- from reportlab.rl_config import register_reset
- register_reset(_reset)
- del register_reset
|