From 9196bdd5341cc82698c542ca76b8ba0752d86fe2 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Fri, 22 Sep 2023 12:08:26 +0100 Subject: [PATCH] Use JSON format UFOs for intermediaries, fixes #731 --- Lib/gftools/builder/__init__.py | 2 +- Lib/gftools/builder/file.py | 6 +++-- Lib/gftools/builder/operations/addSubset.py | 2 +- .../builder/operations/instantiateUfo.py | 3 ++- Lib/gftools/builder/recipeproviders/noto.py | 2 +- Lib/gftools/scripts/add_ds_subsets.py | 5 +++- Lib/gftools/subsetmerger.py | 25 ++++++++++--------- Lib/gftools/utils.py | 10 ++++++++ pyproject.toml | 2 +- 9 files changed, 37 insertions(+), 20 deletions(-) diff --git a/Lib/gftools/builder/__init__.py b/Lib/gftools/builder/__init__.py index c0561adfc..e3b9d330f 100644 --- a/Lib/gftools/builder/__init__.py +++ b/Lib/gftools/builder/__init__.py @@ -99,11 +99,11 @@ def glyphs_to_ufo(self, source): FontProject().run_from_glyphs( str(source.resolve()), **{ - "format": ["ufo"], "output": ["ufo"], "output_dir": directory, "master_dir": directory, "designspace_path": output, + "ufo_structure": "json" }, ) return source.with_suffix(".designspace").name diff --git a/Lib/gftools/builder/file.py b/Lib/gftools/builder/file.py index c1e5ff095..d11cfa504 100644 --- a/Lib/gftools/builder/file.py +++ b/Lib/gftools/builder/file.py @@ -6,6 +6,8 @@ from fontTools.designspaceLib import InstanceDescriptor from glyphsLib.builder import UFOBuilder +from gftools.utils import open_ufo + @dataclass class File: @@ -35,7 +37,7 @@ def is_glyphs(self): @property def is_ufo(self): - return self.extension == "ufo" + return self.extension == "ufo" or ".ufo.json" in self.path @property def is_designspace(self): @@ -79,7 +81,7 @@ def family_name(self): elif self.designspace.sources[0].familyName: return self.designspace.sources[0].familyName else: - self.designspace.loadSourceFonts(ufoLib2.Font.open) + self.designspace.loadSourceFonts(open_ufo) self.designspace.sources[0].font.info.familyName return name diff --git a/Lib/gftools/builder/operations/addSubset.py b/Lib/gftools/builder/operations/addSubset.py index 3b5e1e60e..c0882cbf8 100644 --- a/Lib/gftools/builder/operations/addSubset.py +++ b/Lib/gftools/builder/operations/addSubset.py @@ -10,7 +10,7 @@ class AddSubset(OperationBase): description = "Add a subset from another font" - rule = "gftools-add-ds-subsets -y $yaml -o $out $in" + rule = "gftools-add-ds-subsets -j -y $yaml -o $out $in" def validate(self): # Ensure there is a new name diff --git a/Lib/gftools/builder/operations/instantiateUfo.py b/Lib/gftools/builder/operations/instantiateUfo.py index 32a205d2c..07b617457 100644 --- a/Lib/gftools/builder/operations/instantiateUfo.py +++ b/Lib/gftools/builder/operations/instantiateUfo.py @@ -46,12 +46,13 @@ def targets(self): 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(str(self.instance_dir / (os.path.basename(instance.filename)+".json"))) ] # return [ File(instance.filename) ] @property def variables(self): vars = super().variables + vars["fontmake_args"] += " --ufo-structure=json " if self.first_source.is_glyphs: vars["fontmake_args"] += f"--instance-dir {escape_path(str(self.instance_dir))}" else: diff --git a/Lib/gftools/builder/recipeproviders/noto.py b/Lib/gftools/builder/recipeproviders/noto.py index 42a72fde6..59a7ab4f1 100644 --- a/Lib/gftools/builder/recipeproviders/noto.py +++ b/Lib/gftools/builder/recipeproviders/noto.py @@ -218,7 +218,7 @@ def build_a_static(self, source, instance, output): "operation": "instantiateUfo", "instance_name": instance.name, "target": "full-designspace/instance_ufos/" - + os.path.basename(instance.filename), + + os.path.basename(instance.filename)+".json", }, {"operation": "buildTTF" if output == "ttf" else "buildOTF"}, ] diff --git a/Lib/gftools/scripts/add_ds_subsets.py b/Lib/gftools/scripts/add_ds_subsets.py index 05f062d62..2e1067936 100644 --- a/Lib/gftools/scripts/add_ds_subsets.py +++ b/Lib/gftools/scripts/add_ds_subsets.py @@ -78,6 +78,9 @@ def main(args=None): parser.add_argument("--file", "-f", help="Source file within GitHub repository") parser.add_argument("--name", "-n", help="Name of subset to use from glyphset") parser.add_argument("--codepoints", "-c", help="Range of codepoints to subset") + parser.add_argument( + "--json", "-j", action="store_true", help="Use JSON structured UFOs" + ) parser.add_argument("--output", "-o", help="Output designspace file") @@ -122,7 +125,7 @@ def main(args=None): } ) SubsetMerger( - args.input, args.output, subsets, googlefonts=args.googlefonts + args.input, args.output, subsets, googlefonts=args.googlefonts, json=args.json ).add_subsets() diff --git a/Lib/gftools/subsetmerger.py b/Lib/gftools/subsetmerger.py index ffc2cd525..4235270c9 100644 --- a/Lib/gftools/subsetmerger.py +++ b/Lib/gftools/subsetmerger.py @@ -17,7 +17,7 @@ from ufomerge import merge_ufos from gftools.util.styles import STYLE_NAMES -from gftools.utils import download_file +from gftools.utils import download_file, open_ufo logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -92,12 +92,13 @@ def prepare_minimal_subsets(subsets): class SubsetMerger: def __init__( - self, input_ds, output_ds, subsets, googlefonts=False, cache="../subset-files" + self, input_ds, output_ds, subsets, googlefonts=False, cache="../subset-files", json=False ): self.input = input_ds self.output = output_ds self.subsets = prepare_minimal_subsets(subsets) self.googlefonts = googlefonts + self.json = json self.cache_dir = cache self.subset_instances = {} @@ -107,13 +108,6 @@ def add_subsets(self): outpath = Path(self.output).parent added_subsets = False for master in ds.sources: - # Clone the UFO before doing anything clever with it. - newpath = os.path.join(outpath, os.path.basename(master.path)) - original_ufo = ufoLib2.Font.open(master.path) - original_ufo.save(newpath, overwrite=True) - - master.path = newpath - for subset in self.subsets: added_subsets |= self.add_subset(ds, master, subset) if not added_subsets: @@ -137,7 +131,7 @@ def add_subset(self, ds, ds_source, subset): return False # Open it up and send it to ufomerge, using the options supplied. - target_ufo = ufoLib2.Font.open(ds_source.path) + target_ufo = open_ufo(ds_source.path) existing_handling = "skip" if subset.get("force"): existing_handling = "replace" @@ -152,7 +146,14 @@ def add_subset(self, ds, ds_source, subset): existing_handling=existing_handling, layout_handling=layout_handling, ) - target_ufo.save(ds_source.path, overwrite=True) + if self.json: + if not ds_source.path.endswith(".json"): + ds_source.path += ".json" + if ds_source.filename: + ds_source.filename += ".json" + target_ufo.json_dump(open(ds_source.path+".json", "wb")) + else: + target_ufo.save(ds_source.path, overwrite=True) return True def obtain_upstream(self, upstream, location): @@ -187,7 +188,7 @@ def obtain_upstream(self, upstream, location): source_ds = DesignSpaceDocument.fromfile(path) source_ufo = self.find_source_for_location(source_ds, location, font_name) if source_ufo: - return ufoLib2.Font.open(source_ufo.path) + return open_ufo(source_ufo.path) return None def glyphs_to_ufo(self, source, directory=None): diff --git a/Lib/gftools/utils.py b/Lib/gftools/utils.py index d8059bb85..f827c1f50 100644 --- a/Lib/gftools/utils.py +++ b/Lib/gftools/utils.py @@ -19,6 +19,7 @@ import sys import os import shutil +import ufoLib2 import unicodedata from unidecode import unidecode from collections import namedtuple @@ -536,3 +537,12 @@ def primary_script(ttFont, ignore_latin=True): most_common = script_count.most_common(1) if most_common: return most_common[0][0] + + +def open_ufo(path): + if os.path.isdir(path): + return ufoLib2.Font.open(path) + elif path.endswith(".json"): + return ufoLib2.Font.json_load(open(path, "rb")) + else: # Maybe a .ufoz + return ufoLib2.Font.open(path) diff --git a/pyproject.toml b/pyproject.toml index 91b3ba737..ba16128f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ dependencies = [ 'vttlib', 'pygit2', 'strictyaml', - 'fontmake>=3.3.0', + 'fontmake[json]>=3.3.0', 'skia-pathops', 'statmake', 'PyYAML',