def AddGlyphVariations(font, thin, regular, black): assert "gvar" not in font gvar = font["gvar"] = table__g_v_a_r() gvar.version = 1 gvar.reserved = 0 gvar.variations = {} for glyphName in regular.getGlyphOrder(): regularCoord = GetCoordinates(regular, glyphName) thinCoord = GetCoordinates(thin, glyphName) blackCoord = GetCoordinates(black, glyphName) if not regularCoord or not blackCoord or not thinCoord: logging.warning("glyph %s not present in all input fonts", glyphName) continue if (len(regularCoord) != len(blackCoord) or len(regularCoord) != len(thinCoord)): logging.warning( "glyph %s has not the same number of " "control points in all input fonts", glyphName) continue thinDelta = [] blackDelta = [] for ((regX, regY), (blackX, blackY), (thinX, thinY)) in \ zip(regularCoord, blackCoord, thinCoord): thinDelta.append(((thinX - regX, thinY - regY))) blackDelta.append((blackX - regX, blackY - regY)) thinVar = GlyphVariation({"wght": (-1.0, -1.0, 0.0)}, thinDelta) blackVar = GlyphVariation({"wght": (0.0, 1.0, 1.0)}, blackDelta) gvar.variations[glyphName] = [thinVar, blackVar]
def test_decompileCoord_roundTrip(self): # Make sure we are not affected by https://github.com/behdad/fonttools/issues/286 data = deHexStr("7F B9 80 35") values, _ = GlyphVariation.decompileCoord_(["wght", "wdth"], data, 0) axisValues = {axis:(val, val, val) for axis, val in values.items()} gvar = GlyphVariation(axisValues, [None] * 4) self.assertEqual("7F B9 80 35", hexencode(gvar.compileCoord(["wght", "wdth"])))
def test_decompileCoord_roundTrip(self): # Make sure we are not affected by https://github.com/behdad/fonttools/issues/286 data = deHexStr("7F B9 80 35") values, _ = GlyphVariation.decompileCoord_(["wght", "wdth"], data, 0) axisValues = {axis: (val, val, val) for axis, val in values.items()} gvar = GlyphVariation(axisValues, [None] * 4) self.assertEqual("7F B9 80 35", hexencode(gvar.compileCoord(["wght", "wdth"])))
def test_toXML_allDeltasNone(self): writer = XMLWriter(BytesIO()) axes = {"wght": (0.0, 1.0, 1.0)} g = GlyphVariation(axes, [None] * 5) g.toXML(writer, ["wght", "wdth"]) self.assertEqual([ '<tuple>', '<coord axis="wght" value="1.0"/>', '<!-- no deltas -->', '</tuple>' ], GlyphVariationTest.xml_lines(writer))
def test_decompilePoints_roundTrip(self): numPointsInGlyph = 500 # greater than 255, so we also exercise code path for 16-bit encoding compile = lambda points: GlyphVariation.compilePoints(points, numPointsInGlyph) decompile = lambda data: set(GlyphVariation.decompilePoints_(numPointsInGlyph, data, 0)[0]) for i in range(50): points = set(random.sample(range(numPointsInGlyph), 30)) self.assertSetEqual(points, decompile(compile(points)), "failed round-trip decompile/compilePoints; points=%s" % points) allPoints = set(range(numPointsInGlyph)) self.assertSetEqual(allPoints, decompile(compile(allPoints)))
def test_toXML_allDeltasNone(self): writer = XMLWriter(BytesIO()) axes = {"wght":(0.0, 1.0, 1.0)} g = GlyphVariation(axes, [None] * 5) g.toXML(writer, ["wght", "wdth"]) self.assertEqual([ '<tuple>', '<coord axis="wght" value="1.0"/>', '<!-- no deltas -->', '</tuple>' ], GlyphVariationTest.xml_lines(writer))
def test_compileCoord(self): gvar = GlyphVariation( { "wght": (-1.0, -1.0, -1.0), "wdth": (0.4, 0.5, 0.6) }, [None] * 4) self.assertEqual("C0 00 20 00", hexencode(gvar.compileCoord(["wght", "wdth"]))) self.assertEqual("20 00 C0 00", hexencode(gvar.compileCoord(["wdth", "wght"]))) self.assertEqual("C0 00", hexencode(gvar.compileCoord(["wght"])))
def test_compile_embeddedCoords_intermediate_sharedPoints(self): gvar = GlyphVariation({"wght": (0.0, 0.5, 1.0), "wdth": (0.0, 0.8, 0.8)}, [(7,4), (8,5), (9,6)]) axisTags = ["wght", "wdth"] tuple, data = gvar.compile(axisTags, sharedCoordIndices={}, sharedPoints={0,1,2}) # len(data)=8; flags=EMBEDDED_TUPLE_COORD # embeddedCoord=[(0.5, 0.8)]; intermediateCoord=[(0.0, 0.0), (1.0, 0.8)] self.assertEqual("00 08 C0 00 20 00 33 33 00 00 00 00 40 00 33 33", hexencode(tuple)) self.assertEqual("02 07 08 09 " # deltaX: [7, 8, 9] "02 04 05 06", # deltaY: [4, 5, 6] hexencode(data))
def test_toXML_allDeltasZero(self): writer = XMLWriter(StringIO()) axes = {"wght":(0.0, 1.0, 1.0)} g = GlyphVariation(axes, GlyphCoordinates.zeros(5)) g.toXML(writer, ["wght", "wdth"]) self.assertEqual([ '<tuple>', '<coord axis="wght" value="1.0"/>', '<!-- all deltas are (0,0) -->', '</tuple>' ], GlyphVariationTest.xml_lines(writer))
def test_compile_sharedCoords_nonIntermediate_sharedPoints(self): gvar = GlyphVariation({"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [(7,4), (8,5), (9,6)]) axisTags = ["wght", "wdth"] sharedCoordIndices = { gvar.compileCoord(axisTags): 0x77 } tuple, data = gvar.compile(axisTags, sharedCoordIndices, sharedPoints={0,1,2}) # len(data)=8; flags=None; tupleIndex=0x77 # embeddedCoord=[]; intermediateCoord=[] self.assertEqual("00 08 00 77", hexencode(tuple)) self.assertEqual("02 07 08 09 " # deltaX: [7, 8, 9] "02 04 05 06", # deltaY: [4, 5, 6] hexencode(data))
def test_compile_sharedCoords_intermediate_sharedPoints(self): gvar = GlyphVariation({"wght": (0.3, 0.5, 0.7), "wdth": (0.1, 0.8, 0.9)}, [(7,4), (8,5), (9,6)]) axisTags = ["wght", "wdth"] sharedCoordIndices = { gvar.compileCoord(axisTags): 0x77 } tuple, data = gvar.compile(axisTags, sharedCoordIndices, sharedPoints={0,1,2}) # len(data)=8; flags=INTERMEDIATE_TUPLE; tupleIndex=0x77 # embeddedCoord=[]; intermediateCoord=[(0.3, 0.1), (0.7, 0.9)] self.assertEqual("00 08 40 77 13 33 06 66 2C CD 39 9A", hexencode(tuple)) self.assertEqual("02 07 08 09 " # deltaX: [7, 8, 9] "02 04 05 06", # deltaY: [4, 5, 6] hexencode(data))
def test_compile_embeddedCoords_intermediate_privatePoints(self): gvar = GlyphVariation({"wght": (0.4, 0.5, 0.6), "wdth": (0.7, 0.8, 0.9)}, [(7,4), (8,5), (9,6)]) axisTags = ["wght", "wdth"] tuple, data = gvar.compile(axisTags, sharedCoordIndices={}, sharedPoints=None) # len(data)=13; flags=PRIVATE_POINT_NUMBERS|INTERMEDIATE_TUPLE|EMBEDDED_TUPLE_COORD # embeddedCoord=(0.5, 0.8); intermediateCoord=[(0.4, 0.7), (0.6, 0.9)] self.assertEqual("00 09 E0 00 20 00 33 33 19 9A 2C CD 26 66 39 9A", hexencode(tuple)) self.assertEqual("00 " # all points in glyph "02 07 08 09 " # deltaX: [7, 8, 9] "02 04 05 06", # deltaY: [4, 5, 6] hexencode(data))
def test_compile_embeddedCoords_nonIntermediate_privatePoints(self): gvar = GlyphVariation({"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, GlyphCoordinates([(7,4), (8,5), (9,6)])) axisTags = ["wght", "wdth"] tuple, data = gvar.compile(axisTags, sharedCoordIndices={}, sharedPoints=None) # len(data)=13; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_TUPLE_COORD # embeddedCoord=[(0.5, 0.8)]; intermediateCoord=[] self.assertEqual("00 0D A0 00 20 00 33 33", hexencode(tuple)) self.assertEqual("03 02 00 01 01 " # 3 points: [0, 1, 2] "02 07 08 09 " # deltaX: [7, 8, 9] "02 04 05 06", # deltaY: [4, 5, 6] hexencode(data))
def test_compileGlyph_onlyRedundantVariations(self): table = table__g_v_a_r() axes = {"wght": (0.3, 0.4, 0.5), "opsz": (0.7, 0.8, 0.9)} table.variations = { "glyphname": [ GlyphVariation(axes, [None] * 4), GlyphVariation(axes, [None] * 4), GlyphVariation(axes, [None] * 4) ] } self.assertEqual( b"", table.compileGlyph_("glyphname", 8, ["wght", "opsz"], {}))
def test_compile_sharedCoords_intermediate_privatePoints(self): gvar = GlyphVariation({"wght": (0.0, 0.5, 1.0), "wdth": (0.0, 0.8, 1.0)}, [(7,4), (8,5), (9,6)]) axisTags = ["wght", "wdth"] sharedCoordIndices = { gvar.compileCoord(axisTags): 0x77 } tuple, data = gvar.compile(axisTags, sharedCoordIndices, sharedPoints=None) # len(data)=13; flags=PRIVATE_POINT_NUMBERS; tupleIndex=0x77 # embeddedCoord=[]; intermediateCoord=[(0.0, 0.0), (1.0, 1.0)] self.assertEqual("00 09 60 77 00 00 00 00 40 00 40 00", hexencode(tuple)) self.assertEqual("00 " # all points in glyph "02 07 08 09 " # deltaX: [7, 8, 9] "02 04 05 06", # deltaY: [4, 5, 6] hexencode(data))
def test_decompilePoints_roundTrip(self): numPointsInGlyph = 500 # greater than 255, so we also exercise code path for 16-bit encoding compile = lambda points: GlyphVariation.compilePoints( points, numPointsInGlyph) decompile = lambda data: set( GlyphVariation.decompilePoints_(numPointsInGlyph, data, 0)[0]) for i in range(50): points = set(random.sample(range(numPointsInGlyph), 30)) self.assertSetEqual( points, decompile(compile(points)), "failed round-trip decompile/compilePoints; points=%s" % points) allPoints = set(range(numPointsInGlyph)) self.assertSetEqual(allPoints, decompile(compile(allPoints)))
def test_toXML(self): writer = XMLWriter(StringIO()) axes = {"wdth":(0.3, 0.4, 0.5), "wght":(0.0, 1.0, 1.0), "opsz":(-0.7, -0.7, 0.0)} g = GlyphVariation(axes, GlyphCoordinates([(9,8), (7,6), (0,0), (-1,-2)])) g.toXML(writer, ["wdth", "wght", "opsz"]) self.assertEqual([ '<tuple>', '<coord axis="wdth" max="0.5" min="0.3" value="0.4"/>', '<coord axis="wght" value="1.0"/>', '<coord axis="opsz" value="-0.7"/>', '<delta pt="0" x="9" y="8"/>', '<delta pt="1" x="7" y="6"/>', '<delta pt="3" x="-1" y="-2"/>', '</tuple>' ], GlyphVariationTest.xml_lines(writer))
def test_fromXML(self): g = GlyphVariation({}, [None] * 4) for name, attrs, content in parseXML( '<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>' '<coord axis="wght" value="1.0"/>' '<coord axis="opsz" value="-0.5"/>' '<delta pt="1" x="33" y="44"/>' '<delta pt="2" x="-2" y="170"/>'): g.fromXML(name, attrs, content) self.assertEqual({ "wdth":( 0.3, 0.4, 0.5), "wght":( 0.0, 1.0, 1.0), "opsz":(-0.5, -0.5, 0.0) }, g.axes) self.assertEqual([None, (33, 44), (-2, 170), None], g.coordinates)
def _add_gvar(font, model, master_ttfs): print("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 = GlyphVariation(support, delta) gvar.variations[glyph].append(var)
def test_fromXML(self): g = GlyphVariation({}, [None] * 4) for name, attrs, content in parseXML( '<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>' '<coord axis="wght" value="1.0"/>' '<coord axis="opsz" value="-0.5"/>' '<delta pt="1" x="33" y="44"/>' '<delta pt="2" x="-2" y="170"/>'): g.fromXML(name, attrs, content) self.assertEqual( { "wdth": (0.3, 0.4, 0.5), "wght": (0.0, 1.0, 1.0), "opsz": (-0.5, -0.5, 0.0) }, g.axes) self.assertEqual([None, (33, 44), (-2, 170), None], g.coordinates)
def test_compilePoints(self): compilePoints = lambda p: GlyphVariation.compilePoints( set(p), numPointsInGlyph=999) self.assertEqual("00", hexencode(compilePoints( range(999)))) # all points in glyph self.assertEqual("01 00 07", hexencode(compilePoints([7]))) self.assertEqual("01 80 FF FF", hexencode(compilePoints([65535]))) self.assertEqual("02 01 09 06", hexencode(compilePoints([9, 15]))) self.assertEqual("06 05 07 01 F7 02 01 F2", hexencode(compilePoints([7, 8, 255, 257, 258, 500]))) self.assertEqual("03 01 07 01 80 01 EC", hexencode(compilePoints([7, 8, 500]))) self.assertEqual("04 01 07 01 81 BE E7 0C 0F", hexencode(compilePoints([7, 8, 0xBEEF, 0xCAFE]))) self.maxDiff = None self.assertEqual( "81 2C" + # 300 points (0x12c) in total " 7F 00" + (127 * " 01") + # first run, contains 128 points: [0 .. 127] " 7F" + (128 * " 01") + # second run, contains 128 points: [128 .. 255] " 2B" + (44 * " 01"), # third run, contains 44 points: [256 .. 299] hexencode(compilePoints(range(300)))) self.assertEqual( "81 8F" + # 399 points (0x18f) in total " 7F 00" + (127 * " 01") + # first run, contains 128 points: [0 .. 127] " 7F" + (128 * " 01") + # second run, contains 128 points: [128 .. 255] " 7F" + (128 * " 01") + # third run, contains 128 points: [256 .. 383] " 0E" + (15 * " 01"), # fourth run, contains 15 points: [384 .. 398] hexencode(compilePoints(range(399))))
def test_compile_embeddedCoords_nonIntermediate_sharedPoints(self): gvar = GlyphVariation( { "wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8) }, [(7, 4), (8, 5), (9, 6)]) axisTags = ["wght", "wdth"] tuple, data = gvar.compile(axisTags, sharedCoordIndices={}, sharedPoints={0, 1, 2}) # len(data)=8; flags=EMBEDDED_TUPLE_COORD # embeddedCoord=[(0.5, 0.8)]; intermediateCoord=[] self.assertEqual("00 08 80 00 20 00 33 33", hexencode(tuple)) self.assertEqual( "02 07 08 09 " # deltaX: [7, 8, 9] "02 04 05 06", # deltaY: [4, 5, 6] hexencode(data))
def test_toXML(self): writer = XMLWriter(BytesIO()) axes = { "wdth": (0.3, 0.4, 0.5), "wght": (0.0, 1.0, 1.0), "opsz": (-0.7, -0.7, 0.0) } g = GlyphVariation(axes, [(9, 8), None, (7, 6), (0, 0), (-1, -2), None]) g.toXML(writer, ["wdth", "wght", "opsz"]) self.assertEqual([ '<tuple>', '<coord axis="wdth" max="0.5" min="0.3" value="0.4"/>', '<coord axis="wght" value="1.0"/>', '<coord axis="opsz" value="-0.7"/>', '<delta pt="0" x="9" y="8"/>', '<delta pt="2" x="7" y="6"/>', '<delta pt="3" x="0" y="0"/>', '<delta pt="4" x="-1" y="-2"/>', '</tuple>' ], GlyphVariationTest.xml_lines(writer))
def test_compileDeltaValues(self): compileDeltaValues = lambda values: hexencode( GlyphVariation.compileDeltaValues_(values)) # zeroes self.assertEqual("80", compileDeltaValues([0])) self.assertEqual("BF", compileDeltaValues([0] * 64)) self.assertEqual("BF 80", compileDeltaValues([0] * 65)) self.assertEqual("BF A3", compileDeltaValues([0] * 100)) self.assertEqual("BF BF BF BF", compileDeltaValues([0] * 256)) # bytes self.assertEqual("00 01", compileDeltaValues([1])) self.assertEqual("06 01 02 03 7F 80 FF FE", compileDeltaValues([1, 2, 3, 127, -128, -1, -2])) self.assertEqual("3F" + (64 * " 7F"), compileDeltaValues([127] * 64)) self.assertEqual("3F" + (64 * " 7F") + " 00 7F", compileDeltaValues([127] * 65)) # words self.assertEqual("40 66 66", compileDeltaValues([0x6666])) self.assertEqual("43 66 66 7F FF FF FF 80 00", compileDeltaValues([0x6666, 32767, -1, -32768])) self.assertEqual("7F" + (64 * " 11 22"), compileDeltaValues([0x1122] * 64)) self.assertEqual("7F" + (64 * " 11 22") + " 40 11 22", compileDeltaValues([0x1122] * 65)) # bytes, zeroes, bytes: a single zero is more compact when encoded as part of the bytes run self.assertEqual("04 7F 7F 00 7F 7F", compileDeltaValues([127, 127, 0, 127, 127])) self.assertEqual("01 7F 7F 81 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 127, 127])) self.assertEqual("01 7F 7F 82 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 0, 127, 127])) self.assertEqual("01 7F 7F 83 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 0, 0, 127, 127])) # bytes, zeroes self.assertEqual("01 01 00", compileDeltaValues([1, 0])) self.assertEqual("00 01 81", compileDeltaValues([1, 0, 0])) # words, bytes, words: a single byte is more compact when encoded as part of the words run self.assertEqual("42 66 66 00 02 77 77", compileDeltaValues([0x6666, 2, 0x7777])) self.assertEqual("40 66 66 01 02 02 40 77 77", compileDeltaValues([0x6666, 2, 2, 0x7777])) # words, zeroes, words self.assertEqual("40 66 66 80 40 77 77", compileDeltaValues([0x6666, 0, 0x7777])) self.assertEqual("40 66 66 81 40 77 77", compileDeltaValues([0x6666, 0, 0, 0x7777])) self.assertEqual("40 66 66 82 40 77 77", compileDeltaValues([0x6666, 0, 0, 0, 0x7777])) # words, zeroes, bytes self.assertEqual("40 66 66 80 02 01 02 03", compileDeltaValues([0x6666, 0, 1, 2, 3])) self.assertEqual("40 66 66 81 02 01 02 03", compileDeltaValues([0x6666, 0, 0, 1, 2, 3])) self.assertEqual("40 66 66 82 02 01 02 03", compileDeltaValues([0x6666, 0, 0, 0, 1, 2, 3])) # words, zeroes self.assertEqual("40 66 66 80", compileDeltaValues([0x6666, 0])) self.assertEqual("40 66 66 81", compileDeltaValues([0x6666, 0, 0]))
def _add_gvar(font, axes, master_ttfs, master_locs, base_idx): # Make copies for modification master_ttfs = master_ttfs[:] master_locs = [l.copy() for l in master_locs] axis_tags = axes.keys() # Normalize master locations. TODO Move to a separate function. for tag, (name, lower, default, upper) in axes.items(): for l in master_locs: v = l[tag] if v == default: v = 0 elif v < default: v = (v - default) / (default - lower) else: v = (v - default) / (upper - default) l[tag] = v # Locations are normalized now print("Normalized master positions:") print(master_locs) print("Generating gvar") assert "gvar" not in font gvar = font["gvar"] = table__g_v_a_r() gvar.version = 1 gvar.reserved = 0 gvar.variations = {} # Assume single-model for now. model = VariationModel(master_locs) model_base_idx = model.mapping[base_idx] assert 0 == model_base_idx 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 gvar.variations[glyph] = [] deltas = model.getDeltas(allCoords) supports = model.supports assert len(deltas) == len(supports) for i, (delta, support) in enumerate(zip(deltas, supports)): if i == model_base_idx: continue var = GlyphVariation(support, delta) gvar.variations[glyph].append(var)
def test_compile_embeddedCoords_nonIntermediate_privatePoints(self): gvar = GlyphVariation( { "wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8) }, [(7, 4), (8, 5), (9, 6)]) axisTags = ["wght", "wdth"] tuple, data = gvar.compile(axisTags, sharedCoordIndices={}, sharedPoints=None) # len(data)=13; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_TUPLE_COORD # embeddedCoord=[(0.5, 0.8)]; intermediateCoord=[] self.assertEqual("00 09 A0 00 20 00 33 33", hexencode(tuple)) self.assertEqual( "00 " # all points in glyph "02 07 08 09 " # deltaX: [7, 8, 9] "02 04 05 06", # deltaY: [4, 5, 6] hexencode(data))
def test_compile_sharedCoords_nonIntermediate_sharedPoints(self): gvar = GlyphVariation( { "wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8) }, [(7, 4), (8, 5), (9, 6)]) axisTags = ["wght", "wdth"] sharedCoordIndices = {gvar.compileCoord(axisTags): 0x77} tuple, data = gvar.compile(axisTags, sharedCoordIndices, sharedPoints={0, 1, 2}) # len(data)=8; flags=None; tupleIndex=0x77 # embeddedCoord=[]; intermediateCoord=[] self.assertEqual("00 08 00 77", hexencode(tuple)) self.assertEqual( "02 07 08 09 " # deltaX: [7, 8, 9] "02 04 05 06", # deltaY: [4, 5, 6] hexencode(data))
def test_fromXML(self): g = GlyphVariation({}, [None] * 4) g.fromXML("coord", { "axis": "wdth", "min": "0.3", "value": "0.4", "max": "0.5" }, []) g.fromXML("coord", {"axis": "wght", "value": "1.0"}, []) g.fromXML("coord", {"axis": "opsz", "value": "-0.5"}, []) g.fromXML("delta", {"pt": "1", "x": "33", "y": "44"}, []) g.fromXML("delta", {"pt": "2", "x": "-2", "y": "170"}, []) self.assertEqual( { "wdth": (0.3, 0.4, 0.5), "wght": (0.0, 1.0, 1.0), "opsz": (-0.5, -0.5, 0.0) }, g.axes) self.assertEqual([None, (33, 44), (-2, 170), None], g.coordinates)
def test_compile_sharedCoords_intermediate_sharedPoints(self): gvar = GlyphVariation( { "wght": (0.3, 0.5, 0.7), "wdth": (0.1, 0.8, 0.9) }, [(7, 4), (8, 5), (9, 6)]) axisTags = ["wght", "wdth"] sharedCoordIndices = {gvar.compileCoord(axisTags): 0x77} tuple, data = gvar.compile(axisTags, sharedCoordIndices, sharedPoints={0, 1, 2}) # len(data)=8; flags=INTERMEDIATE_TUPLE; tupleIndex=0x77 # embeddedCoord=[]; intermediateCoord=[(0.3, 0.1), (0.7, 0.9)] self.assertEqual("00 08 40 77 13 33 06 66 2C CD 39 9A", hexencode(tuple)) self.assertEqual( "02 07 08 09 " # deltaX: [7, 8, 9] "02 04 05 06", # deltaY: [4, 5, 6] hexencode(data))
def test_decompileDeltas_roundTrip(self): numDeltas = 30 compile = GlyphVariation.compileDeltaValues_ decompile = lambda data: GlyphVariation.decompileDeltas_(numDeltas, data, 0)[0] for i in range(50): deltas = random.sample(range(-128, 127), 10) deltas.extend(random.sample(range(-32768, 32767), 10)) deltas.extend([0] * 10) random.shuffle(deltas) self.assertListEqual(deltas, decompile(compile(deltas)))
def test_decompileDeltas_roundTrip(self): numDeltas = 30 compile = GlyphVariation.compileDeltaValues_ decompile = lambda data: GlyphVariation.decompileDeltas_( numDeltas, data, 0)[0] for i in range(50): deltas = random.sample(range(-128, 127), 10) deltas.extend(random.sample(range(-32768, 32767), 10)) deltas.extend([0] * 10) random.shuffle(deltas) self.assertListEqual(deltas, decompile(compile(deltas)))
def test_compileGlyph_roundTrip(self): table = table__g_v_a_r() axisTags = ["wght", "wdth"] numPointsInGlyph = 4 glyphCoords = [(1, 1), (2, 2), (3, 3), (4, 4)] gvar1 = GlyphVariation( { "wght": (0.5, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0) }, glyphCoords) gvar2 = GlyphVariation( { "wght": (1.0, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0) }, glyphCoords) table.variations = {"oslash": [gvar1, gvar2]} data = table.compileGlyph_("oslash", numPointsInGlyph, axisTags, {}) self.assertEqual([gvar1, gvar2], table.decompileGlyph_(numPointsInGlyph, {}, axisTags, data))
def test_compile_sharedCoords_intermediate_privatePoints(self): gvar = GlyphVariation( { "wght": (0.0, 0.5, 1.0), "wdth": (0.0, 0.8, 1.0) }, [(7, 4), (8, 5), (9, 6)]) axisTags = ["wght", "wdth"] sharedCoordIndices = {gvar.compileCoord(axisTags): 0x77} tuple, data = gvar.compile(axisTags, sharedCoordIndices, sharedPoints=None) # len(data)=13; flags=PRIVATE_POINT_NUMBERS; tupleIndex=0x77 # embeddedCoord=[]; intermediateCoord=[(0.0, 0.0), (1.0, 1.0)] self.assertEqual("00 09 60 77 00 00 00 00 40 00 40 00", hexencode(tuple)) self.assertEqual( "00 " # all points in glyph "02 07 08 09 " # deltaX: [7, 8, 9] "02 04 05 06", # deltaY: [4, 5, 6] hexencode(data))
def test_decompilePoints_roundTrip(self): numPointsInGlyph = 500 # greater than 255, so we also exercise code path for 16-bit encoding compile = GlyphVariation.compilePoints decompile = lambda data: set(GlyphVariation.decompilePoints_(numPointsInGlyph, data, 0)[0]) for i in range(50): points = set(random.sample(range(numPointsInGlyph), 30)) if hasattr(self, "assertSetEqual"): # Python 2.7 and later self.assertSetEqual(points, decompile(compile(points)), "failed round-trip decompile/compilePoints; points=%s" % points) else: self.assertEqual(points, decompile(compile(points)), "failed round-trip decompile/compilePoints; points=%s" % points)
def test_decompileDeltas_roundTrip(self): numDeltas = 30 compile = GlyphVariation.compileDeltaValues_ decompile = lambda data: GlyphVariation.decompileDeltas_(numDeltas, data, 0)[0] for i in range(50): deltas = random.sample(range(-128, 127), 10) deltas.extend(random.sample(range(-32768, 32767), 10)) deltas.extend([0] * 10) random.shuffle(deltas) if hasattr(self, "assertListEqual"): # Python 2.7 and later self.assertListEqual(deltas, decompile(compile(deltas)), "failed round-trip decompile/compileDeltas; deltas=%s" % deltas) else: # Python 2.6 self.assertEqual(deltas, decompile(compile(deltas)), "failed round-trip decompile/compileDeltas; deltas=%s" % deltas)
def test_compilePoints(self): compilePoints = lambda p: GlyphVariation.compilePoints(set(p), numPointsInGlyph=999) self.assertEqual("00", hexencode(compilePoints(range(999)))) # all points in glyph self.assertEqual("01 00 07", hexencode(compilePoints([7]))) self.assertEqual("01 80 FF FF", hexencode(compilePoints([65535]))) self.assertEqual("02 01 09 06", hexencode(compilePoints([9, 15]))) self.assertEqual("06 05 07 01 F7 02 01 F2", hexencode(compilePoints([7, 8, 255, 257, 258, 500]))) self.assertEqual("03 01 07 01 80 01 F4", hexencode(compilePoints([7, 8, 500]))) self.assertEqual("04 01 07 01 81 BE EF 0C 0F", hexencode(compilePoints([7, 8, 0xBEEF, 0xCAFE]))) self.assertEqual("81 2C" + # 300 points (0x12c) in total " 7F 00" + (127 * " 01") + # first run, contains 128 points: [0 .. 127] " 7F 80" + (127 * " 01") + # second run, contains 128 points: [128 .. 255] " AB 01 00" + (43 * " 00 01"), # third run, contains 44 points: [256 .. 299] hexencode(compilePoints(range(300)))) self.assertEqual("81 8F" + # 399 points (0x18f) in total " 7F 00" + (127 * " 01") + # first run, contains 128 points: [0 .. 127] " 7F 80" + (127 * " 01") + # second run, contains 128 points: [128 .. 255] " FF 01 00" + (127 * " 00 01") + # third run, contains 128 points: [256 .. 383] " 8E 01 80" + (14 * " 00 01"), # fourth run, contains 15 points: [384 .. 398] hexencode(compilePoints(range(399))))
def test_compileIntermediateCoord(self): gvar = GlyphVariation( { "wght": (-1.0, -1.0, 0.0), "wdth": (0.4, 0.5, 0.6) }, [None] * 4) self.assertEqual( "C0 00 19 9A 00 00 26 66", hexencode(gvar.compileIntermediateCoord(["wght", "wdth"]))) self.assertEqual( "19 9A C0 00 26 66 00 00", hexencode(gvar.compileIntermediateCoord(["wdth", "wght"]))) self.assertEqual(None, gvar.compileIntermediateCoord(["wght"])) self.assertEqual("19 9A 26 66", hexencode(gvar.compileIntermediateCoord(["wdth"])))
def test_compileDeltaValues(self): compileDeltaValues = lambda values: hexencode(GlyphVariation.compileDeltaValues_(values)) # zeroes self.assertEqual("80", compileDeltaValues([0])) self.assertEqual("BF", compileDeltaValues([0] * 64)) self.assertEqual("BF 80", compileDeltaValues([0] * 65)) self.assertEqual("BF A3", compileDeltaValues([0] * 100)) self.assertEqual("BF BF BF BF", compileDeltaValues([0] * 256)) # bytes self.assertEqual("00 01", compileDeltaValues([1])) self.assertEqual("06 01 02 03 7F 80 FF FE", compileDeltaValues([1, 2, 3, 127, -128, -1, -2])) self.assertEqual("3F" + (64 * " 7F"), compileDeltaValues([127] * 64)) self.assertEqual("3F" + (64 * " 7F") + " 00 7F", compileDeltaValues([127] * 65)) # words self.assertEqual("40 66 66", compileDeltaValues([0x6666])) self.assertEqual("43 66 66 7F FF FF FF 80 00", compileDeltaValues([0x6666, 32767, -1, -32768])) self.assertEqual("7F" + (64 * " 11 22"), compileDeltaValues([0x1122] * 64)) self.assertEqual("7F" + (64 * " 11 22") + " 40 11 22", compileDeltaValues([0x1122] * 65)) # bytes, zeroes, bytes: a single zero is more compact when encoded as part of the bytes run self.assertEqual("04 7F 7F 00 7F 7F", compileDeltaValues([127, 127, 0, 127, 127])) self.assertEqual("01 7F 7F 81 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 127, 127])) self.assertEqual("01 7F 7F 82 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 0, 127, 127])) self.assertEqual("01 7F 7F 83 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 0, 0, 127, 127])) # bytes, zeroes self.assertEqual("01 01 00", compileDeltaValues([1, 0])) self.assertEqual("00 01 81", compileDeltaValues([1, 0, 0])) # words, bytes, words: a single byte is more compact when encoded as part of the words run self.assertEqual("42 66 66 00 02 77 77", compileDeltaValues([0x6666, 2, 0x7777])) self.assertEqual("40 66 66 01 02 02 40 77 77", compileDeltaValues([0x6666, 2, 2, 0x7777])) # words, zeroes, words self.assertEqual("40 66 66 80 40 77 77", compileDeltaValues([0x6666, 0, 0x7777])) self.assertEqual("40 66 66 81 40 77 77", compileDeltaValues([0x6666, 0, 0, 0x7777])) self.assertEqual("40 66 66 82 40 77 77", compileDeltaValues([0x6666, 0, 0, 0, 0x7777])) # words, zeroes, bytes self.assertEqual("40 66 66 80 02 01 02 03", compileDeltaValues([0x6666, 0, 1, 2, 3])) self.assertEqual("40 66 66 81 02 01 02 03", compileDeltaValues([0x6666, 0, 0, 1, 2, 3])) self.assertEqual("40 66 66 82 02 01 02 03", compileDeltaValues([0x6666, 0, 0, 0, 1, 2, 3])) # words, zeroes self.assertEqual("40 66 66 80", compileDeltaValues([0x6666, 0])) self.assertEqual("40 66 66 81", compileDeltaValues([0x6666, 0, 0]))
def test_compileSharedCoords(self): table = table__g_v_a_r() table.variations = {} deltas = [None] * 4 table.variations["A"] = [ GlyphVariation({ "wght": (1.0, 1.0, 1.0), "wdth": (0.5, 0.7, 1.0) }, deltas) ] table.variations["B"] = [ GlyphVariation({ "wght": (1.0, 1.0, 1.0), "wdth": (0.2, 0.7, 1.0) }, deltas), GlyphVariation({ "wght": (1.0, 1.0, 1.0), "wdth": (0.2, 0.8, 1.0) }, deltas) ] table.variations["C"] = [ GlyphVariation({ "wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.7, 1.0) }, deltas), GlyphVariation({ "wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.8, 1.0) }, deltas), GlyphVariation({ "wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.9, 1.0) }, deltas) ] # {"wght":1.0, "wdth":0.7} is shared 3 times; {"wght":1.0, "wdth":0.8} is shared twice. # Min and max values are not part of the shared coordinate pool and should get ignored. result = table.compileSharedCoords_(["wght", "wdth"]) self.assertEqual(["40 00 2C CD", "40 00 33 33"], [hexencode(c) for c in result])
def test_fromXML(self): g = GlyphVariation({}, GlyphCoordinates.zeros(4)) g.fromXML("coord", {"axis":"wdth", "min":"0.3", "value":"0.4", "max":"0.5"}, []) g.fromXML("coord", {"axis":"wght", "value":"1.0"}, []) g.fromXML("coord", {"axis":"opsz", "value":"-0.5"}, []) g.fromXML("delta", {"pt":"1", "x":"33", "y":"44"}, []) g.fromXML("delta", {"pt":"2", "x":"-2", "y":"170"}, []) self.assertEqual({ "wdth":( 0.3, 0.4, 0.5), "wght":( 0.0, 1.0, 1.0), "opsz":(-0.5, -0.5, 0.0) }, g.axes) self.assertEqual("0,0 33,44 -2,170 0,0", " ".join(["%d,%d" % c for c in g.coordinates]))
def test_compileCoord(self): gvar = GlyphVariation({"wght": (-1.0, -1.0, -1.0), "wdth": (0.4, 0.5, 0.6)}, [None] * 4) self.assertEqual("C0 00 20 00", hexencode(gvar.compileCoord(["wght", "wdth"]))) self.assertEqual("20 00 C0 00", hexencode(gvar.compileCoord(["wdth", "wght"]))) self.assertEqual("C0 00", hexencode(gvar.compileCoord(["wght"])))
def test_hasImpact_someDeltasNotZero(self): axes = {"wght":(0.0, 1.0, 1.0)} gvar = GlyphVariation(axes, [(0,0), (9,8), (7,6)]) self.assertTrue(gvar.hasImpact())
def test_hasImpact_allDeltasZero(self): axes = {"wght": (0.0, 1.0, 1.0)} gvar = GlyphVariation(axes, [(0, 0), (0, 0), (0, 0)]) self.assertTrue(gvar.hasImpact())
def test_hasImpact_allDeltasNone(self): axes = {"wght": (0.0, 1.0, 1.0)} gvar = GlyphVariation(axes, [None, None, None]) self.assertFalse(gvar.hasImpact())
def test_compileDeltas(self): gvar = GlyphVariation({}, [(0, 0), (1, 0), (2, 0), (3, 3)]) points = {1, 2} # deltaX for points: [1, 2]; deltaY for points: [0, 0] self.assertEqual("01 01 02 81", hexencode(gvar.compileDeltas(points)))
def test_fromXML(self): g = GlyphVariation({}, [None] * 4) g.fromXML("coord", {"axis":"wdth", "min":"0.3", "value":"0.4", "max":"0.5"}, []) g.fromXML("coord", {"axis":"wght", "value":"1.0"}, []) g.fromXML("coord", {"axis":"opsz", "value":"-0.5"}, []) g.fromXML("delta", {"pt":"1", "x":"33", "y":"44"}, []) g.fromXML("delta", {"pt":"2", "x":"-2", "y":"170"}, []) self.assertEqual({ "wdth":( 0.3, 0.4, 0.5), "wght":( 0.0, 1.0, 1.0), "opsz":(-0.5, -0.5, 0.0) }, g.axes) self.assertEqual([None, (33, 44), (-2, 170), None], g.coordinates)
def test_hasImpact_allDeltasZero(self): axes = {"wght":(0.0, 1.0, 1.0)} gvar = GlyphVariation(axes, [(0,0), (0,0), (0,0)]) self.assertTrue(gvar.hasImpact())
def test_hasImpact_allDeltasNone(self): axes = {"wght":(0.0, 1.0, 1.0)} gvar = GlyphVariation(axes, [None, None, None]) self.assertFalse(gvar.hasImpact())
def test_compileIntermediateCoord(self): gvar = GlyphVariation({"wght": (-1.0, -1.0, 0.0), "wdth": (0.4, 0.5, 0.6)}, [None] * 4) self.assertEqual("C0 00 19 9A 00 00 26 66", hexencode(gvar.compileIntermediateCoord(["wght", "wdth"]))) self.assertEqual("19 9A C0 00 26 66 00 00", hexencode(gvar.compileIntermediateCoord(["wdth", "wght"]))) self.assertEqual(None, gvar.compileIntermediateCoord(["wght"])) self.assertEqual("19 9A 26 66", hexencode(gvar.compileIntermediateCoord(["wdth"])))
def test_hasImpact_someDeltasNotZero(self): axes = {"wght": (0.0, 1.0, 1.0)} gvar = GlyphVariation(axes, [(0, 0), (9, 8), (7, 6)]) self.assertTrue(gvar.hasImpact())
def decompilePoints(data, offset): points, offset = GlyphVariation.decompilePoints_(numPointsInGlyph, deHexStr(data), offset) # Conversion to list needed for Python 3. return (list(points), offset)
def test_equal_differentCoordinates(self): gvar1 = GlyphVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0), (9, 8), (7, 6)]) gvar2 = GlyphVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0), (9, 8)]) self.assertNotEqual(gvar1, gvar2)
def test_compileDeltas(self): gvar = GlyphVariation({}, [(0,0), (1, 0), (2, 0), (3, 3)]) points = {1, 2} # deltaX for points: [1, 2]; deltaY for points: [0, 0] self.assertEqual("01 01 02 81", hexencode(gvar.compileDeltas(points)))
def test_equal(self): gvar1 = GlyphVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0), (9, 8), (7, 6)]) gvar2 = GlyphVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0), (9, 8), (7, 6)]) self.assertEqual(gvar1, gvar2)
def test_hasImpact_allDeltasZero(self): axes = {"wght":(0.0, 1.0, 1.0)} gvar = GlyphVariation(axes, GlyphCoordinates([(0,0), (0,0), (0,0)])) self.assertFalse(gvar.hasImpact())
def decompilePoints(data, offset): points, offset = GlyphVariation.decompilePoints_( numPointsInGlyph, deHexStr(data), offset) # Conversion to list needed for Python 3. return (list(points), offset)