From f879bd9d180bb1d4f6eccd76b0f9d947e39d8bdb Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Mon, 29 Jan 2024 08:53:41 +0200 Subject: [PATCH 1/6] Apply DesignSpace variable-font's public.fontInfo lib key to variable fonts --- Lib/ufo2ft/_compilers/baseCompiler.py | 14 +++- Lib/ufo2ft/_compilers/otfCompiler.py | 6 +- Lib/ufo2ft/outlineCompiler.py | 2 +- Lib/ufo2ft/postProcessor.py | 101 +++++++++++++++++++++++++- 4 files changed, 114 insertions(+), 9 deletions(-) diff --git a/Lib/ufo2ft/_compilers/baseCompiler.py b/Lib/ufo2ft/_compilers/baseCompiler.py index 15a24379..c581452e 100644 --- a/Lib/ufo2ft/_compilers/baseCompiler.py +++ b/Lib/ufo2ft/_compilers/baseCompiler.py @@ -97,9 +97,11 @@ def compileOutlines(self, ufo, glyphSet): outlineCompiler = self.outlineCompilerClass(ufo, glyphSet=glyphSet, **kwargs) return outlineCompiler.compile() - def postprocess(self, ttf, ufo, glyphSet): + def postprocess(self, ttf, ufo, glyphSet, info=None): if self.postProcessorClass is not None: - postProcessor = self.postProcessorClass(ttf, ufo, glyphSet=glyphSet) + postProcessor = self.postProcessorClass( + ttf, ufo, glyphSet=glyphSet, info=info + ) kwargs = prune_unknown_kwargs(self.__dict__, postProcessor.process) ttf = postProcessor.process(**kwargs) return ttf @@ -249,7 +251,10 @@ def _compileNeededSources(self, designSpaceDoc): f"No default source; expected default master at {default_location}." f" Found master locations:\n{master_location_descriptions}" ) - vfNameToBaseUfo[vfName] = default_source.font + vfNameToBaseUfo[vfName] = ( + default_source.font, + vfDoc.lib.get("public.fontInfo"), + ) for source in vfDoc.sources: sourcesToCompile.add(source.name) @@ -359,8 +364,9 @@ def compile_variable(self, designSpaceDoc): designSpaceDoc, vfNameToTTFont, originalSources, originalGlyphsets ) for vfName, varfont in list(vfNameToTTFont.items()): + ufo, info = vfNameToBaseUfo[vfName] vfNameToTTFont[vfName] = self.postprocess( - varfont, vfNameToBaseUfo[vfName], glyphSet=None + varfont, ufo, glyphSet=None, info=info ) return vfNameToTTFont diff --git a/Lib/ufo2ft/_compilers/otfCompiler.py b/Lib/ufo2ft/_compilers/otfCompiler.py index 6c76bce7..48bbe3ae 100644 --- a/Lib/ufo2ft/_compilers/otfCompiler.py +++ b/Lib/ufo2ft/_compilers/otfCompiler.py @@ -27,9 +27,11 @@ def compileOutlines(self, ufo, glyphSet): outlineCompiler = self.outlineCompilerClass(ufo, glyphSet=glyphSet, **kwargs) return outlineCompiler.compile() - def postprocess(self, font, ufo, glyphSet): + def postprocess(self, font, ufo, glyphSet, info=None): if self.postProcessorClass is not None: - postProcessor = self.postProcessorClass(font, ufo, glyphSet=glyphSet) + postProcessor = self.postProcessorClass( + font, ufo, glyphSet=glyphSet, info=info + ) kwargs = prune_unknown_kwargs(self.__dict__, postProcessor.process) kwargs["optimizeCFF"] = self.optimizeCFF >= CFFOptimization.SUBROUTINIZE font = postProcessor.process(**kwargs) diff --git a/Lib/ufo2ft/outlineCompiler.py b/Lib/ufo2ft/outlineCompiler.py index bfbd87ab..8bd2fffd 100644 --- a/Lib/ufo2ft/outlineCompiler.py +++ b/Lib/ufo2ft/outlineCompiler.py @@ -643,7 +643,7 @@ def adjustOffset(offset, angle): os2.ulUnicodeRange2 = intListToNum(uniRanges, 32, 32) os2.ulUnicodeRange3 = intListToNum(uniRanges, 64, 32) os2.ulUnicodeRange4 = intListToNum(uniRanges, 96, 32) - else: + elif "cmap" in self.otf: os2.recalcUnicodeRanges(self.otf) # codepage ranges diff --git a/Lib/ufo2ft/postProcessor.py b/Lib/ufo2ft/postProcessor.py index a7c25d18..dd4f01f8 100644 --- a/Lib/ufo2ft/postProcessor.py +++ b/Lib/ufo2ft/postProcessor.py @@ -11,6 +11,7 @@ KEEP_GLYPH_NAMES, USE_PRODUCTION_NAMES, ) +from ufo2ft.outlineCompiler import BaseOutlineCompiler logger = logging.getLogger(__name__) @@ -38,10 +39,10 @@ class SubroutinizerBackend(enum.Enum): 2: SubroutinizerBackend.CFFSUBR, } - def __init__(self, otf, ufo, glyphSet=None): + def __init__(self, otf, ufo, glyphSet=None, info=None): self.ufo = ufo self.glyphSet = glyphSet if glyphSet is not None else ufo - + self.info = info self.otf = otf self._postscriptNames = ufo.lib.get("public.postscriptNames") @@ -103,6 +104,9 @@ def process( self.process_glyph_names(useProductionNames) + if self.info: + self.apply_fontinfo() + return self.otf def process_cff(self, *, optimizeCFF=True, cffVersion=None, subroutinizer=None): @@ -357,6 +361,99 @@ def _subroutinize_with_cffsubr(cls, otf, cffVersion): return cffsubr.subroutinize(otf, cff_version=cffVersion, keep_glyph_names=False) + def apply_fontinfo(self): + """Apply the fontinfo data from the DesignSpace variable-font's lib to + the compiled font.""" + import copy + + # Create a temporary UFO and sets its fontinfo to the union of the main + # UFO's fontinfo and the DesignSpace variable-font’s info. + temp_ufo = type(self.ufo)() + temp_ufo.info = copy.copy(self.ufo.info) + for k, v in self.info.items(): + setattr(temp_ufo.info, k, v) + + # Build a temporary font with the only tables that can be modified with + # fontinfo. + tables = {"head", "hhea", "name", "OS/2", "post"} & set(self.otf.keys()) + compiler = InfoCompiler( + temp_ufo, + glyphSet={}, + glyphOrder=[], + tables=tables, + ) + temp_otf = compiler.compile() + + # Merge the modified data from the temporary font to the main font. + for tag in tables: + temp = temp_otf[tag] + orig = self.otf[tag] + if tag == "name": + temp_names = { + (n.nameID, n.platformID, n.platEncID, n.langID): n + for n in temp.names + } + orig_names = { + (n.nameID, n.platformID, n.platEncID, n.langID): n + for n in orig.names + } + orig_names.update(temp_names) + orig.names = list(orig_names.values()) + continue + + for attr in temp.__dict__: + if attr.startswith("_"): + continue + if attr.startswith("reserved"): + continue + if attr in ("tableTag", "tableVersion"): + continue + if tag == "head" and attr in { + "created", + "modified", + "xMin", + "yMin", + "xMax", + "yMax", + "fontDirectionHint", + "indexToLocFormat", + "glyphDataFormat", + }: + continue + if tag == "hhea" and attr in { + "checksumAdjustment", + "metricDataFormat", + "numberOfHMetrics", + "advanceWidthMax", + "minLeftSideBearing", + "minRightSideBearing", + "xMaxExtent", + }: + continue + if tag == "OS/2" and attr in { + "xAvgCharWidth", + "usMaxContext", + }: + continue + if tag == "post" and attr in {"formatType"}: + continue + + setattr(orig, attr, getattr(temp, attr)) + + +class InfoCompiler(BaseOutlineCompiler): + @staticmethod + def makeMissingRequiredGlyphs(*args, **kwargs): + return + + def makeFontBoundingBox(self): + from ufo2ft.outlineCompiler import EMPTY_BOUNDING_BOX + + return EMPTY_BOUNDING_BOX + + def setupTable_maxp(self): + return + # Adapted from fontTools.cff.specializer.programToCommands # https://github.com/fonttools/fonttools/blob/babca16 From 035751bc2945cec86252de57cf08a6637523e1e7 Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Wed, 31 Jan 2024 19:38:47 +0200 Subject: [PATCH 2/6] Move table merging to InfoCompiler Move InfoCompiler into its own module, move all the info update logic into it, make it explicit what attributes to merge, and handle vhea and gasp tables. --- Lib/ufo2ft/infoCompiler.py | 176 ++++++++++++++++++++++++++++++++++++ Lib/ufo2ft/postProcessor.py | 92 +------------------ 2 files changed, 180 insertions(+), 88 deletions(-) create mode 100644 Lib/ufo2ft/infoCompiler.py diff --git a/Lib/ufo2ft/infoCompiler.py b/Lib/ufo2ft/infoCompiler.py new file mode 100644 index 00000000..d9cc224e --- /dev/null +++ b/Lib/ufo2ft/infoCompiler.py @@ -0,0 +1,176 @@ +""" +InfoCompiler is used to apply fontinfo overrides to an already compiled font. +This is used to apply fontinfo from a DesignSpace variable-font after merging +font sources into final variable font. + +It builds a temporary font with the only the tables that can be modified with +fontinfo, then merge relevant table attributes it into the original font. +""" + +import copy + +from ufo2ft.outlineCompiler import BaseOutlineCompiler + + +class InfoCompiler(BaseOutlineCompiler): + info_tables = frozenset( + [ + "head", + "hhea", + "name", + "OS/2", + "post", + "vhea", + "gasp", + ] + ) + + def __init__(self, otf, ufo, info): + self.orig_otf = otf + tables = self.info_tables & set(otf.keys()) + + # Create a temporary UFO and sets its fontinfo to the union of the main + # UFO's fontinfo and the DesignSpace variable-font’s info. + temp_ufo = type(ufo)() + temp_ufo.info = copy.copy(info) + for k, v in info.items(): + setattr(temp_ufo.info, k, v) + + super().__init__(temp_ufo, tables=tables, glyphSet={}, glyphOrder=[]) + + def compile(self): + super().compile() + if "gasp" in self.tables: + self.setupTable_gasp() + return self.orig_otf + + @staticmethod + def makeMissingRequiredGlyphs(*args, **kwargs): + return + + def makeFontBoundingBox(self): + from ufo2ft.outlineCompiler import EMPTY_BOUNDING_BOX + + return EMPTY_BOUNDING_BOX + + def _set_attrs(self, tag, attrs): + temp = self.otf[tag] + orig = self.orig_otf[tag] + for attr in attrs: + if (value := getattr(temp, attr, None)) is not None: + setattr(orig, attr, value) + + def setupTable_head(self): + super().setupTable_head() + self._set_attrs( + "head", + { + "fontRevision", + "unitsPerEm", + "created", + "macStyle", + "flags", + "lowestRecPPEM", + }, + ) + + def setupTable_hhea(self): + super().setupTable_hhea() + self._set_attrs( + "hhea", + { + "ascent", + "descent", + "lineGap", + "caretSlopeRise", + "caretSlopeRun", + "caretOffset", + }, + ) + + def setupTable_vhea(self): + super().setupTable_vhea() + self._set_attrs( + "vhea", + { + "ascent", + "descent", + "lineGap", + "caretSlopeRise", + "caretSlopeRun", + "caretOffset", + }, + ) + + def setupTable_OS2(self): + super().setupTable_OS2() + self._set_attrs( + "OS/2", + { + "usWeightClass", + "usWidthClass", + "fsType", + "ySubscriptXSize", + "ySubscriptYSize", + "ySubscriptYOffset", + "ySubscriptXOffset", + "ySuperscriptXSize", + "ySuperscriptYSize", + "ySuperscriptYOffset", + "ySuperscriptXOffset", + "yStrikeoutSize", + "yStrikeoutPosition", + "sFamilyClass", + "panose", + "ulUnicodeRange1", + "ulUnicodeRange2", + "ulUnicodeRange3", + "ulUnicodeRange4", + "achVendID", + "fsSelection", + "sTypoAscender", + "sTypoDescender", + "sTypoLineGap", + "usWinAscent", + "usWinDescent", + "ulCodePageRange1", + "ulCodePageRange2", + "sxHeight", + "sCapHeight", + }, + ) + + def setupTable_post(self): + super().setupTable_post() + self._set_attrs( + "post", + { + "italicAngle", + "underlinePosition", + "underlineThickness", + "isFixedPitch", + }, + ) + + def setupTable_name(self): + super().setupTable_name() + temp = self.otf["name"] + orig = self.orig_otf["name"] + temp_names = { + (n.nameID, n.platformID, n.platEncID, n.langID): n for n in temp.names + } + orig_names = { + (n.nameID, n.platformID, n.platEncID, n.langID): n for n in orig.names + } + orig_names.update(temp_names) + orig.names = list(orig_names.values()) + + def setupTable_gasp(self): + from ufo2ft.instructionCompiler import InstructionCompiler + + instructionCompiler = InstructionCompiler(self.ufo, self.otf) + instructionCompiler.setupTable_gasp() + self._set_attrs("gasp", {"gaspRange"}) + + def setupTable_maxp(self): + return diff --git a/Lib/ufo2ft/postProcessor.py b/Lib/ufo2ft/postProcessor.py index dd4f01f8..7b7de419 100644 --- a/Lib/ufo2ft/postProcessor.py +++ b/Lib/ufo2ft/postProcessor.py @@ -11,7 +11,6 @@ KEEP_GLYPH_NAMES, USE_PRODUCTION_NAMES, ) -from ufo2ft.outlineCompiler import BaseOutlineCompiler logger = logging.getLogger(__name__) @@ -364,95 +363,12 @@ def _subroutinize_with_cffsubr(cls, otf, cffVersion): def apply_fontinfo(self): """Apply the fontinfo data from the DesignSpace variable-font's lib to the compiled font.""" - import copy - - # Create a temporary UFO and sets its fontinfo to the union of the main - # UFO's fontinfo and the DesignSpace variable-font’s info. - temp_ufo = type(self.ufo)() - temp_ufo.info = copy.copy(self.ufo.info) - for k, v in self.info.items(): - setattr(temp_ufo.info, k, v) - - # Build a temporary font with the only tables that can be modified with - # fontinfo. - tables = {"head", "hhea", "name", "OS/2", "post"} & set(self.otf.keys()) - compiler = InfoCompiler( - temp_ufo, - glyphSet={}, - glyphOrder=[], - tables=tables, - ) - temp_otf = compiler.compile() - - # Merge the modified data from the temporary font to the main font. - for tag in tables: - temp = temp_otf[tag] - orig = self.otf[tag] - if tag == "name": - temp_names = { - (n.nameID, n.platformID, n.platEncID, n.langID): n - for n in temp.names - } - orig_names = { - (n.nameID, n.platformID, n.platEncID, n.langID): n - for n in orig.names - } - orig_names.update(temp_names) - orig.names = list(orig_names.values()) - continue - - for attr in temp.__dict__: - if attr.startswith("_"): - continue - if attr.startswith("reserved"): - continue - if attr in ("tableTag", "tableVersion"): - continue - if tag == "head" and attr in { - "created", - "modified", - "xMin", - "yMin", - "xMax", - "yMax", - "fontDirectionHint", - "indexToLocFormat", - "glyphDataFormat", - }: - continue - if tag == "hhea" and attr in { - "checksumAdjustment", - "metricDataFormat", - "numberOfHMetrics", - "advanceWidthMax", - "minLeftSideBearing", - "minRightSideBearing", - "xMaxExtent", - }: - continue - if tag == "OS/2" and attr in { - "xAvgCharWidth", - "usMaxContext", - }: - continue - if tag == "post" and attr in {"formatType"}: - continue - - setattr(orig, attr, getattr(temp, attr)) - - -class InfoCompiler(BaseOutlineCompiler): - @staticmethod - def makeMissingRequiredGlyphs(*args, **kwargs): - return - - def makeFontBoundingBox(self): - from ufo2ft.outlineCompiler import EMPTY_BOUNDING_BOX + from ufo2ft.infoCompiler import InfoCompiler - return EMPTY_BOUNDING_BOX + logger.info("Applying variable-font info from DesignSpace lib") - def setupTable_maxp(self): - return + compiler = InfoCompiler(self.otf, self.ufo, self.info) + compiler.compile() # Adapted from fontTools.cff.specializer.programToCommands From 72b89b1b88358cd5b121d5ff87c291c8ed3d28ed Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Thu, 1 Feb 2024 21:44:37 +0200 Subject: [PATCH 3/6] Make overlaying fontinfo actually work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Defcon does not allow setting Font.info, and copying ufoLib2 Info does not seem to actually copy the values. The solutions here seem too convoluted, but I don’t know if there is a simpler alternative. --- Lib/ufo2ft/infoCompiler.py | 17 ++++++++++++----- requirements.txt | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Lib/ufo2ft/infoCompiler.py b/Lib/ufo2ft/infoCompiler.py index d9cc224e..588844bb 100644 --- a/Lib/ufo2ft/infoCompiler.py +++ b/Lib/ufo2ft/infoCompiler.py @@ -7,8 +7,6 @@ fontinfo, then merge relevant table attributes it into the original font. """ -import copy - from ufo2ft.outlineCompiler import BaseOutlineCompiler @@ -32,9 +30,18 @@ def __init__(self, otf, ufo, info): # Create a temporary UFO and sets its fontinfo to the union of the main # UFO's fontinfo and the DesignSpace variable-font’s info. temp_ufo = type(ufo)() - temp_ufo.info = copy.copy(info) - for k, v in info.items(): - setattr(temp_ufo.info, k, v) + if hasattr(ufo.info, "getDataForSerialization"): + # defcon + data = ufo.info.getDataForSerialization() + data.update(info) + temp_ufo.info.setDataFromSerialization(data) + else: + # ufoLib2 + from ufoLib2.converters import structure, unstructure + + data = unstructure(ufo.info) + data.update(info) + temp_ufo.info = structure(data, type(ufo.info)) super().__init__(temp_ufo, tables=tables, glyphSet={}, glyphOrder=[]) diff --git a/requirements.txt b/requirements.txt index de30ba82..679d3b84 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,4 @@ cffsubr==0.3.0 skia-pathops==0.8.0.post1 # alternative UFO implementation -ufoLib2==0.16.0 +ufoLib2[converters]==0.16.0 From 5a57036a0e3ed82a34a8c66e313fe1cb23fab9e8 Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Thu, 1 Feb 2024 22:22:41 +0200 Subject: [PATCH 4/6] Add an integration test for variable-font info --- .../data/TestVarFont-Bold.ufo/fontinfo.plist | 36 ++++++++++++ .../glyphs/alef-ar.fina.glif | 15 +++++ .../glyphs/contents.plist | 10 ++++ .../TestVarFont-Bold.ufo/glyphs/space.glif | 7 +++ .../TestVarFont-Bold.ufo/layercontents.plist | 10 ++++ tests/data/TestVarFont-Bold.ufo/lib.plist | 16 ++++++ .../data/TestVarFont-Bold.ufo/metainfo.plist | 10 ++++ tests/data/TestVarFont-MyFontVF1.ttx | 52 ++++++++++++++++++ tests/data/TestVarFont-MyFontVF2.ttx | 55 +++++++++++++++++++ .../TestVarFont-Regular.ufo/fontinfo.plist | 36 ++++++++++++ .../glyphs/alef-ar.fina.glif | 15 +++++ .../glyphs/contents.plist | 10 ++++ .../TestVarFont-Regular.ufo/glyphs/space.glif | 7 +++ .../layercontents.plist | 10 ++++ tests/data/TestVarFont-Regular.ufo/lib.plist | 16 ++++++ .../TestVarFont-Regular.ufo/metainfo.plist | 10 ++++ tests/data/TestVarFont.designspace | 51 +++++++++++++++++ tests/integration_test.py | 13 +++++ 18 files changed, 379 insertions(+) create mode 100644 tests/data/TestVarFont-Bold.ufo/fontinfo.plist create mode 100644 tests/data/TestVarFont-Bold.ufo/glyphs/alef-ar.fina.glif create mode 100644 tests/data/TestVarFont-Bold.ufo/glyphs/contents.plist create mode 100644 tests/data/TestVarFont-Bold.ufo/glyphs/space.glif create mode 100644 tests/data/TestVarFont-Bold.ufo/layercontents.plist create mode 100644 tests/data/TestVarFont-Bold.ufo/lib.plist create mode 100644 tests/data/TestVarFont-Bold.ufo/metainfo.plist create mode 100644 tests/data/TestVarFont-MyFontVF1.ttx create mode 100644 tests/data/TestVarFont-MyFontVF2.ttx create mode 100644 tests/data/TestVarFont-Regular.ufo/fontinfo.plist create mode 100644 tests/data/TestVarFont-Regular.ufo/glyphs/alef-ar.fina.glif create mode 100644 tests/data/TestVarFont-Regular.ufo/glyphs/contents.plist create mode 100644 tests/data/TestVarFont-Regular.ufo/glyphs/space.glif create mode 100644 tests/data/TestVarFont-Regular.ufo/layercontents.plist create mode 100644 tests/data/TestVarFont-Regular.ufo/lib.plist create mode 100644 tests/data/TestVarFont-Regular.ufo/metainfo.plist create mode 100644 tests/data/TestVarFont.designspace diff --git a/tests/data/TestVarFont-Bold.ufo/fontinfo.plist b/tests/data/TestVarFont-Bold.ufo/fontinfo.plist new file mode 100644 index 00000000..f87d03d9 --- /dev/null +++ b/tests/data/TestVarFont-Bold.ufo/fontinfo.plist @@ -0,0 +1,36 @@ + + + + + ascender + 600 + capHeight + 700 + descender + -400 + familyName + TestVarFont + italicAngle + 0 + openTypeHeadCreated + 2022/07/26 14:49:29 + openTypeOS2Type + + 3 + + postscriptUnderlinePosition + -100 + postscriptUnderlineThickness + 50 + styleName + Bold + unitsPerEm + 1000 + versionMajor + 1 + versionMinor + 0 + xHeight + 500 + + diff --git a/tests/data/TestVarFont-Bold.ufo/glyphs/alef-ar.fina.glif b/tests/data/TestVarFont-Bold.ufo/glyphs/alef-ar.fina.glif new file mode 100644 index 00000000..97154af1 --- /dev/null +++ b/tests/data/TestVarFont-Bold.ufo/glyphs/alef-ar.fina.glif @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/tests/data/TestVarFont-Bold.ufo/glyphs/contents.plist b/tests/data/TestVarFont-Bold.ufo/glyphs/contents.plist new file mode 100644 index 00000000..fc86ae10 --- /dev/null +++ b/tests/data/TestVarFont-Bold.ufo/glyphs/contents.plist @@ -0,0 +1,10 @@ + + + + + alef-ar.fina + alef-ar.fina.glif + space + space.glif + + diff --git a/tests/data/TestVarFont-Bold.ufo/glyphs/space.glif b/tests/data/TestVarFont-Bold.ufo/glyphs/space.glif new file mode 100644 index 00000000..98334812 --- /dev/null +++ b/tests/data/TestVarFont-Bold.ufo/glyphs/space.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/data/TestVarFont-Bold.ufo/layercontents.plist b/tests/data/TestVarFont-Bold.ufo/layercontents.plist new file mode 100644 index 00000000..b9c1a4f2 --- /dev/null +++ b/tests/data/TestVarFont-Bold.ufo/layercontents.plist @@ -0,0 +1,10 @@ + + + + + + public.default + glyphs + + + diff --git a/tests/data/TestVarFont-Bold.ufo/lib.plist b/tests/data/TestVarFont-Bold.ufo/lib.plist new file mode 100644 index 00000000..e22d662f --- /dev/null +++ b/tests/data/TestVarFont-Bold.ufo/lib.plist @@ -0,0 +1,16 @@ + + + + + public.glyphOrder + + space + alef-ar.fina + + public.postscriptNames + + alef-ar.fina + uniFE8E + + + diff --git a/tests/data/TestVarFont-Bold.ufo/metainfo.plist b/tests/data/TestVarFont-Bold.ufo/metainfo.plist new file mode 100644 index 00000000..7b8b34ac --- /dev/null +++ b/tests/data/TestVarFont-Bold.ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + com.github.fonttools.ufoLib + formatVersion + 3 + + diff --git a/tests/data/TestVarFont-MyFontVF1.ttx b/tests/data/TestVarFont-MyFontVF1.ttx new file mode 100644 index 00000000..ba4fd428 --- /dev/null +++ b/tests/data/TestVarFont-MyFontVF1.ttx @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Weight + + + TestVarFont + + + Regular + + + 1.000;NONE;TestVarFont-Regular + + + TestVarFont Regular + + + Version 1.000 + + + TestVarFont-Regular + + + Weight + + + + diff --git a/tests/data/TestVarFont-MyFontVF2.ttx b/tests/data/TestVarFont-MyFontVF2.ttx new file mode 100644 index 00000000..3893a29c --- /dev/null +++ b/tests/data/TestVarFont-MyFontVF2.ttx @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Weight + + + My Font Narrow VF + + + Regular + + + 2.000;NONE;MyFontNarrVF-Regular + + + My Font Narrow VF Regular + + + Version 2.000 + + + MyFontNarrVF-Regular + + + Weight + + + My Font Narrow VF is a registered trademark... + + + + diff --git a/tests/data/TestVarFont-Regular.ufo/fontinfo.plist b/tests/data/TestVarFont-Regular.ufo/fontinfo.plist new file mode 100644 index 00000000..a02c887a --- /dev/null +++ b/tests/data/TestVarFont-Regular.ufo/fontinfo.plist @@ -0,0 +1,36 @@ + + + + + ascender + 600 + capHeight + 0 + descender + -400 + familyName + TestVarFont + italicAngle + 0 + openTypeHeadCreated + 2022/07/26 14:49:29 + openTypeOS2Type + + 3 + + postscriptUnderlinePosition + -100 + postscriptUnderlineThickness + 50 + styleName + Regular + unitsPerEm + 1000 + versionMajor + 1 + versionMinor + 0 + xHeight + 0 + + diff --git a/tests/data/TestVarFont-Regular.ufo/glyphs/alef-ar.fina.glif b/tests/data/TestVarFont-Regular.ufo/glyphs/alef-ar.fina.glif new file mode 100644 index 00000000..5f4e5561 --- /dev/null +++ b/tests/data/TestVarFont-Regular.ufo/glyphs/alef-ar.fina.glif @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/tests/data/TestVarFont-Regular.ufo/glyphs/contents.plist b/tests/data/TestVarFont-Regular.ufo/glyphs/contents.plist new file mode 100644 index 00000000..fc86ae10 --- /dev/null +++ b/tests/data/TestVarFont-Regular.ufo/glyphs/contents.plist @@ -0,0 +1,10 @@ + + + + + alef-ar.fina + alef-ar.fina.glif + space + space.glif + + diff --git a/tests/data/TestVarFont-Regular.ufo/glyphs/space.glif b/tests/data/TestVarFont-Regular.ufo/glyphs/space.glif new file mode 100644 index 00000000..c05cd73f --- /dev/null +++ b/tests/data/TestVarFont-Regular.ufo/glyphs/space.glif @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/data/TestVarFont-Regular.ufo/layercontents.plist b/tests/data/TestVarFont-Regular.ufo/layercontents.plist new file mode 100644 index 00000000..b9c1a4f2 --- /dev/null +++ b/tests/data/TestVarFont-Regular.ufo/layercontents.plist @@ -0,0 +1,10 @@ + + + + + + public.default + glyphs + + + diff --git a/tests/data/TestVarFont-Regular.ufo/lib.plist b/tests/data/TestVarFont-Regular.ufo/lib.plist new file mode 100644 index 00000000..e22d662f --- /dev/null +++ b/tests/data/TestVarFont-Regular.ufo/lib.plist @@ -0,0 +1,16 @@ + + + + + public.glyphOrder + + space + alef-ar.fina + + public.postscriptNames + + alef-ar.fina + uniFE8E + + + diff --git a/tests/data/TestVarFont-Regular.ufo/metainfo.plist b/tests/data/TestVarFont-Regular.ufo/metainfo.plist new file mode 100644 index 00000000..7b8b34ac --- /dev/null +++ b/tests/data/TestVarFont-Regular.ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + com.github.fonttools.ufoLib + formatVersion + 3 + + diff --git a/tests/data/TestVarFont.designspace b/tests/data/TestVarFont.designspace new file mode 100644 index 00000000..b41d9851 --- /dev/null +++ b/tests/data/TestVarFont.designspace @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + public.fontInfo + + familyName + My Font Narrow VF + styleName + Regular + postscriptFontName + MyFontNarrVF-Regular + trademark + My Font Narrow VF is a registered trademark... + versionMajor + 2 + + + + + + diff --git a/tests/integration_test.py b/tests/integration_test.py index 12e87457..a3798d5a 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -579,6 +579,19 @@ def test_compile_variable_font_with_gpos_compression( logged = "Compacting GPOS..." in caplog.text assert logged ^ disabled + @pytest.mark.parametrize( + "compileMethod", [compileVariableTTFs, compileVariableCFF2s] + ) + def test_apply_varfont_info(self, FontClass, compileMethod): + designspace = DesignSpaceDocument.fromfile(getpath("TestVarFont.designspace")) + designspace.loadSourceFonts(FontClass) + + fonts = compileMethod(designspace) + assert len(fonts) == 2 + + expectTTX(fonts["MyFontVF1"], "TestVarFont-MyFontVF1.ttx", ["head", "name"]) + expectTTX(fonts["MyFontVF2"], "TestVarFont-MyFontVF2.ttx", ["head", "name"]) + if __name__ == "__main__": sys.exit(pytest.main(sys.argv)) From 2235c372475fa6a40dd6126315287c970a587088 Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Thu, 1 Feb 2024 23:08:39 +0200 Subject: [PATCH 5/6] Add unit tests for InfoCompiler --- tests/infoCompiler_test.py | 77 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/infoCompiler_test.py diff --git a/tests/infoCompiler_test.py b/tests/infoCompiler_test.py new file mode 100644 index 00000000..66ba6bbe --- /dev/null +++ b/tests/infoCompiler_test.py @@ -0,0 +1,77 @@ +import pytest +from fontTools.ttLib import TTFont + +from ufo2ft.infoCompiler import InfoCompiler + +from .outlineCompiler_test import getpath + + +@pytest.fixture +def testttf(): + font = TTFont() + font.importXML(getpath("TestFont.ttx")) + return font + + +@pytest.fixture +def testufo(FontClass): + font = FontClass(getpath("TestFont.ufo")) + return font + + +class InfoCompilerTest: + + def test_head(self, testttf, testufo): + info = {"versionMajor": 5, "versionMinor": 6} + compiler = InfoCompiler(testttf, testufo, info) + ttf = compiler.compile() + assert ttf["head"].fontRevision == 5.006 + + def test_hhea(self, testttf, testufo): + info = {"openTypeHheaAscender": 100, "openTypeHheaDescender": -200} + compiler = InfoCompiler(testttf, testufo, info) + ttf = compiler.compile() + assert ttf["hhea"].ascent == 100 + assert ttf["hhea"].descent == -200 + + def test_vhea(self, testttf, testufo): + info = { + "openTypeVheaVertTypoAscender": 100, + "openTypeVheaVertTypoDescender": -200, + } + compiler = InfoCompiler(testttf, testufo, info) + ttf = compiler.compile() + assert ttf["vhea"].ascent == 100 + assert ttf["vhea"].descent == -200 + + def test_name(self, testttf, testufo): + info = {"postscriptFontName": "TestFontOverride-Italic"} + compiler = InfoCompiler(testttf, testufo, info) + ttf = compiler.compile() + assert ttf["name"].getDebugName(6) == "TestFontOverride-Italic" + + def test_OS2(self, testttf, testufo): + info = {"openTypeOS2TypoAscender": 100, "openTypeOS2TypoDescender": -200} + compiler = InfoCompiler(testttf, testufo, info) + ttf = compiler.compile() + assert ttf["OS/2"].sTypoAscender == 100 + assert ttf["OS/2"].sTypoDescender == -200 + + def test_post(self, testttf, testufo): + info = {"italicAngle": 30.6} + compiler = InfoCompiler(testttf, testufo, info) + ttf = compiler.compile() + assert ttf["post"].italicAngle == 30.6 + + def test_gasp(self, testttf, testufo): + info = { + "openTypeGaspRangeRecords": [ + { + "rangeMaxPPEM": 8, + "rangeGaspBehavior": [0, 2], + } + ] + } + compiler = InfoCompiler(testttf, testufo, info) + ttf = compiler.compile() + assert ttf["gasp"].gaspRange == {8: 5} From 2020a8dbb21ab9afab4fc8b8fcf34435e6e80db5 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 2 Feb 2024 10:32:02 +0000 Subject: [PATCH 6/6] just do copy(ufo.info) and setattr with ufoLib2 https://github.com/googlefonts/ufo2ft/pull/816/commits/72b89b1b88358cd5b121d5ff87c291c8ed3d28ed#r1475867410 --- Lib/ufo2ft/infoCompiler.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/ufo2ft/infoCompiler.py b/Lib/ufo2ft/infoCompiler.py index 588844bb..aef5a989 100644 --- a/Lib/ufo2ft/infoCompiler.py +++ b/Lib/ufo2ft/infoCompiler.py @@ -7,6 +7,8 @@ fontinfo, then merge relevant table attributes it into the original font. """ +import copy + from ufo2ft.outlineCompiler import BaseOutlineCompiler @@ -37,11 +39,9 @@ def __init__(self, otf, ufo, info): temp_ufo.info.setDataFromSerialization(data) else: # ufoLib2 - from ufoLib2.converters import structure, unstructure - - data = unstructure(ufo.info) - data.update(info) - temp_ufo.info = structure(data, type(ufo.info)) + temp_ufo.info = copy.copy(ufo.info) + for k, v in info.items(): + setattr(temp_ufo.info, k, v) super().__init__(temp_ufo, tables=tables, glyphSet={}, glyphOrder=[])