_g_l_y_f.py 96 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683
  1. """_g_l_y_f.py -- Converter classes for the 'glyf' table."""
  2. from collections import namedtuple
  3. from fontTools.misc import sstruct
  4. from fontTools import ttLib
  5. from fontTools import version
  6. from fontTools.misc.transform import DecomposedTransform
  7. from fontTools.misc.textTools import tostr, safeEval, pad
  8. from fontTools.misc.arrayTools import updateBounds, pointInRect
  9. from fontTools.misc.bezierTools import calcQuadraticBounds
  10. from fontTools.misc.fixedTools import (
  11. fixedToFloat as fi2fl,
  12. floatToFixed as fl2fi,
  13. floatToFixedToStr as fl2str,
  14. strToFixedToFloat as str2fl,
  15. )
  16. from fontTools.misc.roundTools import noRound, otRound
  17. from fontTools.misc.vector import Vector
  18. from numbers import Number
  19. from . import DefaultTable
  20. from . import ttProgram
  21. import sys
  22. import struct
  23. import array
  24. import logging
  25. import math
  26. import os
  27. from fontTools.misc import xmlWriter
  28. from fontTools.misc.filenames import userNameToFileName
  29. from fontTools.misc.loggingTools import deprecateFunction
  30. from enum import IntFlag
  31. from functools import partial
  32. from types import SimpleNamespace
  33. from typing import Set
  34. log = logging.getLogger(__name__)
  35. # We compute the version the same as is computed in ttlib/__init__
  36. # so that we can write 'ttLibVersion' attribute of the glyf TTX files
  37. # when glyf is written to separate files.
  38. version = ".".join(version.split(".")[:2])
  39. #
  40. # The Apple and MS rasterizers behave differently for
  41. # scaled composite components: one does scale first and then translate
  42. # and the other does it vice versa. MS defined some flags to indicate
  43. # the difference, but it seems nobody actually _sets_ those flags.
  44. #
  45. # Funny thing: Apple seems to _only_ do their thing in the
  46. # WE_HAVE_A_SCALE (eg. Chicago) case, and not when it's WE_HAVE_AN_X_AND_Y_SCALE
  47. # (eg. Charcoal)...
  48. #
  49. SCALE_COMPONENT_OFFSET_DEFAULT = 0 # 0 == MS, 1 == Apple
  50. class table__g_l_y_f(DefaultTable.DefaultTable):
  51. """Glyph Data Table
  52. This class represents the `glyf <https://docs.microsoft.com/en-us/typography/opentype/spec/glyf>`_
  53. table, which contains outlines for glyphs in TrueType format. In many cases,
  54. it is easier to access and manipulate glyph outlines through the ``GlyphSet``
  55. object returned from :py:meth:`fontTools.ttLib.ttFont.getGlyphSet`::
  56. >> from fontTools.pens.boundsPen import BoundsPen
  57. >> glyphset = font.getGlyphSet()
  58. >> bp = BoundsPen(glyphset)
  59. >> glyphset["A"].draw(bp)
  60. >> bp.bounds
  61. (19, 0, 633, 716)
  62. However, this class can be used for low-level access to the ``glyf`` table data.
  63. Objects of this class support dictionary-like access, mapping glyph names to
  64. :py:class:`Glyph` objects::
  65. >> glyf = font["glyf"]
  66. >> len(glyf["Aacute"].components)
  67. 2
  68. Note that when adding glyphs to the font via low-level access to the ``glyf``
  69. table, the new glyphs must also be added to the ``hmtx``/``vmtx`` table::
  70. >> font["glyf"]["divisionslash"] = Glyph()
  71. >> font["hmtx"]["divisionslash"] = (640, 0)
  72. """
  73. dependencies = ["fvar"]
  74. # this attribute controls the amount of padding applied to glyph data upon compile.
  75. # Glyph lenghts are aligned to multiples of the specified value.
  76. # Allowed values are (0, 1, 2, 4). '0' means no padding; '1' (default) also means
  77. # no padding, except for when padding would allow to use short loca offsets.
  78. padding = 1
  79. def decompile(self, data, ttFont):
  80. self.axisTags = (
  81. [axis.axisTag for axis in ttFont["fvar"].axes] if "fvar" in ttFont else []
  82. )
  83. loca = ttFont["loca"]
  84. pos = int(loca[0])
  85. nextPos = 0
  86. noname = 0
  87. self.glyphs = {}
  88. self.glyphOrder = glyphOrder = ttFont.getGlyphOrder()
  89. self._reverseGlyphOrder = {}
  90. for i in range(0, len(loca) - 1):
  91. try:
  92. glyphName = glyphOrder[i]
  93. except IndexError:
  94. noname = noname + 1
  95. glyphName = "ttxautoglyph%s" % i
  96. nextPos = int(loca[i + 1])
  97. glyphdata = data[pos:nextPos]
  98. if len(glyphdata) != (nextPos - pos):
  99. raise ttLib.TTLibError("not enough 'glyf' table data")
  100. glyph = Glyph(glyphdata)
  101. self.glyphs[glyphName] = glyph
  102. pos = nextPos
  103. if len(data) - nextPos >= 4:
  104. log.warning(
  105. "too much 'glyf' table data: expected %d, received %d bytes",
  106. nextPos,
  107. len(data),
  108. )
  109. if noname:
  110. log.warning("%s glyphs have no name", noname)
  111. if ttFont.lazy is False: # Be lazy for None and True
  112. self.ensureDecompiled()
  113. def ensureDecompiled(self, recurse=False):
  114. # The recurse argument is unused, but part of the signature of
  115. # ensureDecompiled across the library.
  116. for glyph in self.glyphs.values():
  117. glyph.expand(self)
  118. def compile(self, ttFont):
  119. self.axisTags = (
  120. [axis.axisTag for axis in ttFont["fvar"].axes] if "fvar" in ttFont else []
  121. )
  122. if not hasattr(self, "glyphOrder"):
  123. self.glyphOrder = ttFont.getGlyphOrder()
  124. padding = self.padding
  125. assert padding in (0, 1, 2, 4)
  126. locations = []
  127. currentLocation = 0
  128. dataList = []
  129. recalcBBoxes = ttFont.recalcBBoxes
  130. boundsDone = set()
  131. for glyphName in self.glyphOrder:
  132. glyph = self.glyphs[glyphName]
  133. glyphData = glyph.compile(self, recalcBBoxes, boundsDone=boundsDone)
  134. if padding > 1:
  135. glyphData = pad(glyphData, size=padding)
  136. locations.append(currentLocation)
  137. currentLocation = currentLocation + len(glyphData)
  138. dataList.append(glyphData)
  139. locations.append(currentLocation)
  140. if padding == 1 and currentLocation < 0x20000:
  141. # See if we can pad any odd-lengthed glyphs to allow loca
  142. # table to use the short offsets.
  143. indices = [
  144. i for i, glyphData in enumerate(dataList) if len(glyphData) % 2 == 1
  145. ]
  146. if indices and currentLocation + len(indices) < 0x20000:
  147. # It fits. Do it.
  148. for i in indices:
  149. dataList[i] += b"\0"
  150. currentLocation = 0
  151. for i, glyphData in enumerate(dataList):
  152. locations[i] = currentLocation
  153. currentLocation += len(glyphData)
  154. locations[len(dataList)] = currentLocation
  155. data = b"".join(dataList)
  156. if "loca" in ttFont:
  157. ttFont["loca"].set(locations)
  158. if "maxp" in ttFont:
  159. ttFont["maxp"].numGlyphs = len(self.glyphs)
  160. if not data:
  161. # As a special case when all glyph in the font are empty, add a zero byte
  162. # to the table, so that OTS doesn’t reject it, and to make the table work
  163. # on Windows as well.
  164. # See https://github.com/khaledhosny/ots/issues/52
  165. data = b"\0"
  166. return data
  167. def toXML(self, writer, ttFont, splitGlyphs=False):
  168. notice = (
  169. "The xMin, yMin, xMax and yMax values\n"
  170. "will be recalculated by the compiler."
  171. )
  172. glyphNames = ttFont.getGlyphNames()
  173. if not splitGlyphs:
  174. writer.newline()
  175. writer.comment(notice)
  176. writer.newline()
  177. writer.newline()
  178. numGlyphs = len(glyphNames)
  179. if splitGlyphs:
  180. path, ext = os.path.splitext(writer.file.name)
  181. existingGlyphFiles = set()
  182. for glyphName in glyphNames:
  183. glyph = self.get(glyphName)
  184. if glyph is None:
  185. log.warning("glyph '%s' does not exist in glyf table", glyphName)
  186. continue
  187. if glyph.numberOfContours:
  188. if splitGlyphs:
  189. glyphPath = userNameToFileName(
  190. tostr(glyphName, "utf-8"),
  191. existingGlyphFiles,
  192. prefix=path + ".",
  193. suffix=ext,
  194. )
  195. existingGlyphFiles.add(glyphPath.lower())
  196. glyphWriter = xmlWriter.XMLWriter(
  197. glyphPath,
  198. idlefunc=writer.idlefunc,
  199. newlinestr=writer.newlinestr,
  200. )
  201. glyphWriter.begintag("ttFont", ttLibVersion=version)
  202. glyphWriter.newline()
  203. glyphWriter.begintag("glyf")
  204. glyphWriter.newline()
  205. glyphWriter.comment(notice)
  206. glyphWriter.newline()
  207. writer.simpletag("TTGlyph", src=os.path.basename(glyphPath))
  208. else:
  209. glyphWriter = writer
  210. glyphWriter.begintag(
  211. "TTGlyph",
  212. [
  213. ("name", glyphName),
  214. ("xMin", glyph.xMin),
  215. ("yMin", glyph.yMin),
  216. ("xMax", glyph.xMax),
  217. ("yMax", glyph.yMax),
  218. ],
  219. )
  220. glyphWriter.newline()
  221. glyph.toXML(glyphWriter, ttFont)
  222. glyphWriter.endtag("TTGlyph")
  223. glyphWriter.newline()
  224. if splitGlyphs:
  225. glyphWriter.endtag("glyf")
  226. glyphWriter.newline()
  227. glyphWriter.endtag("ttFont")
  228. glyphWriter.newline()
  229. glyphWriter.close()
  230. else:
  231. writer.simpletag("TTGlyph", name=glyphName)
  232. writer.comment("contains no outline data")
  233. if not splitGlyphs:
  234. writer.newline()
  235. writer.newline()
  236. def fromXML(self, name, attrs, content, ttFont):
  237. if name != "TTGlyph":
  238. return
  239. if not hasattr(self, "glyphs"):
  240. self.glyphs = {}
  241. if not hasattr(self, "glyphOrder"):
  242. self.glyphOrder = ttFont.getGlyphOrder()
  243. glyphName = attrs["name"]
  244. log.debug("unpacking glyph '%s'", glyphName)
  245. glyph = Glyph()
  246. for attr in ["xMin", "yMin", "xMax", "yMax"]:
  247. setattr(glyph, attr, safeEval(attrs.get(attr, "0")))
  248. self.glyphs[glyphName] = glyph
  249. for element in content:
  250. if not isinstance(element, tuple):
  251. continue
  252. name, attrs, content = element
  253. glyph.fromXML(name, attrs, content, ttFont)
  254. if not ttFont.recalcBBoxes:
  255. glyph.compact(self, 0)
  256. def setGlyphOrder(self, glyphOrder):
  257. """Sets the glyph order
  258. Args:
  259. glyphOrder ([str]): List of glyph names in order.
  260. """
  261. self.glyphOrder = glyphOrder
  262. self._reverseGlyphOrder = {}
  263. def getGlyphName(self, glyphID):
  264. """Returns the name for the glyph with the given ID.
  265. Raises a ``KeyError`` if the glyph name is not found in the font.
  266. """
  267. return self.glyphOrder[glyphID]
  268. def _buildReverseGlyphOrderDict(self):
  269. self._reverseGlyphOrder = d = {}
  270. for glyphID, glyphName in enumerate(self.glyphOrder):
  271. d[glyphName] = glyphID
  272. def getGlyphID(self, glyphName):
  273. """Returns the ID of the glyph with the given name.
  274. Raises a ``ValueError`` if the glyph is not found in the font.
  275. """
  276. glyphOrder = self.glyphOrder
  277. id = getattr(self, "_reverseGlyphOrder", {}).get(glyphName)
  278. if id is None or id >= len(glyphOrder) or glyphOrder[id] != glyphName:
  279. self._buildReverseGlyphOrderDict()
  280. id = self._reverseGlyphOrder.get(glyphName)
  281. if id is None:
  282. raise ValueError(glyphName)
  283. return id
  284. def removeHinting(self):
  285. """Removes TrueType hints from all glyphs in the glyphset.
  286. See :py:meth:`Glyph.removeHinting`.
  287. """
  288. for glyph in self.glyphs.values():
  289. glyph.removeHinting()
  290. def keys(self):
  291. return self.glyphs.keys()
  292. def has_key(self, glyphName):
  293. return glyphName in self.glyphs
  294. __contains__ = has_key
  295. def get(self, glyphName, default=None):
  296. glyph = self.glyphs.get(glyphName, default)
  297. if glyph is not None:
  298. glyph.expand(self)
  299. return glyph
  300. def __getitem__(self, glyphName):
  301. glyph = self.glyphs[glyphName]
  302. glyph.expand(self)
  303. return glyph
  304. def __setitem__(self, glyphName, glyph):
  305. self.glyphs[glyphName] = glyph
  306. if glyphName not in self.glyphOrder:
  307. self.glyphOrder.append(glyphName)
  308. def __delitem__(self, glyphName):
  309. del self.glyphs[glyphName]
  310. self.glyphOrder.remove(glyphName)
  311. def __len__(self):
  312. assert len(self.glyphOrder) == len(self.glyphs)
  313. return len(self.glyphs)
  314. def _getPhantomPoints(self, glyphName, hMetrics, vMetrics=None):
  315. """Compute the four "phantom points" for the given glyph from its bounding box
  316. and the horizontal and vertical advance widths and sidebearings stored in the
  317. ttFont's "hmtx" and "vmtx" tables.
  318. 'hMetrics' should be ttFont['hmtx'].metrics.
  319. 'vMetrics' should be ttFont['vmtx'].metrics if there is "vmtx" or None otherwise.
  320. If there is no vMetrics passed in, vertical phantom points are set to the zero coordinate.
  321. https://docs.microsoft.com/en-us/typography/opentype/spec/tt_instructing_glyphs#phantoms
  322. """
  323. glyph = self[glyphName]
  324. if not hasattr(glyph, "xMin"):
  325. glyph.recalcBounds(self)
  326. horizontalAdvanceWidth, leftSideBearing = hMetrics[glyphName]
  327. leftSideX = glyph.xMin - leftSideBearing
  328. rightSideX = leftSideX + horizontalAdvanceWidth
  329. if vMetrics:
  330. verticalAdvanceWidth, topSideBearing = vMetrics[glyphName]
  331. topSideY = topSideBearing + glyph.yMax
  332. bottomSideY = topSideY - verticalAdvanceWidth
  333. else:
  334. bottomSideY = topSideY = 0
  335. return [
  336. (leftSideX, 0),
  337. (rightSideX, 0),
  338. (0, topSideY),
  339. (0, bottomSideY),
  340. ]
  341. def _getCoordinatesAndControls(
  342. self, glyphName, hMetrics, vMetrics=None, *, round=otRound
  343. ):
  344. """Return glyph coordinates and controls as expected by "gvar" table.
  345. The coordinates includes four "phantom points" for the glyph metrics,
  346. as mandated by the "gvar" spec.
  347. The glyph controls is a namedtuple with the following attributes:
  348. - numberOfContours: -1 for composite glyphs.
  349. - endPts: list of indices of end points for each contour in simple
  350. glyphs, or component indices in composite glyphs (used for IUP
  351. optimization).
  352. - flags: array of contour point flags for simple glyphs (None for
  353. composite glyphs).
  354. - components: list of base glyph names (str) for each component in
  355. composite glyphs (None for simple glyphs).
  356. The "hMetrics" and vMetrics are used to compute the "phantom points" (see
  357. the "_getPhantomPoints" method).
  358. Return None if the requested glyphName is not present.
  359. """
  360. glyph = self.get(glyphName)
  361. if glyph is None:
  362. return None
  363. if glyph.isComposite():
  364. coords = GlyphCoordinates(
  365. [(getattr(c, "x", 0), getattr(c, "y", 0)) for c in glyph.components]
  366. )
  367. controls = _GlyphControls(
  368. numberOfContours=glyph.numberOfContours,
  369. endPts=list(range(len(glyph.components))),
  370. flags=None,
  371. components=[
  372. (c.glyphName, getattr(c, "transform", None))
  373. for c in glyph.components
  374. ],
  375. )
  376. elif glyph.isVarComposite():
  377. coords = []
  378. controls = []
  379. for component in glyph.components:
  380. (
  381. componentCoords,
  382. componentControls,
  383. ) = component.getCoordinatesAndControls()
  384. coords.extend(componentCoords)
  385. controls.extend(componentControls)
  386. coords = GlyphCoordinates(coords)
  387. controls = _GlyphControls(
  388. numberOfContours=glyph.numberOfContours,
  389. endPts=list(range(len(coords))),
  390. flags=None,
  391. components=[
  392. (c.glyphName, getattr(c, "flags", None)) for c in glyph.components
  393. ],
  394. )
  395. else:
  396. coords, endPts, flags = glyph.getCoordinates(self)
  397. coords = coords.copy()
  398. controls = _GlyphControls(
  399. numberOfContours=glyph.numberOfContours,
  400. endPts=endPts,
  401. flags=flags,
  402. components=None,
  403. )
  404. # Add phantom points for (left, right, top, bottom) positions.
  405. phantomPoints = self._getPhantomPoints(glyphName, hMetrics, vMetrics)
  406. coords.extend(phantomPoints)
  407. coords.toInt(round=round)
  408. return coords, controls
  409. def _setCoordinates(self, glyphName, coord, hMetrics, vMetrics=None):
  410. """Set coordinates and metrics for the given glyph.
  411. "coord" is an array of GlyphCoordinates which must include the "phantom
  412. points" as the last four coordinates.
  413. Both the horizontal/vertical advances and left/top sidebearings in "hmtx"
  414. and "vmtx" tables (if any) are updated from four phantom points and
  415. the glyph's bounding boxes.
  416. The "hMetrics" and vMetrics are used to propagate "phantom points"
  417. into "hmtx" and "vmtx" tables if desired. (see the "_getPhantomPoints"
  418. method).
  419. """
  420. glyph = self[glyphName]
  421. # Handle phantom points for (left, right, top, bottom) positions.
  422. assert len(coord) >= 4
  423. leftSideX = coord[-4][0]
  424. rightSideX = coord[-3][0]
  425. topSideY = coord[-2][1]
  426. bottomSideY = coord[-1][1]
  427. coord = coord[:-4]
  428. if glyph.isComposite():
  429. assert len(coord) == len(glyph.components)
  430. for p, comp in zip(coord, glyph.components):
  431. if hasattr(comp, "x"):
  432. comp.x, comp.y = p
  433. elif glyph.isVarComposite():
  434. for comp in glyph.components:
  435. coord = comp.setCoordinates(coord)
  436. assert not coord
  437. elif glyph.numberOfContours == 0:
  438. assert len(coord) == 0
  439. else:
  440. assert len(coord) == len(glyph.coordinates)
  441. glyph.coordinates = GlyphCoordinates(coord)
  442. glyph.recalcBounds(self, boundsDone=set())
  443. horizontalAdvanceWidth = otRound(rightSideX - leftSideX)
  444. if horizontalAdvanceWidth < 0:
  445. # unlikely, but it can happen, see:
  446. # https://github.com/fonttools/fonttools/pull/1198
  447. horizontalAdvanceWidth = 0
  448. leftSideBearing = otRound(glyph.xMin - leftSideX)
  449. hMetrics[glyphName] = horizontalAdvanceWidth, leftSideBearing
  450. if vMetrics is not None:
  451. verticalAdvanceWidth = otRound(topSideY - bottomSideY)
  452. if verticalAdvanceWidth < 0: # unlikely but do the same as horizontal
  453. verticalAdvanceWidth = 0
  454. topSideBearing = otRound(topSideY - glyph.yMax)
  455. vMetrics[glyphName] = verticalAdvanceWidth, topSideBearing
  456. # Deprecated
  457. def _synthesizeVMetrics(self, glyphName, ttFont, defaultVerticalOrigin):
  458. """This method is wrong and deprecated.
  459. For rationale see:
  460. https://github.com/fonttools/fonttools/pull/2266/files#r613569473
  461. """
  462. vMetrics = getattr(ttFont.get("vmtx"), "metrics", None)
  463. if vMetrics is None:
  464. verticalAdvanceWidth = ttFont["head"].unitsPerEm
  465. topSideY = getattr(ttFont.get("hhea"), "ascent", None)
  466. if topSideY is None:
  467. if defaultVerticalOrigin is not None:
  468. topSideY = defaultVerticalOrigin
  469. else:
  470. topSideY = verticalAdvanceWidth
  471. glyph = self[glyphName]
  472. glyph.recalcBounds(self)
  473. topSideBearing = otRound(topSideY - glyph.yMax)
  474. vMetrics = {glyphName: (verticalAdvanceWidth, topSideBearing)}
  475. return vMetrics
  476. @deprecateFunction("use '_getPhantomPoints' instead", category=DeprecationWarning)
  477. def getPhantomPoints(self, glyphName, ttFont, defaultVerticalOrigin=None):
  478. """Old public name for self._getPhantomPoints().
  479. See: https://github.com/fonttools/fonttools/pull/2266"""
  480. hMetrics = ttFont["hmtx"].metrics
  481. vMetrics = self._synthesizeVMetrics(glyphName, ttFont, defaultVerticalOrigin)
  482. return self._getPhantomPoints(glyphName, hMetrics, vMetrics)
  483. @deprecateFunction(
  484. "use '_getCoordinatesAndControls' instead", category=DeprecationWarning
  485. )
  486. def getCoordinatesAndControls(self, glyphName, ttFont, defaultVerticalOrigin=None):
  487. """Old public name for self._getCoordinatesAndControls().
  488. See: https://github.com/fonttools/fonttools/pull/2266"""
  489. hMetrics = ttFont["hmtx"].metrics
  490. vMetrics = self._synthesizeVMetrics(glyphName, ttFont, defaultVerticalOrigin)
  491. return self._getCoordinatesAndControls(glyphName, hMetrics, vMetrics)
  492. @deprecateFunction("use '_setCoordinates' instead", category=DeprecationWarning)
  493. def setCoordinates(self, glyphName, ttFont):
  494. """Old public name for self._setCoordinates().
  495. See: https://github.com/fonttools/fonttools/pull/2266"""
  496. hMetrics = ttFont["hmtx"].metrics
  497. vMetrics = getattr(ttFont.get("vmtx"), "metrics", None)
  498. self._setCoordinates(glyphName, hMetrics, vMetrics)
  499. _GlyphControls = namedtuple(
  500. "_GlyphControls", "numberOfContours endPts flags components"
  501. )
  502. glyphHeaderFormat = """
  503. > # big endian
  504. numberOfContours: h
  505. xMin: h
  506. yMin: h
  507. xMax: h
  508. yMax: h
  509. """
  510. # flags
  511. flagOnCurve = 0x01
  512. flagXShort = 0x02
  513. flagYShort = 0x04
  514. flagRepeat = 0x08
  515. flagXsame = 0x10
  516. flagYsame = 0x20
  517. flagOverlapSimple = 0x40
  518. flagCubic = 0x80
  519. # These flags are kept for XML output after decompiling the coordinates
  520. keepFlags = flagOnCurve + flagOverlapSimple + flagCubic
  521. _flagSignBytes = {
  522. 0: 2,
  523. flagXsame: 0,
  524. flagXShort | flagXsame: +1,
  525. flagXShort: -1,
  526. flagYsame: 0,
  527. flagYShort | flagYsame: +1,
  528. flagYShort: -1,
  529. }
  530. def flagBest(x, y, onCurve):
  531. """For a given x,y delta pair, returns the flag that packs this pair
  532. most efficiently, as well as the number of byte cost of such flag."""
  533. flag = flagOnCurve if onCurve else 0
  534. cost = 0
  535. # do x
  536. if x == 0:
  537. flag = flag | flagXsame
  538. elif -255 <= x <= 255:
  539. flag = flag | flagXShort
  540. if x > 0:
  541. flag = flag | flagXsame
  542. cost += 1
  543. else:
  544. cost += 2
  545. # do y
  546. if y == 0:
  547. flag = flag | flagYsame
  548. elif -255 <= y <= 255:
  549. flag = flag | flagYShort
  550. if y > 0:
  551. flag = flag | flagYsame
  552. cost += 1
  553. else:
  554. cost += 2
  555. return flag, cost
  556. def flagFits(newFlag, oldFlag, mask):
  557. newBytes = _flagSignBytes[newFlag & mask]
  558. oldBytes = _flagSignBytes[oldFlag & mask]
  559. return newBytes == oldBytes or abs(newBytes) > abs(oldBytes)
  560. def flagSupports(newFlag, oldFlag):
  561. return (
  562. (oldFlag & flagOnCurve) == (newFlag & flagOnCurve)
  563. and flagFits(newFlag, oldFlag, flagXsame | flagXShort)
  564. and flagFits(newFlag, oldFlag, flagYsame | flagYShort)
  565. )
  566. def flagEncodeCoord(flag, mask, coord, coordBytes):
  567. byteCount = _flagSignBytes[flag & mask]
  568. if byteCount == 1:
  569. coordBytes.append(coord)
  570. elif byteCount == -1:
  571. coordBytes.append(-coord)
  572. elif byteCount == 2:
  573. coordBytes.extend(struct.pack(">h", coord))
  574. def flagEncodeCoords(flag, x, y, xBytes, yBytes):
  575. flagEncodeCoord(flag, flagXsame | flagXShort, x, xBytes)
  576. flagEncodeCoord(flag, flagYsame | flagYShort, y, yBytes)
  577. ARG_1_AND_2_ARE_WORDS = 0x0001 # if set args are words otherwise they are bytes
  578. ARGS_ARE_XY_VALUES = 0x0002 # if set args are xy values, otherwise they are points
  579. ROUND_XY_TO_GRID = 0x0004 # for the xy values if above is true
  580. WE_HAVE_A_SCALE = 0x0008 # Sx = Sy, otherwise scale == 1.0
  581. NON_OVERLAPPING = 0x0010 # set to same value for all components (obsolete!)
  582. MORE_COMPONENTS = 0x0020 # indicates at least one more glyph after this one
  583. WE_HAVE_AN_X_AND_Y_SCALE = 0x0040 # Sx, Sy
  584. WE_HAVE_A_TWO_BY_TWO = 0x0080 # t00, t01, t10, t11
  585. WE_HAVE_INSTRUCTIONS = 0x0100 # instructions follow
  586. USE_MY_METRICS = 0x0200 # apply these metrics to parent glyph
  587. OVERLAP_COMPOUND = 0x0400 # used by Apple in GX fonts
  588. SCALED_COMPONENT_OFFSET = 0x0800 # composite designed to have the component offset scaled (designed for Apple)
  589. UNSCALED_COMPONENT_OFFSET = 0x1000 # composite designed not to have the component offset scaled (designed for MS)
  590. CompositeMaxpValues = namedtuple(
  591. "CompositeMaxpValues", ["nPoints", "nContours", "maxComponentDepth"]
  592. )
  593. class Glyph(object):
  594. """This class represents an individual TrueType glyph.
  595. TrueType glyph objects come in two flavours: simple and composite. Simple
  596. glyph objects contain contours, represented via the ``.coordinates``,
  597. ``.flags``, ``.numberOfContours``, and ``.endPtsOfContours`` attributes;
  598. composite glyphs contain components, available through the ``.components``
  599. attributes.
  600. Because the ``.coordinates`` attribute (and other simple glyph attributes mentioned
  601. above) is only set on simple glyphs and the ``.components`` attribute is only
  602. set on composite glyphs, it is necessary to use the :py:meth:`isComposite`
  603. method to test whether a glyph is simple or composite before attempting to
  604. access its data.
  605. For a composite glyph, the components can also be accessed via array-like access::
  606. >> assert(font["glyf"]["Aacute"].isComposite())
  607. >> font["glyf"]["Aacute"][0]
  608. <fontTools.ttLib.tables._g_l_y_f.GlyphComponent at 0x1027b2ee0>
  609. """
  610. def __init__(self, data=b""):
  611. if not data:
  612. # empty char
  613. self.numberOfContours = 0
  614. return
  615. self.data = data
  616. def compact(self, glyfTable, recalcBBoxes=True):
  617. data = self.compile(glyfTable, recalcBBoxes)
  618. self.__dict__.clear()
  619. self.data = data
  620. def expand(self, glyfTable):
  621. if not hasattr(self, "data"):
  622. # already unpacked
  623. return
  624. if not self.data:
  625. # empty char
  626. del self.data
  627. self.numberOfContours = 0
  628. return
  629. dummy, data = sstruct.unpack2(glyphHeaderFormat, self.data, self)
  630. del self.data
  631. # Some fonts (eg. Neirizi.ttf) have a 0 for numberOfContours in
  632. # some glyphs; decompileCoordinates assumes that there's at least
  633. # one, so short-circuit here.
  634. if self.numberOfContours == 0:
  635. return
  636. if self.isComposite():
  637. self.decompileComponents(data, glyfTable)
  638. elif self.isVarComposite():
  639. self.decompileVarComponents(data, glyfTable)
  640. else:
  641. self.decompileCoordinates(data)
  642. def compile(self, glyfTable, recalcBBoxes=True, *, boundsDone=None):
  643. if hasattr(self, "data"):
  644. if recalcBBoxes:
  645. # must unpack glyph in order to recalculate bounding box
  646. self.expand(glyfTable)
  647. else:
  648. return self.data
  649. if self.numberOfContours == 0:
  650. return b""
  651. if recalcBBoxes:
  652. self.recalcBounds(glyfTable, boundsDone=boundsDone)
  653. data = sstruct.pack(glyphHeaderFormat, self)
  654. if self.isComposite():
  655. data = data + self.compileComponents(glyfTable)
  656. elif self.isVarComposite():
  657. data = data + self.compileVarComponents(glyfTable)
  658. else:
  659. data = data + self.compileCoordinates()
  660. return data
  661. def toXML(self, writer, ttFont):
  662. if self.isComposite():
  663. for compo in self.components:
  664. compo.toXML(writer, ttFont)
  665. haveInstructions = hasattr(self, "program")
  666. elif self.isVarComposite():
  667. for compo in self.components:
  668. compo.toXML(writer, ttFont)
  669. haveInstructions = False
  670. else:
  671. last = 0
  672. for i in range(self.numberOfContours):
  673. writer.begintag("contour")
  674. writer.newline()
  675. for j in range(last, self.endPtsOfContours[i] + 1):
  676. attrs = [
  677. ("x", self.coordinates[j][0]),
  678. ("y", self.coordinates[j][1]),
  679. ("on", self.flags[j] & flagOnCurve),
  680. ]
  681. if self.flags[j] & flagOverlapSimple:
  682. # Apple's rasterizer uses flagOverlapSimple in the first contour/first pt to flag glyphs that contain overlapping contours
  683. attrs.append(("overlap", 1))
  684. if self.flags[j] & flagCubic:
  685. attrs.append(("cubic", 1))
  686. writer.simpletag("pt", attrs)
  687. writer.newline()
  688. last = self.endPtsOfContours[i] + 1
  689. writer.endtag("contour")
  690. writer.newline()
  691. haveInstructions = self.numberOfContours > 0
  692. if haveInstructions:
  693. if self.program:
  694. writer.begintag("instructions")
  695. writer.newline()
  696. self.program.toXML(writer, ttFont)
  697. writer.endtag("instructions")
  698. else:
  699. writer.simpletag("instructions")
  700. writer.newline()
  701. def fromXML(self, name, attrs, content, ttFont):
  702. if name == "contour":
  703. if self.numberOfContours < 0:
  704. raise ttLib.TTLibError("can't mix composites and contours in glyph")
  705. self.numberOfContours = self.numberOfContours + 1
  706. coordinates = GlyphCoordinates()
  707. flags = bytearray()
  708. for element in content:
  709. if not isinstance(element, tuple):
  710. continue
  711. name, attrs, content = element
  712. if name != "pt":
  713. continue # ignore anything but "pt"
  714. coordinates.append((safeEval(attrs["x"]), safeEval(attrs["y"])))
  715. flag = bool(safeEval(attrs["on"]))
  716. if "overlap" in attrs and bool(safeEval(attrs["overlap"])):
  717. flag |= flagOverlapSimple
  718. if "cubic" in attrs and bool(safeEval(attrs["cubic"])):
  719. flag |= flagCubic
  720. flags.append(flag)
  721. if not hasattr(self, "coordinates"):
  722. self.coordinates = coordinates
  723. self.flags = flags
  724. self.endPtsOfContours = [len(coordinates) - 1]
  725. else:
  726. self.coordinates.extend(coordinates)
  727. self.flags.extend(flags)
  728. self.endPtsOfContours.append(len(self.coordinates) - 1)
  729. elif name == "component":
  730. if self.numberOfContours > 0:
  731. raise ttLib.TTLibError("can't mix composites and contours in glyph")
  732. self.numberOfContours = -1
  733. if not hasattr(self, "components"):
  734. self.components = []
  735. component = GlyphComponent()
  736. self.components.append(component)
  737. component.fromXML(name, attrs, content, ttFont)
  738. elif name == "varComponent":
  739. if self.numberOfContours > 0:
  740. raise ttLib.TTLibError("can't mix composites and contours in glyph")
  741. self.numberOfContours = -2
  742. if not hasattr(self, "components"):
  743. self.components = []
  744. component = GlyphVarComponent()
  745. self.components.append(component)
  746. component.fromXML(name, attrs, content, ttFont)
  747. elif name == "instructions":
  748. self.program = ttProgram.Program()
  749. for element in content:
  750. if not isinstance(element, tuple):
  751. continue
  752. name, attrs, content = element
  753. self.program.fromXML(name, attrs, content, ttFont)
  754. def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1):
  755. assert self.isComposite() or self.isVarComposite()
  756. nContours = 0
  757. nPoints = 0
  758. initialMaxComponentDepth = maxComponentDepth
  759. for compo in self.components:
  760. baseGlyph = glyfTable[compo.glyphName]
  761. if baseGlyph.numberOfContours == 0:
  762. continue
  763. elif baseGlyph.numberOfContours > 0:
  764. nP, nC = baseGlyph.getMaxpValues()
  765. else:
  766. nP, nC, componentDepth = baseGlyph.getCompositeMaxpValues(
  767. glyfTable, initialMaxComponentDepth + 1
  768. )
  769. maxComponentDepth = max(maxComponentDepth, componentDepth)
  770. nPoints = nPoints + nP
  771. nContours = nContours + nC
  772. return CompositeMaxpValues(nPoints, nContours, maxComponentDepth)
  773. def getMaxpValues(self):
  774. assert self.numberOfContours > 0
  775. return len(self.coordinates), len(self.endPtsOfContours)
  776. def decompileComponents(self, data, glyfTable):
  777. self.components = []
  778. more = 1
  779. haveInstructions = 0
  780. while more:
  781. component = GlyphComponent()
  782. more, haveInstr, data = component.decompile(data, glyfTable)
  783. haveInstructions = haveInstructions | haveInstr
  784. self.components.append(component)
  785. if haveInstructions:
  786. (numInstructions,) = struct.unpack(">h", data[:2])
  787. data = data[2:]
  788. self.program = ttProgram.Program()
  789. self.program.fromBytecode(data[:numInstructions])
  790. data = data[numInstructions:]
  791. if len(data) >= 4:
  792. log.warning(
  793. "too much glyph data at the end of composite glyph: %d excess bytes",
  794. len(data),
  795. )
  796. def decompileVarComponents(self, data, glyfTable):
  797. self.components = []
  798. while len(data) >= GlyphVarComponent.MIN_SIZE:
  799. component = GlyphVarComponent()
  800. data = component.decompile(data, glyfTable)
  801. self.components.append(component)
  802. def decompileCoordinates(self, data):
  803. endPtsOfContours = array.array("H")
  804. endPtsOfContours.frombytes(data[: 2 * self.numberOfContours])
  805. if sys.byteorder != "big":
  806. endPtsOfContours.byteswap()
  807. self.endPtsOfContours = endPtsOfContours.tolist()
  808. pos = 2 * self.numberOfContours
  809. (instructionLength,) = struct.unpack(">h", data[pos : pos + 2])
  810. self.program = ttProgram.Program()
  811. self.program.fromBytecode(data[pos + 2 : pos + 2 + instructionLength])
  812. pos += 2 + instructionLength
  813. nCoordinates = self.endPtsOfContours[-1] + 1
  814. flags, xCoordinates, yCoordinates = self.decompileCoordinatesRaw(
  815. nCoordinates, data, pos
  816. )
  817. # fill in repetitions and apply signs
  818. self.coordinates = coordinates = GlyphCoordinates.zeros(nCoordinates)
  819. xIndex = 0
  820. yIndex = 0
  821. for i in range(nCoordinates):
  822. flag = flags[i]
  823. # x coordinate
  824. if flag & flagXShort:
  825. if flag & flagXsame:
  826. x = xCoordinates[xIndex]
  827. else:
  828. x = -xCoordinates[xIndex]
  829. xIndex = xIndex + 1
  830. elif flag & flagXsame:
  831. x = 0
  832. else:
  833. x = xCoordinates[xIndex]
  834. xIndex = xIndex + 1
  835. # y coordinate
  836. if flag & flagYShort:
  837. if flag & flagYsame:
  838. y = yCoordinates[yIndex]
  839. else:
  840. y = -yCoordinates[yIndex]
  841. yIndex = yIndex + 1
  842. elif flag & flagYsame:
  843. y = 0
  844. else:
  845. y = yCoordinates[yIndex]
  846. yIndex = yIndex + 1
  847. coordinates[i] = (x, y)
  848. assert xIndex == len(xCoordinates)
  849. assert yIndex == len(yCoordinates)
  850. coordinates.relativeToAbsolute()
  851. # discard all flags except "keepFlags"
  852. for i in range(len(flags)):
  853. flags[i] &= keepFlags
  854. self.flags = flags
  855. def decompileCoordinatesRaw(self, nCoordinates, data, pos=0):
  856. # unpack flags and prepare unpacking of coordinates
  857. flags = bytearray(nCoordinates)
  858. # Warning: deep Python trickery going on. We use the struct module to unpack
  859. # the coordinates. We build a format string based on the flags, so we can
  860. # unpack the coordinates in one struct.unpack() call.
  861. xFormat = ">" # big endian
  862. yFormat = ">" # big endian
  863. j = 0
  864. while True:
  865. flag = data[pos]
  866. pos += 1
  867. repeat = 1
  868. if flag & flagRepeat:
  869. repeat = data[pos] + 1
  870. pos += 1
  871. for k in range(repeat):
  872. if flag & flagXShort:
  873. xFormat = xFormat + "B"
  874. elif not (flag & flagXsame):
  875. xFormat = xFormat + "h"
  876. if flag & flagYShort:
  877. yFormat = yFormat + "B"
  878. elif not (flag & flagYsame):
  879. yFormat = yFormat + "h"
  880. flags[j] = flag
  881. j = j + 1
  882. if j >= nCoordinates:
  883. break
  884. assert j == nCoordinates, "bad glyph flags"
  885. # unpack raw coordinates, krrrrrr-tching!
  886. xDataLen = struct.calcsize(xFormat)
  887. yDataLen = struct.calcsize(yFormat)
  888. if len(data) - pos - (xDataLen + yDataLen) >= 4:
  889. log.warning(
  890. "too much glyph data: %d excess bytes",
  891. len(data) - pos - (xDataLen + yDataLen),
  892. )
  893. xCoordinates = struct.unpack(xFormat, data[pos : pos + xDataLen])
  894. yCoordinates = struct.unpack(
  895. yFormat, data[pos + xDataLen : pos + xDataLen + yDataLen]
  896. )
  897. return flags, xCoordinates, yCoordinates
  898. def compileComponents(self, glyfTable):
  899. data = b""
  900. lastcomponent = len(self.components) - 1
  901. more = 1
  902. haveInstructions = 0
  903. for i in range(len(self.components)):
  904. if i == lastcomponent:
  905. haveInstructions = hasattr(self, "program")
  906. more = 0
  907. compo = self.components[i]
  908. data = data + compo.compile(more, haveInstructions, glyfTable)
  909. if haveInstructions:
  910. instructions = self.program.getBytecode()
  911. data = data + struct.pack(">h", len(instructions)) + instructions
  912. return data
  913. def compileVarComponents(self, glyfTable):
  914. return b"".join(c.compile(glyfTable) for c in self.components)
  915. def compileCoordinates(self):
  916. assert len(self.coordinates) == len(self.flags)
  917. data = []
  918. endPtsOfContours = array.array("H", self.endPtsOfContours)
  919. if sys.byteorder != "big":
  920. endPtsOfContours.byteswap()
  921. data.append(endPtsOfContours.tobytes())
  922. instructions = self.program.getBytecode()
  923. data.append(struct.pack(">h", len(instructions)))
  924. data.append(instructions)
  925. deltas = self.coordinates.copy()
  926. deltas.toInt()
  927. deltas.absoluteToRelative()
  928. # TODO(behdad): Add a configuration option for this?
  929. deltas = self.compileDeltasGreedy(self.flags, deltas)
  930. # deltas = self.compileDeltasOptimal(self.flags, deltas)
  931. data.extend(deltas)
  932. return b"".join(data)
  933. def compileDeltasGreedy(self, flags, deltas):
  934. # Implements greedy algorithm for packing coordinate deltas:
  935. # uses shortest representation one coordinate at a time.
  936. compressedFlags = bytearray()
  937. compressedXs = bytearray()
  938. compressedYs = bytearray()
  939. lastflag = None
  940. repeat = 0
  941. for flag, (x, y) in zip(flags, deltas):
  942. # Oh, the horrors of TrueType
  943. # do x
  944. if x == 0:
  945. flag = flag | flagXsame
  946. elif -255 <= x <= 255:
  947. flag = flag | flagXShort
  948. if x > 0:
  949. flag = flag | flagXsame
  950. else:
  951. x = -x
  952. compressedXs.append(x)
  953. else:
  954. compressedXs.extend(struct.pack(">h", x))
  955. # do y
  956. if y == 0:
  957. flag = flag | flagYsame
  958. elif -255 <= y <= 255:
  959. flag = flag | flagYShort
  960. if y > 0:
  961. flag = flag | flagYsame
  962. else:
  963. y = -y
  964. compressedYs.append(y)
  965. else:
  966. compressedYs.extend(struct.pack(">h", y))
  967. # handle repeating flags
  968. if flag == lastflag and repeat != 255:
  969. repeat = repeat + 1
  970. if repeat == 1:
  971. compressedFlags.append(flag)
  972. else:
  973. compressedFlags[-2] = flag | flagRepeat
  974. compressedFlags[-1] = repeat
  975. else:
  976. repeat = 0
  977. compressedFlags.append(flag)
  978. lastflag = flag
  979. return (compressedFlags, compressedXs, compressedYs)
  980. def compileDeltasOptimal(self, flags, deltas):
  981. # Implements optimal, dynaic-programming, algorithm for packing coordinate
  982. # deltas. The savings are negligible :(.
  983. candidates = []
  984. bestTuple = None
  985. bestCost = 0
  986. repeat = 0
  987. for flag, (x, y) in zip(flags, deltas):
  988. # Oh, the horrors of TrueType
  989. flag, coordBytes = flagBest(x, y, flag)
  990. bestCost += 1 + coordBytes
  991. newCandidates = [
  992. (bestCost, bestTuple, flag, coordBytes),
  993. (bestCost + 1, bestTuple, (flag | flagRepeat), coordBytes),
  994. ]
  995. for lastCost, lastTuple, lastFlag, coordBytes in candidates:
  996. if (
  997. lastCost + coordBytes <= bestCost + 1
  998. and (lastFlag & flagRepeat)
  999. and (lastFlag < 0xFF00)
  1000. and flagSupports(lastFlag, flag)
  1001. ):
  1002. if (lastFlag & 0xFF) == (
  1003. flag | flagRepeat
  1004. ) and lastCost == bestCost + 1:
  1005. continue
  1006. newCandidates.append(
  1007. (lastCost + coordBytes, lastTuple, lastFlag + 256, coordBytes)
  1008. )
  1009. candidates = newCandidates
  1010. bestTuple = min(candidates, key=lambda t: t[0])
  1011. bestCost = bestTuple[0]
  1012. flags = []
  1013. while bestTuple:
  1014. cost, bestTuple, flag, coordBytes = bestTuple
  1015. flags.append(flag)
  1016. flags.reverse()
  1017. compressedFlags = bytearray()
  1018. compressedXs = bytearray()
  1019. compressedYs = bytearray()
  1020. coords = iter(deltas)
  1021. ff = []
  1022. for flag in flags:
  1023. repeatCount, flag = flag >> 8, flag & 0xFF
  1024. compressedFlags.append(flag)
  1025. if flag & flagRepeat:
  1026. assert repeatCount > 0
  1027. compressedFlags.append(repeatCount)
  1028. else:
  1029. assert repeatCount == 0
  1030. for i in range(1 + repeatCount):
  1031. x, y = next(coords)
  1032. flagEncodeCoords(flag, x, y, compressedXs, compressedYs)
  1033. ff.append(flag)
  1034. try:
  1035. next(coords)
  1036. raise Exception("internal error")
  1037. except StopIteration:
  1038. pass
  1039. return (compressedFlags, compressedXs, compressedYs)
  1040. def recalcBounds(self, glyfTable, *, boundsDone=None):
  1041. """Recalculates the bounds of the glyph.
  1042. Each glyph object stores its bounding box in the
  1043. ``xMin``/``yMin``/``xMax``/``yMax`` attributes. These bounds must be
  1044. recomputed when the ``coordinates`` change. The ``table__g_l_y_f`` bounds
  1045. must be provided to resolve component bounds.
  1046. """
  1047. if self.isComposite() and self.tryRecalcBoundsComposite(
  1048. glyfTable, boundsDone=boundsDone
  1049. ):
  1050. return
  1051. try:
  1052. coords, endPts, flags = self.getCoordinates(glyfTable)
  1053. self.xMin, self.yMin, self.xMax, self.yMax = coords.calcIntBounds()
  1054. except NotImplementedError:
  1055. pass
  1056. def tryRecalcBoundsComposite(self, glyfTable, *, boundsDone=None):
  1057. """Try recalculating the bounds of a composite glyph that has
  1058. certain constrained properties. Namely, none of the components
  1059. have a transform other than an integer translate, and none
  1060. uses the anchor points.
  1061. Each glyph object stores its bounding box in the
  1062. ``xMin``/``yMin``/``xMax``/``yMax`` attributes. These bounds must be
  1063. recomputed when the ``coordinates`` change. The ``table__g_l_y_f`` bounds
  1064. must be provided to resolve component bounds.
  1065. Return True if bounds were calculated, False otherwise.
  1066. """
  1067. for compo in self.components:
  1068. if hasattr(compo, "firstPt") or hasattr(compo, "transform"):
  1069. return False
  1070. if not float(compo.x).is_integer() or not float(compo.y).is_integer():
  1071. return False
  1072. # All components are untransformed and have an integer x/y translate
  1073. bounds = None
  1074. for compo in self.components:
  1075. glyphName = compo.glyphName
  1076. g = glyfTable[glyphName]
  1077. if boundsDone is None or glyphName not in boundsDone:
  1078. g.recalcBounds(glyfTable, boundsDone=boundsDone)
  1079. if boundsDone is not None:
  1080. boundsDone.add(glyphName)
  1081. # empty components shouldn't update the bounds of the parent glyph
  1082. if g.numberOfContours == 0:
  1083. continue
  1084. x, y = compo.x, compo.y
  1085. bounds = updateBounds(bounds, (g.xMin + x, g.yMin + y))
  1086. bounds = updateBounds(bounds, (g.xMax + x, g.yMax + y))
  1087. if bounds is None:
  1088. bounds = (0, 0, 0, 0)
  1089. self.xMin, self.yMin, self.xMax, self.yMax = bounds
  1090. return True
  1091. def isComposite(self):
  1092. """Test whether a glyph has components"""
  1093. if hasattr(self, "data"):
  1094. return struct.unpack(">h", self.data[:2])[0] == -1 if self.data else False
  1095. else:
  1096. return self.numberOfContours == -1
  1097. def isVarComposite(self):
  1098. """Test whether a glyph has variable components"""
  1099. if hasattr(self, "data"):
  1100. return struct.unpack(">h", self.data[:2])[0] == -2 if self.data else False
  1101. else:
  1102. return self.numberOfContours == -2
  1103. def getCoordinates(self, glyfTable):
  1104. """Return the coordinates, end points and flags
  1105. This method returns three values: A :py:class:`GlyphCoordinates` object,
  1106. a list of the indexes of the final points of each contour (allowing you
  1107. to split up the coordinates list into contours) and a list of flags.
  1108. On simple glyphs, this method returns information from the glyph's own
  1109. contours; on composite glyphs, it "flattens" all components recursively
  1110. to return a list of coordinates representing all the components involved
  1111. in the glyph.
  1112. To interpret the flags for each point, see the "Simple Glyph Flags"
  1113. section of the `glyf table specification <https://docs.microsoft.com/en-us/typography/opentype/spec/glyf#simple-glyph-description>`.
  1114. """
  1115. if self.numberOfContours > 0:
  1116. return self.coordinates, self.endPtsOfContours, self.flags
  1117. elif self.isComposite():
  1118. # it's a composite
  1119. allCoords = GlyphCoordinates()
  1120. allFlags = bytearray()
  1121. allEndPts = []
  1122. for compo in self.components:
  1123. g = glyfTable[compo.glyphName]
  1124. try:
  1125. coordinates, endPts, flags = g.getCoordinates(glyfTable)
  1126. except RecursionError:
  1127. raise ttLib.TTLibError(
  1128. "glyph '%s' contains a recursive component reference"
  1129. % compo.glyphName
  1130. )
  1131. coordinates = GlyphCoordinates(coordinates)
  1132. if hasattr(compo, "firstPt"):
  1133. # component uses two reference points: we apply the transform _before_
  1134. # computing the offset between the points
  1135. if hasattr(compo, "transform"):
  1136. coordinates.transform(compo.transform)
  1137. x1, y1 = allCoords[compo.firstPt]
  1138. x2, y2 = coordinates[compo.secondPt]
  1139. move = x1 - x2, y1 - y2
  1140. coordinates.translate(move)
  1141. else:
  1142. # component uses XY offsets
  1143. move = compo.x, compo.y
  1144. if not hasattr(compo, "transform"):
  1145. coordinates.translate(move)
  1146. else:
  1147. apple_way = compo.flags & SCALED_COMPONENT_OFFSET
  1148. ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
  1149. assert not (apple_way and ms_way)
  1150. if not (apple_way or ms_way):
  1151. scale_component_offset = (
  1152. SCALE_COMPONENT_OFFSET_DEFAULT # see top of this file
  1153. )
  1154. else:
  1155. scale_component_offset = apple_way
  1156. if scale_component_offset:
  1157. # the Apple way: first move, then scale (ie. scale the component offset)
  1158. coordinates.translate(move)
  1159. coordinates.transform(compo.transform)
  1160. else:
  1161. # the MS way: first scale, then move
  1162. coordinates.transform(compo.transform)
  1163. coordinates.translate(move)
  1164. offset = len(allCoords)
  1165. allEndPts.extend(e + offset for e in endPts)
  1166. allCoords.extend(coordinates)
  1167. allFlags.extend(flags)
  1168. return allCoords, allEndPts, allFlags
  1169. elif self.isVarComposite():
  1170. raise NotImplementedError("use TTGlyphSet to draw VarComposite glyphs")
  1171. else:
  1172. return GlyphCoordinates(), [], bytearray()
  1173. def getComponentNames(self, glyfTable):
  1174. """Returns a list of names of component glyphs used in this glyph
  1175. This method can be used on simple glyphs (in which case it returns an
  1176. empty list) or composite glyphs.
  1177. """
  1178. if hasattr(self, "data") and self.isVarComposite():
  1179. # TODO(VarComposite) Add implementation without expanding glyph
  1180. self.expand(glyfTable)
  1181. if not hasattr(self, "data"):
  1182. if self.isComposite() or self.isVarComposite():
  1183. return [c.glyphName for c in self.components]
  1184. else:
  1185. return []
  1186. # Extract components without expanding glyph
  1187. if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
  1188. return [] # Not composite
  1189. data = self.data
  1190. i = 10
  1191. components = []
  1192. more = 1
  1193. while more:
  1194. flags, glyphID = struct.unpack(">HH", data[i : i + 4])
  1195. i += 4
  1196. flags = int(flags)
  1197. components.append(glyfTable.getGlyphName(int(glyphID)))
  1198. if flags & ARG_1_AND_2_ARE_WORDS:
  1199. i += 4
  1200. else:
  1201. i += 2
  1202. if flags & WE_HAVE_A_SCALE:
  1203. i += 2
  1204. elif flags & WE_HAVE_AN_X_AND_Y_SCALE:
  1205. i += 4
  1206. elif flags & WE_HAVE_A_TWO_BY_TWO:
  1207. i += 8
  1208. more = flags & MORE_COMPONENTS
  1209. return components
  1210. def trim(self, remove_hinting=False):
  1211. """Remove padding and, if requested, hinting, from a glyph.
  1212. This works on both expanded and compacted glyphs, without
  1213. expanding it."""
  1214. if not hasattr(self, "data"):
  1215. if remove_hinting:
  1216. if self.isComposite():
  1217. if hasattr(self, "program"):
  1218. del self.program
  1219. elif self.isVarComposite():
  1220. pass # Doesn't have hinting
  1221. else:
  1222. self.program = ttProgram.Program()
  1223. self.program.fromBytecode([])
  1224. # No padding to trim.
  1225. return
  1226. if not self.data:
  1227. return
  1228. numContours = struct.unpack(">h", self.data[:2])[0]
  1229. data = bytearray(self.data)
  1230. i = 10
  1231. if numContours >= 0:
  1232. i += 2 * numContours # endPtsOfContours
  1233. nCoordinates = ((data[i - 2] << 8) | data[i - 1]) + 1
  1234. instructionLen = (data[i] << 8) | data[i + 1]
  1235. if remove_hinting:
  1236. # Zero instruction length
  1237. data[i] = data[i + 1] = 0
  1238. i += 2
  1239. if instructionLen:
  1240. # Splice it out
  1241. data = data[:i] + data[i + instructionLen :]
  1242. instructionLen = 0
  1243. else:
  1244. i += 2 + instructionLen
  1245. coordBytes = 0
  1246. j = 0
  1247. while True:
  1248. flag = data[i]
  1249. i = i + 1
  1250. repeat = 1
  1251. if flag & flagRepeat:
  1252. repeat = data[i] + 1
  1253. i = i + 1
  1254. xBytes = yBytes = 0
  1255. if flag & flagXShort:
  1256. xBytes = 1
  1257. elif not (flag & flagXsame):
  1258. xBytes = 2
  1259. if flag & flagYShort:
  1260. yBytes = 1
  1261. elif not (flag & flagYsame):
  1262. yBytes = 2
  1263. coordBytes += (xBytes + yBytes) * repeat
  1264. j += repeat
  1265. if j >= nCoordinates:
  1266. break
  1267. assert j == nCoordinates, "bad glyph flags"
  1268. i += coordBytes
  1269. # Remove padding
  1270. data = data[:i]
  1271. elif self.isComposite():
  1272. more = 1
  1273. we_have_instructions = False
  1274. while more:
  1275. flags = (data[i] << 8) | data[i + 1]
  1276. if remove_hinting:
  1277. flags &= ~WE_HAVE_INSTRUCTIONS
  1278. if flags & WE_HAVE_INSTRUCTIONS:
  1279. we_have_instructions = True
  1280. data[i + 0] = flags >> 8
  1281. data[i + 1] = flags & 0xFF
  1282. i += 4
  1283. flags = int(flags)
  1284. if flags & ARG_1_AND_2_ARE_WORDS:
  1285. i += 4
  1286. else:
  1287. i += 2
  1288. if flags & WE_HAVE_A_SCALE:
  1289. i += 2
  1290. elif flags & WE_HAVE_AN_X_AND_Y_SCALE:
  1291. i += 4
  1292. elif flags & WE_HAVE_A_TWO_BY_TWO:
  1293. i += 8
  1294. more = flags & MORE_COMPONENTS
  1295. if we_have_instructions:
  1296. instructionLen = (data[i] << 8) | data[i + 1]
  1297. i += 2 + instructionLen
  1298. # Remove padding
  1299. data = data[:i]
  1300. elif self.isVarComposite():
  1301. i = 0
  1302. MIN_SIZE = GlyphVarComponent.MIN_SIZE
  1303. while len(data[i : i + MIN_SIZE]) >= MIN_SIZE:
  1304. size = GlyphVarComponent.getSize(data[i : i + MIN_SIZE])
  1305. i += size
  1306. data = data[:i]
  1307. self.data = data
  1308. def removeHinting(self):
  1309. """Removes TrueType hinting instructions from the glyph."""
  1310. self.trim(remove_hinting=True)
  1311. def draw(self, pen, glyfTable, offset=0):
  1312. """Draws the glyph using the supplied pen object.
  1313. Arguments:
  1314. pen: An object conforming to the pen protocol.
  1315. glyfTable: A :py:class:`table__g_l_y_f` object, to resolve components.
  1316. offset (int): A horizontal offset. If provided, all coordinates are
  1317. translated by this offset.
  1318. """
  1319. if self.isComposite():
  1320. for component in self.components:
  1321. glyphName, transform = component.getComponentInfo()
  1322. pen.addComponent(glyphName, transform)
  1323. return
  1324. coordinates, endPts, flags = self.getCoordinates(glyfTable)
  1325. if offset:
  1326. coordinates = coordinates.copy()
  1327. coordinates.translate((offset, 0))
  1328. start = 0
  1329. maybeInt = lambda v: int(v) if v == int(v) else v
  1330. for end in endPts:
  1331. end = end + 1
  1332. contour = coordinates[start:end]
  1333. cFlags = [flagOnCurve & f for f in flags[start:end]]
  1334. cuFlags = [flagCubic & f for f in flags[start:end]]
  1335. start = end
  1336. if 1 not in cFlags:
  1337. assert all(cuFlags) or not any(cuFlags)
  1338. cubic = all(cuFlags)
  1339. if cubic:
  1340. count = len(contour)
  1341. assert count % 2 == 0, "Odd number of cubic off-curves undefined"
  1342. l = contour[-1]
  1343. f = contour[0]
  1344. p0 = (maybeInt((l[0] + f[0]) * 0.5), maybeInt((l[1] + f[1]) * 0.5))
  1345. pen.moveTo(p0)
  1346. for i in range(0, count, 2):
  1347. p1 = contour[i]
  1348. p2 = contour[i + 1]
  1349. p4 = contour[i + 2 if i + 2 < count else 0]
  1350. p3 = (
  1351. maybeInt((p2[0] + p4[0]) * 0.5),
  1352. maybeInt((p2[1] + p4[1]) * 0.5),
  1353. )
  1354. pen.curveTo(p1, p2, p3)
  1355. else:
  1356. # There is not a single on-curve point on the curve,
  1357. # use pen.qCurveTo's special case by specifying None
  1358. # as the on-curve point.
  1359. contour.append(None)
  1360. pen.qCurveTo(*contour)
  1361. else:
  1362. # Shuffle the points so that the contour is guaranteed
  1363. # to *end* in an on-curve point, which we'll use for
  1364. # the moveTo.
  1365. firstOnCurve = cFlags.index(1) + 1
  1366. contour = contour[firstOnCurve:] + contour[:firstOnCurve]
  1367. cFlags = cFlags[firstOnCurve:] + cFlags[:firstOnCurve]
  1368. cuFlags = cuFlags[firstOnCurve:] + cuFlags[:firstOnCurve]
  1369. pen.moveTo(contour[-1])
  1370. while contour:
  1371. nextOnCurve = cFlags.index(1) + 1
  1372. if nextOnCurve == 1:
  1373. # Skip a final lineTo(), as it is implied by
  1374. # pen.closePath()
  1375. if len(contour) > 1:
  1376. pen.lineTo(contour[0])
  1377. else:
  1378. cubicFlags = [f for f in cuFlags[: nextOnCurve - 1]]
  1379. assert all(cubicFlags) or not any(cubicFlags)
  1380. cubic = any(cubicFlags)
  1381. if cubic:
  1382. assert all(
  1383. cubicFlags
  1384. ), "Mixed cubic and quadratic segment undefined"
  1385. count = nextOnCurve
  1386. assert (
  1387. count >= 3
  1388. ), "At least two cubic off-curve points required"
  1389. assert (
  1390. count - 1
  1391. ) % 2 == 0, "Odd number of cubic off-curves undefined"
  1392. for i in range(0, count - 3, 2):
  1393. p1 = contour[i]
  1394. p2 = contour[i + 1]
  1395. p4 = contour[i + 2]
  1396. p3 = (
  1397. maybeInt((p2[0] + p4[0]) * 0.5),
  1398. maybeInt((p2[1] + p4[1]) * 0.5),
  1399. )
  1400. lastOnCurve = p3
  1401. pen.curveTo(p1, p2, p3)
  1402. pen.curveTo(*contour[count - 3 : count])
  1403. else:
  1404. pen.qCurveTo(*contour[:nextOnCurve])
  1405. contour = contour[nextOnCurve:]
  1406. cFlags = cFlags[nextOnCurve:]
  1407. cuFlags = cuFlags[nextOnCurve:]
  1408. pen.closePath()
  1409. def drawPoints(self, pen, glyfTable, offset=0):
  1410. """Draw the glyph using the supplied pointPen. As opposed to Glyph.draw(),
  1411. this will not change the point indices.
  1412. """
  1413. if self.isComposite():
  1414. for component in self.components:
  1415. glyphName, transform = component.getComponentInfo()
  1416. pen.addComponent(glyphName, transform)
  1417. return
  1418. coordinates, endPts, flags = self.getCoordinates(glyfTable)
  1419. if offset:
  1420. coordinates = coordinates.copy()
  1421. coordinates.translate((offset, 0))
  1422. start = 0
  1423. for end in endPts:
  1424. end = end + 1
  1425. contour = coordinates[start:end]
  1426. cFlags = flags[start:end]
  1427. start = end
  1428. pen.beginPath()
  1429. # Start with the appropriate segment type based on the final segment
  1430. if cFlags[-1] & flagOnCurve:
  1431. segmentType = "line"
  1432. elif cFlags[-1] & flagCubic:
  1433. segmentType = "curve"
  1434. else:
  1435. segmentType = "qcurve"
  1436. for i, pt in enumerate(contour):
  1437. if cFlags[i] & flagOnCurve:
  1438. pen.addPoint(pt, segmentType=segmentType)
  1439. segmentType = "line"
  1440. else:
  1441. pen.addPoint(pt)
  1442. segmentType = "curve" if cFlags[i] & flagCubic else "qcurve"
  1443. pen.endPath()
  1444. def __eq__(self, other):
  1445. if type(self) != type(other):
  1446. return NotImplemented
  1447. return self.__dict__ == other.__dict__
  1448. def __ne__(self, other):
  1449. result = self.__eq__(other)
  1450. return result if result is NotImplemented else not result
  1451. # Vector.__round__ uses the built-in (Banker's) `round` but we want
  1452. # to use otRound below
  1453. _roundv = partial(Vector.__round__, round=otRound)
  1454. def _is_mid_point(p0: tuple, p1: tuple, p2: tuple) -> bool:
  1455. # True if p1 is in the middle of p0 and p2, either before or after rounding
  1456. p0 = Vector(p0)
  1457. p1 = Vector(p1)
  1458. p2 = Vector(p2)
  1459. return ((p0 + p2) * 0.5).isclose(p1) or _roundv(p0) + _roundv(p2) == _roundv(p1) * 2
  1460. def dropImpliedOnCurvePoints(*interpolatable_glyphs: Glyph) -> Set[int]:
  1461. """Drop impliable on-curve points from the (simple) glyph or glyphs.
  1462. In TrueType glyf outlines, on-curve points can be implied when they are located at
  1463. the midpoint of the line connecting two consecutive off-curve points.
  1464. If more than one glyphs are passed, these are assumed to be interpolatable masters
  1465. of the same glyph impliable, and thus only the on-curve points that are impliable
  1466. for all of them will actually be implied.
  1467. Composite glyphs or empty glyphs are skipped, only simple glyphs with 1 or more
  1468. contours are considered.
  1469. The input glyph(s) is/are modified in-place.
  1470. Args:
  1471. interpolatable_glyphs: The glyph or glyphs to modify in-place.
  1472. Returns:
  1473. The set of point indices that were dropped if any.
  1474. Raises:
  1475. ValueError if simple glyphs are not in fact interpolatable because they have
  1476. different point flags or number of contours.
  1477. Reference:
  1478. https://developer.apple.com/fonts/TrueType-Reference-Manual/RM01/Chap1.html
  1479. """
  1480. staticAttributes = SimpleNamespace(
  1481. numberOfContours=None, flags=None, endPtsOfContours=None
  1482. )
  1483. drop = None
  1484. simple_glyphs = []
  1485. for i, glyph in enumerate(interpolatable_glyphs):
  1486. if glyph.numberOfContours < 1:
  1487. # ignore composite or empty glyphs
  1488. continue
  1489. for attr in staticAttributes.__dict__:
  1490. expected = getattr(staticAttributes, attr)
  1491. found = getattr(glyph, attr)
  1492. if expected is None:
  1493. setattr(staticAttributes, attr, found)
  1494. elif expected != found:
  1495. raise ValueError(
  1496. f"Incompatible {attr} for glyph at master index {i}: "
  1497. f"expected {expected}, found {found}"
  1498. )
  1499. may_drop = set()
  1500. start = 0
  1501. coords = glyph.coordinates
  1502. flags = staticAttributes.flags
  1503. endPtsOfContours = staticAttributes.endPtsOfContours
  1504. for last in endPtsOfContours:
  1505. for i in range(start, last + 1):
  1506. if not (flags[i] & flagOnCurve):
  1507. continue
  1508. prv = i - 1 if i > start else last
  1509. nxt = i + 1 if i < last else start
  1510. if (flags[prv] & flagOnCurve) or flags[prv] != flags[nxt]:
  1511. continue
  1512. # we may drop the ith on-curve if halfway between previous/next off-curves
  1513. if not _is_mid_point(coords[prv], coords[i], coords[nxt]):
  1514. continue
  1515. may_drop.add(i)
  1516. start = last + 1
  1517. # we only want to drop if ALL interpolatable glyphs have the same implied oncurves
  1518. if drop is None:
  1519. drop = may_drop
  1520. else:
  1521. drop.intersection_update(may_drop)
  1522. simple_glyphs.append(glyph)
  1523. if drop:
  1524. # Do the actual dropping
  1525. flags = staticAttributes.flags
  1526. assert flags is not None
  1527. newFlags = array.array(
  1528. "B", (flags[i] for i in range(len(flags)) if i not in drop)
  1529. )
  1530. endPts = staticAttributes.endPtsOfContours
  1531. assert endPts is not None
  1532. newEndPts = []
  1533. i = 0
  1534. delta = 0
  1535. for d in sorted(drop):
  1536. while d > endPts[i]:
  1537. newEndPts.append(endPts[i] - delta)
  1538. i += 1
  1539. delta += 1
  1540. while i < len(endPts):
  1541. newEndPts.append(endPts[i] - delta)
  1542. i += 1
  1543. for glyph in simple_glyphs:
  1544. coords = glyph.coordinates
  1545. glyph.coordinates = GlyphCoordinates(
  1546. coords[i] for i in range(len(coords)) if i not in drop
  1547. )
  1548. glyph.flags = newFlags
  1549. glyph.endPtsOfContours = newEndPts
  1550. return drop if drop is not None else set()
  1551. class GlyphComponent(object):
  1552. """Represents a component within a composite glyph.
  1553. The component is represented internally with four attributes: ``glyphName``,
  1554. ``x``, ``y`` and ``transform``. If there is no "two-by-two" matrix (i.e
  1555. no scaling, reflection, or rotation; only translation), the ``transform``
  1556. attribute is not present.
  1557. """
  1558. # The above documentation is not *completely* true, but is *true enough* because
  1559. # the rare firstPt/lastPt attributes are not totally supported and nobody seems to
  1560. # mind - see below.
  1561. def __init__(self):
  1562. pass
  1563. def getComponentInfo(self):
  1564. """Return information about the component
  1565. This method returns a tuple of two values: the glyph name of the component's
  1566. base glyph, and a transformation matrix. As opposed to accessing the attributes
  1567. directly, ``getComponentInfo`` always returns a six-element tuple of the
  1568. component's transformation matrix, even when the two-by-two ``.transform``
  1569. matrix is not present.
  1570. """
  1571. # XXX Ignoring self.firstPt & self.lastpt for now: I need to implement
  1572. # something equivalent in fontTools.objects.glyph (I'd rather not
  1573. # convert it to an absolute offset, since it is valuable information).
  1574. # This method will now raise "AttributeError: x" on glyphs that use
  1575. # this TT feature.
  1576. if hasattr(self, "transform"):
  1577. [[xx, xy], [yx, yy]] = self.transform
  1578. trans = (xx, xy, yx, yy, self.x, self.y)
  1579. else:
  1580. trans = (1, 0, 0, 1, self.x, self.y)
  1581. return self.glyphName, trans
  1582. def decompile(self, data, glyfTable):
  1583. flags, glyphID = struct.unpack(">HH", data[:4])
  1584. self.flags = int(flags)
  1585. glyphID = int(glyphID)
  1586. self.glyphName = glyfTable.getGlyphName(int(glyphID))
  1587. data = data[4:]
  1588. if self.flags & ARG_1_AND_2_ARE_WORDS:
  1589. if self.flags & ARGS_ARE_XY_VALUES:
  1590. self.x, self.y = struct.unpack(">hh", data[:4])
  1591. else:
  1592. x, y = struct.unpack(">HH", data[:4])
  1593. self.firstPt, self.secondPt = int(x), int(y)
  1594. data = data[4:]
  1595. else:
  1596. if self.flags & ARGS_ARE_XY_VALUES:
  1597. self.x, self.y = struct.unpack(">bb", data[:2])
  1598. else:
  1599. x, y = struct.unpack(">BB", data[:2])
  1600. self.firstPt, self.secondPt = int(x), int(y)
  1601. data = data[2:]
  1602. if self.flags & WE_HAVE_A_SCALE:
  1603. (scale,) = struct.unpack(">h", data[:2])
  1604. self.transform = [
  1605. [fi2fl(scale, 14), 0],
  1606. [0, fi2fl(scale, 14)],
  1607. ] # fixed 2.14
  1608. data = data[2:]
  1609. elif self.flags & WE_HAVE_AN_X_AND_Y_SCALE:
  1610. xscale, yscale = struct.unpack(">hh", data[:4])
  1611. self.transform = [
  1612. [fi2fl(xscale, 14), 0],
  1613. [0, fi2fl(yscale, 14)],
  1614. ] # fixed 2.14
  1615. data = data[4:]
  1616. elif self.flags & WE_HAVE_A_TWO_BY_TWO:
  1617. (xscale, scale01, scale10, yscale) = struct.unpack(">hhhh", data[:8])
  1618. self.transform = [
  1619. [fi2fl(xscale, 14), fi2fl(scale01, 14)],
  1620. [fi2fl(scale10, 14), fi2fl(yscale, 14)],
  1621. ] # fixed 2.14
  1622. data = data[8:]
  1623. more = self.flags & MORE_COMPONENTS
  1624. haveInstructions = self.flags & WE_HAVE_INSTRUCTIONS
  1625. self.flags = self.flags & (
  1626. ROUND_XY_TO_GRID
  1627. | USE_MY_METRICS
  1628. | SCALED_COMPONENT_OFFSET
  1629. | UNSCALED_COMPONENT_OFFSET
  1630. | NON_OVERLAPPING
  1631. | OVERLAP_COMPOUND
  1632. )
  1633. return more, haveInstructions, data
  1634. def compile(self, more, haveInstructions, glyfTable):
  1635. data = b""
  1636. # reset all flags we will calculate ourselves
  1637. flags = self.flags & (
  1638. ROUND_XY_TO_GRID
  1639. | USE_MY_METRICS
  1640. | SCALED_COMPONENT_OFFSET
  1641. | UNSCALED_COMPONENT_OFFSET
  1642. | NON_OVERLAPPING
  1643. | OVERLAP_COMPOUND
  1644. )
  1645. if more:
  1646. flags = flags | MORE_COMPONENTS
  1647. if haveInstructions:
  1648. flags = flags | WE_HAVE_INSTRUCTIONS
  1649. if hasattr(self, "firstPt"):
  1650. if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255):
  1651. data = data + struct.pack(">BB", self.firstPt, self.secondPt)
  1652. else:
  1653. data = data + struct.pack(">HH", self.firstPt, self.secondPt)
  1654. flags = flags | ARG_1_AND_2_ARE_WORDS
  1655. else:
  1656. x = otRound(self.x)
  1657. y = otRound(self.y)
  1658. flags = flags | ARGS_ARE_XY_VALUES
  1659. if (-128 <= x <= 127) and (-128 <= y <= 127):
  1660. data = data + struct.pack(">bb", x, y)
  1661. else:
  1662. data = data + struct.pack(">hh", x, y)
  1663. flags = flags | ARG_1_AND_2_ARE_WORDS
  1664. if hasattr(self, "transform"):
  1665. transform = [[fl2fi(x, 14) for x in row] for row in self.transform]
  1666. if transform[0][1] or transform[1][0]:
  1667. flags = flags | WE_HAVE_A_TWO_BY_TWO
  1668. data = data + struct.pack(
  1669. ">hhhh",
  1670. transform[0][0],
  1671. transform[0][1],
  1672. transform[1][0],
  1673. transform[1][1],
  1674. )
  1675. elif transform[0][0] != transform[1][1]:
  1676. flags = flags | WE_HAVE_AN_X_AND_Y_SCALE
  1677. data = data + struct.pack(">hh", transform[0][0], transform[1][1])
  1678. else:
  1679. flags = flags | WE_HAVE_A_SCALE
  1680. data = data + struct.pack(">h", transform[0][0])
  1681. glyphID = glyfTable.getGlyphID(self.glyphName)
  1682. return struct.pack(">HH", flags, glyphID) + data
  1683. def toXML(self, writer, ttFont):
  1684. attrs = [("glyphName", self.glyphName)]
  1685. if not hasattr(self, "firstPt"):
  1686. attrs = attrs + [("x", self.x), ("y", self.y)]
  1687. else:
  1688. attrs = attrs + [("firstPt", self.firstPt), ("secondPt", self.secondPt)]
  1689. if hasattr(self, "transform"):
  1690. transform = self.transform
  1691. if transform[0][1] or transform[1][0]:
  1692. attrs = attrs + [
  1693. ("scalex", fl2str(transform[0][0], 14)),
  1694. ("scale01", fl2str(transform[0][1], 14)),
  1695. ("scale10", fl2str(transform[1][0], 14)),
  1696. ("scaley", fl2str(transform[1][1], 14)),
  1697. ]
  1698. elif transform[0][0] != transform[1][1]:
  1699. attrs = attrs + [
  1700. ("scalex", fl2str(transform[0][0], 14)),
  1701. ("scaley", fl2str(transform[1][1], 14)),
  1702. ]
  1703. else:
  1704. attrs = attrs + [("scale", fl2str(transform[0][0], 14))]
  1705. attrs = attrs + [("flags", hex(self.flags))]
  1706. writer.simpletag("component", attrs)
  1707. writer.newline()
  1708. def fromXML(self, name, attrs, content, ttFont):
  1709. self.glyphName = attrs["glyphName"]
  1710. if "firstPt" in attrs:
  1711. self.firstPt = safeEval(attrs["firstPt"])
  1712. self.secondPt = safeEval(attrs["secondPt"])
  1713. else:
  1714. self.x = safeEval(attrs["x"])
  1715. self.y = safeEval(attrs["y"])
  1716. if "scale01" in attrs:
  1717. scalex = str2fl(attrs["scalex"], 14)
  1718. scale01 = str2fl(attrs["scale01"], 14)
  1719. scale10 = str2fl(attrs["scale10"], 14)
  1720. scaley = str2fl(attrs["scaley"], 14)
  1721. self.transform = [[scalex, scale01], [scale10, scaley]]
  1722. elif "scalex" in attrs:
  1723. scalex = str2fl(attrs["scalex"], 14)
  1724. scaley = str2fl(attrs["scaley"], 14)
  1725. self.transform = [[scalex, 0], [0, scaley]]
  1726. elif "scale" in attrs:
  1727. scale = str2fl(attrs["scale"], 14)
  1728. self.transform = [[scale, 0], [0, scale]]
  1729. self.flags = safeEval(attrs["flags"])
  1730. def __eq__(self, other):
  1731. if type(self) != type(other):
  1732. return NotImplemented
  1733. return self.__dict__ == other.__dict__
  1734. def __ne__(self, other):
  1735. result = self.__eq__(other)
  1736. return result if result is NotImplemented else not result
  1737. #
  1738. # Variable Composite glyphs
  1739. # https://github.com/harfbuzz/boring-expansion-spec/blob/main/glyf1.md
  1740. #
  1741. class VarComponentFlags(IntFlag):
  1742. USE_MY_METRICS = 0x0001
  1743. AXIS_INDICES_ARE_SHORT = 0x0002
  1744. UNIFORM_SCALE = 0x0004
  1745. HAVE_TRANSLATE_X = 0x0008
  1746. HAVE_TRANSLATE_Y = 0x0010
  1747. HAVE_ROTATION = 0x0020
  1748. HAVE_SCALE_X = 0x0040
  1749. HAVE_SCALE_Y = 0x0080
  1750. HAVE_SKEW_X = 0x0100
  1751. HAVE_SKEW_Y = 0x0200
  1752. HAVE_TCENTER_X = 0x0400
  1753. HAVE_TCENTER_Y = 0x0800
  1754. GID_IS_24BIT = 0x1000
  1755. AXES_HAVE_VARIATION = 0x2000
  1756. RESET_UNSPECIFIED_AXES = 0x4000
  1757. VarComponentTransformMappingValues = namedtuple(
  1758. "VarComponentTransformMappingValues",
  1759. ["flag", "fractionalBits", "scale", "defaultValue"],
  1760. )
  1761. VAR_COMPONENT_TRANSFORM_MAPPING = {
  1762. "translateX": VarComponentTransformMappingValues(
  1763. VarComponentFlags.HAVE_TRANSLATE_X, 0, 1, 0
  1764. ),
  1765. "translateY": VarComponentTransformMappingValues(
  1766. VarComponentFlags.HAVE_TRANSLATE_Y, 0, 1, 0
  1767. ),
  1768. "rotation": VarComponentTransformMappingValues(
  1769. VarComponentFlags.HAVE_ROTATION, 12, 180, 0
  1770. ),
  1771. "scaleX": VarComponentTransformMappingValues(
  1772. VarComponentFlags.HAVE_SCALE_X, 10, 1, 1
  1773. ),
  1774. "scaleY": VarComponentTransformMappingValues(
  1775. VarComponentFlags.HAVE_SCALE_Y, 10, 1, 1
  1776. ),
  1777. "skewX": VarComponentTransformMappingValues(
  1778. VarComponentFlags.HAVE_SKEW_X, 12, -180, 0
  1779. ),
  1780. "skewY": VarComponentTransformMappingValues(
  1781. VarComponentFlags.HAVE_SKEW_Y, 12, 180, 0
  1782. ),
  1783. "tCenterX": VarComponentTransformMappingValues(
  1784. VarComponentFlags.HAVE_TCENTER_X, 0, 1, 0
  1785. ),
  1786. "tCenterY": VarComponentTransformMappingValues(
  1787. VarComponentFlags.HAVE_TCENTER_Y, 0, 1, 0
  1788. ),
  1789. }
  1790. class GlyphVarComponent(object):
  1791. MIN_SIZE = 5
  1792. def __init__(self):
  1793. self.location = {}
  1794. self.transform = DecomposedTransform()
  1795. @staticmethod
  1796. def getSize(data):
  1797. size = 5
  1798. flags = struct.unpack(">H", data[:2])[0]
  1799. numAxes = int(data[2])
  1800. if flags & VarComponentFlags.GID_IS_24BIT:
  1801. size += 1
  1802. size += numAxes
  1803. if flags & VarComponentFlags.AXIS_INDICES_ARE_SHORT:
  1804. size += 2 * numAxes
  1805. else:
  1806. axisIndices = array.array("B", data[:numAxes])
  1807. size += numAxes
  1808. for attr_name, mapping_values in VAR_COMPONENT_TRANSFORM_MAPPING.items():
  1809. if flags & mapping_values.flag:
  1810. size += 2
  1811. return size
  1812. def decompile(self, data, glyfTable):
  1813. flags = struct.unpack(">H", data[:2])[0]
  1814. self.flags = int(flags)
  1815. data = data[2:]
  1816. numAxes = int(data[0])
  1817. data = data[1:]
  1818. if flags & VarComponentFlags.GID_IS_24BIT:
  1819. glyphID = int(struct.unpack(">L", b"\0" + data[:3])[0])
  1820. data = data[3:]
  1821. flags ^= VarComponentFlags.GID_IS_24BIT
  1822. else:
  1823. glyphID = int(struct.unpack(">H", data[:2])[0])
  1824. data = data[2:]
  1825. self.glyphName = glyfTable.getGlyphName(int(glyphID))
  1826. if flags & VarComponentFlags.AXIS_INDICES_ARE_SHORT:
  1827. axisIndices = array.array("H", data[: 2 * numAxes])
  1828. if sys.byteorder != "big":
  1829. axisIndices.byteswap()
  1830. data = data[2 * numAxes :]
  1831. flags ^= VarComponentFlags.AXIS_INDICES_ARE_SHORT
  1832. else:
  1833. axisIndices = array.array("B", data[:numAxes])
  1834. data = data[numAxes:]
  1835. assert len(axisIndices) == numAxes
  1836. axisIndices = list(axisIndices)
  1837. axisValues = array.array("h", data[: 2 * numAxes])
  1838. if sys.byteorder != "big":
  1839. axisValues.byteswap()
  1840. data = data[2 * numAxes :]
  1841. assert len(axisValues) == numAxes
  1842. axisValues = [fi2fl(v, 14) for v in axisValues]
  1843. self.location = {
  1844. glyfTable.axisTags[i]: v for i, v in zip(axisIndices, axisValues)
  1845. }
  1846. def read_transform_component(data, values):
  1847. if flags & values.flag:
  1848. return (
  1849. data[2:],
  1850. fi2fl(struct.unpack(">h", data[:2])[0], values.fractionalBits)
  1851. * values.scale,
  1852. )
  1853. else:
  1854. return data, values.defaultValue
  1855. for attr_name, mapping_values in VAR_COMPONENT_TRANSFORM_MAPPING.items():
  1856. data, value = read_transform_component(data, mapping_values)
  1857. setattr(self.transform, attr_name, value)
  1858. if flags & VarComponentFlags.UNIFORM_SCALE:
  1859. if flags & VarComponentFlags.HAVE_SCALE_X and not (
  1860. flags & VarComponentFlags.HAVE_SCALE_Y
  1861. ):
  1862. self.transform.scaleY = self.transform.scaleX
  1863. flags |= VarComponentFlags.HAVE_SCALE_Y
  1864. flags ^= VarComponentFlags.UNIFORM_SCALE
  1865. return data
  1866. def compile(self, glyfTable):
  1867. data = b""
  1868. if not hasattr(self, "flags"):
  1869. flags = 0
  1870. # Calculate optimal transform component flags
  1871. for attr_name, mapping in VAR_COMPONENT_TRANSFORM_MAPPING.items():
  1872. value = getattr(self.transform, attr_name)
  1873. if fl2fi(value / mapping.scale, mapping.fractionalBits) != fl2fi(
  1874. mapping.defaultValue / mapping.scale, mapping.fractionalBits
  1875. ):
  1876. flags |= mapping.flag
  1877. else:
  1878. flags = self.flags
  1879. if (
  1880. flags & VarComponentFlags.HAVE_SCALE_X
  1881. and flags & VarComponentFlags.HAVE_SCALE_Y
  1882. and fl2fi(self.transform.scaleX, 10) == fl2fi(self.transform.scaleY, 10)
  1883. ):
  1884. flags |= VarComponentFlags.UNIFORM_SCALE
  1885. flags ^= VarComponentFlags.HAVE_SCALE_Y
  1886. numAxes = len(self.location)
  1887. data = data + struct.pack(">B", numAxes)
  1888. glyphID = glyfTable.getGlyphID(self.glyphName)
  1889. if glyphID > 65535:
  1890. flags |= VarComponentFlags.GID_IS_24BIT
  1891. data = data + struct.pack(">L", glyphID)[1:]
  1892. else:
  1893. data = data + struct.pack(">H", glyphID)
  1894. axisIndices = [glyfTable.axisTags.index(tag) for tag in self.location.keys()]
  1895. if all(a <= 255 for a in axisIndices):
  1896. axisIndices = array.array("B", axisIndices)
  1897. else:
  1898. axisIndices = array.array("H", axisIndices)
  1899. if sys.byteorder != "big":
  1900. axisIndices.byteswap()
  1901. flags |= VarComponentFlags.AXIS_INDICES_ARE_SHORT
  1902. data = data + bytes(axisIndices)
  1903. axisValues = self.location.values()
  1904. axisValues = array.array("h", (fl2fi(v, 14) for v in axisValues))
  1905. if sys.byteorder != "big":
  1906. axisValues.byteswap()
  1907. data = data + bytes(axisValues)
  1908. def write_transform_component(data, value, values):
  1909. if flags & values.flag:
  1910. return data + struct.pack(
  1911. ">h", fl2fi(value / values.scale, values.fractionalBits)
  1912. )
  1913. else:
  1914. return data
  1915. for attr_name, mapping_values in VAR_COMPONENT_TRANSFORM_MAPPING.items():
  1916. value = getattr(self.transform, attr_name)
  1917. data = write_transform_component(data, value, mapping_values)
  1918. return struct.pack(">H", flags) + data
  1919. def toXML(self, writer, ttFont):
  1920. attrs = [("glyphName", self.glyphName)]
  1921. if hasattr(self, "flags"):
  1922. attrs = attrs + [("flags", hex(self.flags))]
  1923. for attr_name, mapping in VAR_COMPONENT_TRANSFORM_MAPPING.items():
  1924. v = getattr(self.transform, attr_name)
  1925. if v != mapping.defaultValue:
  1926. attrs.append((attr_name, fl2str(v, mapping.fractionalBits)))
  1927. writer.begintag("varComponent", attrs)
  1928. writer.newline()
  1929. writer.begintag("location")
  1930. writer.newline()
  1931. for tag, v in self.location.items():
  1932. writer.simpletag("axis", [("tag", tag), ("value", fl2str(v, 14))])
  1933. writer.newline()
  1934. writer.endtag("location")
  1935. writer.newline()
  1936. writer.endtag("varComponent")
  1937. writer.newline()
  1938. def fromXML(self, name, attrs, content, ttFont):
  1939. self.glyphName = attrs["glyphName"]
  1940. if "flags" in attrs:
  1941. self.flags = safeEval(attrs["flags"])
  1942. for attr_name, mapping in VAR_COMPONENT_TRANSFORM_MAPPING.items():
  1943. if attr_name not in attrs:
  1944. continue
  1945. v = str2fl(safeEval(attrs[attr_name]), mapping.fractionalBits)
  1946. setattr(self.transform, attr_name, v)
  1947. for c in content:
  1948. if not isinstance(c, tuple):
  1949. continue
  1950. name, attrs, content = c
  1951. if name != "location":
  1952. continue
  1953. for c in content:
  1954. if not isinstance(c, tuple):
  1955. continue
  1956. name, attrs, content = c
  1957. assert name == "axis"
  1958. assert not content
  1959. self.location[attrs["tag"]] = str2fl(safeEval(attrs["value"]), 14)
  1960. def getPointCount(self):
  1961. assert hasattr(self, "flags"), "VarComponent with variations must have flags"
  1962. count = 0
  1963. if self.flags & VarComponentFlags.AXES_HAVE_VARIATION:
  1964. count += len(self.location)
  1965. if self.flags & (
  1966. VarComponentFlags.HAVE_TRANSLATE_X | VarComponentFlags.HAVE_TRANSLATE_Y
  1967. ):
  1968. count += 1
  1969. if self.flags & VarComponentFlags.HAVE_ROTATION:
  1970. count += 1
  1971. if self.flags & (
  1972. VarComponentFlags.HAVE_SCALE_X | VarComponentFlags.HAVE_SCALE_Y
  1973. ):
  1974. count += 1
  1975. if self.flags & (VarComponentFlags.HAVE_SKEW_X | VarComponentFlags.HAVE_SKEW_Y):
  1976. count += 1
  1977. if self.flags & (
  1978. VarComponentFlags.HAVE_TCENTER_X | VarComponentFlags.HAVE_TCENTER_Y
  1979. ):
  1980. count += 1
  1981. return count
  1982. def getCoordinatesAndControls(self):
  1983. coords = []
  1984. controls = []
  1985. if self.flags & VarComponentFlags.AXES_HAVE_VARIATION:
  1986. for tag, v in self.location.items():
  1987. controls.append(tag)
  1988. coords.append((fl2fi(v, 14), 0))
  1989. if self.flags & (
  1990. VarComponentFlags.HAVE_TRANSLATE_X | VarComponentFlags.HAVE_TRANSLATE_Y
  1991. ):
  1992. controls.append("translate")
  1993. coords.append((self.transform.translateX, self.transform.translateY))
  1994. if self.flags & VarComponentFlags.HAVE_ROTATION:
  1995. controls.append("rotation")
  1996. coords.append((fl2fi(self.transform.rotation / 180, 12), 0))
  1997. if self.flags & (
  1998. VarComponentFlags.HAVE_SCALE_X | VarComponentFlags.HAVE_SCALE_Y
  1999. ):
  2000. controls.append("scale")
  2001. coords.append(
  2002. (fl2fi(self.transform.scaleX, 10), fl2fi(self.transform.scaleY, 10))
  2003. )
  2004. if self.flags & (VarComponentFlags.HAVE_SKEW_X | VarComponentFlags.HAVE_SKEW_Y):
  2005. controls.append("skew")
  2006. coords.append(
  2007. (
  2008. fl2fi(self.transform.skewX / -180, 12),
  2009. fl2fi(self.transform.skewY / 180, 12),
  2010. )
  2011. )
  2012. if self.flags & (
  2013. VarComponentFlags.HAVE_TCENTER_X | VarComponentFlags.HAVE_TCENTER_Y
  2014. ):
  2015. controls.append("tCenter")
  2016. coords.append((self.transform.tCenterX, self.transform.tCenterY))
  2017. return coords, controls
  2018. def setCoordinates(self, coords):
  2019. i = 0
  2020. if self.flags & VarComponentFlags.AXES_HAVE_VARIATION:
  2021. newLocation = {}
  2022. for tag in self.location:
  2023. newLocation[tag] = fi2fl(coords[i][0], 14)
  2024. i += 1
  2025. self.location = newLocation
  2026. self.transform = DecomposedTransform()
  2027. if self.flags & (
  2028. VarComponentFlags.HAVE_TRANSLATE_X | VarComponentFlags.HAVE_TRANSLATE_Y
  2029. ):
  2030. self.transform.translateX, self.transform.translateY = coords[i]
  2031. i += 1
  2032. if self.flags & VarComponentFlags.HAVE_ROTATION:
  2033. self.transform.rotation = fi2fl(coords[i][0], 12) * 180
  2034. i += 1
  2035. if self.flags & (
  2036. VarComponentFlags.HAVE_SCALE_X | VarComponentFlags.HAVE_SCALE_Y
  2037. ):
  2038. self.transform.scaleX, self.transform.scaleY = fi2fl(
  2039. coords[i][0], 10
  2040. ), fi2fl(coords[i][1], 10)
  2041. i += 1
  2042. if self.flags & (VarComponentFlags.HAVE_SKEW_X | VarComponentFlags.HAVE_SKEW_Y):
  2043. self.transform.skewX, self.transform.skewY = (
  2044. fi2fl(coords[i][0], 12) * -180,
  2045. fi2fl(coords[i][1], 12) * 180,
  2046. )
  2047. i += 1
  2048. if self.flags & (
  2049. VarComponentFlags.HAVE_TCENTER_X | VarComponentFlags.HAVE_TCENTER_Y
  2050. ):
  2051. self.transform.tCenterX, self.transform.tCenterY = coords[i]
  2052. i += 1
  2053. return coords[i:]
  2054. def __eq__(self, other):
  2055. if type(self) != type(other):
  2056. return NotImplemented
  2057. return self.__dict__ == other.__dict__
  2058. def __ne__(self, other):
  2059. result = self.__eq__(other)
  2060. return result if result is NotImplemented else not result
  2061. class GlyphCoordinates(object):
  2062. """A list of glyph coordinates.
  2063. Unlike an ordinary list, this is a numpy-like matrix object which supports
  2064. matrix addition, scalar multiplication and other operations described below.
  2065. """
  2066. def __init__(self, iterable=[]):
  2067. self._a = array.array("d")
  2068. self.extend(iterable)
  2069. @property
  2070. def array(self):
  2071. """Returns the underlying array of coordinates"""
  2072. return self._a
  2073. @staticmethod
  2074. def zeros(count):
  2075. """Creates a new ``GlyphCoordinates`` object with all coordinates set to (0,0)"""
  2076. g = GlyphCoordinates()
  2077. g._a.frombytes(bytes(count * 2 * g._a.itemsize))
  2078. return g
  2079. def copy(self):
  2080. """Creates a new ``GlyphCoordinates`` object which is a copy of the current one."""
  2081. c = GlyphCoordinates()
  2082. c._a.extend(self._a)
  2083. return c
  2084. def __len__(self):
  2085. """Returns the number of coordinates in the array."""
  2086. return len(self._a) // 2
  2087. def __getitem__(self, k):
  2088. """Returns a two element tuple (x,y)"""
  2089. a = self._a
  2090. if isinstance(k, slice):
  2091. indices = range(*k.indices(len(self)))
  2092. # Instead of calling ourselves recursively, duplicate code; faster
  2093. ret = []
  2094. for k in indices:
  2095. x = a[2 * k]
  2096. y = a[2 * k + 1]
  2097. ret.append(
  2098. (int(x) if x.is_integer() else x, int(y) if y.is_integer() else y)
  2099. )
  2100. return ret
  2101. x = a[2 * k]
  2102. y = a[2 * k + 1]
  2103. return (int(x) if x.is_integer() else x, int(y) if y.is_integer() else y)
  2104. def __setitem__(self, k, v):
  2105. """Sets a point's coordinates to a two element tuple (x,y)"""
  2106. if isinstance(k, slice):
  2107. indices = range(*k.indices(len(self)))
  2108. # XXX This only works if len(v) == len(indices)
  2109. for j, i in enumerate(indices):
  2110. self[i] = v[j]
  2111. return
  2112. self._a[2 * k], self._a[2 * k + 1] = v
  2113. def __delitem__(self, i):
  2114. """Removes a point from the list"""
  2115. i = (2 * i) % len(self._a)
  2116. del self._a[i]
  2117. del self._a[i]
  2118. def __repr__(self):
  2119. return "GlyphCoordinates([" + ",".join(str(c) for c in self) + "])"
  2120. def append(self, p):
  2121. self._a.extend(tuple(p))
  2122. def extend(self, iterable):
  2123. for p in iterable:
  2124. self._a.extend(p)
  2125. def toInt(self, *, round=otRound):
  2126. if round is noRound:
  2127. return
  2128. a = self._a
  2129. for i in range(len(a)):
  2130. a[i] = round(a[i])
  2131. def calcBounds(self):
  2132. a = self._a
  2133. if not a:
  2134. return 0, 0, 0, 0
  2135. xs = a[0::2]
  2136. ys = a[1::2]
  2137. return min(xs), min(ys), max(xs), max(ys)
  2138. def calcIntBounds(self, round=otRound):
  2139. return tuple(round(v) for v in self.calcBounds())
  2140. def relativeToAbsolute(self):
  2141. a = self._a
  2142. x, y = 0, 0
  2143. for i in range(0, len(a), 2):
  2144. a[i] = x = a[i] + x
  2145. a[i + 1] = y = a[i + 1] + y
  2146. def absoluteToRelative(self):
  2147. a = self._a
  2148. x, y = 0, 0
  2149. for i in range(0, len(a), 2):
  2150. nx = a[i]
  2151. ny = a[i + 1]
  2152. a[i] = nx - x
  2153. a[i + 1] = ny - y
  2154. x = nx
  2155. y = ny
  2156. def translate(self, p):
  2157. """
  2158. >>> GlyphCoordinates([(1,2)]).translate((.5,0))
  2159. """
  2160. x, y = p
  2161. if x == 0 and y == 0:
  2162. return
  2163. a = self._a
  2164. for i in range(0, len(a), 2):
  2165. a[i] += x
  2166. a[i + 1] += y
  2167. def scale(self, p):
  2168. """
  2169. >>> GlyphCoordinates([(1,2)]).scale((.5,0))
  2170. """
  2171. x, y = p
  2172. if x == 1 and y == 1:
  2173. return
  2174. a = self._a
  2175. for i in range(0, len(a), 2):
  2176. a[i] *= x
  2177. a[i + 1] *= y
  2178. def transform(self, t):
  2179. """
  2180. >>> GlyphCoordinates([(1,2)]).transform(((.5,0),(.2,.5)))
  2181. """
  2182. a = self._a
  2183. for i in range(0, len(a), 2):
  2184. x = a[i]
  2185. y = a[i + 1]
  2186. px = x * t[0][0] + y * t[1][0]
  2187. py = x * t[0][1] + y * t[1][1]
  2188. a[i] = px
  2189. a[i + 1] = py
  2190. def __eq__(self, other):
  2191. """
  2192. >>> g = GlyphCoordinates([(1,2)])
  2193. >>> g2 = GlyphCoordinates([(1.0,2)])
  2194. >>> g3 = GlyphCoordinates([(1.5,2)])
  2195. >>> g == g2
  2196. True
  2197. >>> g == g3
  2198. False
  2199. >>> g2 == g3
  2200. False
  2201. """
  2202. if type(self) != type(other):
  2203. return NotImplemented
  2204. return self._a == other._a
  2205. def __ne__(self, other):
  2206. """
  2207. >>> g = GlyphCoordinates([(1,2)])
  2208. >>> g2 = GlyphCoordinates([(1.0,2)])
  2209. >>> g3 = GlyphCoordinates([(1.5,2)])
  2210. >>> g != g2
  2211. False
  2212. >>> g != g3
  2213. True
  2214. >>> g2 != g3
  2215. True
  2216. """
  2217. result = self.__eq__(other)
  2218. return result if result is NotImplemented else not result
  2219. # Math operations
  2220. def __pos__(self):
  2221. """
  2222. >>> g = GlyphCoordinates([(1,2)])
  2223. >>> g
  2224. GlyphCoordinates([(1, 2)])
  2225. >>> g2 = +g
  2226. >>> g2
  2227. GlyphCoordinates([(1, 2)])
  2228. >>> g2.translate((1,0))
  2229. >>> g2
  2230. GlyphCoordinates([(2, 2)])
  2231. >>> g
  2232. GlyphCoordinates([(1, 2)])
  2233. """
  2234. return self.copy()
  2235. def __neg__(self):
  2236. """
  2237. >>> g = GlyphCoordinates([(1,2)])
  2238. >>> g
  2239. GlyphCoordinates([(1, 2)])
  2240. >>> g2 = -g
  2241. >>> g2
  2242. GlyphCoordinates([(-1, -2)])
  2243. >>> g
  2244. GlyphCoordinates([(1, 2)])
  2245. """
  2246. r = self.copy()
  2247. a = r._a
  2248. for i in range(len(a)):
  2249. a[i] = -a[i]
  2250. return r
  2251. def __round__(self, *, round=otRound):
  2252. r = self.copy()
  2253. r.toInt(round=round)
  2254. return r
  2255. def __add__(self, other):
  2256. return self.copy().__iadd__(other)
  2257. def __sub__(self, other):
  2258. return self.copy().__isub__(other)
  2259. def __mul__(self, other):
  2260. return self.copy().__imul__(other)
  2261. def __truediv__(self, other):
  2262. return self.copy().__itruediv__(other)
  2263. __radd__ = __add__
  2264. __rmul__ = __mul__
  2265. def __rsub__(self, other):
  2266. return other + (-self)
  2267. def __iadd__(self, other):
  2268. """
  2269. >>> g = GlyphCoordinates([(1,2)])
  2270. >>> g += (.5,0)
  2271. >>> g
  2272. GlyphCoordinates([(1.5, 2)])
  2273. >>> g2 = GlyphCoordinates([(3,4)])
  2274. >>> g += g2
  2275. >>> g
  2276. GlyphCoordinates([(4.5, 6)])
  2277. """
  2278. if isinstance(other, tuple):
  2279. assert len(other) == 2
  2280. self.translate(other)
  2281. return self
  2282. if isinstance(other, GlyphCoordinates):
  2283. other = other._a
  2284. a = self._a
  2285. assert len(a) == len(other)
  2286. for i in range(len(a)):
  2287. a[i] += other[i]
  2288. return self
  2289. return NotImplemented
  2290. def __isub__(self, other):
  2291. """
  2292. >>> g = GlyphCoordinates([(1,2)])
  2293. >>> g -= (.5,0)
  2294. >>> g
  2295. GlyphCoordinates([(0.5, 2)])
  2296. >>> g2 = GlyphCoordinates([(3,4)])
  2297. >>> g -= g2
  2298. >>> g
  2299. GlyphCoordinates([(-2.5, -2)])
  2300. """
  2301. if isinstance(other, tuple):
  2302. assert len(other) == 2
  2303. self.translate((-other[0], -other[1]))
  2304. return self
  2305. if isinstance(other, GlyphCoordinates):
  2306. other = other._a
  2307. a = self._a
  2308. assert len(a) == len(other)
  2309. for i in range(len(a)):
  2310. a[i] -= other[i]
  2311. return self
  2312. return NotImplemented
  2313. def __imul__(self, other):
  2314. """
  2315. >>> g = GlyphCoordinates([(1,2)])
  2316. >>> g *= (2,.5)
  2317. >>> g *= 2
  2318. >>> g
  2319. GlyphCoordinates([(4, 2)])
  2320. >>> g = GlyphCoordinates([(1,2)])
  2321. >>> g *= 2
  2322. >>> g
  2323. GlyphCoordinates([(2, 4)])
  2324. """
  2325. if isinstance(other, tuple):
  2326. assert len(other) == 2
  2327. self.scale(other)
  2328. return self
  2329. if isinstance(other, Number):
  2330. if other == 1:
  2331. return self
  2332. a = self._a
  2333. for i in range(len(a)):
  2334. a[i] *= other
  2335. return self
  2336. return NotImplemented
  2337. def __itruediv__(self, other):
  2338. """
  2339. >>> g = GlyphCoordinates([(1,3)])
  2340. >>> g /= (.5,1.5)
  2341. >>> g /= 2
  2342. >>> g
  2343. GlyphCoordinates([(1, 1)])
  2344. """
  2345. if isinstance(other, Number):
  2346. other = (other, other)
  2347. if isinstance(other, tuple):
  2348. if other == (1, 1):
  2349. return self
  2350. assert len(other) == 2
  2351. self.scale((1.0 / other[0], 1.0 / other[1]))
  2352. return self
  2353. return NotImplemented
  2354. def __bool__(self):
  2355. """
  2356. >>> g = GlyphCoordinates([])
  2357. >>> bool(g)
  2358. False
  2359. >>> g = GlyphCoordinates([(0,0), (0.,0)])
  2360. >>> bool(g)
  2361. True
  2362. >>> g = GlyphCoordinates([(0,0), (1,0)])
  2363. >>> bool(g)
  2364. True
  2365. >>> g = GlyphCoordinates([(0,.5), (0,0)])
  2366. >>> bool(g)
  2367. True
  2368. """
  2369. return bool(self._a)
  2370. __nonzero__ = __bool__
  2371. if __name__ == "__main__":
  2372. import doctest, sys
  2373. sys.exit(doctest.testmod().failed)