123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654 |
- """cffLib: read/write Adobe CFF fonts
- OpenType fonts with PostScript outlines contain a completely independent
- font file, 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)
- 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()
- data = [packCard16(len(varStoreData.data)), 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
- # befor 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
|