Exemple #1
0
def _plotLocations3D(model, axes, fig, rows, cols, names, **kwargs):
    ax1, ax2 = axes

    for i, (support, color, name) in enumerate(
            zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names))):
        axis3D = fig.add_subplot(rows, cols, i + 1, projection='3d')
        if name is not None:
            axis3D.set_title(name)
        axis3D.set_xlabel(ax1)
        axis3D.set_ylabel(ax2)
        pyplot.xlim(-1., +1.)
        pyplot.ylim(-1., +1.)

        Xs = support.get(ax1, (-1., 0., +1.))
        Ys = support.get(ax2, (-1., 0., +1.))
        for x in stops(Xs):
            X, Y, Z = [], [], []
            for y in Ys:
                z = supportScalar({ax1: x, ax2: y}, support)
                X.append(x)
                Y.append(y)
                Z.append(z)
            axis3D.plot(X, Y, Z, color=color, **kwargs)
        for y in stops(Ys):
            X, Y, Z = [], [], []
            for x in Xs:
                z = supportScalar({ax1: x, ax2: y}, support)
                X.append(x)
                Y.append(y)
                Z.append(z)
            axis3D.plot(X, Y, Z, color=color, **kwargs)

        _plotLocationsDots(model.locations, [ax1, ax2], axis3D)
Exemple #2
0
	def _getScalar(self, regionIdx):
		scalar = self._scalars.get(regionIdx)
		if scalar is None:
			support = self._regions[regionIdx].get_support(self.fvar_axes)
			scalar = supportScalar(self.location, support)
			self._scalars[regionIdx] = scalar
		return scalar
Exemple #3
0
	def _getScalar(self, regionIdx):
		scalar = self._scalars.get(regionIdx)
		if scalar is None:
			support = self._regions[regionIdx].get_support(self.fvar_axes)
			scalar = supportScalar(self.location, support)
			self._scalars[regionIdx] = scalar
		return scalar
Exemple #4
0
	def draw(self, pen):
		from fontTools.varLib.iup import iup_delta
		from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
		from fontTools.varLib.models import supportScalar

		glyf = self._ttFont['glyf']
		hMetrics = self._ttFont['hmtx'].metrics
		vMetrics = getattr(self._ttFont.get('vmtx'), 'metrics', None)

		variations = self._ttFont['gvar'].variations[self._glyphName]
		coordinates, _ = glyf._getCoordinatesAndControls(self._glyphName, hMetrics, vMetrics)
		origCoords, endPts = None, None
		for var in variations:
			scalar = supportScalar(self._location, var.axes)
			if not scalar:
				continue
			delta = var.coordinates
			if None in delta:
				if origCoords is None:
					origCoords,control = glyf._getCoordinatesAndControls(self._glyphName, hMetrics, vMetrics)
					endPts = control[1] if control[0] >= 1 else list(range(len(control[1])))
				delta = iup_delta(delta, origCoords, endPts)
			coordinates += GlyphCoordinates(delta) * scalar

		glyph = copy(glyf[self._glyphName]) # Shallow copy
		horizontalAdvanceWidth, leftSideBearing, verticalAdvanceWidth = _setCoordinates(glyph, coordinates, glyf)
		self.width = horizontalAdvanceWidth
		self.height = verticalAdvanceWidth
		offset = leftSideBearing - glyph.xMin if hasattr(glyph, "xMin") else 0
		glyph.draw(pen, glyf, offset)
Exemple #5
0
    def draw(self, pen):
        glyph = self._ttFont['glyf'][self._glyphName]
        glyph = self._copyGlyph(glyph, self._ttFont['glyf'])

        if self.drawBefore is not None:  # Call if defined
            self.drawBefore(self, p, view)

        variables = self._ttFont['gvar'].variables[self._glyphName]
        coordinates, _ = _GetCoordinates(self._ttFont, self._glyphName)
        for var in variables:
            scalar = supportScalar(self._location, var.axes)
            if not scalar:
                continue
            # print(var.coordinates)
            if None in var.coordinates:
                print("warning: replacing missing deltas with (0, 0)")
            deltas = GlyphCoordinates([pt or (0, 0) for pt in var.coordinates])
            coordinates += deltas * scalar

        horizontalAdvanceWidth, leftSideBearing = setCoordinates(
            glyph, coordinates, self._ttFont['glyf'])
        self.width = horizontalAdvanceWidth
        glyph.draw(pen, self._ttFont['glyf'])  # XXX offset based on lsb

        if self.drawAfter is not None:  # Call if defined
            self.drawAfter(self, p, view)
Exemple #6
0
def instantiateTupleVariationStore(variations, location, origCoords=None, endPts=None):
    """Instantiate TupleVariation list at the given location.

    The 'variations' list of TupleVariation objects is modified in-place.
    The input location can describe either a full instance (all the axes are assigned an
    explicit coordinate) or partial (some of the axes are omitted).
    Tuples that do not participate are kept as they are. Those that have 0 influence
    at the given location are removed from the variation store.
    Those that are fully instantiated (i.e. all their axes are being pinned) are also
    removed from the variation store, their scaled deltas accummulated and returned, so
    that they can be added by the caller to the default instance's coordinates.
    Tuples that are only partially instantiated (i.e. not all the axes that they
    participate in are being pinned) are kept in the store, and their deltas multiplied
    by the scalar support of the axes to be pinned at the desired location.

    Args:
        variations: List[TupleVariation] from either 'gvar' or 'cvar'.
        location: Dict[str, float]: axes coordinates for the full or partial instance.
        origCoords: GlyphCoordinates: default instance's coordinates for computing 'gvar'
            inferred points (cf. table__g_l_y_f.getCoordinatesAndControls).
        endPts: List[int]: indices of contour end points, for inferring 'gvar' deltas.

    Returns:
        List[float]: the overall delta adjustment after applicable deltas were summed.
    """
    newVariations = collections.OrderedDict()
    for var in variations:
        # Compute the scalar support of the axes to be pinned at the desired location,
        # excluding any axes that we are not pinning.
        # If a TupleVariation doesn't mention an axis, it implies that the axis peak
        # is 0 (i.e. the axis does not participate).
        support = {axis: var.axes.pop(axis, (-1, 0, +1)) for axis in location}
        scalar = supportScalar(location, support)
        if scalar == 0.0:
            # no influence, drop the TupleVariation
            continue

        # compute inferred deltas only for gvar ('origCoords' is None for cvar)
        if origCoords is not None:
            var.calcInferredDeltas(origCoords, endPts)

        var.scaleDeltas(scalar)

        # merge TupleVariations with overlapping "tents"
        axes = tuple(var.axes.items())
        if axes in newVariations:
            newVariations[axes] += var
        else:
            newVariations[axes] = var

    # drop TupleVariation if all axes have been pinned (var.axes.items() is empty);
    # its deltas will be added to the default instance's coordinates
    defaultVar = newVariations.pop(tuple(), None)

    for var in newVariations.values():
        var.roundDeltas()
    variations[:] = list(newVariations.values())

    return defaultVar.coordinates if defaultVar is not None else []
Exemple #7
0
def plotLocationsSurfaces(locations, fig, names=None, **kwargs):

    assert len(locations[0].keys()) == 2

    if names is None:
        names = ['']

    n = len(locations)
    cols = math.ceil(n**.5)
    rows = math.ceil(n / cols)

    model = VariationModel(locations)
    names = [names[model.reverseMapping[i]] for i in range(len(names))]

    ax1, ax2 = sorted(locations[0].keys())
    for i, (support, color, name) in enumerate(
            zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names))):

        axis3D = fig.add_subplot(rows, cols, i + 1, projection='3d')
        axis3D.set_title(name)
        axis3D.set_xlabel(ax1)
        axis3D.set_ylabel(ax2)
        pyplot.xlim(-1., +1.)
        pyplot.ylim(-1., +1.)

        Xs = support.get(ax1, (-1., 0., +1.))
        Ys = support.get(ax2, (-1., 0., +1.))
        for x in stops(Xs):
            X, Y, Z = [], [], []
            for y in Ys:
                z = supportScalar({ax1: x, ax2: y}, support)
                X.append(x)
                Y.append(y)
                Z.append(z)
            axis3D.plot(X, Y, Z, color=color, **kwargs)
        for y in stops(Ys):
            X, Y, Z = [], [], []
            for x in Xs:
                z = supportScalar({ax1: x, ax2: y}, support)
                X.append(x)
                Y.append(y)
                Z.append(z)
            axis3D.plot(X, Y, Z, color=color, **kwargs)

        plotLocations(model.locations, [ax1, ax2], axis3D)
Exemple #8
0
def plotLocationsSurfaces(locations, fig, names=None, **kwargs):

	assert len(locations[0].keys()) == 2

	if names is None:
		names = ['']

	n = len(locations)
	cols = math.ceil(n**.5)
	rows = math.ceil(n / cols)

	model = VariationModel(locations)
	names = [names[model.reverseMapping[i]] for i in range(len(names))]

	ax1, ax2 = sorted(locations[0].keys())
	for i, (support,color, name) in enumerate(zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names))):

		axis3D = fig.add_subplot(rows, cols, i + 1, projection='3d')
		axis3D.set_title(name)
		axis3D.set_xlabel(ax1)
		axis3D.set_ylabel(ax2)
		pyplot.xlim(-1.,+1.)
		pyplot.ylim(-1.,+1.)

		Xs = support.get(ax1, (-1.,0.,+1.))
		Ys = support.get(ax2, (-1.,0.,+1.))
		for x in stops(Xs):
			X, Y, Z = [], [], []
			for y in Ys:
				z = supportScalar({ax1:x, ax2:y}, support)
				X.append(x)
				Y.append(y)
				Z.append(z)
			axis3D.plot(X, Y, Z, color=color, **kwargs)
		for y in stops(Ys):
			X, Y, Z = [], [], []
			for x in Xs:
				z = supportScalar({ax1:x, ax2:y}, support)
				X.append(x)
				Y.append(y)
				Z.append(z)
			axis3D.plot(X, Y, Z, color=color, **kwargs)

		plotLocations(model.locations, [ax1, ax2], axis3D)
Exemple #9
0
def main(args=None):

    if args is None:
        import sys
        args = sys.argv[1:]

    varfilename = args[0]
    locargs = args[1:]
    outfile = os.path.splitext(varfilename)[0] + '-instance.ttf'

    loc = {}
    for arg in locargs:
        tag, val = arg.split('=')
        assert len(tag) <= 4
        loc[tag.ljust(4)] = float(val)
    print("Location:", loc)

    print("Loading variable font")
    varfont = TTFont(varfilename)

    fvar = varfont['fvar']
    axes = {
        a.axisTag: (a.minValue, a.defaultValue, a.maxValue)
        for a in fvar.axes
    }
    # TODO Round to F2Dot14?
    loc = normalizeLocation(loc, axes)
    # Location is normalized now
    print("Normalized location:", loc)

    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)
        for var in variations:
            scalar = supportScalar(loc, var.axes)
            if not scalar: continue
            # TODO Do IUP / handle None items
            coordinates += GlyphCoordinates(var.coordinates) * scalar
        _SetCoordinates(varfont, glyphname, coordinates)

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

    print("Saving instance font", outfile)
    varfont.save(outfile)
Exemple #10
0
def _plotLocations2D(model, axis, fig, cols, rows, names, **kwargs):
    for i, (support, color, name) in enumerate(
            zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names))):
        subplot = fig.add_subplot(rows, cols, i + 1)
        if name is not None:
            subplot.set_title(name)
        subplot.set_xlabel(axis)
        pyplot.xlim(-1., +1.)

        Xs = support.get(axis, (-1., 0., +1.))
        X, Y = [], []
        for x in stops(Xs):
            y = supportScalar({axis: x}, support)
            X.append(x)
            Y.append(y)
        subplot.plot(X, Y, color=color, **kwargs)

        _plotLocationsDots(model.locations, [axis], subplot)
def instantiateGvarGlyph(varfont, location, glyphname):
    glyf = varfont["glyf"]
    gvar = varfont["gvar"]
    variations = gvar.variations[glyphname]
    coordinates = glyf.getCoordinates(glyphname, varfont)
    origCoords = None
    newVariations = []
    pinnedAxes = set(location.keys())
    defaultModified = False
    for var in 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
            deltas = var.coordinates
            hasUntouchedPoints = None in deltas
            if hasUntouchedPoints:
                if origCoords is None:
                    origCoords, g = glyf.getCoordinatesAndControls(glyphname, varfont)
                deltas = iup_delta(deltas, origCoords, g.endPts)
            scaledDeltas = GlyphCoordinates(deltas) * scalar
            if tupleAxes.issubset(pinnedAxes):
                # A tuple for only axes being pinned is discarded, and
                # it's contribution is reflected into the base outlines
                coordinates += scaledDeltas
                defaultModified = True
            else:
                # A tuple for some axes being pinned has to be adjusted
                var.coordinates = scaledDeltas
                for axis in pinnedTupleAxes:
                    del var.axes[axis]
                newVariations.append(var)
    if defaultModified:
        glyf.setCoordinates(glyphname, coordinates, varfont)
    gvar.variations[glyphname] = newVariations
Exemple #12
0
def main(args=None):

	if args is None:
		import sys
		args = sys.argv[1:]

	varfilename = args[0]
	locargs = args[1:]
	outfile = os.path.splitext(varfilename)[0] + '-instance.ttf'

	loc = {}
	for arg in locargs:
		tag,val = arg.split('=')
		assert len(tag) <= 4
		loc[tag.ljust(4)] = float(val)
	print("Location:", loc)

	print("Loading GX font")
	varfont = TTFont(varfilename)

	fvar = varfont['fvar']
	axes = {a.axisTag:(a.minValue,a.defaultValue,a.maxValue) for a in fvar.axes}
	# TODO Round to F2Dot14?
	loc = normalizeLocation(loc, axes)
	# Location is normalized now
	print("Normalized location:", loc)

	gvar = varfont['gvar']
	for glyphname,variations in gvar.variations.items():
		coordinates,_ = _GetCoordinates(varfont, glyphname)
		for var in variations:
			scalar = supportScalar(loc, var.axes)
			if not scalar: continue
			# TODO Do IUP / handle None items
			coordinates += GlyphCoordinates(var.coordinates) * scalar
		_SetCoordinates(varfont, glyphname, coordinates)

	print("Removing GX tables")
	for tag in ('fvar','avar','gvar'):
		if tag in varfont:
			del varfont[tag]

	print("Saving instance font", outfile)
	varfont.save(outfile)
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"]
def instantiateComponentOffsets(ttFont, glyphName, location):
    glyfTable = ttFont["glyf"]
    gvarTable = ttFont["gvar"]
    assert glyfTable[glyphName].isComposite()
    variations = gvarTable.variations[glyphName]
    coordinates, _ = glyfTable.getCoordinatesAndControls(glyphName, ttFont)
    origCoords, endPts = None, None
    for var in variations:
        scalar = supportScalar(location, var.axes)
        if not scalar:
            continue
        delta = var.coordinates
        if None in delta:
            if origCoords is None:
                origCoords, g = glyfTable.getCoordinatesAndControls(glyphName, ttFont)
                endPts = g.endPts
            delta = iup_delta(delta, origCoords, endPts)
        coordinates += GlyphCoordinates(delta) * scalar
    assert len(coordinates) == len(glyfTable[glyphName].components) + 4
    return coordinates[:-4]
Exemple #15
0
def generateInstance(variableFontPath,
                     location,
                     targetDirectory,
                     normalize=False,
                     force=False):

    instanceName = ""

    for k, v in sorted(location.items()):
        # TODO better way to normalize the location name to (0, 1000)
        v = min(v, 1000)
        v = max(v, 0)
        instanceName += "-%s%s" % (k, v)
    targetFileName = '.'.join(variableFontPath.split('/')[-1].split('.')
                              [:-1]) + instanceName + '.ttf'

    if not targetDirectory.endswith('/'):
        targetDirectory += '/'
    if not os.path.exists(targetDirectory):
        os.makedirs(targetDirectory)
    outFile = targetDirectory + targetFileName

    if force or not os.path.exists(outFile):
        #print location
        #print("Loading variable font")
        varFont = TTFont(variableFontPath)

        fvar = varFont['fvar']
        axes = {
            a.axisTag: (a.minValue, a.defaultValue, a.maxValue)
            for a in fvar.axes
        }
        # TODO Apply avar
        # TODO Round to F2Dot14?
        loc = normalizeLocation(location, axes)
        # Location is normalized now
        #print("Normalized location:", loc, 'from', location)

        # Set the instance name IDs in the name table
        platforms = ((1, 0, 0), (3, 1, 0x409))  # Macintosh and Windows
        for platformID, platEncID, langID in platforms:
            familyName = varFont['name'].getName(1, platformID, platEncID,
                                                 langID)  # 1 Font Family name
            if not familyName:
                continue
            familyName = familyName.toUnicode()  # NameRecord to unicode string
            styleName = unicode(
                instanceName)  # TODO make sure this works in any case
            fullFontName = " ".join([familyName, styleName])
            postscriptName = fullFontName.replace(" ", "-")
            varFont['name'].setName(styleName, 2, platformID, platEncID,
                                    langID)  # 2 Font Subfamily name
            varFont['name'].setName(fullFontName, 4, platformID, platEncID,
                                    langID)  # 4 Full font name
            varFont['name'].setName(postscriptName, 6, platformID, platEncID,
                                    langID)  # 6 Postscript name for the font
            # Other important name IDs
            # 3 Unique font identifier (e.g. Version 0.000;NONE;Promise Bold Regular)
            # 25 Variables PostScript Name Prefix

        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)  #, ot=True)
                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)

        #print("Removing variable tables")
        for tag in ('avar', 'cvar', 'fvar', 'gvar', 'HVAR', 'MVAR', 'VVAR',
                    'STAT'):
            if tag in varFont:
                del varFont[tag]

        #print("Saving instance font", outFile)
        varFont.save(outFile)

    # Installing the font in DrawBot. Answer font name and path.
    return c.installFont(outFile), outFile
def instantiateItemVariationStore(varfont, tableName, location):
    log.info("Instantiating ItemVariation store of %s table", tableName)

    table = varfont[tableName].table
    fvar = varfont["fvar"]
    newRegions = []
    regionInfluenceMap = {}
    pinnedAxes = set(location.keys())
    fvarAxisIndices = {
        axis.axisTag: index
        for index, axis in enumerate(fvar.axes)
        if axis.axisTag in pinnedAxes
    }
    for regionIndex, region in enumerate(table.VarStore.VarRegionList.Region):
        # collect set of axisTags which have influence: peak != 0
        regionAxes = set(
            axis
            for axis, (start, peak, end) in region.get_support(fvar.axes).items()
            if peak != 0
        )
        pinnedRegionAxes = regionAxes & pinnedAxes
        if not pinnedRegionAxes:
            # A region where none of the axes having effect are pinned
            newRegions.append(region)
            continue
        if len(pinnedRegionAxes) == len(regionAxes):
            # All the axes having effect in this region are being pinned so
            # remove it
            regionInfluenceMap.update({regionIndex: None})
        else:
            # This region will be retained but the deltas have to be adjusted.
            pinnedSupport = {
                axis: support
                for axis, support in region.get_support(fvar.axes).items()
                if axis in pinnedRegionAxes
            }
            pinnedScalar = supportScalar(location, pinnedSupport)
            regionInfluenceMap.update({regionIndex: pinnedScalar})

            for axis in pinnedRegionAxes:
                # For all pinnedRegionAxes make their influence null by setting
                # PeakCoord to 0.
                index = fvarAxisIndices[axis]
                region.VarRegionAxis[index].PeakCoord = 0

            newRegions.append(region)

    table.VarStore.VarRegionList.Region = newRegions

    if not table.VarStore.VarRegionList.Region:
        # Delete table if no more regions left.
        del varfont[tableName]
        return

    # Start modifying deltas.
    if regionInfluenceMap:
        regionsToBeRemoved = sorted(
            [
                regionIndex
                for regionIndex, scalar in regionInfluenceMap.items()
                if scalar is None
            ]
        )
        for vardata in table.VarStore.VarData:
            varRegionIndexMapping = {v: k for k, v in enumerate(vardata.VarRegionIndex)}
            # Apply scalars for regions to be retained.
            for regionIndex, scalar in regionInfluenceMap.items():
                if scalar is not None:
                    varRegionIndex = varRegionIndexMapping[regionIndex]
                    for item in vardata.Item:
                        item[varRegionIndex] = otRound(item[varRegionIndex] * scalar)

            if regionsToBeRemoved:
                # Delete deltas (in reverse order) for regions to be removed.
                for regionIndex in sorted(
                    regionsToBeRemoved,
                    key=lambda x: varRegionIndexMapping[x],
                    reverse=True,
                ):
                    varRegionIndex = varRegionIndexMapping[regionIndex]
                    for item in vardata.Item:
                        del item[varRegionIndex]

                # Adjust VarRegionIndex since we are deleting regions.
                newVarRegionIndex = []
                for varRegionIndex in vardata.VarRegionIndex:
                    if varRegionIndex not in regionsToBeRemoved:
                        newVarRegionIndex.append(
                            varRegionIndex
                            - bisect.bisect_left(regionsToBeRemoved, varRegionIndex)
                        )
                vardata.VarRegionIndex = newVarRegionIndex
Exemple #17
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()
		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)
		if 'ltag' in varfont:
			# Drop the whole 'ltag' table if all its language tags are referenced by
			# name records to be pruned.
			# TODO: prune unused ltag tags and re-enumerate langIDs accordingly
			excludedUnicodeLangIDs = [
				n.langID for n in varfont['name'].names
				if n.nameID in exclude and n.platformID == 0 and n.langID != 0xFFFF
			]
			if set(excludedUnicodeLangIDs) == set(range(len((varfont['ltag'].tags)))):
				del varfont['ltag']
		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
Exemple #18
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
def test_supportScalar():
    assert supportScalar({}, {}) == 1.0
    assert supportScalar({'wght': .2}, {}) == 1.0
    assert supportScalar({'wght': .2}, {'wght': (0, 2, 3)}) == 0.1
    assert supportScalar({'wght': 2.5}, {'wght': (0, 2, 4)}) == 0.75
Exemple #20
0
def main(args=None):

    if args is None:
        import sys
        args = sys.argv[1:]

    varfilename = args[0]
    locargs = args[1:]
    outfile = os.path.splitext(varfilename)[0] + '-instance.ttf'

    loc = {}
    for arg in locargs:
        tag, val = arg.split('=')
        assert len(tag) <= 4
        loc[tag.ljust(4)] = float(val)
    print("Location:", loc)

    print("Loading variable font")
    varfont = TTFont(varfilename)

    fvar = varfont['fvar']
    axes = {
        a.axisTag: (a.minValue, a.defaultValue, a.maxValue)
        for a in fvar.axes
    }
    # TODO Apply avar
    # TODO Round to F2Dot14?
    loc = normalizeLocation(loc, axes)
    # Location is normalized now
    print("Normalized location:", loc)

    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, ot=True)
            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)

    # Interpolate cvt

    if 'cvar' in varfont:
        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] += int(round(delta))

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

    print("Saving instance font", outfile)
    varfont.save(outfile)
Exemple #21
0
def makeInstance(pathOrVarFont,
                 location,
                 dstPath=None,
                 normalize=True,
                 cached=True,
                 lazy=True,
                 kerning=None):
    """Instantiate an instance of a variable font at the specified location.

    Keyword arguments:
    - varfilename -- a variable font file path
    - location -- a dictionary of axis tag and value {"wght": 0.75, "wdth": -0.5}

    >>> vf = findFont('RobotoDelta-VF')
    >>> print(vf)
    <Font RobotoDelta-VF>
    >>> print(len(vf))
    188
    >>> instance = makeInstance(vf.path, dict(opsz=8), cached=False)
    >>> instance
    <Font RobotoDelta-VF-opsz8>
    >>> len(instance)
    241
    >>> len(instance['H'].points)
    12
    >>> instance['Egrave']
    <PageBot Glyph Egrave Pts:0/Cnt:0/Cmp:2>
    >>> len(instance['Egrave'].components)
    2
    """
    # make a custom file name from the location e.g.
    # VariableFont-wghtXXX-wdthXXX.ttf
    instanceName = ""

    if isinstance(pathOrVarFont, Font):
        pathOrVarFont = pathOrVarFont.path

    varFont = Font(pathOrVarFont, lazy=lazy)
    ttFont = varFont.ttFont

    for k, v in sorted(location.items()):
        # TODO better way to normalize the location name to (0, 1000)
        v = min(v, 1000)
        v = max(v, 0)
        instanceName += "-%s%s" % (k, v)

    if dstPath is None:
        targetFileName = '.'.join(varFont.path.split('/')[-1].split('.')
                                  [:-1]) + instanceName + '.ttf'
        targetDirectory = getInstancePath()
        if not targetDirectory.endswith('/'):
            targetDirectory += '/'
        if not os.path.exists(targetDirectory):
            os.makedirs(targetDirectory)
        dstPath = targetDirectory + targetFileName

    # Instance does not exist as file. Create it.
    if not cached or not os.path.exists(dstPath):
        # Set the instance name IDs in the name table
        platforms = ((1, 0, 0), (3, 1, 0x409))  # Macintosh and Windows

        for platformID, platEncID, langID in platforms:
            familyName = ttFont['name'].getName(1, platformID, platEncID,
                                                langID)  # 1 Font Family name
            if not familyName:
                continue
            familyName = familyName.toUnicode()  # NameRecord to unicode string
            styleName = unicode(
                instanceName)  # TODO make sure this works in any case
            fullFontName = " ".join([familyName, styleName])
            postscriptName = fullFontName.replace(" ", "-")
            ttFont['name'].setName(styleName, 2, platformID, platEncID,
                                   langID)  # 2 Font Subfamily name
            ttFont['name'].setName(fullFontName, 4, platformID, platEncID,
                                   langID)  # 4 Full font name
            ttFont['name'].setName(postscriptName, 6, platformID, platEncID,
                                   langID)  # 6 Postscript name for the font
            # Other important name IDs
            # 3 Unique font identifier (e.g. Version 0.000;NONE;Promise Bold Regular)
            # 25 Variables PostScript Name Prefix

        fvar = ttFont['fvar']
        axes = {
            a.axisTag: (a.minValue, a.defaultValue, a.maxValue)
            for a in fvar.axes
        }
        # TODO Apply avar
        # TODO Round to F2Dot14?
        loc = normalizeLocation(location, axes)
        # Location is normalized now
        #print("Normalized location:", loc)

        gvar = ttFont['gvar']
        glyf = ttFont['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(ttFont, glyphName)
            origCoords, endPts = None, None

            for var in variations:
                scalar = supportScalar(loc, var.axes)  #, ot=True)
                if not scalar: continue
                delta = var.coordinates
                if None in delta:
                    if origCoords is None:
                        origCoords, control = _GetCoordinates(
                            ttFont, 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(ttFont, glyphName, coordinates)

        # Interpolate cvt

        if 'cvar' in ttFont:
            cvar = ttFont['cvar']
            cvt = ttFont['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] += int(round(delta))

        #print("Removing variable tables")
        for tag in ('avar', 'cvar', 'fvar', 'gvar', 'HVAR', 'MVAR', 'VVAR',
                    'STAT'):
            if tag in ttFont:
                del ttFont[tag]

        if kerning is not None:
            for pair, value in kerning.items():
                varFont.kerning[pair] = value

        #print("Saving instance font", outFile)
        varFont.save(dstPath)

    # Answer instance.
    return Font(dstPath, lazy=lazy)
Exemple #22
0
def main(args=None):

	if args is None:
		import sys
		args = sys.argv[1:]

	varfilename = args[0]
	locargs = args[1:]
	outfile = os.path.splitext(varfilename)[0] + '-instance.ttf'

	loc = {}
	for arg in locargs:
		tag,val = arg.split('=')
		assert len(tag) <= 4
		loc[tag.ljust(4)] = float(val)
	print("Location:", loc)

	print("Loading variable font")
	varfont = TTFont(varfilename)

	fvar = varfont['fvar']
	axes = {a.axisTag:(a.minValue,a.defaultValue,a.maxValue) for a in fvar.axes}
	# TODO Apply avar
	# TODO Round to F2Dot14?
	loc = normalizeLocation(loc, axes)
	# Location is normalized now
	print("Normalized location:", loc)

	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)
				# TODO Do IUP / handle None items
			coordinates += GlyphCoordinates(delta) * scalar
		_SetCoordinates(varfont, glyphname, coordinates)

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

	print("Saving instance font", outfile)
	varfont.save(outfile)
Exemple #23
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:_DesignspaceAxis._map(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)

	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)

	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 '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)

	if 'GDEF' in varfont:
		log.info("Mutating GDEF/GPOS/GSUB tables")
		merger = MutatorMerger(varfont, loc)

		log.info("Building interpolated tables")
		merger.instantiate()

	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
Exemple #24
0
def generateInstance(variableFontPath, location, targetDirectory, normalize=True):
    u"""
    Instantiate an instance of a variable font at the specified location.
    Keyword arguments:
        varfilename -- a variable font file path
        location -- a dictionary of axis tag and value {"wght": 0.75, "wdth": -0.5}
    """
    # make a custom file name from the location e.g. VariableFont-wghtXXX-wdthXXX.ttf
    instanceName = ""

    for k, v in sorted(location.items()):
        # TODO better way to normalize the location name to (0, 1000)
        v = min(v, 1000)
        v = max(v, 0)
        instanceName += "-%s%s" % (k, v)
    targetFileName = '.'.join(variableFontPath.split('/')[-1].split('.')[:-1]) + instanceName + '.ttf'

    if not targetDirectory.endswith('/'):
        targetDirectory += '/'
    if not os.path.exists(targetDirectory):
        os.makedirs(targetDirectory)
    outFile = targetDirectory + targetFileName

    if not os.path.exists(outFile):
        # Instance does not exist as file. Create it.

        # print("Loading GX font")
        varFont = TTFont(variableFontPath)

        # Set the instance name IDs in the name table
        platforms=((1, 0, 0), (3, 1, 0x409)) # Macintosh and Windows
        for platformID, platEncID, langID in platforms:
            familyName = varFont['name'].getName(1, platformID, platEncID, langID) # 1 Font Family name
            if not familyName:
                continue
            familyName = familyName.toUnicode() # NameRecord to unicode string
            styleName = unicode(instanceName) # TODO make sure this works in any case
            fullFontName = " ".join([familyName, styleName])
            postscriptName = fullFontName.replace(" ", "-")
            varFont['name'].setName(styleName, 2, platformID, platEncID, langID) # 2 Font Subfamily name
            varFont['name'].setName(fullFontName, 4, platformID, platEncID, langID) # 4 Full font name
            varFont['name'].setName(postscriptName, 6, platformID, platEncID, langID) # 6 Postscript name for the font
            # Other important name IDs
            # 3 Unique font identifier (e.g. Version 0.000;NONE;Promise Bold Regular)
            # 25 Variables PostScript Name Prefix

        fvar = varFont['fvar']
        axes = {a.axisTag: (a.minValue, a.defaultValue, a.maxValue) for a in fvar.axes}
        # TODO Round to F2Dot14?
        if normalize:
            normalizedLoc = normalizeLocation(location, axes)
        else:
            normalizedLoc = location
        # Location is normalized now
        if DEBUG:
            print("Normalized location:", varFileName, normalizedLoc)

        gvar = varFont['gvar']
        for glyphName, variations in gvar.variations.items():
            coordinates, _ = _GetCoordinates(varFont, glyphName)
            for var in variations:
                scalar = supportScalar(normalizedLoc, var.axes)
                if not scalar: continue
                # TODO Do IUP / handle None items
                varCoords = []
                for coord in var.coordinates:
                    # TODO temp hack to avoid NoneType
                    if coord is None:
                        varCoords.append((0, 0))
                    else:
                        varCoords.append(coord)
                coordinates += GlyphCoordinates(varCoords) * scalar
                # coordinates += GlyphCoordinates(var.coordinates) * scalar
            _SetCoordinates(varFont, glyphName, coordinates)

        # print("Removing GX tables")
        for tag in ('fvar', 'avar', 'gvar'):
            if tag in varFont:
                del varFont[tag]

        # Fix leading bug in drawbot by setting lineGap to 0
        varFont['hhea'].lineGap = 0

        if DEBUG:
            print("Saving instance font", outFile)
        varFont.save(outFile)

    # Installing the font in DrawBot. Answer font name and path.
    return installFont(outFile), outFile
Exemple #25
0
def test_supportScalar():
    assert supportScalar({}, {}) == 1.0
    assert supportScalar({"wght": 0.2}, {}) == 1.0
    assert supportScalar({"wght": 0.2}, {"wght": (0, 2, 3)}) == 0.1
    assert supportScalar({"wght": 2.5}, {"wght": (0, 2, 4)}) == 0.75
Exemple #26
0
def test_supportScalar():
    assert supportScalar({}, {}) == 1.0
    assert supportScalar({'wght':.2}, {}) == 1.0
    assert supportScalar({'wght':.2}, {'wght':(0,2,3)}) == 0.1
    assert supportScalar({'wght':2.5}, {'wght':(0,2,4)}) == 0.75
def generateInstance(variableFontPath,
                     location,
                     targetDirectory,
                     normalize=True,
                     cached=True,
                     lazy=True):
    """
    D E P R E C A T E D

    Use pagebot.fonttoolbox.objects.font.instantiateVariableFont instead
    (calling fontTools)

    Instantiate an instance of a variable font at the specified location.
    Keyword arguments:
        varfilename -- a variable font file path
        location -- a dictionary of axis tag and value {"wght": 0.75, "wdth":
        -0.5}
    """
    # make a custom file name from the location e.g. VariableFont-wghtXXX-wdthXXX.ttf
    instanceName = ""

    for k, v in sorted(location.items()):
        # TODO better way to normalize the location name to (0, 1000)
        v = min(v, 1000)
        v = max(v, 0)
        instanceName += "-%s%s" % (k, v)

    targetFileName = '.'.join(variableFontPath.split('/')[-1].split('.')
                              [:-1]) + instanceName + '.ttf'

    if not targetDirectory.endswith('/'):
        targetDirectory += '/'
    if not os.path.exists(targetDirectory):
        os.makedirs(targetDirectory)
    outFile = targetDirectory + targetFileName

    if not cached or not os.path.exists(outFile):
        # Instance does not exist as file. Create it.

        # print("Loading GX font")
        varfont = TTFont(variableFontPath, lazy=lazy)

        # Set the instance name IDs in the name table
        platforms = ((1, 0, 0), (3, 1, 0x409))  # Macintosh and Windows
        for platformID, platEncID, langID in platforms:
            familyName = varfont['name'].getName(1, platformID, platEncID,
                                                 langID)  # 1 Font Family name
            if not familyName:
                continue
            familyName = familyName.toUnicode()  # NameRecord to unicode string
            styleName = unicode(
                instanceName)  # TODO make sure this works in any case
            fullFontName = " ".join([familyName, styleName])
            postscriptName = fullFontName.replace(" ", "-")
            varfont['name'].setName(styleName, 2, platformID, platEncID,
                                    langID)  # 2 Font Subfamily name
            varfont['name'].setName(fullFontName, 4, platformID, platEncID,
                                    langID)  # 4 Full font name
            varfont['name'].setName(postscriptName, 6, platformID, platEncID,
                                    langID)  # 6 Postscript name for the font
            # Other important name IDs
            # 3 Unique font identifier (e.g. Version 0.000;NONE;Promise Bold Regular)
            # 25 Variables PostScript Name Prefix

        fvar = varfont['fvar']
        axes = {
            a.axisTag: (a.minValue, a.defaultValue, a.maxValue)
            for a in fvar.axes
        }
        # TODO Apply avar
        # TODO Round to F2Dot14?
        loc = normalizeLocation(location, axes)
        # Location is normalized now
        #print("Normalized location:", loc)

        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)  #, ot=True)
                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)

        # Interpolate cvt

        if 'cvar' in varfont:
            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] += int(round(delta))

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

        #print("Saving instance font", outFile)
        varfont.save(outFile)

    # Answer the font name path.
    return outFile