• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import print_function, division, absolute_import
2from fontTools.misc.py23 import *
3from . import DefaultTable
4from fontTools.misc import sstruct
5from fontTools.misc.textTools import safeEval
6import struct
7
8VDMX_HeaderFmt = """
9	>                 # big endian
10	version:     H    # Version number (0 or 1)
11	numRecs:     H    # Number of VDMX groups present
12	numRatios:   H    # Number of aspect ratio groupings
13"""
14# the VMDX header is followed by an array of RatRange[numRatios] (i.e. aspect
15# ratio ranges);
16VDMX_RatRangeFmt = """
17	>                 # big endian
18	bCharSet:    B    # Character set
19	xRatio:      B    # Value to use for x-Ratio
20	yStartRatio: B    # Starting y-Ratio value
21	yEndRatio:   B    # Ending y-Ratio value
22"""
23# followed by an array of offset[numRatios] from start of VDMX table to the
24# VDMX Group for this ratio range (offsets will be re-calculated on compile);
25# followed by an array of Group[numRecs] records;
26VDMX_GroupFmt = """
27	>                 # big endian
28	recs:        H    # Number of height records in this group
29	startsz:     B    # Starting yPelHeight
30	endsz:       B    # Ending yPelHeight
31"""
32# followed by an array of vTable[recs] records.
33VDMX_vTableFmt = """
34	>                 # big endian
35	yPelHeight:  H    # yPelHeight to which values apply
36	yMax:        h    # Maximum value (in pels) for this yPelHeight
37	yMin:        h    # Minimum value (in pels) for this yPelHeight
38"""
39
40
41class table_V_D_M_X_(DefaultTable.DefaultTable):
42
43	def decompile(self, data, ttFont):
44		pos = 0  # track current position from to start of VDMX table
45		dummy, data = sstruct.unpack2(VDMX_HeaderFmt, data, self)
46		pos += sstruct.calcsize(VDMX_HeaderFmt)
47		self.ratRanges = []
48		for i in range(self.numRatios):
49			ratio, data = sstruct.unpack2(VDMX_RatRangeFmt, data)
50			pos += sstruct.calcsize(VDMX_RatRangeFmt)
51			# the mapping between a ratio and a group is defined further below
52			ratio['groupIndex'] = None
53			self.ratRanges.append(ratio)
54		lenOffset = struct.calcsize('>H')
55		_offsets = []  # temporarily store offsets to groups
56		for i in range(self.numRatios):
57			offset = struct.unpack('>H', data[0:lenOffset])[0]
58			data = data[lenOffset:]
59			pos += lenOffset
60			_offsets.append(offset)
61		self.groups = []
62		for groupIndex in range(self.numRecs):
63			# the offset to this group from beginning of the VDMX table
64			currOffset = pos
65			group, data = sstruct.unpack2(VDMX_GroupFmt, data)
66			# the group lenght and bounding sizes are re-calculated on compile
67			recs = group.pop('recs')
68			startsz = group.pop('startsz')
69			endsz = group.pop('endsz')
70			pos += sstruct.calcsize(VDMX_GroupFmt)
71			for j in range(recs):
72				vTable, data = sstruct.unpack2(VDMX_vTableFmt, data)
73				vTableLength = sstruct.calcsize(VDMX_vTableFmt)
74				pos += vTableLength
75				# group is a dict of (yMax, yMin) tuples keyed by yPelHeight
76				group[vTable['yPelHeight']] = (vTable['yMax'], vTable['yMin'])
77			# make sure startsz and endsz match the calculated values
78			minSize = min(group.keys())
79			maxSize = max(group.keys())
80			assert startsz == minSize, \
81				"startsz (%s) must equal min yPelHeight (%s): group %d" % \
82				(group.startsz, minSize, groupIndex)
83			assert endsz == maxSize, \
84				"endsz (%s) must equal max yPelHeight (%s): group %d" % \
85				(group.endsz, maxSize, groupIndex)
86			self.groups.append(group)
87			# match the defined offsets with the current group's offset
88			for offsetIndex, offsetValue in enumerate(_offsets):
89				# when numRecs < numRatios there can more than one ratio range
90				# sharing the same VDMX group
91				if currOffset == offsetValue:
92					# map the group with the ratio range thas has the same
93					# index as the offset to that group (it took me a while..)
94					self.ratRanges[offsetIndex]['groupIndex'] = groupIndex
95		# check that all ratio ranges have a group
96		for i in range(self.numRatios):
97			ratio = self.ratRanges[i]
98			if ratio['groupIndex'] is None:
99				from fontTools import ttLib
100				raise ttLib.TTLibError(
101					"no group defined for ratRange %d" % i)
102
103	def _getOffsets(self):
104		"""
105		Calculate offsets to VDMX_Group records.
106		For each ratRange return a list of offset values from the beginning of
107		the VDMX table to a VDMX_Group.
108		"""
109		lenHeader = sstruct.calcsize(VDMX_HeaderFmt)
110		lenRatRange = sstruct.calcsize(VDMX_RatRangeFmt)
111		lenOffset = struct.calcsize('>H')
112		lenGroupHeader = sstruct.calcsize(VDMX_GroupFmt)
113		lenVTable = sstruct.calcsize(VDMX_vTableFmt)
114		# offset to the first group
115		pos = lenHeader + self.numRatios*lenRatRange + self.numRatios*lenOffset
116		groupOffsets = []
117		for group in self.groups:
118			groupOffsets.append(pos)
119			lenGroup = lenGroupHeader + len(group) * lenVTable
120			pos += lenGroup  # offset to next group
121		offsets = []
122		for ratio in self.ratRanges:
123			groupIndex = ratio['groupIndex']
124			offsets.append(groupOffsets[groupIndex])
125		return offsets
126
127	def compile(self, ttFont):
128		if not(self.version == 0 or self.version == 1):
129			from fontTools import ttLib
130			raise ttLib.TTLibError(
131				"unknown format for VDMX table: version %s" % self.version)
132		data = sstruct.pack(VDMX_HeaderFmt, self)
133		for ratio in self.ratRanges:
134			data += sstruct.pack(VDMX_RatRangeFmt, ratio)
135		# recalculate offsets to VDMX groups
136		for offset in self._getOffsets():
137			data += struct.pack('>H', offset)
138		for group in self.groups:
139			recs = len(group)
140			startsz = min(group.keys())
141			endsz = max(group.keys())
142			gHeader = {'recs': recs, 'startsz': startsz, 'endsz': endsz}
143			data += sstruct.pack(VDMX_GroupFmt, gHeader)
144			for yPelHeight, (yMax, yMin) in sorted(group.items()):
145				vTable = {'yPelHeight': yPelHeight, 'yMax': yMax, 'yMin': yMin}
146				data += sstruct.pack(VDMX_vTableFmt, vTable)
147		return data
148
149	def toXML(self, writer, ttFont):
150		writer.simpletag("version", value=self.version)
151		writer.newline()
152		writer.begintag("ratRanges")
153		writer.newline()
154		for ratio in self.ratRanges:
155			groupIndex = ratio['groupIndex']
156			writer.simpletag(
157				"ratRange",
158				bCharSet=ratio['bCharSet'],
159				xRatio=ratio['xRatio'],
160				yStartRatio=ratio['yStartRatio'],
161				yEndRatio=ratio['yEndRatio'],
162				groupIndex=groupIndex
163				)
164			writer.newline()
165		writer.endtag("ratRanges")
166		writer.newline()
167		writer.begintag("groups")
168		writer.newline()
169		for groupIndex in range(self.numRecs):
170			group = self.groups[groupIndex]
171			recs = len(group)
172			startsz = min(group.keys())
173			endsz = max(group.keys())
174			writer.begintag("group", index=groupIndex)
175			writer.newline()
176			writer.comment("recs=%d, startsz=%d, endsz=%d" %
177							(recs, startsz, endsz))
178			writer.newline()
179			for yPelHeight, (yMax, yMin) in sorted(group.items()):
180				writer.simpletag(
181					"record",
182					[('yPelHeight', yPelHeight), ('yMax', yMax), ('yMin', yMin)])
183				writer.newline()
184			writer.endtag("group")
185			writer.newline()
186		writer.endtag("groups")
187		writer.newline()
188
189	def fromXML(self, name, attrs, content, ttFont):
190		if name == "version":
191			self.version = safeEval(attrs["value"])
192		elif name == "ratRanges":
193			if not hasattr(self, "ratRanges"):
194				self.ratRanges = []
195			for element in content:
196				if not isinstance(element, tuple):
197					continue
198				name, attrs, content = element
199				if name == "ratRange":
200					if not hasattr(self, "numRatios"):
201						self.numRatios = 1
202					else:
203						self.numRatios += 1
204					ratio = {
205						"bCharSet": safeEval(attrs["bCharSet"]),
206						"xRatio": safeEval(attrs["xRatio"]),
207						"yStartRatio": safeEval(attrs["yStartRatio"]),
208						"yEndRatio": safeEval(attrs["yEndRatio"]),
209						"groupIndex": safeEval(attrs["groupIndex"])
210						}
211					self.ratRanges.append(ratio)
212		elif name == "groups":
213			if not hasattr(self, "groups"):
214				self.groups = []
215			for element in content:
216				if not isinstance(element, tuple):
217					continue
218				name, attrs, content = element
219				if name == "group":
220					if not hasattr(self, "numRecs"):
221						self.numRecs = 1
222					else:
223						self.numRecs += 1
224					group = {}
225					for element in content:
226						if not isinstance(element, tuple):
227							continue
228						name, attrs, content = element
229						if name == "record":
230							yPelHeight = safeEval(attrs["yPelHeight"])
231							yMax = safeEval(attrs["yMax"])
232							yMin = safeEval(attrs["yMin"])
233							group[yPelHeight] = (yMax, yMin)
234					self.groups.append(group)
235