From d1e85cb88809c87f7f3d56b11974f804d5debd14 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Wed, 3 Feb 2021 14:12:46 +0000 Subject: [PATCH 1/6] Allow 'sub X by NULL;' sequence to delete a glyph --- Lib/fontTools/feaLib/ast.py | 18 ++++++++++++- Lib/fontTools/feaLib/parser.py | 23 +++++++++++----- Tests/feaLib/builder_test.py | 2 +- Tests/feaLib/data/delete_glyph.fea | 3 +++ Tests/feaLib/data/delete_glyph.ttx | 43 ++++++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 Tests/feaLib/data/delete_glyph.fea create mode 100644 Tests/feaLib/data/delete_glyph.ttx diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index 7ef9afd92..6c2bfce85 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -188,6 +188,21 @@ class Comment(Element): return self.text +class NullGlyph(Expression): + """The NULL glyph, used in glyph deletion substitutions.""" + + def __init__(self, location=None): + Expression.__init__(self, location) + #: The name itself as a string + + def glyphSet(self): + """The glyphs in this class as a tuple of :class:`GlyphName` objects.""" + return () + + def asFea(self, indent=""): + return "NULL" + + class GlyphName(Expression): """A single glyph name, such as ``cedilla``.""" @@ -1246,8 +1261,9 @@ class MultipleSubstStatement(Statement): res += " " + " ".join(map(asFea, self.suffix)) else: res += asFea(self.glyph) + replacement = self.replacement or [ NullGlyph() ] res += " by " - res += " ".join(map(asFea, self.replacement)) + res += " ".join(map(asFea, replacement)) res += ";" return res diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index 7439fbf34..7100cf656 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -314,10 +314,15 @@ class Parser(object): location, ) - def parse_glyphclass_(self, accept_glyphname): + def parse_glyphclass_(self, accept_glyphname, accept_null=False): # Parses a glyph class, either named or anonymous, or (if - # ``bool(accept_glyphname)``) a glyph name. + # ``bool(accept_glyphname)``) a glyph name. If ``bool(accept_null)`` then + # also accept the special NULL glyph. if accept_glyphname and self.next_token_type_ in (Lexer.NAME, Lexer.CID): + if accept_null and self.next_token_ == "NULL": + # If you want a glyph called NULL, you should escape it. + self.advance_lexer_() + return self.ast.NullGlyph(location=self.cur_token_location_) glyph = self.expect_glyph_() self.check_glyph_name_in_glyph_set(glyph) return self.ast.GlyphName(glyph, location=self.cur_token_location_) @@ -375,7 +380,8 @@ class Parser(object): self.expect_symbol_("-") range_end = self.expect_cid_() self.check_glyph_name_in_glyph_set( - f"cid{range_start:05d}", f"cid{range_end:05d}", + f"cid{range_start:05d}", + f"cid{range_end:05d}", ) glyphs.add_cid_range( range_start, @@ -804,7 +810,7 @@ class Parser(object): if self.next_token_ == "by": keyword = self.expect_keyword_("by") while self.next_token_ != ";": - gc = self.parse_glyphclass_(accept_glyphname=True) + gc = self.parse_glyphclass_(accept_glyphname=True, accept_null=True) new.append(gc) elif self.next_token_ == "from": keyword = self.expect_keyword_("from") @@ -837,6 +843,9 @@ class Parser(object): num_lookups = len([l for l in lookups if l is not None]) + if len(new) == 1 and len(new[0].glyphSet()) == 0: + new = [] # Deletion + # GSUB lookup type 1: Single substitution. # Format A: "substitute a by a.sc;" # Format B: "substitute [one.fitted one.oldstyle] by one;" @@ -863,8 +872,10 @@ class Parser(object): not reverse and len(old) == 1 and len(old[0].glyphSet()) == 1 - and len(new) > 1 - and max([len(n.glyphSet()) for n in new]) == 1 + and ( + (len(new) > 1 and max([len(n.glyphSet()) for n in new]) == 1) + or len(new) == 0 + ) and num_lookups == 0 ): return self.ast.MultipleSubstStatement( diff --git a/Tests/feaLib/builder_test.py b/Tests/feaLib/builder_test.py index 151cd896a..279e8ca87 100644 --- a/Tests/feaLib/builder_test.py +++ b/Tests/feaLib/builder_test.py @@ -73,7 +73,7 @@ class BuilderTest(unittest.TestCase): LigatureSubtable AlternateSubtable MultipleSubstSubtable SingleSubstSubtable aalt_chain_contextual_subst AlternateChained MultipleLookupsPerGlyph MultipleLookupsPerGlyph2 GSUB_6_formats - GSUB_5_formats + GSUB_5_formats delete_glyph """.split() def __init__(self, methodName): diff --git a/Tests/feaLib/data/delete_glyph.fea b/Tests/feaLib/data/delete_glyph.fea new file mode 100644 index 000000000..36e0f0f9a --- /dev/null +++ b/Tests/feaLib/data/delete_glyph.fea @@ -0,0 +1,3 @@ +feature test { + sub a by NULL; +} test; diff --git a/Tests/feaLib/data/delete_glyph.ttx b/Tests/feaLib/data/delete_glyph.ttx new file mode 100644 index 000000000..777f6e364 --- /dev/null +++ b/Tests/feaLib/data/delete_glyph.ttx @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 56df97b5f26facbe1ed45375f3d0db646e70ef7a Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Wed, 3 Feb 2021 14:16:57 +0000 Subject: [PATCH 2/6] Prohibit non-functional "sub A B by NULL" --- Lib/fontTools/feaLib/parser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index 7100cf656..23a496181 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -843,8 +843,10 @@ class Parser(object): num_lookups = len([l for l in lookups if l is not None]) + is_deletion = False if len(new) == 1 and len(new[0].glyphSet()) == 0: new = [] # Deletion + is_deletion = True # GSUB lookup type 1: Single substitution. # Format A: "substitute a by a.sc;" @@ -947,7 +949,7 @@ class Parser(object): ) # If there are remaining glyphs to parse, this is an invalid GSUB statement - if len(new) != 0: + if len(new) != 0 or is_deletion: raise FeatureLibError("Invalid substitution statement", location) # GSUB lookup type 6: Chaining contextual substitution. From 93c23eaaf7c2ca83d6a6e69a6cd43e89c9204b7f Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Wed, 3 Feb 2021 18:21:03 +0000 Subject: [PATCH 3/6] COLRv1: define new PaintSweepGradient, amend tests with new format numbers --- Lib/fontTools/colorLib/builder.py | 17 ++++++++++ Lib/fontTools/ttLib/tables/otData.py | 31 +++++++++++------ Lib/fontTools/ttLib/tables/otTables.py | 15 +++++---- Tests/colorLib/builder_test.py | 34 +++++++++---------- Tests/ttLib/tables/C_O_L_R_test.py | 46 +++++++++++++------------- 5 files changed, 85 insertions(+), 58 deletions(-) diff --git a/Lib/fontTools/colorLib/builder.py b/Lib/fontTools/colorLib/builder.py index 998ab60d1..5a52edbc9 100644 --- a/Lib/fontTools/colorLib/builder.py +++ b/Lib/fontTools/colorLib/builder.py @@ -549,6 +549,23 @@ class LayerV1ListBuilder: return ot_paint + def buildPaintSweepGradient( + self, + colorLine: _ColorLineInput, + centerX: _ScalarInput, + centerY: _ScalarInput, + startAngle: _ScalarInput, + endAngle: _ScalarInput, + ) -> ot.Paint: + ot_paint = ot.Paint() + ot_paint.Format = int(ot.Paint.Format.PaintSweepGradient) + ot_paint.ColorLine = _to_color_line(colorLine) + ot_paint.centerX = _to_variable_int16(centerX) + ot_paint.centerY = _to_variable_int16(centerY) + ot_paint.startAngle = _to_variable_f16dot16_float(startAngle) + ot_paint.endAngle = _to_variable_f16dot16_float(endAngle) + return ot_paint + def buildPaintGlyph(self, glyph: str, paint: _PaintInput) -> ot.Paint: ot_paint = ot.Paint() ot_paint.Format = int(ot.Paint.Format.PaintGlyph) diff --git a/Lib/fontTools/ttLib/tables/otData.py b/Lib/fontTools/ttLib/tables/otData.py index a6f9619e6..59ff40f65 100755 --- a/Lib/fontTools/ttLib/tables/otData.py +++ b/Lib/fontTools/ttLib/tables/otData.py @@ -1648,38 +1648,47 @@ otData = [ ('PaintFormat5', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 5'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintGlyph table) to Paint subtable.'), - ('GlyphID', 'Glyph', None, None, 'Glyph ID for the source outline.'), + ('Offset24', 'ColorLine', None, None, 'Offset (from beginning of PaintSweep table) to ColorLine subtable.'), + ('VarInt16', 'centerX', None, None, 'Center x coordinate.'), + ('VarInt16', 'centerY', None, None, 'Center y coordinate.'), + ('VarFixed', 'startAngle', None, None, 'Start of the angular range of the gradient.'), + ('VarFixed', 'endAngle', None, None, 'End of the angular range of the gradient.'), ]), ('PaintFormat6', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 6'), - ('GlyphID', 'Glyph', None, None, 'Virtual glyph ID for a BaseGlyphV1List base glyph.'), + ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintGlyph table) to Paint subtable.'), + ('GlyphID', 'Glyph', None, None, 'Glyph ID for the source outline.'), ]), ('PaintFormat7', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 7'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintTransformed table) to Paint subtable.'), - ('Affine2x3', 'Transform', None, None, 'Offset (from beginning of PaintTrasformed table) to Affine2x3 subtable.'), + ('GlyphID', 'Glyph', None, None, 'Virtual glyph ID for a BaseGlyphV1List base glyph.'), ]), ('PaintFormat8', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 8'), + ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintTransformed table) to Paint subtable.'), + ('Affine2x3', 'Transform', None, None, 'Offset (from beginning of PaintTrasformed table) to Affine2x3 subtable.'), + ]), + + ('PaintFormat9', [ + ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 9'), ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintTranslate table) to Paint subtable.'), ('VarFixed', 'dx', None, None, 'Translation in x direction.'), ('VarFixed', 'dy', None, None, 'Translation in y direction.'), ]), - ('PaintFormat9', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 9'), + ('PaintFormat10', [ + ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 10'), ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintRotate table) to Paint subtable.'), ('VarFixed', 'angle', None, None, ''), ('VarFixed', 'centerX', None, None, ''), ('VarFixed', 'centerY', None, None, ''), ]), - ('PaintFormat10', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 10'), + ('PaintFormat11', [ + ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 11'), ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintSkew table) to Paint subtable.'), ('VarFixed', 'xSkewAngle', None, None, ''), ('VarFixed', 'ySkewAngle', None, None, ''), @@ -1687,8 +1696,8 @@ otData = [ ('VarFixed', 'centerY', None, None, ''), ]), - ('PaintFormat11', [ - ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 11'), + ('PaintFormat12', [ + ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 12'), ('LOffset24To(Paint)', 'SourcePaint', None, None, 'Offset (from beginning of PaintComposite table) to source Paint subtable.'), ('CompositeMode', 'CompositeMode', None, None, 'A CompositeMode enumeration value.'), ('LOffset24To(Paint)', 'BackdropPaint', None, None, 'Offset (from beginning of PaintComposite table) to backdrop Paint subtable.'), diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py index 7f42921d7..ec5c5db4a 100644 --- a/Lib/fontTools/ttLib/tables/otTables.py +++ b/Lib/fontTools/ttLib/tables/otTables.py @@ -1331,13 +1331,14 @@ class Paint(getFormatSwitchingBaseTableClass("uint8")): PaintSolid = 2 PaintLinearGradient = 3 PaintRadialGradient = 4 - PaintGlyph = 5 - PaintColrGlyph = 6 - PaintTransform = 7 - PaintTranslate = 8 - PaintRotate = 9 - PaintSkew = 10 - PaintComposite = 11 + PaintSweepGradient = 5 + PaintGlyph = 6 + PaintColrGlyph = 7 + PaintTransform = 8 + PaintTranslate = 9 + PaintRotate = 10 + PaintSkew = 11 + PaintComposite = 12 def getFormatName(self): try: diff --git a/Tests/colorLib/builder_test.py b/Tests/colorLib/builder_test.py index 43ec96a41..fae00a419 100644 --- a/Tests/colorLib/builder_test.py +++ b/Tests/colorLib/builder_test.py @@ -545,10 +545,10 @@ def test_buildPaintComposite(): composite = layerBuilder.buildPaintComposite( mode=ot.CompositeMode.SRC_OVER, source={ - "format": 11, + "format": 12, "mode": "src_over", - "source": {"format": 5, "glyph": "c", "paint": 2}, - "backdrop": {"format": 5, "glyph": "b", "paint": 1}, + "source": {"format": 6, "glyph": "c", "paint": 2}, + "backdrop": {"format": 6, "glyph": "b", "paint": 1}, }, backdrop=layerBuilder.buildPaintGlyph( "a", layerBuilder.buildPaintSolid(paletteIndex=0, alpha=1.0) @@ -679,7 +679,7 @@ def test_buildColrV1_more_than_255_paints(): colorGlyphs = { "a": [ { - "format": 5, # PaintGlyph + "format": 6, # PaintGlyph "paint": 0, "glyph": name, } @@ -775,18 +775,18 @@ def assertNoV0Content(colr): def test_build_layerv1list_empty(): - # Nobody uses PaintColrLayers (format 8), no layerlist + # Nobody uses PaintColrLayers (format 1), no layerlist colr = builder.buildCOLR( { "a": { - "format": 5, # PaintGlyph + "format": 6, # PaintGlyph "paint": {"format": 2, "paletteIndex": 2, "alpha": 0.8}, "glyph": "b", }, # A list of 1 shouldn't become a PaintColrLayers "b": [ { - "format": 5, # PaintGlyph + "format": 6, # PaintGlyph "paint": { "format": 3, "colorLine": { @@ -832,17 +832,17 @@ def test_build_layerv1list_simple(): # All layers use the same solid paint solid_paint = {"format": 2, "paletteIndex": 2, "alpha": 0.8} backdrop = { - "format": 5, # PaintGlyph + "format": 6, # PaintGlyph "paint": solid_paint, "glyph": "back", } a_foreground = { - "format": 5, # PaintGlyph + "format": 6, # PaintGlyph "paint": solid_paint, "glyph": "a_fore", } b_foreground = { - "format": 5, # PaintGlyph + "format": 6, # PaintGlyph "paint": solid_paint, "glyph": "b_fore", } @@ -882,33 +882,33 @@ def test_build_layerv1list_with_sharing(): solid_paint = {"format": 2, "paletteIndex": 2, "alpha": 0.8} backdrop = [ { - "format": 5, # PaintGlyph + "format": 6, # PaintGlyph "paint": solid_paint, "glyph": "back1", }, { - "format": 5, # PaintGlyph + "format": 6, # PaintGlyph "paint": solid_paint, "glyph": "back2", }, ] a_foreground = { - "format": 5, # PaintGlyph + "format": 6, # PaintGlyph "paint": solid_paint, "glyph": "a_fore", } b_background = { - "format": 5, # PaintGlyph + "format": 6, # PaintGlyph "paint": solid_paint, "glyph": "b_back", } b_foreground = { - "format": 5, # PaintGlyph + "format": 6, # PaintGlyph "paint": solid_paint, "glyph": "b_fore", } c_background = { - "format": 5, # PaintGlyph + "format": 6, # PaintGlyph "paint": solid_paint, "glyph": "c_back", } @@ -951,7 +951,7 @@ def test_build_layerv1list_with_sharing(): def test_build_layerv1list_with_overlaps(): paints = [ { - "format": 5, # PaintGlyph + "format": 6, # PaintGlyph "paint": {"format": 2, "paletteIndex": 2, "alpha": 0.8}, "glyph": c, } diff --git a/Tests/ttLib/tables/C_O_L_R_test.py b/Tests/ttLib/tables/C_O_L_R_test.py index 7f3f71ea2..b5ee9f5f1 100644 --- a/Tests/ttLib/tables/C_O_L_R_test.py +++ b/Tests/ttLib/tables/C_O_L_R_test.py @@ -131,11 +131,11 @@ COLR_V1_SAMPLE = ( (b"\x01", "BaseGlyphV1Record[0].Paint.Format (1)"), (b"\x04", "BaseGlyphV1Record[0].Paint.NumLayers (4)"), (b"\x00\x00\x00\x00", "BaseGlyphV1Record[0].Paint.FirstLayerIndex (0)"), - (b"\x0B", "BaseGlyphV1Record[1].Paint.Format (11)"), + (b"\x0C", "BaseGlyphV1Record[1].Paint.Format (12)"), (b"\x00\x00<", "Offset to SourcePaint from beginning of PaintComposite (60)"), (b"\x03", "BaseGlyphV1Record[1].Paint.CompositeMode [SRC_OVER] (3)"), (b"\x00\x00\x08", "Offset to BackdropPaint from beginning of PaintComposite (8)"), - (b"\x07", "BaseGlyphV1Record[1].Paint.BackdropPaint.Format (7)"), + (b"\x08", "BaseGlyphV1Record[1].Paint.BackdropPaint.Format (8)"), (b"\x00\x00\x34", "Offset to Paint from beginning of PaintTransform (52)"), (b"\x00\x01\x00\x00\x00\x00\x00\x00", "Affine2x3.xx.value (1.0)"), (b"\x00\x00\x00\x00\x00\x00\x00\x00", "Affine2x3.xy.value (0.0)"), @@ -143,7 +143,7 @@ COLR_V1_SAMPLE = ( (b"\x00\x01\x00\x00\x00\x00\x00\x00", "Affine2x3.yy.value (1.0)"), (b"\x01\x2c\x00\x00\x00\x00\x00\x00", "Affine2x3.dx.value (300.0)"), (b"\x00\x00\x00\x00\x00\x00\x00\x00", "Affine2x3.dy.value (0.0)"), - (b"\x06", "BaseGlyphV1Record[1].Paint.SourcePaint.Format (6)"), + (b"\x07", "BaseGlyphV1Record[1].Paint.SourcePaint.Format (7)"), (b"\x00\n", "BaseGlyphV1Record[1].Paint.SourcePaint.Glyph (10)"), (b"\x00\x00\x00\x04", "LayerV1List.LayerCount (4)"), ( @@ -163,11 +163,11 @@ COLR_V1_SAMPLE = ( "Fourth Offset to Paint table from beginning of LayerV1List (246)", ), # PaintGlyph glyph00011 - (b"\x05", "LayerV1List.Paint[0].Format (5)"), + (b"\x06", "LayerV1List.Paint[0].Format (6)"), (b"\x00\x01<", "Offset24 to Paint subtable from beginning of PaintGlyph (316)"), (b"\x00\x0b", "LayerV1List.Paint[0].Glyph (glyph00011)"), # PaintGlyph glyph00012 - (b"\x05", "LayerV1List.Paint[1].Format (5)"), + (b"\x06", "LayerV1List.Paint[1].Format (6)"), (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"), (b"\x00\x0c", "LayerV1List.Paint[1].Glyph (glyph00012)"), (b"\x03", "LayerV1List.Paint[1].Paint.Format (3)"), @@ -202,10 +202,10 @@ COLR_V1_SAMPLE = ( (b"@\x00", "ColorLine.ColorStop[2].Color.Alpha.value (1.0)"), (b"\x00\x00\x00\x00", "ColorLine.ColorStop[2].Color.Alpha.varIdx (0)"), # PaintGlyph glyph00013 - (b"\x05", "LayerV1List.Paint[2].Format (5)"), + (b"\x06", "LayerV1List.Paint[2].Format (6)"), (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"), (b"\x00\r", "LayerV1List.Paint[2].Glyph (13)"), - (b"\x07", "LayerV1List.Paint[2].Paint.Format (5)"), + (b"\x08", "LayerV1List.Paint[2].Paint.Format (8)"), (b"\x00\x00\x34", "Offset to Paint subtable from beginning of PaintTransform (52)"), (b"\xff\xf3\x00\x00\x00\x00\x00\x00", "Affine2x3.xx.value (-13)"), (b"\x00\x0e\x00\x00\x00\x00\x00\x00", "Affine2x3.xy.value (14)"), @@ -230,25 +230,25 @@ COLR_V1_SAMPLE = ( (b"\x00\x07", "ColorLine.ColorStop[1].Color.PaletteIndex (7)"), (b"\x19\x9a\x00\x00\x00\x00", "ColorLine.ColorStop[1].Color.Alpha.value (0.4)"), # PaintTranslate - (b"\x08", "LayerV1List.Paint[3].Format (8)"), + (b"\x09", "LayerV1List.Paint[3].Format (9)"), (b"\x00\x00\x14", "Offset to Paint subtable from beginning of PaintTranslate (20)"), (b"\x01\x01\x00\x00\x00\x00\x00\x00", "dx.value (257)"), (b"\x01\x02\x00\x00\x00\x00\x00\x00", "dy.value (258)"), # PaintRotate - (b"\x09", "LayerV1List.Paint[3].Paint.Format (9)"), + (b"\x0a", "LayerV1List.Paint[3].Paint.Format (10)"), (b"\x00\x00\x1c", "Offset to Paint subtable from beginning of PaintRotate (28)"), (b"\x00\x2d\x00\x00\x00\x00\x00\x00", "angle.value (45)"), (b"\x00\xff\x00\x00\x00\x00\x00\x00", "centerX.value (255)"), (b"\x01\x00\x00\x00\x00\x00\x00\x00", "centerY.value (256)"), # PaintSkew - (b"\x0a", "LayerV1List.Paint[3].Paint.Paint.Format (10)"), + (b"\x0b", "LayerV1List.Paint[3].Paint.Paint.Format (11)"), (b"\x00\x00\x24", "Offset to Paint subtable from beginning of PaintSkew (36)"), (b"\xff\xf5\x00\x00\x00\x00\x00\x00", "xSkewAngle (-11)"), (b"\x00\x05\x00\x00\x00\x00\x00\x00", "ySkewAngle (5)"), (b"\x00\xfd\x00\x00\x00\x00\x00\x00", "centerX.value (253)"), (b"\x00\xfe\x00\x00\x00\x00\x00\x00", "centerY.value (254)"), # PaintGlyph - (b"\x05", "LayerV1List.Paint[2].Format (5)"), + (b"\x06", "LayerV1List.Paint[2].Format (6)"), (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"), (b"\x00\x0b", "LayerV1List.Paint[2].Glyph (11)"), # PaintSolid @@ -296,13 +296,13 @@ COLR_V1_XML = [ " ", ' ', ' ', - ' ', - ' ', + ' ', + ' ', ' ', " ", ' ', - ' ', - ' ', + ' ', + ' ', ' ', " ", " ", @@ -319,7 +319,7 @@ COLR_V1_XML = [ "", "", " ", - ' ', + ' ', ' ', " ", ' ', @@ -328,7 +328,7 @@ COLR_V1_XML = [ " ", ' ', " ", - ' ', + ' ', ' ', " ", ' ', @@ -364,8 +364,8 @@ COLR_V1_XML = [ " ", ' ', " ", - ' ', - ' ', + ' ', + ' ', ' ', " ", ' ', @@ -403,10 +403,10 @@ COLR_V1_XML = [ " ", ' ', " ", - ' ', - ' ', - ' ', - ' ', + ' ', + ' ', + ' ', + ' ', ' ', " ", ' ', From a7d145f027e136006c29b3fddaf17d03914a5691 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Wed, 3 Feb 2021 19:11:44 +0000 Subject: [PATCH 4/6] update tests for PaintSweepGradient --- Tests/colorLib/builder_test.py | 23 +++++++++ Tests/ttLib/tables/C_O_L_R_test.py | 77 ++++++++++++++++++++++++++---- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/Tests/colorLib/builder_test.py b/Tests/colorLib/builder_test.py index fae00a419..73d6089ca 100644 --- a/Tests/colorLib/builder_test.py +++ b/Tests/colorLib/builder_test.py @@ -385,6 +385,29 @@ def test_buildPaintRadialGradient(): assert gradient.ColorLine.ColorStop == color_stops +def test_buildPaintSweepGradient(): + layerBuilder = LayerV1ListBuilder() + paint = layerBuilder.buildPaintSweepGradient( + colorLine=builder.buildColorLine( + stops=[ + builder.buildColorStop(0.0, 0), + builder.buildColorStop(0.5, 1), + builder.buildColorStop(1.0, 2, alpha=0.8), + ], + ), + centerX=127, + centerY=129, + startAngle=15, + endAngle=42, + ) + + assert paint.Format == ot.Paint.Format.PaintSweepGradient + assert paint.centerX.value == 127 + assert paint.centerY.value == 129 + assert paint.startAngle.value == 15 + assert paint.endAngle.value == 42 + + def test_buildPaintGlyph_Solid(): layerBuilder = LayerV1ListBuilder() layer = layerBuilder.buildPaintGlyph("a", 2) diff --git a/Tests/ttLib/tables/C_O_L_R_test.py b/Tests/ttLib/tables/C_O_L_R_test.py index b5ee9f5f1..0560084be 100644 --- a/Tests/ttLib/tables/C_O_L_R_test.py +++ b/Tests/ttLib/tables/C_O_L_R_test.py @@ -106,7 +106,7 @@ COLR_V1_SAMPLE = ( (b"\x00\x00\x00 ", "Offset to LayerRecordArray from beginning of table (32)"), (b"\x00\x03", "LayerRecordCount (3)"), (b"\x00\x00\x00,", "Offset to BaseGlyphV1List from beginning of table (44)"), - (b"\x00\x00\x00\x81", "Offset to LayerV1List from beginning of table (129)"), + (b"\x00\x00\x00\xcc", "Offset to LayerV1List from beginning of table (204)"), (b"\x00\x00\x00\x00", "Offset to VarStore (NULL)"), (b"\x00\x06", "BaseGlyphRecord[0].BaseGlyph (6)"), (b"\x00\x00", "BaseGlyphRecord[0].FirstLayerIndex (0)"), @@ -117,20 +117,28 @@ COLR_V1_SAMPLE = ( (b"\x00\x01", "LayerRecord[1].PaletteIndex (1)"), (b"\x00\t", "LayerRecord[2].LayerGlyph (9)"), (b"\x00\x02", "LayerRecord[2].PaletteIndex (2)"), - (b"\x00\x00\x00\x02", "BaseGlyphV1List.BaseGlyphCount (2)"), + # BaseGlyphV1List + (b"\x00\x00\x00\x03", "BaseGlyphV1List.BaseGlyphCount (3)"), (b"\x00\n", "BaseGlyphV1List.BaseGlyphV1Record[0].BaseGlyph (10)"), - ( - b"\x00\x00\x00\x10", - "Offset to Paint table from beginning of BaseGlyphV1List (16)", - ), - (b"\x00\x0e", "BaseGlyphV1List.BaseGlyphV1Record[1].BaseGlyph (14)"), ( b"\x00\x00\x00\x16", "Offset to Paint table from beginning of BaseGlyphV1List (22)", ), + (b"\x00\x0e", "BaseGlyphV1List.BaseGlyphV1Record[1].BaseGlyph (14)"), + ( + b"\x00\x00\x00\x1c", + "Offset to Paint table from beginning of BaseGlyphV1List (28)", + ), + (b"\x00\x0f", "BaseGlyphV1List.BaseGlyphV1Record[2].BaseGlyph (15)"), + ( + b"\x00\x00\x00\x5b", + "Offset to Paint table from beginning of BaseGlyphV1List (91)", + ), + # BaseGlyphV1Record[0] (b"\x01", "BaseGlyphV1Record[0].Paint.Format (1)"), (b"\x04", "BaseGlyphV1Record[0].Paint.NumLayers (4)"), (b"\x00\x00\x00\x00", "BaseGlyphV1Record[0].Paint.FirstLayerIndex (0)"), + # BaseGlyphV1Record[1] (b"\x0C", "BaseGlyphV1Record[1].Paint.Format (12)"), (b"\x00\x00<", "Offset to SourcePaint from beginning of PaintComposite (60)"), (b"\x03", "BaseGlyphV1Record[1].Paint.CompositeMode [SRC_OVER] (3)"), @@ -145,6 +153,29 @@ COLR_V1_SAMPLE = ( (b"\x00\x00\x00\x00\x00\x00\x00\x00", "Affine2x3.dy.value (0.0)"), (b"\x07", "BaseGlyphV1Record[1].Paint.SourcePaint.Format (7)"), (b"\x00\n", "BaseGlyphV1Record[1].Paint.SourcePaint.Glyph (10)"), + # BaseGlyphV1Record[2] + (b"\x06", "BaseGlyphV1Record[2].Paint.Format (6)"), + (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"), + (b"\x00\x0b", "BaseGlyphV1Record[2].Paint.Glyph (11)"), + (b"\x05", "BaseGlyphV1Record[2].Paint.Paint.Format (5)"), + (b"\x00\x00 ", "Offset to ColorLine from beginning of PaintSweepGradient (32)"), + (b"\x01\x03\x00\x00\x00\x00", "centerX.value (259)"), + (b"\x01\x2c\x00\x00\x00\x00", "centerY.value (300)"), + (b"\x00\x2d\x00\x00\x00\x00\x00\x00", "startAngle (45.0)"), + (b"\x00\x87\x00\x00\x00\x00\x00\x00", "endAngle (135.0)"), + (b"\x00", "ColorLine.Extend (0; pad)"), + (b"\x00\x02", "ColorLine.StopCount (2)"), + (b"\x00\x00", "ColorLine.ColorStop[0].StopOffset.value (0.0)"), + (b"\x00\x00\x00\x00", "ColorLine.ColorStop[0].StopOffset.varIdx (0)"), + (b"\x00\x03", "ColorLine.ColorStop[0].Color.PaletteIndex (3)"), + (b"@\x00", "ColorLine.ColorStop[0].Color.Alpha.value (1.0)"), + (b"\x00\x00\x00\x00", "ColorLine.ColorStop[0].Color.Alpha.varIdx (0)"), + (b"@\x00", "ColorLine.ColorStop[1].StopOffset.value (1.0)"), + (b"\x00\x00\x00\x00", "ColorLine.ColorStop[1].StopOffset.varIdx (0)"), + (b"\x00\x05", "ColorLine.ColorStop[1].Color.PaletteIndex (5)"), + (b"@\x00", "ColorLine.ColorStop[1].Color.Alpha.value (1.0)"), + (b"\x00\x00\x00\x00", "ColorLine.ColorStop[1].Color.Alpha.varIdx (0)"), + # LayerV1List (b"\x00\x00\x00\x04", "LayerV1List.LayerCount (4)"), ( b"\x00\x00\x00\x14", @@ -286,7 +317,7 @@ COLR_V1_XML = [ "", "", "", - " ", + " ", ' ', ' ', ' ', @@ -316,6 +347,36 @@ COLR_V1_XML = [ " ", " ", " ", + ' ', + ' ', + ' ', + ' ', + " ", + ' ', + " ", + ' ', + ' ', + " ", + ' ', + ' ', + " ", + " ", + ' ', + ' ', + " ", + ' ', + ' ', + " ", + " ", + " ", + ' ', + ' ', + ' ', + ' ', + " ", + ' ', + " ", + " ", "", "", " ", From f416a5cb175d40e63ba5e0e482a1d01e25de285f Mon Sep 17 00:00:00 2001 From: justvanrossum Date: Wed, 3 Feb 2021 20:24:04 +0100 Subject: [PATCH 5/6] fix Vector division --- Lib/fontTools/misc/arrayTools.py | 4 ++-- Tests/misc/arrayTools_test.py | 13 ++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Lib/fontTools/misc/arrayTools.py b/Lib/fontTools/misc/arrayTools.py index 81b2418dc..e76ced7f8 100644 --- a/Lib/fontTools/misc/arrayTools.py +++ b/Lib/fontTools/misc/arrayTools.py @@ -313,9 +313,9 @@ class Vector(object): __rmul__ = __mul__ def __truediv__(self, other): - return Vector(self._scalarOp(other, operator.div), keep=True) + return Vector(self._scalarOp(other, operator.truediv), keep=True) def __itruediv__(self, other): - self.values = self._scalarOp(other, operator.div) + self.values = self._scalarOp(other, operator.truediv) return self def __pos__(self): diff --git a/Tests/misc/arrayTools_test.py b/Tests/misc/arrayTools_test.py index 127f153c8..73e0ab17e 100644 --- a/Tests/misc/arrayTools_test.py +++ b/Tests/misc/arrayTools_test.py @@ -1,7 +1,7 @@ from fontTools.misc.py23 import * from fontTools.misc.py23 import round3 from fontTools.misc.arrayTools import ( - calcBounds, calcIntBounds, updateBounds, pointInRect, pointsInRect, + Vector, calcBounds, calcIntBounds, updateBounds, pointInRect, pointsInRect, vectorLength, asInt16, normRect, scaleRect, offsetRect, insetRect, sectRect, unionRect, rectCenter, intRect) import math @@ -88,3 +88,14 @@ def test_rectCenter(): def test_intRect(): assert intRect((0.9, 2.9, 3.1, 4.1)) == (0, 2, 4, 5) + + +def test_Vector(): + v = Vector([100, 200]) + assert v == Vector([100, 200]) + assert v == [100, 200] + assert v + Vector([1, 2]) == [101, 202] + assert v - Vector([1, 2]) == [99, 198] + assert v * 2 == [200, 400] + assert v * 0.5 == [50, 100] + assert v / 2 == [50, 100] From a3d13abcffa52445f229b4f39acf556d9abccdc9 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Thu, 4 Feb 2021 11:32:22 +0000 Subject: [PATCH 6/6] otData: fix typo, add comments --- Lib/fontTools/ttLib/tables/otData.py | 34 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/otData.py b/Lib/fontTools/ttLib/tables/otData.py index 59ff40f65..389ac5c42 100755 --- a/Lib/fontTools/ttLib/tables/otData.py +++ b/Lib/fontTools/ttLib/tables/otData.py @@ -1612,21 +1612,21 @@ otData = [ ('uint16', 'StopCount', None, None, 'Number of Color stops.'), ('ColorStop', 'ColorStop', 'StopCount', 0, 'Array of Color stops.'), ]), - + # PaintColrLayers ('PaintFormat1', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 1'), ('uint8', 'NumLayers', None, None, 'Number of offsets to Paint to read from LayerV1List.'), ('uint32', 'FirstLayerIndex', None, None, 'Index into LayerV1List.'), ]), - + # PaintSolid ('PaintFormat2', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 2'), ('ColorIndex', 'Color', None, None, 'A solid color paint.'), ]), - + # PaintLinearGradient ('PaintFormat3', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 3'), - ('Offset24', 'ColorLine', None, None, 'Offset (from beginning of Paint table) to ColorLine subtable.'), + ('Offset24', 'ColorLine', None, None, 'Offset (from beginning of PaintLinearGradient table) to ColorLine subtable.'), ('VarInt16', 'x0', None, None, ''), ('VarInt16', 'y0', None, None, ''), ('VarInt16', 'x1', None, None, ''), @@ -1634,10 +1634,10 @@ otData = [ ('VarInt16', 'x2', None, None, ''), ('VarInt16', 'y2', None, None, ''), ]), - + # PaintRadialGradient ('PaintFormat4', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 4'), - ('Offset24', 'ColorLine', None, None, 'Offset (from beginning of Paint table) to ColorLine subtable.'), + ('Offset24', 'ColorLine', None, None, 'Offset (from beginning of PaintRadialGradient table) to ColorLine subtable.'), ('VarInt16', 'x0', None, None, ''), ('VarInt16', 'y0', None, None, ''), ('VarUInt16', 'r0', None, None, ''), @@ -1645,40 +1645,40 @@ otData = [ ('VarInt16', 'y1', None, None, ''), ('VarUInt16', 'r1', None, None, ''), ]), - + # PaintSweepGradient ('PaintFormat5', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 5'), - ('Offset24', 'ColorLine', None, None, 'Offset (from beginning of PaintSweep table) to ColorLine subtable.'), + ('Offset24', 'ColorLine', None, None, 'Offset (from beginning of PaintSweepGradient table) to ColorLine subtable.'), ('VarInt16', 'centerX', None, None, 'Center x coordinate.'), ('VarInt16', 'centerY', None, None, 'Center y coordinate.'), ('VarFixed', 'startAngle', None, None, 'Start of the angular range of the gradient.'), ('VarFixed', 'endAngle', None, None, 'End of the angular range of the gradient.'), ]), - + # PaintGlyph ('PaintFormat6', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 6'), ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintGlyph table) to Paint subtable.'), ('GlyphID', 'Glyph', None, None, 'Glyph ID for the source outline.'), ]), - + # PaintColrGlyph ('PaintFormat7', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 7'), ('GlyphID', 'Glyph', None, None, 'Virtual glyph ID for a BaseGlyphV1List base glyph.'), ]), - + # PaintTransform ('PaintFormat8', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 8'), - ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintTransformed table) to Paint subtable.'), - ('Affine2x3', 'Transform', None, None, 'Offset (from beginning of PaintTrasformed table) to Affine2x3 subtable.'), + ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintTransform table) to Paint subtable.'), + ('Affine2x3', 'Transform', None, None, '2x3 matrix for 2D affine transformations.'), ]), - + # PaintTranslate ('PaintFormat9', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 9'), ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintTranslate table) to Paint subtable.'), ('VarFixed', 'dx', None, None, 'Translation in x direction.'), ('VarFixed', 'dy', None, None, 'Translation in y direction.'), ]), - + # PaintRotate ('PaintFormat10', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 10'), ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintRotate table) to Paint subtable.'), @@ -1686,7 +1686,7 @@ otData = [ ('VarFixed', 'centerX', None, None, ''), ('VarFixed', 'centerY', None, None, ''), ]), - + # PaintSkew ('PaintFormat11', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 11'), ('Offset24', 'Paint', None, None, 'Offset (from beginning of PaintSkew table) to Paint subtable.'), @@ -1695,7 +1695,7 @@ otData = [ ('VarFixed', 'centerX', None, None, ''), ('VarFixed', 'centerY', None, None, ''), ]), - + # PaintComposite ('PaintFormat12', [ ('uint8', 'PaintFormat', None, None, 'Format identifier-format = 12'), ('LOffset24To(Paint)', 'SourcePaint', None, None, 'Offset (from beginning of PaintComposite table) to source Paint subtable.'),