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
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			# 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, str):
176					continue
177				name, attrs, content = element
178				glyphRec.fromXML(name, attrs, content, ttFont)
179			glyphRec.offset = -1
180			glyphRec.nMetaEntry = len(glyphRec.stringRecs)
181		else:
182			setattr(self, name, safeEval(attrs["value"]))
183
184
185class GlyphRecord(object):
186	def __init__(self):
187		self.glyphID = -1
188		self.nMetaEntry = -1
189		self.offset = -1
190		self.stringRecs = []
191
192	def toXML(self, writer, ttFont):
193		writer.begintag("GlyphRecord")
194		writer.newline()
195		writer.simpletag("glyphID", value=self.glyphID)
196		writer.newline()
197		writer.simpletag("nMetaEntry", value=self.nMetaEntry)
198		writer.newline()
199		for stringRec in self.stringRecs:
200			stringRec.toXML(writer, ttFont)
201		writer.endtag("GlyphRecord")
202		writer.newline()
203
204	def fromXML(self, name, attrs, content, ttFont):
205		if name == "StringRecord":
206			stringRec = StringRecord()
207			self.stringRecs.append(stringRec)
208			for element in content:
209				if isinstance(element, str):
210					continue
211				stringRec.fromXML(name, attrs, content, ttFont)
212			stringRec.stringLen = len(stringRec.string)
213		else:
214			setattr(self, name, safeEval(attrs["value"]))
215
216	def compile(self, parentTable):
217		data = sstruct.pack(METAGlyphRecordFormat, self)
218		if parentTable.metaFlags == 0:
219			datum = struct.pack(">H", self.offset)
220		elif parentTable.metaFlags == 1:
221			datum = struct.pack(">L", self.offset)
222		data = data + datum
223		return data
224
225	def __repr__(self):
226		return "GlyphRecord[ glyphID: " + str(self.glyphID) + ", nMetaEntry: " + str(self.nMetaEntry) + ", offset: " + str(self.offset) + " ]"
227
228# XXX The following two functions are really broken around UTF-8 vs Unicode
229
230def mapXMLToUTF8(string):
231	uString = str()
232	strLen = len(string)
233	i = 0
234	while i < strLen:
235		prefixLen = 0
236		if  (string[i:i+3] == "&#x"):
237			prefixLen = 3
238		elif  (string[i:i+7] == "&amp;#x"):
239			prefixLen = 7
240		if prefixLen:
241			i = i+prefixLen
242			j= i
243			while string[i] != ";":
244				i = i+1
245			valStr = string[j:i]
246
247			uString = uString + chr(eval('0x' + valStr))
248		else:
249			uString = uString + chr(byteord(string[i]))
250		i = i +1
251
252	return uString.encode('utf_8')
253
254
255def mapUTF8toXML(string):
256	uString = string.decode('utf_8')
257	string = ""
258	for uChar in uString:
259		i = ord(uChar)
260		if (i < 0x80) and (i > 0x1F):
261			string = string + uChar
262		else:
263			string = string + "&#x" + hex(i)[2:] + ";"
264	return string
265
266
267class StringRecord(object):
268
269	def toXML(self, writer, ttFont):
270		writer.begintag("StringRecord")
271		writer.newline()
272		writer.simpletag("labelID", value=self.labelID)
273		writer.comment(getLabelString(self.labelID))
274		writer.newline()
275		writer.newline()
276		writer.simpletag("string", value=mapUTF8toXML(self.string))
277		writer.newline()
278		writer.endtag("StringRecord")
279		writer.newline()
280
281	def fromXML(self, name, attrs, content, ttFont):
282		for element in content:
283			if isinstance(element, str):
284				continue
285			name, attrs, content = element
286			value = attrs["value"]
287			if name == "string":
288				self.string = mapXMLToUTF8(value)
289			else:
290				setattr(self, name, safeEval(value))
291
292	def compile(self, parentTable):
293		data = sstruct.pack(METAStringRecordFormat, self)
294		if parentTable.metaFlags == 0:
295			datum = struct.pack(">H", self.offset)
296		elif parentTable.metaFlags == 1:
297			datum = struct.pack(">L", self.offset)
298		data = data + datum
299		return data
300
301	def __repr__(self):
302		return "StringRecord [ labelID: " + str(self.labelID) + " aka " + getLabelString(self.labelID) \
303			+ ", offset: " + str(self.offset) + ", length: " + str(self.stringLen) + ", string: " +self.string + " ]"
304