def contour() -> Contour: g = Glyph("a") pen = g.getPen() pen.moveTo((0, 0)) pen.curveTo((10, 10), (10, 20), (0, 20)) pen.closePath() return g.contours[0]
def test_insertGlyph(): g = Glyph() pen = g.getPen() pen.moveTo((0, 0)) pen.lineTo((1, 1)) pen.lineTo((0, 1)) pen.closePath() layer = Layer() layer.insertGlyph(g, "a") assert "a" in layer assert layer["a"].name == "a" assert layer["a"].contours == g.contours assert layer["a"] is not g layer.insertGlyph(g, "b") assert "b" in layer assert layer["b"].name == "b" assert layer["b"].contours == layer["a"].contours assert layer["b"] is not layer["a"] assert layer["b"] is not g assert g.name is None with pytest.raises(KeyError, match="glyph named 'a' already exists"): layer.insertGlyph(g, "a", overwrite=False) with pytest.raises(ValueError, match=".*Glyph .* has no name; can't add it"): layer.insertGlyph(g)
def layer() -> Layer: a = Glyph("a") pen = a.getPen() pen.moveTo((0, 0)) pen.curveTo((10, 10), (10, 20), (0, 20)) pen.closePath() layer = Layer(glyphs=[a]) return layer
def test_font_defcon_behavior(ufo_UbuTestData): font = ufo_UbuTestData font.newGlyph("b") assert "b" in font glyph = Glyph("c") font.addGlyph(glyph) assert font["c"] is glyph font.renameGlyph("c", "d") assert font["d"] is glyph assert font["d"].name == "d" guideline = Guideline(x=1) font.appendGuideline(guideline) assert font.info.guidelines[-1] is guideline font.appendGuideline({"y": 1, "name": "asdf"}) assert font.info.guidelines[-1].name == "asdf" font.newLayer("abc") assert "abc" in font.layers font.renameLayer("abc", "def") assert "abc" not in font.layers assert "def" in font.layers
def addRCJKGlyphToVarCoUFO( ufo, rcjkGlyphSet, srcGlyphName, dstGlyphName, unicodes, renameTable, componentSourceGlyphSet, globalAxisNames, ): if renameTable is None: renameTable = {} rcjkGlyph = rcjkGlyphSet.getGlyph(srcGlyphName) if rcjkGlyph.components and not rcjkGlyph.outline.isEmpty(): logger.warning( f"glyph {srcGlyphName} has both outlines and components") glyph = UGlyph(dstGlyphName) glyph.unicodes = unicodes glyph.width = max(0, rcjkGlyph.width) # width can't be negative rcjkGlyphToVarCoGlyph(rcjkGlyph, glyph, renameTable, componentSourceGlyphSet) if globalAxisNames is None: axisNameMapping = _makeAxisNameMapping(rcjkGlyph.axes) axisNames = set(axisNameMapping.values()) else: axisNames = globalAxisNames for varIndex, rcjkVarGlyph in enumerate(rcjkGlyph.variations): location = rcjkVarGlyph.location location = normalizeLocation(location, rcjkGlyph.axes) if globalAxisNames is None: location = {axisNameMapping[k]: v for k, v in location.items()} sparseLocation = {k: v for k, v in location.items() if v != 0} layerName = layerNameFromLocation(sparseLocation, axisNames) assert layerName, (srcGlyphName, varIndex, location, rcjkGlyph.axes) layer = getUFOLayer(ufo, layerName) varGlyph = UGlyph(dstGlyphName) varGlyph.width = max(0, rcjkVarGlyph.width) # width can't be negative rcjkGlyphToVarCoGlyph(rcjkVarGlyph, varGlyph, renameTable, componentSourceGlyphSet) layer[dstGlyphName] = varGlyph ufo[dstGlyphName] = glyph
def test_structure_forbid_extra_keys(forbid_extra_keys: bool) -> None: conv = cattr.GenConverter(forbid_extra_keys=forbid_extra_keys) register_hooks(conv) data = {"name": "a", "foo": "bar"} if forbid_extra_keys: with pytest.raises(Exception, match="Extra fields in constructor for .*: foo"): conv.structure(data, Glyph) else: assert conv.structure(data, Glyph) == Glyph(name="a")
def test_init_layer_with_glyphs_list(): a = Glyph("a") b = Glyph("b") layer = Layer(glyphs=[a, b]) assert layer["a"] is a assert layer["b"] is b with pytest.raises(KeyError, match=".*Glyph .* can't be added twice"): Layer(glyphs=[a, a]) c = Glyph() with pytest.raises(ValueError, match=".*Glyph .* has no name"): Layer(glyphs=[c]) with pytest.raises(KeyError, match="glyph named 'b' already exists"): Layer(glyphs=[a, b, Glyph("b")]) with pytest.raises(TypeError, match="Expected Glyph, found int"): Layer(glyphs=[1])
def test_addGlyph(): a = Glyph("a") layer = Layer() layer.addGlyph(a) assert "a" in layer assert layer["a"] is a with pytest.raises(KeyError, match="glyph named 'a' already exists"): layer.addGlyph(a)
def addFlattenedGlyphsToUFO(self, ufo, location, numDecimalsRounding=0, characterSet=None, glyphSet=None): if characterSet is not None and glyphSet is not None: raise TypeError("can't pass both characterSet and glyphSet") if numDecimalsRounding == 1: roundFunc = roundFuncOneDecimal elif numDecimalsRounding != 0: assert 0, numDecimalsRounding else: roundFunc = otRound revCmap = self.getGlyphNamesAndUnicodes() glyphNames = filterGlyphNames(sorted(revCmap)) for glyphName in glyphNames: if glyphSet is not None: if glyphName not in glyphSet: continue elif characterSet is not None: codePoints = set(revCmap[glyphName]) if not codePoints & characterSet: continue glyph = UGlyph(glyphName) glyph.unicodes = revCmap[glyphName] copyMarkColor(self.characterGlyphGlyphSet.getGlyph(glyphName), glyph) pen = RoundingPointPen(glyph.getPointPen(), roundFunc) try: width = self.drawPointsCharacterGlyph(glyphName, location, pen) except InterpolationError as e: logger.warning( f"glyph {glyphName} can't be interpolated ({e})") except Exception as e: logger.warning(f"glyph {glyphName} caused an error: {e!r}") raise else: glyph.width = max(0, width) # can't be negative ufo[glyphName] = glyph
def _draw_glyph_extents(ufo: ufoLib2.Font, glyph: Glyph): # apparently on Mac (but not Linux) Chrome and Firefox end up relying on the # extents of the base layer to determine where the glyph might paint. If you # leave the base blank the COLR glyph never renders. # TODO we could narrow this to bbox to cover all layers pen = glyph.getPen() pen.moveTo((0, 0)) pen.lineTo((ufo.info.unitsPerEm, ufo.info.unitsPerEm)) pen.endPath() return glyph
def test_font_mapping_behavior(ufo_UbuTestData): font = ufo_UbuTestData assert font["a"] is font.layers.defaultLayer["a"] assert ("a" in font) == ("a" in font.layers.defaultLayer) assert len(font) == len(font.layers.defaultLayer) glyph = Glyph("b") font["b"] = glyph assert font["b"] is glyph assert font.layers.defaultLayer["b"] is glyph del font["a"] assert "a" not in font assert "a" not in font.layers.defaultLayer
def test_init_layer_with_glyphs_dict(): a = Glyph() b = Glyph() layer = Layer("My Layer", {"a": a, "b": b}) assert layer.name == "My Layer" assert "a" in layer assert layer["a"] is a assert a.name == "a" assert "b" in layer assert layer["b"] is b assert b.name == "b" with pytest.raises( ValueError, match="glyph has incorrect name: expected 'a', found 'b'"): Layer(glyphs={"a": b}) with pytest.raises(KeyError, match=".*Glyph .* can't be added twice"): Layer(glyphs={"a": a, "b": a}) with pytest.raises(TypeError, match="Expected Glyph, found int"): Layer(glyphs={"a": 1})
def test_renameGlyph() -> None: g = Glyph() layer = Layer(glyphs={"a": g}) assert g.name == "a" layer.renameGlyph("a", "a") # no-op assert g.name == "a" layer.renameGlyph("a", "b") assert g.name == "b" layer.insertGlyph(g, "a") with pytest.raises(KeyError, match="target glyph named 'a' already exists"): layer.renameGlyph("b", "a")
def test_custom_type_overrides() -> None: conv = cattr.GenConverter( type_overrides={Image: cattr.override(omit=True)}) register_hooks(conv) # check that Glyph.image attribute (of type Image) is omitted assert conv.unstructure(Glyph()) == { "width": 0, "height": 0, "unicodes": [], "lib": {}, "anchors": [], "components": [], "contours": [], "guidelines": [], }
def layer() -> Layer: a = Glyph("a") pen = a.getPen() pen.moveTo((8, 0)) pen.lineTo((18, 0)) pen.lineTo((18, 20)) pen.lineTo((8, 20)) pen.closePath() a.width = 30 a.appendAnchor({"x": 10, "y": 30, "name": "top"}) b = Glyph("b", width=a.width, components=[Component("a", (1, 0, 0, 1, 2, -5))]) layer = Layer(glyphs=[a, b]) return layer
def _draw_glyph_extents(ufo: ufoLib2.Font, glyph: Glyph, bounds: Tuple[float, float, float, float]): # apparently on Mac (but not Linux) Chrome and Firefox end up relying on the # extents of the base layer to determine where the glyph might paint. If you # leave the base blank the COLR glyph never renders. if rectArea(bounds) == 0: return start, end = bounds[:2], bounds[2:] pen = glyph.getPen() pen.moveTo(start) pen.lineTo(end) pen.endPath() return glyph
def test_glyph_get_margins(layer): a = layer["a"] # for simple contour glyphs without components, layer is optional/unused assert a.getLeftMargin() == 8 assert a.getLeftMargin(layer) == 8 assert a.getRightMargin() == 12 assert a.getRightMargin(layer) == 12 assert a.getBottomMargin() == 0 assert a.getBottomMargin(layer) == 0 assert a.getTopMargin() == -20 assert a.getTopMargin(layer) == -20 a.verticalOrigin = 20 assert a.getBottomMargin() == -20 assert a.getBottomMargin(layer) == -20 assert a.getTopMargin() == 0 assert a.getTopMargin(layer) == 0 b = layer["b"] # for composite glyphs, layer is required for m in ("Left", "Right", "Top", "Bottom"): with pytest.raises(TypeError, match="layer is required to compute bounds"): getattr(b, f"get{m}Margin")() assert b.getLeftMargin(layer) == 10 assert b.getRightMargin(layer) == 10 assert b.getBottomMargin(layer) == -5 assert b.getTopMargin(layer) == -15 b.verticalOrigin = 15 assert b.getBottomMargin(layer) == -20 assert b.getTopMargin(layer) == 0 c = Glyph() # empty glyph assert c.getLeftMargin() is None assert c.getRightMargin() is None assert c.getBottomMargin() is None assert c.getTopMargin() is None
def test_glyph_get_bounds_empty(): g = Glyph() assert g.getBounds() is None assert g.getControlBounds() is None
def test_glyph_without_name(): assert Glyph().name is None
Guideline(x=1, y=2, angle=45.0, name="baz", color="0,1,0.5,1", identifier="0001"), { "x": 1, "y": 2, "angle": 45.0, "name": "baz", "color": "0,1,0.5,1", "identifier": "0001", }, ), (Glyph(), {}), ( Glyph( "a", width=1000, height=800, unicodes=[97], lib=Lib(foo=b"bar"), note="Latin lowercase 'a'", anchors=[Anchor(100, 200, "top"), Anchor(100, -50, "bottom")], contours=[ Contour([ Point(0, 0, "line"), Point(1, 1, "line"), Point(1, 0, "line")
def test_glyph_repr(): g = Glyph() assert repr(g) == f"<ufoLib2.objects.glyph.Glyph at {hex(id(g))}>" g = Glyph("a") assert repr(g) == f"<ufoLib2.objects.glyph.Glyph 'a' at {hex(id(g))}>"
def test_glyph_get_bounds(): a = Glyph("a") pen = a.getPen() pen.moveTo((0, 0)) pen.curveTo((10, 10), (10, 20), (0, 20)) pen.closePath() b = Glyph("b", components=[Component("a", (1, 0, 0, 1, -50, 100))]) layer = Layer(glyphs=[a, b]) assert a.getBounds(layer) == BoundingBox(xMin=0, yMin=0, xMax=7.5, yMax=20) assert a.getControlBounds(layer) == BoundingBox(xMin=0, yMin=0, xMax=10, yMax=20) with pytest.raises( TypeError, match="layer is required to compute bounds of components"): b.getBounds() with pytest.raises( TypeError, match="layer is required to compute bounds of components"): b.getControlBounds() assert b.getBounds(layer) == (-50, 100, -42.5, 120 ) # namedtuple is a tuple assert b.getControlBounds(layer) == (-50, 100, -40, 120)
def test_glyph_defcon_behavior(): glyph = Glyph() glyph.appendAnchor(Anchor(1, 2, "top")) glyph.appendAnchor({"x": 3, "y": 4, "name": "bottom"}) assert glyph.anchors == [Anchor(1, 2, "top"), Anchor(3, 4, "bottom")] glyph = Glyph() glyph.appendContour(Contour([Point(1, 2)])) assert glyph.contours == [Contour([Point(1, 2)])] glyph = Glyph() glyph.appendGuideline(Guideline(x=1)) glyph.appendGuideline({"x": 2}) assert glyph.guidelines == [Guideline(x=1), Guideline(x=2)]
def test_copyDataFromGlyph(ufo_UbuTestData): font = ufo_UbuTestData a = font["a"] a.height = 500 a.image = Image("a.png") a.note = "a note" a.lib = {"bar": [3, 2, 1]} a.anchors = [Anchor(250, 0, "bottom")] a.guidelines = [Guideline(y=500)] a.components = [Component("A")] b = Glyph("b") b.width = 350 b.height = 1000 b.image = Image("b.png") b.note = "b note" b.lib = {"foo": [1, 2, 3]} b.anchors = [Anchor(350, 800, "top")] b.guidelines = [Guideline(x=50)] assert b.name != a.name assert b.width != a.width assert b.height != a.height assert b.unicodes != a.unicodes assert b.image != a.image assert b.note != a.note assert b.lib != a.lib assert b.anchors != a.anchors assert b.guidelines != a.guidelines assert b.contours != a.contours assert b.components != a.components def _assert_equal_but_distinct_objects(glyph1, glyph2): assert glyph1.width == glyph2.width assert glyph1.height == glyph2.height assert glyph1.unicodes == glyph2.unicodes assert glyph1.unicodes is not glyph2.unicodes assert glyph1.image == glyph2.image assert glyph1.image is not glyph2.image assert glyph1.note == glyph2.note assert glyph1.lib == glyph2.lib assert glyph1.lib is not glyph2.lib assert glyph1.lib["bar"] == glyph2.lib["bar"] assert glyph1.lib["bar"] is not glyph2.lib["bar"] assert glyph1.anchors == glyph2.anchors assert glyph1.anchors is not glyph2.anchors assert glyph1.anchors[0] is not glyph2.anchors[0] assert glyph1.guidelines == glyph2.guidelines assert glyph1.guidelines is not glyph2.guidelines assert glyph1.guidelines[0] is not glyph2.guidelines[0] assert glyph1.contours == glyph2.contours assert glyph1.contours is not glyph2.contours assert glyph1.contours[0] is not glyph2.contours[0] assert glyph1.components == glyph2.components assert glyph1.components is not glyph2.components assert glyph1.components[0] is not glyph2.components[0] b.copyDataFromGlyph(a) assert b.name != a.name _assert_equal_but_distinct_objects(b, a) c = a.copy() assert c.name == a.name _assert_equal_but_distinct_objects(c, a) d = a.copy(name="d") assert d.name == "d" _assert_equal_but_distinct_objects(d, a)