• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc import sstruct
2from fontTools.misc.textTools import byteord, safeEval
3from . import DefaultTable
4import pdb
5import struct
6
7
8METAHeaderFormat = """
9		>	# big endian
10		tableVersionMajor:			H
11		tableVersionMinor:			H
12		metaEntriesVersionMajor:	H
13		metaEntriesVersionMinor:	H
14		unicodeVersion:				L
15		metaFlags:					H
16		nMetaRecs:					H
17"""
18# This record is followed by nMetaRecs of METAGlyphRecordFormat.
19# This in turn is followd by as many METAStringRecordFormat entries
20# as specified by the METAGlyphRecordFormat entries
21# this is followed by the strings specifried in the  METAStringRecordFormat
22METAGlyphRecordFormat = """
23		>	# big endian
24		glyphID:			H
25		nMetaEntry:			H
26"""
27# This record is followd by a variable data length field:
28# 	USHORT or ULONG	hdrOffset
29# Offset from start of META table to the beginning
30# of this glyphs array of ns Metadata string entries.
31# Size determined by metaFlags field
32# METAGlyphRecordFormat entries must be sorted by glyph ID
33
34METAStringRecordFormat = """
35		>	# big endian
36		labelID:			H
37		stringLen:			H
38"""
39# This record is followd by a variable data length field:
40# 	USHORT or ULONG	stringOffset
41# METAStringRecordFormat entries must be sorted in order of labelID
42# There may be more than one entry with the same labelID
43# There may be more than one strign with the same content.
44
45# Strings shall be Unicode UTF-8 encoded, and null-terminated.
46
47METALabelDict = {
48    0: "MojikumiX4051",  # An integer in the range 1-20
49    1: "UNIUnifiedBaseChars",
50    2: "BaseFontName",
51    3: "Language",
52    4: "CreationDate",
53    5: "FoundryName",
54    6: "FoundryCopyright",
55    7: "OwnerURI",
56    8: "WritingScript",
57    10: "StrokeCount",
58    11: "IndexingRadical",
59}
60
61
62def getLabelString(labelID):
63    try:
64        label = METALabelDict[labelID]
65    except KeyError:
66        label = "Unknown label"
67    return str(label)
68
69
70class table_M_E_T_A_(DefaultTable.DefaultTable):
71    dependencies = []
72
73    def decompile(self, data, ttFont):
74        dummy, newData = sstruct.unpack2(METAHeaderFormat, data, self)
75        self.glyphRecords = []
76        for i in range(self.nMetaRecs):
77            glyphRecord, newData = sstruct.unpack2(
78                METAGlyphRecordFormat, newData, GlyphRecord()
79            )
80            if self.metaFlags == 0:
81                [glyphRecord.offset] = struct.unpack(">H", newData[:2])
82                newData = newData[2:]
83            elif self.metaFlags == 1:
84                [glyphRecord.offset] = struct.unpack(">H", newData[:4])
85                newData = newData[4:]
86            else:
87                assert 0, (
88                    "The metaFlags field in the META table header has a value other than 0 or 1 :"
89                    + str(self.metaFlags)
90                )
91            glyphRecord.stringRecs = []
92            newData = data[glyphRecord.offset :]
93            for j in range(glyphRecord.nMetaEntry):
94                stringRec, newData = sstruct.unpack2(
95                    METAStringRecordFormat, newData, StringRecord()
96                )
97                if self.metaFlags == 0:
98                    [stringRec.offset] = struct.unpack(">H", newData[:2])
99                    newData = newData[2:]
100                else:
101                    [stringRec.offset] = struct.unpack(">H", newData[:4])
102                    newData = newData[4:]
103                stringRec.string = data[
104                    stringRec.offset : stringRec.offset + stringRec.stringLen
105                ]
106                glyphRecord.stringRecs.append(stringRec)
107            self.glyphRecords.append(glyphRecord)
108
109    def compile(self, ttFont):
110        offsetOK = 0
111        self.nMetaRecs = len(self.glyphRecords)
112        count = 0
113        while offsetOK != 1:
114            count = count + 1
115            if count > 4:
116                pdb.set_trace()
117            metaData = sstruct.pack(METAHeaderFormat, self)
118            stringRecsOffset = len(metaData) + self.nMetaRecs * (
119                6 + 2 * (self.metaFlags & 1)
120            )
121            stringRecSize = 6 + 2 * (self.metaFlags & 1)
122            for glyphRec in self.glyphRecords:
123                glyphRec.offset = stringRecsOffset
124                if (glyphRec.offset > 65535) and ((self.metaFlags & 1) == 0):
125                    self.metaFlags = self.metaFlags + 1
126                    offsetOK = -1
127                    break
128                metaData = metaData + glyphRec.compile(self)
129                stringRecsOffset = stringRecsOffset + (
130                    glyphRec.nMetaEntry * stringRecSize
131                )
132                # this will be the String Record offset for the next GlyphRecord.
133            if offsetOK == -1:
134                offsetOK = 0
135                continue
136
137            # metaData now contains the header and all of the GlyphRecords. Its length should bw
138            # the offset to the first StringRecord.
139            stringOffset = stringRecsOffset
140            for glyphRec in self.glyphRecords:
141                assert glyphRec.offset == len(
142                    metaData
143                ), "Glyph record offset did not compile correctly! for rec:" + str(
144                    glyphRec
145                )
146                for stringRec in glyphRec.stringRecs:
147                    stringRec.offset = stringOffset
148                    if (stringRec.offset > 65535) and ((self.metaFlags & 1) == 0):
149                        self.metaFlags = self.metaFlags + 1
150                        offsetOK = -1
151                        break
152                    metaData = metaData + stringRec.compile(self)
153                    stringOffset = stringOffset + stringRec.stringLen
154            if offsetOK == -1:
155                offsetOK = 0
156                continue
157
158            if ((self.metaFlags & 1) == 1) and (stringOffset < 65536):
159                self.metaFlags = self.metaFlags - 1
160                continue
161            else:
162                offsetOK = 1
163
164            # metaData now contains the header and all of the GlyphRecords and all of the String Records.
165            # Its length should be the offset to the first string datum.
166            for glyphRec in self.glyphRecords:
167                for stringRec in glyphRec.stringRecs:
168                    assert stringRec.offset == len(
169                        metaData
170                    ), "String offset did not compile correctly! for string:" + str(
171                        stringRec.string
172                    )
173                    metaData = metaData + stringRec.string
174
175        return metaData
176
177    def toXML(self, writer, ttFont):
178        writer.comment(
179            "Lengths and number of entries in this table will be recalculated by the compiler"
180        )
181        writer.newline()
182        formatstring, names, fixes = sstruct.getformat(METAHeaderFormat)
183        for name in names:
184            value = getattr(self, name)
185            writer.simpletag(name, value=value)
186            writer.newline()
187        for glyphRec in self.glyphRecords:
188            glyphRec.toXML(writer, ttFont)
189
190    def fromXML(self, name, attrs, content, ttFont):
191        if name == "GlyphRecord":
192            if not hasattr(self, "glyphRecords"):
193                self.glyphRecords = []
194            glyphRec = GlyphRecord()
195            self.glyphRecords.append(glyphRec)
196            for element in content:
197                if isinstance(element, str):
198                    continue
199                name, attrs, content = element
200                glyphRec.fromXML(name, attrs, content, ttFont)
201            glyphRec.offset = -1
202            glyphRec.nMetaEntry = len(glyphRec.stringRecs)
203        else:
204            setattr(self, name, safeEval(attrs["value"]))
205
206
207class GlyphRecord(object):
208    def __init__(self):
209        self.glyphID = -1
210        self.nMetaEntry = -1
211        self.offset = -1
212        self.stringRecs = []
213
214    def toXML(self, writer, ttFont):
215        writer.begintag("GlyphRecord")
216        writer.newline()
217        writer.simpletag("glyphID", value=self.glyphID)
218        writer.newline()
219        writer.simpletag("nMetaEntry", value=self.nMetaEntry)
220        writer.newline()
221        for stringRec in self.stringRecs:
222            stringRec.toXML(writer, ttFont)
223        writer.endtag("GlyphRecord")
224        writer.newline()
225
226    def fromXML(self, name, attrs, content, ttFont):
227        if name == "StringRecord":
228            stringRec = StringRecord()
229            self.stringRecs.append(stringRec)
230            for element in content:
231                if isinstance(element, str):
232                    continue
233                stringRec.fromXML(name, attrs, content, ttFont)
234            stringRec.stringLen = len(stringRec.string)
235        else:
236            setattr(self, name, safeEval(attrs["value"]))
237
238    def compile(self, parentTable):
239        data = sstruct.pack(METAGlyphRecordFormat, self)
240        if parentTable.metaFlags == 0:
241            datum = struct.pack(">H", self.offset)
242        elif parentTable.metaFlags == 1:
243            datum = struct.pack(">L", self.offset)
244        data = data + datum
245        return data
246
247    def __repr__(self):
248        return (
249            "GlyphRecord[ glyphID: "
250            + str(self.glyphID)
251            + ", nMetaEntry: "
252            + str(self.nMetaEntry)
253            + ", offset: "
254            + str(self.offset)
255            + " ]"
256        )
257
258
259# XXX The following two functions are really broken around UTF-8 vs Unicode
260
261
262def mapXMLToUTF8(string):
263    uString = str()
264    strLen = len(string)
265    i = 0
266    while i < strLen:
267        prefixLen = 0
268        if string[i : i + 3] == "&#x":
269            prefixLen = 3
270        elif string[i : i + 7] == "&amp;#x":
271            prefixLen = 7
272        if prefixLen:
273            i = i + prefixLen
274            j = i
275            while string[i] != ";":
276                i = i + 1
277            valStr = string[j:i]
278
279            uString = uString + chr(eval("0x" + valStr))
280        else:
281            uString = uString + chr(byteord(string[i]))
282        i = i + 1
283
284    return uString.encode("utf_8")
285
286
287def mapUTF8toXML(string):
288    uString = string.decode("utf_8")
289    string = ""
290    for uChar in uString:
291        i = ord(uChar)
292        if (i < 0x80) and (i > 0x1F):
293            string = string + uChar
294        else:
295            string = string + "&#x" + hex(i)[2:] + ";"
296    return string
297
298
299class StringRecord(object):
300    def toXML(self, writer, ttFont):
301        writer.begintag("StringRecord")
302        writer.newline()
303        writer.simpletag("labelID", value=self.labelID)
304        writer.comment(getLabelString(self.labelID))
305        writer.newline()
306        writer.newline()
307        writer.simpletag("string", value=mapUTF8toXML(self.string))
308        writer.newline()
309        writer.endtag("StringRecord")
310        writer.newline()
311
312    def fromXML(self, name, attrs, content, ttFont):
313        for element in content:
314            if isinstance(element, str):
315                continue
316            name, attrs, content = element
317            value = attrs["value"]
318            if name == "string":
319                self.string = mapXMLToUTF8(value)
320            else:
321                setattr(self, name, safeEval(value))
322
323    def compile(self, parentTable):
324        data = sstruct.pack(METAStringRecordFormat, self)
325        if parentTable.metaFlags == 0:
326            datum = struct.pack(">H", self.offset)
327        elif parentTable.metaFlags == 1:
328            datum = struct.pack(">L", self.offset)
329        data = data + datum
330        return data
331
332    def __repr__(self):
333        return (
334            "StringRecord [ labelID: "
335            + str(self.labelID)
336            + " aka "
337            + getLabelString(self.labelID)
338            + ", offset: "
339            + str(self.offset)
340            + ", length: "
341            + str(self.stringLen)
342            + ", string: "
343            + self.string
344            + " ]"
345        )
346