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