12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659 |
- """cffLib: read/write Adobe CFF fonts
- OpenType fonts with PostScript outlines embed a completely independent
- font file in Adobe's *Compact Font Format*. So dealing with OpenType fonts
- requires also dealing with CFF. This module allows you to read and write
- fonts written in the CFF format.
- In 2016, OpenType 1.8 introduced the `CFF2 <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2>`_
- format which, along with other changes, extended the CFF format to deal with
- the demands of variable fonts. This module parses both original CFF and CFF2.
- """
- from fontTools.misc import sstruct
- from fontTools.misc import psCharStrings
- from fontTools.misc.arrayTools import unionRect, intRect
- from fontTools.misc.textTools import (
- bytechr,
- byteord,
- bytesjoin,
- tobytes,
- tostr,
- safeEval,
- )
- from fontTools.ttLib import TTFont
- from fontTools.ttLib.tables.otBase import OTTableWriter
- from fontTools.ttLib.tables.otBase import OTTableReader
- from fontTools.ttLib.tables import otTables as ot
- from io import BytesIO
- import struct
- import logging
- import re
- # mute cffLib debug messages when running ttx in verbose mode
- DEBUG = logging.DEBUG - 1
- log = logging.getLogger(__name__)
- cffHeaderFormat = """
- major: B
- minor: B
- hdrSize: B
- """
- maxStackLimit = 513
- # maxstack operator has been deprecated. max stack is now always 513.
- class CFFFontSet(object):
- """A CFF font "file" can contain more than one font, although this is
- extremely rare (and not allowed within OpenType fonts).
- This class is the entry point for parsing a CFF table. To actually
- manipulate the data inside the CFF font, you will want to access the
- ``CFFFontSet``'s :class:`TopDict` object. To do this, a ``CFFFontSet``
- object can either be treated as a dictionary (with appropriate
- ``keys()`` and ``values()`` methods) mapping font names to :class:`TopDict`
- objects, or as a list.
- .. code:: python
- from fontTools import ttLib
- tt = ttLib.TTFont("Tests/cffLib/data/LinLibertine_RBI.otf")
- tt["CFF "].cff
- # <fontTools.cffLib.CFFFontSet object at 0x101e24c90>
- tt["CFF "].cff[0] # Here's your actual font data
- # <fontTools.cffLib.TopDict object at 0x1020f1fd0>
- """
- def decompile(self, file, otFont, isCFF2=None):
- """Parse a binary CFF file into an internal representation. ``file``
- should be a file handle object. ``otFont`` is the top-level
- :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file.
- If ``isCFF2`` is passed and set to ``True`` or ``False``, then the
- library makes an assertion that the CFF header is of the appropriate
- version.
- """
- self.otFont = otFont
- sstruct.unpack(cffHeaderFormat, file.read(3), self)
- if isCFF2 is not None:
- # called from ttLib: assert 'major' as read from file matches the
- # expected version
- expected_major = 2 if isCFF2 else 1
- if self.major != expected_major:
- raise ValueError(
- "Invalid CFF 'major' version: expected %d, found %d"
- % (expected_major, self.major)
- )
- else:
- # use 'major' version from file to determine if isCFF2
- assert self.major in (1, 2), "Unknown CFF format"
- isCFF2 = self.major == 2
- if not isCFF2:
- self.offSize = struct.unpack("B", file.read(1))[0]
- file.seek(self.hdrSize)
- self.fontNames = list(tostr(s) for s in Index(file, isCFF2=isCFF2))
- self.topDictIndex = TopDictIndex(file, isCFF2=isCFF2)
- self.strings = IndexedStrings(file)
- else: # isCFF2
- self.topDictSize = struct.unpack(">H", file.read(2))[0]
- file.seek(self.hdrSize)
- self.fontNames = ["CFF2Font"]
- cff2GetGlyphOrder = otFont.getGlyphOrder
- # in CFF2, offsetSize is the size of the TopDict data.
- self.topDictIndex = TopDictIndex(
- file, cff2GetGlyphOrder, self.topDictSize, isCFF2=isCFF2
- )
- self.strings = None
- self.GlobalSubrs = GlobalSubrsIndex(file, isCFF2=isCFF2)
- self.topDictIndex.strings = self.strings
- self.topDictIndex.GlobalSubrs = self.GlobalSubrs
- def __len__(self):
- return len(self.fontNames)
- def keys(self):
- return list(self.fontNames)
- def values(self):
- return self.topDictIndex
- def __getitem__(self, nameOrIndex):
- """Return TopDict instance identified by name (str) or index (int
- or any object that implements `__index__`).
- """
- if hasattr(nameOrIndex, "__index__"):
- index = nameOrIndex.__index__()
- elif isinstance(nameOrIndex, str):
- name = nameOrIndex
- try:
- index = self.fontNames.index(name)
- except ValueError:
- raise KeyError(nameOrIndex)
- else:
- raise TypeError(nameOrIndex)
- return self.topDictIndex[index]
- def compile(self, file, otFont, isCFF2=None):
- """Write the object back into binary representation onto the given file.
- ``file`` should be a file handle object. ``otFont`` is the top-level
- :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file.
- If ``isCFF2`` is passed and set to ``True`` or ``False``, then the
- library makes an assertion that the CFF header is of the appropriate
- version.
- """
- self.otFont = otFont
- if isCFF2 is not None:
- # called from ttLib: assert 'major' value matches expected version
- expected_major = 2 if isCFF2 else 1
- if self.major != expected_major:
- raise ValueError(
- "Invalid CFF 'major' version: expected %d, found %d"
- % (expected_major, self.major)
- )
- else:
- # use current 'major' value to determine output format
- assert self.major in (1, 2), "Unknown CFF format"
- isCFF2 = self.major == 2
- if otFont.recalcBBoxes and not isCFF2:
- for topDict in self.topDictIndex:
- topDict.recalcFontBBox()
- if not isCFF2:
- strings = IndexedStrings()
- else:
- strings = None
- writer = CFFWriter(isCFF2)
- topCompiler = self.topDictIndex.getCompiler(strings, self, isCFF2=isCFF2)
- if isCFF2:
- self.hdrSize = 5
- writer.add(sstruct.pack(cffHeaderFormat, self))
- # Note: topDictSize will most likely change in CFFWriter.toFile().
- self.topDictSize = topCompiler.getDataLength()
- writer.add(struct.pack(">H", self.topDictSize))
- else:
- self.hdrSize = 4
- self.offSize = 4 # will most likely change in CFFWriter.toFile().
- writer.add(sstruct.pack(cffHeaderFormat, self))
- writer.add(struct.pack("B", self.offSize))
- if not isCFF2:
- fontNames = Index()
- for name in self.fontNames:
- fontNames.append(name)
- writer.add(fontNames.getCompiler(strings, self, isCFF2=isCFF2))
- writer.add(topCompiler)
- if not isCFF2:
- writer.add(strings.getCompiler())
- writer.add(self.GlobalSubrs.getCompiler(strings, self, isCFF2=isCFF2))
- for topDict in self.topDictIndex:
- if not hasattr(topDict, "charset") or topDict.charset is None:
- charset = otFont.getGlyphOrder()
- topDict.charset = charset
- children = topCompiler.getChildren(strings)
- for child in children:
- writer.add(child)
- writer.toFile(file)
- def toXML(self, xmlWriter):
- """Write the object into XML representation onto the given
- :class:`fontTools.misc.xmlWriter.XMLWriter`.
- .. code:: python
- writer = xmlWriter.XMLWriter(sys.stdout)
- tt["CFF "].cff.toXML(writer)
- """
- xmlWriter.simpletag("major", value=self.major)
- xmlWriter.newline()
- xmlWriter.simpletag("minor", value=self.minor)
- xmlWriter.newline()
- for fontName in self.fontNames:
- xmlWriter.begintag("CFFFont", name=tostr(fontName))
- xmlWriter.newline()
- font = self[fontName]
- font.toXML(xmlWriter)
- xmlWriter.endtag("CFFFont")
- xmlWriter.newline()
- xmlWriter.newline()
- xmlWriter.begintag("GlobalSubrs")
- xmlWriter.newline()
- self.GlobalSubrs.toXML(xmlWriter)
- xmlWriter.endtag("GlobalSubrs")
- xmlWriter.newline()
- def fromXML(self, name, attrs, content, otFont=None):
- """Reads data from the XML element into the ``CFFFontSet`` object."""
- self.otFont = otFont
- # set defaults. These will be replaced if there are entries for them
- # in the XML file.
- if not hasattr(self, "major"):
- self.major = 1
- if not hasattr(self, "minor"):
- self.minor = 0
- if name == "CFFFont":
- if self.major == 1:
- if not hasattr(self, "offSize"):
- # this will be recalculated when the cff is compiled.
- self.offSize = 4
- if not hasattr(self, "hdrSize"):
- self.hdrSize = 4
- if not hasattr(self, "GlobalSubrs"):
- self.GlobalSubrs = GlobalSubrsIndex()
- if not hasattr(self, "fontNames"):
- self.fontNames = []
- self.topDictIndex = TopDictIndex()
- fontName = attrs["name"]
- self.fontNames.append(fontName)
- topDict = TopDict(GlobalSubrs=self.GlobalSubrs)
- topDict.charset = None # gets filled in later
- elif self.major == 2:
- if not hasattr(self, "hdrSize"):
- self.hdrSize = 5
- if not hasattr(self, "GlobalSubrs"):
- self.GlobalSubrs = GlobalSubrsIndex()
- if not hasattr(self, "fontNames"):
- self.fontNames = ["CFF2Font"]
- cff2GetGlyphOrder = self.otFont.getGlyphOrder
- topDict = TopDict(
- GlobalSubrs=self.GlobalSubrs, cff2GetGlyphOrder=cff2GetGlyphOrder
- )
- self.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder)
- self.topDictIndex.append(topDict)
- for element in content:
- if isinstance(element, str):
- continue
- name, attrs, content = element
- topDict.fromXML(name, attrs, content)
- if hasattr(topDict, "VarStore") and topDict.FDArray[0].vstore is None:
- fdArray = topDict.FDArray
- for fontDict in fdArray:
- if hasattr(fontDict, "Private"):
- fontDict.Private.vstore = topDict.VarStore
- elif name == "GlobalSubrs":
- subrCharStringClass = psCharStrings.T2CharString
- if not hasattr(self, "GlobalSubrs"):
- self.GlobalSubrs = GlobalSubrsIndex()
- for element in content:
- if isinstance(element, str):
- continue
- name, attrs, content = element
- subr = subrCharStringClass()
- subr.fromXML(name, attrs, content)
- self.GlobalSubrs.append(subr)
- elif name == "major":
- self.major = int(attrs["value"])
- elif name == "minor":
- self.minor = int(attrs["value"])
- def convertCFFToCFF2(self, otFont):
- from .CFFToCFF2 import _convertCFFToCFF2
- _convertCFFToCFF2(self, otFont)
- def convertCFF2ToCFF(self, otFont):
- from .CFF2ToCFF import _convertCFF2ToCFF
- _convertCFF2ToCFF(self, otFont)
- def desubroutinize(self):
- from .transforms import desubroutinize
- desubroutinize(self)
- def remove_hints(self):
- from .transforms import remove_hints
- remove_hints(self)
- def remove_unused_subroutines(self):
- from .transforms import remove_unused_subroutines
- remove_unused_subroutines(self)
- class CFFWriter(object):
- """Helper class for serializing CFF data to binary. Used by
- :meth:`CFFFontSet.compile`."""
- def __init__(self, isCFF2):
- self.data = []
- self.isCFF2 = isCFF2
- def add(self, table):
- self.data.append(table)
- def toFile(self, file):
- lastPosList = None
- count = 1
- while True:
- log.log(DEBUG, "CFFWriter.toFile() iteration: %d", count)
- count = count + 1
- pos = 0
- posList = [pos]
- for item in self.data:
- if hasattr(item, "getDataLength"):
- endPos = pos + item.getDataLength()
- if isinstance(item, TopDictIndexCompiler) and item.isCFF2:
- self.topDictSize = item.getDataLength()
- else:
- endPos = pos + len(item)
- if hasattr(item, "setPos"):
- item.setPos(pos, endPos)
- pos = endPos
- posList.append(pos)
- if posList == lastPosList:
- break
- lastPosList = posList
- log.log(DEBUG, "CFFWriter.toFile() writing to file.")
- begin = file.tell()
- if self.isCFF2:
- self.data[1] = struct.pack(">H", self.topDictSize)
- else:
- self.offSize = calcOffSize(lastPosList[-1])
- self.data[1] = struct.pack("B", self.offSize)
- posList = [0]
- for item in self.data:
- if hasattr(item, "toFile"):
- item.toFile(file)
- else:
- file.write(item)
- posList.append(file.tell() - begin)
- assert posList == lastPosList
- def calcOffSize(largestOffset):
- if largestOffset < 0x100:
- offSize = 1
- elif largestOffset < 0x10000:
- offSize = 2
- elif largestOffset < 0x1000000:
- offSize = 3
- else:
- offSize = 4
- return offSize
- class IndexCompiler(object):
- """Base class for writing CFF `INDEX data <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#5-index-data>`_
- to binary."""
- def __init__(self, items, strings, parent, isCFF2=None):
- if isCFF2 is None and hasattr(parent, "isCFF2"):
- isCFF2 = parent.isCFF2
- assert isCFF2 is not None
- self.isCFF2 = isCFF2
- self.items = self.getItems(items, strings)
- self.parent = parent
- def getItems(self, items, strings):
- return items
- def getOffsets(self):
- # An empty INDEX contains only the count field.
- if self.items:
- pos = 1
- offsets = [pos]
- for item in self.items:
- if hasattr(item, "getDataLength"):
- pos = pos + item.getDataLength()
- else:
- pos = pos + len(item)
- offsets.append(pos)
- else:
- offsets = []
- return offsets
- def getDataLength(self):
- if self.isCFF2:
- countSize = 4
- else:
- countSize = 2
- if self.items:
- lastOffset = self.getOffsets()[-1]
- offSize = calcOffSize(lastOffset)
- dataLength = (
- countSize
- + 1 # count
- + (len(self.items) + 1) * offSize # offSize
- + lastOffset # the offsets
- - 1 # size of object data
- )
- else:
- # count. For empty INDEX tables, this is the only entry.
- dataLength = countSize
- return dataLength
- def toFile(self, file):
- offsets = self.getOffsets()
- if self.isCFF2:
- writeCard32(file, len(self.items))
- else:
- writeCard16(file, len(self.items))
- # An empty INDEX contains only the count field.
- if self.items:
- offSize = calcOffSize(offsets[-1])
- writeCard8(file, offSize)
- offSize = -offSize
- pack = struct.pack
- for offset in offsets:
- binOffset = pack(">l", offset)[offSize:]
- assert len(binOffset) == -offSize
- file.write(binOffset)
- for item in self.items:
- if hasattr(item, "toFile"):
- item.toFile(file)
- else:
- data = tobytes(item, encoding="latin1")
- file.write(data)
- class IndexedStringsCompiler(IndexCompiler):
- def getItems(self, items, strings):
- return items.strings
- class TopDictIndexCompiler(IndexCompiler):
- """Helper class for writing the TopDict to binary."""
- def getItems(self, items, strings):
- out = []
- for item in items:
- out.append(item.getCompiler(strings, self))
- return out
- def getChildren(self, strings):
- children = []
- for topDict in self.items:
- children.extend(topDict.getChildren(strings))
- return children
- def getOffsets(self):
- if self.isCFF2:
- offsets = [0, self.items[0].getDataLength()]
- return offsets
- else:
- return super(TopDictIndexCompiler, self).getOffsets()
- def getDataLength(self):
- if self.isCFF2:
- dataLength = self.items[0].getDataLength()
- return dataLength
- else:
- return super(TopDictIndexCompiler, self).getDataLength()
- def toFile(self, file):
- if self.isCFF2:
- self.items[0].toFile(file)
- else:
- super(TopDictIndexCompiler, self).toFile(file)
- class FDArrayIndexCompiler(IndexCompiler):
- """Helper class for writing the
- `Font DICT INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#10-font-dict-index-font-dicts-and-fdselect>`_
- to binary."""
- def getItems(self, items, strings):
- out = []
- for item in items:
- out.append(item.getCompiler(strings, self))
- return out
- def getChildren(self, strings):
- children = []
- for fontDict in self.items:
- children.extend(fontDict.getChildren(strings))
- return children
- def toFile(self, file):
- offsets = self.getOffsets()
- if self.isCFF2:
- writeCard32(file, len(self.items))
- else:
- writeCard16(file, len(self.items))
- offSize = calcOffSize(offsets[-1])
- writeCard8(file, offSize)
- offSize = -offSize
- pack = struct.pack
- for offset in offsets:
- binOffset = pack(">l", offset)[offSize:]
- assert len(binOffset) == -offSize
- file.write(binOffset)
- for item in self.items:
- if hasattr(item, "toFile"):
- item.toFile(file)
- else:
- file.write(item)
- def setPos(self, pos, endPos):
- self.parent.rawDict["FDArray"] = pos
- class GlobalSubrsCompiler(IndexCompiler):
- """Helper class for writing the `global subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
- to binary."""
- def getItems(self, items, strings):
- out = []
- for cs in items:
- cs.compile(self.isCFF2)
- out.append(cs.bytecode)
- return out
- class SubrsCompiler(GlobalSubrsCompiler):
- """Helper class for writing the `local subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
- to binary."""
- def setPos(self, pos, endPos):
- offset = pos - self.parent.pos
- self.parent.rawDict["Subrs"] = offset
- class CharStringsCompiler(GlobalSubrsCompiler):
- """Helper class for writing the `CharStrings INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
- to binary."""
- def getItems(self, items, strings):
- out = []
- for cs in items:
- cs.compile(self.isCFF2)
- out.append(cs.bytecode)
- return out
- def setPos(self, pos, endPos):
- self.parent.rawDict["CharStrings"] = pos
- class Index(object):
- """This class represents what the CFF spec calls an INDEX (an array of
- variable-sized objects). `Index` items can be addressed and set using
- Python list indexing."""
- compilerClass = IndexCompiler
- def __init__(self, file=None, isCFF2=None):
- self.items = []
- self.offsets = offsets = []
- name = self.__class__.__name__
- if file is None:
- return
- self._isCFF2 = isCFF2
- log.log(DEBUG, "loading %s at %s", name, file.tell())
- self.file = file
- if isCFF2:
- count = readCard32(file)
- else:
- count = readCard16(file)
- if count == 0:
- return
- self.items = [None] * count
- offSize = readCard8(file)
- log.log(DEBUG, " index count: %s offSize: %s", count, offSize)
- assert offSize <= 4, "offSize too large: %s" % offSize
- pad = b"\0" * (4 - offSize)
- for index in range(count + 1):
- chunk = file.read(offSize)
- chunk = pad + chunk
- (offset,) = struct.unpack(">L", chunk)
- offsets.append(int(offset))
- self.offsetBase = file.tell() - 1
- file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot
- log.log(DEBUG, " end of %s at %s", name, file.tell())
- def __len__(self):
- return len(self.items)
- def __getitem__(self, index):
- item = self.items[index]
- if item is not None:
- return item
- offset = self.offsets[index] + self.offsetBase
- size = self.offsets[index + 1] - self.offsets[index]
- file = self.file
- file.seek(offset)
- data = file.read(size)
- assert len(data) == size
- item = self.produceItem(index, data, file, offset)
- self.items[index] = item
- return item
- def __setitem__(self, index, item):
- self.items[index] = item
- def produceItem(self, index, data, file, offset):
- return data
- def append(self, item):
- """Add an item to an INDEX."""
- self.items.append(item)
- def getCompiler(self, strings, parent, isCFF2=None):
- return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
- def clear(self):
- """Empty the INDEX."""
- del self.items[:]
- class GlobalSubrsIndex(Index):
- """This index contains all the global subroutines in the font. A global
- subroutine is a set of ``CharString`` data which is accessible to any
- glyph in the font, and are used to store repeated instructions - for
- example, components may be encoded as global subroutines, but so could
- hinting instructions.
- Remember that when interpreting a ``callgsubr`` instruction (or indeed
- a ``callsubr`` instruction) that you will need to add the "subroutine
- number bias" to number given:
- .. code:: python
- tt = ttLib.TTFont("Almendra-Bold.otf")
- u = tt["CFF "].cff[0].CharStrings["udieresis"]
- u.decompile()
- u.toXML(XMLWriter(sys.stdout))
- # <some stuff>
- # -64 callgsubr <-- Subroutine which implements the dieresis mark
- # <other stuff>
- tt["CFF "].cff[0].GlobalSubrs[-64] # <-- WRONG
- # <T2CharString (bytecode) at 103451d10>
- tt["CFF "].cff[0].GlobalSubrs[-64 + 107] # <-- RIGHT
- # <T2CharString (source) at 103451390>
- ("The bias applied depends on the number of subrs (gsubrs). If the number of
- subrs (gsubrs) is less than 1240, the bias is 107. Otherwise if it is less
- than 33900, it is 1131; otherwise it is 32768.",
- `Subroutine Operators <https://docs.microsoft.com/en-us/typography/opentype/otspec180/cff2charstr#section4.4>`)
- """
- compilerClass = GlobalSubrsCompiler
- subrClass = psCharStrings.T2CharString
- charStringClass = psCharStrings.T2CharString
- def __init__(
- self,
- file=None,
- globalSubrs=None,
- private=None,
- fdSelect=None,
- fdArray=None,
- isCFF2=None,
- ):
- super(GlobalSubrsIndex, self).__init__(file, isCFF2=isCFF2)
- self.globalSubrs = globalSubrs
- self.private = private
- if fdSelect:
- self.fdSelect = fdSelect
- if fdArray:
- self.fdArray = fdArray
- def produceItem(self, index, data, file, offset):
- if self.private is not None:
- private = self.private
- elif hasattr(self, "fdArray") and self.fdArray is not None:
- if hasattr(self, "fdSelect") and self.fdSelect is not None:
- fdIndex = self.fdSelect[index]
- else:
- fdIndex = 0
- private = self.fdArray[fdIndex].Private
- else:
- private = None
- return self.subrClass(data, private=private, globalSubrs=self.globalSubrs)
- def toXML(self, xmlWriter):
- """Write the subroutines index into XML representation onto the given
- :class:`fontTools.misc.xmlWriter.XMLWriter`.
- .. code:: python
- writer = xmlWriter.XMLWriter(sys.stdout)
- tt["CFF "].cff[0].GlobalSubrs.toXML(writer)
- """
- xmlWriter.comment(
- "The 'index' attribute is only for humans; " "it is ignored when parsed."
- )
- xmlWriter.newline()
- for i in range(len(self)):
- subr = self[i]
- if subr.needsDecompilation():
- xmlWriter.begintag("CharString", index=i, raw=1)
- else:
- xmlWriter.begintag("CharString", index=i)
- xmlWriter.newline()
- subr.toXML(xmlWriter)
- xmlWriter.endtag("CharString")
- xmlWriter.newline()
- def fromXML(self, name, attrs, content):
- if name != "CharString":
- return
- subr = self.subrClass()
- subr.fromXML(name, attrs, content)
- self.append(subr)
- def getItemAndSelector(self, index):
- sel = None
- if hasattr(self, "fdSelect"):
- sel = self.fdSelect[index]
- return self[index], sel
- class SubrsIndex(GlobalSubrsIndex):
- """This index contains a glyph's local subroutines. A local subroutine is a
- private set of ``CharString`` data which is accessible only to the glyph to
- which the index is attached."""
- compilerClass = SubrsCompiler
- class TopDictIndex(Index):
- """This index represents the array of ``TopDict`` structures in the font
- (again, usually only one entry is present). Hence the following calls are
- equivalent:
- .. code:: python
- tt["CFF "].cff[0]
- # <fontTools.cffLib.TopDict object at 0x102ed6e50>
- tt["CFF "].cff.topDictIndex[0]
- # <fontTools.cffLib.TopDict object at 0x102ed6e50>
- """
- compilerClass = TopDictIndexCompiler
- def __init__(self, file=None, cff2GetGlyphOrder=None, topSize=0, isCFF2=None):
- self.cff2GetGlyphOrder = cff2GetGlyphOrder
- if file is not None and isCFF2:
- self._isCFF2 = isCFF2
- self.items = []
- name = self.__class__.__name__
- log.log(DEBUG, "loading %s at %s", name, file.tell())
- self.file = file
- count = 1
- self.items = [None] * count
- self.offsets = [0, topSize]
- self.offsetBase = file.tell()
- # pretend we've read the whole lot
- file.seek(self.offsetBase + topSize)
- log.log(DEBUG, " end of %s at %s", name, file.tell())
- else:
- super(TopDictIndex, self).__init__(file, isCFF2=isCFF2)
- def produceItem(self, index, data, file, offset):
- top = TopDict(
- self.strings,
- file,
- offset,
- self.GlobalSubrs,
- self.cff2GetGlyphOrder,
- isCFF2=self._isCFF2,
- )
- top.decompile(data)
- return top
- def toXML(self, xmlWriter):
- for i in range(len(self)):
- xmlWriter.begintag("FontDict", index=i)
- xmlWriter.newline()
- self[i].toXML(xmlWriter)
- xmlWriter.endtag("FontDict")
- xmlWriter.newline()
- class FDArrayIndex(Index):
- compilerClass = FDArrayIndexCompiler
- def toXML(self, xmlWriter):
- for i in range(len(self)):
- xmlWriter.begintag("FontDict", index=i)
- xmlWriter.newline()
- self[i].toXML(xmlWriter)
- xmlWriter.endtag("FontDict")
- xmlWriter.newline()
- def produceItem(self, index, data, file, offset):
- fontDict = FontDict(
- self.strings,
- file,
- offset,
- self.GlobalSubrs,
- isCFF2=self._isCFF2,
- vstore=self.vstore,
- )
- fontDict.decompile(data)
- return fontDict
- def fromXML(self, name, attrs, content):
- if name != "FontDict":
- return
- fontDict = FontDict()
- for element in content:
- if isinstance(element, str):
- continue
- name, attrs, content = element
- fontDict.fromXML(name, attrs, content)
- self.append(fontDict)
- class VarStoreData(object):
- def __init__(self, file=None, otVarStore=None):
- self.file = file
- self.data = None
- self.otVarStore = otVarStore
- self.font = TTFont() # dummy font for the decompile function.
- def decompile(self):
- if self.file:
- # read data in from file. Assume position is correct.
- length = readCard16(self.file)
- # https://github.com/fonttools/fonttools/issues/3673
- if length == 65535:
- self.data = self.file.read()
- else:
- self.data = self.file.read(length)
- globalState = {}
- reader = OTTableReader(self.data, globalState)
- self.otVarStore = ot.VarStore()
- self.otVarStore.decompile(reader, self.font)
- self.data = None
- return self
- def compile(self):
- writer = OTTableWriter()
- self.otVarStore.compile(writer, self.font)
- # Note that this omits the initial Card16 length from the CFF2
- # VarStore data block
- self.data = writer.getAllData()
- def writeXML(self, xmlWriter, name):
- self.otVarStore.toXML(xmlWriter, self.font)
- def xmlRead(self, name, attrs, content, parent):
- self.otVarStore = ot.VarStore()
- for element in content:
- if isinstance(element, tuple):
- name, attrs, content = element
- self.otVarStore.fromXML(name, attrs, content, self.font)
- else:
- pass
- return None
- def __len__(self):
- return len(self.data)
- def getNumRegions(self, vsIndex):
- if vsIndex is None:
- vsIndex = 0
- varData = self.otVarStore.VarData[vsIndex]
- numRegions = varData.VarRegionCount
- return numRegions
- class FDSelect(object):
- def __init__(self, file=None, numGlyphs=None, format=None):
- if file:
- # read data in from file
- self.format = readCard8(file)
- if self.format == 0:
- from array import array
- self.gidArray = array("B", file.read(numGlyphs)).tolist()
- elif self.format == 3:
- gidArray = [None] * numGlyphs
- nRanges = readCard16(file)
- fd = None
- prev = None
- for i in range(nRanges):
- first = readCard16(file)
- if prev is not None:
- for glyphID in range(prev, first):
- gidArray[glyphID] = fd
- prev = first
- fd = readCard8(file)
- if prev is not None:
- first = readCard16(file)
- for glyphID in range(prev, first):
- gidArray[glyphID] = fd
- self.gidArray = gidArray
- elif self.format == 4:
- gidArray = [None] * numGlyphs
- nRanges = readCard32(file)
- fd = None
- prev = None
- for i in range(nRanges):
- first = readCard32(file)
- if prev is not None:
- for glyphID in range(prev, first):
- gidArray[glyphID] = fd
- prev = first
- fd = readCard16(file)
- if prev is not None:
- first = readCard32(file)
- for glyphID in range(prev, first):
- gidArray[glyphID] = fd
- self.gidArray = gidArray
- else:
- assert False, "unsupported FDSelect format: %s" % format
- else:
- # reading from XML. Make empty gidArray, and leave format as passed in.
- # format is None will result in the smallest representation being used.
- self.format = format
- self.gidArray = []
- def __len__(self):
- return len(self.gidArray)
- def __getitem__(self, index):
- return self.gidArray[index]
- def __setitem__(self, index, fdSelectValue):
- self.gidArray[index] = fdSelectValue
- def append(self, fdSelectValue):
- self.gidArray.append(fdSelectValue)
- class CharStrings(object):
- """The ``CharStrings`` in the font represent the instructions for drawing
- each glyph. This object presents a dictionary interface to the font's
- CharStrings, indexed by glyph name:
- .. code:: python
- tt["CFF "].cff[0].CharStrings["a"]
- # <T2CharString (bytecode) at 103451e90>
- See :class:`fontTools.misc.psCharStrings.T1CharString` and
- :class:`fontTools.misc.psCharStrings.T2CharString` for how to decompile,
- compile and interpret the glyph drawing instructions in the returned objects.
- """
- def __init__(
- self,
- file,
- charset,
- globalSubrs,
- private,
- fdSelect,
- fdArray,
- isCFF2=None,
- varStore=None,
- ):
- self.globalSubrs = globalSubrs
- self.varStore = varStore
- if file is not None:
- self.charStringsIndex = SubrsIndex(
- file, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2
- )
- self.charStrings = charStrings = {}
- for i in range(len(charset)):
- charStrings[charset[i]] = i
- # read from OTF file: charStrings.values() are indices into
- # charStringsIndex.
- self.charStringsAreIndexed = 1
- else:
- self.charStrings = {}
- # read from ttx file: charStrings.values() are actual charstrings
- self.charStringsAreIndexed = 0
- self.private = private
- if fdSelect is not None:
- self.fdSelect = fdSelect
- if fdArray is not None:
- self.fdArray = fdArray
- def keys(self):
- return list(self.charStrings.keys())
- def values(self):
- if self.charStringsAreIndexed:
- return self.charStringsIndex
- else:
- return list(self.charStrings.values())
- def has_key(self, name):
- return name in self.charStrings
- __contains__ = has_key
- def __len__(self):
- return len(self.charStrings)
- def __getitem__(self, name):
- charString = self.charStrings[name]
- if self.charStringsAreIndexed:
- charString = self.charStringsIndex[charString]
- return charString
- def __setitem__(self, name, charString):
- if self.charStringsAreIndexed:
- index = self.charStrings[name]
- self.charStringsIndex[index] = charString
- else:
- self.charStrings[name] = charString
- def getItemAndSelector(self, name):
- if self.charStringsAreIndexed:
- index = self.charStrings[name]
- return self.charStringsIndex.getItemAndSelector(index)
- else:
- if hasattr(self, "fdArray"):
- if hasattr(self, "fdSelect"):
- sel = self.charStrings[name].fdSelectIndex
- else:
- sel = 0
- else:
- sel = None
- return self.charStrings[name], sel
- def toXML(self, xmlWriter):
- names = sorted(self.keys())
- for name in names:
- charStr, fdSelectIndex = self.getItemAndSelector(name)
- if charStr.needsDecompilation():
- raw = [("raw", 1)]
- else:
- raw = []
- if fdSelectIndex is None:
- xmlWriter.begintag("CharString", [("name", name)] + raw)
- else:
- xmlWriter.begintag(
- "CharString",
- [("name", name), ("fdSelectIndex", fdSelectIndex)] + raw,
- )
- xmlWriter.newline()
- charStr.toXML(xmlWriter)
- xmlWriter.endtag("CharString")
- xmlWriter.newline()
- def fromXML(self, name, attrs, content):
- for element in content:
- if isinstance(element, str):
- continue
- name, attrs, content = element
- if name != "CharString":
- continue
- fdID = -1
- if hasattr(self, "fdArray"):
- try:
- fdID = safeEval(attrs["fdSelectIndex"])
- except KeyError:
- fdID = 0
- private = self.fdArray[fdID].Private
- else:
- private = self.private
- glyphName = attrs["name"]
- charStringClass = psCharStrings.T2CharString
- charString = charStringClass(private=private, globalSubrs=self.globalSubrs)
- charString.fromXML(name, attrs, content)
- if fdID >= 0:
- charString.fdSelectIndex = fdID
- self[glyphName] = charString
- def readCard8(file):
- return byteord(file.read(1))
- def readCard16(file):
- (value,) = struct.unpack(">H", file.read(2))
- return value
- def readCard32(file):
- (value,) = struct.unpack(">L", file.read(4))
- return value
- def writeCard8(file, value):
- file.write(bytechr(value))
- def writeCard16(file, value):
- file.write(struct.pack(">H", value))
- def writeCard32(file, value):
- file.write(struct.pack(">L", value))
- def packCard8(value):
- return bytechr(value)
- def packCard16(value):
- return struct.pack(">H", value)
- def packCard32(value):
- return struct.pack(">L", value)
- def buildOperatorDict(table):
- d = {}
- for op, name, arg, default, conv in table:
- d[op] = (name, arg)
- return d
- def buildOpcodeDict(table):
- d = {}
- for op, name, arg, default, conv in table:
- if isinstance(op, tuple):
- op = bytechr(op[0]) + bytechr(op[1])
- else:
- op = bytechr(op)
- d[name] = (op, arg)
- return d
- def buildOrder(table):
- l = []
- for op, name, arg, default, conv in table:
- l.append(name)
- return l
- def buildDefaults(table):
- d = {}
- for op, name, arg, default, conv in table:
- if default is not None:
- d[name] = default
- return d
- def buildConverters(table):
- d = {}
- for op, name, arg, default, conv in table:
- d[name] = conv
- return d
- class SimpleConverter(object):
- def read(self, parent, value):
- if not hasattr(parent, "file"):
- return self._read(parent, value)
- file = parent.file
- pos = file.tell()
- try:
- return self._read(parent, value)
- finally:
- file.seek(pos)
- def _read(self, parent, value):
- return value
- def write(self, parent, value):
- return value
- def xmlWrite(self, xmlWriter, name, value):
- xmlWriter.simpletag(name, value=value)
- xmlWriter.newline()
- def xmlRead(self, name, attrs, content, parent):
- return attrs["value"]
- class ASCIIConverter(SimpleConverter):
- def _read(self, parent, value):
- return tostr(value, encoding="ascii")
- def write(self, parent, value):
- return tobytes(value, encoding="ascii")
- def xmlWrite(self, xmlWriter, name, value):
- xmlWriter.simpletag(name, value=tostr(value, encoding="ascii"))
- xmlWriter.newline()
- def xmlRead(self, name, attrs, content, parent):
- return tobytes(attrs["value"], encoding=("ascii"))
- class Latin1Converter(SimpleConverter):
- def _read(self, parent, value):
- return tostr(value, encoding="latin1")
- def write(self, parent, value):
- return tobytes(value, encoding="latin1")
- def xmlWrite(self, xmlWriter, name, value):
- value = tostr(value, encoding="latin1")
- if name in ["Notice", "Copyright"]:
- value = re.sub(r"[\r\n]\s+", " ", value)
- xmlWriter.simpletag(name, value=value)
- xmlWriter.newline()
- def xmlRead(self, name, attrs, content, parent):
- return tobytes(attrs["value"], encoding=("latin1"))
- def parseNum(s):
- try:
- value = int(s)
- except:
- value = float(s)
- return value
- def parseBlendList(s):
- valueList = []
- for element in s:
- if isinstance(element, str):
- continue
- name, attrs, content = element
- blendList = attrs["value"].split()
- blendList = [eval(val) for val in blendList]
- valueList.append(blendList)
- if len(valueList) == 1:
- valueList = valueList[0]
- return valueList
- class NumberConverter(SimpleConverter):
- def xmlWrite(self, xmlWriter, name, value):
- if isinstance(value, list):
- xmlWriter.begintag(name)
- xmlWriter.newline()
- xmlWriter.indent()
- blendValue = " ".join([str(val) for val in value])
- xmlWriter.simpletag(kBlendDictOpName, value=blendValue)
- xmlWriter.newline()
- xmlWriter.dedent()
- xmlWriter.endtag(name)
- xmlWriter.newline()
- else:
- xmlWriter.simpletag(name, value=value)
- xmlWriter.newline()
- def xmlRead(self, name, attrs, content, parent):
- valueString = attrs.get("value", None)
- if valueString is None:
- value = parseBlendList(content)
- else:
- value = parseNum(attrs["value"])
- return value
- class ArrayConverter(SimpleConverter):
- def xmlWrite(self, xmlWriter, name, value):
- if value and isinstance(value[0], list):
- xmlWriter.begintag(name)
- xmlWriter.newline()
- xmlWriter.indent()
- for valueList in value:
- blendValue = " ".join([str(val) for val in valueList])
- xmlWriter.simpletag(kBlendDictOpName, value=blendValue)
- xmlWriter.newline()
- xmlWriter.dedent()
- xmlWriter.endtag(name)
- xmlWriter.newline()
- else:
- value = " ".join([str(val) for val in value])
- xmlWriter.simpletag(name, value=value)
- xmlWriter.newline()
- def xmlRead(self, name, attrs, content, parent):
- valueString = attrs.get("value", None)
- if valueString is None:
- valueList = parseBlendList(content)
- else:
- values = valueString.split()
- valueList = [parseNum(value) for value in values]
- return valueList
- class TableConverter(SimpleConverter):
- def xmlWrite(self, xmlWriter, name, value):
- xmlWriter.begintag(name)
- xmlWriter.newline()
- value.toXML(xmlWriter)
- xmlWriter.endtag(name)
- xmlWriter.newline()
- def xmlRead(self, name, attrs, content, parent):
- ob = self.getClass()()
- for element in content:
- if isinstance(element, str):
- continue
- name, attrs, content = element
- ob.fromXML(name, attrs, content)
- return ob
- class PrivateDictConverter(TableConverter):
- def getClass(self):
- return PrivateDict
- def _read(self, parent, value):
- size, offset = value
- file = parent.file
- isCFF2 = parent._isCFF2
- try:
- vstore = parent.vstore
- except AttributeError:
- vstore = None
- priv = PrivateDict(parent.strings, file, offset, isCFF2=isCFF2, vstore=vstore)
- file.seek(offset)
- data = file.read(size)
- assert len(data) == size
- priv.decompile(data)
- return priv
- def write(self, parent, value):
- return (0, 0) # dummy value
- class SubrsConverter(TableConverter):
- def getClass(self):
- return SubrsIndex
- def _read(self, parent, value):
- file = parent.file
- isCFF2 = parent._isCFF2
- file.seek(parent.offset + value) # Offset(self)
- return SubrsIndex(file, isCFF2=isCFF2)
- def write(self, parent, value):
- return 0 # dummy value
- class CharStringsConverter(TableConverter):
- def _read(self, parent, value):
- file = parent.file
- isCFF2 = parent._isCFF2
- charset = parent.charset
- varStore = getattr(parent, "VarStore", None)
- globalSubrs = parent.GlobalSubrs
- if hasattr(parent, "FDArray"):
- fdArray = parent.FDArray
- if hasattr(parent, "FDSelect"):
- fdSelect = parent.FDSelect
- else:
- fdSelect = None
- private = None
- else:
- fdSelect, fdArray = None, None
- private = parent.Private
- file.seek(value) # Offset(0)
- charStrings = CharStrings(
- file,
- charset,
- globalSubrs,
- private,
- fdSelect,
- fdArray,
- isCFF2=isCFF2,
- varStore=varStore,
- )
- return charStrings
- def write(self, parent, value):
- return 0 # dummy value
- def xmlRead(self, name, attrs, content, parent):
- if hasattr(parent, "FDArray"):
- # if it is a CID-keyed font, then the private Dict is extracted from the
- # parent.FDArray
- fdArray = parent.FDArray
- if hasattr(parent, "FDSelect"):
- fdSelect = parent.FDSelect
- else:
- fdSelect = None
- private = None
- else:
- # if it is a name-keyed font, then the private dict is in the top dict,
- # and
- # there is no fdArray.
- private, fdSelect, fdArray = parent.Private, None, None
- charStrings = CharStrings(
- None,
- None,
- parent.GlobalSubrs,
- private,
- fdSelect,
- fdArray,
- varStore=getattr(parent, "VarStore", None),
- )
- charStrings.fromXML(name, attrs, content)
- return charStrings
- class CharsetConverter(SimpleConverter):
- def _read(self, parent, value):
- isCID = hasattr(parent, "ROS")
- if value > 2:
- numGlyphs = parent.numGlyphs
- file = parent.file
- file.seek(value)
- log.log(DEBUG, "loading charset at %s", value)
- format = readCard8(file)
- if format == 0:
- charset = parseCharset0(numGlyphs, file, parent.strings, isCID)
- elif format == 1 or format == 2:
- charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
- else:
- raise NotImplementedError
- assert len(charset) == numGlyphs
- log.log(DEBUG, " charset end at %s", file.tell())
- # make sure glyph names are unique
- allNames = {}
- newCharset = []
- for glyphName in charset:
- if glyphName in allNames:
- # make up a new glyphName that's unique
- n = allNames[glyphName]
- while (glyphName + "#" + str(n)) in allNames:
- n += 1
- allNames[glyphName] = n + 1
- glyphName = glyphName + "#" + str(n)
- allNames[glyphName] = 1
- newCharset.append(glyphName)
- charset = newCharset
- else: # offset == 0 -> no charset data.
- if isCID or "CharStrings" not in parent.rawDict:
- # We get here only when processing fontDicts from the FDArray of
- # CFF-CID fonts. Only the real topDict references the charset.
- assert value == 0
- charset = None
- elif value == 0:
- charset = cffISOAdobeStrings
- elif value == 1:
- charset = cffIExpertStrings
- elif value == 2:
- charset = cffExpertSubsetStrings
- if charset and (len(charset) != parent.numGlyphs):
- charset = charset[: parent.numGlyphs]
- return charset
- def write(self, parent, value):
- return 0 # dummy value
- def xmlWrite(self, xmlWriter, name, value):
- # XXX only write charset when not in OT/TTX context, where we
- # dump charset as a separate "GlyphOrder" table.
- # # xmlWriter.simpletag("charset")
- xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
- xmlWriter.newline()
- def xmlRead(self, name, attrs, content, parent):
- pass
- class CharsetCompiler(object):
- def __init__(self, strings, charset, parent):
- assert charset[0] == ".notdef"
- isCID = hasattr(parent.dictObj, "ROS")
- data0 = packCharset0(charset, isCID, strings)
- data = packCharset(charset, isCID, strings)
- if len(data) < len(data0):
- self.data = data
- else:
- self.data = data0
- self.parent = parent
- def setPos(self, pos, endPos):
- self.parent.rawDict["charset"] = pos
- def getDataLength(self):
- return len(self.data)
- def toFile(self, file):
- file.write(self.data)
- def getStdCharSet(charset):
- # check to see if we can use a predefined charset value.
- predefinedCharSetVal = None
- predefinedCharSets = [
- (cffISOAdobeStringCount, cffISOAdobeStrings, 0),
- (cffExpertStringCount, cffIExpertStrings, 1),
- (cffExpertSubsetStringCount, cffExpertSubsetStrings, 2),
- ]
- lcs = len(charset)
- for cnt, pcs, csv in predefinedCharSets:
- if predefinedCharSetVal is not None:
- break
- if lcs > cnt:
- continue
- predefinedCharSetVal = csv
- for i in range(lcs):
- if charset[i] != pcs[i]:
- predefinedCharSetVal = None
- break
- return predefinedCharSetVal
- def getCIDfromName(name, strings):
- return int(name[3:])
- def getSIDfromName(name, strings):
- return strings.getSID(name)
- def packCharset0(charset, isCID, strings):
- fmt = 0
- data = [packCard8(fmt)]
- if isCID:
- getNameID = getCIDfromName
- else:
- getNameID = getSIDfromName
- for name in charset[1:]:
- data.append(packCard16(getNameID(name, strings)))
- return bytesjoin(data)
- def packCharset(charset, isCID, strings):
- fmt = 1
- ranges = []
- first = None
- end = 0
- if isCID:
- getNameID = getCIDfromName
- else:
- getNameID = getSIDfromName
- for name in charset[1:]:
- SID = getNameID(name, strings)
- if first is None:
- first = SID
- elif end + 1 != SID:
- nLeft = end - first
- if nLeft > 255:
- fmt = 2
- ranges.append((first, nLeft))
- first = SID
- end = SID
- if end:
- nLeft = end - first
- if nLeft > 255:
- fmt = 2
- ranges.append((first, nLeft))
- data = [packCard8(fmt)]
- if fmt == 1:
- nLeftFunc = packCard8
- else:
- nLeftFunc = packCard16
- for first, nLeft in ranges:
- data.append(packCard16(first) + nLeftFunc(nLeft))
- return bytesjoin(data)
- def parseCharset0(numGlyphs, file, strings, isCID):
- charset = [".notdef"]
- if isCID:
- for i in range(numGlyphs - 1):
- CID = readCard16(file)
- charset.append("cid" + str(CID).zfill(5))
- else:
- for i in range(numGlyphs - 1):
- SID = readCard16(file)
- charset.append(strings[SID])
- return charset
- def parseCharset(numGlyphs, file, strings, isCID, fmt):
- charset = [".notdef"]
- count = 1
- if fmt == 1:
- nLeftFunc = readCard8
- else:
- nLeftFunc = readCard16
- while count < numGlyphs:
- first = readCard16(file)
- nLeft = nLeftFunc(file)
- if isCID:
- for CID in range(first, first + nLeft + 1):
- charset.append("cid" + str(CID).zfill(5))
- else:
- for SID in range(first, first + nLeft + 1):
- charset.append(strings[SID])
- count = count + nLeft + 1
- return charset
- class EncodingCompiler(object):
- def __init__(self, strings, encoding, parent):
- assert not isinstance(encoding, str)
- data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
- data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
- if len(data0) < len(data1):
- self.data = data0
- else:
- self.data = data1
- self.parent = parent
- def setPos(self, pos, endPos):
- self.parent.rawDict["Encoding"] = pos
- def getDataLength(self):
- return len(self.data)
- def toFile(self, file):
- file.write(self.data)
- class EncodingConverter(SimpleConverter):
- def _read(self, parent, value):
- if value == 0:
- return "StandardEncoding"
- elif value == 1:
- return "ExpertEncoding"
- else:
- assert value > 1
- file = parent.file
- file.seek(value)
- log.log(DEBUG, "loading Encoding at %s", value)
- fmt = readCard8(file)
- haveSupplement = fmt & 0x80
- if haveSupplement:
- raise NotImplementedError("Encoding supplements are not yet supported")
- fmt = fmt & 0x7F
- if fmt == 0:
- encoding = parseEncoding0(
- parent.charset, file, haveSupplement, parent.strings
- )
- elif fmt == 1:
- encoding = parseEncoding1(
- parent.charset, file, haveSupplement, parent.strings
- )
- return encoding
- def write(self, parent, value):
- if value == "StandardEncoding":
- return 0
- elif value == "ExpertEncoding":
- return 1
- return 0 # dummy value
- def xmlWrite(self, xmlWriter, name, value):
- if value in ("StandardEncoding", "ExpertEncoding"):
- xmlWriter.simpletag(name, name=value)
- xmlWriter.newline()
- return
- xmlWriter.begintag(name)
- xmlWriter.newline()
- for code in range(len(value)):
- glyphName = value[code]
- if glyphName != ".notdef":
- xmlWriter.simpletag("map", code=hex(code), name=glyphName)
- xmlWriter.newline()
- xmlWriter.endtag(name)
- xmlWriter.newline()
- def xmlRead(self, name, attrs, content, parent):
- if "name" in attrs:
- return attrs["name"]
- encoding = [".notdef"] * 256
- for element in content:
- if isinstance(element, str):
- continue
- name, attrs, content = element
- code = safeEval(attrs["code"])
- glyphName = attrs["name"]
- encoding[code] = glyphName
- return encoding
- def parseEncoding0(charset, file, haveSupplement, strings):
- nCodes = readCard8(file)
- encoding = [".notdef"] * 256
- for glyphID in range(1, nCodes + 1):
- code = readCard8(file)
- if code != 0:
- encoding[code] = charset[glyphID]
- return encoding
- def parseEncoding1(charset, file, haveSupplement, strings):
- nRanges = readCard8(file)
- encoding = [".notdef"] * 256
- glyphID = 1
- for i in range(nRanges):
- code = readCard8(file)
- nLeft = readCard8(file)
- for glyphID in range(glyphID, glyphID + nLeft + 1):
- encoding[code] = charset[glyphID]
- code = code + 1
- glyphID = glyphID + 1
- return encoding
- def packEncoding0(charset, encoding, strings):
- fmt = 0
- m = {}
- for code in range(len(encoding)):
- name = encoding[code]
- if name != ".notdef":
- m[name] = code
- codes = []
- for name in charset[1:]:
- code = m.get(name)
- codes.append(code)
- while codes and codes[-1] is None:
- codes.pop()
- data = [packCard8(fmt), packCard8(len(codes))]
- for code in codes:
- if code is None:
- code = 0
- data.append(packCard8(code))
- return bytesjoin(data)
- def packEncoding1(charset, encoding, strings):
- fmt = 1
- m = {}
- for code in range(len(encoding)):
- name = encoding[code]
- if name != ".notdef":
- m[name] = code
- ranges = []
- first = None
- end = 0
- for name in charset[1:]:
- code = m.get(name, -1)
- if first is None:
- first = code
- elif end + 1 != code:
- nLeft = end - first
- ranges.append((first, nLeft))
- first = code
- end = code
- nLeft = end - first
- ranges.append((first, nLeft))
- # remove unencoded glyphs at the end.
- while ranges and ranges[-1][0] == -1:
- ranges.pop()
- data = [packCard8(fmt), packCard8(len(ranges))]
- for first, nLeft in ranges:
- if first == -1: # unencoded
- first = 0
- data.append(packCard8(first) + packCard8(nLeft))
- return bytesjoin(data)
- class FDArrayConverter(TableConverter):
- def _read(self, parent, value):
- try:
- vstore = parent.VarStore
- except AttributeError:
- vstore = None
- file = parent.file
- isCFF2 = parent._isCFF2
- file.seek(value)
- fdArray = FDArrayIndex(file, isCFF2=isCFF2)
- fdArray.vstore = vstore
- fdArray.strings = parent.strings
- fdArray.GlobalSubrs = parent.GlobalSubrs
- return fdArray
- def write(self, parent, value):
- return 0 # dummy value
- def xmlRead(self, name, attrs, content, parent):
- fdArray = FDArrayIndex()
- for element in content:
- if isinstance(element, str):
- continue
- name, attrs, content = element
- fdArray.fromXML(name, attrs, content)
- return fdArray
- class FDSelectConverter(SimpleConverter):
- def _read(self, parent, value):
- file = parent.file
- file.seek(value)
- fdSelect = FDSelect(file, parent.numGlyphs)
- return fdSelect
- def write(self, parent, value):
- return 0 # dummy value
- # The FDSelect glyph data is written out to XML in the charstring keys,
- # so we write out only the format selector
- def xmlWrite(self, xmlWriter, name, value):
- xmlWriter.simpletag(name, [("format", value.format)])
- xmlWriter.newline()
- def xmlRead(self, name, attrs, content, parent):
- fmt = safeEval(attrs["format"])
- file = None
- numGlyphs = None
- fdSelect = FDSelect(file, numGlyphs, fmt)
- return fdSelect
- class VarStoreConverter(SimpleConverter):
- def _read(self, parent, value):
- file = parent.file
- file.seek(value)
- varStore = VarStoreData(file)
- varStore.decompile()
- return varStore
- def write(self, parent, value):
- return 0 # dummy value
- def xmlWrite(self, xmlWriter, name, value):
- value.writeXML(xmlWriter, name)
- def xmlRead(self, name, attrs, content, parent):
- varStore = VarStoreData()
- varStore.xmlRead(name, attrs, content, parent)
- return varStore
- def packFDSelect0(fdSelectArray):
- fmt = 0
- data = [packCard8(fmt)]
- for index in fdSelectArray:
- data.append(packCard8(index))
- return bytesjoin(data)
- def packFDSelect3(fdSelectArray):
- fmt = 3
- fdRanges = []
- lenArray = len(fdSelectArray)
- lastFDIndex = -1
- for i in range(lenArray):
- fdIndex = fdSelectArray[i]
- if lastFDIndex != fdIndex:
- fdRanges.append([i, fdIndex])
- lastFDIndex = fdIndex
- sentinelGID = i + 1
- data = [packCard8(fmt)]
- data.append(packCard16(len(fdRanges)))
- for fdRange in fdRanges:
- data.append(packCard16(fdRange[0]))
- data.append(packCard8(fdRange[1]))
- data.append(packCard16(sentinelGID))
- return bytesjoin(data)
- def packFDSelect4(fdSelectArray):
- fmt = 4
- fdRanges = []
- lenArray = len(fdSelectArray)
- lastFDIndex = -1
- for i in range(lenArray):
- fdIndex = fdSelectArray[i]
- if lastFDIndex != fdIndex:
- fdRanges.append([i, fdIndex])
- lastFDIndex = fdIndex
- sentinelGID = i + 1
- data = [packCard8(fmt)]
- data.append(packCard32(len(fdRanges)))
- for fdRange in fdRanges:
- data.append(packCard32(fdRange[0]))
- data.append(packCard16(fdRange[1]))
- data.append(packCard32(sentinelGID))
- return bytesjoin(data)
- class FDSelectCompiler(object):
- def __init__(self, fdSelect, parent):
- fmt = fdSelect.format
- fdSelectArray = fdSelect.gidArray
- if fmt == 0:
- self.data = packFDSelect0(fdSelectArray)
- elif fmt == 3:
- self.data = packFDSelect3(fdSelectArray)
- elif fmt == 4:
- self.data = packFDSelect4(fdSelectArray)
- else:
- # choose smaller of the two formats
- data0 = packFDSelect0(fdSelectArray)
- data3 = packFDSelect3(fdSelectArray)
- if len(data0) < len(data3):
- self.data = data0
- fdSelect.format = 0
- else:
- self.data = data3
- fdSelect.format = 3
- self.parent = parent
- def setPos(self, pos, endPos):
- self.parent.rawDict["FDSelect"] = pos
- def getDataLength(self):
- return len(self.data)
- def toFile(self, file):
- file.write(self.data)
- class VarStoreCompiler(object):
- def __init__(self, varStoreData, parent):
- self.parent = parent
- if not varStoreData.data:
- varStoreData.compile()
- varStoreDataLen = min(0xFFFF, len(varStoreData.data))
- data = [packCard16(varStoreDataLen), varStoreData.data]
- self.data = bytesjoin(data)
- def setPos(self, pos, endPos):
- self.parent.rawDict["VarStore"] = pos
- def getDataLength(self):
- return len(self.data)
- def toFile(self, file):
- file.write(self.data)
- class ROSConverter(SimpleConverter):
- def xmlWrite(self, xmlWriter, name, value):
- registry, order, supplement = value
- xmlWriter.simpletag(
- name,
- [
- ("Registry", tostr(registry)),
- ("Order", tostr(order)),
- ("Supplement", supplement),
- ],
- )
- xmlWriter.newline()
- def xmlRead(self, name, attrs, content, parent):
- return (attrs["Registry"], attrs["Order"], safeEval(attrs["Supplement"]))
- topDictOperators = [
- # opcode name argument type default converter
- (25, "maxstack", "number", None, None),
- ((12, 30), "ROS", ("SID", "SID", "number"), None, ROSConverter()),
- ((12, 20), "SyntheticBase", "number", None, None),
- (0, "version", "SID", None, None),
- (1, "Notice", "SID", None, Latin1Converter()),
- ((12, 0), "Copyright", "SID", None, Latin1Converter()),
- (2, "FullName", "SID", None, Latin1Converter()),
- ((12, 38), "FontName", "SID", None, Latin1Converter()),
- (3, "FamilyName", "SID", None, Latin1Converter()),
- (4, "Weight", "SID", None, None),
- ((12, 1), "isFixedPitch", "number", 0, None),
- ((12, 2), "ItalicAngle", "number", 0, None),
- ((12, 3), "UnderlinePosition", "number", -100, None),
- ((12, 4), "UnderlineThickness", "number", 50, None),
- ((12, 5), "PaintType", "number", 0, None),
- ((12, 6), "CharstringType", "number", 2, None),
- ((12, 7), "FontMatrix", "array", [0.001, 0, 0, 0.001, 0, 0], None),
- (13, "UniqueID", "number", None, None),
- (5, "FontBBox", "array", [0, 0, 0, 0], None),
- ((12, 8), "StrokeWidth", "number", 0, None),
- (14, "XUID", "array", None, None),
- ((12, 21), "PostScript", "SID", None, None),
- ((12, 22), "BaseFontName", "SID", None, None),
- ((12, 23), "BaseFontBlend", "delta", None, None),
- ((12, 31), "CIDFontVersion", "number", 0, None),
- ((12, 32), "CIDFontRevision", "number", 0, None),
- ((12, 33), "CIDFontType", "number", 0, None),
- ((12, 34), "CIDCount", "number", 8720, None),
- (15, "charset", "number", None, CharsetConverter()),
- ((12, 35), "UIDBase", "number", None, None),
- (16, "Encoding", "number", 0, EncodingConverter()),
- (18, "Private", ("number", "number"), None, PrivateDictConverter()),
- ((12, 37), "FDSelect", "number", None, FDSelectConverter()),
- ((12, 36), "FDArray", "number", None, FDArrayConverter()),
- (17, "CharStrings", "number", None, CharStringsConverter()),
- (24, "VarStore", "number", None, VarStoreConverter()),
- ]
- topDictOperators2 = [
- # opcode name argument type default converter
- (25, "maxstack", "number", None, None),
- ((12, 7), "FontMatrix", "array", [0.001, 0, 0, 0.001, 0, 0], None),
- ((12, 37), "FDSelect", "number", None, FDSelectConverter()),
- ((12, 36), "FDArray", "number", None, FDArrayConverter()),
- (17, "CharStrings", "number", None, CharStringsConverter()),
- (24, "VarStore", "number", None, VarStoreConverter()),
- ]
- # Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order,
- # in order for the font to compile back from xml.
- kBlendDictOpName = "blend"
- blendOp = 23
- privateDictOperators = [
- # opcode name argument type default converter
- (22, "vsindex", "number", None, None),
- (
- blendOp,
- kBlendDictOpName,
- "blendList",
- None,
- None,
- ), # This is for reading to/from XML: it not written to CFF.
- (6, "BlueValues", "delta", None, None),
- (7, "OtherBlues", "delta", None, None),
- (8, "FamilyBlues", "delta", None, None),
- (9, "FamilyOtherBlues", "delta", None, None),
- ((12, 9), "BlueScale", "number", 0.039625, None),
- ((12, 10), "BlueShift", "number", 7, None),
- ((12, 11), "BlueFuzz", "number", 1, None),
- (10, "StdHW", "number", None, None),
- (11, "StdVW", "number", None, None),
- ((12, 12), "StemSnapH", "delta", None, None),
- ((12, 13), "StemSnapV", "delta", None, None),
- ((12, 14), "ForceBold", "number", 0, None),
- ((12, 15), "ForceBoldThreshold", "number", None, None), # deprecated
- ((12, 16), "lenIV", "number", None, None), # deprecated
- ((12, 17), "LanguageGroup", "number", 0, None),
- ((12, 18), "ExpansionFactor", "number", 0.06, None),
- ((12, 19), "initialRandomSeed", "number", 0, None),
- (20, "defaultWidthX", "number", 0, None),
- (21, "nominalWidthX", "number", 0, None),
- (19, "Subrs", "number", None, SubrsConverter()),
- ]
- privateDictOperators2 = [
- # opcode name argument type default converter
- (22, "vsindex", "number", None, None),
- (
- blendOp,
- kBlendDictOpName,
- "blendList",
- None,
- None,
- ), # This is for reading to/from XML: it not written to CFF.
- (6, "BlueValues", "delta", None, None),
- (7, "OtherBlues", "delta", None, None),
- (8, "FamilyBlues", "delta", None, None),
- (9, "FamilyOtherBlues", "delta", None, None),
- ((12, 9), "BlueScale", "number", 0.039625, None),
- ((12, 10), "BlueShift", "number", 7, None),
- ((12, 11), "BlueFuzz", "number", 1, None),
- (10, "StdHW", "number", None, None),
- (11, "StdVW", "number", None, None),
- ((12, 12), "StemSnapH", "delta", None, None),
- ((12, 13), "StemSnapV", "delta", None, None),
- ((12, 17), "LanguageGroup", "number", 0, None),
- ((12, 18), "ExpansionFactor", "number", 0.06, None),
- (19, "Subrs", "number", None, SubrsConverter()),
- ]
- def addConverters(table):
- for i in range(len(table)):
- op, name, arg, default, conv = table[i]
- if conv is not None:
- continue
- if arg in ("delta", "array"):
- conv = ArrayConverter()
- elif arg == "number":
- conv = NumberConverter()
- elif arg == "SID":
- conv = ASCIIConverter()
- elif arg == "blendList":
- conv = None
- else:
- assert False
- table[i] = op, name, arg, default, conv
- addConverters(privateDictOperators)
- addConverters(topDictOperators)
- class TopDictDecompiler(psCharStrings.DictDecompiler):
- operators = buildOperatorDict(topDictOperators)
- class PrivateDictDecompiler(psCharStrings.DictDecompiler):
- operators = buildOperatorDict(privateDictOperators)
- class DictCompiler(object):
- maxBlendStack = 0
- def __init__(self, dictObj, strings, parent, isCFF2=None):
- if strings:
- assert isinstance(strings, IndexedStrings)
- if isCFF2 is None and hasattr(parent, "isCFF2"):
- isCFF2 = parent.isCFF2
- assert isCFF2 is not None
- self.isCFF2 = isCFF2
- self.dictObj = dictObj
- self.strings = strings
- self.parent = parent
- rawDict = {}
- for name in dictObj.order:
- value = getattr(dictObj, name, None)
- if value is None:
- continue
- conv = dictObj.converters[name]
- value = conv.write(dictObj, value)
- if value == dictObj.defaults.get(name):
- continue
- rawDict[name] = value
- self.rawDict = rawDict
- def setPos(self, pos, endPos):
- pass
- def getDataLength(self):
- return len(self.compile("getDataLength"))
- def compile(self, reason):
- log.log(DEBUG, "-- compiling %s for %s", self.__class__.__name__, reason)
- rawDict = self.rawDict
- data = []
- for name in self.dictObj.order:
- value = rawDict.get(name)
- if value is None:
- continue
- op, argType = self.opcodes[name]
- if isinstance(argType, tuple):
- l = len(argType)
- assert len(value) == l, "value doesn't match arg type"
- for i in range(l):
- arg = argType[i]
- v = value[i]
- arghandler = getattr(self, "arg_" + arg)
- data.append(arghandler(v))
- else:
- arghandler = getattr(self, "arg_" + argType)
- data.append(arghandler(value))
- data.append(op)
- data = bytesjoin(data)
- return data
- def toFile(self, file):
- data = self.compile("toFile")
- file.write(data)
- def arg_number(self, num):
- if isinstance(num, list):
- data = [encodeNumber(val) for val in num]
- data.append(encodeNumber(1))
- data.append(bytechr(blendOp))
- datum = bytesjoin(data)
- else:
- datum = encodeNumber(num)
- return datum
- def arg_SID(self, s):
- return psCharStrings.encodeIntCFF(self.strings.getSID(s))
- def arg_array(self, value):
- data = []
- for num in value:
- data.append(self.arg_number(num))
- return bytesjoin(data)
- def arg_delta(self, value):
- if not value:
- return b""
- val0 = value[0]
- if isinstance(val0, list):
- data = self.arg_delta_blend(value)
- else:
- out = []
- last = 0
- for v in value:
- out.append(v - last)
- last = v
- data = []
- for num in out:
- data.append(encodeNumber(num))
- return bytesjoin(data)
- def arg_delta_blend(self, value):
- """A delta list with blend lists has to be *all* blend lists.
- The value is a list is arranged as follows::
- [
- [V0, d0..dn]
- [V1, d0..dn]
- ...
- [Vm, d0..dn]
- ]
- ``V`` is the absolute coordinate value from the default font, and ``d0-dn``
- are the delta values from the *n* regions. Each ``V`` is an absolute
- coordinate from the default font.
- We want to return a list::
- [
- [v0, v1..vm]
- [d0..dn]
- ...
- [d0..dn]
- numBlends
- blendOp
- ]
- where each ``v`` is relative to the previous default font value.
- """
- numMasters = len(value[0])
- numBlends = len(value)
- numStack = (numBlends * numMasters) + 1
- if numStack > self.maxBlendStack:
- # Figure out the max number of value we can blend
- # and divide this list up into chunks of that size.
- numBlendValues = int((self.maxBlendStack - 1) / numMasters)
- out = []
- while True:
- numVal = min(len(value), numBlendValues)
- if numVal == 0:
- break
- valList = value[0:numVal]
- out1 = self.arg_delta_blend(valList)
- out.extend(out1)
- value = value[numVal:]
- else:
- firstList = [0] * numBlends
- deltaList = [None] * numBlends
- i = 0
- prevVal = 0
- while i < numBlends:
- # For PrivateDict BlueValues, the default font
- # values are absolute, not relative.
- # Must convert these back to relative coordinates
- # before writing to CFF2.
- defaultValue = value[i][0]
- firstList[i] = defaultValue - prevVal
- prevVal = defaultValue
- deltaList[i] = value[i][1:]
- i += 1
- relValueList = firstList
- for blendList in deltaList:
- relValueList.extend(blendList)
- out = [encodeNumber(val) for val in relValueList]
- out.append(encodeNumber(numBlends))
- out.append(bytechr(blendOp))
- return out
- def encodeNumber(num):
- if isinstance(num, float):
- return psCharStrings.encodeFloat(num)
- else:
- return psCharStrings.encodeIntCFF(num)
- class TopDictCompiler(DictCompiler):
- opcodes = buildOpcodeDict(topDictOperators)
- def getChildren(self, strings):
- isCFF2 = self.isCFF2
- children = []
- if self.dictObj.cff2GetGlyphOrder is None:
- if hasattr(self.dictObj, "charset") and self.dictObj.charset:
- if hasattr(self.dictObj, "ROS"): # aka isCID
- charsetCode = None
- else:
- charsetCode = getStdCharSet(self.dictObj.charset)
- if charsetCode is None:
- children.append(
- CharsetCompiler(strings, self.dictObj.charset, self)
- )
- else:
- self.rawDict["charset"] = charsetCode
- if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding:
- encoding = self.dictObj.Encoding
- if not isinstance(encoding, str):
- children.append(EncodingCompiler(strings, encoding, self))
- else:
- if hasattr(self.dictObj, "VarStore"):
- varStoreData = self.dictObj.VarStore
- varStoreComp = VarStoreCompiler(varStoreData, self)
- children.append(varStoreComp)
- if hasattr(self.dictObj, "FDSelect"):
- # I have not yet supported merging a ttx CFF-CID font, as there are
- # interesting issues about merging the FDArrays. Here I assume that
- # either the font was read from XML, and the FDSelect indices are all
- # in the charstring data, or the FDSelect array is already fully defined.
- fdSelect = self.dictObj.FDSelect
- # probably read in from XML; assume fdIndex in CharString data
- if len(fdSelect) == 0:
- charStrings = self.dictObj.CharStrings
- for name in self.dictObj.charset:
- fdSelect.append(charStrings[name].fdSelectIndex)
- fdSelectComp = FDSelectCompiler(fdSelect, self)
- children.append(fdSelectComp)
- if hasattr(self.dictObj, "CharStrings"):
- items = []
- charStrings = self.dictObj.CharStrings
- for name in self.dictObj.charset:
- items.append(charStrings[name])
- charStringsComp = CharStringsCompiler(items, strings, self, isCFF2=isCFF2)
- children.append(charStringsComp)
- if hasattr(self.dictObj, "FDArray"):
- # I have not yet supported merging a ttx CFF-CID font, as there are
- # interesting issues about merging the FDArrays. Here I assume that the
- # FDArray info is correct and complete.
- fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self)
- children.append(fdArrayIndexComp)
- children.extend(fdArrayIndexComp.getChildren(strings))
- if hasattr(self.dictObj, "Private"):
- privComp = self.dictObj.Private.getCompiler(strings, self)
- children.append(privComp)
- children.extend(privComp.getChildren(strings))
- return children
- class FontDictCompiler(DictCompiler):
- opcodes = buildOpcodeDict(topDictOperators)
- def __init__(self, dictObj, strings, parent, isCFF2=None):
- super(FontDictCompiler, self).__init__(dictObj, strings, parent, isCFF2=isCFF2)
- #
- # We now take some effort to detect if there were any key/value pairs
- # supplied that were ignored in the FontDict context, and issue a warning
- # for those cases.
- #
- ignoredNames = []
- dictObj = self.dictObj
- for name in sorted(set(dictObj.converters) - set(dictObj.order)):
- if name in dictObj.rawDict:
- # The font was directly read from binary. In this
- # case, we want to report *all* "useless" key/value
- # pairs that are in the font, not just the ones that
- # are different from the default.
- ignoredNames.append(name)
- else:
- # The font was probably read from a TTX file. We only
- # warn about keys whos value is not the default. The
- # ones that have the default value will not be written
- # to binary anyway.
- default = dictObj.defaults.get(name)
- if default is not None:
- conv = dictObj.converters[name]
- default = conv.read(dictObj, default)
- if getattr(dictObj, name, None) != default:
- ignoredNames.append(name)
- if ignoredNames:
- log.warning(
- "Some CFF FDArray/FontDict keys were ignored upon compile: "
- + " ".join(sorted(ignoredNames))
- )
- def getChildren(self, strings):
- children = []
- if hasattr(self.dictObj, "Private"):
- privComp = self.dictObj.Private.getCompiler(strings, self)
- children.append(privComp)
- children.extend(privComp.getChildren(strings))
- return children
- class PrivateDictCompiler(DictCompiler):
- maxBlendStack = maxStackLimit
- opcodes = buildOpcodeDict(privateDictOperators)
- def setPos(self, pos, endPos):
- size = endPos - pos
- self.parent.rawDict["Private"] = size, pos
- self.pos = pos
- def getChildren(self, strings):
- children = []
- if hasattr(self.dictObj, "Subrs"):
- children.append(self.dictObj.Subrs.getCompiler(strings, self))
- return children
- class BaseDict(object):
- def __init__(self, strings=None, file=None, offset=None, isCFF2=None):
- assert (isCFF2 is None) == (file is None)
- self.rawDict = {}
- self.skipNames = []
- self.strings = strings
- if file is None:
- return
- self._isCFF2 = isCFF2
- self.file = file
- if offset is not None:
- log.log(DEBUG, "loading %s at %s", self.__class__.__name__, offset)
- self.offset = offset
- def decompile(self, data):
- log.log(DEBUG, " length %s is %d", self.__class__.__name__, len(data))
- dec = self.decompilerClass(self.strings, self)
- dec.decompile(data)
- self.rawDict = dec.getDict()
- self.postDecompile()
- def postDecompile(self):
- pass
- def getCompiler(self, strings, parent, isCFF2=None):
- return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
- def __getattr__(self, name):
- if name[:2] == name[-2:] == "__":
- # to make deepcopy() and pickle.load() work, we need to signal with
- # AttributeError that dunder methods like '__deepcopy__' or '__getstate__'
- # aren't implemented. For more details, see:
- # https://github.com/fonttools/fonttools/pull/1488
- raise AttributeError(name)
- value = self.rawDict.get(name, None)
- if value is None:
- value = self.defaults.get(name)
- if value is None:
- raise AttributeError(name)
- conv = self.converters[name]
- value = conv.read(self, value)
- setattr(self, name, value)
- return value
- def toXML(self, xmlWriter):
- for name in self.order:
- if name in self.skipNames:
- continue
- value = getattr(self, name, None)
- # XXX For "charset" we never skip calling xmlWrite even if the
- # value is None, so we always write the following XML comment:
- #
- # <!-- charset is dumped separately as the 'GlyphOrder' element -->
- #
- # Charset is None when 'CFF ' table is imported from XML into an
- # empty TTFont(). By writing this comment all the time, we obtain
- # the same XML output whether roundtripping XML-to-XML or
- # dumping binary-to-XML
- if value is None and name != "charset":
- continue
- conv = self.converters[name]
- conv.xmlWrite(xmlWriter, name, value)
- ignoredNames = set(self.rawDict) - set(self.order)
- if ignoredNames:
- xmlWriter.comment(
- "some keys were ignored: %s" % " ".join(sorted(ignoredNames))
- )
- xmlWriter.newline()
- def fromXML(self, name, attrs, content):
- conv = self.converters[name]
- value = conv.xmlRead(name, attrs, content, self)
- setattr(self, name, value)
- class TopDict(BaseDict):
- """The ``TopDict`` represents the top-level dictionary holding font
- information. CFF2 tables contain a restricted set of top-level entries
- as described `here <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#7-top-dict-data>`_,
- but CFF tables may contain a wider range of information. This information
- can be accessed through attributes or through the dictionary returned
- through the ``rawDict`` property:
- .. code:: python
- font = tt["CFF "].cff[0]
- font.FamilyName
- # 'Linux Libertine O'
- font.rawDict["FamilyName"]
- # 'Linux Libertine O'
- More information is available in the CFF file's private dictionary, accessed
- via the ``Private`` property:
- .. code:: python
- tt["CFF "].cff[0].Private.BlueValues
- # [-15, 0, 515, 515, 666, 666]
- """
- defaults = buildDefaults(topDictOperators)
- converters = buildConverters(topDictOperators)
- compilerClass = TopDictCompiler
- order = buildOrder(topDictOperators)
- decompilerClass = TopDictDecompiler
- def __init__(
- self,
- strings=None,
- file=None,
- offset=None,
- GlobalSubrs=None,
- cff2GetGlyphOrder=None,
- isCFF2=None,
- ):
- super(TopDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
- self.cff2GetGlyphOrder = cff2GetGlyphOrder
- self.GlobalSubrs = GlobalSubrs
- if isCFF2:
- self.defaults = buildDefaults(topDictOperators2)
- self.charset = cff2GetGlyphOrder()
- self.order = buildOrder(topDictOperators2)
- else:
- self.defaults = buildDefaults(topDictOperators)
- self.order = buildOrder(topDictOperators)
- def getGlyphOrder(self):
- """Returns a list of glyph names in the CFF font."""
- return self.charset
- def postDecompile(self):
- offset = self.rawDict.get("CharStrings")
- if offset is None:
- return
- # get the number of glyphs beforehand.
- self.file.seek(offset)
- if self._isCFF2:
- self.numGlyphs = readCard32(self.file)
- else:
- self.numGlyphs = readCard16(self.file)
- def toXML(self, xmlWriter):
- if hasattr(self, "CharStrings"):
- self.decompileAllCharStrings()
- if hasattr(self, "ROS"):
- self.skipNames = ["Encoding"]
- if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
- # these values have default values, but I only want them to show up
- # in CID fonts.
- self.skipNames = [
- "CIDFontVersion",
- "CIDFontRevision",
- "CIDFontType",
- "CIDCount",
- ]
- BaseDict.toXML(self, xmlWriter)
- def decompileAllCharStrings(self):
- # Make sure that all the Private Dicts have been instantiated.
- for i, charString in enumerate(self.CharStrings.values()):
- try:
- charString.decompile()
- except:
- log.error("Error in charstring %s", i)
- raise
- def recalcFontBBox(self):
- fontBBox = None
- for charString in self.CharStrings.values():
- bounds = charString.calcBounds(self.CharStrings)
- if bounds is not None:
- if fontBBox is not None:
- fontBBox = unionRect(fontBBox, bounds)
- else:
- fontBBox = bounds
- if fontBBox is None:
- self.FontBBox = self.defaults["FontBBox"][:]
- else:
- self.FontBBox = list(intRect(fontBBox))
- class FontDict(BaseDict):
- #
- # Since fonttools used to pass a lot of fields that are not relevant in the FDArray
- # FontDict, there are 'ttx' files in the wild that contain all these. These got in
- # the ttx files because fonttools writes explicit values for all the TopDict default
- # values. These are not actually illegal in the context of an FDArray FontDict - you
- # can legally, per spec, put any arbitrary key/value pair in a FontDict - but are
- # useless since current major company CFF interpreters ignore anything but the set
- # listed in this file. So, we just silently skip them. An exception is Weight: this
- # is not used by any interpreter, but some foundries have asked that this be
- # supported in FDArray FontDicts just to preserve information about the design when
- # the font is being inspected.
- #
- # On top of that, there are fonts out there that contain such useless FontDict values.
- #
- # By subclassing TopDict, we *allow* all key/values from TopDict, both when reading
- # from binary or when reading from XML, but by overriding `order` with a limited
- # list of names, we ensure that only the useful names ever get exported to XML and
- # ever get compiled into the binary font.
- #
- # We override compilerClass so we can warn about "useless" key/value pairs, either
- # from the original binary font or from TTX input.
- #
- # See:
- # - https://github.com/fonttools/fonttools/issues/740
- # - https://github.com/fonttools/fonttools/issues/601
- # - https://github.com/adobe-type-tools/afdko/issues/137
- #
- defaults = {}
- converters = buildConverters(topDictOperators)
- compilerClass = FontDictCompiler
- orderCFF = ["FontName", "FontMatrix", "Weight", "Private"]
- orderCFF2 = ["Private"]
- decompilerClass = TopDictDecompiler
- def __init__(
- self,
- strings=None,
- file=None,
- offset=None,
- GlobalSubrs=None,
- isCFF2=None,
- vstore=None,
- ):
- super(FontDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
- self.vstore = vstore
- self.setCFF2(isCFF2)
- def setCFF2(self, isCFF2):
- # isCFF2 may be None.
- if isCFF2:
- self.order = self.orderCFF2
- self._isCFF2 = True
- else:
- self.order = self.orderCFF
- self._isCFF2 = False
- class PrivateDict(BaseDict):
- defaults = buildDefaults(privateDictOperators)
- converters = buildConverters(privateDictOperators)
- order = buildOrder(privateDictOperators)
- decompilerClass = PrivateDictDecompiler
- compilerClass = PrivateDictCompiler
- def __init__(self, strings=None, file=None, offset=None, isCFF2=None, vstore=None):
- super(PrivateDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
- self.vstore = vstore
- if isCFF2:
- self.defaults = buildDefaults(privateDictOperators2)
- self.order = buildOrder(privateDictOperators2)
- # Provide dummy values. This avoids needing to provide
- # an isCFF2 state in a lot of places.
- self.nominalWidthX = self.defaultWidthX = None
- self._isCFF2 = True
- else:
- self.defaults = buildDefaults(privateDictOperators)
- self.order = buildOrder(privateDictOperators)
- self._isCFF2 = False
- @property
- def in_cff2(self):
- return self._isCFF2
- def getNumRegions(self, vi=None): # called from misc/psCharStrings.py
- # if getNumRegions is being called, we can assume that VarStore exists.
- if vi is None:
- if hasattr(self, "vsindex"):
- vi = self.vsindex
- else:
- vi = 0
- numRegions = self.vstore.getNumRegions(vi)
- return numRegions
- class IndexedStrings(object):
- """SID -> string mapping."""
- def __init__(self, file=None):
- if file is None:
- strings = []
- else:
- strings = [tostr(s, encoding="latin1") for s in Index(file, isCFF2=False)]
- self.strings = strings
- def getCompiler(self):
- return IndexedStringsCompiler(self, None, self, isCFF2=False)
- def __len__(self):
- return len(self.strings)
- def __getitem__(self, SID):
- if SID < cffStandardStringCount:
- return cffStandardStrings[SID]
- else:
- return self.strings[SID - cffStandardStringCount]
- def getSID(self, s):
- if not hasattr(self, "stringMapping"):
- self.buildStringMapping()
- s = tostr(s, encoding="latin1")
- if s in cffStandardStringMapping:
- SID = cffStandardStringMapping[s]
- elif s in self.stringMapping:
- SID = self.stringMapping[s]
- else:
- SID = len(self.strings) + cffStandardStringCount
- self.strings.append(s)
- self.stringMapping[s] = SID
- return SID
- def getStrings(self):
- return self.strings
- def buildStringMapping(self):
- self.stringMapping = {}
- for index in range(len(self.strings)):
- self.stringMapping[self.strings[index]] = index + cffStandardStringCount
- # The 391 Standard Strings as used in the CFF format.
- # from Adobe Technical None #5176, version 1.0, 18 March 1998
- cffStandardStrings = [
- ".notdef",
- "space",
- "exclam",
- "quotedbl",
- "numbersign",
- "dollar",
- "percent",
- "ampersand",
- "quoteright",
- "parenleft",
- "parenright",
- "asterisk",
- "plus",
- "comma",
- "hyphen",
- "period",
- "slash",
- "zero",
- "one",
- "two",
- "three",
- "four",
- "five",
- "six",
- "seven",
- "eight",
- "nine",
- "colon",
- "semicolon",
- "less",
- "equal",
- "greater",
- "question",
- "at",
- "A",
- "B",
- "C",
- "D",
- "E",
- "F",
- "G",
- "H",
- "I",
- "J",
- "K",
- "L",
- "M",
- "N",
- "O",
- "P",
- "Q",
- "R",
- "S",
- "T",
- "U",
- "V",
- "W",
- "X",
- "Y",
- "Z",
- "bracketleft",
- "backslash",
- "bracketright",
- "asciicircum",
- "underscore",
- "quoteleft",
- "a",
- "b",
- "c",
- "d",
- "e",
- "f",
- "g",
- "h",
- "i",
- "j",
- "k",
- "l",
- "m",
- "n",
- "o",
- "p",
- "q",
- "r",
- "s",
- "t",
- "u",
- "v",
- "w",
- "x",
- "y",
- "z",
- "braceleft",
- "bar",
- "braceright",
- "asciitilde",
- "exclamdown",
- "cent",
- "sterling",
- "fraction",
- "yen",
- "florin",
- "section",
- "currency",
- "quotesingle",
- "quotedblleft",
- "guillemotleft",
- "guilsinglleft",
- "guilsinglright",
- "fi",
- "fl",
- "endash",
- "dagger",
- "daggerdbl",
- "periodcentered",
- "paragraph",
- "bullet",
- "quotesinglbase",
- "quotedblbase",
- "quotedblright",
- "guillemotright",
- "ellipsis",
- "perthousand",
- "questiondown",
- "grave",
- "acute",
- "circumflex",
- "tilde",
- "macron",
- "breve",
- "dotaccent",
- "dieresis",
- "ring",
- "cedilla",
- "hungarumlaut",
- "ogonek",
- "caron",
- "emdash",
- "AE",
- "ordfeminine",
- "Lslash",
- "Oslash",
- "OE",
- "ordmasculine",
- "ae",
- "dotlessi",
- "lslash",
- "oslash",
- "oe",
- "germandbls",
- "onesuperior",
- "logicalnot",
- "mu",
- "trademark",
- "Eth",
- "onehalf",
- "plusminus",
- "Thorn",
- "onequarter",
- "divide",
- "brokenbar",
- "degree",
- "thorn",
- "threequarters",
- "twosuperior",
- "registered",
- "minus",
- "eth",
- "multiply",
- "threesuperior",
- "copyright",
- "Aacute",
- "Acircumflex",
- "Adieresis",
- "Agrave",
- "Aring",
- "Atilde",
- "Ccedilla",
- "Eacute",
- "Ecircumflex",
- "Edieresis",
- "Egrave",
- "Iacute",
- "Icircumflex",
- "Idieresis",
- "Igrave",
- "Ntilde",
- "Oacute",
- "Ocircumflex",
- "Odieresis",
- "Ograve",
- "Otilde",
- "Scaron",
- "Uacute",
- "Ucircumflex",
- "Udieresis",
- "Ugrave",
- "Yacute",
- "Ydieresis",
- "Zcaron",
- "aacute",
- "acircumflex",
- "adieresis",
- "agrave",
- "aring",
- "atilde",
- "ccedilla",
- "eacute",
- "ecircumflex",
- "edieresis",
- "egrave",
- "iacute",
- "icircumflex",
- "idieresis",
- "igrave",
- "ntilde",
- "oacute",
- "ocircumflex",
- "odieresis",
- "ograve",
- "otilde",
- "scaron",
- "uacute",
- "ucircumflex",
- "udieresis",
- "ugrave",
- "yacute",
- "ydieresis",
- "zcaron",
- "exclamsmall",
- "Hungarumlautsmall",
- "dollaroldstyle",
- "dollarsuperior",
- "ampersandsmall",
- "Acutesmall",
- "parenleftsuperior",
- "parenrightsuperior",
- "twodotenleader",
- "onedotenleader",
- "zerooldstyle",
- "oneoldstyle",
- "twooldstyle",
- "threeoldstyle",
- "fouroldstyle",
- "fiveoldstyle",
- "sixoldstyle",
- "sevenoldstyle",
- "eightoldstyle",
- "nineoldstyle",
- "commasuperior",
- "threequartersemdash",
- "periodsuperior",
- "questionsmall",
- "asuperior",
- "bsuperior",
- "centsuperior",
- "dsuperior",
- "esuperior",
- "isuperior",
- "lsuperior",
- "msuperior",
- "nsuperior",
- "osuperior",
- "rsuperior",
- "ssuperior",
- "tsuperior",
- "ff",
- "ffi",
- "ffl",
- "parenleftinferior",
- "parenrightinferior",
- "Circumflexsmall",
- "hyphensuperior",
- "Gravesmall",
- "Asmall",
- "Bsmall",
- "Csmall",
- "Dsmall",
- "Esmall",
- "Fsmall",
- "Gsmall",
- "Hsmall",
- "Ismall",
- "Jsmall",
- "Ksmall",
- "Lsmall",
- "Msmall",
- "Nsmall",
- "Osmall",
- "Psmall",
- "Qsmall",
- "Rsmall",
- "Ssmall",
- "Tsmall",
- "Usmall",
- "Vsmall",
- "Wsmall",
- "Xsmall",
- "Ysmall",
- "Zsmall",
- "colonmonetary",
- "onefitted",
- "rupiah",
- "Tildesmall",
- "exclamdownsmall",
- "centoldstyle",
- "Lslashsmall",
- "Scaronsmall",
- "Zcaronsmall",
- "Dieresissmall",
- "Brevesmall",
- "Caronsmall",
- "Dotaccentsmall",
- "Macronsmall",
- "figuredash",
- "hypheninferior",
- "Ogoneksmall",
- "Ringsmall",
- "Cedillasmall",
- "questiondownsmall",
- "oneeighth",
- "threeeighths",
- "fiveeighths",
- "seveneighths",
- "onethird",
- "twothirds",
- "zerosuperior",
- "foursuperior",
- "fivesuperior",
- "sixsuperior",
- "sevensuperior",
- "eightsuperior",
- "ninesuperior",
- "zeroinferior",
- "oneinferior",
- "twoinferior",
- "threeinferior",
- "fourinferior",
- "fiveinferior",
- "sixinferior",
- "seveninferior",
- "eightinferior",
- "nineinferior",
- "centinferior",
- "dollarinferior",
- "periodinferior",
- "commainferior",
- "Agravesmall",
- "Aacutesmall",
- "Acircumflexsmall",
- "Atildesmall",
- "Adieresissmall",
- "Aringsmall",
- "AEsmall",
- "Ccedillasmall",
- "Egravesmall",
- "Eacutesmall",
- "Ecircumflexsmall",
- "Edieresissmall",
- "Igravesmall",
- "Iacutesmall",
- "Icircumflexsmall",
- "Idieresissmall",
- "Ethsmall",
- "Ntildesmall",
- "Ogravesmall",
- "Oacutesmall",
- "Ocircumflexsmall",
- "Otildesmall",
- "Odieresissmall",
- "OEsmall",
- "Oslashsmall",
- "Ugravesmall",
- "Uacutesmall",
- "Ucircumflexsmall",
- "Udieresissmall",
- "Yacutesmall",
- "Thornsmall",
- "Ydieresissmall",
- "001.000",
- "001.001",
- "001.002",
- "001.003",
- "Black",
- "Bold",
- "Book",
- "Light",
- "Medium",
- "Regular",
- "Roman",
- "Semibold",
- ]
- cffStandardStringCount = 391
- assert len(cffStandardStrings) == cffStandardStringCount
- # build reverse mapping
- cffStandardStringMapping = {}
- for _i in range(cffStandardStringCount):
- cffStandardStringMapping[cffStandardStrings[_i]] = _i
- cffISOAdobeStrings = [
- ".notdef",
- "space",
- "exclam",
- "quotedbl",
- "numbersign",
- "dollar",
- "percent",
- "ampersand",
- "quoteright",
- "parenleft",
- "parenright",
- "asterisk",
- "plus",
- "comma",
- "hyphen",
- "period",
- "slash",
- "zero",
- "one",
- "two",
- "three",
- "four",
- "five",
- "six",
- "seven",
- "eight",
- "nine",
- "colon",
- "semicolon",
- "less",
- "equal",
- "greater",
- "question",
- "at",
- "A",
- "B",
- "C",
- "D",
- "E",
- "F",
- "G",
- "H",
- "I",
- "J",
- "K",
- "L",
- "M",
- "N",
- "O",
- "P",
- "Q",
- "R",
- "S",
- "T",
- "U",
- "V",
- "W",
- "X",
- "Y",
- "Z",
- "bracketleft",
- "backslash",
- "bracketright",
- "asciicircum",
- "underscore",
- "quoteleft",
- "a",
- "b",
- "c",
- "d",
- "e",
- "f",
- "g",
- "h",
- "i",
- "j",
- "k",
- "l",
- "m",
- "n",
- "o",
- "p",
- "q",
- "r",
- "s",
- "t",
- "u",
- "v",
- "w",
- "x",
- "y",
- "z",
- "braceleft",
- "bar",
- "braceright",
- "asciitilde",
- "exclamdown",
- "cent",
- "sterling",
- "fraction",
- "yen",
- "florin",
- "section",
- "currency",
- "quotesingle",
- "quotedblleft",
- "guillemotleft",
- "guilsinglleft",
- "guilsinglright",
- "fi",
- "fl",
- "endash",
- "dagger",
- "daggerdbl",
- "periodcentered",
- "paragraph",
- "bullet",
- "quotesinglbase",
- "quotedblbase",
- "quotedblright",
- "guillemotright",
- "ellipsis",
- "perthousand",
- "questiondown",
- "grave",
- "acute",
- "circumflex",
- "tilde",
- "macron",
- "breve",
- "dotaccent",
- "dieresis",
- "ring",
- "cedilla",
- "hungarumlaut",
- "ogonek",
- "caron",
- "emdash",
- "AE",
- "ordfeminine",
- "Lslash",
- "Oslash",
- "OE",
- "ordmasculine",
- "ae",
- "dotlessi",
- "lslash",
- "oslash",
- "oe",
- "germandbls",
- "onesuperior",
- "logicalnot",
- "mu",
- "trademark",
- "Eth",
- "onehalf",
- "plusminus",
- "Thorn",
- "onequarter",
- "divide",
- "brokenbar",
- "degree",
- "thorn",
- "threequarters",
- "twosuperior",
- "registered",
- "minus",
- "eth",
- "multiply",
- "threesuperior",
- "copyright",
- "Aacute",
- "Acircumflex",
- "Adieresis",
- "Agrave",
- "Aring",
- "Atilde",
- "Ccedilla",
- "Eacute",
- "Ecircumflex",
- "Edieresis",
- "Egrave",
- "Iacute",
- "Icircumflex",
- "Idieresis",
- "Igrave",
- "Ntilde",
- "Oacute",
- "Ocircumflex",
- "Odieresis",
- "Ograve",
- "Otilde",
- "Scaron",
- "Uacute",
- "Ucircumflex",
- "Udieresis",
- "Ugrave",
- "Yacute",
- "Ydieresis",
- "Zcaron",
- "aacute",
- "acircumflex",
- "adieresis",
- "agrave",
- "aring",
- "atilde",
- "ccedilla",
- "eacute",
- "ecircumflex",
- "edieresis",
- "egrave",
- "iacute",
- "icircumflex",
- "idieresis",
- "igrave",
- "ntilde",
- "oacute",
- "ocircumflex",
- "odieresis",
- "ograve",
- "otilde",
- "scaron",
- "uacute",
- "ucircumflex",
- "udieresis",
- "ugrave",
- "yacute",
- "ydieresis",
- "zcaron",
- ]
- cffISOAdobeStringCount = 229
- assert len(cffISOAdobeStrings) == cffISOAdobeStringCount
- cffIExpertStrings = [
- ".notdef",
- "space",
- "exclamsmall",
- "Hungarumlautsmall",
- "dollaroldstyle",
- "dollarsuperior",
- "ampersandsmall",
- "Acutesmall",
- "parenleftsuperior",
- "parenrightsuperior",
- "twodotenleader",
- "onedotenleader",
- "comma",
- "hyphen",
- "period",
- "fraction",
- "zerooldstyle",
- "oneoldstyle",
- "twooldstyle",
- "threeoldstyle",
- "fouroldstyle",
- "fiveoldstyle",
- "sixoldstyle",
- "sevenoldstyle",
- "eightoldstyle",
- "nineoldstyle",
- "colon",
- "semicolon",
- "commasuperior",
- "threequartersemdash",
- "periodsuperior",
- "questionsmall",
- "asuperior",
- "bsuperior",
- "centsuperior",
- "dsuperior",
- "esuperior",
- "isuperior",
- "lsuperior",
- "msuperior",
- "nsuperior",
- "osuperior",
- "rsuperior",
- "ssuperior",
- "tsuperior",
- "ff",
- "fi",
- "fl",
- "ffi",
- "ffl",
- "parenleftinferior",
- "parenrightinferior",
- "Circumflexsmall",
- "hyphensuperior",
- "Gravesmall",
- "Asmall",
- "Bsmall",
- "Csmall",
- "Dsmall",
- "Esmall",
- "Fsmall",
- "Gsmall",
- "Hsmall",
- "Ismall",
- "Jsmall",
- "Ksmall",
- "Lsmall",
- "Msmall",
- "Nsmall",
- "Osmall",
- "Psmall",
- "Qsmall",
- "Rsmall",
- "Ssmall",
- "Tsmall",
- "Usmall",
- "Vsmall",
- "Wsmall",
- "Xsmall",
- "Ysmall",
- "Zsmall",
- "colonmonetary",
- "onefitted",
- "rupiah",
- "Tildesmall",
- "exclamdownsmall",
- "centoldstyle",
- "Lslashsmall",
- "Scaronsmall",
- "Zcaronsmall",
- "Dieresissmall",
- "Brevesmall",
- "Caronsmall",
- "Dotaccentsmall",
- "Macronsmall",
- "figuredash",
- "hypheninferior",
- "Ogoneksmall",
- "Ringsmall",
- "Cedillasmall",
- "onequarter",
- "onehalf",
- "threequarters",
- "questiondownsmall",
- "oneeighth",
- "threeeighths",
- "fiveeighths",
- "seveneighths",
- "onethird",
- "twothirds",
- "zerosuperior",
- "onesuperior",
- "twosuperior",
- "threesuperior",
- "foursuperior",
- "fivesuperior",
- "sixsuperior",
- "sevensuperior",
- "eightsuperior",
- "ninesuperior",
- "zeroinferior",
- "oneinferior",
- "twoinferior",
- "threeinferior",
- "fourinferior",
- "fiveinferior",
- "sixinferior",
- "seveninferior",
- "eightinferior",
- "nineinferior",
- "centinferior",
- "dollarinferior",
- "periodinferior",
- "commainferior",
- "Agravesmall",
- "Aacutesmall",
- "Acircumflexsmall",
- "Atildesmall",
- "Adieresissmall",
- "Aringsmall",
- "AEsmall",
- "Ccedillasmall",
- "Egravesmall",
- "Eacutesmall",
- "Ecircumflexsmall",
- "Edieresissmall",
- "Igravesmall",
- "Iacutesmall",
- "Icircumflexsmall",
- "Idieresissmall",
- "Ethsmall",
- "Ntildesmall",
- "Ogravesmall",
- "Oacutesmall",
- "Ocircumflexsmall",
- "Otildesmall",
- "Odieresissmall",
- "OEsmall",
- "Oslashsmall",
- "Ugravesmall",
- "Uacutesmall",
- "Ucircumflexsmall",
- "Udieresissmall",
- "Yacutesmall",
- "Thornsmall",
- "Ydieresissmall",
- ]
- cffExpertStringCount = 166
- assert len(cffIExpertStrings) == cffExpertStringCount
- cffExpertSubsetStrings = [
- ".notdef",
- "space",
- "dollaroldstyle",
- "dollarsuperior",
- "parenleftsuperior",
- "parenrightsuperior",
- "twodotenleader",
- "onedotenleader",
- "comma",
- "hyphen",
- "period",
- "fraction",
- "zerooldstyle",
- "oneoldstyle",
- "twooldstyle",
- "threeoldstyle",
- "fouroldstyle",
- "fiveoldstyle",
- "sixoldstyle",
- "sevenoldstyle",
- "eightoldstyle",
- "nineoldstyle",
- "colon",
- "semicolon",
- "commasuperior",
- "threequartersemdash",
- "periodsuperior",
- "asuperior",
- "bsuperior",
- "centsuperior",
- "dsuperior",
- "esuperior",
- "isuperior",
- "lsuperior",
- "msuperior",
- "nsuperior",
- "osuperior",
- "rsuperior",
- "ssuperior",
- "tsuperior",
- "ff",
- "fi",
- "fl",
- "ffi",
- "ffl",
- "parenleftinferior",
- "parenrightinferior",
- "hyphensuperior",
- "colonmonetary",
- "onefitted",
- "rupiah",
- "centoldstyle",
- "figuredash",
- "hypheninferior",
- "onequarter",
- "onehalf",
- "threequarters",
- "oneeighth",
- "threeeighths",
- "fiveeighths",
- "seveneighths",
- "onethird",
- "twothirds",
- "zerosuperior",
- "onesuperior",
- "twosuperior",
- "threesuperior",
- "foursuperior",
- "fivesuperior",
- "sixsuperior",
- "sevensuperior",
- "eightsuperior",
- "ninesuperior",
- "zeroinferior",
- "oneinferior",
- "twoinferior",
- "threeinferior",
- "fourinferior",
- "fiveinferior",
- "sixinferior",
- "seveninferior",
- "eightinferior",
- "nineinferior",
- "centinferior",
- "dollarinferior",
- "periodinferior",
- "commainferior",
- ]
- cffExpertSubsetStringCount = 87
- assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount
|