def test_project_dump_load(tmpdir): destPath = pathlib.Path(tmpdir / "test.gggls") pr = Project() fontPath1 = getFontPath("IBMPlexSans-Regular.ttf") pr.addFont(fontPath1, 0) fontPath2 = getFontPath("IBMPlexSans-Regular.otf") pr.addFont(fontPath2, 0) json = pr.asJSON(destPath.parent) pr2 = Project.fromJSON(json, destPath.parent) for f1, f2 in zip(pr.fonts, pr2.fonts): assert f1.fontKey == f2.fontKey
async def test_DSFont(): ufoPath = getFontPath("MutatorSans.designspace") font = DSFont(ufoPath, 0) await font.load(sys.stderr.write) expected = [ 'MutatorSansBoldCondensed.ufo', 'MutatorSansBoldWide.ufo', 'MutatorSansLightCondensed.ufo', 'MutatorSansLightWide.ufo', ] assert expected == [p.name for p in font.getExternalFiles()] run = font.getGlyphRun("ABC") ax = [gi.ax for gi in run] assert [396, 443, 499] == ax # Glyph 'A' has custom vertical glyph metrics in the default master run = font.getGlyphRun("A", direction="TTB") assert run[0].ax == 0 assert run[0].ay == -986 assert run[0].dx == -198 assert run[0].dy == -777 # But not at the other masters, so expect defaults there run = font.getGlyphRun("A", varLocation=dict(wght=1000), direction="TTB") assert run[0].ax == 0 assert run[0].ay == -900 assert run[0].dx == -370 assert run[0].dy == -700
def test_pointCollector(): ufoPath = getFontPath("MutatorSansBoldWideMutated.ufo") reader = UFOReader(ufoPath) glyphSet = reader.getGlyphSet() pen = PointCollector(glyphSet) glyphSet["B"].draw(pen) assert len(pen.points) == 38 assert len(pen.tags) == 38 assert pen.contours == [3, 37] assert pen.tags[:12] == [1, 1, 1, 1, 1, 1, 2, 2, 1, 2, 2, 1] pen = PointCollector(glyphSet, decompose=False) glyphSet["Aacute"].draw(pen) assert len(pen.points) == 0 assert pen.contours == [] assert pen.components == [("A", (1, 0, 0, 1, 0, 0)), ("acute", (1, 0, 0, 1, 484, 20))] pen = PointCollector(glyphSet, decompose=True) glyphSet["Aacute"].draw(pen) assert len(pen.points) == 20 assert pen.contours == [3, 7, 11, 15, 19] assert pen.components == [] pen = PointCollector(glyphSet) glyphSet["O"].draw(pen) assert len(pen.points) == 28 assert pen.contours == [13, 27]
def test_shape_getFeatures(): s = HBShape.fromPath(getFontPath("IBMPlexSans-Regular.ttf")) expected = { 'aalt', 'ccmp', 'dnom', 'frac', 'liga', 'numr', 'ordn', 'salt', 'sinf', 'ss01', 'ss02', 'ss03', 'ss04', 'ss05', 'subs', 'sups', 'zero' } assert expected == set(s.getFeatures("GSUB"))
async def test_openFonts(fileName, expectedSortInfo, featuresGSUB, featuresGPOS, scripts, axes, ext, location, text, glyphNames, ax): fontPath = getFontPath(fileName) numFonts, opener, getSortInfo = getOpener(fontPath) assert numFonts(fontPath) == 1 font = opener(fontPath, 0) await font.load(None) sortInfo = getSortInfo(fontPath, 0) assert sortInfo == expectedSortInfo assert font.featuresGSUB == featuresGSUB assert font.featuresGPOS == featuresGPOS assert font.scripts == scripts assert font.axes == axes run = font.getGlyphRun(text, varLocation=location) assert [gi.name for gi in run] == glyphNames assert ext == [p.name for p in font.getExternalFiles()] if ax is not None: assert [gi.ax for gi in run] == ax
def test_ufoCharacterMapping_glyphNames(): ufoPath = getFontPath("MutatorSansBoldWideMutated.ufo") reader = UFOReader(ufoPath) cmap, revCmap, anchors = fetchCharacterMappingAndAnchors( reader.getGlyphSet(), ufoPath, ["A"]) assert cmap[0x0041] == "A" assert revCmap["A"] == [0x0041] assert anchors == {"A": [("top", 645, 815)]}
def test_getUpdateInfo(tmpdir): ufoSource = getFontPath("MutatorSansBoldWideMutated.ufo") ufoPath = shutil.copytree(ufoSource, tmpdir / "test.ufo") reader = UFOReader(ufoPath, validate=False) glyphSet = reader.getGlyphSet() cmap, unicodes, anchors = fetchCharacterMappingAndAnchors(glyphSet, ufoPath) state = UFOState(reader, glyphSet, getUnicodesAndAnchors=lambda: (unicodes, anchors)) feaPath = pathlib.Path(reader.fs.getsyspath("/features.fea")) feaPath.touch() state = state.newState() (needsFeaturesUpdate, needsGlyphUpdate, needsInfoUpdate, needsCmapUpdate, needsLibUpdate) = state.getUpdateInfo() assert needsFeaturesUpdate assert not needsGlyphUpdate assert not needsInfoUpdate assert not needsCmapUpdate infoPath = pathlib.Path(reader.fs.getsyspath("/fontinfo.plist")) infoPath.touch() state = state.newState() (needsFeaturesUpdate, needsGlyphUpdate, needsInfoUpdate, needsCmapUpdate, needsLibUpdate) = state.getUpdateInfo() assert not needsFeaturesUpdate assert not needsGlyphUpdate assert needsInfoUpdate assert not needsCmapUpdate glyph = Glyph("A", None) ppen = RecordingPointPen() glyphSet.readGlyph("A", glyph, ppen) glyph.anchors[0]["x"] = 123 glyphSet.writeGlyph("A", glyph, ppen.replay) state = state.newState() (needsFeaturesUpdate, needsGlyphUpdate, needsInfoUpdate, needsCmapUpdate, needsLibUpdate) = state.getUpdateInfo() assert needsFeaturesUpdate assert needsGlyphUpdate assert not needsInfoUpdate assert not needsCmapUpdate glyph = Glyph("A", None) ppen = RecordingPointPen() glyphSet.readGlyph("A", glyph, ppen) glyph.unicodes = [123] glyphSet.writeGlyph("A", glyph, ppen.replay) state = state.newState() (needsFeaturesUpdate, needsGlyphUpdate, needsInfoUpdate, needsCmapUpdate, needsLibUpdate) = state.getUpdateInfo() assert not needsFeaturesUpdate assert needsGlyphUpdate assert not needsInfoUpdate assert needsCmapUpdate
async def test_project_loadFonts(): pr = Project() fontPath = getFontPath("IBMPlexSans-Regular.ttf") pr.addFont(fontPath, 0) fii = pr.fonts[0] assert fii.font is None await pr.loadFonts() fii = pr.fonts[0] assert fii.font.axes == {} # simple check to see if we have a font at all
async def test_project_loadFont(): pr = Project() fontPath = getFontPath("IBMPlexSans-Regular.ttf") pr.addFont(fontPath, 0) await pr.fonts[0].load() with pytest.raises(TypeError): pr.addFont("a string", 0) fii = pr.fonts[0] assert fii.font.axes == {} # simple check to see if we have a font at all
def test_pointCollectorQuad(): ufoPath = getFontPath("QuadTest-Regular.ufo") reader = UFOReader(ufoPath) glyphSet = reader.getGlyphSet() pen = PointCollector(glyphSet) glyphSet["a"].draw(pen) assert len(pen.points) == 4 assert len(pen.tags) == 4 assert pen.contours == [3] assert pen.tags == [0, 0, 0, 0]
async def test_compileUFOToPath(tmpdir): ufoPath = getFontPath("MutatorSansBoldWideMutated.ufo") ttPath = tmpdir / "test.ttf" output = [] error = await compileUFOToPath(ufoPath, ttPath, output.append) output = "".join(output) assert ttPath.exists() assert os.stat(ttPath).st_size > 0 assert not error assert output == ""
def test_shape_getStylisticSetNames(): s = HBShape.fromPath(getFontPath("IBMPlexSans-Regular.ttf")) ssNames = s.getStylisticSetNames() expected = { 'ss01': 'simple lowercase a', 'ss02': 'simple lowercase g', 'ss03': 'slashed number zero', 'ss04': 'dotted number zero', 'ss05': 'alternate lowercase eszett' } assert expected == ssNames
async def test_getGlyphRunFromTextInfo(text, expectedGlyphNames, expectedPositions): fontPath = getFontPath('IBMPlexSansArabic-Regular.ttf') numFonts, opener, getSortInfo = getOpener(fontPath) font = opener(fontPath, 0) await font.load(None) textInfo = TextInfo(text) glyphs = font.getGlyphRunFromTextInfo(textInfo) glyphNames = [g.name for g in glyphs] positions = [g.pos for g in glyphs] assert expectedGlyphNames == glyphNames assert expectedPositions == positions
async def test_compileUFOToPathMultiple(tmpdir): ufoPaths = [ getFontPath("MutatorSansBoldCondensed.ufo"), getFontPath("MutatorSansBoldWide.ufo"), getFontPath("MutatorSansIntermediateCondensed.ufo"), getFontPath("MutatorSansIntermediateWide.ufo"), getFontPath("MutatorSansLightCondensed.ufo"), getFontPath("MutatorSansLightCondensed_support.S.middle.ufo"), getFontPath("MutatorSansLightCondensed_support.S.wide.ufo"), getFontPath("MutatorSansLightCondensed_support.crossbar.ufo"), getFontPath("MutatorSansLightWide.ufo"), ] ttPaths = [tmpdir / (u.name + ".ttf") for u in ufoPaths] output = [] coros = (compileUFOToPath(u, t, output.append) for u, t in zip(ufoPaths, ttPaths)) results = await asyncio.gather(*coros) assert results == [None] * len(results) assert [(os.stat(p).st_size > 0) for p in ttPaths] == [True] * len(results)
async def test_project_purgeFonts(): pr = Project() fontPath1 = getFontPath("IBMPlexSans-Regular.ttf") pr.addFont(fontPath1, 0) fontPath2 = getFontPath("IBMPlexSans-Regular.otf") pr.addFont(fontPath2, 0) assert len(pr.fonts) == 2 assert [fii.fontKey for fii in pr.fonts] == [(fontPath1, 0), (fontPath2, 0)] assert [fii.identifier for fii in pr.fonts] == ["fontItem_0", "fontItem_1"] assert len(pr._fontLoader.fonts) == 0 await pr.loadFonts() assert len(pr._fontLoader.fonts) == 2 del pr.fonts[0] assert list(pr._fontLoader.fonts) == [(fontPath1, 0), (fontPath2, 0)] pr.purgeFonts() assert list(pr._fontLoader.fonts) == [(fontPath2, 0)] del pr.fonts[0] assert list(pr._fontLoader.fonts) == [(fontPath2, 0)] pr.purgeFonts() assert list(pr._fontLoader.fonts) == []
def test_ufoCharacterMapping(): ufoPath = getFontPath("MutatorSansBoldWideMutated.ufo") reader = UFOReader(ufoPath) cmap, revCmap, anchors = fetchCharacterMappingAndAnchors( reader.getGlyphSet(), ufoPath) assert cmap[0x0041] == "A" assert revCmap["A"] == [0x0041] # MutatorSansBoldWideMutated.ufo/glyphs/A_.glif contains a commented-out <unicode> # tag, that must not be parsed, as well as a commented-out <anchor>. assert 0x1234 not in cmap assert anchors == { "A": [("top", 645, 815)], "E": [("top", 582.5, 815)], "macroncmb": [("_top", 0, 815)] }
async def test_mapGlyphsToChars(): text = "عربي بِّ" fontPath = getFontPath('Amiri-Regular.ttf') numFonts, opener, getSortInfo = getOpener(fontPath) font = opener(fontPath, 0) await font.load(None) textInfo = TextInfo(text) glyphs = font.getGlyphRunFromTextInfo(textInfo) charIndices = [] for glyphIndex in range(len(glyphs)): charIndices.append(glyphs.mapGlyphsToChars({glyphIndex})) expectedCharIndices = [{7}, {6}, {5}, {4}, {3}, {2}, {1}, {0}] assert expectedCharIndices == charIndices glyphIndices = [] for charIndex in range(len(text)): glyphIndices.append(glyphs.mapCharsToGlyphs({charIndex})) expectedGlyphIndices = [{7}, {6}, {5}, {4}, {3}, {2}, {1}, {0}] assert expectedGlyphIndices == glyphIndices
async def test_verticalGlyphMetricsFromUFO(): fontPath = getFontPath('MutatorSansBoldWideMutated.ufo') numFonts, opener, getSortInfo = getOpener(fontPath) font = opener(fontPath, 0) await font.load(None) textInfo = TextInfo("ABCDE") textInfo.directionOverride = "TTB" glyphs = font.getGlyphRunFromTextInfo(textInfo) ax = [g.ax for g in glyphs] ay = [g.ay for g in glyphs] dx = [g.dx for g in glyphs] dy = [g.dy for g in glyphs] expectedAX = [0, 0, 0, 0, 0] expectedAY = [-1022, -1000, -1000, -1000, -1000] expectedDX = [-645, -635, -687, -658, -560] expectedDY = [-822, -800, -800, -800, -800] assert expectedAX == ax assert expectedAY == ay assert expectedDX == dx assert expectedDY == dy
def test_pointCollector(): ufoPath = getFontPath("MutatorSansBoldWideMutated.ufo") reader = UFOReader(ufoPath) glyphSet = reader.getGlyphSet() pen = PointCollector(glyphSet) glyphSet["B"].draw(pen) assert len(pen.points) == 38 assert len(pen.tags) == 38 assert pen.contours == [3, 37] assert pen.tags[:12] == [1, 1, 1, 1, 1, 1, 2, 2, 1, 2, 2, 1] pen = PointCollector(glyphSet) glyphSet["Aacute"].draw(pen) assert len(pen.points) == 20 assert pen.contours == [3, 7, 11, 15, 19] pen = PointCollector(glyphSet) glyphSet["O"].draw(pen) assert len(pen.points) == 28 assert pen.contours == [13, 27]
async def test_colrV1Font(): fontPath = getFontPath("more_samples-glyf_colr_1.ttf") numFonts, opener, getSortInfo = getOpener(fontPath) font = opener(fontPath, 0) await font.load(None) textInfo = TextInfo("c") glyphs = font.getGlyphRunFromTextInfo(textInfo) glyphNames = [g.name for g in glyphs] glyphDrawing, *_ = font.getGlyphDrawings(glyphNames, True) boundingBox = glyphDrawing.bounds assert (100, 0, 900, 1000) == boundingBox surface = CoreGraphicsPixelSurface(boundingBox) context = NSGraphicsContext.graphicsContextWithCGContext_flipped_( surface.context, False) savedContext = NSGraphicsContext.currentContext() try: NSGraphicsContext.setCurrentContext_(context) glyphDrawing.draw(glyphs.colorPalette, (0, 0, 0, 1)) finally: NSGraphicsContext.setCurrentContext_(savedContext)
def test_shape_latin(testString, features, expectedGlyphNames): s = HBShape.fromPath(getFontPath("IBMPlexSans-Regular.ttf")) glyphs = s.shape(testString, features=features) assert [g.name for g in glyphs] == expectedGlyphNames
def test_sniffFontType(): fontPath = getFontPath("IBMPlexSans-Regular.ttf") assert sniffFontType(fontPath) == "ttf"
async def test_project_load_ttc(): pr = Project() fontPath = getFontPath("MutatorSans.ttc") for fontPath, fontNumber, getSortInfo in iterFontNumbers(fontPath): pr.addFont(fontPath, fontNumber) await pr.loadFonts()
async def test_DSFont_getOutline(): ufoPath = getFontPath("MutatorSans.designspace") font = DSFont(ufoPath, 0) await font.load(sys.stderr.write) drawing, *_ = font.getGlyphDrawings(["A"]) assert ((20, 0), (356, 700)) == drawing.path.bounds()
def _getFonts(fileName): p = getFontPath(fileName) ttf = TTFont(p, lazy=True) return FTFont.fromPath(p), ttf.getGlyphSet()
def test_shape_GlyphInfo_repr(): s = HBShape.fromPath(getFontPath("IBMPlexSans-Regular.ttf")) glyphs = s.shape("a") assert repr( glyphs[0] ) == "GlyphInfo(gid=4, name='a', cluster=0, dx=0, dy=0, ax=534, ay=0)"