Beispiel #1
0
def merge(merger, self, lst):
	assert self.Format == 1
	XCoords = [a.XCoordinate for a in lst]
	YCoords = [a.YCoordinate for a in lst]
	model = merger.model
	scalars = merger.scalars
	self.XCoordinate = otRound(model.interpolateFromMastersAndScalars(XCoords, scalars))
	self.YCoordinate = otRound(model.interpolateFromMastersAndScalars(YCoords, scalars))
Beispiel #2
0
def merge(merger, self, lst):
	assert self.Format == 1
	XCoords = [a.XCoordinate for a in lst]
	YCoords = [a.YCoordinate for a in lst]
	model = merger.model
	scalars = merger.scalars
	self.XCoordinate = otRound(model.interpolateFromMastersAndScalars(XCoords, scalars))
	self.YCoordinate = otRound(model.interpolateFromMastersAndScalars(YCoords, scalars))
Beispiel #3
0
 def _marksAsAST(self):
     return [
         [
             (ast.Anchor(x=otRound(anchor.x), y=otRound(anchor.y)), anchor.markClass)
             for anchor in sorted(component, key=lambda a: a.name)
         ]
         for component in self.marks
     ]
Beispiel #4
0
def transformAnchor(anchor, matrix):
    if not anchor:
        return anchor

    from fontTools.misc.fixedTools import otRound
    anchor.x, anchor.y = matrix.transformPoint((anchor.x, anchor.y))
    anchor.x = otRound(anchor.x)
    anchor.y = otRound(anchor.y)

    return anchor
	def roundDeltas(self):
		coordWidth = self.getCoordWidth()
		self.coordinates = [
			None
			if d is None
			else otRound(d)
			if coordWidth == 1
			else (otRound(d[0]), otRound(d[1]))
			for d in self.coordinates
		]
Beispiel #6
0
def replace(match, factor):
    pos = Parser(StringIO(match.group(0))).parse_pos_()

    if not any(pos):
        return match.group(0)

    adv, dx, dy, adv_adjust_by, dx_adjust_by, dy_adjust_by = pos

    ret = ""
    if adv is not None:
        adv = otRound(adv * factor)
        ret += f" ADV {adv:g}"
        for at, adjust_by in adv_adjust_by.items():
            at, adjust_by = otRound(at * factor), otRound(adjust_by * factor)
            ret += f" ADJUST_BY {adjust_by} AT {at}"

    if dx is not None:
        dx = otRound(dx * factor)
        ret += f" DX {dx:g}"
        for at, adjust_by in dx_adjust_by.items():
            at, adjust_by = otRound(at * factor), otRound(adjust_by * factor)
            ret += f" ADJUST_BY {adjust_by} AT {at}"

    if dy is not None:
        dy = otRound(dy * factor)
        ret += f" DY {dy:g}"
        for at, adjust_by in dy_adjust_by.items():
            at, adjust_by = otRound(at * factor), otRound(adjust_by * factor)
            ret += f" ADJUST_BY {adjust_by} AT {at}"

    return f"POS{ret} END_POS"
Beispiel #7
0
    def build_hmtx(self):
        ctx = self.ctx
        data = self.metadataProvider
        self.otf["hmtx"] = hmtx = ttLib.newTable("hmtx")
        hmtx.metrics = {}

        for glyphName, glyph in self.glyphMap.items():
            width = otRound(data.glyph_width(ctx, glyph))
            bounds = self.glyphBoundsMap[glyphName]
            left = otRound(bounds.left) if not bounds.empty else 0

            hmtx[glyphName] = (width, left)
Beispiel #8
0
	def _setCoordinates(self, glyphName, coord, hMetrics, vMetrics=None):
		"""Set coordinates and metrics for the given glyph.

		"coord" is an array of GlyphCoordinates which must include the "phantom
		points" as the last four coordinates.

		Both the horizontal/vertical advances and left/top sidebearings in "hmtx"
		and "vmtx" tables (if any) are updated from four phantom points and
		the glyph's bounding boxes.

		The "hMetrics" and vMetrics are used to propagate "phantom points"
		into "hmtx" and "vmtx" tables if desired.  (see the "_getPhantomPoints"
		method).
		"""
		glyph = self[glyphName]

		# Handle phantom points for (left, right, top, bottom) positions.
		assert len(coord) >= 4
		leftSideX = coord[-4][0]
		rightSideX = coord[-3][0]
		topSideY = coord[-2][1]
		bottomSideY = coord[-1][1]

		coord = coord[:-4]

		if glyph.isComposite():
			assert len(coord) == len(glyph.components)
			for p, comp in zip(coord, glyph.components):
				if hasattr(comp, 'x'):
					comp.x, comp.y = p
		elif glyph.numberOfContours == 0:
			assert len(coord) == 0
		else:
			assert len(coord) == len(glyph.coordinates)
			glyph.coordinates = GlyphCoordinates(coord)

		glyph.recalcBounds(self)

		horizontalAdvanceWidth = otRound(rightSideX - leftSideX)
		if horizontalAdvanceWidth < 0:
			# unlikely, but it can happen, see:
			# https://github.com/fonttools/fonttools/pull/1198
			horizontalAdvanceWidth = 0
		leftSideBearing = otRound(glyph.xMin - leftSideX)
		hMetrics[glyphName] = horizontalAdvanceWidth, leftSideBearing

		if vMetrics is not None:
			verticalAdvanceWidth = otRound(topSideY - bottomSideY)
			if verticalAdvanceWidth < 0:  # unlikely but do the same as horizontal
				verticalAdvanceWidth = 0
			topSideBearing = otRound(topSideY - glyph.yMax)
			vMetrics[glyphName] = verticalAdvanceWidth, topSideBearing
Beispiel #9
0
 def get_origin_height(self, font, origin):
     if origin is self.Origin.BASELINE:
         return 0
     elif origin is self.Origin.CAP_HEIGHT:
         return getAttrWithFallback(font.info, "capHeight")
     elif origin is self.Origin.HALF_CAP_HEIGHT:
         return otRound(getAttrWithFallback(font.info, "capHeight") / 2)
     elif origin is self.Origin.X_HEIGHT:
         return getAttrWithFallback(font.info, "xHeight")
     elif origin is self.Origin.HALF_X_HEIGHT:
         return otRound(getAttrWithFallback(font.info, "xHeight") / 2)
     else:
         raise AssertionError(origin)
Beispiel #10
0
 def get_origin_height(self, font, origin):
     if origin is self.Origin.BASELINE:
         return 0
     elif origin is self.Origin.CAP_HEIGHT:
         return font.info.capHeight
     elif origin is self.Origin.HALF_CAP_HEIGHT:
         return otRound(font.info.capHeight / 2)
     elif origin is self.Origin.X_HEIGHT:
         return font.info.xHeight
     elif origin is self.Origin.HALF_X_HEIGHT:
         return otRound(font.info.xHeight / 2)
     else:
         raise AssertionError(origin)
Beispiel #11
0
    def compile(self, ttFont):
        metrics = []
        hasNegativeAdvances = False
        for glyphName in ttFont.getGlyphOrder():
            advanceWidth, sideBearing = self.metrics[glyphName]
            if advanceWidth < 0:
                log.error("Glyph %r has negative advance %s" %
                          (glyphName, self.advanceName))
                hasNegativeAdvances = True
            metrics.append([advanceWidth, sideBearing])

        headerTable = ttFont.get(self.headerTag)
        if headerTable is not None:
            lastAdvance = metrics[-1][0]
            lastIndex = len(metrics)
            while metrics[lastIndex - 2][0] == lastAdvance:
                lastIndex -= 1
                if lastIndex <= 1:
                    # all advances are equal
                    lastIndex = 1
                    break
            additionalMetrics = metrics[lastIndex:]
            additionalMetrics = [otRound(sb) for _, sb in additionalMetrics]
            metrics = metrics[:lastIndex]
            numberOfMetrics = len(metrics)
            setattr(headerTable, self.numberOfMetricsName, numberOfMetrics)
        else:
            # no hhea/vhea, can't store numberOfMetrics; assume == numGlyphs
            numberOfMetrics = ttFont["maxp"].numGlyphs
            additionalMetrics = []

        allMetrics = []
        for advance, sb in metrics:
            allMetrics.extend([otRound(advance), otRound(sb)])
        metricsFmt = ">" + self.longMetricFormat * numberOfMetrics
        try:
            data = struct.pack(metricsFmt, *allMetrics)
        except struct.error as e:
            if "out of range" in str(e) and hasNegativeAdvances:
                raise ttLib.TTLibError(
                    "'%s' table can't contain negative advance %ss" %
                    (self.tableTag, self.advanceName))
            else:
                raise
        additionalMetrics = array.array("h", additionalMetrics)
        if sys.byteorder != "big": additionalMetrics.byteswap()
        data = data + additionalMetrics.tostring()
        return data
Beispiel #12
0
	def compile(self, ttFont):
		metrics = []
		hasNegativeAdvances = False
		for glyphName in ttFont.getGlyphOrder():
			advanceWidth, sideBearing = self.metrics[glyphName]
			if advanceWidth < 0:
				log.error("Glyph %r has negative advance %s" % (
					glyphName, self.advanceName))
				hasNegativeAdvances = True
			metrics.append([advanceWidth, sideBearing])

		headerTable = ttFont.get(self.headerTag)
		if headerTable is not None:
			lastAdvance = metrics[-1][0]
			lastIndex = len(metrics)
			while metrics[lastIndex-2][0] == lastAdvance:
				lastIndex -= 1
				if lastIndex <= 1:
					# all advances are equal
					lastIndex = 1
					break
			additionalMetrics = metrics[lastIndex:]
			additionalMetrics = [otRound(sb) for _, sb in additionalMetrics]
			metrics = metrics[:lastIndex]
			numberOfMetrics = len(metrics)
			setattr(headerTable, self.numberOfMetricsName, numberOfMetrics)
		else:
			# no hhea/vhea, can't store numberOfMetrics; assume == numGlyphs
			numberOfMetrics = ttFont["maxp"].numGlyphs
			additionalMetrics = []

		allMetrics = []
		for advance, sb in metrics:
			allMetrics.extend([otRound(advance), otRound(sb)])
		metricsFmt = ">" + self.longMetricFormat * numberOfMetrics
		try:
			data = struct.pack(metricsFmt, *allMetrics)
		except struct.error as e:
			if "out of range" in str(e) and hasNegativeAdvances:
				raise ttLib.TTLibError(
					"'%s' table can't contain negative advance %ss"
					% (self.tableTag, self.advanceName))
			else:
				raise
		additionalMetrics = array.array("h", additionalMetrics)
		if sys.byteorder != "big": additionalMetrics.byteswap()
		data = data + additionalMetrics.tostring()
		return data
Beispiel #13
0
	def storeDeltas(self, deltas):
		# Pity that this exists here, since VarData_addItem
		# does the same.  But to look into our cache, it's
		# good to adjust deltas here as well...
		deltas = [otRound(d) for d in deltas]
		if len(deltas) == len(self._supports) + 1:
			deltas = tuple(deltas[1:])
		else:
			assert len(deltas) == len(self._supports)
			deltas = tuple(deltas)

		varIdx = self._cache.get(deltas)
		if varIdx is not None:
			return varIdx

		if not self._data:
			self._add_VarData()
		inner = len(self._data.Item)
		if inner == 0xFFFF:
			# Full array. Start new one.
			self._add_VarData()
			return self.storeDeltas(deltas)
		self._data.addItem(deltas)

		varIdx = (self._outer << 16) + inner
		self._cache[deltas] = varIdx
		return varIdx
 def encodeDeltaRunAsBytes_(deltas, offset, stream):
     runLength = 0
     pos = offset
     numDeltas = len(deltas)
     while pos < numDeltas and runLength < 64:
         value = deltas[pos]
         if value < -128 or value > 127:
             break
         # Within a byte-encoded run of deltas, a single zero
         # is best stored literally as 0x00 value. However,
         # if are two or more zeroes in a sequence, it is
         # better to start a new run. For example, the sequence
         # of deltas [15, 15, 0, 15, 15] becomes 6 bytes
         # (04 0F 0F 00 0F 0F) when storing the zero value
         # literally, but 7 bytes (01 0F 0F 80 01 0F 0F)
         # when starting a new run.
         if value == 0 and pos + 1 < numDeltas and deltas[pos + 1] == 0:
             break
         pos += 1
         runLength += 1
     assert runLength >= 1 and runLength <= 64
     stream.write(bytechr(runLength - 1))
     for i in range(offset, pos):
         stream.write(struct.pack('b', otRound(deltas[i])))
     return pos
Beispiel #15
0
	def storeDeltas(self, deltas):
		# Pity that this exists here, since VarData_addItem
		# does the same.  But to look into our cache, it's
		# good to adjust deltas here as well...
		deltas = [otRound(d) for d in deltas]
		if len(deltas) == len(self._supports) + 1:
			deltas = tuple(deltas[1:])
		else:
			assert len(deltas) == len(self._supports)
			deltas = tuple(deltas)

		varIdx = self._cache.get(deltas)
		if varIdx is not None:
			return varIdx

		if not self._data:
			self._add_VarData()
		inner = len(self._data.Item)
		if inner == 0xFFFF:
			# Full array. Start new one.
			self._add_VarData()
			return self.storeDeltas(deltas)
		self._data.addItem(deltas)

		varIdx = (self._outer << 16) + inner
		self._cache[deltas] = varIdx
		return varIdx
Beispiel #16
0
	def toInt(self):
		if not self.isFloat():
			return
		a = array.array("h")
		for n in self._a:
			a.append(otRound(n))
		self._a = a
Beispiel #17
0
	def toInt(self):
		if not self.isFloat():
			return
		a = array.array("h")
		for n in self._a:
			a.append(otRound(n))
		self._a = a
Beispiel #18
0
	def encodeDeltaRunAsBytes_(deltas, offset, stream):
		runLength = 0
		pos = offset
		numDeltas = len(deltas)
		while pos < numDeltas and runLength < 64:
			value = deltas[pos]
			if value < -128 or value > 127:
				break
			# Within a byte-encoded run of deltas, a single zero
			# is best stored literally as 0x00 value. However,
			# if are two or more zeroes in a sequence, it is
			# better to start a new run. For example, the sequence
			# of deltas [15, 15, 0, 15, 15] becomes 6 bytes
			# (04 0F 0F 00 0F 0F) when storing the zero value
			# literally, but 7 bytes (01 0F 0F 80 01 0F 0F)
			# when starting a new run.
			if value == 0 and pos+1 < numDeltas and deltas[pos+1] == 0:
				break
			pos += 1
			runLength += 1
		assert runLength >= 1 and runLength <= 64
		stream.write(bytechr(runLength - 1))
		for i in range(offset, pos):
			stream.write(struct.pack('b', otRound(deltas[i])))
		return pos
Beispiel #19
0
	def encodeDeltaRunAsWords_(deltas, offset, stream):
		runLength = 0
		pos = offset
		numDeltas = len(deltas)
		while pos < numDeltas and runLength < 64:
			value = deltas[pos]
			# Within a word-encoded run of deltas, it is easiest
			# to start a new run (with a different encoding)
			# whenever we encounter a zero value. For example,
			# the sequence [0x6666, 0, 0x7777] needs 7 bytes when
			# storing the zero literally (42 66 66 00 00 77 77),
			# and equally 7 bytes when starting a new run
			# (40 66 66 80 40 77 77).
			if value == 0:
				break

			# Within a word-encoded run of deltas, a single value
			# in the range (-128..127) should be encoded literally
			# because it is more compact. For example, the sequence
			# [0x6666, 2, 0x7777] becomes 7 bytes when storing
			# the value literally (42 66 66 00 02 77 77), but 8 bytes
			# when starting a new run (40 66 66 00 02 40 77 77).
			isByteEncodable = lambda value: value >= -128 and value <= 127
			if isByteEncodable(value) and pos+1 < numDeltas and isByteEncodable(deltas[pos+1]):
				break
			pos += 1
			runLength += 1
		assert runLength >= 1 and runLength <= 64
		stream.write(bytechr(DELTAS_ARE_WORDS | (runLength - 1)))
		for i in range(offset, pos):
			stream.write(struct.pack('>h', otRound(deltas[i])))
		return pos
Beispiel #20
0
    def build_post(self):
        ctx = self.ctx
        data = self.metadataProvider
        self.otf["post"] = post = ttLib.newTable("post")
        post.formatType = 3.0

        post.italicAngle = data.italicAngle(ctx)

        post.underlinePosition = otRound(data.post_underlinePosition(ctx))
        post.underlineThickness = otRound(data.post_underlineThickness(ctx))
        post.isFixedPitch = data.post_isFixedPitch(ctx)

        post.minMemType42 = 0
        post.maxMemType42 = 0
        post.minMemType1 = 0
        post.maxMemType1 = 0
Beispiel #21
0
def merge(merger, self, lst):

    # All other structs are merged with self pointing to a copy of base font,
    # except for ValueRecords which are sometimes created later and initialized
    # to have 0/None members.  Hence the copy.
    self.__dict__ = lst[0].__dict__.copy()

    instancer = merger.instancer
    # TODO Handle differing valueformats
    for name, tableName in [('XAdvance', 'XAdvDevice'),
                            ('YAdvance', 'YAdvDevice'),
                            ('XPlacement', 'XPlaDevice'),
                            ('YPlacement', 'YPlaDevice')]:

        if not hasattr(self, tableName):
            continue
        dev = getattr(self, tableName)
        delattr(self, tableName)
        if dev is None:
            continue

        assert dev.DeltaFormat == 0x8000
        varidx = (dev.StartSize << 16) + dev.EndSize
        delta = otRound(instancer[varidx])

        setattr(self, name, getattr(self, name) + delta)
Beispiel #22
0
def merge(merger, self, lst):

	# Hack till we become selfless.
	self.__dict__ = lst[0].__dict__.copy()

	if self.Format != 3:
		return

	instancer = merger.instancer
	for v in "XY":
		tableName = v+'DeviceTable'
		if not hasattr(self, tableName):
			continue
		dev = getattr(self, tableName)
		delattr(self, tableName)
		if dev is None:
			continue

		assert dev.DeltaFormat == 0x8000
		varidx = (dev.StartSize << 16) + dev.EndSize
		delta = otRound(instancer[varidx])

		attr = v+'Coordinate'
		setattr(self, attr, getattr(self, attr) + delta)

	self.Format = 1
Beispiel #23
0
def interpolate_cff2_charstrings(topDict, interpolateFromDeltas, glyphOrder):
	charstrings = topDict.CharStrings
	for gname in glyphOrder:
		# Interpolate charstring
		charstring = charstrings[gname]
		pd = charstring.private
		vsindex = pd.vsindex if (hasattr(pd, 'vsindex')) else 0
		num_regions = pd.getNumRegions(vsindex)
		numMasters = num_regions + 1
		new_program = []
		last_i = 0
		for i, token in enumerate(charstring.program):
			if token == 'blend':
				num_args = charstring.program[i - 1]
				""" The stack is now:
				..args for following operations
				num_args values  from the default font
				num_args tuples, each with numMasters-1 delta values
				num_blend_args
				'blend'
				"""
				argi = i - (num_args*numMasters + 1)
				end_args = tuplei = argi + num_args
				while argi < end_args:
					next_ti = tuplei + num_regions
					deltas = charstring.program[tuplei:next_ti]
					delta = interpolateFromDeltas(vsindex, deltas)
					charstring.program[argi] += otRound(delta)
					tuplei = next_ti
					argi += 1
				new_program.extend(charstring.program[last_i:end_args])
				last_i = i + 1
		if last_i != 0:
			new_program.extend(charstring.program[last_i:])
			charstring.program = new_program
Beispiel #24
0
def merge(merger, self, lst):

    # Hack till we become selfless.
    self.__dict__ = lst[0].__dict__.copy()

    if self.Format != 3:
        return

    instancer = merger.instancer
    for v in "XY":
        tableName = v + 'DeviceTable'
        if not hasattr(self, tableName):
            continue
        dev = getattr(self, tableName)
        if merger.deleteVariations:
            delattr(self, tableName)
        if dev is None:
            continue

        assert dev.DeltaFormat == 0x8000
        varidx = (dev.StartSize << 16) + dev.EndSize
        delta = otRound(instancer[varidx])

        attr = v + 'Coordinate'
        setattr(self, attr, getattr(self, attr) + delta)

    if merger.deleteVariations:
        self.Format = 1
Beispiel #25
0
    def draw_charstring(self, glyph, private, globalSubrs):
        ctx = self.ctx
        data = self.metadataProvider
        layer = data.glyph_layer(ctx, glyph)

        width = data.layer_width(ctx, layer)
        defaultWidth = private.defaultWidthX
        nominalWidth = private.nominalWidthX
        if width == defaultWidth:
            # if width equals the default it can be omitted from charstring
            width = None
        else:
            # subtract the nominal width
            width -= nominalWidth
        if width is not None:
            width = otRound(width)

        pen = T2CharStringPen(width,
                              self.glyphOrder,
                              roundTolerance=self.roundTolerance)
        drawing.draw_layer(layer, pen)
        charString = pen.getCharString(private,
                                       globalSubrs,
                                       optimize=self.optimizeCFF)
        return charString
Beispiel #26
0
def interpolate_cff2_charstrings(topDict, interpolateFromDeltas, glyphOrder):
	charstrings = topDict.CharStrings
	for gname in glyphOrder:
		# Interpolate charstring
		charstring = charstrings[gname]
		pd = charstring.private
		vsindex = pd.vsindex if (hasattr(pd, 'vsindex')) else 0
		num_regions = pd.getNumRegions(vsindex)
		numMasters = num_regions + 1
		new_program = []
		last_i = 0
		for i, token in enumerate(charstring.program):
			if token == 'blend':
				num_args = charstring.program[i - 1]
				""" The stack is now:
				..args for following operations
				num_args values  from the default font
				num_args tuples, each with numMasters-1 delta values
				num_blend_args
				'blend'
				"""
				argi = i - (num_args*numMasters + 1)
				end_args = tuplei = argi + num_args
				while argi < end_args:
					next_ti = tuplei + num_regions
					deltas = charstring.program[tuplei:next_ti]
					delta = interpolateFromDeltas(vsindex, deltas)
					charstring.program[argi] += otRound(delta)
					tuplei = next_ti
					argi += 1
				new_program.extend(charstring.program[last_i:end_args])
				last_i = i + 1
		if last_i != 0:
			new_program.extend(charstring.program[last_i:])
			charstring.program = new_program
    def encodeDeltaRunAsWords_(deltas, offset, stream):
        runLength = 0
        pos = offset
        numDeltas = len(deltas)
        while pos < numDeltas and runLength < 64:
            value = deltas[pos]
            # Within a word-encoded run of deltas, it is easiest
            # to start a new run (with a different encoding)
            # whenever we encounter a zero value. For example,
            # the sequence [0x6666, 0, 0x7777] needs 7 bytes when
            # storing the zero literally (42 66 66 00 00 77 77),
            # and equally 7 bytes when starting a new run
            # (40 66 66 80 40 77 77).
            if value == 0:
                break

            # Within a word-encoded run of deltas, a single value
            # in the range (-128..127) should be encoded literally
            # because it is more compact. For example, the sequence
            # [0x6666, 2, 0x7777] becomes 7 bytes when storing
            # the value literally (42 66 66 00 02 77 77), but 8 bytes
            # when starting a new run (40 66 66 00 02 40 77 77).
            isByteEncodable = lambda value: value >= -128 and value <= 127
            if isByteEncodable(
                    value) and pos + 1 < numDeltas and isByteEncodable(
                        deltas[pos + 1]):
                break
            pos += 1
            runLength += 1
        assert runLength >= 1 and runLength <= 64
        stream.write(bytechr(DELTAS_ARE_WORDS | (runLength - 1)))
        for i in range(offset, pos):
            stream.write(struct.pack('>h', otRound(deltas[i])))
        return pos
Beispiel #28
0
def encodeFixed(f, pack=struct.pack):
	"""For T2 only"""
	value = otRound(f * 65536)  # convert the float to fixed point
	if value & 0xFFFF == 0:  # check if the fractional part is zero
		return encodeIntT2(value >> 16)  # encode only the integer part
	else:
		return b"\xff" + pack(">l", value)  # encode the entire fixed point value
Beispiel #29
0
def encodeFixed(f, pack=struct.pack):
	"""For T2 only"""
	value = otRound(f * 65536)  # convert the float to fixed point
	if value & 0xFFFF == 0:  # check if the fractional part is zero
		return encodeIntT2(value >> 16)  # encode only the integer part
	else:
		return b"\xff" + pack(">l", value)  # encode the entire fixed point value
Beispiel #30
0
def fix_isFixedPitch(ttfont):

    same_width = set()
    glyph_metrics = ttfont['hmtx'].metrics
    for character in [chr(c) for c in range(65, 91)]:
        same_width.add(glyph_metrics[character][0])

    if len(same_width) == 1:
        if ttfont['post'].isFixedPitch == 1:
            print("Skipping isFixedPitch is set correctly")
        else:
            print("Font is monospace. Updating isFixedPitch to 0")
            ttfont['post'].isFixedPitch = 1

        familyType = ttfont['OS/2'].panose.bFamilyType
        if familyType == 2:
            expected = 9
        elif familyType == 3 or familyType == 5:
            expected = 3
        elif familyType == 0:
            print(
                "Font is monospace but panose fields seems to be not set."
                " Setting values to defaults (FamilyType = 2, Proportion = 9)."
            )
            ttfont['OS/2'].panose.bFamilyType = 2
            ttfont['OS/2'].panose.bProportion = 9
            expected = None
        else:
            expected = None

        if expected:
            if ttfont['OS/2'].panose.bProportion == expected:
                print("Skipping OS/2.panose.bProportion is set correctly")
            else:
                print(("Font is monospace."
                       " Since OS/2.panose.bFamilyType is {}"
                       " we're updating OS/2.panose.bProportion"
                       " to {}").format(familyType, expected))
                ttfont['OS/2'].panose.bProportion = expected

        widths = [m[0] for m in ttfont['hmtx'].metrics.values() if m[0] > 0]
        width_max = max(widths)
        if ttfont['hhea'].advanceWidthMax == width_max:
            print("Skipping hhea.advanceWidthMax is set correctly")
        else:
            print("Font is monospace. Updating hhea.advanceWidthMax to %i" %
                  width_max)
            ttfont['hhea'].advanceWidthMax = width_max

        avg_width = otRound(sum(widths) / len(widths))
        if avg_width == ttfont['OS/2'].xAvgCharWidth:
            print("Skipping OS/2.xAvgCharWidth is set correctly")
        else:
            print("Font is monospace. Updating OS/2.xAvgCharWidth to %i" %
                  avg_width)
            ttfont['OS/2'].xAvgCharWidth = avg_width
    else:
        ttfont['post'].isFixedPitch = 0
        ttfont['OS/2'].panose.bProportion = 0
Beispiel #31
0
	def compile(self, more, haveInstructions, glyfTable):
		data = b""

		# reset all flags we will calculate ourselves
		flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
				NON_OVERLAPPING | OVERLAP_COMPOUND)
		if more:
			flags = flags | MORE_COMPONENTS
		if haveInstructions:
			flags = flags | WE_HAVE_INSTRUCTIONS

		if hasattr(self, "firstPt"):
			if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255):
				data = data + struct.pack(">BB", self.firstPt, self.secondPt)
			else:
				data = data + struct.pack(">HH", self.firstPt, self.secondPt)
				flags = flags | ARG_1_AND_2_ARE_WORDS
		else:
			x = otRound(self.x)
			y = otRound(self.y)
			flags = flags | ARGS_ARE_XY_VALUES
			if (-128 <= x <= 127) and (-128 <= y <= 127):
				data = data + struct.pack(">bb", x, y)
			else:
				data = data + struct.pack(">hh", x, y)
				flags = flags | ARG_1_AND_2_ARE_WORDS

		if hasattr(self, "transform"):
			transform = [[fl2fi(x,14) for x in row] for row in self.transform]
			if transform[0][1] or transform[1][0]:
				flags = flags | WE_HAVE_A_TWO_BY_TWO
				data = data + struct.pack(">hhhh",
						transform[0][0], transform[0][1],
						transform[1][0], transform[1][1])
			elif transform[0][0] != transform[1][1]:
				flags = flags | WE_HAVE_AN_X_AND_Y_SCALE
				data = data + struct.pack(">hh",
						transform[0][0], transform[1][1])
			else:
				flags = flags | WE_HAVE_A_SCALE
				data = data + struct.pack(">h",
						transform[0][0])

		glyphID = glyfTable.getGlyphID(self.glyphName)
		return struct.pack(">HH", flags, glyphID) + data
Beispiel #32
0
	def compile(self, more, haveInstructions, glyfTable):
		data = b""

		# reset all flags we will calculate ourselves
		flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
				SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
				NON_OVERLAPPING | OVERLAP_COMPOUND)
		if more:
			flags = flags | MORE_COMPONENTS
		if haveInstructions:
			flags = flags | WE_HAVE_INSTRUCTIONS

		if hasattr(self, "firstPt"):
			if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255):
				data = data + struct.pack(">BB", self.firstPt, self.secondPt)
			else:
				data = data + struct.pack(">HH", self.firstPt, self.secondPt)
				flags = flags | ARG_1_AND_2_ARE_WORDS
		else:
			x = otRound(self.x)
			y = otRound(self.y)
			flags = flags | ARGS_ARE_XY_VALUES
			if (-128 <= x <= 127) and (-128 <= y <= 127):
				data = data + struct.pack(">bb", x, y)
			else:
				data = data + struct.pack(">hh", x, y)
				flags = flags | ARG_1_AND_2_ARE_WORDS

		if hasattr(self, "transform"):
			transform = [[fl2fi(x,14) for x in row] for row in self.transform]
			if transform[0][1] or transform[1][0]:
				flags = flags | WE_HAVE_A_TWO_BY_TWO
				data = data + struct.pack(">hhhh",
						transform[0][0], transform[0][1],
						transform[1][0], transform[1][1])
			elif transform[0][0] != transform[1][1]:
				flags = flags | WE_HAVE_AN_X_AND_Y_SCALE
				data = data + struct.pack(">hh",
						transform[0][0], transform[1][1])
			else:
				flags = flags | WE_HAVE_A_SCALE
				data = data + struct.pack(">h",
						transform[0][0])

		glyphID = glyfTable.getGlyphID(self.glyphName)
		return struct.pack(">HH", flags, glyphID) + data
Beispiel #33
0
 def test_double_precision_float(self):
     # https://github.com/fonttools/fonttools/issues/963
     afloat = 242.50000000000003
     g = GlyphCoordinates([(afloat, 0)])
     g.toInt()
     # this would return 242 if the internal array.array typecode is 'f',
     # since the Python float is truncated to a C float.
     # when using typecode 'd' it should return the correct value 243
     assert g[0][0] == otRound(afloat)
Beispiel #34
0
 def test_double_precision_float(self):
     # https://github.com/fonttools/fonttools/issues/963
     afloat = 242.50000000000003
     g = GlyphCoordinates([(afloat, 0)])
     g.toInt()
     # this would return 242 if the internal array.array typecode is 'f',
     # since the Python float is truncated to a C float.
     # when using typecode 'd' it should return the correct value 243
     assert g[0][0] == otRound(afloat)
Beispiel #35
0
def degreesToInt(value):
    # Fit the range -360..360 into -32768..32768
    # If angle is outside the range, force it into the range
    if value >= 360:
        # print("warning, angle out of range:", value)
        value %= 360
    elif value <= -360:
        # print("warning, angle out of range:", value)
        value %= -360
    return otRound(value * DEGREES_SCALE)
Beispiel #36
0
def instantiateCvar(varfont, location):
    log.info("Instantiating cvt/cvar tables")

    cvar = varfont["cvar"]
    cvt = varfont["cvt "]
    pinnedAxes = set(location.keys())
    newVariations = []
    deltas = {}
    for var in cvar.variations:
        tupleAxes = set(var.axes.keys())
        pinnedTupleAxes = tupleAxes & pinnedAxes
        if not pinnedTupleAxes:
            # A tuple for only axes being kept is untouched
            newVariations.append(var)
            continue
        else:
            # compute influence at pinned location only for the pinned axes
            pinnedAxesSupport = {a: var.axes[a] for a in pinnedTupleAxes}
            scalar = supportScalar(location, pinnedAxesSupport)
            if not scalar:
                # no influence (default value or out of range); drop tuple
                continue
            if tupleAxes.issubset(pinnedAxes):
                for i, c in enumerate(var.coordinates):
                    if c is not None:
                        # Compute deltas which need to be applied to values in cvt
                        deltas[i] = deltas.get(i, 0) + scalar * c
            else:
                # Apply influence to delta values
                for i, d in enumerate(var.coordinates):
                    if d is not None:
                        var.coordinates[i] = otRound(d * scalar)
                for axis in pinnedTupleAxes:
                    del var.axes[axis]
                newVariations.append(var)
    if deltas:
        for i, delta in deltas.items():
            cvt[i] += otRound(delta)
    if newVariations:
        cvar.variations = newVariations
    else:
        del varfont["cvar"]
Beispiel #37
0
def openTypeHheaCaretSlopeRunFallback(info):
    """
    Fallback to *openTypeHheaCaretSlopeRun*. If the italicAngle is zero,
    return 0. If italicAngle is non-zero, compute the slope run from the
    complementary openTypeHheaCaretSlopeRise.
    """
    italicAngle = getAttrWithFallback(info, "italicAngle")
    if italicAngle != 0:
        slopeRise = getAttrWithFallback(info, "openTypeHheaCaretSlopeRise")
        return otRound(math.tan(math.radians(-italicAngle)) * slopeRise)
    return 0
def t2c_round(number, tolerance=0.5):
    if tolerance == 0:
        return number  # no-op
    rounded = otRound(number)
    # return rounded integer if the tolerance >= 0.5, or if the absolute
    # difference between the original float and the rounded integer is
    # within the tolerance
    if tolerance >= .5 or abs(rounded - number) <= tolerance:
        return rounded
    else:
        # else return the value un-rounded
        return number
Beispiel #39
0
def VarData_addItem(self, deltas):
	deltas = [otRound(d) for d in deltas]

	countUs = self.VarRegionCount
	countThem = len(deltas)
	if countUs + 1 == countThem:
		deltas = tuple(deltas[1:])
	else:
		assert countUs == countThem, (countUs, countThem)
		deltas = tuple(deltas)
	self.Item.append(list(deltas))
	self.ItemCount = len(self.Item)
Beispiel #40
0
    def average_char_width(ctx, otf):
        hmtx = otf.get("hmtx")

        if hmtx is not None:
            widths = list(
                filter(lambda w: w > 0,
                       map(lambda hrec: hrec[0], hmtx.metrics.values())))
            if widths:
                return otRound(sum(widths) / len(widths))
        else:
            ctx.log.append(semlog.warning_missing_hmtx(target="avgCharWidth"))
        return 0
Beispiel #41
0
def t2c_round(number, tolerance=0.5):
    if tolerance == 0:
        return number  # no-op
    rounded = otRound(number)
    # return rounded integer if the tolerance >= 0.5, or if the absolute
    # difference between the original float and the rounded integer is
    # within the tolerance
    if tolerance >= .5 or abs(rounded - number) <= tolerance:
        return rounded
    else:
        # else return the value un-rounded
        return number
Beispiel #42
0
def VarData_addItem(self, deltas):
	deltas = [otRound(d) for d in deltas]

	countUs = self.VarRegionCount
	countThem = len(deltas)
	if countUs + 1 == countThem:
		deltas = tuple(deltas[1:])
	else:
		assert countUs == countThem, (countUs, countThem)
		deltas = tuple(deltas)
	self.Item.append(list(deltas))
	self.ItemCount = len(self.Item)
Beispiel #43
0
def interpolate_cff2_PrivateDict(topDict, interpolateFromDeltas):
    pd_blend_lists = ("BlueValues", "OtherBlues", "FamilyBlues",
                      "FamilyOtherBlues", "StemSnapH", "StemSnapV")
    pd_blend_values = ("BlueScale", "BlueShift", "BlueFuzz", "StdHW", "StdVW")
    for fontDict in topDict.FDArray:
        pd = fontDict.Private
        vsindex = pd.vsindex if (hasattr(pd, 'vsindex')) else 0
        for key, value in pd.rawDict.items():
            if (key in pd_blend_values) and isinstance(value, list):
                delta = interpolateFromDeltas(vsindex, value[1:])
                pd.rawDict[key] = otRound(value[0] + delta)
            elif (key in pd_blend_lists) and isinstance(value[0], list):
                """If any argument in a BlueValues list is a blend list,
				then they all are. The first value of each list is an
				absolute value. The delta tuples are calculated from
				relative master values, hence we need to append all the
				deltas to date to each successive absolute value."""
                delta = 0
                for i, val_list in enumerate(value):
                    delta += otRound(
                        interpolateFromDeltas(vsindex, val_list[1:]))
                    value[i] = val_list[0] + delta
Beispiel #44
0
def _SetCoordinates(font, glyphName, coord):
	glyf = font["glyf"]
	assert glyphName in glyf.glyphs
	glyph = glyf[glyphName]

	# Handle phantom points for (left, right, top, bottom) positions.
	assert len(coord) >= 4
	if not hasattr(glyph, 'xMin'):
		glyph.recalcBounds(glyf)
	leftSideX = coord[-4][0]
	rightSideX = coord[-3][0]
	topSideY = coord[-2][1]
	bottomSideY = coord[-1][1]

	for _ in range(4):
		del coord[-1]

	if glyph.isComposite():
		assert len(coord) == len(glyph.components)
		for p,comp in zip(coord, glyph.components):
			if hasattr(comp, 'x'):
				comp.x,comp.y = p
	elif glyph.numberOfContours is 0:
		assert len(coord) == 0
	else:
		assert len(coord) == len(glyph.coordinates)
		glyph.coordinates = coord

	glyph.recalcBounds(glyf)

	horizontalAdvanceWidth = otRound(rightSideX - leftSideX)
	if horizontalAdvanceWidth < 0:
		# unlikely, but it can happen, see:
		# https://github.com/fonttools/fonttools/pull/1198
		horizontalAdvanceWidth = 0
	leftSideBearing = otRound(glyph.xMin - leftSideX)
	# XXX Handle vertical
	font["hmtx"].metrics[glyphName] = horizontalAdvanceWidth, leftSideBearing
Beispiel #45
0
def interpolate_cff2_PrivateDict(topDict, interpolateFromDeltas):
	pd_blend_lists = ("BlueValues", "OtherBlues", "FamilyBlues",
						"FamilyOtherBlues", "StemSnapH",
						"StemSnapV")
	pd_blend_values = ("BlueScale", "BlueShift",
						"BlueFuzz", "StdHW", "StdVW")
	for fontDict in topDict.FDArray:
		pd = fontDict.Private
		vsindex = pd.vsindex if (hasattr(pd, 'vsindex')) else 0
		for key, value in pd.rawDict.items():
			if (key in pd_blend_values) and isinstance(value, list):
					delta = interpolateFromDeltas(vsindex, value[1:])
					pd.rawDict[key] = otRound(value[0] + delta)
			elif (key in pd_blend_lists) and isinstance(value[0], list):
				"""If any argument in a BlueValues list is a blend list,
				then they all are. The first value of each list is an
				absolute value. The delta tuples are calculated from
				relative master values, hence we need to append all the
				deltas to date to each successive absolute value."""
				delta = 0
				for i, val_list in enumerate(value):
					delta += otRound(interpolateFromDeltas(vsindex,
										val_list[1:]))
					value[i] = val_list[0] + delta
Beispiel #46
0
def merge(merger, self, lst):
	model = merger.model
	scalars = merger.scalars
	# TODO Handle differing valueformats
	for name, tableName in [('XAdvance','XAdvDevice'),
				('YAdvance','YAdvDevice'),
				('XPlacement','XPlaDevice'),
				('YPlacement','YPlaDevice')]:

		assert not hasattr(self, tableName)

		if hasattr(self, name):
			values = [getattr(a, name, 0) for a in lst]
			value = otRound(model.interpolateFromMastersAndScalars(values, scalars))
			setattr(self, name, value)
Beispiel #47
0
 def getCharString(self, private=None, globalSubrs=None, optimize=True):
     commands = self._commands
     if optimize:
         maxstack = 48 if not self._CFF2 else 513
         commands = specializeCommands(commands,
                                       generalizeFirst=False,
                                       maxstack=maxstack)
     program = commandsToProgram(commands)
     if self._width is not None:
         assert not self._CFF2, "CFF2 does not allow encoding glyph width in CharString."
         program.insert(0, otRound(self._width))
     if not self._CFF2:
         program.append('endchar')
     charString = T2CharString(
         program=program, private=private, globalSubrs=globalSubrs)
     return charString
Beispiel #48
0
def interpolate_cff2_metrics(varfont, topDict, glyphOrder, loc):
	"""Unlike TrueType glyphs, neither advance width nor bounding box
	info is stored in a CFF2 charstring. The width data exists only in
	the hmtx and HVAR tables. Since LSB data cannot be interpolated
	reliably from the master LSB values in the hmtx table, we traverse
	the charstring to determine the actual bound box. """

	charstrings = topDict.CharStrings
	boundsPen = BoundsPen(glyphOrder)
	hmtx = varfont['hmtx']
	hvar_table = None
	if 'HVAR' in varfont:
		hvar_table = varfont['HVAR'].table
		fvar = varfont['fvar']
		varStoreInstancer = VarStoreInstancer(hvar_table.VarStore, fvar.axes, loc)

	for gid, gname in enumerate(glyphOrder):
		entry = list(hmtx[gname])
		# get width delta.
		if hvar_table:
			if hvar_table.AdvWidthMap:
				width_idx = hvar_table.AdvWidthMap.mapping[gname]
			else:
				width_idx = gid
			width_delta = otRound(varStoreInstancer[width_idx])
		else:
			width_delta = 0

		# get LSB.
		boundsPen.init()
		charstring = charstrings[gname]
		charstring.draw(boundsPen)
		if boundsPen.bounds is None:
			# Happens with non-marking glyphs
			lsb_delta = 0
		else:
			lsb = boundsPen.bounds[0]
		lsb_delta = entry[1] - lsb

		if lsb_delta or width_delta:
			if width_delta:
				entry[0] += width_delta
			if lsb_delta:
				entry[1] = lsb
			hmtx[gname] = tuple(entry)
Beispiel #49
0
def merge(merger, self, lst):

	# Hack till we become selfless.
	self.__dict__ = lst[0].__dict__.copy()

	if self.Format != 3:
		return

	instancer = merger.instancer
	dev = self.DeviceTable
	del self.DeviceTable
	if dev:
		assert dev.DeltaFormat == 0x8000
		varidx = (dev.StartSize << 16) + dev.EndSize
		delta = otRound(instancer[varidx])
		self.Coordinate  += delta

	self.Format = 1
Beispiel #50
0
	def storeMasters(self, master_values):
		deltas = [otRound(d) for d in self._model.getDeltas(master_values)]
		base = deltas.pop(0)
		deltas = tuple(deltas)
		varIdx = self._cache.get(deltas)
		if varIdx is not None:
			return base, varIdx

		if not self._data:
			self._add_VarData()
		inner = len(self._data.Item)
		if inner == 0xFFFF:
			# Full array. Start new one.
			self._add_VarData()
			return self.storeMasters(master_values)
		self._data.Item.append(deltas)

		varIdx = (self._outer << 16) + inner
		self._cache[deltas] = varIdx
		return base, varIdx
Beispiel #51
0
def interpolate_cff2_charstrings(topDict, interpolateFromDeltas, glyphOrder):
	charstrings = topDict.CharStrings
	for gname in glyphOrder:
		# Interpolate charstring
		# e.g replace blend op args with regular args,
		# and use and discard vsindex op.
		charstring = charstrings[gname]
		new_program = []
		vsindex = 0
		last_i = 0
		for i, token in enumerate(charstring.program):
			if token == 'vsindex':
				vsindex = charstring.program[i - 1]
				if last_i != 0:
					new_program.extend(charstring.program[last_i:i - 1])
				last_i = i + 1
			elif token == 'blend':
				num_regions = charstring.getNumRegions(vsindex)
				numMasters = 1 + num_regions
				num_args = charstring.program[i - 1]
				# The program list starting at program[i] is now:
				# ..args for following operations
				# num_args values  from the default font
				# num_args tuples, each with numMasters-1 delta values
				# num_blend_args
				# 'blend'
				argi = i - (num_args * numMasters + 1)
				end_args = tuplei = argi + num_args
				while argi < end_args:
					next_ti = tuplei + num_regions
					deltas = charstring.program[tuplei:next_ti]
					delta = interpolateFromDeltas(vsindex, deltas)
					charstring.program[argi] += otRound(delta)
					tuplei = next_ti
					argi += 1
				new_program.extend(charstring.program[last_i:end_args])
				last_i = i + 1
		if last_i != 0:
			new_program.extend(charstring.program[last_i:])
			charstring.program = new_program
Beispiel #52
0
def merge(merger, self, lst):

	# Hack till we become selfless.
	self.__dict__ = lst[0].__dict__.copy()

	instancer = merger.instancer
	# TODO Handle differing valueformats
	for name, tableName in [('XAdvance','XAdvDevice'),
				('YAdvance','YAdvDevice'),
				('XPlacement','XPlaDevice'),
				('YPlacement','YPlaDevice')]:

		if not hasattr(self, tableName):
			continue
		dev = getattr(self, tableName)
		delattr(self, tableName)
		if dev is None:
			continue

		assert dev.DeltaFormat == 0x8000
		varidx = (dev.StartSize << 16) + dev.EndSize
		delta = otRound(instancer[varidx])

		setattr(self, name, getattr(self, name) + delta)
Beispiel #53
0
	def storeMasters(self, master_values):
		deltas = self._model.getDeltas(master_values)
		base = otRound(deltas.pop(0))
		return base, self.storeDeltas(deltas)
Beispiel #54
0
def _merge_TTHinting(font, model, 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 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)
			_remove_TTHinting(font)
			return

	# cvt table

	all_cvs = [Vector(m["cvt "].values) for m in master_ttfs if "cvt " in m]
	
	if len(all_cvs) == 0:
		# There is no cvt table to make a cvar table from, we're done here.
		return

	if len(all_cvs) != len(master_ttfs):
		log.warning("Some masters have no cvt table, hinting is discarded.")
		_remove_TTHinting(font)
		return

	num_cvt0 = len(all_cvs[0])
	if (any(len(c) != num_cvt0 for c in all_cvs)):
		log.warning("Masters have incompatible cvt tables, hinting is discarded.")
		_remove_TTHinting(font)
		return

	# We can build the cvar table now.

	cvar = font["cvar"] = newTable('cvar')
	cvar.version = 1
	cvar.variations = []

	deltas = model.getDeltas(all_cvs)
	supports = model.supports
	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)
		cvar.variations.append(var)
Beispiel #55
0
def _add_HVAR(font, model, master_ttfs, axisTags):

	log.info("Generating HVAR")

	hAdvanceDeltas = {}
	metricses = [m["hmtx"].metrics for m in master_ttfs]
	for glyph in font.getGlyphOrder():
		hAdvances = [metrics[glyph][0] for metrics in metricses]
		# TODO move round somewhere else?
		hAdvanceDeltas[glyph] = tuple(otRound(d) for d in model.getDeltas(hAdvances)[1:])

	# Direct mapping
	supports = model.supports[1:]
	varTupleList = builder.buildVarRegionList(supports, axisTags)
	varTupleIndexes = list(range(len(supports)))
	n = len(supports)
	items = []
	for glyphName in font.getGlyphOrder():
		items.append(hAdvanceDeltas[glyphName])

	# Build indirect mapping to save on duplicates, compare both sizes
	uniq = list(set(items))
	mapper = {v:i for i,v in enumerate(uniq)}
	mapping = [mapper[item] for item in items]
	advanceMapping = builder.buildVarIdxMap(mapping, font.getGlyphOrder())

	# Direct
	varData = builder.buildVarData(varTupleIndexes, items)
	directStore = builder.buildVarStore(varTupleList, [varData])

	# Indirect
	varData = builder.buildVarData(varTupleIndexes, uniq)
	indirectStore = builder.buildVarStore(varTupleList, [varData])
	mapping = indirectStore.optimize()
	advanceMapping.mapping = {k:mapping[v] for k,v in advanceMapping.mapping.items()}

	# 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())

	use_direct = directSize < indirectSize

	# Done; put it all together.
	assert "HVAR" not in font
	HVAR = font["HVAR"] = newTable('HVAR')
	hvar = HVAR.table = ot.HVAR()
	hvar.Version = 0x00010000
	hvar.LsbMap = hvar.RsbMap = None
	if use_direct:
		hvar.VarStore = directStore
		hvar.AdvWidthMap = None
	else:
		hvar.VarStore = indirectStore
		hvar.AdvWidthMap = advanceMapping
Beispiel #56
0
def instantiateVariableFont(varfont, location, inplace=False):
	""" Generate a static instance from a variable TTFont and a dictionary
	defining the desired location along the variable font's axes.
	The location values must be specified as user-space coordinates, e.g.:

		{'wght': 400, 'wdth': 100}

	By default, a new TTFont object is returned. If ``inplace`` is True, the
	input varfont is modified and reduced to a static font.
	"""
	if not inplace:
		# make a copy to leave input varfont unmodified
		stream = BytesIO()
		varfont.save(stream)
		stream.seek(0)
		varfont = TTFont(stream)

	fvar = varfont['fvar']
	axes = {a.axisTag:(a.minValue,a.defaultValue,a.maxValue) for a in fvar.axes}
	loc = normalizeLocation(location, axes)
	if 'avar' in varfont:
		maps = varfont['avar'].segments
		loc = {k: piecewiseLinearMap(v, maps[k]) for k,v in loc.items()}
	# Quantize to F2Dot14, to avoid surprise interpolations.
	loc = {k:floatToFixedToFloat(v, 14) for k,v in loc.items()}
	# Location is normalized now
	log.info("Normalized location: %s", loc)

	if 'gvar' in varfont:
		log.info("Mutating glyf/gvar tables")
		gvar = varfont['gvar']
		glyf = varfont['glyf']
		# get list of glyph names in gvar sorted by component depth
		glyphnames = sorted(
			gvar.variations.keys(),
			key=lambda name: (
				glyf[name].getCompositeMaxpValues(glyf).maxComponentDepth
				if glyf[name].isComposite() else 0,
				name))
		for glyphname in glyphnames:
			variations = gvar.variations[glyphname]
			coordinates,_ = _GetCoordinates(varfont, glyphname)
			origCoords, endPts = None, None
			for var in variations:
				scalar = supportScalar(loc, var.axes)
				if not scalar: continue
				delta = var.coordinates
				if None in delta:
					if origCoords is None:
						origCoords,control = _GetCoordinates(varfont, glyphname)
						endPts = control[1] if control[0] >= 1 else list(range(len(control[1])))
					delta = iup_delta(delta, origCoords, endPts)
				coordinates += GlyphCoordinates(delta) * scalar
			_SetCoordinates(varfont, glyphname, coordinates)
	else:
		glyf = None

	if 'cvar' in varfont:
		log.info("Mutating cvt/cvar tables")
		cvar = varfont['cvar']
		cvt = varfont['cvt ']
		deltas = {}
		for var in cvar.variations:
			scalar = supportScalar(loc, var.axes)
			if not scalar: continue
			for i, c in enumerate(var.coordinates):
				if c is not None:
					deltas[i] = deltas.get(i, 0) + scalar * c
		for i, delta in deltas.items():
			cvt[i] += otRound(delta)

	if 'CFF2' in varfont:
		log.info("Mutating CFF2 table")
		glyphOrder = varfont.getGlyphOrder()
		CFF2 = varfont['CFF2']
		topDict = CFF2.cff.topDictIndex[0]
		vsInstancer = VarStoreInstancer(topDict.VarStore.otVarStore, fvar.axes, loc)
		interpolateFromDeltas = vsInstancer.interpolateFromDeltas
		interpolate_cff2_PrivateDict(topDict, interpolateFromDeltas)
		CFF2.desubroutinize(varfont)
		interpolate_cff2_charstrings(topDict, interpolateFromDeltas, glyphOrder)
		interpolate_cff2_metrics(varfont, topDict, glyphOrder, loc)
		del topDict.rawDict['VarStore']
		del topDict.VarStore

	if 'MVAR' in varfont:
		log.info("Mutating MVAR table")
		mvar = varfont['MVAR'].table
		varStoreInstancer = VarStoreInstancer(mvar.VarStore, fvar.axes, loc)
		records = mvar.ValueRecord
		for rec in records:
			mvarTag = rec.ValueTag
			if mvarTag not in MVAR_ENTRIES:
				continue
			tableTag, itemName = MVAR_ENTRIES[mvarTag]
			delta = otRound(varStoreInstancer[rec.VarIdx])
			if not delta:
				continue
			setattr(varfont[tableTag], itemName,
				getattr(varfont[tableTag], itemName) + delta)

	log.info("Mutating FeatureVariations")
	for tableTag in 'GSUB','GPOS':
		if not tableTag in varfont:
			continue
		table = varfont[tableTag].table
		if not hasattr(table, 'FeatureVariations'):
			continue
		variations = table.FeatureVariations
		for record in variations.FeatureVariationRecord:
			applies = True
			for condition in record.ConditionSet.ConditionTable:
				if condition.Format == 1:
					axisIdx = condition.AxisIndex
					axisTag = fvar.axes[axisIdx].axisTag
					Min = condition.FilterRangeMinValue
					Max = condition.FilterRangeMaxValue
					v = loc[axisTag]
					if not (Min <= v <= Max):
						applies = False
				else:
					applies = False
				if not applies:
					break

			if applies:
				assert record.FeatureTableSubstitution.Version == 0x00010000
				for rec in record.FeatureTableSubstitution.SubstitutionRecord:
					table.FeatureList.FeatureRecord[rec.FeatureIndex].Feature = rec.Feature
				break
		del table.FeatureVariations

	if 'GDEF' in varfont and varfont['GDEF'].table.Version >= 0x00010003:
		log.info("Mutating GDEF/GPOS/GSUB tables")
		gdef = varfont['GDEF'].table
		instancer = VarStoreInstancer(gdef.VarStore, fvar.axes, loc)

		merger = MutatorMerger(varfont, loc)
		merger.mergeTables(varfont, [varfont], ['GDEF', 'GPOS'])

		# Downgrade GDEF.
		del gdef.VarStore
		gdef.Version = 0x00010002
		if gdef.MarkGlyphSetsDef is None:
			del gdef.MarkGlyphSetsDef
			gdef.Version = 0x00010000

		if not (gdef.LigCaretList or
			gdef.MarkAttachClassDef or
			gdef.GlyphClassDef or
			gdef.AttachList or
			(gdef.Version >= 0x00010002 and gdef.MarkGlyphSetsDef)):
			del varfont['GDEF']

	addidef = False
	if glyf:
		for glyph in glyf.glyphs.values():
			if hasattr(glyph, "program"):
				instructions = glyph.program.getAssembly()
				# If GETVARIATION opcode is used in bytecode of any glyph add IDEF
				addidef = any(op.startswith("GETVARIATION") for op in instructions)
				if addidef:
					break
	if addidef:
		log.info("Adding IDEF to fpgm table for GETVARIATION opcode")
		asm = []
		if 'fpgm' in varfont:
			fpgm = varfont['fpgm']
			asm = fpgm.program.getAssembly()
		else:
			fpgm = newTable('fpgm')
			fpgm.program = ttProgram.Program()
			varfont['fpgm'] = fpgm
		asm.append("PUSHB[000] 145")
		asm.append("IDEF[ ]")
		args = [str(len(loc))]
		for a in fvar.axes:
			args.append(str(floatToFixed(loc[a.axisTag], 14)))
		asm.append("NPUSHW[ ] " + ' '.join(args))
		asm.append("ENDF[ ]")
		fpgm.program.fromAssembly(asm)

		# Change maxp attributes as IDEF is added
		if 'maxp' in varfont:
			maxp = varfont['maxp']
			if hasattr(maxp, "maxInstructionDefs"):
				maxp.maxInstructionDefs += 1
			else:
				setattr(maxp, "maxInstructionDefs", 1)
			if hasattr(maxp, "maxStackElements"):
				maxp.maxStackElements = max(len(loc), maxp.maxStackElements)
			else:
				setattr(maxp, "maxInstructionDefs", len(loc))

	if 'name' in varfont:
		log.info("Pruning name table")
		exclude = {a.axisNameID for a in fvar.axes}
		for i in fvar.instances:
			exclude.add(i.subfamilyNameID)
			exclude.add(i.postscriptNameID)
		varfont['name'].names[:] = [
			n for n in varfont['name'].names
			if n.nameID not in exclude
		]

	if "wght" in location and "OS/2" in varfont:
		varfont["OS/2"].usWeightClass = otRound(
			max(1, min(location["wght"], 1000))
		)
	if "wdth" in location:
		wdth = location["wdth"]
		for percent, widthClass in sorted(OS2_WIDTH_CLASS_VALUES.items()):
			if wdth < percent:
				varfont["OS/2"].usWidthClass = widthClass
				break
		else:
			varfont["OS/2"].usWidthClass = 9
	if "slnt" in location and "post" in varfont:
		varfont["post"].italicAngle = max(-90, min(location["slnt"], 90))

	log.info("Removing variable tables")
	for tag in ('avar','cvar','fvar','gvar','HVAR','MVAR','VVAR','STAT'):
		if tag in varfont:
			del varfont[tag]

	return varfont
Beispiel #57
0
 def _makeAnchorFormatA(x, y):
     return ast.Anchor(x=otRound(x), y=otRound(y))