From ea4d3eb58f84bced5c3d436ce61c34055ce596b3 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 28 Jul 2023 15:52:16 +0100 Subject: [PATCH 1/2] add allQuadratic=False option to allow building glyf v1 with cubics a mix of cubics or quadratic depending on which is more economical --- Lib/ufo2ft/__init__.py | 11 ++ Lib/ufo2ft/filters/cubicToQuadratic.py | 2 + Lib/ufo2ft/preProcessor.py | 5 + tests/data/TestFont-not-allQuadratic.ttx | 170 ++++++++++++++++++ .../TestVariableFont-TTF-not-allQuadratic.ttx | 118 ++++++++++++ tests/integration_test.py | 19 ++ 6 files changed, 325 insertions(+) create mode 100644 tests/data/TestFont-not-allQuadratic.ttx create mode 100644 tests/data/TestVariableFont-TTF-not-allQuadratic.ttx diff --git a/Lib/ufo2ft/__init__.py b/Lib/ufo2ft/__init__.py index afb0d2ff8..048862a2c 100644 --- a/Lib/ufo2ft/__init__.py +++ b/Lib/ufo2ft/__init__.py @@ -225,6 +225,7 @@ def compileOTF(ufo, **kwargs): flattenComponents=False, autoUseMyMetrics=True, dropImpliedOnCurves=False, + allQuadratic=True, ), } @@ -252,6 +253,10 @@ def compileTTF(ufo, **kwargs): *dropImpliedOnCurves* (bool) specifies whether on-curve points that are exactly in between two off-curves can be dropped when building glyphs (default: False). + + *allQuadratic* (bool) specifies whether to convert all curves to quadratic - True + by default, builds traditional glyf v0 table. If False, quadratic curves or cubic + curves are generated depending on which has fewer points; a glyf v1 is generated. """ kwargs = init_kwargs(kwargs, compileTTF_args) @@ -280,6 +285,7 @@ def compileTTF(ufo, **kwargs): colrAutoClipBoxes=False, extraSubstitutions=None, autoUseMyMetrics=True, + allQuadratic=True, ), } @@ -562,6 +568,7 @@ def compileFeatures( colrAutoClipBoxes=False, autoUseMyMetrics=True, dropImpliedOnCurves=False, + allQuadratic=True, ), } @@ -612,6 +619,10 @@ def compileVariableTTFs(designSpaceDoc: DesignSpaceDocument, **kwargs): to build. If not provided, all variable fonts listed in the given designspace will by built. + *allQuadratic* (bool) specifies whether to convert all curves to quadratic - True + by default, builds traditional glyf v0 table. If False, quadratic curves or cubic + curves are generated depending on which has fewer points; a glyf v1 is generated. + The rest of the arguments works the same as in the other compile functions. Returns a dictionary that maps each variable font filename to a new variable diff --git a/Lib/ufo2ft/filters/cubicToQuadratic.py b/Lib/ufo2ft/filters/cubicToQuadratic.py index b9d492707..36cec26c3 100644 --- a/Lib/ufo2ft/filters/cubicToQuadratic.py +++ b/Lib/ufo2ft/filters/cubicToQuadratic.py @@ -14,6 +14,7 @@ class CubicToQuadraticFilter(BaseFilter): "conversionError": None, "reverseDirection": True, "rememberCurveType": False, + "allQuadratic": True, } def set_context(self, font, glyphSet): @@ -64,6 +65,7 @@ def filter(self, glyph): self.context.absoluteError, reverse_direction=self.options.reverseDirection, stats=self.context.stats, + all_quadratic=self.options.allQuadratic, ) contours = list(glyph) glyph.clearContours() diff --git a/Lib/ufo2ft/preProcessor.py b/Lib/ufo2ft/preProcessor.py index 369d3f6b6..46c2d6ced 100644 --- a/Lib/ufo2ft/preProcessor.py +++ b/Lib/ufo2ft/preProcessor.py @@ -195,6 +195,7 @@ def initDefaultFilters( flattenComponents=False, convertCubics=True, conversionError=None, + allQuadratic=True, reverseDirection=True, rememberCurveType=True, ): @@ -227,6 +228,7 @@ def initDefaultFilters( conversionError=conversionError, reverseDirection=reverseDirection, rememberCurveType=rememberCurveType and self.inplace, + allQuadratic=allQuadratic, ) ) return filters @@ -266,6 +268,7 @@ def __init__( layerNames=None, skipExportGlyphs=None, filters=None, + allQuadratic=True, ): from fontTools.cu2qu.ufo import DEFAULT_MAX_ERR @@ -293,6 +296,7 @@ def __init__( ] self._reverseDirection = reverseDirection self._rememberCurveType = rememberCurveType + self.allQuadratic = allQuadratic self.defaultFilters = [] for ufo in ufos: @@ -336,6 +340,7 @@ def process(self): reverse_direction=self._reverseDirection, dump_stats=True, remember_curve_type=self._rememberCurveType and self.inplace, + all_quadratic=self.allQuadratic, ) # TrueType fonts cannot mix contours and components, so pick out all glyphs diff --git a/tests/data/TestFont-not-allQuadratic.ttx b/tests/data/TestFont-not-allQuadratic.ttx new file mode 100644 index 000000000..94af884e0 --- /dev/null +++ b/tests/data/TestFont-not-allQuadratic.ttx @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/TestVariableFont-TTF-not-allQuadratic.ttx b/tests/data/TestVariableFont-TTF-not-allQuadratic.ttx new file mode 100644 index 000000000..7f730bc0e --- /dev/null +++ b/tests/data/TestVariableFont-TTF-not-allQuadratic.ttx @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/integration_test.py b/tests/integration_test.py index 7a4a75532..ed10b374f 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -443,6 +443,25 @@ def test_compile_overloaded_codepoints(self, FontClass, compileFunc): ): _ = compileFunc(ufo) + def test_compileTTF_not_allQuadratic(self, testufo): + ttf = compileTTF(testufo, allQuadratic=False) + expectTTX(ttf, "TestFont-not-allQuadratic.ttx", tables=["glyf"]) + + def test_compileVariableTTF_not_allQuadratic(self, designspace): + base_master = designspace.findDefault() + assert base_master is not None + # add a glyph with some curveTo to exercise the cu2qu codepath + glyph = base_master.font.newGlyph("curved") + glyph.width = 1000 + pen = glyph.getPen() + pen.moveTo((500, 0)) + pen.curveTo((500, 277.614), (388.072, 500), (250, 500)) + pen.curveTo((111.928, 500), (0, 277.614), (0, 0)) + pen.closePath() + + vf = compileVariableTTF(designspace, allQuadratic=False) + expectTTX(vf, "TestVariableFont-TTF-not-allQuadratic.ttx", tables=["glyf"]) + if __name__ == "__main__": sys.exit(pytest.main(sys.argv)) From fc1d8d3f5d23c3d50c133794281274476beeb0d3 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 28 Jul 2023 16:28:32 +0100 Subject: [PATCH 2/2] set head.glyphDataFormat=1 if not allQuadratic --- Lib/ufo2ft/__init__.py | 6 +++++- Lib/ufo2ft/outlineCompiler.py | 4 +++- tests/integration_test.py | 8 ++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Lib/ufo2ft/__init__.py b/Lib/ufo2ft/__init__.py index 048862a2c..7ac1e5434 100644 --- a/Lib/ufo2ft/__init__.py +++ b/Lib/ufo2ft/__init__.py @@ -263,7 +263,10 @@ def compileTTF(ufo, **kwargs): glyphSet = call_preprocessor(ufo, **kwargs) logger.info("Building OpenType tables") - otf = call_outline_compiler(ufo, glyphSet, **kwargs) + + otf = call_outline_compiler( + ufo, glyphSet, glyphDataFormat=(0 if kwargs["allQuadratic"] else 1), **kwargs + ) # Only the default layer is likely to have all glyphs used in feature code. if kwargs["layerName"] is None: @@ -333,6 +336,7 @@ def compileInterpolatableTTFs(ufos, **kwargs): ufo, glyphSet, **kwargs, + glyphDataFormat=(0 if kwargs["allQuadratic"] else 1), tables=SPARSE_TTF_MASTER_TABLES if layerName else None, # we want to keep coordinates as floats in glyf masters so that fonttools # can compute impliable on-curve points from unrounded coordinates before diff --git a/Lib/ufo2ft/outlineCompiler.py b/Lib/ufo2ft/outlineCompiler.py index 14b7bfed4..28771a0bc 100644 --- a/Lib/ufo2ft/outlineCompiler.py +++ b/Lib/ufo2ft/outlineCompiler.py @@ -363,7 +363,7 @@ def setupTable_head(self): ) head.fontDirectionHint = 2 head.indexToLocFormat = 0 - head.glyphDataFormat = 0 + head.glyphDataFormat = getattr(self, "glyphDataFormat", 0) def setupTable_name(self): """ @@ -1440,6 +1440,7 @@ def __init__( dropImpliedOnCurves=False, autoUseMyMetrics=True, roundCoordinates=True, + glyphDataFormat=0, ): super().__init__( font, @@ -1454,6 +1455,7 @@ def __init__( self.autoUseMyMetrics = autoUseMyMetrics self.dropImpliedOnCurves = dropImpliedOnCurves self.roundCoordinates = roundCoordinates + self.glyphDataFormat = glyphDataFormat def compileGlyphs(self): """Compile and return the TrueType glyphs for this font.""" diff --git a/tests/integration_test.py b/tests/integration_test.py index ed10b374f..06bc90a67 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -443,11 +443,13 @@ def test_compile_overloaded_codepoints(self, FontClass, compileFunc): ): _ = compileFunc(ufo) - def test_compileTTF_not_allQuadratic(self, testufo): + def test_compileTTF_glyf1_not_allQuadratic(self, testufo): ttf = compileTTF(testufo, allQuadratic=False) expectTTX(ttf, "TestFont-not-allQuadratic.ttx", tables=["glyf"]) - def test_compileVariableTTF_not_allQuadratic(self, designspace): + assert ttf["head"].glyphDataFormat == 1 + + def test_compileVariableTTF_glyf1_not_allQuadratic(self, designspace): base_master = designspace.findDefault() assert base_master is not None # add a glyph with some curveTo to exercise the cu2qu codepath @@ -462,6 +464,8 @@ def test_compileVariableTTF_not_allQuadratic(self, designspace): vf = compileVariableTTF(designspace, allQuadratic=False) expectTTX(vf, "TestVariableFont-TTF-not-allQuadratic.ttx", tables=["glyf"]) + assert vf["head"].glyphDataFormat == 1 + if __name__ == "__main__": sys.exit(pytest.main(sys.argv))