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