Home | History | Annotate | Download | only in tables
      1 from __future__ import print_function, division, absolute_import
      2 from fontTools.misc.py23 import *
      3 from fontTools.misc import sstruct
      4 from fontTools.misc.textTools import safeEval
      5 from . import DefaultTable
      6 import struct
      7 
      8 
      9 METAHeaderFormat = """
     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
     23 METAGlyphRecordFormat = """
     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  
     35 METAStringRecordFormat = """
     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 
     48 METALabelDict = {
     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 
     63 def getLabelString(labelID):
     64 	try:
     65 		label = METALabelDict[labelID]
     66 	except KeyError:
     67 		label = "Unknown label"
     68 	return str(label)
     69 
     70 
     71 class 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 
    187 class 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 
    233 def 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 
    258 def 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 
    270 class 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