blob: 3ffe02668d34e638bd8130ddc21606094e12ceb1 [file] [log] [blame]
Behdad Esfahbod1ae29592014-01-14 15:07:50 +08001from __future__ import print_function, division, absolute_import
Behdad Esfahbod30e691e2013-11-27 17:27:45 -05002from fontTools.misc.py23 import *
3from fontTools.misc.textTools import safeEval, readHex
Behdad Esfahbod2b06aaa2013-11-27 02:34:11 -05004from . import DefaultTable
Behdad Esfahbod30e691e2013-11-27 17:27:45 -05005import sys
Just7842e561999-12-16 21:34:53 +00006import struct
Just7842e561999-12-16 21:34:53 +00007import array
jvrd299b552006-10-21 13:54:30 +00008import operator
Just7842e561999-12-16 21:34:53 +00009
10
11class table__c_m_a_p(DefaultTable.DefaultTable):
12
13 def getcmap(self, platformID, platEncID):
14 for subtable in self.tables:
15 if (subtable.platformID == platformID and
16 subtable.platEncID == platEncID):
17 return subtable
18 return None # not found
19
20 def decompile(self, data, ttFont):
21 tableVersion, numSubTables = struct.unpack(">HH", data[:4])
22 self.tableVersion = int(tableVersion)
23 self.tables = tables = []
jvrd299b552006-10-21 13:54:30 +000024 seenOffsets = {}
Just7842e561999-12-16 21:34:53 +000025 for i in range(numSubTables):
26 platformID, platEncID, offset = struct.unpack(
27 ">HHl", data[4+i*8:4+(i+1)*8])
28 platformID, platEncID = int(platformID), int(platEncID)
29 format, length = struct.unpack(">HH", data[offset:offset+4])
Roozbeh Pournader51a17822013-10-09 15:55:07 -070030 if format in [8,10,12,13]:
jvr924e4e22003-02-08 10:45:23 +000031 format, reserved, length = struct.unpack(">HHL", data[offset:offset+8])
jvr0cb8a082008-05-16 15:07:09 +000032 elif format in [14]:
33 format, length = struct.unpack(">HL", data[offset:offset+6])
34
jvr2db352c2008-02-29 14:43:49 +000035 if not length:
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -050036 print("Error: cmap subtable is reported as having zero length: platformID %s, platEncID %s, format %s offset %s. Skipping table." % (platformID, platEncID,format, offset))
jvr2db352c2008-02-29 14:43:49 +000037 continue
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -050038 if format not in cmap_classes:
Just7842e561999-12-16 21:34:53 +000039 table = cmap_format_unknown(format)
40 else:
41 table = cmap_classes[format](format)
42 table.platformID = platformID
43 table.platEncID = platEncID
jvrd299b552006-10-21 13:54:30 +000044 # Note that by default we decompile only the subtable header info;
45 # any other data gets decompiled only when an attribute of the
46 # subtable is referenced.
47 table.decompileHeader(data[offset:offset+int(length)], ttFont)
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -050048 if offset in seenOffsets:
jvrd299b552006-10-21 13:54:30 +000049 table.cmap = tables[seenOffsets[offset]].cmap
50 else:
51 seenOffsets[offset] = i
Just7842e561999-12-16 21:34:53 +000052 tables.append(table)
53
54 def compile(self, ttFont):
Behdad Esfahbodb7fd2e12013-11-27 18:58:45 -050055 self.tables.sort() # sort according to the spec; see CmapSubtable.__lt__()
Just7842e561999-12-16 21:34:53 +000056 numSubTables = len(self.tables)
57 totalOffset = 4 + 8 * numSubTables
58 data = struct.pack(">HH", self.tableVersion, numSubTables)
Behdad Esfahbod821572c2013-11-27 21:09:03 -050059 tableData = b""
jvrd299b552006-10-21 13:54:30 +000060 seen = {} # Some tables are the same object reference. Don't compile them twice.
61 done = {} # Some tables are different objects, but compile to the same data chunk
Just7842e561999-12-16 21:34:53 +000062 for table in self.tables:
jvrd299b552006-10-21 13:54:30 +000063 try:
64 offset = seen[id(table.cmap)]
65 except KeyError:
66 chunk = table.compile(ttFont)
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -050067 if chunk in done:
jvrd299b552006-10-21 13:54:30 +000068 offset = done[chunk]
69 else:
70 offset = seen[id(table.cmap)] = done[chunk] = totalOffset + len(tableData)
71 tableData = tableData + chunk
Just7842e561999-12-16 21:34:53 +000072 data = data + struct.pack(">HHl", table.platformID, table.platEncID, offset)
73 return data + tableData
74
75 def toXML(self, writer, ttFont):
76 writer.simpletag("tableVersion", version=self.tableVersion)
77 writer.newline()
78 for table in self.tables:
79 table.toXML(writer, ttFont)
80
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -050081 def fromXML(self, name, attrs, content, ttFont):
Just7842e561999-12-16 21:34:53 +000082 if name == "tableVersion":
83 self.tableVersion = safeEval(attrs["version"])
84 return
Behdad Esfahbod180ace62013-11-27 02:40:30 -050085 if name[:12] != "cmap_format_":
Just7842e561999-12-16 21:34:53 +000086 return
87 if not hasattr(self, "tables"):
88 self.tables = []
jvr0cd79a52004-09-25 07:30:47 +000089 format = safeEval(name[12:])
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -050090 if format not in cmap_classes:
Just7842e561999-12-16 21:34:53 +000091 table = cmap_format_unknown(format)
92 else:
93 table = cmap_classes[format](format)
94 table.platformID = safeEval(attrs["platformID"])
95 table.platEncID = safeEval(attrs["platEncID"])
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -050096 table.fromXML(name, attrs, content, ttFont)
Just7842e561999-12-16 21:34:53 +000097 self.tables.append(table)
98
99
Behdad Esfahbode388db52013-11-28 14:26:58 -0500100class CmapSubtable(object):
Just7842e561999-12-16 21:34:53 +0000101
102 def __init__(self, format):
103 self.format = format
jvrd299b552006-10-21 13:54:30 +0000104 self.data = None
105 self.ttFont = None
106
107 def __getattr__(self, attr):
108 # allow lazy decompilation of subtables.
109 if attr[:2] == '__': # don't handle requests for member functions like '__lt__'
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500110 raise AttributeError(attr)
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500111 if self.data is None:
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500112 raise AttributeError(attr)
jvrd299b552006-10-21 13:54:30 +0000113 self.decompile(None, None) # use saved data.
114 self.data = None # Once this table has been decompiled, make sure we don't
115 # just return the original data. Also avoids recursion when
116 # called with an attribute that the cmap subtable doesn't have.
117 return getattr(self, attr)
Just7842e561999-12-16 21:34:53 +0000118
jvrd299b552006-10-21 13:54:30 +0000119 def decompileHeader(self, data, ttFont):
120 format, length, language = struct.unpack(">HHH", data[:6])
121 assert len(data) == length, "corrupt cmap table format %d (data length: %d, header length: %d)" % (format, len(data), length)
122 self.format = int(format)
123 self.length = int(length)
124 self.language = int(language)
125 self.data = data[6:]
126 self.ttFont = ttFont
127
Just7842e561999-12-16 21:34:53 +0000128 def toXML(self, writer, ttFont):
129 writer.begintag(self.__class__.__name__, [
130 ("platformID", self.platformID),
131 ("platEncID", self.platEncID),
jvra84b28d2004-09-25 09:06:58 +0000132 ("language", self.language),
Just7842e561999-12-16 21:34:53 +0000133 ])
134 writer.newline()
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500135 codes = sorted(self.cmap.items())
jvra84b28d2004-09-25 09:06:58 +0000136 self._writeCodes(codes, writer)
Just7842e561999-12-16 21:34:53 +0000137 writer.endtag(self.__class__.__name__)
138 writer.newline()
jvra84b28d2004-09-25 09:06:58 +0000139
140 def _writeCodes(self, codes, writer):
jvrd299b552006-10-21 13:54:30 +0000141 if (self.platformID, self.platEncID) == (3, 1) or (self.platformID, self.platEncID) == (3, 10) or self.platformID == 0:
jvra84b28d2004-09-25 09:06:58 +0000142 from fontTools.unicode import Unicode
143 isUnicode = 1
144 else:
145 isUnicode = 0
146 for code, name in codes:
147 writer.simpletag("map", code=hex(code), name=name)
148 if isUnicode:
149 writer.comment(Unicode[code])
150 writer.newline()
Just7842e561999-12-16 21:34:53 +0000151
Behdad Esfahbodb7fd2e12013-11-27 18:58:45 -0500152 def __lt__(self, other):
153 if not isinstance(other, CmapSubtable):
Behdad Esfahbod273a9002013-12-07 03:40:44 -0500154 return NotImplemented
Behdad Esfahbod96b321c2013-08-17 11:11:22 -0400155
Behdad Esfahbodb7fd2e12013-11-27 18:58:45 -0500156 # implemented so that list.sort() sorts according to the spec.
Just7842e561999-12-16 21:34:53 +0000157 selfTuple = (
Behdad Esfahbod94118dc2013-10-28 12:16:41 +0100158 getattr(self, "platformID", None),
159 getattr(self, "platEncID", None),
160 getattr(self, "language", None),
161 self.__dict__)
Just7842e561999-12-16 21:34:53 +0000162 otherTuple = (
Behdad Esfahbod94118dc2013-10-28 12:16:41 +0100163 getattr(other, "platformID", None),
164 getattr(other, "platEncID", None),
165 getattr(other, "language", None),
166 other.__dict__)
Behdad Esfahbodb7fd2e12013-11-27 18:58:45 -0500167 return selfTuple < otherTuple
Just7842e561999-12-16 21:34:53 +0000168
169
170class cmap_format_0(CmapSubtable):
171
172 def decompile(self, data, ttFont):
jvrd299b552006-10-21 13:54:30 +0000173 # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None.
174 # If not, someone is calling the subtable decompile() directly, and must provide both args.
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500175 if data is not None and ttFont is not None:
jvrd299b552006-10-21 13:54:30 +0000176 self.decompileHeader(data[offset:offset+int(length)], ttFont)
177 else:
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500178 assert (data is None and ttFont is None), "Need both data and ttFont arguments"
jvrd299b552006-10-21 13:54:30 +0000179 data = self.data # decompileHeader assigns the data after the header to self.data
180 assert 262 == self.length, "Format 0 cmap subtable not 262 bytes"
Just7842e561999-12-16 21:34:53 +0000181 glyphIdArray = array.array("B")
jvrd299b552006-10-21 13:54:30 +0000182 glyphIdArray.fromstring(self.data)
Just7842e561999-12-16 21:34:53 +0000183 self.cmap = cmap = {}
jvrd299b552006-10-21 13:54:30 +0000184 lenArray = len(glyphIdArray)
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -0500185 charCodes = list(range(lenArray))
jvrd299b552006-10-21 13:54:30 +0000186 names = map(self.ttFont.getGlyphName, glyphIdArray)
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500187 list(map(operator.setitem, [cmap]*lenArray, charCodes, names))
jvrd299b552006-10-21 13:54:30 +0000188
Just7842e561999-12-16 21:34:53 +0000189
190 def compile(self, ttFont):
jvrd299b552006-10-21 13:54:30 +0000191 if self.data:
192 return struct.pack(">HHH", 0, 262, self.language) + self.data
193
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500194 charCodeList = sorted(self.cmap.items())
jvrd299b552006-10-21 13:54:30 +0000195 charCodes = [entry[0] for entry in charCodeList]
196 valueList = [entry[1] for entry in charCodeList]
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -0500197 assert charCodes == list(range(256))
jvrd299b552006-10-21 13:54:30 +0000198 valueList = map(ttFont.getGlyphID, valueList)
199
Behdad Esfahbod8da82422013-08-16 12:56:08 -0400200 glyphIdArray = array.array("B", valueList)
jvr0cd79a52004-09-25 07:30:47 +0000201 data = struct.pack(">HHH", 0, 262, self.language) + glyphIdArray.tostring()
Just7842e561999-12-16 21:34:53 +0000202 assert len(data) == 262
203 return data
204
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500205 def fromXML(self, name, attrs, content, ttFont):
jvr0cd79a52004-09-25 07:30:47 +0000206 self.language = safeEval(attrs["language"])
jvrd299b552006-10-21 13:54:30 +0000207 if not hasattr(self, "cmap"):
208 self.cmap = {}
209 cmap = self.cmap
Just7842e561999-12-16 21:34:53 +0000210 for element in content:
Behdad Esfahbodb774f9f2013-11-27 05:17:37 -0500211 if not isinstance(element, tuple):
Just7842e561999-12-16 21:34:53 +0000212 continue
213 name, attrs, content = element
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500214 if name != "map":
Just7842e561999-12-16 21:34:53 +0000215 continue
jvrd299b552006-10-21 13:54:30 +0000216 cmap[safeEval(attrs["code"])] = attrs["name"]
Just7842e561999-12-16 21:34:53 +0000217
218
jvrbafa66e2003-08-28 18:04:23 +0000219subHeaderFormat = ">HHhH"
Behdad Esfahbode388db52013-11-28 14:26:58 -0500220class SubHeader(object):
jvrbafa66e2003-08-28 18:04:23 +0000221 def __init__(self):
222 self.firstCode = None
223 self.entryCount = None
224 self.idDelta = None
225 self.idRangeOffset = None
226 self.glyphIndexArray = []
227
Just7842e561999-12-16 21:34:53 +0000228class cmap_format_2(CmapSubtable):
229
jvrd299b552006-10-21 13:54:30 +0000230 def setIDDelta(self, subHeader):
231 subHeader.idDelta = 0
232 # find the minGI which is not zero.
233 minGI = subHeader.glyphIndexArray[0]
234 for gid in subHeader.glyphIndexArray:
235 if (gid != 0) and (gid < minGI):
236 minGI = gid
237 # The lowest gid in glyphIndexArray, after subtracting idDelta, must be 1.
238 # idDelta is a short, and must be between -32K and 32K. minGI can be between 1 and 64K.
239 # We would like to pick an idDelta such that the first glyphArray GID is 1,
240 # so that we are more likely to be able to combine glypharray GID subranges.
241 # This means that we have a problem when minGI is > 32K
242 # Since the final gi is reconstructed from the glyphArray GID by:
243 # (short)finalGID = (gid + idDelta) % 0x10000),
244 # we can get from a glypharray GID of 1 to a final GID of 65K by subtracting 2, and casting the
245 # negative number to an unsigned short.
246
247 if (minGI > 1):
248 if minGI > 0x7FFF:
249 subHeader.idDelta = -(0x10000 - minGI) -1
250 else:
251 subHeader.idDelta = minGI -1
252 idDelta = subHeader.idDelta
253 for i in range(subHeader.entryCount):
254 gid = subHeader.glyphIndexArray[i]
255 if gid > 0:
256 subHeader.glyphIndexArray[i] = gid - idDelta
257
258
Just7842e561999-12-16 21:34:53 +0000259 def decompile(self, data, ttFont):
jvrd299b552006-10-21 13:54:30 +0000260 # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None.
261 # If not, someone is calling the subtable decompile() directly, and must provide both args.
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500262 if data is not None and ttFont is not None:
jvrd299b552006-10-21 13:54:30 +0000263 self.decompileHeader(data[offset:offset+int(length)], ttFont)
264 else:
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500265 assert (data is None and ttFont is None), "Need both data and ttFont arguments"
jvrd299b552006-10-21 13:54:30 +0000266
267 data = self.data # decompileHeader assigns the data after the header to self.data
jvrbafa66e2003-08-28 18:04:23 +0000268 subHeaderKeys = []
269 maxSubHeaderindex = 0
jvrbafa66e2003-08-28 18:04:23 +0000270 # get the key array, and determine the number of subHeaders.
jvrd299b552006-10-21 13:54:30 +0000271 allKeys = array.array("H")
272 allKeys.fromstring(data[:512])
273 data = data[512:]
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500274 if sys.byteorder != "big":
jvrd299b552006-10-21 13:54:30 +0000275 allKeys.byteswap()
Behdad Esfahbod32c10ee2013-11-27 17:46:17 -0500276 subHeaderKeys = [ key//8 for key in allKeys]
jvrd299b552006-10-21 13:54:30 +0000277 maxSubHeaderindex = max(subHeaderKeys)
Just7842e561999-12-16 21:34:53 +0000278
jvrbafa66e2003-08-28 18:04:23 +0000279 #Load subHeaders
280 subHeaderList = []
jvrd299b552006-10-21 13:54:30 +0000281 pos = 0
jvrbafa66e2003-08-28 18:04:23 +0000282 for i in range(maxSubHeaderindex + 1):
283 subHeader = SubHeader()
284 (subHeader.firstCode, subHeader.entryCount, subHeader.idDelta, \
jvrd299b552006-10-21 13:54:30 +0000285 subHeader.idRangeOffset) = struct.unpack(subHeaderFormat, data[pos:pos + 8])
286 pos += 8
287 giDataPos = pos + subHeader.idRangeOffset-2
288 giList = array.array("H")
289 giList.fromstring(data[giDataPos:giDataPos + subHeader.entryCount*2])
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500290 if sys.byteorder != "big":
jvrd299b552006-10-21 13:54:30 +0000291 giList.byteswap()
292 subHeader.glyphIndexArray = giList
jvrbafa66e2003-08-28 18:04:23 +0000293 subHeaderList.append(subHeader)
jvrbafa66e2003-08-28 18:04:23 +0000294 # How this gets processed.
295 # Charcodes may be one or two bytes.
296 # The first byte of a charcode is mapped through the subHeaderKeys, to select
297 # a subHeader. For any subheader but 0, the next byte is then mapped through the
298 # selected subheader. If subheader Index 0 is selected, then the byte itself is
299 # mapped through the subheader, and there is no second byte.
300 # Then assume that the subsequent byte is the first byte of the next charcode,and repeat.
301 #
302 # Each subheader references a range in the glyphIndexArray whose length is entryCount.
303 # The range in glyphIndexArray referenced by a sunheader may overlap with the range in glyphIndexArray
304 # referenced by another subheader.
305 # The only subheader that will be referenced by more than one first-byte value is the subheader
306 # that maps the entire range of glyphID values to glyphIndex 0, e.g notdef:
307 # {firstChar 0, EntryCount 0,idDelta 0,idRangeOffset xx}
308 # A byte being mapped though a subheader is treated as in index into a mapping of array index to font glyphIndex.
309 # A subheader specifies a subrange within (0...256) by the
310 # firstChar and EntryCount values. If the byte value is outside the subrange, then the glyphIndex is zero
311 # (e.g. glyph not in font).
312 # If the byte index is in the subrange, then an offset index is calculated as (byteIndex - firstChar).
313 # The index to glyphIndex mapping is a subrange of the glyphIndexArray. You find the start of the subrange by
314 # counting idRangeOffset bytes from the idRangeOffset word. The first value in this subrange is the
315 # glyphIndex for the index firstChar. The offset index should then be used in this array to get the glyphIndex.
316 # Example for Logocut-Medium
317 # first byte of charcode = 129; selects subheader 1.
318 # subheader 1 = {firstChar 64, EntryCount 108,idDelta 42,idRangeOffset 0252}
319 # second byte of charCode = 66
320 # the index offset = 66-64 = 2.
321 # The subrange of the glyphIndexArray starting at 0x0252 bytes from the idRangeOffset word is:
322 # [glyphIndexArray index], [subrange array index] = glyphIndex
323 # [256], [0]=1 from charcode [129, 64]
324 # [257], [1]=2 from charcode [129, 65]
325 # [258], [2]=3 from charcode [129, 66]
326 # [259], [3]=4 from charcode [129, 67]
jvrd299b552006-10-21 13:54:30 +0000327 # So, the glyphIndex = 3 from the array. Then if idDelta is not zero and the glyph ID is not zero,
328 # add it to the glyphID to get the final glyphIndex
jvrbafa66e2003-08-28 18:04:23 +0000329 # value. In this case the final glyph index = 3+ 42 -> 45 for the final glyphIndex. Whew!
jvrbafa66e2003-08-28 18:04:23 +0000330
Behdad Esfahbod5f6418d2013-11-27 22:00:49 -0500331 self.data = b""
jvrd299b552006-10-21 13:54:30 +0000332 self.cmap = cmap = {}
333 notdefGI = 0
jvrbafa66e2003-08-28 18:04:23 +0000334 for firstByte in range(256):
335 subHeadindex = subHeaderKeys[firstByte]
336 subHeader = subHeaderList[subHeadindex]
337 if subHeadindex == 0:
338 if (firstByte < subHeader.firstCode) or (firstByte >= subHeader.firstCode + subHeader.entryCount):
jvrd299b552006-10-21 13:54:30 +0000339 continue # gi is notdef.
jvrbafa66e2003-08-28 18:04:23 +0000340 else:
341 charCode = firstByte
342 offsetIndex = firstByte - subHeader.firstCode
343 gi = subHeader.glyphIndexArray[offsetIndex]
344 if gi != 0:
jvrd299b552006-10-21 13:54:30 +0000345 gi = (gi + subHeader.idDelta) % 0x10000
346 else:
347 continue # gi is notdef.
348 cmap[charCode] = gi
jvrbafa66e2003-08-28 18:04:23 +0000349 else:
350 if subHeader.entryCount:
jvrd299b552006-10-21 13:54:30 +0000351 charCodeOffset = firstByte * 256 + subHeader.firstCode
jvrbafa66e2003-08-28 18:04:23 +0000352 for offsetIndex in range(subHeader.entryCount):
jvrd299b552006-10-21 13:54:30 +0000353 charCode = charCodeOffset + offsetIndex
jvrbafa66e2003-08-28 18:04:23 +0000354 gi = subHeader.glyphIndexArray[offsetIndex]
355 if gi != 0:
jvrd299b552006-10-21 13:54:30 +0000356 gi = (gi + subHeader.idDelta) % 0x10000
357 else:
358 continue
359 cmap[charCode] = gi
360 # If not subHeader.entryCount, then all char codes with this first byte are
361 # mapped to .notdef. We can skip this subtable, and leave the glyphs un-encoded, which is the
362 # same as mapping it to .notdef.
363 # cmap values are GID's.
364 glyphOrder = self.ttFont.getGlyphOrder()
Behdad Esfahbodc2297cd2013-11-27 06:26:55 -0500365 gids = list(cmap.values())
366 charCodes = list(cmap.keys())
jvrd299b552006-10-21 13:54:30 +0000367 lenCmap = len(gids)
368 try:
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500369 names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids ))
jvrd299b552006-10-21 13:54:30 +0000370 except IndexError:
371 getGlyphName = self.ttFont.getGlyphName
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500372 names = list(map(getGlyphName, gids ))
373 list(map(operator.setitem, [cmap]*lenCmap, charCodes, names))
jvrd299b552006-10-21 13:54:30 +0000374
jvrbafa66e2003-08-28 18:04:23 +0000375
Just7842e561999-12-16 21:34:53 +0000376 def compile(self, ttFont):
jvrd299b552006-10-21 13:54:30 +0000377 if self.data:
378 return struct.pack(">HHH", self.format, self.length, self.language) + self.data
jvrbafa66e2003-08-28 18:04:23 +0000379 kEmptyTwoCharCodeRange = -1
jvrd299b552006-10-21 13:54:30 +0000380 notdefGI = 0
381
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500382 items = sorted(self.cmap.items())
jvrd299b552006-10-21 13:54:30 +0000383 charCodes = [item[0] for item in items]
384 names = [item[1] for item in items]
385 nameMap = ttFont.getReverseGlyphMap()
386 lenCharCodes = len(charCodes)
387 try:
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500388 gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names))
jvrd299b552006-10-21 13:54:30 +0000389 except KeyError:
Behdad Esfahboddc873722013-12-04 21:28:50 -0500390 nameMap = ttFont.getReverseGlyphMap(rebuild=True)
jvrd299b552006-10-21 13:54:30 +0000391 try:
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500392 gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names))
jvrd299b552006-10-21 13:54:30 +0000393 except KeyError:
394 # allow virtual GIDs in format 2 tables
395 gids = []
396 for name in names:
397 try:
398 gid = nameMap[name]
399 except KeyError:
400 try:
401 if (name[:3] == 'gid'):
402 gid = eval(name[3:])
403 else:
404 gid = ttFont.getGlyphID(name)
405 except:
406 raise KeyError(name)
jvrbafa66e2003-08-28 18:04:23 +0000407
jvrd299b552006-10-21 13:54:30 +0000408 gids.append(gid)
409
410 # Process the (char code to gid) item list in char code order.
411 # By definition, all one byte char codes map to subheader 0.
412 # For all the two byte char codes, we assume that the first byte maps maps to the empty subhead (with an entry count of 0,
413 # which defines all char codes in its range to map to notdef) unless proven otherwise.
414 # Note that since the char code items are processed in char code order, all the char codes with the
415 # same first byte are in sequential order.
416
417 subHeaderKeys = [ kEmptyTwoCharCodeRange for x in range(256)] # list of indices into subHeaderList.
jvrbafa66e2003-08-28 18:04:23 +0000418 subHeaderList = []
jvrd299b552006-10-21 13:54:30 +0000419
420 # We force this subheader entry 0 to exist in the subHeaderList in the case where some one comes up
421 # with a cmap where all the one byte char codes map to notdef,
422 # with the result that the subhead 0 would not get created just by processing the item list.
423 charCode = charCodes[0]
424 if charCode > 255:
425 subHeader = SubHeader()
426 subHeader.firstCode = 0
427 subHeader.entryCount = 0
428 subHeader.idDelta = 0
429 subHeader.idRangeOffset = 0
430 subHeaderList.append(subHeader)
431
jvrbafa66e2003-08-28 18:04:23 +0000432
433 lastFirstByte = -1
jvrd299b552006-10-21 13:54:30 +0000434 items = zip(charCodes, gids)
435 for charCode, gid in items:
436 if gid == 0:
437 continue
jvrbafa66e2003-08-28 18:04:23 +0000438 firstbyte = charCode >> 8
439 secondByte = charCode & 0x00FF
jvrd299b552006-10-21 13:54:30 +0000440
441 if firstbyte != lastFirstByte: # Need to update the current subhead, and start a new one.
jvrbafa66e2003-08-28 18:04:23 +0000442 if lastFirstByte > -1:
jvrd299b552006-10-21 13:54:30 +0000443 # fix GI's and iDelta of current subheader.
444 self.setIDDelta(subHeader)
445
446 # If it was sunheader 0 for one-byte charCodes, then we need to set the subHeaderKeys value to zero
447 # for the indices matching the char codes.
448 if lastFirstByte == 0:
449 for index in range(subHeader.entryCount):
450 charCode = subHeader.firstCode + index
451 subHeaderKeys[charCode] = 0
452
jvrbafa66e2003-08-28 18:04:23 +0000453 assert (subHeader.entryCount == len(subHeader.glyphIndexArray)), "Error - subhead entry count does not match len of glyphID subrange."
454 # init new subheader
455 subHeader = SubHeader()
456 subHeader.firstCode = secondByte
jvrd299b552006-10-21 13:54:30 +0000457 subHeader.entryCount = 1
458 subHeader.glyphIndexArray.append(gid)
459 subHeaderList.append(subHeader)
460 subHeaderKeys[firstbyte] = len(subHeaderList) -1
jvrbafa66e2003-08-28 18:04:23 +0000461 lastFirstByte = firstbyte
462 else:
jvrd299b552006-10-21 13:54:30 +0000463 # need to fill in with notdefs all the code points between the last charCode and the current charCode.
jvrbafa66e2003-08-28 18:04:23 +0000464 codeDiff = secondByte - (subHeader.firstCode + subHeader.entryCount)
465 for i in range(codeDiff):
jvrd299b552006-10-21 13:54:30 +0000466 subHeader.glyphIndexArray.append(notdefGI)
467 subHeader.glyphIndexArray.append(gid)
jvrbafa66e2003-08-28 18:04:23 +0000468 subHeader.entryCount = subHeader.entryCount + codeDiff + 1
jvrd299b552006-10-21 13:54:30 +0000469
470 # fix GI's and iDelta of last subheader that we we added to the subheader array.
471 self.setIDDelta(subHeader)
jvrbafa66e2003-08-28 18:04:23 +0000472
jvrd299b552006-10-21 13:54:30 +0000473 # Now we add a final subheader for the subHeaderKeys which maps to empty two byte charcode ranges.
jvrbafa66e2003-08-28 18:04:23 +0000474 subHeader = SubHeader()
475 subHeader.firstCode = 0
476 subHeader.entryCount = 0
477 subHeader.idDelta = 0
478 subHeader.idRangeOffset = 2
479 subHeaderList.append(subHeader)
480 emptySubheadIndex = len(subHeaderList) - 1
481 for index in range(256):
jvrd299b552006-10-21 13:54:30 +0000482 if subHeaderKeys[index] == kEmptyTwoCharCodeRange:
jvrbafa66e2003-08-28 18:04:23 +0000483 subHeaderKeys[index] = emptySubheadIndex
484 # Since this is the last subheader, the GlyphIndex Array starts two bytes after the start of the
jvrd299b552006-10-21 13:54:30 +0000485 # idRangeOffset word of this subHeader. We can safely point to the first entry in the GlyphIndexArray,
jvrbafa66e2003-08-28 18:04:23 +0000486 # since the first subrange of the GlyphIndexArray is for subHeader 0, which always starts with
487 # charcode 0 and GID 0.
488
jvrbafa66e2003-08-28 18:04:23 +0000489 idRangeOffset = (len(subHeaderList)-1)*8 + 2 # offset to beginning of glyphIDArray from first subheader idRangeOffset.
jvrd299b552006-10-21 13:54:30 +0000490 subheadRangeLen = len(subHeaderList) -1 # skip last special empty-set subheader; we've already hardocodes its idRangeOffset to 2.
491 for index in range(subheadRangeLen):
492 subHeader = subHeaderList[index]
493 subHeader.idRangeOffset = 0
494 for j in range(index):
495 prevSubhead = subHeaderList[j]
496 if prevSubhead.glyphIndexArray == subHeader.glyphIndexArray: # use the glyphIndexArray subarray
497 subHeader.idRangeOffset = prevSubhead.idRangeOffset - (index-j)*8
498 subHeader.glyphIndexArray = []
499 break
500 if subHeader.idRangeOffset == 0: # didn't find one.
501 subHeader.idRangeOffset = idRangeOffset
502 idRangeOffset = (idRangeOffset - 8) + subHeader.entryCount*2 # one less subheader, one more subArray.
503 else:
504 idRangeOffset = idRangeOffset - 8 # one less subheader
505
jvrbafa66e2003-08-28 18:04:23 +0000506 # Now we can write out the data!
507 length = 6 + 512 + 8*len(subHeaderList) # header, 256 subHeaderKeys, and subheader array.
508 for subhead in subHeaderList[:-1]:
jvrd299b552006-10-21 13:54:30 +0000509 length = length + len(subhead.glyphIndexArray)*2 # We can't use subhead.entryCount, as some of the subhead may share subArrays.
510 dataList = [struct.pack(">HHH", 2, length, self.language)]
jvrbafa66e2003-08-28 18:04:23 +0000511 for index in subHeaderKeys:
jvrd299b552006-10-21 13:54:30 +0000512 dataList.append(struct.pack(">H", index*8))
jvrbafa66e2003-08-28 18:04:23 +0000513 for subhead in subHeaderList:
jvrd299b552006-10-21 13:54:30 +0000514 dataList.append(struct.pack(subHeaderFormat, subhead.firstCode, subhead.entryCount, subhead.idDelta, subhead.idRangeOffset))
jvrbafa66e2003-08-28 18:04:23 +0000515 for subhead in subHeaderList[:-1]:
516 for gi in subhead.glyphIndexArray:
jvrd299b552006-10-21 13:54:30 +0000517 dataList.append(struct.pack(">H", gi))
Behdad Esfahbod18316aa2013-11-27 21:17:35 -0500518 data = bytesjoin(dataList)
jvrbafa66e2003-08-28 18:04:23 +0000519 assert (len(data) == length), "Error: cmap format 2 is not same length as calculated! actual: " + str(len(data))+ " calc : " + str(length)
520 return data
jvrd299b552006-10-21 13:54:30 +0000521
522
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500523 def fromXML(self, name, attrs, content, ttFont):
jvr0cd79a52004-09-25 07:30:47 +0000524 self.language = safeEval(attrs["language"])
jvrd299b552006-10-21 13:54:30 +0000525 if not hasattr(self, "cmap"):
526 self.cmap = {}
527 cmap = self.cmap
528
jvrbafa66e2003-08-28 18:04:23 +0000529 for element in content:
Behdad Esfahbodb774f9f2013-11-27 05:17:37 -0500530 if not isinstance(element, tuple):
jvrbafa66e2003-08-28 18:04:23 +0000531 continue
532 name, attrs, content = element
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500533 if name != "map":
jvrbafa66e2003-08-28 18:04:23 +0000534 continue
jvrd299b552006-10-21 13:54:30 +0000535 cmap[safeEval(attrs["code"])] = attrs["name"]
Just7842e561999-12-16 21:34:53 +0000536
537
538cmap_format_4_format = ">7H"
539
jvr1f8a4bb2002-07-23 07:51:23 +0000540#uint16 endCode[segCount] # Ending character code for each segment, last = 0xFFFF.
541#uint16 reservedPad # This value should be zero
542#uint16 startCode[segCount] # Starting character code for each segment
543#uint16 idDelta[segCount] # Delta for all character codes in segment
544#uint16 idRangeOffset[segCount] # Offset in bytes to glyph indexArray, or 0
545#uint16 glyphIndexArray[variable] # Glyph index array
Just7842e561999-12-16 21:34:53 +0000546
jvr542b9512002-07-20 21:57:26 +0000547def splitRange(startCode, endCode, cmap):
jvr1f8a4bb2002-07-23 07:51:23 +0000548 # Try to split a range of character codes into subranges with consecutive
549 # glyph IDs in such a way that the cmap4 subtable can be stored "most"
550 # efficiently. I can't prove I've got the optimal solution, but it seems
551 # to do well with the fonts I tested: none became bigger, many became smaller.
jvr542b9512002-07-20 21:57:26 +0000552 if startCode == endCode:
553 return [], [endCode]
554
jvr542b9512002-07-20 21:57:26 +0000555 lastID = cmap[startCode]
556 lastCode = startCode
557 inOrder = None
558 orderedBegin = None
jvr1f8a4bb2002-07-23 07:51:23 +0000559 subRanges = []
jvr542b9512002-07-20 21:57:26 +0000560
jvr1f8a4bb2002-07-23 07:51:23 +0000561 # Gather subranges in which the glyph IDs are consecutive.
jvr542b9512002-07-20 21:57:26 +0000562 for code in range(startCode + 1, endCode + 1):
563 glyphID = cmap[code]
jvr542b9512002-07-20 21:57:26 +0000564
565 if glyphID - 1 == lastID:
566 if inOrder is None or not inOrder:
567 inOrder = 1
568 orderedBegin = lastCode
569 else:
570 if inOrder:
571 inOrder = 0
jvr1f8a4bb2002-07-23 07:51:23 +0000572 subRanges.append((orderedBegin, lastCode))
jvr542b9512002-07-20 21:57:26 +0000573 orderedBegin = None
574
575 lastID = glyphID
576 lastCode = code
577
578 if inOrder:
jvr1f8a4bb2002-07-23 07:51:23 +0000579 subRanges.append((orderedBegin, lastCode))
jvr542b9512002-07-20 21:57:26 +0000580 assert lastCode == endCode
581
jvr1f8a4bb2002-07-23 07:51:23 +0000582 # Now filter out those new subranges that would only make the data bigger.
583 # A new segment cost 8 bytes, not using a new segment costs 2 bytes per
584 # character.
585 newRanges = []
586 for b, e in subRanges:
jvr542b9512002-07-20 21:57:26 +0000587 if b == startCode and e == endCode:
588 break # the whole range, we're fine
589 if b == startCode or e == endCode:
590 threshold = 4 # split costs one more segment
591 else:
592 threshold = 8 # split costs two more segments
593 if (e - b + 1) > threshold:
jvr1f8a4bb2002-07-23 07:51:23 +0000594 newRanges.append((b, e))
595 subRanges = newRanges
jvr542b9512002-07-20 21:57:26 +0000596
jvr1f8a4bb2002-07-23 07:51:23 +0000597 if not subRanges:
jvr542b9512002-07-20 21:57:26 +0000598 return [], [endCode]
599
jvr1f8a4bb2002-07-23 07:51:23 +0000600 if subRanges[0][0] != startCode:
601 subRanges.insert(0, (startCode, subRanges[0][0] - 1))
602 if subRanges[-1][1] != endCode:
603 subRanges.append((subRanges[-1][1] + 1, endCode))
604
605 # Fill the "holes" in the segments list -- those are the segments in which
606 # the glyph IDs are _not_ consecutive.
jvr542b9512002-07-20 21:57:26 +0000607 i = 1
jvr1f8a4bb2002-07-23 07:51:23 +0000608 while i < len(subRanges):
609 if subRanges[i-1][1] + 1 != subRanges[i][0]:
610 subRanges.insert(i, (subRanges[i-1][1] + 1, subRanges[i][0] - 1))
jvr542b9512002-07-20 21:57:26 +0000611 i = i + 1
612 i = i + 1
613
jvr1f8a4bb2002-07-23 07:51:23 +0000614 # Transform the ranges into startCode/endCode lists.
jvr542b9512002-07-20 21:57:26 +0000615 start = []
616 end = []
jvr1f8a4bb2002-07-23 07:51:23 +0000617 for b, e in subRanges:
jvr542b9512002-07-20 21:57:26 +0000618 start.append(b)
619 end.append(e)
620 start.pop(0)
621
622 assert len(start) + 1 == len(end)
623 return start, end
624
625
Just7842e561999-12-16 21:34:53 +0000626class cmap_format_4(CmapSubtable):
627
628 def decompile(self, data, ttFont):
jvrd299b552006-10-21 13:54:30 +0000629 # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None.
630 # If not, someone is calling the subtable decompile() directly, and must provide both args.
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500631 if data is not None and ttFont is not None:
jvrd299b552006-10-21 13:54:30 +0000632 self.decompileHeader(self.data[offset:offset+int(length)], ttFont)
633 else:
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500634 assert (data is None and ttFont is None), "Need both data and ttFont arguments"
jvrd299b552006-10-21 13:54:30 +0000635
636 data = self.data # decompileHeader assigns the data after the header to self.data
637 (segCountX2, searchRange, entrySelector, rangeShift) = \
638 struct.unpack(">4H", data[:8])
639 data = data[8:]
Behdad Esfahbod32c10ee2013-11-27 17:46:17 -0500640 segCount = segCountX2 // 2
Just7842e561999-12-16 21:34:53 +0000641
jvr542b9512002-07-20 21:57:26 +0000642 allCodes = array.array("H")
jvrd299b552006-10-21 13:54:30 +0000643 allCodes.fromstring(data)
644 self.data = data = None
645
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500646 if sys.byteorder != "big":
jvr542b9512002-07-20 21:57:26 +0000647 allCodes.byteswap()
Just7842e561999-12-16 21:34:53 +0000648
649 # divide the data
jvr542b9512002-07-20 21:57:26 +0000650 endCode = allCodes[:segCount]
651 allCodes = allCodes[segCount+1:] # the +1 is skipping the reservedPad field
652 startCode = allCodes[:segCount]
653 allCodes = allCodes[segCount:]
654 idDelta = allCodes[:segCount]
655 allCodes = allCodes[segCount:]
656 idRangeOffset = allCodes[:segCount]
657 glyphIndexArray = allCodes[segCount:]
jvrd299b552006-10-21 13:54:30 +0000658 lenGIArray = len(glyphIndexArray)
659
Just7842e561999-12-16 21:34:53 +0000660 # build 2-byte character mapping
jvrd299b552006-10-21 13:54:30 +0000661 charCodes = []
662 gids = []
Just7842e561999-12-16 21:34:53 +0000663 for i in range(len(startCode) - 1): # don't do 0xffff!
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -0500664 rangeCharCodes = list(range(startCode[i], endCode[i] + 1))
jvrd299b552006-10-21 13:54:30 +0000665 charCodes = charCodes + rangeCharCodes
666 for charCode in rangeCharCodes:
Just7842e561999-12-16 21:34:53 +0000667 rangeOffset = idRangeOffset[i]
668 if rangeOffset == 0:
669 glyphID = charCode + idDelta[i]
670 else:
671 # *someone* needs to get killed.
Behdad Esfahbod32c10ee2013-11-27 17:46:17 -0500672 index = idRangeOffset[i] // 2 + (charCode - startCode[i]) + i - len(idRangeOffset)
jvrd299b552006-10-21 13:54:30 +0000673 assert (index < lenGIArray), "In format 4 cmap, range (%d), the calculated index (%d) into the glyph index array is not less than the length of the array (%d) !" % (i, index, lenGIArray)
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500674 if glyphIndexArray[index] != 0: # if not missing glyph
Just7842e561999-12-16 21:34:53 +0000675 glyphID = glyphIndexArray[index] + idDelta[i]
676 else:
677 glyphID = 0 # missing glyph
jvrd299b552006-10-21 13:54:30 +0000678 gids.append(glyphID % 0x10000)
679
680 self.cmap = cmap = {}
681 lenCmap = len(gids)
682 glyphOrder = self.ttFont.getGlyphOrder()
683 try:
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500684 names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids ))
jvrd299b552006-10-21 13:54:30 +0000685 except IndexError:
686 getGlyphName = self.ttFont.getGlyphName
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500687 names = list(map(getGlyphName, gids ))
688 list(map(operator.setitem, [cmap]*lenCmap, charCodes, names))
jvrd299b552006-10-21 13:54:30 +0000689
690
691
692 def setIDDelta(self, idDelta):
693 # The lowest gid in glyphIndexArray, after subtracting idDelta, must be 1.
694 # idDelta is a short, and must be between -32K and 32K
695 # startCode can be between 0 and 64K-1, and the first glyph index can be between 1 and 64K-1
696 # This means that we have a problem because we can need to assign to idDelta values
697 # between -(64K-2) and 64K -1.
698 # Since the final gi is reconstructed from the glyphArray GID by:
699 # (short)finalGID = (gid + idDelta) % 0x10000),
700 # we can get from a startCode of 0 to a final GID of 64 -1K by subtracting 1, and casting the
701 # negative number to an unsigned short.
702 # Similarly , we can get from a startCode of 64K-1 to a final GID of 1 by adding 2, because of
703 # the modulo arithmetic.
704
705 if idDelta > 0x7FFF:
706 idDelta = idDelta - 0x10000
707 elif idDelta < -0x7FFF:
708 idDelta = idDelta + 0x10000
709
710 return idDelta
711
712
Just7842e561999-12-16 21:34:53 +0000713 def compile(self, ttFont):
jvrd299b552006-10-21 13:54:30 +0000714 if self.data:
715 return struct.pack(">HHH", self.format, self.length, self.language) + self.data
716
jvrea9dfa92002-05-12 17:14:50 +0000717 from fontTools.ttLib.sfnt import maxPowerOfTwo
Just7842e561999-12-16 21:34:53 +0000718
Behdad Esfahbodc2297cd2013-11-27 06:26:55 -0500719 charCodes = list(self.cmap.keys())
jvrd299b552006-10-21 13:54:30 +0000720 lenCharCodes = len(charCodes)
721 if lenCharCodes == 0:
722 startCode = [0xffff]
723 endCode = [0xffff]
724 else:
jvr2db352c2008-02-29 14:43:49 +0000725 charCodes.sort()
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500726 names = list(map(operator.getitem, [self.cmap]*lenCharCodes, charCodes))
jvrd299b552006-10-21 13:54:30 +0000727 nameMap = ttFont.getReverseGlyphMap()
728 try:
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500729 gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names))
jvrd299b552006-10-21 13:54:30 +0000730 except KeyError:
Behdad Esfahboddc873722013-12-04 21:28:50 -0500731 nameMap = ttFont.getReverseGlyphMap(rebuild=True)
jvrd299b552006-10-21 13:54:30 +0000732 try:
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500733 gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names))
jvrd299b552006-10-21 13:54:30 +0000734 except KeyError:
735 # allow virtual GIDs in format 4 tables
736 gids = []
737 for name in names:
738 try:
739 gid = nameMap[name]
740 except KeyError:
741 try:
742 if (name[:3] == 'gid'):
743 gid = eval(name[3:])
744 else:
745 gid = ttFont.getGlyphID(name)
746 except:
747 raise KeyError(name)
748
749 gids.append(gid)
750 cmap = {} # code:glyphID mapping
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500751 list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids))
Just7842e561999-12-16 21:34:53 +0000752
jvrd299b552006-10-21 13:54:30 +0000753 # Build startCode and endCode lists.
754 # Split the char codes in ranges of consecutive char codes, then split
755 # each range in more ranges of consecutive/not consecutive glyph IDs.
756 # See splitRange().
757 lastCode = charCodes[0]
758 endCode = []
759 startCode = [lastCode]
760 for charCode in charCodes[1:]: # skip the first code, it's the first start code
761 if charCode == lastCode + 1:
762 lastCode = charCode
763 continue
764 start, end = splitRange(startCode[-1], lastCode, cmap)
765 startCode.extend(start)
766 endCode.extend(end)
767 startCode.append(charCode)
jvr542b9512002-07-20 21:57:26 +0000768 lastCode = charCode
jvrd299b552006-10-21 13:54:30 +0000769 endCode.append(lastCode)
770 startCode.append(0xffff)
771 endCode.append(0xffff)
Just7842e561999-12-16 21:34:53 +0000772
jvr542b9512002-07-20 21:57:26 +0000773 # build up rest of cruft
Just7842e561999-12-16 21:34:53 +0000774 idDelta = []
775 idRangeOffset = []
776 glyphIndexArray = []
Just7842e561999-12-16 21:34:53 +0000777 for i in range(len(endCode)-1): # skip the closing codes (0xffff)
778 indices = []
jvr542b9512002-07-20 21:57:26 +0000779 for charCode in range(startCode[i], endCode[i] + 1):
780 indices.append(cmap[charCode])
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -0500781 if (indices == list(range(indices[0], indices[0] + len(indices)))):
jvrd299b552006-10-21 13:54:30 +0000782 idDeltaTemp = self.setIDDelta(indices[0] - startCode[i])
783 idDelta.append( idDeltaTemp)
Just7842e561999-12-16 21:34:53 +0000784 idRangeOffset.append(0)
785 else:
786 # someone *definitely* needs to get killed.
787 idDelta.append(0)
788 idRangeOffset.append(2 * (len(endCode) + len(glyphIndexArray) - i))
jvr542b9512002-07-20 21:57:26 +0000789 glyphIndexArray.extend(indices)
Just7842e561999-12-16 21:34:53 +0000790 idDelta.append(1) # 0xffff + 1 == (tadaa!) 0. So this end code maps to .notdef
791 idRangeOffset.append(0)
792
793 # Insane.
794 segCount = len(endCode)
795 segCountX2 = segCount * 2
jvr542b9512002-07-20 21:57:26 +0000796 maxExponent = maxPowerOfTwo(segCount)
797 searchRange = 2 * (2 ** maxExponent)
798 entrySelector = maxExponent
Just7842e561999-12-16 21:34:53 +0000799 rangeShift = 2 * segCount - searchRange
800
Behdad Esfahbod8da82422013-08-16 12:56:08 -0400801 charCodeArray = array.array("H", endCode + [0] + startCode)
802 idDeltaeArray = array.array("h", idDelta)
803 restArray = array.array("H", idRangeOffset + glyphIndexArray)
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500804 if sys.byteorder != "big":
Behdad Esfahbod8da82422013-08-16 12:56:08 -0400805 charCodeArray.byteswap()
806 idDeltaeArray.byteswap()
807 restArray.byteswap()
jvrd299b552006-10-21 13:54:30 +0000808 data = charCodeArray.tostring() + idDeltaeArray.tostring() + restArray.tostring()
809
Just7842e561999-12-16 21:34:53 +0000810 length = struct.calcsize(cmap_format_4_format) + len(data)
jvr0cd79a52004-09-25 07:30:47 +0000811 header = struct.pack(cmap_format_4_format, self.format, length, self.language,
Just7842e561999-12-16 21:34:53 +0000812 segCountX2, searchRange, entrySelector, rangeShift)
jvrd299b552006-10-21 13:54:30 +0000813 return header + data
Just7842e561999-12-16 21:34:53 +0000814
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500815 def fromXML(self, name, attrs, content, ttFont):
jvr0cd79a52004-09-25 07:30:47 +0000816 self.language = safeEval(attrs["language"])
jvrd299b552006-10-21 13:54:30 +0000817 if not hasattr(self, "cmap"):
818 self.cmap = {}
819 cmap = self.cmap
820
Just7842e561999-12-16 21:34:53 +0000821 for element in content:
Behdad Esfahbodb774f9f2013-11-27 05:17:37 -0500822 if not isinstance(element, tuple):
Just7842e561999-12-16 21:34:53 +0000823 continue
jvrd299b552006-10-21 13:54:30 +0000824 nameMap, attrsMap, dummyContent = element
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500825 if nameMap != "map":
jvrd299b552006-10-21 13:54:30 +0000826 assert 0, "Unrecognized keyword in cmap subtable"
827 cmap[safeEval(attrsMap["code"])] = attrsMap["name"]
Just7842e561999-12-16 21:34:53 +0000828
829
830class cmap_format_6(CmapSubtable):
831
832 def decompile(self, data, ttFont):
jvrd299b552006-10-21 13:54:30 +0000833 # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None.
834 # If not, someone is calling the subtable decompile() directly, and must provide both args.
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500835 if data is not None and ttFont is not None:
jvrd299b552006-10-21 13:54:30 +0000836 self.decompileHeader(data[offset:offset+int(length)], ttFont)
837 else:
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500838 assert (data is None and ttFont is None), "Need both data and ttFont arguments"
jvrd299b552006-10-21 13:54:30 +0000839
840 data = self.data # decompileHeader assigns the data after the header to self.data
841 firstCode, entryCount = struct.unpack(">HH", data[:4])
Just7842e561999-12-16 21:34:53 +0000842 firstCode = int(firstCode)
jvrd299b552006-10-21 13:54:30 +0000843 data = data[4:]
Justf6b15632000-08-23 12:33:14 +0000844 #assert len(data) == 2 * entryCount # XXX not true in Apple's Helvetica!!!
Just7842e561999-12-16 21:34:53 +0000845 glyphIndexArray = array.array("H")
Just43fa4be2000-10-11 18:04:03 +0000846 glyphIndexArray.fromstring(data[:2 * int(entryCount)])
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500847 if sys.byteorder != "big":
Just7842e561999-12-16 21:34:53 +0000848 glyphIndexArray.byteswap()
jvrd299b552006-10-21 13:54:30 +0000849 self.data = data = None
850
Just7842e561999-12-16 21:34:53 +0000851 self.cmap = cmap = {}
jvrd299b552006-10-21 13:54:30 +0000852
853 lenArray = len(glyphIndexArray)
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -0500854 charCodes = list(range(firstCode, firstCode + lenArray))
jvrd299b552006-10-21 13:54:30 +0000855 glyphOrder = self.ttFont.getGlyphOrder()
856 try:
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500857 names = list(map(operator.getitem, [glyphOrder]*lenArray, glyphIndexArray ))
jvrd299b552006-10-21 13:54:30 +0000858 except IndexError:
859 getGlyphName = self.ttFont.getGlyphName
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500860 names = list(map(getGlyphName, glyphIndexArray ))
861 list(map(operator.setitem, [cmap]*lenArray, charCodes, names))
Just7842e561999-12-16 21:34:53 +0000862
863 def compile(self, ttFont):
jvrd299b552006-10-21 13:54:30 +0000864 if self.data:
865 return struct.pack(">HHH", self.format, self.length, self.language) + self.data
866 cmap = self.cmap
Behdad Esfahbodc2297cd2013-11-27 06:26:55 -0500867 codes = list(cmap.keys())
jvrd299b552006-10-21 13:54:30 +0000868 if codes: # yes, there are empty cmap tables.
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -0500869 codes = list(range(codes[0], codes[-1] + 1))
jvrd299b552006-10-21 13:54:30 +0000870 firstCode = codes[0]
Behdad Esfahbod13a08d02013-11-26 15:49:36 -0500871 valueList = [cmap.get(code, ".notdef") for code in codes]
jvrd299b552006-10-21 13:54:30 +0000872 valueList = map(ttFont.getGlyphID, valueList)
Behdad Esfahbod8da82422013-08-16 12:56:08 -0400873 glyphIndexArray = array.array("H", valueList)
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500874 if sys.byteorder != "big":
Behdad Esfahbod8da82422013-08-16 12:56:08 -0400875 glyphIndexArray.byteswap()
jvrd299b552006-10-21 13:54:30 +0000876 data = glyphIndexArray.tostring()
877 else:
Behdad Esfahbod5f6418d2013-11-27 22:00:49 -0500878 data = b""
jvrd299b552006-10-21 13:54:30 +0000879 firstCode = 0
Just7842e561999-12-16 21:34:53 +0000880 header = struct.pack(">HHHHH",
jvrd299b552006-10-21 13:54:30 +0000881 6, len(data) + 10, self.language, firstCode, len(codes))
Just7842e561999-12-16 21:34:53 +0000882 return header + data
883
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -0500884 def fromXML(self, name, attrs, content, ttFont):
jvr0cd79a52004-09-25 07:30:47 +0000885 self.language = safeEval(attrs["language"])
jvrd299b552006-10-21 13:54:30 +0000886 if not hasattr(self, "cmap"):
887 self.cmap = {}
888 cmap = self.cmap
889
Just7842e561999-12-16 21:34:53 +0000890 for element in content:
Behdad Esfahbodb774f9f2013-11-27 05:17:37 -0500891 if not isinstance(element, tuple):
Just7842e561999-12-16 21:34:53 +0000892 continue
893 name, attrs, content = element
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500894 if name != "map":
Just7842e561999-12-16 21:34:53 +0000895 continue
jvrd299b552006-10-21 13:54:30 +0000896 cmap[safeEval(attrs["code"])] = attrs["name"]
Just7842e561999-12-16 21:34:53 +0000897
898
Roozbeh Pournader51a17822013-10-09 15:55:07 -0700899class cmap_format_12_or_13(CmapSubtable):
jvr924e4e22003-02-08 10:45:23 +0000900
jvrd299b552006-10-21 13:54:30 +0000901 def __init__(self, format):
902 self.format = format
903 self.reserved = 0
904 self.data = None
905 self.ttFont = None
906
907 def decompileHeader(self, data, ttFont):
jvr924e4e22003-02-08 10:45:23 +0000908 format, reserved, length, language, nGroups = struct.unpack(">HHLLL", data[:16])
Roozbeh Pournader51a17822013-10-09 15:55:07 -0700909 assert len(data) == (16 + nGroups*12) == (length), "corrupt cmap table format %d (data length: %d, header length: %d)" % (format, len(data), length)
jvr924e4e22003-02-08 10:45:23 +0000910 self.format = format
911 self.reserved = reserved
912 self.length = length
913 self.language = language
914 self.nGroups = nGroups
jvrd299b552006-10-21 13:54:30 +0000915 self.data = data[16:]
916 self.ttFont = ttFont
917
918 def decompile(self, data, ttFont):
919 # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None.
920 # If not, someone is calling the subtable decompile() directly, and must provide both args.
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500921 if data is not None and ttFont is not None:
jvrd299b552006-10-21 13:54:30 +0000922 self.decompileHeader(data[offset:offset+int(length)], ttFont)
923 else:
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500924 assert (data is None and ttFont is None), "Need both data and ttFont arguments"
jvrd299b552006-10-21 13:54:30 +0000925
926 data = self.data # decompileHeader assigns the data after the header to self.data
927 charCodes = []
928 gids = []
929 pos = 0
930 for i in range(self.nGroups):
931 startCharCode, endCharCode, glyphID = struct.unpack(">LLL",data[pos:pos+12] )
932 pos += 12
933 lenGroup = 1 + endCharCode - startCharCode
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -0500934 charCodes += list(range(startCharCode, endCharCode +1))
Roozbeh Pournader51a17822013-10-09 15:55:07 -0700935 gids += self._computeGIDs(glyphID, lenGroup)
jvrd299b552006-10-21 13:54:30 +0000936 self.data = data = None
937 self.cmap = cmap = {}
938 lenCmap = len(gids)
939 glyphOrder = self.ttFont.getGlyphOrder()
940 try:
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500941 names = list(map(operator.getitem, [glyphOrder]*lenCmap, gids ))
jvrd299b552006-10-21 13:54:30 +0000942 except IndexError:
943 getGlyphName = self.ttFont.getGlyphName
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500944 names = list(map(getGlyphName, gids ))
945 list(map(operator.setitem, [cmap]*lenCmap, charCodes, names))
jvr924e4e22003-02-08 10:45:23 +0000946
947 def compile(self, ttFont):
jvrd299b552006-10-21 13:54:30 +0000948 if self.data:
Roozbeh Pournader51a17822013-10-09 15:55:07 -0700949 return struct.pack(">HHLLL", self.format, self.reserved, self.length, self.language, self.nGroups) + self.data
Behdad Esfahbodc2297cd2013-11-27 06:26:55 -0500950 charCodes = list(self.cmap.keys())
jvrd299b552006-10-21 13:54:30 +0000951 lenCharCodes = len(charCodes)
Behdad Esfahbodc2297cd2013-11-27 06:26:55 -0500952 names = list(self.cmap.values())
jvrd299b552006-10-21 13:54:30 +0000953 nameMap = ttFont.getReverseGlyphMap()
954 try:
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500955 gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names))
jvrd299b552006-10-21 13:54:30 +0000956 except KeyError:
Behdad Esfahboddc873722013-12-04 21:28:50 -0500957 nameMap = ttFont.getReverseGlyphMap(rebuild=True)
jvrd299b552006-10-21 13:54:30 +0000958 try:
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500959 gids = list(map(operator.getitem, [nameMap]*lenCharCodes, names))
jvrd299b552006-10-21 13:54:30 +0000960 except KeyError:
961 # allow virtual GIDs in format 12 tables
962 gids = []
963 for name in names:
964 try:
965 gid = nameMap[name]
966 except KeyError:
967 try:
968 if (name[:3] == 'gid'):
969 gid = eval(name[3:])
970 else:
971 gid = ttFont.getGlyphID(name)
972 except:
973 raise KeyError(name)
974
975 gids.append(gid)
976
977 cmap = {} # code:glyphID mapping
Behdad Esfahbode5ca7962013-11-27 04:38:16 -0500978 list(map(operator.setitem, [cmap]*len(charCodes), charCodes, gids))
jvrd299b552006-10-21 13:54:30 +0000979
jvr924e4e22003-02-08 10:45:23 +0000980 charCodes.sort()
jvrd299b552006-10-21 13:54:30 +0000981 index = 0
jvr924e4e22003-02-08 10:45:23 +0000982 startCharCode = charCodes[0]
983 startGlyphID = cmap[startCharCode]
Roozbeh Pournader51a17822013-10-09 15:55:07 -0700984 lastGlyphID = startGlyphID - self._format_step
jvrd299b552006-10-21 13:54:30 +0000985 lastCharCode = startCharCode - 1
jvr0cd79a52004-09-25 07:30:47 +0000986 nGroups = 0
jvrd299b552006-10-21 13:54:30 +0000987 dataList = []
988 maxIndex = len(charCodes)
989 for index in range(maxIndex):
990 charCode = charCodes[index]
jvr924e4e22003-02-08 10:45:23 +0000991 glyphID = cmap[charCode]
Roozbeh Pournader51a17822013-10-09 15:55:07 -0700992 if not self._IsInSameRun(glyphID, lastGlyphID, charCode, lastCharCode):
jvrd299b552006-10-21 13:54:30 +0000993 dataList.append(struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID))
jvr924e4e22003-02-08 10:45:23 +0000994 startCharCode = charCode
jvrd299b552006-10-21 13:54:30 +0000995 startGlyphID = glyphID
jvr924e4e22003-02-08 10:45:23 +0000996 nGroups = nGroups + 1
jvrd299b552006-10-21 13:54:30 +0000997 lastGlyphID = glyphID
998 lastCharCode = charCode
999 dataList.append(struct.pack(">LLL", startCharCode, lastCharCode, startGlyphID))
jvr0cd79a52004-09-25 07:30:47 +00001000 nGroups = nGroups + 1
Behdad Esfahbod18316aa2013-11-27 21:17:35 -05001001 data = bytesjoin(dataList)
jvrd299b552006-10-21 13:54:30 +00001002 lengthSubtable = len(data) +16
1003 assert len(data) == (nGroups*12) == (lengthSubtable-16)
1004 return struct.pack(">HHLLL", self.format, self.reserved , lengthSubtable, self.language, nGroups) + data
jvr924e4e22003-02-08 10:45:23 +00001005
1006 def toXML(self, writer, ttFont):
1007 writer.begintag(self.__class__.__name__, [
1008 ("platformID", self.platformID),
1009 ("platEncID", self.platEncID),
1010 ("format", self.format),
1011 ("reserved", self.reserved),
1012 ("length", self.length),
1013 ("language", self.language),
1014 ("nGroups", self.nGroups),
1015 ])
1016 writer.newline()
Behdad Esfahbodac1b4352013-11-27 04:15:34 -05001017 codes = sorted(self.cmap.items())
jvra84b28d2004-09-25 09:06:58 +00001018 self._writeCodes(codes, writer)
jvr924e4e22003-02-08 10:45:23 +00001019 writer.endtag(self.__class__.__name__)
1020 writer.newline()
1021
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -05001022 def fromXML(self, name, attrs, content, ttFont):
jvrd299b552006-10-21 13:54:30 +00001023 self.format = safeEval(attrs["format"])
1024 self.reserved = safeEval(attrs["reserved"])
1025 self.length = safeEval(attrs["length"])
jvr924e4e22003-02-08 10:45:23 +00001026 self.language = safeEval(attrs["language"])
jvrd299b552006-10-21 13:54:30 +00001027 self.nGroups = safeEval(attrs["nGroups"])
1028 if not hasattr(self, "cmap"):
1029 self.cmap = {}
1030 cmap = self.cmap
1031
jvr924e4e22003-02-08 10:45:23 +00001032 for element in content:
Behdad Esfahbodb774f9f2013-11-27 05:17:37 -05001033 if not isinstance(element, tuple):
jvr924e4e22003-02-08 10:45:23 +00001034 continue
1035 name, attrs, content = element
Behdad Esfahbod180ace62013-11-27 02:40:30 -05001036 if name != "map":
jvr924e4e22003-02-08 10:45:23 +00001037 continue
jvrd299b552006-10-21 13:54:30 +00001038 cmap[safeEval(attrs["code"])] = attrs["name"]
jvr924e4e22003-02-08 10:45:23 +00001039
1040
Roozbeh Pournader51a17822013-10-09 15:55:07 -07001041class cmap_format_12(cmap_format_12_or_13):
1042 def __init__(self, format):
1043 cmap_format_12_or_13.__init__(self, format)
1044 self._format_step = 1
1045
1046 def _computeGIDs(self, startingGlyph, numberOfGlyphs):
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -05001047 return list(range(startingGlyph, startingGlyph + numberOfGlyphs))
Roozbeh Pournader51a17822013-10-09 15:55:07 -07001048
1049 def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode):
1050 return (glyphID == 1 + lastGlyphID) and (charCode == 1 + lastCharCode)
1051
1052
1053class cmap_format_13(cmap_format_12_or_13):
1054 def __init__(self, format):
1055 cmap_format_12_or_13.__init__(self, format)
1056 self._format_step = 0
1057
1058 def _computeGIDs(self, startingGlyph, numberOfGlyphs):
1059 return [startingGlyph] * numberOfGlyphs
1060
1061 def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode):
1062 return (glyphID == lastGlyphID) and (charCode == 1 + lastCharCode)
1063
1064
jvr0cb8a082008-05-16 15:07:09 +00001065def cvtToUVS(threeByteString):
Behdad Esfahbod2242b262013-11-28 06:35:12 -05001066 data = b"\0" + threeByteString
jvr0cb8a082008-05-16 15:07:09 +00001067 val, = struct.unpack(">L", data)
1068 return val
1069
1070def cvtFromUVS(val):
Behdad Esfahbod2242b262013-11-28 06:35:12 -05001071 assert 0 <= val < 0x1000000
1072 fourByteString = struct.pack(">L", val)
1073 return fourByteString[1:]
jvr0cb8a082008-05-16 15:07:09 +00001074
Behdad Esfahbodb7fd2e12013-11-27 18:58:45 -05001075
jvr0cb8a082008-05-16 15:07:09 +00001076class cmap_format_14(CmapSubtable):
1077
1078 def decompileHeader(self, data, ttFont):
1079 format, length, numVarSelectorRecords = struct.unpack(">HLL", data[:10])
1080 self.data = data[10:]
1081 self.length = length
1082 self.numVarSelectorRecords = numVarSelectorRecords
1083 self.ttFont = ttFont
1084 self.language = 0xFF # has no language.
1085
1086 def decompile(self, data, ttFont):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -05001087 if data is not None and ttFont is not None and ttFont.lazy:
jvr0cb8a082008-05-16 15:07:09 +00001088 self.decompileHeader(data, ttFont)
1089 else:
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -05001090 assert (data is None and ttFont is None), "Need both data and ttFont arguments"
jvr0cb8a082008-05-16 15:07:09 +00001091 data = self.data
1092
1093 self.cmap = {} # so that clients that expect this to exist in a cmap table won't fail.
1094 uvsDict = {}
1095 recOffset = 0
1096 for n in range(self.numVarSelectorRecords):
1097 uvs, defOVSOffset, nonDefUVSOffset = struct.unpack(">3sLL", data[recOffset:recOffset +11])
1098 recOffset += 11
1099 varUVS = cvtToUVS(uvs)
1100 if defOVSOffset:
1101 startOffset = defOVSOffset - 10
1102 numValues, = struct.unpack(">L", data[startOffset:startOffset+4])
1103 startOffset +=4
1104 for r in range(numValues):
1105 uv, addtlCnt = struct.unpack(">3sB", data[startOffset:startOffset+4])
1106 startOffset += 4
1107 firstBaseUV = cvtToUVS(uv)
1108 cnt = addtlCnt+1
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -05001109 baseUVList = list(range(firstBaseUV, firstBaseUV+cnt))
jvr0cb8a082008-05-16 15:07:09 +00001110 glyphList = [None]*cnt
1111 localUVList = zip(baseUVList, glyphList)
1112 try:
1113 uvsDict[varUVS].extend(localUVList)
1114 except KeyError:
Behdad Esfahbodfa5f2e82013-11-27 04:13:15 -05001115 uvsDict[varUVS] = list(localUVList)
jvr0cb8a082008-05-16 15:07:09 +00001116
1117 if nonDefUVSOffset:
1118 startOffset = nonDefUVSOffset - 10
1119 numRecs, = struct.unpack(">L", data[startOffset:startOffset+4])
1120 startOffset +=4
1121 localUVList = []
1122 for r in range(numRecs):
1123 uv, gid = struct.unpack(">3sH", data[startOffset:startOffset+5])
1124 startOffset += 5
1125 uv = cvtToUVS(uv)
1126 glyphName = self.ttFont.getGlyphName(gid)
1127 localUVList.append( [uv, glyphName] )
1128 try:
1129 uvsDict[varUVS].extend(localUVList)
1130 except KeyError:
1131 uvsDict[varUVS] = localUVList
1132
1133 self.uvsDict = uvsDict
1134
1135 def toXML(self, writer, ttFont):
1136 writer.begintag(self.__class__.__name__, [
1137 ("platformID", self.platformID),
1138 ("platEncID", self.platEncID),
1139 ("format", self.format),
1140 ("length", self.length),
1141 ("numVarSelectorRecords", self.numVarSelectorRecords),
1142 ])
1143 writer.newline()
1144 uvsDict = self.uvsDict
Behdad Esfahbodac1b4352013-11-27 04:15:34 -05001145 uvsList = sorted(uvsDict.keys())
jvr0cb8a082008-05-16 15:07:09 +00001146 for uvs in uvsList:
1147 uvList = uvsDict[uvs]
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -05001148 uvList.sort(key=lambda item: (item[1] is not None, item[0], item[1]))
jvr0cb8a082008-05-16 15:07:09 +00001149 for uv, gname in uvList:
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -05001150 if gname is None:
jvr0cb8a082008-05-16 15:07:09 +00001151 gname = "None"
1152 # I use the arg rather than th keyword syntax in order to preserve the attribute order.
1153 writer.simpletag("map", [ ("uvs",hex(uvs)), ("uv",hex(uv)), ("name", gname)] )
1154 writer.newline()
1155 writer.endtag(self.__class__.__name__)
1156 writer.newline()
1157
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -05001158 def fromXML(self, name, attrs, content, ttFont):
jvr0cb8a082008-05-16 15:07:09 +00001159 self.format = safeEval(attrs["format"])
1160 self.length = safeEval(attrs["length"])
1161 self.numVarSelectorRecords = safeEval(attrs["numVarSelectorRecords"])
Behdad Esfahbodb7fd2e12013-11-27 18:58:45 -05001162 self.language = 0xFF # provide a value so that CmapSubtable.__lt__() won't fail
jvr0cb8a082008-05-16 15:07:09 +00001163 if not hasattr(self, "cmap"):
1164 self.cmap = {} # so that clients that expect this to exist in a cmap table won't fail.
1165 if not hasattr(self, "uvsDict"):
1166 self.uvsDict = {}
1167 uvsDict = self.uvsDict
1168
1169 for element in content:
Behdad Esfahbodb774f9f2013-11-27 05:17:37 -05001170 if not isinstance(element, tuple):
jvr0cb8a082008-05-16 15:07:09 +00001171 continue
1172 name, attrs, content = element
Behdad Esfahbod180ace62013-11-27 02:40:30 -05001173 if name != "map":
jvr0cb8a082008-05-16 15:07:09 +00001174 continue
1175 uvs = safeEval(attrs["uvs"])
1176 uv = safeEval(attrs["uv"])
1177 gname = attrs["name"]
1178 if gname == "None":
1179 gname = None
1180 try:
1181 uvsDict[uvs].append( [uv, gname])
1182 except KeyError:
1183 uvsDict[uvs] = [ [uv, gname] ]
1184
1185
1186 def compile(self, ttFont):
1187 if self.data:
1188 return struct.pack(">HLL", self.format, self.length , self.numVarSelectorRecords) + self.data
1189
1190 uvsDict = self.uvsDict
Behdad Esfahbodac1b4352013-11-27 04:15:34 -05001191 uvsList = sorted(uvsDict.keys())
jvr0cb8a082008-05-16 15:07:09 +00001192 self.numVarSelectorRecords = len(uvsList)
1193 offset = 10 + self.numVarSelectorRecords*11 # current value is end of VarSelectorRecords block.
1194 data = []
1195 varSelectorRecords =[]
1196 for uvs in uvsList:
1197 entryList = uvsDict[uvs]
1198
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -05001199 defList = [entry for entry in entryList if entry[1] is None]
jvr0cb8a082008-05-16 15:07:09 +00001200 if defList:
Behdad Esfahbode5ca7962013-11-27 04:38:16 -05001201 defList = [entry[0] for entry in defList]
jvr0cb8a082008-05-16 15:07:09 +00001202 defOVSOffset = offset
1203 defList.sort()
1204
1205 lastUV = defList[0]
1206 cnt = -1
1207 defRecs = []
1208 for defEntry in defList:
1209 cnt +=1
1210 if (lastUV+cnt) != defEntry:
1211 rec = struct.pack(">3sB", cvtFromUVS(lastUV), cnt-1)
1212 lastUV = defEntry
1213 defRecs.append(rec)
1214 cnt = 0
1215
1216 rec = struct.pack(">3sB", cvtFromUVS(lastUV), cnt)
1217 defRecs.append(rec)
1218
1219 numDefRecs = len(defRecs)
1220 data.append(struct.pack(">L", numDefRecs))
1221 data.extend(defRecs)
1222 offset += 4 + numDefRecs*4
1223 else:
1224 defOVSOffset = 0
1225
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -05001226 ndefList = [entry for entry in entryList if entry[1] is not None]
jvr0cb8a082008-05-16 15:07:09 +00001227 if ndefList:
1228 nonDefUVSOffset = offset
1229 ndefList.sort()
1230 numNonDefRecs = len(ndefList)
1231 data.append(struct.pack(">L", numNonDefRecs))
1232 offset += 4 + numNonDefRecs*5
1233
1234 for uv, gname in ndefList:
1235 gid = ttFont.getGlyphID(gname)
1236 ndrec = struct.pack(">3sH", cvtFromUVS(uv), gid)
1237 data.append(ndrec)
1238 else:
1239 nonDefUVSOffset = 0
1240
1241 vrec = struct.pack(">3sLL", cvtFromUVS(uvs), defOVSOffset, nonDefUVSOffset)
1242 varSelectorRecords.append(vrec)
1243
Behdad Esfahbod18316aa2013-11-27 21:17:35 -05001244 data = bytesjoin(varSelectorRecords) + bytesjoin(data)
jvr0cb8a082008-05-16 15:07:09 +00001245 self.length = 10 + len(data)
1246 headerdata = struct.pack(">HLL", self.format, self.length , self.numVarSelectorRecords)
1247 self.data = headerdata + data
1248
1249 return self.data
1250
1251
Just7842e561999-12-16 21:34:53 +00001252class cmap_format_unknown(CmapSubtable):
1253
jvra84b28d2004-09-25 09:06:58 +00001254 def toXML(self, writer, ttFont):
jvrd299b552006-10-21 13:54:30 +00001255 cmapName = self.__class__.__name__[:12] + str(self.format)
1256 writer.begintag(cmapName, [
jvra84b28d2004-09-25 09:06:58 +00001257 ("platformID", self.platformID),
1258 ("platEncID", self.platEncID),
1259 ])
1260 writer.newline()
jvrd299b552006-10-21 13:54:30 +00001261 writer.dumphex(self.data)
1262 writer.endtag(cmapName)
jvra84b28d2004-09-25 09:06:58 +00001263 writer.newline()
1264
Behdad Esfahbod3a9fd302013-11-27 03:19:32 -05001265 def fromXML(self, name, attrs, content, ttFont):
jvrd299b552006-10-21 13:54:30 +00001266 self.data = readHex(content)
1267 self.cmap = {}
1268
1269 def decompileHeader(self, data, ttFont):
jvr427f9802004-09-26 18:32:50 +00001270 self.language = 0 # dummy value
Just7842e561999-12-16 21:34:53 +00001271 self.data = data
1272
jvrd299b552006-10-21 13:54:30 +00001273 def decompile(self, data, ttFont):
1274 # we usually get here indirectly from the subtable __getattr__ function, in which case both args must be None.
1275 # If not, someone is calling the subtable decompile() directly, and must provide both args.
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -05001276 if data is not None and ttFont is not None:
jvrd299b552006-10-21 13:54:30 +00001277 self.decompileHeader(data[offset:offset+int(length)], ttFont)
1278 else:
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -05001279 assert (data is None and ttFont is None), "Need both data and ttFont arguments"
Just7842e561999-12-16 21:34:53 +00001280
jvrd299b552006-10-21 13:54:30 +00001281 def compile(self, ttFont):
1282 if self.data:
1283 return self.data
1284 else:
1285 return None
Just7842e561999-12-16 21:34:53 +00001286
1287cmap_classes = {
1288 0: cmap_format_0,
1289 2: cmap_format_2,
1290 4: cmap_format_4,
1291 6: cmap_format_6,
jvr924e4e22003-02-08 10:45:23 +00001292 12: cmap_format_12,
Roozbeh Pournader51a17822013-10-09 15:55:07 -07001293 13: cmap_format_13,
jvr0cb8a082008-05-16 15:07:09 +00001294 14: cmap_format_14,
Just7842e561999-12-16 21:34:53 +00001295 }