Exemple #1
0
def _merge_OTL(font, model, master_fonts, axisTags):

	log.info("Merging OpenType Layout tables")
	merger = VariationMerger(model, axisTags, font)

	merger.mergeTables(font, master_fonts, ['GPOS'])
	# TODO Merge GSUB
	# TODO Merge GDEF itself!
	store = merger.store_builder.finish()
	if not store.VarData:
		return
	try:
		GDEF = font['GDEF'].table
		assert GDEF.Version <= 0x00010002
	except KeyError:
		font['GDEF']= newTable('GDEF')
		GDEFTable = font["GDEF"] = newTable('GDEF')
		GDEF = GDEFTable.table = ot.GDEF()
	GDEF.Version = 0x00010003
	GDEF.VarStore = store

	# Optimize
	varidx_map = store.optimize()
	GDEF.remap_device_varidxes(varidx_map)
	if 'GPOS' in font:
		font['GPOS'].table.remap_device_varidxes(varidx_map)
Exemple #2
0
def otf_to_ttf(ttFont, post_format=POST_FORMAT, **kwargs):
    assert ttFont.sfntVersion == "OTTO"
    assert "CFF " in ttFont

    glyphOrder = ttFont.getGlyphOrder()

    ttFont["loca"] = newTable("loca")
    ttFont["glyf"] = glyf = newTable("glyf")
    glyf.glyphOrder = glyphOrder
    glyf.glyphs = glyphs_to_quadratic(ttFont.getGlyphSet(), **kwargs)
    del ttFont["CFF "]

    ttFont["maxp"] = maxp = newTable("maxp")
    maxp.tableVersion = 0x00010000
    maxp.maxZones = 1
    maxp.maxTwilightPoints = 0
    maxp.maxStorage = 0
    maxp.maxFunctionDefs = 0
    maxp.maxInstructionDefs = 0
    maxp.maxStackElements = 0
    maxp.maxSizeOfInstructions = 0
    maxp.maxComponentElements = max(
        len(g.components if hasattr(g, 'components') else [])
        for g in glyf.glyphs.values())

    post = ttFont["post"]
    post.formatType = post_format
    post.extraNames = []
    post.mapping = {}
    post.glyphOrder = glyphOrder

    ttFont.sfntVersion = "\000\001\000\000"
    def setupTable_glyf(self):
        """Make the glyf table."""

        allGlyphs = self.allGlyphs
        if self.convertCubics:
            from cu2qu.pens import Cu2QuPen
            allGlyphs = {}
            for name, glyph in self.allGlyphs.items():
                if isinstance(glyph, StubGlyph):
                    allGlyphs[name] = glyph
                    continue
                newGlyph = glyph.__class__()
                glyph.draw(Cu2QuPen(
                    newGlyph.getPen(), self.cubicConversionError,
                    reverse_direction=True))
                allGlyphs[name] = newGlyph

        self.otf["loca"] = newTable("loca")
        self.otf["glyf"] = glyf = newTable("glyf")
        glyf.glyphs = {}
        glyf.glyphOrder = self.glyphOrder

        for name in self.glyphOrder:
            pen = TTGlyphPen(allGlyphs)
            allGlyphs[name].draw(pen)
            glyf[name] = pen.glyph()
 def makeFont(self):
     cvt, cvar, fvar = newTable("cvt "), newTable("cvar"), newTable("fvar")
     font = {"cvt ": cvt, "cvar": cvar, "fvar": fvar}
     cvt.values = [0, 1000, -2000]
     Axis = getTableModule("fvar").Axis
     fvar.axes = [Axis(), Axis()]
     fvar.axes[0].axisTag, fvar.axes[1].axisTag = "wght", "wdth"
     return font, cvar
Exemple #5
0
def colorize(font):
    COLR = newTable("COLR")
    CPAL = newTable("CPAL")
    glyf = font["glyf"]
    hmtx = font["hmtx"]

    CPAL.version = 0
    COLR.version = 0

    palette = list(GROUPS.values())
    CPAL.palettes = [palette]
    CPAL.numPaletteEntries = len(palette)

    COLR.ColorLayers = {}

    glyphOrder = list(font.getGlyphOrder())
    for name in glyphOrder:
        glyph = glyf[name]

        layers = []
        if glyph.isComposite() and len(glyph.components) > 1:
            componentColors = [getGlyphColor(c.getComponentInfo()[0]) for c in glyph.components]
            if any(componentColors):
                for component in glyph.components:
                    componentName, trans = component.getComponentInfo()
                    componentColor = getGlyphColor(componentName)
                    if componentColor is None:
                        componentColor = 0xFFFF
                    else:
                        componentColor = palette.index(componentColor)
                    if trans == (1, 0, 0, 1, 0, 0):
                        layers.append(newLayer(componentName, componentColor))
                    else:
                        newName = "%s.%s" % (componentName, hash(trans))
                        if newName not in font.glyphOrder:
                            font.glyphOrder.append(newName)

                            newGlyph = getTableModule("glyf").Glyph()
                            newGlyph.numberOfContours = -1
                            newGlyph.components = [component]
                            glyf.glyphs[newName] = newGlyph
                            assert len(glyf.glyphs) == len(font.glyphOrder), (name, newName)

                            width = hmtx[name][0]
                            lsb = hmtx[componentName][1] + trans[4]
                            hmtx.metrics[newName] = [width, lsb]
                        layers.append(newLayer(newName, componentColor))

        if not layers:
            color = getGlyphColor(name)
            if color is not None:
                layers = [newLayer(name, palette.index(color))]

        if layers:
            COLR[name] = layers

    font["COLR"] = COLR
    font["CPAL"] = CPAL
Exemple #6
0
	def setUp(self):
		self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
		font.setGlyphOrder(self.glyphOrder)
		font['head'] = ttLib.newTable('head')
		font['maxp'] = ttLib.newTable('maxp')
		font['loca'] = WOFF2LocaTable()
		font['glyf'] = WOFF2GlyfTable()
		for tag in self.transformedTags:
			font[tag].decompile(self.tables[tag], font)
Exemple #7
0
 def setUp(self):
     self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
     font.setGlyphOrder(self.glyphOrder)
     font["head"] = ttLib.newTable("head")
     font["maxp"] = ttLib.newTable("maxp")
     font["loca"] = WOFF2LocaTable()
     font["glyf"] = WOFF2GlyfTable()
     for tag in self.transformedTags:
         font[tag].decompile(self.tables[tag], font)
Exemple #8
0
 def makeFont(self, numGlyphs, numberOfMetrics):
     font = TTFont()
     maxp = font['maxp'] = newTable('maxp')
     maxp.numGlyphs = numGlyphs
     # from A to ...
     font.glyphOrder = [chr(i) for i in range(65, 65+numGlyphs)]
     headerTag = self.tableClass.headerTag
     font[headerTag] = newTable(headerTag)
     numberOfMetricsName = self.tableClass.numberOfMetricsName
     setattr(font[headerTag], numberOfMetricsName, numberOfMetrics)
     return font
Exemple #9
0
    def setupTable_glyf(self):
        """Make the glyf table."""

        self.otf["loca"] = newTable("loca")
        self.otf["glyf"] = glyf = newTable("glyf")
        glyf.glyphs = {}
        glyf.glyphOrder = self.glyphOrder

        for name in self.glyphOrder:
            pen = TTGlyphPen(self.allGlyphs)
            self.allGlyphs[name].draw(pen)
            glyf[name] = pen.glyph()
Exemple #10
0
    def setupTable_glyf(self):
        """Make the glyf table."""

        self.otf["loca"] = newTable("loca")
        self.otf["glyf"] = glyf = newTable("glyf")
        glyf.glyphs = {}
        glyf.glyphOrder = self.glyphOrder

        for glyph in self.ufo:
            pen = TTGlyphPen(self.ufo)
            glyph.draw(pen)
            glyf[glyph.name] = pen.glyph()
 def makeFont(self, variations):
     glyphs = [".notdef", "space", "I"]
     Axis = getTableModule("fvar").Axis
     Glyph = getTableModule("glyf").Glyph
     glyf, fvar, gvar = newTable("glyf"), newTable("fvar"), newTable("gvar")
     font = FakeFont(glyphs)
     font.tables = {"glyf": glyf, "gvar": gvar, "fvar": fvar}
     glyf.glyphs = {glyph: Glyph() for glyph in glyphs}
     glyf.glyphs["I"].coordinates = [(10, 10), (10, 20), (20, 20), (20, 10)]
     fvar.axes = [Axis(), Axis()]
     fvar.axes[0].axisTag, fvar.axes[1].axisTag = "wght", "wdth"
     gvar.variations = variations
     return font, gvar
Exemple #12
0
 def test_toXML(self):
     font = TTFont(sfntVersion="\x00\x01\x00\x00")
     glyfTable = font['glyf'] = newTable('glyf')
     font['head'] = newTable('head')
     font['loca'] = newTable('loca')
     font['maxp'] = newTable('maxp')
     font['maxp'].decompile(self.maxpData, font)
     font['head'].decompile(self.headData, font)
     font['loca'].decompile(self.locaData, font)
     glyfTable.decompile(self.glyfData, font)
     out = UnicodeIO()
     font.saveXML(out)
     glyfXML = strip_ttLibVersion(out.getvalue()).splitlines()
     self.assertEqual(glyfXML, self.glyfXML)
Exemple #13
0
def buildGSUB():
    """Build a GSUB table from scratch."""
    fontTable = newTable("GSUB")
    gsub = fontTable.table = ot.GSUB()
    gsub.Version = 0x00010001  # allow gsub.FeatureVariations

    gsub.ScriptList = ot.ScriptList()
    gsub.ScriptList.ScriptRecord = []
    gsub.FeatureList = ot.FeatureList()
    gsub.FeatureList.FeatureRecord = []
    gsub.LookupList = ot.LookupList()
    gsub.LookupList.Lookup = []

    srec = ot.ScriptRecord()
    srec.ScriptTag = 'DFLT'
    srec.Script = ot.Script()
    srec.Script.DefaultLangSys = None
    srec.Script.LangSysRecord = []

    langrec = ot.LangSysRecord()
    langrec.LangSys = ot.LangSys()
    langrec.LangSys.ReqFeatureIndex = 0xFFFF
    langrec.LangSys.FeatureIndex = [0]
    srec.Script.DefaultLangSys = langrec.LangSys

    gsub.ScriptList.ScriptRecord.append(srec)
    gsub.FeatureVariations = None

    return fontTable
Exemple #14
0
    def fix(self):
        if 'DSIG' in self.font:
            return False

        try:
            from fontTools.ttLib.tables.D_S_I_G_ import SignatureRecord
        except ImportError:
            error_message = ("The '{}' font does not have an existing"
                             " digital signature proving its authenticity,"
                             " so Fontbakery needs to add one. To do this"
                             " requires version 2.3 or later of Fonttools"
                             " to be installed. Please upgrade at"
                             " https://pypi.python.org/pypi/FontTools/2.4")
            logger.error(error_message.format(os.path.basename(self.fontpath)))
            return False


        newDSIG = ttLib.newTable("DSIG")
        newDSIG.ulVersion = 1
        newDSIG.usFlag = 1
        newDSIG.usNumSigs = 1
        sig = SignatureRecord()
        sig.ulLength = 20
        sig.cbSignature = 12
        sig.usReserved2 = 0
        sig.usReserved1 = 0
        sig.pkcs7 = '\xd3M4\xd3M5\xd3M4\xd3M4'
        sig.ulFormat = 1
        sig.ulOffset = 20
        newDSIG.signatureRecords = [sig]
        self.font.tables["DSIG"] = newDSIG
        return True
Exemple #15
0
def normalise_table(font, tag, padding=4):
    """ Return normalised table data. Keep 'font' instance unmodified. """
    assert tag in ("glyf", "loca", "head")
    assert tag in font
    if tag == "head":
        origHeadFlags = font["head"].flags
        font["head"].flags |= 1 << 11
        tableData = font["head"].compile(font)
    if font.sfntVersion in ("\x00\x01\x00\x00", "true"):
        assert {"glyf", "loca", "head"}.issubset(font.keys())
        origIndexFormat = font["head"].indexToLocFormat
        if hasattr(font["loca"], "locations"):
            origLocations = font["loca"].locations[:]
        else:
            origLocations = []
        glyfTable = ttLib.newTable("glyf")
        glyfTable.decompile(font.getTableData("glyf"), font)
        glyfTable.padding = padding
        if tag == "glyf":
            tableData = glyfTable.compile(font)
        elif tag == "loca":
            glyfTable.compile(font)
            tableData = font["loca"].compile(font)
        if tag == "head":
            glyfTable.compile(font)
            font["loca"].compile(font)
            tableData = font["head"].compile(font)
        font["head"].indexToLocFormat = origIndexFormat
        font["loca"].set(origLocations)
    if tag == "head":
        font["head"].flags = origHeadFlags
    return tableData
Exemple #16
0
def _add_stat(font, axes):
	# for now we just get the axis tags and nameIDs from the fvar,
	# so we can reuse the same nameIDs which were defined in there.
	# TODO make use of 'axes' once it adds style attributes info:
	# https://github.com/LettError/designSpaceDocument/issues/8

	if "STAT" in font:
		return

	fvarTable = font['fvar']

	STAT = font["STAT"] = newTable('STAT')
	stat = STAT.table = ot.STAT()
	stat.Version = 0x00010002

	axisRecords = []
	for i, a in enumerate(fvarTable.axes):
		axis = ot.AxisRecord()
		axis.AxisTag = Tag(a.axisTag)
		axis.AxisNameID = a.axisNameID
		axis.AxisOrdering = i
		axisRecords.append(axis)

	axisRecordArray = ot.AxisRecordArray()
	axisRecordArray.Axis = axisRecords
	# XXX these should not be hard-coded but computed automatically
	stat.DesignAxisRecordSize = 8
	stat.DesignAxisCount = len(axisRecords)
	stat.DesignAxisRecord = axisRecordArray

	# for the elided fallback name, we default to the base style name.
	# TODO make this user-configurable via designspace document
	stat.ElidedFallbackNameID = 2
Exemple #17
0
def main():
	# open the source font
	f = ttLib.TTFont("NyanTemplate.ttf")

	# mapping of image size to directory name
	sets = {
		15: "pngs"
	}

	sbix = ttLib.newTable("sbix")
	go = f.getGlyphOrder()

	for s, d in sets.iteritems():
		# make an empty bitmap set for current image size
		mySet = BitmapSet(size=s)
		for root, dirs, files in walk(d, topdown=False):
			for myFile in files:
				if myFile[-4:] == ".png":
					# use file name without suffix as glyph name
					# FIXME: filename clashes with case-sensitive glyph names
					glyphname = myFile[:-4]
					if glyphname in go: # only use files that have a matching glyph in the source font
						print glyphname
						img = open(join(root, myFile), "rb")
						imgData = img.read()
						img.close()
						# make a bitmap record for the current image
						myBitmap = Bitmap(glyphName=glyphname, imageFormatTag="png ", imageData=imgData)
						# add bitmap to current bitmap set
						mySet.bitmaps[glyphname] = myBitmap
		sbix.bitmapSets[s] = mySet
	# add sbix table to the source font
	f["sbix"] = sbix
	# save font under new name
	f.save("Nyan.ttf")
Exemple #18
0
def normalise_table(font, tag, padding=4):
	""" Return normalised table data. Keep 'font' instance unmodified. """
	assert tag in ('glyf', 'loca', 'head')
	assert tag in font
	if tag == 'head':
		origHeadFlags = font['head'].flags
		font['head'].flags |= (1 << 11)
		tableData = font['head'].compile(font)
	if font.sfntVersion in ("\x00\x01\x00\x00", "true"):
		assert {'glyf', 'loca', 'head'}.issubset(font.keys())
		origIndexFormat = font['head'].indexToLocFormat
		if hasattr(font['loca'], 'locations'):
			origLocations = font['loca'].locations[:]
		else:
			origLocations = []
		glyfTable = ttLib.newTable('glyf')
		glyfTable.decompile(font.getTableData('glyf'), font)
		glyfTable.padding = padding
		if tag == 'glyf':
			tableData = glyfTable.compile(font)
		elif tag == 'loca':
			glyfTable.compile(font)
			tableData = font['loca'].compile(font)
		if tag == 'head':
			glyfTable.compile(font)
			font['loca'].compile(font)
			tableData = font['head'].compile(font)
		font['head'].indexToLocFormat = origIndexFormat
		font['loca'].set(origLocations)
	if tag == 'head':
		font['head'].flags = origHeadFlags
	return tableData
Exemple #19
0
def makeTable_cmap(ttf, glyphs):
    unicodeCMAP = {index: glyph for glyph in glyphs if glyph in ttf["glyf"].glyphs for index in glyphs[glyph][1]}
    macRoman = dict(CMAP_MACROMAN)
    macRomanCMAP = {index: macRoman[index] if index in macRoman and macRoman[index] in ttf["glyf"].glyphs else '.notdef' for index in range(256)}

    # Unicode
    cmap4_0_3 = cmap_format_4(4)
    cmap4_0_3.platformID = 0
    cmap4_0_3.platEncID = 3
    cmap4_0_3.language = 0
    cmap4_0_3.cmap = unicodeCMAP

    # Mac Roman
    cmap0_1_0 = cmap_format_0(0)
    cmap0_1_0.platformID = 1
    cmap0_1_0.platEncID = 0
    cmap0_1_0.language = 0
    cmap0_1_0.cmap = macRomanCMAP

    # Windows
    cmap4_3_1 = cmap_format_4(4)
    cmap4_3_1.platformID = 3
    cmap4_3_1.platEncID = 1
    cmap4_3_1.language = 0
    cmap4_3_1.cmap = unicodeCMAP

    cmap = newTable("cmap")
    cmap.tableVersion = 0
    cmap.tables = [cmap4_0_3, cmap0_1_0, cmap4_3_1]
    ttf["cmap"] = cmap
Exemple #20
0
    def test_decompile_not_enough_data(self):
        font = self.makeFont(numGlyphs=1, numberOfMetrics=1)
        mtxTable = newTable(self.tag)
        msg = "not enough '%s' table data" % self.tag

        with self.assertRaisesRegex(TTLibError, msg):
            mtxTable.decompile(b"\0\0\0", font)
    def setupTable_vmtx(self):
        """
        Make the vmtx table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """

        self.otf["vmtx"] = vmtx = newTable("vmtx")
        vmtx.metrics = {}
        for glyphName, glyph in self.allGlyphs.items():
            height = glyph.height
            verticalOrigin = _getVerticalOrigin(glyph)
            top = 0
            if len(glyph) or len(glyph.components):
                # tsb should be consistent with glyf yMax, which is just
                # maximum y for coordinate data
                pen = ControlBoundsPen(self.ufo, ignoreSinglePoints=True)
                glyph.draw(pen)
                if pen.bounds is not None:
                    top = pen.bounds[3]
            # take ceil of tsb/yMax, as fontTools does with max bounds
            vmtx[glyphName] = (_roundInt(height),
                               int(math.ceil(verticalOrigin) - math.ceil(top)))
Exemple #22
0
def _merge_OTL(font, model, master_fonts, axisTags, base_idx):

	log.info("Merging OpenType Layout tables")
	merger = VariationMerger(model, axisTags, font)

	merge_tables(font, merger, master_fonts, axisTags, base_idx, ['GPOS'])
	store = merger.store_builder.finish()
	try:
		GDEF = font['GDEF'].table
		assert GDEF.Version <= 0x00010002
	except KeyError:
		font['GDEF']= newTable('GDEF')
		GDEFTable = font["GDEF"] = newTable('GDEF')
		GDEF = GDEFTable.table = ot.GDEF()
	GDEF.Version = 0x00010003
	GDEF.VarStore = store
Exemple #23
0
 def test_toXML_v1(self):
     name = FakeNameTable({258: "Spring theme", 259: "Winter theme",
                           513: "darks", 515: "lights"})
     cpal = newTable('CPAL')
     ttFont = {"name": name, "CPAL": cpal}
     cpal.decompile(CPAL_DATA_V1, ttFont)
     self.assertEqual(getXML(cpal.toXML, ttFont),
                      '<version value="1"/>'
                      '<numPaletteEntries value="3"/>'
                      '<palette index="0" label="258" type="1">'
                      '  <!-- Spring theme -->'
                      '  <color index="0" value="#CAFECAFE"/>'
                      '  <color index="1" value="#22110033"/>'
                      '  <color index="2" value="#66554477"/>'
                      '</palette>'
                      '<palette index="1" label="259" type="2">'
                      '  <!-- Winter theme -->'
                      '  <color index="0" value="#59413127"/>'
                      '  <color index="1" value="#42424242"/>'
                      '  <color index="2" value="#13330037"/>'
                      '</palette>'
                      '<paletteEntryLabels>'
                      '  <label index="0" value="513"/><!-- darks -->'
                      '  <label index="1" value="514"/>'
                      '  <label index="2" value="515"/><!-- lights -->'
                      '</paletteEntryLabels>')
Exemple #24
0
    def test_delitem(self):
        mtxTable = newTable(self.tag)
        mtxTable.metrics = {'A': (0, 0)}

        del mtxTable['A']

        self.assertTrue('A' not in mtxTable.metrics)
Exemple #25
0
    def test_compile_struct_out_of_range(self):
        font = self.makeFont(numGlyphs=1, numberOfMetrics=1)
        mtxTable = font[self.tag] = newTable(self.tag)
        mtxTable.metrics = {'A': (0xFFFF+1, -0x8001)}

        with self.assertRaises(struct.error):
            mtxTable.compile(font)
Exemple #26
0
	def test_addMultilingualName(self):
		# Microsoft Windows has language codes for “English” (en)
		# and for “Standard German as used in Switzerland” (de-CH).
		# In this case, we expect that the implementation just
		# encodes the name for the Windows platform; Apple platforms
		# have been able to decode Windows names since the early days
		# of OSX (~2001). However, Windows has no language code for
		# “Swiss German as used in Liechtenstein” (gsw-LI), so we
		# expect that the implementation populates the 'ltag' table
 		# to represent that particular, rather exotic BCP47 code.
		font = FakeFont(glyphs=[".notdef", "A"])
		nameTable = font.tables['name'] = newTable("name")
		with CapturingLogHandler(log, "WARNING") as captor:
			widthID = nameTable.addMultilingualName({
				"en": "Width",
				"de-CH": "Breite",
				"gsw-LI": "Bräiti",
			}, ttFont=font)
			self.assertEqual(widthID, 256)
			xHeightID = nameTable.addMultilingualName({
				"en": "X-Height",
				"gsw-LI": "X-Hööchi"
			}, ttFont=font)
			self.assertEqual(xHeightID, 257)
		captor.assertRegex("cannot add Windows name in language gsw-LI")
		self.assertEqual(names(nameTable), [
			(256, 0, 4,      0, "Bräiti"),
			(256, 3, 1, 0x0409, "Width"),
			(256, 3, 1, 0x0807, "Breite"),
			(257, 0, 4,      0, "X-Hööchi"),
			(257, 3, 1, 0x0409, "X-Height"),
		])
		self.assertEqual(set(font.tables.keys()), {"ltag", "name"})
		self.assertEqual(font["ltag"].tags, ["gsw-LI"])
Exemple #27
0
def _add_gvar(font, model, master_ttfs):

	log.info("Generating gvar")
	assert "gvar" not in font
	gvar = font["gvar"] = newTable('gvar')
	gvar.version = 1
	gvar.reserved = 0
	gvar.variations = {}

	for glyph in font.getGlyphOrder():

		allData = [_GetCoordinates(m, glyph) for m in master_ttfs]
		allCoords = [d[0] for d in allData]
		allControls = [d[1] for d in allData]
		control = allControls[0]
		if (any(c != control for c in allControls)):
			warnings.warn("glyph %s has incompatible masters; skipping" % glyph)
			continue
		del allControls

		# Update gvar
		gvar.variations[glyph] = []
		deltas = model.getDeltas(allCoords)
		supports = model.supports
		assert len(deltas) == len(supports)
		for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])):
			var = TupleVariation(support, delta)
			gvar.variations[glyph].append(var)
Exemple #28
0
def makeDSIG(tt_font):
    '''
    Add a dummy DSIG table to an OpenType-TTF font, so positioning features work in Office applications on Windows.

    thanks to Ben Kiel on TypeDrawers:
    http://typedrawers.com/discussion/192/making-ot-ttf-layout-features-work-in-ms-word-2010

    '''
    from fontTools import ttLib
    from fontTools.ttLib.tables.D_S_I_G_ import SignatureRecord
    newDSIG = ttLib.newTable("DSIG")
    newDSIG.ulVersion = 1
    newDSIG.usFlag = 1
    newDSIG.usNumSigs = 1
    sig = SignatureRecord()
    sig.ulLength = 20
    sig.cbSignature = 12
    sig.usReserved2 = 0
    sig.usReserved1 = 0
    sig.pkcs7 = '\xd3M4\xd3M5\xd3M4\xd3M4'
    sig.ulFormat = 1
    sig.ulOffset = 20
    newDSIG.signatureRecords = [sig]
    tt_font["DSIG"] = newDSIG
    # ugly but necessary -> so all tables are added to ttfont
    # tt_font.lazy = False
    for key in tt_font.keys():
        print tt_font[key]
Exemple #29
0
    def fourStyleFamily(self, position, suffix=None):
        """
        Replaces the name table and certain OS/2 values with those that will make a four-style family.
        """
        f = self.f
        source = TTFont(fourStyleFamilySources[position])

        tf = tempfile.mkstemp()
        pathToXML = tf[1]
        source.saveXML(pathToXML, tables=['name'])
        os.close(tf[0])
        
        with open(pathToXML, "r") as temp:
            xml = temp.read()

        # make the changes
        if suffix:
            xml = xml.replace("Input", "Input" + suffix)

        # save the table
        with open(pathToXML, 'w') as temp:
            temp.write(xml)
            temp.write('\r')

        f['OS/2'].usWeightClass = source['OS/2'].usWeightClass
        f['OS/2'].fsType = source['OS/2'].fsType

        # write the table
        f['name'] = newTable('name')
        importXML(f, pathToXML)
Exemple #30
0
    def setupTable_post(self):
        """
        Make the post table.

        **This should not be called externally.** Subclasses
        may override or supplement this method to handle the
        table creation in a different way if desired.
        """
        self.otf["post"] = post = newTable("post")
        font = self.ufo
        post.formatType = 3.0
        # italic angle
        italicAngle = getAttrWithFallback(font.info, "italicAngle")
        post.italicAngle = italicAngle
        # underline
        underlinePosition = getAttrWithFallback(font.info, "postscriptUnderlinePosition")
        if underlinePosition is None:
            underlinePosition = 0
        post.underlinePosition = _roundInt(underlinePosition)
        underlineThickness = getAttrWithFallback(font.info, "postscriptUnderlineThickness")
        if underlineThickness is None:
            underlineThickness = 0
        post.underlineThickness = _roundInt(underlineThickness)
        # determine if the font has a fixed width
        widths = set([glyph.width for glyph in self.allGlyphs.values()])
        post.isFixedPitch = getAttrWithFallback(font.info, "postscriptIsFixedPitch")
        # misc
        post.minMemType42 = 0
        post.maxMemType42 = 0
        post.minMemType1 = 0
        post.maxMemType1 = 0
Exemple #31
0
def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):

    assert tolerance >= 0

    log.info("Generating gvar")
    assert "gvar" not in font
    gvar = font["gvar"] = newTable('gvar')
    gvar.version = 1
    gvar.reserved = 0
    gvar.variations = {}

    glyf = font['glyf']

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

        isComposite = glyf[glyph].isComposite()

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

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

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

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

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

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

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

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

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

                    if optimized_len < unoptimized_len:
                        var = var_opt

            gvar.variations[glyph].append(var)
Exemple #32
0
def _add_avar(font, axes):
    """
	Add 'avar' table to font.

	axes is an ordered dictionary of AxisDescriptor objects.
	"""

    assert axes
    assert isinstance(axes, OrderedDict)

    log.info("Generating avar")

    avar = newTable('avar')

    interesting = False
    for axis in axes.values():
        # Currently, some rasterizers require that the default value maps
        # (-1 to -1, 0 to 0, and 1 to 1) be present for all the segment
        # maps, even when the default normalization mapping for the axis
        # was not modified.
        # https://github.com/googlei18n/fontmake/issues/295
        # https://github.com/fonttools/fonttools/issues/1011
        # TODO(anthrotype) revert this (and 19c4b37) when issue is fixed
        curve = avar.segments[axis.tag] = {-1.0: -1.0, 0.0: 0.0, 1.0: 1.0}
        if not axis.map:
            continue

        items = sorted(axis.map)
        keys = [item[0] for item in items]
        vals = [item[1] for item in items]

        # Current avar requirements.  We don't have to enforce
        # these on the designer and can deduce some ourselves,
        # but for now just enforce them.
        assert axis.minimum == min(keys)
        assert axis.maximum == max(keys)
        assert axis.default in keys
        # No duplicates
        assert len(set(keys)) == len(keys)
        assert len(set(vals)) == len(vals)
        # Ascending values
        assert sorted(vals) == vals

        keys_triple = (axis.minimum, axis.default, axis.maximum)
        vals_triple = tuple(axis.map_forward(v) for v in keys_triple)

        keys = [models.normalizeValue(v, keys_triple) for v in keys]
        vals = [models.normalizeValue(v, vals_triple) for v in vals]

        if all(k == v for k, v in zip(keys, vals)):
            continue
        interesting = True

        curve.update(zip(keys, vals))

        assert 0.0 in curve and curve[0.0] == 0.0
        assert -1.0 not in curve or curve[-1.0] == -1.0
        assert +1.0 not in curve or curve[+1.0] == +1.0
        # curve.update({-1.0: -1.0, 0.0: 0.0, 1.0: 1.0})

    assert "avar" not in font
    if not interesting:
        log.info("No need for avar")
        avar = None
    else:
        font['avar'] = avar

    return avar
Exemple #33
0
def _merge_TTHinting(font, masterModel, master_ttfs):

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

    # Check that the existing hinting is compatible

    # fpgm and prep table

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

    # glyf table

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

    # cvt table

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

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

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

    variations = []
    deltas, supports = masterModel.getDeltasAndSupports(
        all_cvs, round=round
    )  # builtin round calls into Vector.__round__, which uses builtin round as we like
    for i, (delta, support) in enumerate(zip(deltas[1:], supports[1:])):
        if all(v == 0 for v in delta):
            continue
        var = TupleVariation(support, delta)
        variations.append(var)

    # We can build the cvar table now.
    if variations:
        cvar = font["cvar"] = newTable('cvar')
        cvar.version = 1
        cvar.variations = variations
Exemple #34
0
    def build_CFF(self):
        ctx = self.ctx
        data = self.metadataProvider
        self.otf["CFF "] = cff = ttLib.newTable("CFF ")
        cff = cff.cff

        cff.major = 1
        cff.minor = 0
        cff.hdrSize = 4
        cff.offSize = 4

        cff.fontNames = []
        strings = IndexedStrings()
        cff.strings = strings
        private = PrivateDict(strings=strings)
        private.rawDict.update(private.defaults)
        globalSubrs = GlobalSubrsIndex(private=private)
        topDict = TopDict(GlobalSubrs=globalSubrs, strings=strings)
        topDict.Private = private
        charStrings = topDict.CharStrings = CharStrings(
            file=None,
            charset=None,
            globalSubrs=globalSubrs,
            private=private,
            fdSelect=None,
            fdArray=None,
        )
        charStrings.charStringsAreIndexed = True
        topDict.charset = []
        charStringsIndex = charStrings.charStringsIndex = SubrsIndex(
            private=private, globalSubrs=globalSubrs)
        cff.topDictIndex = topDictIndex = TopDictIndex()
        topDictIndex.append(topDict)
        topDictIndex.strings = strings
        cff.GlobalSubrs = globalSubrs

        cff.fontNames.append(data.name_postscriptFontName(ctx))
        topDict = cff.topDictIndex[0]
        topDict.version = data.version(ctx)
        topDict.Notice = conversion.to_postscript_string(
            data.name_trademark(ctx), lambda result: ctx.log.append(
                semlog.warning_attr_truncated(attr="trademark", result=result))
        )
        topDict.Copyright = conversion.to_postscript_string(
            data.copyright(ctx), lambda result: ctx.log.append(
                semlog.warning_attr_truncated(attr="copyright", result=result))
        )
        topDict.FullName = data.name_postscriptFullName(ctx)
        topDict.FamilyName = data.CFF_postscriptFamilyName(ctx)
        topDict.Weight = data.CFF_postscriptWeightName(ctx)

        topDict.isFixedPitch = data.post_isFixedPitch(ctx)
        topDict.ItalicAngle = data.italicAngle(ctx)
        topDict.UnderlinePosition = otRound(data.post_underlinePosition(ctx))
        topDict.UnderlineThickness = otRound(data.post_underlineThickness(ctx))

        scale = 1.0 / otRound(data.unitsPerEm(ctx))
        topDict.FontMatrix = [scale, 0, 0, scale, 0, 0]

        defaultWidthX, nominalWidthX = FontProc.postscript_width_stats(
            ctx, self.otf)
        if defaultWidthX:
            private.rawDict["defaultWidthX"] = defaultWidthX
        if nominalWidthX:
            private.rawDict["nominalWidthX"] = nominalWidthX

        blueFuzz = otRound(data.CFF_postscriptBlueFuzz(ctx))
        blueShift = otRound(data.CFF_postscriptBlueShift(ctx))
        blueScale = data.CFF_postscriptBlueScale(ctx)
        forceBold = data.CFF_postscriptForceBold(ctx)
        blueValues = otRoundSequence(data.CFF_postscriptBlueValues(ctx))
        otherBlues = otRoundSequence(data.CFF_postscriptOtherBlues(ctx))
        familyBlues = otRoundSequence(data.CFF_postscriptFamilyBlues(ctx))
        familyOtherBlues = otRoundSequence(
            data.CFF_postscriptFamilyOtherBlues(ctx))
        stemSnapH = otRoundSequence(data.CFF_postscriptStemSnapH(ctx))
        stemSnapV = otRoundSequence(data.CFF_postscriptStemSnapV(ctx))
        # only write the blues data if some blues are defined.
        if any((blueValues, otherBlues, familyBlues, familyOtherBlues)):
            private.rawDict["BlueFuzz"] = blueFuzz
            private.rawDict["BlueShift"] = blueShift
            private.rawDict["BlueScale"] = blueScale
            private.rawDict["ForceBold"] = forceBold
            if blueValues:
                private.rawDict["BlueValues"] = blueValues
            if otherBlues:
                private.rawDict["OtherBlues"] = otherBlues
            if familyBlues:
                private.rawDict["FamilyBlues"] = familyBlues
            if familyOtherBlues:
                private.rawDict["FamilyOtherBlues"] = familyOtherBlues
        # only write the stems if both are defined.
        if stemSnapH and stemSnapV:
            private.rawDict["StemSnapH"] = stemSnapH
            private.rawDict["StdHW"] = stemSnapH[0]
            private.rawDict["StemSnapV"] = stemSnapV
            private.rawDict["StdVW"] = stemSnapV[0]
        # populate glyphs
        for glyphName in self.glyphOrder:
            glyph = self.glyphMap[glyphName]
            charString = self.draw_charstring(glyph, private, globalSubrs)

            # add to the font
            charStringsIndex.append(charString)
            glyphID = len(topDict.charset)
            charStrings.charStrings[glyphName] = glyphID
            topDict.charset.append(glyphName)
        bounds = self.fontBounds
        topDict.FontBBox = (bounds.left, bounds.bottom, bounds.right,
                            bounds.top)
 def setUp(self):
     vhea = newTable('vhea')
     self.font = TTFont(sfntVersion='OTTO')
     self.font['vhea'] = vhea
def nametable_from_filename(filepath):
    """Generate a new nametable based on a ttf and the GF Spec"""
    font = TTFont(filepath)
    old_table = font['name']
    new_table = newTable('name')
    filename = ntpath.basename(filepath)[:-4]

    family_name, style_name = filename.split('-')
    family_name = _split_camelcase(family_name)

    font_version = font['name'].getName(5, 3, 1, 1033)
    font_version = str(font_version).decode('utf_16_be')
    vendor_id = font['OS/2'].achVendID

    # SET MAC NAME FIELDS
    # -------------------
    # Copyright
    old_cp = old_table.getName(0, 3, 1, 1033).string.decode('utf_16_be')
    new_table.setName(old_cp.encode('mac_roman'), 0, 1, 0, 0)
    # Font Family Name
    new_table.setName(family_name.encode('mac_roman'), 1, 1, 0, 0)
    # Subfamily name
    mac_subfamily_name = _mac_subfamily_name(style_name).encode('mac_roman')
    new_table.setName(mac_subfamily_name, 2, 1, 0, 0)
    # Unique ID
    unique_id = _unique_id(_version(font_version), vendor_id, filename)
    mac_unique_id = unique_id.encode('mac_roman')
    new_table.setName(mac_unique_id, 3, 1, 0, 0)
    # Full name
    fullname = _full_name(family_name, style_name)
    mac_fullname = fullname.encode('mac_roman')
    new_table.setName(mac_fullname, 4, 1, 0, 0)
    # Version string
    old_v = old_table.getName(5, 3, 1, 1033).string.decode('utf_16_be')
    mac_old_v = old_v.encode('mac_roman')
    new_table.setName(mac_old_v, 5, 1, 0, 0)
    # Postscript name
    mac_ps_name = filename.encode('mac_roman')
    new_table.setName(mac_ps_name, 6, 1, 0, 0)

    # SET WIN NAME FIELDS
    # -------------------
    # Copyright
    new_table.setName(old_cp, 0, 3, 1, 1033)
    # Font Family Name
    win_family_name = _win_family_name(family_name, style_name)
    win_family_name = win_family_name.encode('utf_16_be')
    new_table.setName(win_family_name, 1, 3, 1, 1033)
    # Subfamily Name
    win_subfamily_name = _win_subfamily_name(style_name).encode('utf_16_be')
    new_table.setName(win_subfamily_name, 2, 3, 1, 1033)
    # Unique ID
    win_unique_id = unique_id.encode('utf_16_be')
    new_table.setName(win_unique_id, 3, 3, 1, 1033)
    # Full name
    win_fullname = fullname.encode('utf_16_be')
    new_table.setName(win_fullname, 4, 3, 1, 1033)
    # Version string
    win_old_v = old_v.encode('utf_16_be')
    new_table.setName(win_old_v, 5, 3, 1, 1033)
    # Postscript name
    win_ps_name = filename.encode('utf_16_be')
    new_table.setName(win_ps_name, 6, 3, 1, 1033)

    if style_name not in WIN_SAFE_STYLES:
        # Preferred Family Name
        new_table.setName(family_name.encode('utf_16_be'), 16, 3, 1, 1033)
        # Preferred SubfamilyName
        win_pref_subfam_name = _mac_subfamily_name(style_name).encode(
            'utf_16_be')
        new_table.setName(win_pref_subfam_name, 17, 3, 1, 1033)

    # PAD missing fields
    # ------------------
    for field in REQUIRED_FIELDS:
        text = None
        if new_table.getName(*field):
            pass  # Name has already been updated
        elif old_table.getName(*field):
            text = old_table.getName(*field).string
        elif old_table.getName(field[0], 3, 1, 1033):
            text = old_table.getName(field[0], 3, 1,
                                     1033).string.decode('utf_16_be')
        elif old_table.getName(field[0], 1, 0,
                               0):  # check if field exists for mac
            text = old_table.getName(field[0], 3, 1,
                                     1033).string.decode('mac_roman')

        if text:
            enc = 'utf_16_be' if field[0] == 3 else 'mac_roman'
            new_table.setName(text.encode(enc), *field)
    return new_table
 def test_compile_v1(self):
     cpal = newTable('CPAL')
     cpal.decompile(CPAL_DATA_V1, ttFont=None)
     self.assertEqual(cpal.compile(ttFont=None), CPAL_DATA_V1)
 def test_decompile_toXML_version_1_1(self):
     table = newTable('STAT')
     table.decompile(STAT_DATA_VERSION_1_1,
                     font=FakeFont(['.notdef']))
     self.assertEqual(getXML(table.toXML), STAT_XML_VERSION_1_1)
 def test_decompile_toXML_format3(self):
     table = newTable('STAT')
     table.decompile(STAT_DATA_AXIS_VALUE_FORMAT3,
                     font=FakeFont(['.notdef']))
     self.assertEqual(getXML(table.toXML), STAT_XML_AXIS_VALUE_FORMAT3)
 def test_decompile_toXML(self):
     table = newTable('STAT')
     table.decompile(STAT_DATA, font=FakeFont(['.notdef']))
     self.assertEqual(getXML(table.toXML), STAT_XML)
Exemple #41
0
def _add_fvar(font, axes, instances):
    """
	Add 'fvar' table to font.

	axes is an ordered dictionary of DesignspaceAxis objects.

	instances is list of dictionary objects with 'location', 'stylename',
	and possibly 'postscriptfontname' entries.
	"""

    assert axes
    assert isinstance(axes, OrderedDict)

    log.info("Generating fvar")

    fvar = newTable('fvar')
    nameTable = font['name']

    for a in axes.values():
        axis = Axis()
        axis.axisTag = Tag(a.tag)
        # TODO Skip axes that have no variation.
        axis.minValue, axis.defaultValue, axis.maxValue = a.minimum, a.default, a.maximum
        axis.axisNameID = nameTable.addMultilingualName(a.labelNames,
                                                        font,
                                                        minNameID=256)
        axis.flags = int(a.hidden)
        fvar.axes.append(axis)

    for instance in instances:
        coordinates = instance.location

        if "en" not in instance.localisedStyleName:
            if not instance.styleName:
                raise VarLibValidationError(
                    f"Instance at location '{coordinates}' must have a default English "
                    "style name ('stylename' attribute on the instance element or a "
                    "stylename element with an 'xml:lang=\"en\"' attribute).")
            localisedStyleName = dict(instance.localisedStyleName)
            localisedStyleName["en"] = tostr(instance.styleName)
        else:
            localisedStyleName = instance.localisedStyleName

        psname = instance.postScriptFontName

        inst = NamedInstance()
        inst.subfamilyNameID = nameTable.addMultilingualName(
            localisedStyleName)
        if psname is not None:
            psname = tostr(psname)
            inst.postscriptNameID = nameTable.addName(psname)
        inst.coordinates = {
            axes[k].tag: axes[k].map_backward(v)
            for k, v in coordinates.items()
        }
        #inst.coordinates = {axes[k].tag:v for k,v in coordinates.items()}
        fvar.instances.append(inst)

    assert "fvar" not in font
    font['fvar'] = fvar

    return fvar
Exemple #42
0
def _merge_TTHinting(font, masterModel, master_ttfs, tolerance=0.5):

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

    # Check that the existing hinting is compatible

    # fpgm and prep table

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

    # glyf table

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

    # cvt table

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

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

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

    # We can build the cvar table now.

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

    deltas, supports = masterModel.getDeltasAndSupports(all_cvs)
    for i, (delta, support) in enumerate(zip(deltas[1:], supports[1:])):
        delta = [otRound(d) for d in delta]
        if all(abs(v) <= tolerance for v in delta):
            continue
        var = TupleVariation(support, delta)
        cvar.variations.append(var)
Exemple #43
0
def _add_MVAR(font, masterModel, master_ttfs, axisTags):

    log.info("Generating MVAR")

    store_builder = varStore.OnlineVarStoreBuilder(axisTags)

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

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

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

        # TODO support gasp entries

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

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

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

        MVAR = font["MVAR"] = newTable('MVAR')
        mvar = MVAR.table = ot.MVAR()
        mvar.Version = 0x00010000
        mvar.Reserved = 0
        mvar.VarStore = store
        # XXX these should not be hard-coded but computed automatically
        mvar.ValueRecordSize = 8
        mvar.ValueRecordCount = len(records)
        mvar.ValueRecord = sorted(records, key=lambda r: r.ValueTag)
 def test_compile_fromXML_version_1_1(self):
     table = newTable('STAT')
     font = FakeFont(['.notdef'])
     for name, attrs, content in parseXML(STAT_XML_VERSION_1_1):
         table.fromXML(name, attrs, content, font=font)
     self.assertEqual(table.compile(font), STAT_DATA_VERSION_1_1)
Exemple #45
0
def colorize(font):
    COLR = newTable("COLR")
    CPAL = newTable("CPAL")
    glyf = font["glyf"]
    hmtx = font["hmtx"]

    CPAL.version = 0
    COLR.version = 0

    palette = list(GROUPS.values())
    palette.append(BLACK)
    CPAL.palettes = [palette]
    CPAL.numPaletteEntries = len(palette)

    COLR.ColorLayers = {}

    glyphOrder = list(font.getGlyphOrder())
    for name in glyphOrder:
        glyph = glyf[name]

        layers = []
        if glyph.isComposite() and len(glyph.components) > 1:
            componentColors = [
                getGlyphColor(c.getComponentInfo()[0])
                for c in glyph.components
            ]
            if any(componentColors):
                for component in glyph.components:
                    componentName, trans = component.getComponentInfo()
                    componentColor = getGlyphColor(componentName)
                    if trans == (1, 0, 0, 1, 0, 0):
                        if componentColor is None:
                            # broken in current versions of Firefox,
                            # see https://bugzilla.mozilla.org/show_bug.cgi?id=1283932
                            #layers.append(newLayer(componentName, 0xFFFF)) # broken if FF47
                            layers.append(
                                newLayer(componentName, palette.index(BLACK)))
                        else:
                            layers.append(
                                newLayer(componentName,
                                         palette.index(componentColor)))
                    else:
                        newName = "%s.%s" % (componentName, hash(trans))
                        if newName not in font.glyphOrder:
                            font.glyphOrder.append(newName)

                            newGlyph = getTableModule("glyf").Glyph()
                            newGlyph.numberOfContours = -1
                            newGlyph.components = [component]
                            glyf.glyphs[newName] = newGlyph
                            assert (len(glyf.glyphs) == len(
                                font.glyphOrder)), (name, newName)

                            width = hmtx[name][0]
                            lsb = hmtx[componentName][1] + trans[4]
                            hmtx.metrics[newName] = [width, lsb]

                        if componentColor is None:
                            # broken in current versions of Firefox,
                            # see https://bugzilla.mozilla.org/show_bug.cgi?id=1283932
                            #layers.append(newLayer(componentName, 0xFFFF)) # broken if FF47
                            layers.append(
                                newLayer(newName, palette.index(BLACK)))
                        else:
                            layers.append(
                                newLayer(newName,
                                         palette.index(componentColor)))

        if not layers:
            color = getGlyphColor(name)
            if color is not None:
                layers = [newLayer(name, palette.index(color))]

        if layers:
            COLR[name] = layers

    font["COLR"] = COLR
    font["CPAL"] = CPAL
Exemple #46
0
    def changeUPM(self, targetUPM):
        # Calculate scaling factor between old and new UPM
        upmOld = self.font["head"].unitsPerEm
        upmNew = int(targetUPM)
        isCubic = self.font.has_key("CFF ")
        if upmOld == upmNew:
            return
        elif upmNew < 16 or upmNew > 16384:
            print("WARNING: Invalid UPM value. --UPM is now ignored.",
                  file=sys.stderr)
            return
        elif isCubic:
            print(
                "WARNING: CFF-based font detected. Unfortunately it is currently not supported.",
                file=sys.stderr)
            return
        elif upmNew > 5000:
            print(
                "WARNING: UPM > 5000 will cause problems in Adobe InDesign and Illustrator.",
                file=sys.stderr)
        else:
            pass
        scaleFactor = upmNew / upmOld  # Get float because __future__.division has been imported

        # Conversion: re-scale all glyphs
        scaledGlyphs = {}
        glyphOrder = self.font.getGlyphOrder()
        glyphSet = self.font.getGlyphSet()
        for glyphName in glyphSet.keys():
            glyph = glyphSet[glyphName]
            if isCubic:  # TODO: `CFF `
                # basePen = OTGlyphPen(glyphSet)
                pass
            else:  # `glyf`
                basePen = TTGlyphPen(glyphSet)
            scalePen = TransformPen(basePen, Scale(scaleFactor, scaleFactor))
            # Deal with quad composites (all cubics are not affected)
            if not isCubic and glyph._glyph.isComposite(
            ):  # Scale each component's xy offset
                glyph.draw(basePen)
                for i in range(len(basePen.components)):
                    componentName, oldTrans = basePen.components[i]
                    newTrans = (oldTrans[0], oldTrans[1], oldTrans[2],
                                oldTrans[3], oldTrans[4] * scaleFactor,
                                oldTrans[5] * scaleFactor)
                    basePen.components[i] = (componentName, newTrans)
            else:  # Scale all cubics or base quads so that their composites will not be scaled multiple times
                glyph.draw(scalePen)
            # Glyph-specific hinting will be removed upon TTGlyphPen.glyph() call.
            scaledGlyphs[glyphName] = basePen.glyph()

        # Apply `glyf` table with scaled glyphs
        glyf = newTable("glyf")
        glyf.glyphOrder = glyphOrder
        glyf.glyphs = scaledGlyphs
        self.font["glyf"] = glyf

        # Update tables to apply the new UPM
        self.__applyNewUPM(upmOld, upmNew)

        # Recalculate `head`, `glyf`, `maxp` upon compile
        self.font.recalcBBoxes = True
        return
Exemple #47
0
    def build_OS2(self):
        ctx = self.ctx
        data = self.metadataProvider
        self.otf["OS/2"] = os2 = ttLib.newTable("OS/2")
        os2.version = 0x0004
        os2.xAvgCharWidth = FontProc.average_char_width(ctx, self.otf)

        os2.usWeightClass = data.OS2_weightClass(ctx)
        os2.usWidthClass = data.OS2_widthClass(ctx)

        os2.fsType = data.OS2_fsType(ctx)

        os2.ySubscriptXSize = otRound(data.OS2_subscriptXSize(ctx))
        os2.ySubscriptYSize = otRound(data.OS2_subscriptYSize(ctx))
        os2.ySubscriptXOffset = otRound(data.OS2_subscriptXOffset(ctx))
        os2.ySubscriptYOffset = otRound(data.OS2_subscriptYOffset(ctx))

        os2.ySuperscriptXSize = otRound(data.OS2_superscriptXSize(ctx))
        os2.ySuperscriptYSize = otRound(data.OS2_superscriptYSize(ctx))
        os2.ySuperscriptXOffset = otRound(data.OS2_superscriptXOffset(ctx))
        os2.ySuperscriptYOffset = otRound(data.OS2_superscriptYOffset(ctx))

        os2.yStrikeoutSize = otRound(data.OS2_strikeoutSize(ctx))
        os2.yStrikeoutPosition = otRound(data.OS2_strikeoutPosition(ctx))

        os2.sFamilyClass = data.OS2_familyClass(ctx)

        panose = Panose()
        panose.bFamilyType, \
        panose.bSerifStyle, \
        panose.bWeight, \
        panose.bProportion, \
        panose.bContrast, \
        panose.bStrokeVariation, \
        panose.bArmStyle, \
        panose.bLetterForm, \
        panose.bMidline, \
        panose.bXHeight = data.OS2_panose(ctx)
        os2.panose = panose

        unicodeRanges = data.OS2_unicodeRanges(ctx)
        if unicodeRanges is not None:
            os2.ulUnicodeRange1 = conversion.to_bitflags(unicodeRanges, 0, 32)
            os2.ulUnicodeRange2 = conversion.to_bitflags(unicodeRanges, 32, 32)
            os2.ulUnicodeRange3 = conversion.to_bitflags(unicodeRanges, 64, 32)
            os2.ulUnicodeRange4 = conversion.to_bitflags(unicodeRanges, 96, 32)
        else:
            os2.recalcUnicodeRanges(self.otf)

        codepageRanges = data.OS2_codepageRanges(ctx)
        if codepageRanges is None:
            codepageRanges = FontProc.codepage_ranges(
                ctx, self.unicodeToGlyphNameMap.keys())
        os2.ulCodePageRange1 = conversion.to_bitflags(codepageRanges, 0, 32)
        os2.ulCodePageRange2 = conversion.to_bitflags(codepageRanges, 32, 32)

        os2.achVendID = data.OS2_vendorID(ctx)

        os2.sxHeight = otRound(data.xHeight(ctx))
        os2.sCapHeight = otRound(data.capHeight(ctx))
        os2.sTypoAscender, \
        os2.sTypoDescender, \
        os2.sTypoLineGap = otRoundSequence(data.OS2_typoMetrics(ctx))
        os2.usWinAscent, \
        os2.usWinDescent = otRoundSequence(data.OS2_winMetrics(ctx, self.fontBounds))

        os2.fsSelection = data.OS2_fsSelection(ctx)

        os2.fsFirstCharIndex, \
        os2.fsLastCharIndex = FontProc.minmax_cids(ctx, self.unicodeToGlyphNameMap.keys())
        os2.usBreakChar = 32
        os2.usDefaultChar = 0
        # maximum contextual lookup length
        os2.usMaxContext = 0
Exemple #48
0
import os
import sys
from fontTools.ttLib import TTFont, newTable
print 'inserting STAT table from external file...'
arguments = sys.argv[1:]
path = arguments[0]
f = TTFont(path)
base = os.path.split(os.path.split(__file__)[0])[0]
pathToXML = os.path.join(base, 'sources/2-build/STAT.ttx')
f['STAT'] = newTable('STAT')
f.importXML(pathToXML)
os.remove(path)
f.save(path)
Exemple #49
0
 def build_maxp(self):
     self.otf["maxp"] = maxp = ttLib.newTable("maxp")
     maxp.tableVersion = 0x00005000
     maxp.numGlyphs = len(self.glyphOrder)
Exemple #50
0
def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
    if tolerance < 0:
        raise ValueError("`tolerance` must be a positive number.")

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

    master_datas = [
        _MasterData(m['glyf'], m['hmtx'].metrics,
                    getattr(m.get('vmtx'), 'metrics', None))
        for m in master_ttfs
    ]

    for glyph in font.getGlyphOrder():

        isComposite = glyf[glyph].isComposite()

        allData = [
            m.glyf._getCoordinatesAndControls(glyph, m.hMetrics, m.vMetrics)
            for m in master_datas
        ]

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

        model, allData = masterModel.getSubModel(allData)

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

        # Update gvar
        gvar.variations[glyph] = []
        deltas = model.getDeltas(allCoords,
                                 round=partial(GlyphCoordinates.__round__,
                                               round=round))
        supports = model.supports
        assert len(deltas) == len(supports)

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

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

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

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

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

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

                    if optimized_len < unoptimized_len:
                        var = var_opt

            gvar.variations[glyph].append(var)
Exemple #51
0
 def test_decompile_toXML_format1(self):
     table = newTable('opbd')
     table.decompile(OPBD_FORMAT_1_DATA, self.font)
     self.assertEqual(getXML(table.toXML), OPBD_FORMAT_1_XML)
Exemple #52
0
def makeLookup1():
    # make a variation of the shell TTX data
    f = open(shellSourcePath)
    ttxData = f.read()
    f.close()
    ttxData = ttxData.replace("__familyName__", "gsubtest-lookup1")
    tempShellSourcePath = shellSourcePath + ".temp"
    f = open(tempShellSourcePath, "wb")
    f.write(ttxData)
    f.close()

    # compile the shell
    shell = TTFont(sfntVersion="OTTO")
    shell.importXML(tempShellSourcePath)
    shell.save(shellTempPath)
    os.remove(tempShellSourcePath)

    # load the shell
    shell = TTFont(shellTempPath)

    # grab the PASS and FAIL data
    hmtx = shell["hmtx"]
    glyphSet = shell.getGlyphSet()

    failGlyph = glyphSet["F"]
    failGlyph.decompile()
    failGlyphProgram = list(failGlyph.program)
    failGlyphMetrics = hmtx["F"]

    passGlyph = glyphSet["P"]
    passGlyph.decompile()
    passGlyphProgram = list(passGlyph.program)
    passGlyphMetrics = hmtx["P"]

    # grab some tables
    hmtx = shell["hmtx"]
    cmap = shell["cmap"]

    # start the glyph order
    existingGlyphs = [".notdef", "space", "F", "P"]
    glyphOrder = list(existingGlyphs)

    # start the CFF
    cff = shell["CFF "].cff
    globalSubrs = cff.GlobalSubrs
    topDict = cff.topDictIndex[0]
    topDict.charset = existingGlyphs
    private = topDict.Private
    charStrings = topDict.CharStrings
    charStringsIndex = charStrings.charStringsIndex

    features = sorted(mapping)

    # build the outline, hmtx and cmap data
    cp = baseCodepoint
    for index, tag in enumerate(features):

        # tag.pass
        glyphName = "%s.pass" % tag
        glyphOrder.append(glyphName)
        addGlyphToCFF(glyphName=glyphName,
                      program=passGlyphProgram,
                      private=private,
                      globalSubrs=globalSubrs,
                      charStringsIndex=charStringsIndex,
                      topDict=topDict,
                      charStrings=charStrings)
        hmtx[glyphName] = passGlyphMetrics

        for table in cmap.tables:
            if table.format == 4:
                table.cmap[cp] = glyphName
            else:
                raise NotImplementedError, "Unsupported cmap table format: %d" % table.format
        cp += 1

        # tag.fail
        glyphName = "%s.fail" % tag
        glyphOrder.append(glyphName)
        addGlyphToCFF(glyphName=glyphName,
                      program=failGlyphProgram,
                      private=private,
                      globalSubrs=globalSubrs,
                      charStringsIndex=charStringsIndex,
                      topDict=topDict,
                      charStrings=charStrings)
        hmtx[glyphName] = failGlyphMetrics

        for table in cmap.tables:
            if table.format == 4:
                table.cmap[cp] = glyphName
            else:
                raise NotImplementedError, "Unsupported cmap table format: %d" % table.format

    # bump this up so that the sequence is the same as the lookup 3 font
        cp += 3

    # set the glyph order
    shell.setGlyphOrder(glyphOrder)

    # start the GSUB
    shell["GSUB"] = newTable("GSUB")
    gsub = shell["GSUB"].table = GSUB()
    gsub.Version = 1.0

    # make a list of all the features we will make
    featureCount = len(features)

    # set up the script list
    scriptList = gsub.ScriptList = ScriptList()
    scriptList.ScriptCount = 1
    scriptList.ScriptRecord = []
    scriptRecord = ScriptRecord()
    scriptList.ScriptRecord.append(scriptRecord)
    scriptRecord.ScriptTag = "DFLT"
    script = scriptRecord.Script = Script()
    defaultLangSys = script.DefaultLangSys = DefaultLangSys()
    defaultLangSys.FeatureCount = featureCount
    defaultLangSys.FeatureIndex = range(defaultLangSys.FeatureCount)
    defaultLangSys.ReqFeatureIndex = 65535
    defaultLangSys.LookupOrder = None
    script.LangSysCount = 0
    script.LangSysRecord = []

    # set up the feature list
    featureList = gsub.FeatureList = FeatureList()
    featureList.FeatureCount = featureCount
    featureList.FeatureRecord = []
    for index, tag in enumerate(features):
        # feature record
        featureRecord = FeatureRecord()
        featureRecord.FeatureTag = tag
        feature = featureRecord.Feature = Feature()
        featureList.FeatureRecord.append(featureRecord)
        # feature
        feature.FeatureParams = None
        feature.LookupCount = 1
        feature.LookupListIndex = [index]

    # write the lookups
    lookupList = gsub.LookupList = LookupList()
    lookupList.LookupCount = featureCount
    lookupList.Lookup = []
    for tag in features:
        # lookup
        lookup = Lookup()
        lookup.LookupType = 1
        lookup.LookupFlag = 0
        lookup.SubTableCount = 1
        lookup.SubTable = []
        lookupList.Lookup.append(lookup)
        # subtable
        subtable = SingleSubst()
        subtable.Format = 2
        subtable.LookupType = 1
        subtable.mapping = {
            "%s.pass" % tag: "%s.fail" % tag,
            "%s.fail" % tag: "%s.pass" % tag,
        }
        lookup.SubTable.append(subtable)

    path = outputPath % 1 + ".otf"
    if os.path.exists(path):
        os.remove(path)
    shell.save(path)

    # get rid of the shell
    if os.path.exists(shellTempPath):
        os.remove(shellTempPath)
Exemple #53
0
 def test_compile_fromXML(self):
     table = newTable('morx')
     for name, attrs, content in parseXML(MORX_NONCONTEXTUAL_XML):
         table.fromXML(name, attrs, content, font=self.font)
     self.assertEqual(hexStr(table.compile(self.font)),
                      hexStr(MORX_NONCONTEXTUAL_DATA))
Exemple #54
0
 def test_compile_fromXML_format1(self):
     table = newTable('opbd')
     for name, attrs, content in parseXML(OPBD_FORMAT_1_XML):
         table.fromXML(name, attrs, content, font=self.font)
     self.assertEqual(hexStr(table.compile(self.font)),
                      hexStr(OPBD_FORMAT_1_DATA))
Exemple #55
0
 def test_decompile_AppleChancery(self):
     # Make sure we do not crash when decompiling the 'opbd' table of
     # AppleChancery.ttf. https://github.com/fonttools/fonttools/issues/1031
     table = newTable('opbd')
     table.decompile(OPBD_APPLE_CHANCERY_DATA, self.font)
     self.assertIn('<OpticalBounds Format="0">', getXML(table.toXML))
Exemple #56
0
def processFont(path, d, flatKernData):
    font = TTFont(path)
    oldD, f = os.path.split(path)
    new = os.path.join(d, f)

    name = font['name'].getName(6, 1, 0, 0).string

    # Make a flat kerning table
    if flatKernData is not None:
        newKern = ttLib.newTable("kern")
        newKern.version = 0
        newKern.kernTables = [KernTable_format_0()]

        flat = flatKernData[name]
        sortedKeys = flat.keys()
        sortedKeys.sort()

        left = ''
        i = 0
        for key in sortedKeys:
            if left == '':
                left = key[0]
                kern_table = {key: flat[key]}
                count = _flat_kern_count(left, sortedKeys)
                i += 1
            elif i + 1 < len(sortedKeys) and left != sortedKeys[i + 1][0]:
                if count + _flat_kern_count(sortedKeys[i + 1][0],
                                            sortedKeys) > 10920:
                    kern_table[key] = flat[key]

                    t = len(newKern.kernTables) - 1

                    table = newKern.kernTables[t]
                    table.kernTable = kern_table
                    table.version = 0
                    table.coverage = 1
                    table.apple = False
                    newKern.kernTables.append(KernTable_format_0())
                    left = ''
                else:
                    kern_table[key] = flat[key]
                    left = sortedKeys[i + 1][0]
                    count = _flat_kern_count(sortedKeys[i + 1][0],
                                             sortedKeys) + count
                i += 1
            elif len(sortedKeys) == i + 1:
                kern_table[key] = flat[key]
                t = len(newKern.kernTables) - 1
                table = newKern.kernTables[t]
                table.kernTable = kern_table
                table.version = 0
                table.coverage = 1
                table.apple = False
                i += 1
            else:
                kern_table[key] = flat[key]
                i += 1

        # Add this table back to the font
        font.tables["kern"] = newKern

    # Fix the ugly id string
    versionClean(font)
    nameTableTweak(font)
    makeDSIG(font)

    font.save(new)
 def test_compile_fromXML_withAxisJunk(self):
     table = newTable('STAT')
     font = FakeFont(['.notdef'])
     for name, attrs, content in parseXML(STAT_XML_WITH_AXIS_JUNK):
         table.fromXML(name, attrs, content, font=font)
     self.assertEqual(table.compile(font), STAT_DATA_WITH_AXIS_JUNK)
 def test_compile_v1_noLabelsNoTypes(self):
     cpal = newTable('CPAL')
     cpal.decompile(CPAL_DATA_V1_NOLABELS_NOTYPES, ttFont=None)
     self.assertEqual(cpal.compile(ttFont=None),
                      CPAL_DATA_V1_NOLABELS_NOTYPES)
 def test_decompile_toXML_withAxisJunk(self):
     table = newTable('STAT')
     table.decompile(STAT_DATA_WITH_AXIS_JUNK, font=FakeFont(['.notdef']))
     self.assertEqual(getXML(table.toXML), STAT_XML_WITH_AXIS_JUNK)
 def test_compile_fromXML_format3(self):
     table = newTable('STAT')
     font = FakeFont(['.notdef'])
     for name, attrs, content in parseXML(STAT_XML_AXIS_VALUE_FORMAT3):
         table.fromXML(name, attrs, content, font=font)
     self.assertEqual(table.compile(font), STAT_DATA_AXIS_VALUE_FORMAT3)