M_E_T_A_.py revision 0ba7aa7ab5153e6a490425dd0f859cc5947360f4
1import DefaultTable
2import struct
3from fontTools.misc import sstruct
4from fontTools.misc.textTools import safeEval
5import string
6from types import FloatType, ListType, StringType, TupleType
7import sys
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
72	dependencies = []
73
74	def decompile(self, data, ttFont):
75		dummy, newData = sstruct.unpack2(METAHeaderFormat, data, self)
76		self.glyphRecords = []
77		for i in range(self.nMetaRecs):
78			glyphRecord, newData = sstruct.unpack2(METAGlyphRecordFormat, newData, GlyphRecord())
79			if self.metaFlags == 0:
80				[glyphRecord.offset] = struct.unpack(">H", newData[:2])
81				newData = newData[2:]
82			elif self.metaFlags == 1:
83				[glyphRecord.offset] = struct.unpack(">H", newData[:4])
84				newData = newData[4:]
85			else:
86				assert 0, "The metaFlags field in the META table header has a value other than 0 or 1 :" + str(self.metaFlags)
87			glyphRecord.stringRecs = []
88			newData = data[glyphRecord.offset:]
89			for j in range(glyphRecord.nMetaEntry):
90				stringRec, newData = sstruct.unpack2(METAStringRecordFormat, newData, StringRecord())
91				if self.metaFlags == 0:
92					[stringRec.offset] = struct.unpack(">H", newData[:2])
93					newData = newData[2:]
94				else:
95					[stringRec.offset] = struct.unpack(">H", newData[:4])
96					newData = newData[4:]
97				stringRec.string = data[stringRec.offset:stringRec.offset + stringRec.stringLen]
98				glyphRecord.stringRecs.append(stringRec)
99			self.glyphRecords.append(glyphRecord)
100
101	def compile(self, ttFont):
102		offsetOK = 0
103		self.nMetaRecs = len(self.glyphRecords)
104		count = 0
105		while ( offsetOK != 1):
106			count = count + 1
107			if count > 4:
108				pdb_set_trace()
109			metaData = sstruct.pack(METAHeaderFormat, self)
110			stringRecsOffset = len(metaData) + self.nMetaRecs * (6 + 2*(self.metaFlags & 1))
111			stringRecSize = (6 + 2*(self.metaFlags & 1))
112			for glyphRec in self.glyphRecords:
113				glyphRec.offset = stringRecsOffset
114				if (glyphRec.offset > 65535) and ((self.metaFlags & 1) == 0):
115					self.metaFlags = self.metaFlags + 1
116					offsetOK = -1
117					break
118				metaData = metaData + glyphRec.compile(self)
119				stringRecsOffset = stringRecsOffset + (glyphRec.nMetaEntry * stringRecSize)
120				# this will be the String Record offset for the next GlyphRecord.
121			if 	offsetOK == -1:
122				offsetOK = 0
123				continue
124
125			# metaData now contains the header and all of the GlyphRecords. Its length should bw
126			# the offset to the first StringRecord.
127			stringOffset = stringRecsOffset
128			for glyphRec in self.glyphRecords:
129				assert (glyphRec.offset == len(metaData)), "Glyph record offset did not compile correctly! for rec:" + str(glyphRec)
130				for stringRec in glyphRec.stringRecs:
131					stringRec.offset = stringOffset
132					if (stringRec.offset > 65535) and ((self.metaFlags & 1) == 0):
133						self.metaFlags = self.metaFlags + 1
134						offsetOK = -1
135						break
136					metaData = metaData + stringRec.compile(self)
137					stringOffset = stringOffset + stringRec.stringLen
138			if 	offsetOK == -1:
139				offsetOK = 0
140				continue
141
142			if ((self.metaFlags & 1) == 1) and (stringOffset < 65536):
143				self.metaFlags = self.metaFlags - 1
144				continue
145			else:
146				offsetOK = 1
147
148
149			# metaData now contains the header and all of the GlyphRecords and all of the String Records.
150			# Its length should be the offset to the first string datum.
151			for glyphRec in self.glyphRecords:
152				for stringRec in glyphRec.stringRecs:
153					assert (stringRec.offset == len(metaData)), "String offset did not compile correctly! for string:" + str(stringRec.string)
154					metaData = metaData + stringRec.string
155
156		return metaData
157
158	def toXML(self, writer, ttFont):
159		writer.comment("Lengths and number of entries in this table will be recalculated by the compiler")
160		writer.newline()
161		formatstring, names, fixes = sstruct.getformat(METAHeaderFormat)
162		for name in names:
163			value = getattr(self, name)
164			writer.simpletag(name, value=value)
165			writer.newline()
166		for glyphRec in self.glyphRecords:
167			glyphRec.toXML(writer, ttFont)
168
169	def fromXML(self, (name, attrs, content), ttFont):
170		if name == "GlyphRecord":
171			if not hasattr(self, "glyphRecords"):
172				self.glyphRecords = []
173			glyphRec = GlyphRecord()
174			self.glyphRecords.append(glyphRec)
175			for element in content:
176				if isinstance(element, StringType):
177					continue
178				glyphRec.fromXML(element, ttFont)
179			glyphRec.offset = -1
180			glyphRec.nMetaEntry = len(glyphRec.stringRecs)
181		else:
182			value = attrs["value"]
183			try:
184				value = safeEval(value)
185			except OverflowError:
186				value = long(value)
187			setattr(self, name, value)
188
189
190class GlyphRecord:
191	def __init__(self):
192		self.glyphID = -1
193		self.nMetaEntry = -1
194		self.offset = -1
195		self.stringRecs = []
196
197	def toXML(self, writer, ttFont):
198		writer.begintag("GlyphRecord")
199		writer.newline()
200		writer.simpletag("glyphID", value=self.glyphID)
201		writer.newline()
202		writer.simpletag("nMetaEntry", value=self.nMetaEntry)
203		writer.newline()
204		for stringRec in self.stringRecs:
205			stringRec.toXML(writer, ttFont)
206		writer.endtag("GlyphRecord")
207		writer.newline()
208
209
210	def fromXML(self, (name, attrs, content), ttFont):
211		if name == "StringRecord":
212			stringRec = StringRecord()
213			self.stringRecs.append(stringRec)
214			for element in content:
215				if isinstance(element, StringType):
216					continue
217				stringRec.fromXML(element, ttFont)
218			stringRec.stringLen = len(stringRec.string)
219		else:
220			value = attrs["value"]
221			try:
222				value = safeEval(value)
223			except OverflowError:
224				value = long(value)
225			setattr(self, name, value)
226
227	def compile(self, parentTable):
228		data = sstruct.pack(METAGlyphRecordFormat, self)
229		if parentTable.metaFlags == 0:
230			datum = struct.pack(">H", self.offset)
231		elif parentTable.metaFlags == 1:
232			datum = struct.pack(">L", self.offset)
233		data = data + datum
234		return data
235
236
237	def __cmp__(self, other):
238		"""Compare method, so a list of NameRecords can be sorted
239		according to the spec by just sorting it..."""
240
241		if type(self) != type(other): return cmp(type(self), type(other))
242		if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__)
243
244		return cmp(self.glyphID, other.glyphID)
245
246	def __repr__(self):
247		return "GlyphRecord[ glyphID: " + str(self.glyphID) + ", nMetaEntry: " + str(self.nMetaEntry) + ", offset: " + str(self.offset) + " ]"
248
249
250def mapXMLToUTF8(string):
251	uString = u""
252	strLen = len(string)
253	i = 0
254	while i < strLen:
255		prefixLen = 0
256		if  (string[i:i+3] == "&#x"):
257			prefixLen = 3
258		elif  (string[i:i+7] == "&amp;#x"):
259			prefixLen = 7
260		if prefixLen:
261			i = i+prefixLen
262			j= i
263			while string[i] != ";":
264				i = i+1
265			valStr = string[j:i]
266
267			uString = uString + unichr(eval('0x' + valStr))
268		else:
269			uString = uString + unichr(ord(string[i]))
270		i = i +1
271
272	return uString.encode('utf8')
273
274
275def mapUTF8toXML(string):
276	uString = string.decode('utf8')
277	string = ""
278	for uChar in uString:
279		i = ord(uChar)
280		if (i < 0x80) and (i > 0x1F):
281			string = string + chr(i)
282		else:
283			string = string + "&#x" + hex(i)[2:] + ";"
284	return string
285
286
287class StringRecord:
288	def __init__(self):
289		self.labelID = -1
290		self.string = ""
291		self.stringLen = -1
292		self.offset = -1
293
294	def toXML(self, writer, ttFont):
295		writer.begintag("StringRecord")
296		writer.newline()
297		writer.simpletag("labelID", value=self.labelID)
298		writer.comment(getLabelString(self.labelID))
299		writer.newline()
300		writer.newline()
301		writer.simpletag("string", value=mapUTF8toXML(self.string))
302		writer.newline()
303		writer.endtag("StringRecord")
304		writer.newline()
305
306	def fromXML(self, (name, attrs, content), ttFont):
307		value = attrs["value"]
308		if name == "string":
309			self.string = mapXMLToUTF8(value)
310		else:
311			try:
312				value = safeEval(value)
313			except OverflowError:
314				value = long(value)
315			setattr(self, name, value)
316
317	def compile(self, parentTable):
318		data = sstruct.pack(METAStringRecordFormat, self)
319		if parentTable.metaFlags == 0:
320			datum = struct.pack(">H", self.offset)
321		elif parentTable.metaFlags == 1:
322			datum = struct.pack(">L", self.offset)
323		data = data + datum
324		return data
325
326	def __cmp__(self, other):
327		"""Compare method, so a list of NameRecords can be sorted
328		according to the spec by just sorting it..."""
329
330		if type(self) != type(other): return cmp(type(self), type(other))
331		if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__)
332
333		return cmp(self.labelID, other.labelID)
334
335	def __repr__(self):
336		return "StringRecord [ labelID: " + str(self.labelID) + " aka " + getLabelString(self.labelID) \
337			+ ", offset: " + str(self.offset) + ", length: " + str(self.stringLen) + ", string: " +self.string + " ]"
338
339