M_E_T_A_.py revision 96b321c8aea4dc64110d15a541c6f85152ae19cf
1import DefaultTable
2import struct, sstruct
3from fontTools.misc.textTools import safeEval
4import string
5from types import FloatType, ListType, StringType, TupleType
6import sys
7METAHeaderFormat = """
8		>	# big endian
9		tableVersionMajor:			H
10		tableVersionMinor:			H
11		metaEntriesVersionMajor:	H
12		metaEntriesVersionMinor:	H
13		unicodeVersion:				L
14		metaFlags:					H
15		nMetaRecs:					H
16"""
17# This record is followed by nMetaRecs of METAGlyphRecordFormat.
18# This in turn is followd by as many METAStringRecordFormat entries
19# as specified by the METAGlyphRecordFormat entries
20# this is followed by the strings specifried in the  METAStringRecordFormat
21METAGlyphRecordFormat = """
22		>	# big endian
23		glyphID:			H
24		nMetaEntry:			H
25"""
26# This record is followd by a variable data length field:
27# 	USHORT or ULONG	hdrOffset
28# Offset from start of META table to the beginning
29# of this glyphs array of ns Metadata string entries.
30# Size determined by metaFlags field
31# METAGlyphRecordFormat entries must be sorted by glyph ID
32
33METAStringRecordFormat = """
34		>	# big endian
35		labelID:			H
36		stringLen:			H
37"""
38# This record is followd by a variable data length field:
39# 	USHORT or ULONG	stringOffset
40# METAStringRecordFormat entries must be sorted in order of labelID
41# There may be more than one entry with the same labelID
42# There may be more than one strign with the same content.
43
44# Strings shall be Unicode UTF-8 encoded, and null-terminated.
45
46METALabelDict = {
47	0 : "MojikumiX4051", # An integer in the range 1-20
48	1 : "UNIUnifiedBaseChars",
49	2 : "BaseFontName",
50	3 : "Language",
51	4 : "CreationDate",
52	5 : "FoundryName",
53	6 : "FoundryCopyright",
54	7 : "OwnerURI",
55	8 : "WritingScript",
56	10 : "StrokeCount",
57	11 : "IndexingRadical",
58}
59
60
61def getLabelString(labelID):
62	try:
63		label = METALabelDict[labelID]
64	except KeyError:
65		label = "Unknown label"
66	return str(label)
67
68
69class table_M_E_T_A_(DefaultTable.DefaultTable):
70
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(METAGlyphRecordFormat, newData, GlyphRecord())
78			if self.metaFlags == 0:
79				[glyphRecord.offset] = struct.unpack(">H", newData[:2])
80				newData = newData[2:]
81			elif self.metaFlags == 1:
82				[glyphRecord.offset] = struct.unpack(">H", newData[:4])
83				newData = newData[4:]
84			else:
85				assert 0, "The metaFlags field in the META table header has a value other than 0 or 1 :" + str(self.metaFlags)
86			glyphRecord.stringRecs = []
87			newData = data[glyphRecord.offset:]
88			for j in range(glyphRecord.nMetaEntry):
89				stringRec, newData = sstruct.unpack2(METAStringRecordFormat, newData, StringRecord())
90				if self.metaFlags == 0:
91					[stringRec.offset] = struct.unpack(">H", newData[:2])
92					newData = newData[2:]
93				else:
94					[stringRec.offset] = struct.unpack(">H", newData[:4])
95					newData = newData[4:]
96				stringRec.string = data[stringRec.offset:stringRec.offset + stringRec.stringLen]
97				glyphRecord.stringRecs.append(stringRec)
98			self.glyphRecords.append(glyphRecord)
99
100	def compile(self, ttFont):
101		offsetOK = 0
102		self.nMetaRecs = len(self.glyphRecords)
103		count = 0
104		while ( offsetOK != 1):
105			count = count + 1
106			if count > 4:
107				pdb_set_trace()
108			metaData = sstruct.pack(METAHeaderFormat, self)
109			stringRecsOffset = len(metaData) + self.nMetaRecs * (6 + 2*(self.metaFlags & 1))
110			stringRecSize = (6 + 2*(self.metaFlags & 1))
111			for glyphRec in self.glyphRecords:
112				glyphRec.offset = stringRecsOffset
113				if (glyphRec.offset > 65535) and ((self.metaFlags & 1) == 0):
114					self.metaFlags = self.metaFlags + 1
115					offsetOK = -1
116					break
117				metaData = metaData + glyphRec.compile(self)
118				stringRecsOffset = stringRecsOffset + (glyphRec.nMetaEntry * stringRecSize)
119				# this will be the String Record offset for the next GlyphRecord.
120			if 	offsetOK == -1:
121				offsetOK = 0
122				continue
123
124			# metaData now contains the header and all of the GlyphRecords. Its length should bw
125			# the offset to the first StringRecord.
126			stringOffset = stringRecsOffset
127			for glyphRec in self.glyphRecords:
128				assert (glyphRec.offset == len(metaData)), "Glyph record offset did not compile correctly! for rec:" + str(glyphRec)
129				for stringRec in glyphRec.stringRecs:
130					stringRec.offset = stringOffset
131					if (stringRec.offset > 65535) and ((self.metaFlags & 1) == 0):
132						self.metaFlags = self.metaFlags + 1
133						offsetOK = -1
134						break
135					metaData = metaData + stringRec.compile(self)
136					stringOffset = stringOffset + stringRec.stringLen
137			if 	offsetOK == -1:
138				offsetOK = 0
139				continue
140
141			if ((self.metaFlags & 1) == 1) and (stringOffset < 65536):
142				self.metaFlags = self.metaFlags - 1
143				continue
144			else:
145				offsetOK = 1
146
147
148			# metaData now contains the header and all of the GlyphRecords and all of the String Records.
149			# Its length should be the offset to the first string datum.
150			for glyphRec in self.glyphRecords:
151				for stringRec in glyphRec.stringRecs:
152					assert (stringRec.offset == len(metaData)), "String offset did not compile correctly! for string:" + str(stringRec.string)
153					metaData = metaData + stringRec.string
154
155		return metaData
156
157	def toXML(self, writer, ttFont):
158		writer.comment("Lengths and number of entries in this table will be recalculated by the compiler")
159		writer.newline()
160		formatstring, names, fixes = sstruct.getformat(METAHeaderFormat)
161		for name in names:
162			value = getattr(self, name)
163			writer.simpletag(name, value=value)
164			writer.newline()
165		for glyphRec in self.glyphRecords:
166			glyphRec.toXML(writer, ttFont)
167
168	def fromXML(self, (name, attrs, content), ttFont):
169		if name == "GlyphRecord":
170			if not hasattr(self, "glyphRecords"):
171				self.glyphRecords = []
172			glyphRec = GlyphRecord()
173			self.glyphRecords.append(glyphRec)
174			for element in content:
175				if isinstance(element, StringType):
176					continue
177				glyphRec.fromXML(element, ttFont)
178			glyphRec.offset = -1
179			glyphRec.nMetaEntry = len(glyphRec.stringRecs)
180		else:
181			value = attrs["value"]
182			try:
183				value = safeEval(value)
184			except OverflowError:
185				value = long(value)
186			setattr(self, name, value)
187
188
189class GlyphRecord:
190	def __init__(self):
191		self.glyphID = -1
192		self.nMetaEntry = -1
193		self.offset = -1
194		self.stringRecs = []
195
196	def toXML(self, writer, ttFont):
197		writer.begintag("GlyphRecord")
198		writer.newline()
199		writer.simpletag("glyphID", value=self.glyphID)
200		writer.newline()
201		writer.simpletag("nMetaEntry", value=self.nMetaEntry)
202		writer.newline()
203		for stringRec in self.stringRecs:
204			stringRec.toXML(writer, ttFont)
205		writer.endtag("GlyphRecord")
206		writer.newline()
207
208
209	def fromXML(self, (name, attrs, content), ttFont):
210		if name == "StringRecord":
211			stringRec = StringRecord()
212			self.stringRecs.append(stringRec)
213			for element in content:
214				if isinstance(element, StringType):
215					continue
216				stringRec.fromXML(element, ttFont)
217			stringRec.stringLen = len(stringRec.string)
218		else:
219			value = attrs["value"]
220			try:
221				value = safeEval(value)
222			except OverflowError:
223				value = long(value)
224			setattr(self, name, value)
225
226	def compile(self, parentTable):
227		data = sstruct.pack(METAGlyphRecordFormat, self)
228		if parentTable.metaFlags == 0:
229			datum = struct.pack(">H", self.offset)
230		elif parentTable.metaFlags == 1:
231			datum = struct.pack(">L", self.offset)
232		data = data + datum
233		return data
234
235
236	def __cmp__(self, other):
237		"""Compare method, so a list of NameRecords can be sorted
238		according to the spec by just sorting it..."""
239
240		if type(self) != type(other) or \
241		   self.__class__ != other.__class__:
242			return cmp(id(self), id(other))
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) or \
331		   self.__class__ != other.__class__:
332			return cmp(id(self), id(other))
333
334		return cmp(self.labelID, other.labelID)
335
336	def __repr__(self):
337		return "StringRecord [ labelID: " + str(self.labelID) + " aka " + getLabelString(self.labelID) \
338			+ ", offset: " + str(self.offset) + ", length: " + str(self.stringLen) + ", string: " +self.string + " ]"
339
340