def test_build_ttf(tmpdir): outPath = os.path.join(str(tmpdir), "test.ttf") fb, advanceWidths, nameStrings = _setupFontBuilder(True) pen = TTGlyphPen(None) drawTestGlyph(pen) glyph = pen.glyph() glyphs = {".notdef": glyph, "A": glyph, "a": glyph, ".null": glyph} fb.setupGlyf(glyphs) metrics = {} glyphTable = fb.font["glyf"] for gn, advanceWidth in advanceWidths.items(): metrics[gn] = (advanceWidth, glyphTable[gn].xMin) fb.setupHorizontalMetrics(metrics) fb.setupHorizontalHeader(ascent=824, descent=200) fb.setupNameTable(nameStrings) fb.setupOS2() fb.setupPost() fb.setupDummyDSIG() fb.save(outPath) _verifyOutput(outPath)
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)) # the width is needed for autoUseMyMetrics method below newGlyph.width = glyph.width 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: glyph = allGlyphs[name] pen = TTGlyphPen(allGlyphs) glyph.draw(pen) ttGlyph = pen.glyph() if ttGlyph.isComposite() and self.autoUseMyMetrics: self.autoUseMyMetrics(ttGlyph, glyph.width, allGlyphs) glyf[name] = ttGlyph
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)
def test_build_ttf(tmpdir): outPath = os.path.join(str(tmpdir), "test.ttf") fb, advanceWidths, nameStrings = _setupFontBuilder(True) pen = TTGlyphPen(None) drawTestGlyph(pen) glyph = pen.glyph() glyphs = {".notdef": glyph, "A": glyph, "a": glyph, ".null": glyph} fb.setupGlyf(glyphs) metrics = {} glyphTable = fb.font["glyf"] for gn, advanceWidth in advanceWidths.items(): metrics[gn] = (advanceWidth, glyphTable[gn].xMin) fb.setupHorizontalMetrics(metrics) fb.setupHorizontalHeader(ascent=824, descent=200) fb.setupNameTable(nameStrings) fb.setupOS2() fb.setupPost() fb.setupDummyDSIG() fb.save(outPath) f = TTFont(outPath) f.saveXML(outPath + ".ttx") with open(outPath + ".ttx") as f: testData = strip_VariableItems(f.read()) refData = strip_VariableItems(getTestData("test.ttf.ttx")) assert refData == testData
def test_closePath_ignoresAnchors(self): pen = TTGlyphPen(None) pen.moveTo((0, 0)) pen.closePath() self.assertFalse(pen.points) self.assertFalse(pen.types) self.assertFalse(pen.endPts)
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 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 test_remove_extra_move_points(self): pen = TTGlyphPen(None) pen.moveTo((0, 0)) pen.lineTo((100, 0)) pen.qCurveTo((100, 50), (50, 100), (0, 0)) pen.closePath() assert len(pen.points) == 4 assert pen.points[0] == (0, 0)
def test_remove_extra_move_points(self): pen = TTGlyphPen(None) pen.moveTo((0, 0)) pen.lineTo((100, 0)) pen.qCurveTo((100, 50), (50, 100), (0, 0)) pen.closePath() self.assertEqual(len(pen.points), 4) self.assertEqual(pen.points[0], (0, 0))
def glyphs_to_quadratic(glyphs, max_err, **kwargs): quadGlyphs = {} for gname in glyphs.keys(): glyph = glyphs[gname] ttPen = TTGlyphPen(glyphs) cu2quPen = Cu2QuPen(ttPen, max_err, **kwargs) glyph.draw(cu2quPen) quadGlyphs[gname] = ttPen.glyph() return quadGlyphs
def test_keep_move_point(self): pen = TTGlyphPen(None) pen.moveTo((0, 0)) pen.lineTo((100, 0)) pen.qCurveTo((100, 50), (50, 100), (30, 30)) # when last and move pts are different, closePath() implies a lineTo pen.closePath() assert len(pen.points) == 5 assert pen.points[0] == (0, 0)
def ttfGlyphFromSkPath(path: pathops.Path) -> _g_l_y_f.Glyph: # Skia paths have no 'components', no need for glyphSet ttPen = TTGlyphPen(glyphSet=None) path.draw(ttPen) glyph = ttPen.glyph() assert not glyph.isComposite() # compute glyph.xMin (glyfTable parameter unused for non composites) glyph.recalcBounds(glyfTable=None) return glyph
def skia_path_to_ttfont_glyph(skia_path: pathops.Path) -> _g_l_y_f.Glyph: """ Converts a pathops.Path object to a fontTools.ttLib._g_l_y_f.Glyph object """ tt_pen = TTGlyphPen(glyphSet=None) skia_path.draw(tt_pen) glyph = tt_pen.glyph() glyph.recalcBounds(glyfTable=None) return glyph
def glyphs_to_quadratic( glyphs, max_err=MAX_ERR, reverse_direction=REVERSE_DIRECTION): quadGlyphs = {} for gname in glyphs.keys(): glyph = glyphs[gname] ttPen = TTGlyphPen(glyphs) cu2quPen = Cu2QuPen(ttPen, max_err, reverse_direction=reverse_direction) glyph.draw(cu2quPen) quadGlyphs[gname] = ttPen.glyph() return quadGlyphs
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 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()
def test_keep_move_point(self): pen = TTGlyphPen(None) pen.moveTo((0, 0)) pen.lineTo((100, 0)) pen.qCurveTo((100, 50), (50, 100), (30, 30)) # when last and move pts are different, closePath() implies a lineTo pen.closePath() self.assertEqual(len(pen.points), 5) self.assertEqual(pen.points[0], (0, 0))
def __init__(self, path=None, lazy=False, size=1500, ft_load_glyph_flags=FTHintMode.UNHINTED): self.path = path self.ttfont = TTFont(self.path) has_outlines = self.ttfont.has_key("glyf") or self.ttfont.has_key( "CFF ") if not has_outlines: # Create faux empty glyf table with empty glyphs to make # it a valid font, e.g. for old-style CBDT/CBLC fonts logger.warning( "No outlines present, treating {} as bitmap font".format( self.path)) self.ttfont["glyf"] = newTable("glyf") self.ttfont["glyf"].glyphs = {} pen = TTGlyphPen({}) for name in self.ttfont.getGlyphOrder(): self.ttfont["glyf"].glyphs[name] = pen.glyph() self._src_ttfont = TTFont(self.path) self.glyphset = None self.recalc_glyphset() self.axis_order = None self.instance_coordinates = self._get_dflt_instance_coordinates() self.instances_coordinates = self._get_instances_coordinates() self.glyphs = self.marks = self.mkmks = self.kerns = \ self.glyph_metrics = self.names = self.attribs = None self.ftfont = freetype.Face(self.path) self.ftslot = self.ftfont.glyph self.ft_load_glyph_flags = ft_load_glyph_flags self.size = size if self.ftfont.is_scalable: self.ftfont.set_char_size(self.size) with open(self.path, 'rb') as fontfile: self._fontdata = fontfile.read() self.hbface = hb.Face.create(self._fontdata) self.hbfont = hb.Font.create(self.hbface) self.hbfont.scale = (self.size, self.size) if not lazy: self.recalc_tables()
def test_recursiveComponent(self): glyphSet = {} pen_dummy = TTGlyphPen(glyphSet) glyph_dummy = pen_dummy.glyph() glyphSet["A"] = glyph_dummy glyphSet["B"] = glyph_dummy pen_A = TTGlyphPen(glyphSet) pen_A.addComponent("B", (1, 0, 0, 1, 0, 0)) pen_B = TTGlyphPen(glyphSet) pen_B.addComponent("A", (1, 0, 0, 1, 0, 0)) glyph_A = pen_A.glyph() glyph_B = pen_B.glyph() glyphSet["A"] = glyph_A glyphSet["B"] = glyph_B with self.assertRaisesRegex(TTLibError, "glyph '.' contains a recursive component reference"): glyph_A.getCoordinates(glyphSet)
def process_glyf(self) -> None: """ Processes glyf table """ if self.ttf_components: glyf = self.font["glyf"] gs = self.font.getGlyphSet() for glyph_name in self.font.glyphOrder: if glyph_name in self.keep_g_names: continue pen = TTGlyphPen(gs) pen.addComponent(self.replacer, (1, 0, 0, 1, 0, 0)) glyf[glyph_name] = pen.glyph() else: self._process_base(self.font["glyf"]) return None
def glyphs_to_quadratic(glyphs, max_err=MAX_ERR, reverse_direction=REVERSE_DIRECTION): try: from cu2qu.pens import Cu2QuPen from fontTools.pens.ttGlyphPen import TTGlyphPen except ImportError: raise ValueError("cannot convert glyphs due to missing libs") quadGlyphs = {} for gname in glyphs.keys(): glyph = glyphs[gname] ttPen = TTGlyphPen(glyphs) cu2quPen = Cu2QuPen(ttPen, max_err, reverse_direction=reverse_direction) glyph.draw(cu2quPen) quadGlyphs[gname] = ttPen.glyph() return quadGlyphs
def decomposeAndRemoveOverlap(font, glyphName): glyfTable = font["glyf"] glyphSet = font.getGlyphSet() # record TTGlyph outlines without components dcPen = DecomposingRecordingPen(glyphSet) glyphSet[glyphName].draw(dcPen) # replay recording onto a skia-pathops Path path = pathops.Path() pathPen = path.getPen() dcPen.replay(pathPen) # remove overlaps path.simplify() # create new TTGlyph from Path ttPen = TTGlyphPen(None) path.draw(ttPen) glyfTable[glyphName] = ttPen.glyph()
def test_closePath_ignoresAnchors(self): pen = TTGlyphPen(None) pen.moveTo((0, 0)) pen.closePath() assert not pen.points assert not pen.types assert not pen.endPts
def runEndToEnd(self, filename): font = ttLib.TTFont() ttx_path = os.path.join( os.path.abspath(os.path.dirname(os.path.realpath(__file__))), '..', 'ttLib', 'data', filename) font.importXML(ttx_path) glyphSet = font.getGlyphSet() glyfTable = font['glyf'] pen = TTGlyphPen(font.getGlyphSet()) for name in font.getGlyphOrder(): oldGlyph = glyphSet[name] oldGlyph.draw(pen) oldGlyph = oldGlyph._glyph newGlyph = pen.glyph() if hasattr(oldGlyph, 'program'): newGlyph.program = oldGlyph.program self.assertEqual( oldGlyph.compile(glyfTable), newGlyph.compile(glyfTable))
def test_unicodeVariationSequences(tmpdir): familyName = "UVSTestFont" styleName = "Regular" nameStrings = dict(familyName=familyName, styleName=styleName) nameStrings['psName'] = familyName + "-" + styleName glyphOrder = [".notdef", "space", "zero", "zero.slash"] cmap = {ord(" "): "space", ord("0"): "zero"} uvs = [ (0x0030, 0xFE00, "zero.slash"), (0x0030, 0xFE01, None), # not an official sequence, just testing ] metrics = {gn: (600, 0) for gn in glyphOrder} pen = TTGlyphPen(None) glyph = pen.glyph() # empty placeholder glyphs = {gn: glyph for gn in glyphOrder} fb = FontBuilder(1024, isTTF=True) fb.setupGlyphOrder(glyphOrder) fb.setupCharacterMap(cmap, uvs) fb.setupGlyf(glyphs) fb.setupHorizontalMetrics(metrics) fb.setupHorizontalHeader(ascent=824, descent=200) fb.setupNameTable(nameStrings) fb.setupOS2() fb.setupPost() outPath = os.path.join(str(tmpdir), "test_uvs.ttf") fb.save(outPath) _verifyOutput(outPath, tables=["cmap"]) uvs = [ (0x0030, 0xFE00, "zero.slash"), ( 0x0030, 0xFE01, "zero" ), # should result in the exact same subtable data, due to cmap[0x0030] == "zero" ] fb.setupCharacterMap(cmap, uvs) fb.save(outPath) _verifyOutput(outPath, tables=["cmap"])
def runEndToEnd(self, filename): font = ttLib.TTFont() ttx_path = os.path.join( os.path.abspath(os.path.dirname(os.path.realpath(__file__))), '..', 'ttLib', 'testdata', filename) font.importXML(ttx_path, quiet=True) glyphSet = font.getGlyphSet() glyfTable = font['glyf'] pen = TTGlyphPen(font.getGlyphSet()) for name in font.getGlyphOrder(): oldGlyph = glyphSet[name] oldGlyph.draw(pen) oldGlyph = oldGlyph._glyph newGlyph = pen.glyph() if hasattr(oldGlyph, 'program'): newGlyph.program = oldGlyph.program self.assertEqual(oldGlyph.compile(glyfTable), newGlyph.compile(glyfTable))
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 allGlyphs = self.allGlyphs for name in self.glyphOrder: glyph = allGlyphs[name] pen = TTGlyphPen(allGlyphs) try: glyph.draw(pen) except NotImplementedError: logger.error("%r has invalid curve format; skipped", name) ttGlyph = Glyph() else: ttGlyph = pen.glyph() if ttGlyph.isComposite() and self.autoUseMyMetrics: self.autoUseMyMetrics(ttGlyph, glyph.width, allGlyphs) glyf[name] = ttGlyph
def test_unicodeVariationSequences(tmpdir): familyName = "UVSTestFont" styleName = "Regular" nameStrings = dict(familyName=familyName, styleName=styleName) nameStrings['psName'] = familyName + "-" + styleName glyphOrder = [".notdef", "space", "zero", "zero.slash"] cmap = {ord(" "): "space", ord("0"): "zero"} uvs = [ (0x0030, 0xFE00, "zero.slash"), (0x0030, 0xFE01, None), # not an official sequence, just testing ] metrics = {gn: (600, 0) for gn in glyphOrder} pen = TTGlyphPen(None) glyph = pen.glyph() # empty placeholder glyphs = {gn: glyph for gn in glyphOrder} fb = FontBuilder(1024, isTTF=True) fb.setupGlyphOrder(glyphOrder) fb.setupCharacterMap(cmap, uvs) fb.setupGlyf(glyphs) fb.setupHorizontalMetrics(metrics) fb.setupHorizontalHeader(ascent=824, descent=200) fb.setupNameTable(nameStrings) fb.setupOS2() fb.setupPost() outPath = os.path.join(str(tmpdir), "test_uvs.ttf") fb.save(outPath) _verifyOutput(outPath, tables=["cmap"]) uvs = [ (0x0030, 0xFE00, "zero.slash"), (0x0030, 0xFE01, "zero"), # should result in the exact same subtable data, due to cmap[0x0030] == "zero" ] fb.setupCharacterMap(cmap, uvs) fb.save(outPath) _verifyOutput(outPath, tables=["cmap"])
def process_font(input_path, output_path=None, verbose=False): """ De-componentize a single font at input_path, saving to output_path (or input_path if None) """ font = TTFont(input_path) output_path = output_path or input_path gs = font.getGlyphSet() drpen = DecomposingRecordingPen(gs) ttgpen = TTGlyphPen(gs) count = 0 for glyphname in font.glyphOrder: glyph = font["glyf"][glyphname] if not glyph.isComposite(): continue if verbose: print(f" decomposing '{glyphname}'") # reset the pens drpen.value = [] ttgpen.init() # draw the composite glyph into the decomposing pen glyph.draw(drpen, font["glyf"]) # replay the recorded decomposed glyph into the TTGlyphPen drpen.replay(ttgpen) # store the decomposed glyph in the 'glyf' table font["glyf"][glyphname] = ttgpen.glyph() count += 1 font.save(output_path) return count
def test_no_handle_overflowing_transform(self): componentName = 'a' glyphSet = {} pen = TTGlyphPen(glyphSet, handleOverflowingTransforms=False) pen.moveTo((0, 0)) pen.lineTo((0, 1)) pen.lineTo((1, 0)) pen.closePath() baseGlyph = pen.glyph() glyphSet[componentName] = _TestGlyph(baseGlyph) pen.addComponent(componentName, (3, 0, 0, 1, 0, 0)) compositeGlyph = pen.glyph() self.assertEqual(compositeGlyph.components[0].transform, ((3, 0), (0, 1))) with self.assertRaises(struct.error): compositeGlyph.compile({'a': baseGlyph})
def draw(self, ppp: float): pen = TTGlyphPen(None) for y, row in enumerate(reversed(self.bits)): for x, col in enumerate(row): if col: pen.moveTo( ((x + self.bbx.x) * ppp, (y + self.bbx.y) * ppp)) pen.lineTo( ((x + self.bbx.x + 1) * ppp, (y + self.bbx.y) * ppp)) pen.lineTo(( (x + self.bbx.x + 1) * ppp, (y + self.bbx.y + 1) * ppp, )) pen.lineTo( ((x + self.bbx.x) * ppp, (y + self.bbx.y + 1) * ppp)) pen.lineTo( ((x + self.bbx.x) * ppp, (y + self.bbx.y) * ppp)) pen.closePath() return pen.glyph()
def test_build_var(tmpdir): outPath = os.path.join(str(tmpdir), "test_var.ttf") fb = FontBuilder(1024, isTTF=True) fb.setupGlyphOrder([".notdef", ".null", "A", "a"]) fb.setupCharacterMap({65: "A", 97: "a"}) advanceWidths = {".notdef": 600, "A": 600, "a": 600, ".null": 600} familyName = "HelloTestFont" styleName = "TotallyNormal" nameStrings = dict(familyName=dict(en="HelloTestFont", nl="HalloTestFont"), styleName=dict(en="TotallyNormal", nl="TotaalNormaal")) nameStrings['psName'] = familyName + "-" + styleName pen = TTGlyphPen(None) pen.moveTo((100, 0)) pen.lineTo((100, 400)) pen.lineTo((500, 400)) pen.lineTo((500, 000)) pen.closePath() glyph = pen.glyph() pen = TTGlyphPen(None) emptyGlyph = pen.glyph() glyphs = { ".notdef": emptyGlyph, "A": glyph, "a": glyph, ".null": emptyGlyph } fb.setupGlyf(glyphs) metrics = {} glyphTable = fb.font["glyf"] for gn, advanceWidth in advanceWidths.items(): metrics[gn] = (advanceWidth, glyphTable[gn].xMin) fb.setupHorizontalMetrics(metrics) fb.setupHorizontalHeader(ascent=824, descent=200) fb.setupNameTable(nameStrings) axes = [ ('LEFT', 0, 0, 100, "Left"), ('RGHT', 0, 0, 100, "Right"), ('UPPP', 0, 0, 100, "Up"), ('DOWN', 0, 0, 100, "Down"), ] instances = [ dict(location=dict(LEFT=0, RGHT=0, UPPP=0, DOWN=0), stylename="TotallyNormal"), dict(location=dict(LEFT=0, RGHT=100, UPPP=100, DOWN=0), stylename="Right Up"), ] fb.setupFvar(axes, instances) variations = {} # Four (x, y) pairs and four phantom points: leftDeltas = [(-200, 0), (-200, 0), (0, 0), (0, 0), None, None, None, None] rightDeltas = [(0, 0), (0, 0), (200, 0), (200, 0), None, None, None, None] upDeltas = [(0, 0), (0, 200), (0, 200), (0, 0), None, None, None, None] downDeltas = [(0, -200), (0, 0), (0, 0), (0, -200), None, None, None, None] variations['a'] = [ TupleVariation(dict(RGHT=(0, 1, 1)), rightDeltas), TupleVariation(dict(LEFT=(0, 1, 1)), leftDeltas), TupleVariation(dict(UPPP=(0, 1, 1)), upDeltas), TupleVariation(dict(DOWN=(0, 1, 1)), downDeltas), ] fb.setupGvar(variations) fb.setupOS2() fb.setupPost() fb.setupDummyDSIG() fb.save(outPath) _verifyOutput(outPath)
def makeGlyfBBox1(calcBBoxes=True, composite=False): font = getTTFont(sfntTTFSourcePath, recalcBBoxes=calcBBoxes) glyf = font["glyf"] hmtx = font["hmtx"] for name in ("bbox1", "bbox2"): pen = TTGlyphPen(None) if name == "bbox1": pen.moveTo((0, 0)) pen.lineTo((0, 1000)) pen.lineTo((1000, 1000)) pen.lineTo((1000, 0)) pen.closePath() else: pen.moveTo((0, 0)) pen.qCurveTo((500, 750), (600, 500), (500, 250), (0, 0)) pen.closePath() glyph = pen.glyph() if not calcBBoxes: glyph.recalcBounds(glyf) glyph.xMax -= 100 glyf.glyphs[name] = glyph hmtx.metrics[name] = (0, 0) glyf.glyphOrder.append(name) if composite: name = "bbox3" pen = TTGlyphPen(glyf.glyphOrder) pen.addComponent("bbox1", [1, 0, 0, 1, 0, 0]) pen.addComponent("bbox2", [1, 0, 0, 1, 1000, 0]) glyph = pen.glyph() glyph.recalcBounds(glyf) glyf.glyphs[name] = glyph hmtx.metrics[name] = (0, 0) glyf.glyphOrder.append(name) tableData = getSFNTData(font)[0] font.close() del font header, directory, tableData = defaultSFNTTestData(tableData=tableData, flavor="TTF") data = packSFNT(header, directory, tableData, flavor="TTF") return data
def test_within_range_component_transform(self): componentName = 'a' glyphSet = {} pen = TTGlyphPen(glyphSet) pen.moveTo((0, 0)) pen.lineTo((0, 1)) pen.lineTo((1, 0)) pen.closePath() glyphSet[componentName] = _TestGlyph(pen.glyph()) pen.addComponent(componentName, (1.5, 0, 0, 1, 0, 0)) pen.addComponent(componentName, (1, 0, 0, -1.5, 0, 0)) compositeGlyph = pen.glyph() pen.addComponent(componentName, (1.5, 0, 0, 1, 0, 0)) pen.addComponent(componentName, (1, 0, 0, -1.5, 0, 0)) expectedGlyph = pen.glyph() self.assertEqual(expectedGlyph, compositeGlyph)
def test_glyph_decomposes(self): componentName = 'a' glyphSet = {} pen = TTGlyphPen(glyphSet) pen.moveTo((0, 0)) pen.lineTo((0, 1)) pen.lineTo((1, 0)) pen.closePath() glyphSet[componentName] = _TestGlyph(pen.glyph()) pen.moveTo((0, 0)) pen.lineTo((0, 1)) pen.lineTo((1, 0)) pen.closePath() pen.addComponent(componentName, (1, 0, 0, 1, 2, 0)) compositeGlyph = pen.glyph() pen.moveTo((0, 0)) pen.lineTo((0, 1)) pen.lineTo((1, 0)) pen.closePath() pen.moveTo((2, 0)) pen.lineTo((2, 1)) pen.lineTo((3, 0)) pen.closePath() plainGlyph = pen.glyph() self.assertEqual(plainGlyph, compositeGlyph)
def makeCollectionHmtxTransform2(): fonts = [getTTFont(sfntTTFSourcePath), getTTFont(sfntTTFSourcePath)] for i, font in enumerate(fonts): glyf = font["glyf"] hmtx = font["hmtx"] maxp = font["maxp"] for name in glyf.glyphs: glyph = glyf.glyphs[name] glyph.expand(glyf) if hasattr(glyph, "xMin"): metrics = hmtx.metrics[name] if i == 0: # Move the glyph so that xMin is 0 pen = TTGlyphPen(None) glyph.draw(pen, glyf, -glyph.xMin) glyph = pen.glyph() glyph.recalcBounds(glyf) assert glyph.xMin == 0 glyf.glyphs[name] = glyph hmtx.metrics[name] = (metrics[0], 0) # Build a unique glyph for each font, but with the same advance and LSB name = "box" pen = TTGlyphPen(None) pen.moveTo([0, 0]) pen.lineTo([0, 1000]) if i > 0: pen.lineTo([0, 2000]) pen.lineTo([1000, 2000]) pen.lineTo([1000, 1000]) pen.lineTo([1000, 0]) pen.closePath() glyph = pen.glyph() glyph.recalcBounds(glyf) glyf.glyphs[name] = glyph hmtx.metrics[name] = (glyph.xMax, glyph.xMin) glyf.glyphOrder.append(name) maxp.recalc(font) data = hmtx.compile(font) hmtx.decompile(data, font) data = getSFNTCollectionData(fonts, shared=["hmtx"]) return data
def test_clamp_to_almost_2_component_transform(self): componentName = 'a' glyphSet = {} pen = TTGlyphPen(glyphSet) pen.moveTo((0, 0)) pen.lineTo((0, 1)) pen.lineTo((1, 0)) pen.closePath() glyphSet[componentName] = _TestGlyph(pen.glyph()) pen.addComponent(componentName, (1.99999, 0, 0, 1, 0, 0)) pen.addComponent(componentName, (1, 2, 0, 1, 0, 0)) pen.addComponent(componentName, (1, 0, 2, 1, 0, 0)) pen.addComponent(componentName, (1, 0, 0, 2, 0, 0)) pen.addComponent(componentName, (-2, 0, 0, -2, 0, 0)) compositeGlyph = pen.glyph() almost2 = MAX_F2DOT14 # 0b1.11111111111111 pen.addComponent(componentName, (almost2, 0, 0, 1, 0, 0)) pen.addComponent(componentName, (1, almost2, 0, 1, 0, 0)) pen.addComponent(componentName, (1, 0, almost2, 1, 0, 0)) pen.addComponent(componentName, (1, 0, 0, almost2, 0, 0)) pen.addComponent(componentName, (-2, 0, 0, -2, 0, 0)) expectedGlyph = pen.glyph() self.assertEqual(expectedGlyph, compositeGlyph)
def test_keep_duplicate_end_point(self): pen = TTGlyphPen(None) pen.moveTo((0, 0)) pen.lineTo((100, 0)) pen.qCurveTo((100, 50), (50, 100), (0, 0)) pen.lineTo((0, 0)) # the duplicate point is not removed pen.closePath() self.assertEqual(len(pen.points), 5) self.assertEqual(pen.points[0], (0, 0))
def make_font(feature_source, fea_type='fea'): """Return font with GSUB compiled from given source. Adds a bunch of filler tables so the font can be saved if needed, for debugging purposes. """ # copied from fontTools' feaLib/builder_test. glyphs = """ .notdef space slash fraction semicolon period comma ampersand quotedblleft quotedblright quoteleft quoteright zero one two three four five six seven eight nine zero.oldstyle one.oldstyle two.oldstyle three.oldstyle four.oldstyle five.oldstyle six.oldstyle seven.oldstyle eight.oldstyle nine.oldstyle onequarter onehalf threequarters onesuperior twosuperior threesuperior ordfeminine ordmasculine A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z A.sc B.sc C.sc D.sc E.sc F.sc G.sc H.sc I.sc J.sc K.sc L.sc M.sc N.sc O.sc P.sc Q.sc R.sc S.sc T.sc U.sc V.sc W.sc X.sc Y.sc Z.sc A.alt1 A.alt2 A.alt3 B.alt1 B.alt2 B.alt3 C.alt1 C.alt2 C.alt3 a.alt1 a.alt2 a.alt3 a.end b.alt c.mid d.alt d.mid e.begin e.mid e.end m.begin n.end s.end z.end Eng Eng.alt1 Eng.alt2 Eng.alt3 A.swash B.swash C.swash D.swash E.swash F.swash G.swash H.swash I.swash J.swash K.swash L.swash M.swash N.swash O.swash P.swash Q.swash R.swash S.swash T.swash U.swash V.swash W.swash X.swash Y.swash Z.swash f_l c_h c_k c_s c_t f_f f_f_i f_f_l f_i o_f_f_i s_t f_i.begin a_n_d T_h T_h.swash germandbls ydieresis yacute breve grave acute dieresis macron circumflex cedilla umlaut ogonek caron damma hamza sukun kasratan lam_meem_jeem noon.final noon.initial by feature lookup sub table """.split() font = TTFont() font.setGlyphOrder(glyphs) glyph_order = font.getGlyphOrder() font['cmap'] = cmap = newTable('cmap') table = cmap_format_4(4) table.platformID = 3 table.platEncID = 1 table.language = 0 table.cmap = {AGL2UV[n]: n for n in glyph_order if n in AGL2UV} cmap.tableVersion = 0 cmap.tables = [table] font['glyf'] = glyf = newTable('glyf') glyf.glyphs = {} glyf.glyphOrder = glyph_order for name in glyph_order: pen = TTGlyphPen(None) glyf[name] = pen.glyph() font['head'] = head = newTable('head') head.tableVersion = 1.0 head.fontRevision = 1.0 head.flags = head.checkSumAdjustment = head.magicNumber =\ head.created = head.modified = head.macStyle = head.lowestRecPPEM =\ head.fontDirectionHint = head.indexToLocFormat =\ head.glyphDataFormat =\ head.xMin = head.xMax = head.yMin = head.yMax = 0 head.unitsPerEm = 1000 font['hhea'] = hhea = newTable('hhea') hhea.tableVersion = 0x00010000 hhea.ascent = hhea.descent = hhea.lineGap =\ hhea.caretSlopeRise = hhea.caretSlopeRun = hhea.caretOffset =\ hhea.reserved0 = hhea.reserved1 = hhea.reserved2 = hhea.reserved3 =\ hhea.metricDataFormat = hhea.advanceWidthMax = hhea.xMaxExtent =\ hhea.minLeftSideBearing = hhea.minRightSideBearing =\ hhea.numberOfHMetrics = 0 font['hmtx'] = hmtx = newTable('hmtx') hmtx.metrics = {} for name in glyph_order: hmtx[name] = (600, 50) font['loca'] = newTable('loca') font['maxp'] = maxp = newTable('maxp') maxp.tableVersion = 0x00005000 maxp.numGlyphs = 0 font['post'] = post = newTable('post') post.formatType = 2.0 post.extraNames = [] post.mapping = {} post.glyphOrder = glyph_order post.italicAngle = post.underlinePosition = post.underlineThickness =\ post.isFixedPitch = post.minMemType42 = post.maxMemType42 =\ post.minMemType1 = post.maxMemType1 = 0 if fea_type == 'fea': addOpenTypeFeaturesFromString(font, feature_source) elif fea_type == 'mti': font['GSUB'] = mtiLib.build(UnicodeIO(feature_source), font) return font
def test_moveTo_errorWithinContour(self): pen = TTGlyphPen(None) pen.moveTo((0, 0)) with self.assertRaises(AssertionError): pen.moveTo((1, 0))
def test_endPath_sameAsClosePath(self): pen = TTGlyphPen(None) pen.moveTo((0, 0)) pen.lineTo((0, 1)) pen.lineTo((1, 0)) pen.closePath() closePathGlyph = pen.glyph() pen.moveTo((0, 0)) pen.lineTo((0, 1)) pen.lineTo((1, 0)) pen.endPath() endPathGlyph = pen.glyph() self.assertEqual(closePathGlyph, endPathGlyph)
def test_glyph_errorOnUnendedContour(self): pen = TTGlyphPen(None) pen.moveTo((0, 0)) with self.assertRaises(AssertionError): pen.glyph()
def test_out_of_range_transform_decomposed(self): componentName = 'a' glyphSet = {} pen = TTGlyphPen(glyphSet) pen.moveTo((0, 0)) pen.lineTo((0, 1)) pen.lineTo((1, 0)) pen.closePath() glyphSet[componentName] = _TestGlyph(pen.glyph()) pen.addComponent(componentName, (3, 0, 0, 2, 0, 0)) pen.addComponent(componentName, (1, 0, 0, 1, -1, 2)) pen.addComponent(componentName, (2, 0, 0, -3, 0, 0)) compositeGlyph = pen.glyph() pen.moveTo((0, 0)) pen.lineTo((0, 2)) pen.lineTo((3, 0)) pen.closePath() pen.moveTo((-1, 2)) pen.lineTo((-1, 3)) pen.lineTo((0, 2)) pen.closePath() pen.moveTo((0, 0)) pen.lineTo((0, -3)) pen.lineTo((2, 0)) pen.closePath() expectedGlyph = pen.glyph() self.assertEqual(expectedGlyph, compositeGlyph)
def test_endPath_sameAsClosePath(self): pen = TTGlyphPen(None) pen.moveTo((0, 0)) pen.lineTo((0, 1)) pen.lineTo((1, 0)) pen.closePath() closePathGlyph = pen.glyph() pen.moveTo((0, 0)) pen.lineTo((0, 1)) pen.lineTo((1, 0)) pen.endPath() endPathGlyph = pen.glyph() endPathGlyph.program = closePathGlyph.program self.assertEqual(closePathGlyph, endPathGlyph)
src = sys.argv[1] dst = sys.argv[2] with TTFont(src) as f: glyfTable = f["glyf"] glyphSet = f.getGlyphSet() for glyphName in glyphSet.keys(): if not glyfTable[glyphName].isComposite(): continue # record TTGlyph outlines without components dcPen = DecomposingRecordingPen(glyphSet) glyphSet[glyphName].draw(dcPen) # replay recording onto a skia-pathops Path path = pathops.Path() pathPen = path.getPen() dcPen.replay(pathPen) # remove overlaps path.simplify() # create new TTGlyph from Path ttPen = TTGlyphPen(None) path.draw(ttPen) glyfTable[glyphName] = ttPen.glyph() f.save(dst)
def test_recursiveComponent(self): glyphSet = {} pen_dummy = TTGlyphPen(glyphSet) glyph_dummy = pen_dummy.glyph() glyphSet["A"] = glyph_dummy glyphSet["B"] = glyph_dummy pen_A = TTGlyphPen(glyphSet) pen_A.addComponent("B", (1, 0, 0, 1, 0, 0)) pen_B = TTGlyphPen(glyphSet) pen_B.addComponent("A", (1, 0, 0, 1, 0, 0)) glyph_A = pen_A.glyph() glyph_B = pen_B.glyph() glyphSet["A"] = glyph_A glyphSet["B"] = glyph_B with self.assertRaisesRegex( TTLibError, "glyph '.' contains a recursive component reference"): glyph_A.getCoordinates(glyphSet)
def test_glyph_decomposes(self): componentName = 'a' glyphSet = {} pen = TTGlyphPen(glyphSet) pen.moveTo((0, 0)) pen.lineTo((0, 1)) pen.lineTo((1, 0)) pen.closePath() glyphSet[componentName] = _TestGlyph(pen.glyph()) pen.moveTo((0, 0)) pen.lineTo((0, 1)) pen.lineTo((1, 0)) pen.closePath() pen.addComponent(componentName, (1, 0, 0, 1, 2, 0)) compositeGlyph = pen.glyph() pen.moveTo((0, 0)) pen.lineTo((0, 1)) pen.lineTo((1, 0)) pen.closePath() pen.moveTo((2, 0)) pen.lineTo((2, 1)) pen.lineTo((3, 0)) pen.closePath() plainGlyph = pen.glyph() plainGlyph.program = compositeGlyph.program self.assertEqual(plainGlyph, compositeGlyph)
def fontfile(): class Glyph(object): def __init__(self, empty=False, **kwargs): if not empty: self.draw = partial(self.drawRect, **kwargs) else: self.draw = lambda pen: None @staticmethod def drawRect(pen, xMin, xMax): pen.moveTo((xMin, 0)) pen.lineTo((xMin, 1000)) pen.lineTo((xMax, 1000)) pen.lineTo((xMax, 0)) pen.closePath() class CompositeGlyph(object): def __init__(self, components): self.components = components def draw(self, pen): for baseGlyph, (offsetX, offsetY) in self.components: pen.addComponent(baseGlyph, (1, 0, 0, 1, offsetX, offsetY)) fb = fontBuilder.FontBuilder(unitsPerEm=1000, isTTF=True) fb.setupGlyphOrder( [".notdef", "space", "A", "acutecomb", "Aacute", "zero", "one", "two"] ) fb.setupCharacterMap( { 0x20: "space", 0x41: "A", 0x0301: "acutecomb", 0xC1: "Aacute", 0x30: "zero", 0x31: "one", 0x32: "two", } ) fb.setupHorizontalMetrics( { ".notdef": (500, 50), "space": (600, 0), "A": (550, 40), "acutecomb": (0, -40), "Aacute": (550, 40), "zero": (500, 30), "one": (500, 50), "two": (500, 40), } ) fb.setupHorizontalHeader(ascent=1000, descent=-200) srcGlyphs = { ".notdef": Glyph(xMin=50, xMax=450), "space": Glyph(empty=True), "A": Glyph(xMin=40, xMax=510), "acutecomb": Glyph(xMin=-40, xMax=60), "Aacute": CompositeGlyph([("A", (0, 0)), ("acutecomb", (200, 0))]), "zero": Glyph(xMin=30, xMax=470), "one": Glyph(xMin=50, xMax=450), "two": Glyph(xMin=40, xMax=460), } pen = TTGlyphPen(srcGlyphs) glyphSet = {} for glyphName, glyph in srcGlyphs.items(): glyph.draw(pen) glyphSet[glyphName] = pen.glyph() fb.setupGlyf(glyphSet) fb.setupNameTable( { "familyName": "TestWOFF2", "styleName": "Regular", "uniqueFontIdentifier": "TestWOFF2 Regular; Version 1.000; ABCD", "fullName": "TestWOFF2 Regular", "version": "Version 1.000", "psName": "TestWOFF2-Regular", } ) fb.setupOS2() fb.setupPost() buf = BytesIO() fb.save(buf) buf.seek(0) assert fb.font["maxp"].numGlyphs == 8 assert fb.font["hhea"].numberOfHMetrics == 6 for glyphName in fb.font.getGlyphOrder(): xMin = getattr(fb.font["glyf"][glyphName], "xMin", 0) assert xMin == fb.font["hmtx"][glyphName][1] return buf
def test_build_var(tmpdir): outPath = os.path.join(str(tmpdir), "test_var.ttf") fb = FontBuilder(1024, isTTF=True) fb.setupGlyphOrder([".notdef", ".null", "A", "a"]) fb.setupCharacterMap({65: "A", 97: "a"}) advanceWidths = {".notdef": 600, "A": 600, "a": 600, ".null": 600} familyName = "HelloTestFont" styleName = "TotallyNormal" nameStrings = dict(familyName=dict(en="HelloTestFont", nl="HalloTestFont"), styleName=dict(en="TotallyNormal", nl="TotaalNormaal")) nameStrings['psName'] = familyName + "-" + styleName pen = TTGlyphPen(None) pen.moveTo((100, 0)) pen.lineTo((100, 400)) pen.lineTo((500, 400)) pen.lineTo((500, 000)) pen.closePath() glyph = pen.glyph() pen = TTGlyphPen(None) emptyGlyph = pen.glyph() glyphs = {".notdef": emptyGlyph, "A": glyph, "a": glyph, ".null": emptyGlyph} fb.setupGlyf(glyphs) metrics = {} glyphTable = fb.font["glyf"] for gn, advanceWidth in advanceWidths.items(): metrics[gn] = (advanceWidth, glyphTable[gn].xMin) fb.setupHorizontalMetrics(metrics) fb.setupHorizontalHeader(ascent=824, descent=200) fb.setupNameTable(nameStrings) axes = [ ('LEFT', 0, 0, 100, "Left"), ('RGHT', 0, 0, 100, "Right"), ('UPPP', 0, 0, 100, "Up"), ('DOWN', 0, 0, 100, "Down"), ] instances = [ dict(location=dict(LEFT=0, RGHT=0, UPPP=0, DOWN=0), stylename="TotallyNormal"), dict(location=dict(LEFT=0, RGHT=100, UPPP=100, DOWN=0), stylename="Right Up"), ] fb.setupFvar(axes, instances) variations = {} # Four (x, y) pairs and four phantom points: leftDeltas = [(-200, 0), (-200, 0), (0, 0), (0, 0), None, None, None, None] rightDeltas = [(0, 0), (0, 0), (200, 0), (200, 0), None, None, None, None] upDeltas = [(0, 0), (0, 200), (0, 200), (0, 0), None, None, None, None] downDeltas = [(0, -200), (0, 0), (0, 0), (0, -200), None, None, None, None] variations['a'] = [ TupleVariation(dict(RGHT=(0, 1, 1)), rightDeltas), TupleVariation(dict(LEFT=(0, 1, 1)), leftDeltas), TupleVariation(dict(UPPP=(0, 1, 1)), upDeltas), TupleVariation(dict(DOWN=(0, 1, 1)), downDeltas), ] fb.setupGvar(variations) fb.setupOS2() fb.setupPost() fb.setupDummyDSIG() fb.save(outPath) _verifyOutput(outPath)