From 1eba7667fe9937912455adfa264fa4b73ca94e96 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Wed, 20 Nov 2024 10:06:33 +0000 Subject: [PATCH 1/4] Disable JSON structure when using fontc --- Lib/gftools/builder/__init__.py | 7 ++- Lib/gftools/builder/operations/__init__.py | 5 +++ .../operations/fontc/fontcAddSubset.py | 43 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 Lib/gftools/builder/operations/fontc/fontcAddSubset.py diff --git a/Lib/gftools/builder/__init__.py b/Lib/gftools/builder/__init__.py index e030da56..99413997 100644 --- a/Lib/gftools/builder/__init__.py +++ b/Lib/gftools/builder/__init__.py @@ -37,6 +37,7 @@ def edge_with_operation(node, operation): class GFBuilder: config: dict recipe: Recipe + fontc_args: FontcArgs def __init__( self, @@ -60,6 +61,7 @@ def __init__( else: self._orig_config = yaml.dump(config) self.config = config + self.fontc_args = fontc_args fontc_args.modify_config(self.config) self.known_operations = OperationRegistry(use_fontc=fontc_args.use_fontc) @@ -150,6 +152,9 @@ def glyphs_to_ufo(self, source): glyph_data = self.config.get("glyphData") if glyph_data is not None: glyph_data = glyph_data + ufo_structure = "json" + if self.fontc_args.use_fontc: + ufo_structure = "package" FontProject().run_from_glyphs( str(source.resolve()), **{ @@ -157,7 +162,7 @@ def glyphs_to_ufo(self, source): "output_dir": directory, "master_dir": directory, "designspace_path": output, - "ufo_structure": "json", + "ufo_structure": ufo_structure, "glyph_data": glyph_data, }, ) diff --git a/Lib/gftools/builder/operations/__init__.py b/Lib/gftools/builder/operations/__init__.py index ca4d3cd0..24f32fcd 100644 --- a/Lib/gftools/builder/operations/__init__.py +++ b/Lib/gftools/builder/operations/__init__.py @@ -173,6 +173,11 @@ def get(self, operation_name: str): return FontcBuildOTF + if operation_name == "addSubset": + from .fontc.fontcAddSubset import FontcAddSubset + + return FontcAddSubset + return self.known_operations.get(operation_name) diff --git a/Lib/gftools/builder/operations/fontc/fontcAddSubset.py b/Lib/gftools/builder/operations/fontc/fontcAddSubset.py new file mode 100644 index 00000000..251365ac --- /dev/null +++ b/Lib/gftools/builder/operations/fontc/fontcAddSubset.py @@ -0,0 +1,43 @@ +import os +from tempfile import NamedTemporaryFile, TemporaryDirectory + +import yaml + +from gftools.builder.file import File +from gftools.builder.operations import OperationBase + + +class FontcAddSubset(OperationBase): + description = "Add a subset from another font" + rule = "gftools-add-ds-subsets $args -y $yaml -o $out $in" + + def validate(self): + # Ensure there is a new name + if not self.first_source.is_font_source: + raise ValueError("%s is not a font source file" % self.first_source) + if "subsets" not in self.original: + raise ValueError("No subsets defined") + + def convert_dependencies(self, graph): + self._target = TemporaryDirectory() # Stow object + self._orig = NamedTemporaryFile(delete=False, mode="w") + yaml.dump(self.original["subsets"], self._orig) + self._orig.close() + + @property + def targets(self): + if "directory" in self.original: + target = self.original["directory"] + else: + target = self._target.name + dspath = os.path.join( + target, self.first_source.basename.rsplit(".", 1)[0] + ".designspace" + ) + return [File(dspath)] + + @property + def variables(self): + return { + "yaml": self._orig.name, + "args": self.original.get("args"), + } From 97f71a910ea7a4f0917fed3f594b14d850bc8a08 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Wed, 20 Nov 2024 10:12:03 +0000 Subject: [PATCH 2/4] Small graphing fix; ninja command line order is important --- Lib/gftools/builder/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/gftools/builder/__init__.py b/Lib/gftools/builder/__init__.py index 99413997..3e66ee5c 100644 --- a/Lib/gftools/builder/__init__.py +++ b/Lib/gftools/builder/__init__.py @@ -343,7 +343,7 @@ def draw_graph(self): import pydot dot = subprocess.run( - ["ninja", "-t", "graph", "-f", self.ninja_file_name], capture_output=True + ["ninja", "-f", self.ninja_file_name, "-t", "graph"], capture_output=True ) graphs = pydot.graph_from_dot_data(dot.stdout.decode("utf-8")) targets = self.recipe.keys() From f1f5346cde25e0faaff31e69a28df07933d6687e Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Wed, 20 Nov 2024 10:30:54 +0000 Subject: [PATCH 3/4] Get static building working again --- Lib/gftools/builder/fontc.py | 5 +- Lib/gftools/builder/operations/__init__.py | 5 ++ .../operations/fontc/fontcInstantiateUfo.py | 70 +++++++++++++++++++ .../builder/recipeproviders/googlefonts.py | 3 +- Lib/gftools/builder/recipeproviders/noto.py | 2 +- 5 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 Lib/gftools/builder/operations/fontc/fontcInstantiateUfo.py diff --git a/Lib/gftools/builder/fontc.py b/Lib/gftools/builder/fontc.py index b9b2ed1b..50cb9ede 100644 --- a/Lib/gftools/builder/fontc.py +++ b/Lib/gftools/builder/fontc.py @@ -69,13 +69,10 @@ def modify_config(self, config: dict): extra_args = config.get("extraFontmakeArgs") or "" extra_args += " --no-production-names --drop-implied-oncurves" config["extraFontmakeArgs"] = extra_args - # override config to turn not build instances if we're variable - if self.will_build_variable_font(config): - config["buildStatic"] = False # if the font doesn't explicitly request CFF, just build TT outlines # if the font _only_ wants CFF outlines, we will try to build them # ( but fail on fontc for now) (but is this even a thing?) - elif config.get("buildTTF", True): + if config.get("buildTTF", True): config["buildOTF"] = False if self.simple_output_path: output_dir = str(self.simple_output_path) diff --git a/Lib/gftools/builder/operations/__init__.py b/Lib/gftools/builder/operations/__init__.py index 24f32fcd..027cc5cf 100644 --- a/Lib/gftools/builder/operations/__init__.py +++ b/Lib/gftools/builder/operations/__init__.py @@ -178,6 +178,11 @@ def get(self, operation_name: str): return FontcAddSubset + if operation_name == "instantiateUfo": + from .fontc.fontcInstantiateUfo import FontcInstantiateUFO + + return FontcInstantiateUFO + return self.known_operations.get(operation_name) diff --git a/Lib/gftools/builder/operations/fontc/fontcInstantiateUfo.py b/Lib/gftools/builder/operations/fontc/fontcInstantiateUfo.py new file mode 100644 index 00000000..5541e58d --- /dev/null +++ b/Lib/gftools/builder/operations/fontc/fontcInstantiateUfo.py @@ -0,0 +1,70 @@ +from pathlib import Path +from typing import List +from gftools.builder.file import File +from gftools.builder.operations import FontmakeOperationBase +import glyphsLib +import os +import gftools.builder +from functools import cached_property +from glyphsLib.builder import UFOBuilder +from ninja.ninja_syntax import Writer, escape_path +from fontTools.designspaceLib import InstanceDescriptor + + +class FontcInstantiateUFO(FontmakeOperationBase): + description = "Create instance UFOs from a Glyphs or designspace file" + rule = 'fontmake -i "$instance_name" -o ufo $fontmake_type $in $args' + + def validate(self): + # Ensure there is an instance name + if "instance_name" not in self.original: + raise ValueError("No instance name specified") + # Ensure the instance is defined in the font + desired = self.original["instance_name"] + if "target" not in self.original and not self.relevant_instance: + raise ValueError( + f"Instance {desired} not found in {self.first_source.path}" + ) + + @cached_property + def relevant_instance(self): + desired = self.original["instance_name"] + relevant_instance = [ + i + for i in self.first_source.instances + if i.name == desired or i.familyName + " " + i.styleName == desired + ] + if len(relevant_instance) == 0: + return None + return relevant_instance[0] + + @property + def instance_dir(self): + return Path(self.first_source.path).parent / "instance_ufos" + + @property + def targets(self): + if "target" in self.original: + return [File(self.original["target"])] + instance = self.relevant_instance + assert instance is not None + assert instance.filename is not None + # if self.first_source.is_glyphs: + return [File(str(self.instance_dir / (os.path.basename(instance.filename))))] + # return [ File(instance.filename) ] + + @property + def variables(self): + vars = super().variables + if self.first_source.is_glyphs: + vars["args"] += f"--instance-dir {escape_path(str(self.instance_dir))}" + else: + vars["args"] += f"--output-dir {escape_path(str(self.instance_dir))}" + vars["instance_name"] = self.original["instance_name"] + if self.original.get("glyphData") is not None: + for glyphData in self.original["glyphData"]: + vars["args"] += f" --glyph-data {escape_path(glyphData)}" + return vars + + def set_target(self, target: File): + raise ValueError("Cannot set target on InstantiateUFO") diff --git a/Lib/gftools/builder/recipeproviders/googlefonts.py b/Lib/gftools/builder/recipeproviders/googlefonts.py index 350328c9..b8b53657 100644 --- a/Lib/gftools/builder/recipeproviders/googlefonts.py +++ b/Lib/gftools/builder/recipeproviders/googlefonts.py @@ -333,8 +333,7 @@ def build_a_static(self, source: File, instance: InstanceDescriptor, output): steps = [ {"source": source.path}, ] - # if we're running fontc we skip conversion to UFO - if not source.is_ufo and not self.config.get("use_fontc", False): + if not source.is_ufo: instancename = instance.name if instancename is None: if not instance.familyName or not instance.styleName: diff --git a/Lib/gftools/builder/recipeproviders/noto.py b/Lib/gftools/builder/recipeproviders/noto.py index d83d9ebc..20c8a72b 100644 --- a/Lib/gftools/builder/recipeproviders/noto.py +++ b/Lib/gftools/builder/recipeproviders/noto.py @@ -277,7 +277,7 @@ def build_a_static(self, source, instance, output): "instance_name": instance.name, "target": "full-designspace/instance_ufos/" + os.path.basename(instance.filename) - + ".json", + + ("" if self.builder.fontc_args.use_fontc else ".json"), }, { "operation": "buildTTF" if output == "ttf" else "buildOTF", From 94298965b1409a4ecee3009c3bffc4451c65cd93 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Wed, 20 Nov 2024 10:52:16 +0000 Subject: [PATCH 4/4] Silence deprecation warnings --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 998bff06..fa19fb82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -185,3 +185,6 @@ extend-exclude = ''' .*_pb2.py # exclude autogenerated Protocol Buffer files anywhere in the project ) ''' + +[tool.pytest.ini_options] +filterwarnings = 'ignore::DeprecationWarning'