Example #1
0
def flattenGlyphs(input, fontNumber, output):
    font = TTFont(input, fontNumber=fontNumber)
    font.recalcBBoxes = False
    if "glyf" in font:
        for glyphName in font.getGlyphOrder():
            glyph = font["glyf"][glyphName]
            coordinates, endPtsOfContours, flags = glyph.getCoordinates(
                font["glyf"])
            glyph.numberOfContours = len(endPtsOfContours)
            glyph.coordinates = coordinates
            glyph.endPtsOfContours = endPtsOfContours
            glyph.flags = flags
            glyph.program = ttProgram.Program()
            font["glyf"][glyphName] = glyph
    elif "CFF " in font:
        cff = font["CFF "]
        fontName = cff.cff.fontNames[0]
        topDict = cff.cff[fontName]
        for glyphID in range(len(font.getGlyphOrder())):
            charString = topDict.CharStrings.charStringsIndex[glyphID]
            charString.decompile()
            localSubrs = getattr(charString.private, "Subrs", [])
            globalSubrs = charString.globalSubrs
            inlinedProgram = inlineProgram(localSubrs, globalSubrs,
                                           charString.program)
            charString.program = inlinedProgram
        if "Private" in topDict.rawDict and "Subrs" in topDict.Private.rawDict:
            topDict.Private.Subrs
            del topDict.Private.rawDict["Subrs"]
            del topDict.Private.Subrs
        topDict.GlobalSubrs.items = []
    else:
        raise FlattenError("Could not flatten glyphs.")

    font.save(output)
Example #2
0
    def glyph(self, componentFlags: int = 0x4) -> Glyph:
        """
        Returns a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.
        """
        if not self._isClosed():
            raise PenError("Didn't close last contour.")
        components = self._buildComponents(componentFlags)

        glyph = Glyph()
        glyph.coordinates = GlyphCoordinates(self.points)
        glyph.coordinates.toInt()
        glyph.endPtsOfContours = self.endPts
        glyph.flags = array("B", self.types)
        self.init()

        if components:
            # If both components and contours were present, they have by now
            # been decomposed by _buildComponents.
            glyph.components = components
            glyph.numberOfContours = -1
        else:
            glyph.numberOfContours = len(glyph.endPtsOfContours)
            glyph.program = ttProgram.Program()
            glyph.program.fromBytecode(b"")

        return glyph
Example #3
0
    def test_trim_remove_hinting_composite_glyph(self):
        glyphSet = {"dummy": TTGlyphPen(None).glyph()}

        pen = TTGlyphPen(glyphSet)
        pen.addComponent("dummy", (1, 0, 0, 1, 0, 0))
        composite = pen.glyph()
        p = ttProgram.Program()
        p.fromAssembly(['SVTCA[0]'])
        composite.program = p
        glyphSet["composite"] = composite

        glyfTable = newTable("glyf")
        glyfTable.glyphs = glyphSet
        glyfTable.glyphOrder = sorted(glyphSet)

        composite.compact(glyfTable)

        self.assertTrue(hasattr(composite, "data"))

        # remove hinting from the compacted composite glyph, without expanding it
        composite.trim(remove_hinting=True)

        # check that, after expanding the glyph, we have no instructions
        composite.expand(glyfTable)
        self.assertFalse(hasattr(composite, "program"))

        # now remove hinting from expanded composite glyph
        composite.program = p
        composite.trim(remove_hinting=True)

        # check we have no instructions
        self.assertFalse(hasattr(composite, "program"))

        composite.compact(glyfTable)
Example #4
0
    def glyph(self, componentFlags=0x4):
        assert self._isClosed(), "Didn't close last contour."

        components = []
        for glyphName, transformation in self.components:
            if self.points:
                # can't have both, so decompose the glyph
                tpen = TransformPen(self, transformation)
                self.glyphSet[glyphName].draw(tpen)
                continue

            component = GlyphComponent()
            component.glyphName = glyphName
            if transformation[:4] != (1, 0, 0, 1):
                component.transform = (transformation[:2], transformation[2:4])
            component.x, component.y = transformation[4:]
            component.flags = componentFlags
            components.append(component)

        glyph = Glyph()
        glyph.coordinates = GlyphCoordinates(self.points)
        glyph.endPtsOfContours = self.endPts
        glyph.flags = array("B", self.types)
        self.init()

        if components:
            glyph.components = components
            glyph.numberOfContours = -1
        else:
            glyph.numberOfContours = len(glyph.endPtsOfContours)
            glyph.program = ttProgram.Program()
            glyph.program.fromBytecode(b"")

        return glyph
Example #5
0
def main():
    args = parser.parse_args()

    # Open the font file supplied as the first argument on the command line
    fontfile_in = os.path.abspath(args.fontfile_in[0])
    font = ttLib.TTFont(fontfile_in)

    # Save a backup
    backupfont = '{}-backup-fonttools-prep-gasp{}'.format(
        fontfile_in[0:-4], fontfile_in[-4:])
    # print "Saving to ", backupfont
    font.save(backupfont)
    print(backupfont, " saved.")

    # Print the Gasp table
    if "gasp" in font:
        print("GASP was: ", font["gasp"].gaspRange)
    else:
        print("GASP wasn't there")

    # Print the PREP table
    if "prep" in font:
        old_program = ttProgram.Program.getAssembly(font["prep"].program)
        print("PREP was:\n\t" + "\n\t".join(old_program))
    else:
        print("PREP wasn't there")

    # Create a new GASP table
    gasp = ttLib.newTable("gasp")

    # Set GASP to the magic number
    gasp.gaspRange = {0xFFFF: 15}

    # Create a new hinting program
    program = ttProgram.Program()

    assembly = ['PUSHW[]', '511', 'SCANCTRL[]', 'PUSHB[]', '4', 'SCANTYPE[]']
    program.fromAssembly(assembly)

    # Create a new PREP table
    prep = ttLib.newTable("prep")

    # Insert the magic program into it
    prep.program = program

    # Add the tables to the font, replacing existing ones
    font["gasp"] = gasp
    font["prep"] = prep

    # Print the Gasp table
    print("GASP now: ", font["gasp"].gaspRange)

    # Print the PREP table
    current_program = ttProgram.Program.getAssembly(font["prep"].program)
    print("PREP now:\n\t" + "\n\t".join(current_program))

    # Save the new file with the name of the input file
    fontfile_out = os.path.abspath(args.fontfile_out[0])
    font.save(fontfile_out)
    print(fontfile_out, " saved.")
Example #6
0
 def _decodeInstructions(self, glyph):
     glyphStream = self.glyphStream
     instructionStream = self.instructionStream
     instructionLength, glyphStream = unpack255UShort(glyphStream)
     glyph.program = ttProgram.Program()
     glyph.program.fromBytecode(instructionStream[:instructionLength])
     self.glyphStream = glyphStream
     self.instructionStream = instructionStream[instructionLength:]
Example #7
0
    def glyph(self, componentFlags=0x4):
        assert self._isClosed(), "Didn't close last contour."

        if self.handleOverflowingTransforms:
            # we can't encode transform values > 2 or < -2 in F2Dot14,
            # so we must decompose the glyph if any transform exceeds these
            overflowing = any(s > 2 or s < -2
                              for (glyphName,
                                   transformation) in self.components
                              for s in transformation[:4])

        components = []
        for glyphName, transformation in self.components:
            if (self.points
                    or (self.handleOverflowingTransforms and overflowing)):
                # can't have both coordinates and components, so decompose
                try:
                    baseGlyph = self.glyphSet[glyphName]
                except KeyError:
                    self.log.debug(
                        "can't decompose non-existing component '%s'; skipped",
                        glyphName)
                    continue
                else:
                    tpen = TransformPen(self, transformation)
                    baseGlyph.draw(tpen)
                    continue

            component = GlyphComponent()
            component.glyphName = glyphName
            component.x, component.y = transformation[4:]
            transformation = transformation[:4]
            if transformation != (1, 0, 0, 1):
                if (self.handleOverflowingTransforms
                        and any(MAX_F2DOT14 < s <= 2 for s in transformation)):
                    # clamp values ~= +2.0 so we can keep the component
                    transformation = tuple(
                        MAX_F2DOT14 if MAX_F2DOT14 < s <= 2 else s
                        for s in transformation)
                component.transform = (transformation[:2], transformation[2:])
            component.flags = componentFlags
            components.append(component)

        glyph = Glyph()
        glyph.coordinates = GlyphCoordinates(self.points)
        glyph.endPtsOfContours = self.endPts
        glyph.flags = array("B", self.types)
        self.init()

        if components:
            glyph.components = components
            glyph.numberOfContours = -1
        else:
            glyph.numberOfContours = len(glyph.endPtsOfContours)
            glyph.program = ttProgram.Program()
            glyph.program.fromBytecode(b"")

        return glyph
Example #8
0
def generateFont(options, font):
    generateFeatures(font, options)

    from datetime import datetime
    info = font.info
    major, minor = options.version.split(".")
    info.versionMajor, info.versionMinor = int(major), int(minor)
    year = datetime.now().year
    info.copyright = f"Copyright 2010-{year} The Amiri Project Authors (https://github.com/alif-type/amiri)."
    info.openTypeNameLicense = "This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: https://scripts.sil.org/OFL"
    info.openTypeNameLicenseURL = "https://scripts.sil.org/OFL"

    if options.output.endswith(".ttf"):
        from fontTools.ttLib import newTable
        from fontTools.ttLib.tables import ttProgram
        otf = compileTTF(font,
                         inplace=True,
                         removeOverlaps=True,
                         overlapsBackend="pathops",
                         featureWriters=[])

        otf["DSIG"] = DSIG = newTable("DSIG")
        DSIG.ulVersion = 1
        DSIG.usFlag = 0
        DSIG.usNumSigs = 0
        DSIG.signatureRecords = []

        otf["prep"] = prep = newTable("prep")
        prep.program = ttProgram.Program()
        prep.program.fromAssembly(
            ['PUSHW[]', '511', 'SCANCTRL[]', 'PUSHB[]', '4', 'SCANTYPE[]'])
    else:
        import cffsubr
        otf = compileOTF(font,
                         inplace=True,
                         optimizeCFF=0,
                         removeOverlaps=True,
                         overlapsBackend="pathops",
                         featureWriters=[])
        cffsubr.subroutinize(otf)

    if info.styleMapStyleName and "italic" in info.styleMapStyleName:
        otf['name'].names = [n for n in otf['name'].names if n.nameID != 17]
        for name in otf['name'].names:
            if name.nameID == 2:
                name.string = info.styleName
    glyf = otf.get("glyf")
    if glyf:
        from fontTools.ttLib.tables._g_l_y_f import UNSCALED_COMPONENT_OFFSET
        for name, glyph in glyf.glyphs.items():
            glyph.expand(glyf)
            if glyph.isComposite():
                for component in glyph.components:
                    component.flags |= UNSCALED_COMPONENT_OFFSET

    return otf
def main():
    font = TTFont(sys.argv[1])

    # Drop VOLT table
    if "TSIV" in font:
        del font["TSIV"]

    # Add STAT table
    os2 = font["OS/2"]
    italic = bool(os2.fsSelection & (1 << 0))

    fvar = font["fvar"]
    axes = [dict(tag=a.axisTag, name=a.axisNameID) for a in fvar.axes]

    if italic:
        value = dict(value=italic, name="Italic")
    else:
        value = dict(value=italic, name="Roman", flags=0x0002, linkedValue=1)
    axes.append(dict(tag="ital", name="Italic", values=[value]))

    buildStatTable(font, axes)

    # Prune name table
    names = [n for n in font["name"].names if n.platformID == 3]

    # Drop Regular from Roman font names
    if not italic:
        for name in names:
            if name.nameID in (3, 6):
                name.string = str(name).replace("-Regular", "")
            if name.nameID == 4:
                name.string = str(name).replace(" Regular", "")

    font["name"].names = names
    font["OS/2"].usMaxContext = maxCtxFont(font)

    font["DSIG"] = DSIG = newTable("DSIG")
    DSIG.ulVersion = 1
    DSIG.usFlag = 0
    DSIG.usNumSigs = 0
    DSIG.signatureRecords = []

    if "glyf" in font and "prep" not in font:
        # Google Fonts “smart dropout control”
        font["prep"] = prep = newTable("prep")
        prep.program = ttProgram.Program()
        prep.program.fromAssembly(
            ["PUSHW[]", "511", "SCANCTRL[]", "PUSHB[]", "4", "SCANTYPE[]"]
        )

    if "MVAR" in font:
        del font["MVAR"]

    font.save(sys.argv[1])
Example #10
0
def test__bool__():
    fpgm = table__f_p_g_m()
    assert not bool(fpgm)

    p = ttProgram.Program()
    fpgm.program = p
    assert not bool(fpgm)

    bc = bytearray([0])
    p.fromBytecode(bc)
    assert bool(fpgm)

    p.bytecode.pop()
    assert not bool(fpgm)
Example #11
0
    def glyph(self):
        glyph = Glyph()

        glyph.coordinates = GlyphCoordinates(self.points)
        glyph.endPtsOfContours = self.endPts
        glyph.flags = array("B", self.types)
        glyph.components = self.components

        if glyph.components:
            assert not glyph.endPtsOfContours, (
                "TrueType glyph can't have both contours and components.")
            glyph.numberOfContours = -1
        else:
            glyph.numberOfContours = len(glyph.endPtsOfContours)

        glyph.program = ttProgram.Program()
        glyph.program.fromBytecode("")

        return glyph
Example #12
0
    def glyph(self, componentFlags=0x4):
        assert self._isClosed(), "Didn't close last contour."

        components = self._buildComponents(componentFlags)

        glyph = Glyph()
        glyph.coordinates = GlyphCoordinates(self.points)
        glyph.endPtsOfContours = self.endPts
        glyph.flags = array("B", self.types)
        self.init()

        if components:
            glyph.components = components
            glyph.numberOfContours = -1
        else:
            glyph.numberOfContours = len(glyph.endPtsOfContours)
            glyph.program = ttProgram.Program()
            glyph.program.fromBytecode(b"")

        return glyph
Example #13
0
def generateFont(options, font):
    generateFeatures(font, options)

    from datetime import datetime
    info = font.info
    major, minor = options.version.split(".")
    info.versionMajor, info.versionMinor = int(major), int(minor)
    year = datetime.now().year
    info.copyright = f"Copyright 2010-{year} The Amiri Project Authors (https://github.com/alif-type/amiri)."
    info.openTypeNameLicense = "This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: https://scripts.sil.org/OFL"
    info.openTypeNameLicenseURL = "https://scripts.sil.org/OFL"

    if options.output.endswith(".ttf"):
        from fontTools.ttLib import newTable
        from fontTools.ttLib.tables import ttProgram
        otf = compileTTF(font,
                         inplace=True,
                         removeOverlaps=True,
                         overlapsBackend="pathops",
                         featureWriters=[])

        otf["prep"] = prep = newTable("prep")
        prep.program = ttProgram.Program()
        prep.program.fromAssembly(
            ['PUSHW[]', '511', 'SCANCTRL[]', 'PUSHB[]', '4', 'SCANTYPE[]'])
    else:
        otf = compileOTF(font,
                         inplace=True,
                         optimizeCFF=2,
                         subroutinizer="cffsubr",
                         removeOverlaps=True,
                         overlapsBackend="pathops",
                         featureWriters=[])

    if info.styleMapStyleName and "italic" in info.styleMapStyleName:
        otf['name'].names = [n for n in otf['name'].names if n.nameID != 17]
        for name in otf['name'].names:
            if name.nameID == 2:
                name.string = info.styleName

    return otf
Example #14
0
    def glyph(self, componentFlags=0x4):
        """Returns a :py:class:`~._g_l_y_f.Glyph` object representing the glyph."""
        assert self._isClosed(), "Didn't close last contour."

        components = self._buildComponents(componentFlags)

        glyph = Glyph()
        glyph.coordinates = GlyphCoordinates(self.points)
        glyph.coordinates.toInt()
        glyph.endPtsOfContours = self.endPts
        glyph.flags = array("B", self.types)
        self.init()

        if components:
            glyph.components = components
            glyph.numberOfContours = -1
        else:
            glyph.numberOfContours = len(glyph.endPtsOfContours)
            glyph.program = ttProgram.Program()
            glyph.program.fromBytecode(b"")

        return glyph
Example #15
0
def fix_unhinted_font(ttFont):
    """Improve the appearance of an unhinted font on Win platforms by:
        - Add a new GASP table with a newtable that has a single
          range which is set to smooth.
        - Add a new prep table which is optimized for unhinted fonts.
    
    Args:
        ttFont: a TTFont instance
    """
    gasp = newTable("gasp")
    # Set GASP so all sizes are smooth
    gasp.gaspRange = {0xFFFF: 15}

    program = ttProgram.Program()
    assembly = ["PUSHW[]", "511", "SCANCTRL[]", "PUSHB[]", "4", "SCANTYPE[]"]
    program.fromAssembly(assembly)

    prep = newTable("prep")
    prep.program = program

    ttFont["gasp"] = gasp
    ttFont["prep"] = prep
Example #16
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
Example #17
0
def main():
    parser = argparse.ArgumentParser(description="Merge Aref Ruqaa fonts.")
    parser.add_argument("file1", metavar="FILE", help="input font to process")
    parser.add_argument("file2", metavar="FILE", help="input font to process")
    parser.add_argument("--color", action="store_true", help="merge color tables")
    parser.add_argument("--family", metavar="STR", help="base family name")
    parser.add_argument("--suffix", metavar="STR", help="suffix to add to family name")
    parser.add_argument(
        "--out-file", metavar="FILE", help="output font to write", required=True
    )

    args = parser.parse_args()

    configLogger(level=logging.ERROR)

    merger = merge.Merger()
    font = merger.merge([args.file1, args.file2])

    if args.color:
        orig = TTFont(args.file1)
        font["CPAL"] = copy.deepcopy(orig["CPAL"])
        font["COLR"] = copy.deepcopy(orig["COLR"])

        name = font["name"]
        family = args.family
        psname = args.family.replace(" ", "")
        for rec in name.names:
            if rec.nameID in (1, 3, 4, 6):
                rec.string = (
                    str(rec)
                    .replace(family, family + " " + args.suffix)
                    .replace(psname, psname + args.suffix)
                )

    unicodes = font.getBestCmap().keys()

    options = subset.Options()
    options.set(
        layout_features="*",
        layout_scripts="*",
        name_IDs="*",
        name_languages="*",
        notdef_outline=True,
        glyph_names=False,
        recalc_average_width=True,
    )
    subsetter = subset.Subsetter(options=options)
    subsetter.populate(unicodes=unicodes)
    subsetter.subset(font)

    if "glyf" in font:
        from fontTools.ttLib import newTable
        from fontTools.ttLib.tables import ttProgram

        font["prep"] = prep = newTable("prep")
        prep.program = ttProgram.Program()
        prep.program.fromAssembly(
            ["PUSHW[]", "511", "SCANCTRL[]", "PUSHB[]", "4", "SCANTYPE[]"]
        )

    font.save(args.out_file)
    print("GASP wasn't there")

# Print the PREP table
if "prep" in font:
    print("PREP was: ", ttProgram.Program.getAssembly(font["prep"].program))
else:
    print("PREP wasn't there")

# Create a new GASP table
gasp = ttLib.newTable("gasp")

# Set GASP to the magic number
gasp.gaspRange = {65535: 15}

# Create a new hinting program
program = ttProgram.Program()

assembly = ['PUSHW[]', '511', 'SCANCTRL[]', 'PUSHB[]', '4', 'SCANTYPE[]']
program.fromAssembly(assembly)

# Create a new PREP table
prep = ttLib.newTable("prep")

# Insert the magic program into it
prep.program = program

# Add the tables to the font, replacing existing ones
font["gasp"] = gasp
font["prep"] = prep

# Print the Gasp table
Example #19
0
def build(instance, isTTF, version):
    font = instance.parent
    source = font.masters[0]

    fea, marks = makeFeatures(instance, source)

    source.blueValues = []
    source.otherBlues = []

    for zone in sorted(source.alignmentZones):
        pos = zone.position
        size = zone.size
        vals = sorted((pos, pos + size))
        if pos == 0 or size >= 0:
            source.blueValues.extend(vals)
        else:
            source.otherBlues.extend(vals)

    fontinfo = f"""
        FontName {instance.fontName}
        OrigEmSqUnits {font.upm}
        DominantV {source.verticalStems}
        DominantH {source.horizontalStems}
        BaselineOvershoot {source.blueValues[0]}
        BaselineYCoord {source.blueValues[1]}
        LcHeight {source.blueValues[2]}
        LcOvershoot {source.blueValues[3] - source.blueValues[2]}
        CapHeight {source.blueValues[4]}
        CapOvershoot {source.blueValues[5] - source.blueValues[4]}
        AscenderHeight {source.blueValues[6]}
        AscenderOvershoot {source.blueValues[7] - source.blueValues[6]}
        Baseline5 {source.otherBlues[1]}
        Baseline5Overshoot {source.otherBlues[0] - source.otherBlues[1]}

        FlexOK true
        BlueFuzz 1
    """

    characterMap = {}
    glyphs = {}
    metrics = {}
    layerSet = {g.name: g.layers[source.id] for g in font.glyphs}

    if isTTF:
        from fontTools.pens.cu2quPen import Cu2QuPen
        from fontTools.pens.recordingPen import RecordingPen

        for glyph in font.glyphs:
            layer = glyph.layers[source.id]
            pen = RecordingPen()
            layer.draw(pen)
            layer.paths = []
            layer.components = []
            pen.replay(Cu2QuPen(layer.getPen(), 1.0, reverse_direction=True))

    for glyph in font.glyphs:
        if not glyph.export and not isTTF:
            continue
        name = glyph.name

        for code in glyph.unicodes:
            characterMap[int(code, 16)] = name

        layer = glyph.layers[source.id]
        width = 0 if name in marks else layer.width

        pen = BoundsPen(layerSet)
        layer.draw(pen)
        metrics[name] = (width, pen.bounds[0] if pen.bounds else 0)

        if isTTF:
            from fontTools.pens.ttGlyphPen import TTGlyphPen

            pen = TTGlyphPen(layerSet)
            if layer.paths:
                # Decompose and remove overlaps.
                path = Path()
                layer.draw(DecomposePathPen(path, layerSet))
                path.simplify(fix_winding=True, keep_starting_points=True)
                path.draw(pen)
            else:
                # Composite-only glyph, no need to decompose.
                layer.draw(FlattenComponentsPen(pen, layerSet))
            glyphs[name] = pen.glyph()
        else:
            from fontTools.pens.t2CharStringPen import T2CharStringPen

            # Draw glyph and remove overlaps.
            path = Path()
            layer.draw(DecomposePathPen(path, layerSet))
            path.simplify(fix_winding=True, keep_starting_points=True)

            # Build CharString.
            pen = T2CharStringPen(width, None)
            path.draw(pen)
            glyphs[name] = pen.getCharString()

    vendor = font.customParameters["vendorID"]
    names = {
        "copyright": font.copyright,
        "familyName": instance.familyName,
        "styleName": instance.name,
        "uniqueFontIdentifier": f"{version};{vendor};{instance.fontName}",
        "fullName": instance.fullName,
        "version": f"Version {version}",
        "psName": instance.fontName,
        "manufacturer": font.manufacturer,
        "designer": font.designer,
        "description": font.customParameters["description"],
        "vendorURL": font.manufacturerURL,
        "designerURL": font.designerURL,
        "licenseDescription": font.customParameters["license"],
        "licenseInfoURL": font.customParameters["licenseURL"],
        "sampleText": font.customParameters["sampleText"],
    }

    date = int(font.date.timestamp()) - epoch_diff
    fb = FontBuilder(font.upm, isTTF=isTTF)
    fb.updateHead(fontRevision=version, created=date, modified=date)
    fb.setupGlyphOrder(font.glyphOrder)
    fb.setupCharacterMap(characterMap)
    fb.setupNameTable(names, mac=False)
    fb.setupHorizontalHeader(
        ascent=source.ascender,
        descent=source.descender,
        lineGap=source.customParameters["typoLineGap"],
    )

    if isTTF:
        fb.setupGlyf(glyphs)
    else:
        privateDict = {
            "BlueValues": source.blueValues,
            "OtherBlues": source.otherBlues,
            "StemSnapH": source.horizontalStems,
            "StemSnapV": source.verticalStems,
            "StdHW": source.horizontalStems[0],
            "StdVW": source.verticalStems[0],
        }

        fontInfo = {
            "FullName": names["fullName"],
            "Notice": names["copyright"].replace("©", "\(c\)"),
            "version": f"{version}",
            "Weight": instance.name,
        }

        fb.setupCFF(names["psName"], fontInfo, glyphs, privateDict)

    fb.setupHorizontalMetrics(metrics)

    codePages = [CODEPAGE_RANGES[v] for v in font.customParameters["codePageRanges"]]
    fb.setupOS2(
        version=4,
        sTypoAscender=source.ascender,
        sTypoDescender=source.descender,
        sTypoLineGap=source.customParameters["typoLineGap"],
        usWinAscent=source.ascender,
        usWinDescent=-source.descender,
        sxHeight=source.xHeight,
        sCapHeight=source.capHeight,
        achVendID=vendor,
        fsType=calcBits(font.customParameters["fsType"], 0, 16),
        fsSelection=calcFsSelection(instance),
        ulUnicodeRange1=calcBits(font.customParameters["unicodeRanges"], 0, 32),
        ulCodePageRange1=calcBits(codePages, 0, 32),
    )

    underlineThickness = int(source.customParameters["underlineThickness"])
    underlinePosition = int(source.customParameters["underlinePosition"])
    fb.setupPost(
        keepGlyphNames=False,
        underlineThickness=underlineThickness,
        underlinePosition=underlinePosition + underlineThickness // 2,
    )

    fb.font["meta"] = meta = newTable("meta")
    meta.data = {"dlng": "Arab", "slng": "Arab"}

    fb.addOpenTypeFeatures(fea)

    if isTTF:
        from fontTools.ttLib.tables import ttProgram

        fb.setupDummyDSIG()

        fb.font["gasp"] = gasp = newTable("gasp")
        gasp.gaspRange = {0xFFFF: 15}

        fb.font["prep"] = prep = newTable("prep")
        prep.program = ttProgram.Program()
        assembly = ["PUSHW[]", "511", "SCANCTRL[]", "PUSHB[]", "4", "SCANTYPE[]"]
        prep.program.fromAssembly(assembly)
    else:
        from cffsubr import subroutinize

        subroutinize(fb.font)

    return fb.font