M_E_T_A_.py revision 7ed91eca1eaa96b79eae780778e89bb9ec44c1ee
1from . import DefaultTable
2import struct
3from fontTools.misc import sstruct
4from fontTools.misc.textTools import safeEval
5from fontTools.misc.py23 import *
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
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, str):
177					continue
178				name, attrs, content = element
179				glyphRec.fromXML(name, attrs, content, ttFont)
180			glyphRec.offset = -1
181			glyphRec.nMetaEntry = len(glyphRec.stringRecs)
182		else:
183			setattr(self, name, safeEval(attrs["value"]))
184
185
186class GlyphRecord:
187	def __init__(self):
188		self.glyphID = -1
189		self.nMetaEntry = -1
190		self.offset = -1
191		self.stringRecs = []
192
193	def toXML(self, writer, ttFont):
194		writer.begintag("GlyphRecord")
195		writer.newline()
196		writer.simpletag("glyphID", value=self.glyphID)
197		writer.newline()
198		writer.simpletag("nMetaEntry", value=self.nMetaEntry)
199		writer.newline()
200		for stringRec in self.stringRecs:
201			stringRec.toXML(writer, ttFont)
202		writer.endtag("GlyphRecord")
203		writer.newline()
204
205
206	def fromXML(self, name, attrs, content, ttFont):
207		if name == "StringRecord":
208			stringRec = StringRecord()
209			self.stringRecs.append(stringRec)
210			for element in content:
211				if isinstance(element, str):
212					continue
213				stringRec.fromXML(name, attrs, content, ttFont)
214			stringRec.stringLen = len(stringRec.string)
215		else:
216			setattr(self, name, safeEval(attrs["value"]))
217
218	def compile(self, parentTable):
219		data = sstruct.pack(METAGlyphRecordFormat, self)
220		if parentTable.metaFlags == 0:
221			datum = struct.pack(">H", self.offset)
222		elif parentTable.metaFlags == 1:
223			datum = struct.pack(">L", self.offset)
224		data = data + datum
225		return data
226
227
228	def __cmp__(self, other):
229		"""Compare method, so a list of NameRecords can be sorted
230		according to the spec by just sorting it..."""
231
232		if not isinstance(self, type(other)): return cmp(type(self), type(other))
233		if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__)
234
235		return cmp(self.glyphID, other.glyphID)
236
237	def __repr__(self):
238		return "GlyphRecord[ glyphID: " + str(self.glyphID) + ", nMetaEntry: " + str(self.nMetaEntry) + ", offset: " + str(self.offset) + " ]"
239
240
241def mapXMLToUTF8(string):
242	uString = unicode()
243	strLen = len(string)
244	i = 0
245	while i < strLen:
246		prefixLen = 0
247		if  (string[i:i+3] == "&#x"):
248			prefixLen = 3
249		elif  (string[i:i+7] == "&amp;#x"):
250			prefixLen = 7
251		if prefixLen:
252			i = i+prefixLen
253			j= i
254			while string[i] != ";":
255				i = i+1
256			valStr = string[j:i]
257
258			uString = uString + unichr(eval('0x' + valStr))
259		else:
260			uString = uString + unichr(ord(string[i]))
261		i = i +1
262
263	return uString.encode('utf8')
264
265
266def mapUTF8toXML(string):
267	uString = string.decode('utf8')
268	string = bytes()
269	for uChar in uString:
270		i = ord(uChar)
271		if (i < 0x80) and (i > 0x1F):
272			string = string + bytechr(i)
273		else:
274			string = string + "&#x" + hex(i)[2:] + ";"
275	return string
276
277
278class StringRecord:
279	def __init__(self):
280		self.labelID = -1
281		self.string = ""
282		self.stringLen = -1
283		self.offset = -1
284
285	def toXML(self, writer, ttFont):
286		writer.begintag("StringRecord")
287		writer.newline()
288		writer.simpletag("labelID", value=self.labelID)
289		writer.comment(getLabelString(self.labelID))
290		writer.newline()
291		writer.newline()
292		writer.simpletag("string", value=mapUTF8toXML(self.string))
293		writer.newline()
294		writer.endtag("StringRecord")
295		writer.newline()
296
297	def fromXML(self, name, attrs, content, ttFont):
298		value = attrs["value"]
299		if name == "string":
300			self.string = mapXMLToUTF8(value)
301		else:
302			setattr(self, name, safeEval(value))
303
304	def compile(self, parentTable):
305		data = sstruct.pack(METAStringRecordFormat, self)
306		if parentTable.metaFlags == 0:
307			datum = struct.pack(">H", self.offset)
308		elif parentTable.metaFlags == 1:
309			datum = struct.pack(">L", self.offset)
310		data = data + datum
311		return data
312
313	def __cmp__(self, other):
314		"""Compare method, so a list of NameRecords can be sorted
315		according to the spec by just sorting it..."""
316
317		if not isinstance(self, type(other)): return cmp(type(self), type(other))
318		if self.__class__ != other.__class__: return cmp(self.__class__, other.__class__)
319
320		return cmp(self.labelID, other.labelID)
321
322	def __repr__(self):
323		return "StringRecord [ labelID: " + str(self.labelID) + " aka " + getLabelString(self.labelID) \
324			+ ", offset: " + str(self.offset) + ", length: " + str(self.stringLen) + ", string: " +self.string + " ]"
325
326