Skip to content

Commit

Permalink
Merge pull request #441 from anthrotype/custom-ufo2ft-feature-writers
Browse files Browse the repository at this point in the history
set custom 'append' mode for ufo2ft KernFeatureWriter
  • Loading branch information
anthrotype authored Oct 12, 2018
2 parents 2144aa9 + a2f246a commit cfef8fb
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 4 deletions.
18 changes: 18 additions & 0 deletions Lib/glyphsLib/builder/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,21 @@
}

REVERSE_CODEPAGE_RANGES = {value: key for key, value in CODEPAGE_RANGES.items()}

UFO2FT_FEATURE_WRITERS_KEY = "com.github.googlei18n.ufo2ft.featureWriters"

# ufo2ft KernFeatureWriter default to "skip" mode (i.e. do not write features
# if they are already present), while Glyphs.app always adds its automatic
# kerning to any user written kern lookups. So we need to pass custom "append"
# mode for the ufo2ft KernFeatureWriter whenever the GSFont contain a non-automatic
# 'kern' feature.
# See https://glyphsapp.com/tutorials/contextual-kerning
# NOTE: Even though we use the default "skip" mode for the MarkFeatureWriter,
# we still must include it this custom featureWriters list, as this is used
# instead of the default ufo2ft list of feature writers.
# This also means that if ufo2ft adds new writers to that default list, we
# would need to update this accordingly... :-/
DEFAULT_FEATURE_WRITERS = [
{"class": "KernFeatureWriter", "options": {"mode": "append"}},
{"class": "MarkFeatureWriter", "options": {"mode": "skip"}},
]
27 changes: 26 additions & 1 deletion Lib/glyphsLib/builder/user_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@
import os
import posixpath

from .constants import GLYPHS_PREFIX, GLYPHLIB_PREFIX, PUBLIC_PREFIX
from .constants import (
GLYPHS_PREFIX,
GLYPHLIB_PREFIX,
PUBLIC_PREFIX,
UFO2FT_FEATURE_WRITERS_KEY,
DEFAULT_FEATURE_WRITERS,
)

UFO_DATA_KEY = GLYPHLIB_PREFIX + "ufoData"
FONT_USER_DATA_KEY = GLYPHLIB_PREFIX + "fontUserData"
Expand All @@ -26,12 +32,27 @@
NODE_USER_DATA_KEY = GLYPHLIB_PREFIX + "nodeUserData"


def _has_manual_kern_feature(font):
"""Return true if the GSFont contains a manually written 'kern' feature."""
return any(f for f in font.features if f.name == "kern" and not f.automatic)


def to_designspace_family_user_data(self):
if self.use_designspace:
for key, value in dict(self.font.userData).items():
if _user_data_has_no_special_meaning(key):
self.designspace.lib[key] = value

# only write our custom ufo2ft featureWriters settings if the font
# does have a manually written 'kern' feature; and if the lib key wasn't
# already set in font.userData (in which case we assume the user knows
# what she's doing).
if (
_has_manual_kern_feature(self.font)
and UFO2FT_FEATURE_WRITERS_KEY not in self.designspace.lib
):
self.designspace.lib[UFO2FT_FEATURE_WRITERS_KEY] = DEFAULT_FEATURE_WRITERS


def to_ufo_family_user_data(self, ufo):
"""Set family-wide user data as Glyphs does."""
Expand Down Expand Up @@ -83,6 +104,10 @@ def to_glyphs_family_user_data_from_designspace(self):
"""Set the GSFont userData from the designspace family-wide lib data."""
target_user_data = self.font.userData
for key, value in self.designspace.lib.items():
if key == UFO2FT_FEATURE_WRITERS_KEY and value == DEFAULT_FEATURE_WRITERS:
# if the designspace contains featureWriters settings that are the
# same as glyphsLib default settings, there's no need to store them
continue
if _user_data_has_no_special_meaning(key):
target_user_data[key] = value

Expand Down
4 changes: 4 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ python_classes =
addopts =
-v
-r a
filterwarnings =
default::DeprecationWarning
default::PendingDeprecationWarning
ignore:The 'path':DeprecationWarning:.*defcon.*
65 changes: 63 additions & 2 deletions tests/builder/lib_and_user_data_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@
from fontTools.designspaceLib import DesignSpaceDocument
from glyphsLib import classes
from glyphsLib.types import BinaryData
from glyphsLib.builder.constants import GLYPHLIB_PREFIX

from glyphsLib.builder.constants import (
GLYPHLIB_PREFIX,
UFO2FT_FEATURE_WRITERS_KEY,
DEFAULT_FEATURE_WRITERS,
)
from glyphsLib import to_glyphs, to_ufos, to_designspace


Expand All @@ -49,6 +52,64 @@ def test_designspace_lib_equivalent_to_font_user_data(tmpdir):
assert designspace.lib["designspaceLibKey1"] == "designspaceLibValue1"


def test_default_featureWriters_in_designspace_lib(tmpdir):
"""Test that the glyphsLib custom featureWriters settings (with mode="append")
are exported to the designspace lib whenever a GSFont contains a manual 'kern'
feature. And that they are not imported back to GSFont.userData if they are
the same as the default value.
"""
font = classes.GSFont()
font.masters.append(classes.GSFontMaster())
kern = classes.GSFeature(name="kern", code="pos a b 100;")
font.features.append(kern)

designspace = to_designspace(font)
path = str(tmpdir / "test.designspace")
designspace.write(path)
for source in designspace.sources:
source.font.save(str(tmpdir / source.filename))

designspace2 = DesignSpaceDocument.fromfile(path)

assert UFO2FT_FEATURE_WRITERS_KEY in designspace2.lib
assert designspace2.lib[UFO2FT_FEATURE_WRITERS_KEY] == DEFAULT_FEATURE_WRITERS

font2 = to_glyphs(designspace2)

assert not len(font2.userData)
assert len([f for f in font2.features if f.name == "kern"]) == 1


def test_custom_featureWriters_in_designpace_lib(tmpdir):
"""Test that we can roundtrip custom user-defined ufo2ft featureWriters
settings that are stored in the designspace lib or GSFont.userData.
"""
font = classes.GSFont()
font.masters.append(classes.GSFontMaster())
kern = classes.GSFeature(name="kern", code="pos a b 100;")
font.features.append(kern)
customFeatureWriters = list(DEFAULT_FEATURE_WRITERS) + [
{"class": "MyCustomWriter", "module": "myCustomWriter"}
]
font.userData[UFO2FT_FEATURE_WRITERS_KEY] = customFeatureWriters

designspace = to_designspace(font)
path = str(tmpdir / "test.designspace")
designspace.write(path)
for source in designspace.sources:
source.font.save(str(tmpdir / source.filename))

designspace2 = DesignSpaceDocument.fromfile(path)

assert UFO2FT_FEATURE_WRITERS_KEY in designspace2.lib
assert designspace2.lib[UFO2FT_FEATURE_WRITERS_KEY] == customFeatureWriters

font2 = to_glyphs(designspace2)

assert len(font2.userData) == 1
assert font2.userData[UFO2FT_FEATURE_WRITERS_KEY] == customFeatureWriters


def test_font_user_data_to_ufo_lib():
# This happens only when not building a designspace
# Since there is no designspace.lib to store the font userData,
Expand Down
1 change: 0 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ commands =
python -c 'print(r"hint: run {envdir}/bin/pre-commit or {envdir}/Scripts/pre-commit install to add checks as pre-commit hook")'

[testenv:htmlcov]
basepython = python3.6
deps =
coverage
skip_install = true
Expand Down

0 comments on commit cfef8fb

Please sign in to comment.