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