Ejemplo n.º 1
0
    def reorder_blend_args(self, commands, get_delta_func, round_func):
        """
		We first re-order the master coordinate values.
		For a moveto to lineto, the args are now arranged as:
			[ [master_0 x,y], [master_1 x,y], [master_2 x,y] ]
		We re-arrange this to
		[	[master_0 x, master_1 x, master_2 x],
			[master_0 y, master_1 y, master_2 y]
		]
		If the master values are all the same, we collapse the list to
		as single value instead of a list.

		We then convert this to:
		[ [master_0 x] + [x delta tuple] + [numBlends=1]
		  [master_0 y] + [y delta tuple] + [numBlends=1]
		]
		"""
        for cmd in commands:
            # arg[i] is the set of arguments for this operator from master i.
            args = cmd[1]
            m_args = zip(*args)
            # m_args[n] is now all num_master args for the i'th argument
            # for this operation.
            cmd[1] = list(m_args)
        lastOp = None
        for cmd in commands:
            op = cmd[0]
            # masks are represented by two cmd's: first has only op names,
            # second has only args.
            if lastOp in ['hintmask', 'cntrmask']:
                coord = list(cmd[1])
                if not allEqual(coord):
                    raise VarLibMergeError(
                        "Hintmask values cannot differ between source fonts.")
                cmd[1] = [coord[0][0]]
            else:
                coords = cmd[1]
                new_coords = []
                for coord in coords:
                    if allEqual(coord):
                        new_coords.append(coord[0])
                    else:
                        # convert to deltas
                        deltas = get_delta_func(coord)[1:]
                        if round_func:
                            deltas = [round_func(delta) for delta in deltas]
                        coord = [coord[0]] + deltas
                        new_coords.append(coord)
                cmd[1] = new_coords
            lastOp = op
        return commands
Ejemplo n.º 2
0
def _Lookup_PairPosFormat2_subtables_flatten(lst, font):
	assert allEqual([l.ValueFormat2 == 0 for l in lst if l.Class1Record]), "Report bug against fontemon_blender_addon.fontTools."

	self = ot.PairPos()
	self.Format = 2
	self.Coverage = ot.Coverage()
	self.Coverage.Format = 1
	self.ValueFormat1 = reduce(int.__or__, [l.ValueFormat1 for l in lst], 0)
	self.ValueFormat2 = reduce(int.__or__, [l.ValueFormat2 for l in lst], 0)

	# Align them
	glyphs, _ = _merge_GlyphOrders(font,
				       [v.Coverage.glyphs for v in lst])
	self.Coverage.glyphs = glyphs

	matrices = _PairPosFormat2_align_matrices(self, lst, font, transparent=True)

	matrix = self.Class1Record = []
	for rows in zip(*matrices):
		row = ot.Class1Record()
		matrix.append(row)
		row.Class2Record = []
		row = row.Class2Record
		for cols in zip(*list(r.Class2Record for r in rows)):
			col = next(iter(c for c in cols if c is not None))
			row.append(col)

	return self
Ejemplo n.º 3
0
def _PairPosFormat2_merge(self, lst, merger):
	assert allEqual([l.ValueFormat2 == 0 for l in lst if l.Class1Record]), "Report bug against fontemon_blender_addon.fontTools."

	merger.mergeObjects(self, lst,
			    exclude=('Coverage',
				     'ClassDef1', 'Class1Count',
				     'ClassDef2', 'Class2Count',
				     'Class1Record',
				     'ValueFormat1', 'ValueFormat2'))

	# Align coverages
	glyphs, _ = _merge_GlyphOrders(merger.font,
				       [v.Coverage.glyphs for v in lst])
	self.Coverage.glyphs = glyphs

	# Currently, if the coverage of PairPosFormat2 subtables are different,
	# we do NOT bother walking down the subtable list when filling in new
	# rows for alignment.  As such, this is only correct if current subtable
	# is the last subtable in the lookup.  Ensure that.
	#
	# Note that our canonicalization process merges trailing PairPosFormat2's,
	# so in reality this is rare.
	for l,subtables in zip(lst,merger.lookup_subtables):
		if l.Coverage.glyphs != glyphs:
			assert l == subtables[-1]

	matrices = _PairPosFormat2_align_matrices(self, lst, merger.font)

	self.Class1Record = list(matrices[0]) # TODO move merger to be selfless
	merger.mergeLists(self.Class1Record, matrices)
Ejemplo n.º 4
0
def _PairPosFormat1_merge(self, lst, merger):
	assert allEqual([l.ValueFormat2 == 0 for l in lst if l.PairSet]), "Report bug against fontemon_blender_addon.fontTools."

	# Merge everything else; makes sure Format is the same.
	merger.mergeObjects(self, lst,
			    exclude=('Coverage',
				     'PairSet', 'PairSetCount',
				     'ValueFormat1', 'ValueFormat2'))

	empty = ot.PairSet()
	empty.PairValueRecord = []
	empty.PairValueCount = 0

	# Align them
	glyphs, padded = _merge_GlyphOrders(merger.font,
					    [v.Coverage.glyphs for v in lst],
					    [v.PairSet for v in lst],
					    default=empty)

	self.Coverage.glyphs = glyphs
	self.PairSet = [ot.PairSet() for _ in glyphs]
	self.PairSetCount = len(self.PairSet)
	for glyph, ps in zip(glyphs, self.PairSet):
		ps._firstGlyph = glyph

	merger.mergeLists(self.PairSet, padded)
Ejemplo n.º 5
0
def _Lookup_PairPosFormat1_subtables_flatten(lst, font):
	assert allEqual([l.ValueFormat2 == 0 for l in lst if l.PairSet]), "Report bug against fontemon_blender_addon.fontTools."

	self = ot.PairPos()
	self.Format = 1
	self.Coverage = ot.Coverage()
	self.Coverage.Format = 1
	self.ValueFormat1 = reduce(int.__or__, [l.ValueFormat1 for l in lst], 0)
	self.ValueFormat2 = reduce(int.__or__, [l.ValueFormat2 for l in lst], 0)

	# Align them
	glyphs, padded = _merge_GlyphOrders(font,
					    [v.Coverage.glyphs for v in lst],
					    [v.PairSet for v in lst])

	self.Coverage.glyphs = glyphs
	self.PairSet = [_PairSet_flatten([v for v in values if v is not None], font)
		        for values in zip(*padded)]
	self.PairSetCount = len(self.PairSet)
	return self
Ejemplo n.º 6
0
def merge(merger, self, lst):
	if self is None:
		if not allNone(lst):
			raise VarLibMergeError(lst)
		return

	lst = [l.classDefs for l in lst]
	self.classDefs = {}
	# We only care about the .classDefs
	self = self.classDefs

	allKeys = set()
	allKeys.update(*[l.keys() for l in lst])
	for k in allKeys:
		allValues = nonNone(l.get(k) for l in lst)
		if not allEqual(allValues):
			raise VarLibMergeError(allValues)
		if not allValues:
			self[k] = None
		else:
			self[k] = allValues[0]
Ejemplo n.º 7
0
def merge(merger, self, lst):
	subtables = merger.lookup_subtables = [l.SubTable for l in lst]

	# Remove Extension subtables
	for l,sts in list(zip(lst,subtables))+[(self,self.SubTable)]:
		if not sts:
			continue
		if sts[0].__class__.__name__.startswith('Extension'):
			if not allEqual([st.__class__ for st in sts]):
				raise VarLibMergeError(
					"Use of extensions inconsistent between masters: "
					f"{[st.__class__.__name__ for st in sts]}."
				)
			if not allEqual([st.ExtensionLookupType for st in sts]):
				raise VarLibMergeError(
					"Extension lookup type differs between masters: "
					f"{[st.ExtensionLookupType for st in sts]}."
				)
			l.LookupType = sts[0].ExtensionLookupType
			new_sts = [st.ExtSubTable for st in sts]
			del sts[:]
			sts.extend(new_sts)

	isPairPos = self.SubTable and isinstance(self.SubTable[0], ot.PairPos)

	if isPairPos:
		# AFDKO and feaLib sometimes generate two Format1 subtables instead of one.
		# Merge those before continuing.
		# https://github.com/fontemon_blender_addon.fontTools/fontemon_blender_addon.fontTools/issues/719
		self.SubTable = _Lookup_PairPos_subtables_canonicalize(self.SubTable, merger.font)
		subtables = merger.lookup_subtables = [_Lookup_PairPos_subtables_canonicalize(st, merger.font) for st in subtables]
	else:
		isSinglePos = self.SubTable and isinstance(self.SubTable[0], ot.SinglePos)
		if isSinglePos:
			numSubtables = [len(st) for st in subtables]
			if not all([nums == numSubtables[0] for nums in numSubtables]):
				# Flatten list of SinglePos subtables to single Format 2 subtable,
				# with all value records set to the rec format type.
				# We use buildSinglePos() to optimize the lookup after merging.
				valueFormatList = [t.ValueFormat for st in subtables for t in st]
				# Find the minimum value record that can accomodate all the singlePos subtables.
				mirf = reduce(ior, valueFormatList)
				self.SubTable = _Lookup_SinglePos_subtables_flatten(self.SubTable, merger.font, mirf)
				subtables = merger.lookup_subtables = [
					_Lookup_SinglePos_subtables_flatten(st, merger.font, mirf) for st in subtables]
				flattened = True
			else:
				flattened = False

	merger.mergeLists(self.SubTable, subtables)
	self.SubTableCount = len(self.SubTable)

	if isPairPos:
		# If format-1 subtable created during canonicalization is empty, remove it.
		assert len(self.SubTable) >= 1 and self.SubTable[0].Format == 1
		if not self.SubTable[0].Coverage.glyphs:
			self.SubTable.pop(0)
			self.SubTableCount -= 1

		# If format-2 subtable created during canonicalization is empty, remove it.
		assert len(self.SubTable) >= 1 and self.SubTable[-1].Format == 2
		if not self.SubTable[-1].Coverage.glyphs:
			self.SubTable.pop(-1)
			self.SubTableCount -= 1

	elif isSinglePos and flattened:
		singlePosTable = self.SubTable[0]
		glyphs = singlePosTable.Coverage.glyphs
		# We know that singlePosTable is Format 2, as this is set
		# in _Lookup_SinglePos_subtables_flatten.
		singlePosMapping = {
			gname: valRecord
			for gname, valRecord in zip(glyphs, singlePosTable.Value)
		}
		self.SubTable = buildSinglePos(singlePosMapping, merger.font.getReverseGlyphMap())
	merger.mergeObjects(self, lst, exclude=['SubTable', 'SubTableCount'])

	del merger.lookup_subtables
Ejemplo n.º 8
0
def _MarkBasePosFormat1_merge(self, lst, merger, Mark='Mark', Base='Base'):
	self.ClassCount = max(l.ClassCount for l in lst)

	MarkCoverageGlyphs, MarkRecords = \
		_merge_GlyphOrders(merger.font,
				   [getattr(l, Mark+'Coverage').glyphs for l in lst],
				   [getattr(l, Mark+'Array').MarkRecord for l in lst])
	getattr(self, Mark+'Coverage').glyphs = MarkCoverageGlyphs

	BaseCoverageGlyphs, BaseRecords = \
		_merge_GlyphOrders(merger.font,
				   [getattr(l, Base+'Coverage').glyphs for l in lst],
				   [getattr(getattr(l, Base+'Array'), Base+'Record') for l in lst])
	getattr(self, Base+'Coverage').glyphs = BaseCoverageGlyphs

	# MarkArray
	records = []
	for g,glyphRecords in zip(MarkCoverageGlyphs, zip(*MarkRecords)):
		allClasses = [r.Class for r in glyphRecords if r is not None]

		# TODO Right now we require that all marks have same class in
		# all masters that cover them.  This is not required.
		#
		# We can relax that by just requiring that all marks that have
		# the same class in a master, have the same class in every other
		# master.  Indeed, if, say, a sparse master only covers one mark,
		# that mark probably will get class 0, which would possibly be
		# different from its class in other masters.
		#
		# We can even go further and reclassify marks to support any
		# input.  But, since, it's unlikely that two marks being both,
		# say, "top" in one master, and one being "top" and other being
		# "top-right" in another master, we shouldn't do that, as any
		# failures in that case will probably signify mistakes in the
		# input masters.

		if not allEqual(allClasses):
			raise VarLibMergeError(allClasses)
		if not allClasses:
			rec = None
		else:
			rec = ot.MarkRecord()
			rec.Class = allClasses[0]
			allAnchors = [None if r is None else r.MarkAnchor for r in glyphRecords]
			if allNone(allAnchors):
				anchor = None
			else:
				anchor = ot.Anchor()
				anchor.Format = 1
				merger.mergeThings(anchor, allAnchors)
			rec.MarkAnchor = anchor
		records.append(rec)
	array = ot.MarkArray()
	array.MarkRecord = records
	array.MarkCount = len(records)
	setattr(self, Mark+"Array", array)

	# BaseArray
	records = []
	for g,glyphRecords in zip(BaseCoverageGlyphs, zip(*BaseRecords)):
		if allNone(glyphRecords):
			rec = None
		else:
			rec = getattr(ot, Base+'Record')()
			anchors = []
			setattr(rec, Base+'Anchor', anchors)
			glyphAnchors = [[] if r is None else getattr(r, Base+'Anchor')
					for r in glyphRecords]
			for l in glyphAnchors:
				l.extend([None] * (self.ClassCount - len(l)))
			for allAnchors in zip(*glyphAnchors):
				if allNone(allAnchors):
					anchor = None
				else:
					anchor = ot.Anchor()
					anchor.Format = 1
					merger.mergeThings(anchor, allAnchors)
				anchors.append(anchor)
		records.append(rec)
	array = getattr(ot, Base+'Array')()
	setattr(array, Base+'Record', records)
	setattr(array, Base+'Count', len(records))
	setattr(self, Base+'Array', array)
Ejemplo n.º 9
0
def buildVarDevTable(store_builder, master_values):
	if allEqual(master_values):
		return master_values[0], None
	base, varIdx = store_builder.storeMasters(master_values)
	return base, builder.buildVarDevTable(varIdx)
Ejemplo n.º 10
0
def merge_PrivateDicts(top_dicts, vsindex_dict, var_model, fd_map):
    """
	I step through the FontDicts in the FDArray of the varfont TopDict.
	For each varfont FontDict:
		step through each key in FontDict.Private.
		For each key, step through each relevant source font Private dict, and
		build a list of values to blend.
	The 'relevant' source fonts are selected by first getting the right
	submodel using vsindex_dict[vsindex]. The indices of the
	subModel.locations are mapped to source font list indices by
	assuming the latter order is the same as the order of the
	var_model.locations. I can then get the index of each subModel
	location in the list of var_model.locations.
	"""

    topDict = top_dicts[0]
    region_top_dicts = top_dicts[1:]
    if hasattr(region_top_dicts[0], 'FDArray'):
        regionFDArrays = [fdTopDict.FDArray for fdTopDict in region_top_dicts]
    else:
        regionFDArrays = [[fdTopDict] for fdTopDict in region_top_dicts]
    for fd_index, font_dict in enumerate(topDict.FDArray):
        private_dict = font_dict.Private
        vsindex = getattr(private_dict, 'vsindex', 0)
        # At the moment, no PrivateDict has a vsindex key, but let's support
        # how it should work. See comment at end of
        # merge_charstrings() - still need to optimize use of vsindex.
        sub_model, _ = vsindex_dict[vsindex]
        master_indices = []
        for loc in sub_model.locations[1:]:
            i = var_model.locations.index(loc) - 1
            master_indices.append(i)
        pds = [private_dict]
        last_pd = private_dict
        for ri in master_indices:
            pd = get_private(regionFDArrays, fd_index, ri, fd_map)
            # If the region font doesn't have this FontDict, just reference
            # the last one used.
            if pd is None:
                pd = last_pd
            else:
                last_pd = pd
            pds.append(pd)
        num_masters = len(pds)
        for key, value in private_dict.rawDict.items():
            dataList = []
            if key not in pd_blend_fields:
                continue
            if isinstance(value, list):
                try:
                    values = [pd.rawDict[key] for pd in pds]
                except KeyError:
                    print("Warning: {key} in default font Private dict is "
                          "missing from another font, and was "
                          "discarded.".format(key=key))
                    continue
                try:
                    values = zip(*values)
                except IndexError:
                    raise VarLibCFFDictMergeError(key, value, values)
                """
				Row 0 contains the first  value from each master.
				Convert each row from absolute values to relative
				values from the previous row.
				e.g for three masters,	a list of values was:
				master 0 OtherBlues = [-217,-205]
				master 1 OtherBlues = [-234,-222]
				master 1 OtherBlues = [-188,-176]
				The call to zip() converts this to:
				[(-217, -234, -188), (-205, -222, -176)]
				and is converted finally to:
				OtherBlues = [[-217, 17.0, 46.0], [-205, 0.0, 0.0]]
				"""
                prev_val_list = [0] * num_masters
                any_points_differ = False
                for val_list in values:
                    rel_list = [(val - prev_val_list[i])
                                for (i, val) in enumerate(val_list)]
                    if (not any_points_differ) and not allEqual(rel_list):
                        any_points_differ = True
                    prev_val_list = val_list
                    deltas = sub_model.getDeltas(rel_list)
                    # For PrivateDict BlueValues, the default font
                    # values are absolute, not relative to the prior value.
                    deltas[0] = val_list[0]
                    dataList.append(deltas)
                # If there are no blend values,then
                # we can collapse the blend lists.
                if not any_points_differ:
                    dataList = [data[0] for data in dataList]
            else:
                values = [pd.rawDict[key] for pd in pds]
                if not allEqual(values):
                    dataList = sub_model.getDeltas(values)
                else:
                    dataList = values[0]

            # Convert numbers with no decimal part to an int
            if isinstance(dataList, list):
                for i, item in enumerate(dataList):
                    if isinstance(item, list):
                        for j, jtem in enumerate(item):
                            dataList[i][j] = conv_to_int(jtem)
                    else:
                        dataList[i] = conv_to_int(item)
            else:
                dataList = conv_to_int(dataList)

            private_dict.rawDict[key] = dataList
Ejemplo n.º 11
0
def _add_MVAR(font, masterModel, master_ttfs, axisTags):

	log.info("Generating MVAR")

	store_builder = varStore.OnlineVarStoreBuilder(axisTags)

	records = []
	lastTableTag = None
	fontTable = None
	tables = None
	# HACK: we need to special-case post.underlineThickness and .underlinePosition
	# and unilaterally/arbitrarily define a sentinel value to distinguish the case
	# when a post table is present in a given master simply because that's where
	# the glyph names in TrueType must be stored, but the underline values are not
	# meant to be used for building MVAR's deltas. The value of -0x8000 (-36768)
	# the minimum FWord (int16) value, was chosen for its unlikelyhood to appear
	# in real-world underline position/thickness values.
	specialTags = {"unds": -0x8000, "undo": -0x8000}

	for tag, (tableTag, itemName) in sorted(MVAR_ENTRIES.items(), key=lambda kv: kv[1]):
		# For each tag, fetch the associated table from all fonts (or not when we are
		# still looking at a tag from the same tables) and set up the variation model
		# for them.
		if tableTag != lastTableTag:
			tables = fontTable = None
			if tableTag in font:
				fontTable = font[tableTag]
				tables = []
				for master in master_ttfs:
					if tableTag not in master or (
						tag in specialTags
						and getattr(master[tableTag], itemName) == specialTags[tag]
					):
						tables.append(None)
					else:
						tables.append(master[tableTag])
				model, tables = masterModel.getSubModel(tables)
				store_builder.setModel(model)
			lastTableTag = tableTag

		if tables is None:  # Tag not applicable to the master font.
			continue

		# TODO support gasp entries

		master_values = [getattr(table, itemName) for table in tables]
		if models.allEqual(master_values):
			base, varIdx = master_values[0], None
		else:
			base, varIdx = store_builder.storeMasters(master_values)
		setattr(fontTable, itemName, base)

		if varIdx is None:
			continue
		log.info('	%s: %s.%s	%s', tag, tableTag, itemName, master_values)
		rec = ot.MetricsValueRecord()
		rec.ValueTag = tag
		rec.VarIdx = varIdx
		records.append(rec)

	assert "MVAR" not in font
	if records:
		store = store_builder.finish()
		# Optimize
		mapping = store.optimize()
		for rec in records:
			rec.VarIdx = mapping[rec.VarIdx]

		MVAR = font["MVAR"] = newTable('MVAR')
		mvar = MVAR.table = ot.MVAR()
		mvar.Version = 0x00010000
		mvar.Reserved = 0
		mvar.VarStore = store
		# XXX these should not be hard-coded but computed automatically
		mvar.ValueRecordSize = 8
		mvar.ValueRecordCount = len(records)
		mvar.ValueRecord = sorted(records, key=lambda r: r.ValueTag)
Ejemplo n.º 12
0
def _get_advance_metrics(font, masterModel, master_ttfs,
		axisTags, glyphOrder, advMetricses, vOrigMetricses=None):

	vhAdvanceDeltasAndSupports = {}
	vOrigDeltasAndSupports = {}
	for glyph in glyphOrder:
		vhAdvances = [metrics[glyph][0] if glyph in metrics else None for metrics in advMetricses]
		vhAdvanceDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vhAdvances)

	singleModel = models.allEqual(id(v[1]) for v in vhAdvanceDeltasAndSupports.values())

	if vOrigMetricses:
		singleModel = False
		for glyph in glyphOrder:
			# We need to supply a vOrigs tuple with non-None default values
			# for each glyph. vOrigMetricses contains values only for those
			# glyphs which have a non-default vOrig.
			vOrigs = [metrics[glyph] if glyph in metrics else defaultVOrig
				for metrics, defaultVOrig in vOrigMetricses]
			vOrigDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vOrigs)

	directStore = None
	if singleModel:
		# Build direct mapping
		supports = next(iter(vhAdvanceDeltasAndSupports.values()))[1][1:]
		varTupleList = builder.buildVarRegionList(supports, axisTags)
		varTupleIndexes = list(range(len(supports)))
		varData = builder.buildVarData(varTupleIndexes, [], optimize=False)
		for glyphName in glyphOrder:
			varData.addItem(vhAdvanceDeltasAndSupports[glyphName][0])
		varData.optimize()
		directStore = builder.buildVarStore(varTupleList, [varData])

	# Build optimized indirect mapping
	storeBuilder = varStore.OnlineVarStoreBuilder(axisTags)
	advMapping = {}
	for glyphName in glyphOrder:
		deltas, supports = vhAdvanceDeltasAndSupports[glyphName]
		storeBuilder.setSupports(supports)
		advMapping[glyphName] = storeBuilder.storeDeltas(deltas)

	if vOrigMetricses:
		vOrigMap = {}
		for glyphName in glyphOrder:
			deltas, supports = vOrigDeltasAndSupports[glyphName]
			storeBuilder.setSupports(supports)
			vOrigMap[glyphName] = storeBuilder.storeDeltas(deltas)

	indirectStore = storeBuilder.finish()
	mapping2 = indirectStore.optimize()
	advMapping = [mapping2[advMapping[g]] for g in glyphOrder]
	advanceMapping = builder.buildVarIdxMap(advMapping, glyphOrder)

	if vOrigMetricses:
		vOrigMap = [mapping2[vOrigMap[g]] for g in glyphOrder]

	useDirect = False
	vOrigMapping = None
	if directStore:
		# Compile both, see which is more compact

		writer = OTTableWriter()
		directStore.compile(writer, font)
		directSize = len(writer.getAllData())

		writer = OTTableWriter()
		indirectStore.compile(writer, font)
		advanceMapping.compile(writer, font)
		indirectSize = len(writer.getAllData())

		useDirect = directSize < indirectSize

	if useDirect:
		metricsStore = directStore
		advanceMapping = None
	else:
		metricsStore = indirectStore
		if vOrigMetricses:
			vOrigMapping = builder.buildVarIdxMap(vOrigMap, glyphOrder)

	return metricsStore, advanceMapping, vOrigMapping
Ejemplo n.º 13
0
def _merge_TTHinting(font, masterModel, master_ttfs, tolerance=0.5):

	log.info("Merging TT hinting")
	assert "cvar" not in font

	# Check that the existing hinting is compatible

	# fpgm and prep table

	for tag in ("fpgm", "prep"):
		all_pgms = [m[tag].program for m in master_ttfs if tag in m]
		if len(all_pgms) == 0:
			continue
		if tag in font:
			font_pgm = font[tag].program
		else:
			font_pgm = Program()
		if any(pgm != font_pgm for pgm in all_pgms):
			log.warning("Masters have incompatible %s tables, hinting is discarded." % tag)
			_remove_TTHinting(font)
			return

	# glyf table

	for name, glyph in font["glyf"].glyphs.items():
		all_pgms = [
			m["glyf"][name].program
			for m in master_ttfs
			if name in m['glyf'] and hasattr(m["glyf"][name], "program")
		]
		if not any(all_pgms):
			continue
		glyph.expand(font["glyf"])
		if hasattr(glyph, "program"):
			font_pgm = glyph.program
		else:
			font_pgm = Program()
		if any(pgm != font_pgm for pgm in all_pgms if pgm):
			log.warning("Masters have incompatible glyph programs in glyph '%s', hinting is discarded." % name)
			# TODO Only drop hinting from this glyph.
			_remove_TTHinting(font)
			return

	# cvt table

	all_cvs = [Vector(m["cvt "].values) if 'cvt ' in m else None
		   for m in master_ttfs]

	nonNone_cvs = models.nonNone(all_cvs)
	if not nonNone_cvs:
		# There is no cvt table to make a cvar table from, we're done here.
		return

	if not models.allEqual(len(c) for c in nonNone_cvs):
		log.warning("Masters have incompatible cvt tables, hinting is discarded.")
		_remove_TTHinting(font)
		return

	variations = []
	deltas, supports = masterModel.getDeltasAndSupports(all_cvs)
	for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])):
		delta = [otRound(d) for d in delta]
		if all(abs(v) <= tolerance for v in delta):
			continue
		var = TupleVariation(support, delta)
		variations.append(var)

	# We can build the cvar table now.
	if variations:
		cvar = font["cvar"] = newTable('cvar')
		cvar.version = 1
		cvar.variations = variations
Ejemplo n.º 14
0
def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
	if tolerance < 0:
		raise ValueError("`tolerance` must be a positive number.")

	log.info("Generating gvar")
	assert "gvar" not in font
	gvar = font["gvar"] = newTable('gvar')
	glyf = font['glyf']
	defaultMasterIndex = masterModel.reverseMapping[0]

	# use hhea.ascent of base master as default vertical origin when vmtx is missing
	baseAscent = font['hhea'].ascent
	for glyph in font.getGlyphOrder():

		isComposite = glyf[glyph].isComposite()

		allData = [
			m["glyf"].getCoordinatesAndControls(glyph, m, defaultVerticalOrigin=baseAscent)
			for m in master_ttfs
		]

		if allData[defaultMasterIndex][1].numberOfContours != 0:
			# If the default master is not empty, interpret empty non-default masters
			# as missing glyphs from a sparse master
			allData = [
				d if d is not None and d[1].numberOfContours != 0 else None
				for d in allData
			]

		model, allData = masterModel.getSubModel(allData)

		allCoords = [d[0] for d in allData]
		allControls = [d[1] for d in allData]
		control = allControls[0]
		if not models.allEqual(allControls):
			log.warning("glyph %s has incompatible masters; skipping" % glyph)
			continue
		del allControls

		# Update gvar
		gvar.variations[glyph] = []
		deltas = model.getDeltas(allCoords)
		supports = model.supports
		assert len(deltas) == len(supports)

		# Prepare for IUP optimization
		origCoords = deltas[0]
		endPts = control.endPts

		for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])):
			if all(abs(v) <= tolerance for v in delta.array) and not isComposite:
				continue
			var = TupleVariation(support, delta)
			if optimize:
				delta_opt = iup_delta_optimize(delta, origCoords, endPts, tolerance=tolerance)

				if None in delta_opt:
					"""In composite glyphs, there should be one 0 entry
					to make sure the gvar entry is written to the font.

					This is to work around an issue with macOS 10.14 and can be
					removed once the behaviour of macOS is changed.

					https://github.com/fontemon_blender_addon.fontTools/fontemon_blender_addon.fontTools/issues/1381
					"""
					if all(d is None for d in delta_opt):
						delta_opt = [(0, 0)] + [None] * (len(delta_opt) - 1)
					# Use "optimized" version only if smaller...
					var_opt = TupleVariation(support, delta_opt)

					axis_tags = sorted(support.keys()) # Shouldn't matter that this is different from fvar...?
					tupleData, auxData, _ = var.compile(axis_tags, [], None)
					unoptimized_len = len(tupleData) + len(auxData)
					tupleData, auxData, _ = var_opt.compile(axis_tags, [], None)
					optimized_len = len(tupleData) + len(auxData)

					if optimized_len < unoptimized_len:
						var = var_opt

			gvar.variations[glyph].append(var)