Skip to content

Commit

Permalink
Merge pull request #668 from moyogo/custom-glyphdata
Browse files Browse the repository at this point in the history
Allow using custom GlyphData.xml with glyphs2ufo or to_ufos()
  • Loading branch information
khaledhosny authored Nov 28, 2021
2 parents 56cd711 + b06c46a commit bb211c7
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 15 deletions.
1 change: 1 addition & 0 deletions Lib/glyphsLib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def build_masters(
write_skipexportglyphs=False,
ufo_module=None,
minimal=False,
glyph_data=None,
):
"""Write and return UFOs from the masters and the designspace defined in a
.glyphs file.
Expand Down
4 changes: 4 additions & 0 deletions Lib/glyphsLib/builder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def to_ufos(
store_editor_state=True,
write_skipexportglyphs=False,
minimal=False,
glyph_data=None,
):
"""Take a GSFont object and convert it into one UFO per master.
Expand Down Expand Up @@ -60,6 +61,7 @@ def to_ufos(
store_editor_state=store_editor_state,
write_skipexportglyphs=write_skipexportglyphs,
minimal=minimal,
glyph_data=glyph_data,
)

result = list(builder.masters)
Expand All @@ -80,6 +82,7 @@ def to_designspace(
store_editor_state=True,
write_skipexportglyphs=False,
minimal=False,
glyph_data=None,
):
"""Take a GSFont object and convert it into a Designspace Document + UFOS.
The UFOs are available as the attribute `font` of each SourceDescriptor of
Expand Down Expand Up @@ -115,6 +118,7 @@ def to_designspace(
store_editor_state=store_editor_state,
write_skipexportglyphs=write_skipexportglyphs,
minimal=minimal,
glyph_data=glyph_data,
)
return builder.designspace

Expand Down
15 changes: 14 additions & 1 deletion Lib/glyphsLib/builder/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

from fontTools import designspaceLib

from glyphsLib import classes, util
from glyphsLib import classes, glyphdata, util
from .constants import (
PUBLIC_PREFIX,
FONT_CUSTOM_PARAM_PREFIX,
Expand Down Expand Up @@ -73,6 +73,7 @@ def __init__(
store_editor_state=True,
write_skipexportglyphs=False,
minimal=False,
glyph_data=None,
):
"""Create a builder that goes from Glyphs to UFO + designspace.
Expand Down Expand Up @@ -103,6 +104,7 @@ def __init__(
into the UFOs' and Designspace's lib instead
of the glyph level lib key
"com.schriftgestaltung.Glyphs.Export".
glyph_data -- A list of GlyphData.
"""
self.font = font

Expand Down Expand Up @@ -188,6 +190,17 @@ def __init__(
# instances with matching 'familyName' custom parameter
self._do_filter_instances_by_family = True

if glyph_data:
from io import BytesIO

glyphdata_files = []
for path in glyph_data:
with open(path, "rb") as fp:
glyphdata_files.append(BytesIO(fp.read()))
self.glyphdata = glyphdata.GlyphData.from_files(*glyphdata_files)
else:
self.glyphdata = None

def _is_vertical(self):
master_ids = {m.id for m in self.font.masters}
for glyph in self.font.glyphs:
Expand Down
2 changes: 1 addition & 1 deletion Lib/glyphsLib/builder/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def _build_gdef(ufo, skipExportGlyphs=None):
# First check glyph.lib for category/subCategory overrides. Otherwise,
# use global values from GlyphData.
glyphinfo = glyphdata.get_glyph(
glyph.name, [f"{c:04X}" for c in glyph.unicodes]
glyph.name, unicodes=[f"{c:04X}" for c in glyph.unicodes]
)
category = glyph.lib.get(category_key) or glyphinfo.category
subCategory = glyph.lib.get(subCategory_key) or glyphinfo.subCategory
Expand Down
43 changes: 32 additions & 11 deletions Lib/glyphsLib/builder/glyph.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,31 @@ def to_ufo_glyph(self, ufo_glyph, layer, glyph, do_color_layers=True): # noqa:

# FIXME: (jany) next line should be an API of GSGlyph?
glyphinfo = glyphsLib.glyphdata.get_glyph(ufo_glyph.name)
if glyph.production:
production_name = glyph.production
if self.glyphdata is not None:
custom = glyphsLib.glyphdata.get_glyph(ufo_glyph.name, self.glyphdata)
production_name = glyph.production or (
custom.production_name
if custom.production_name != glyphinfo.production_name
else None
)
category = glyph.category or (
custom.category if custom.category != glyphinfo.category else None
)
subCategory = glyph.subCategory or (
custom.subCategory if custom.subCategory != glyphinfo.subCategory else None
)
script = glyph.script or (
custom.script if custom.script != glyphinfo.script else None
)
else:
production_name, category, subCategory, script = (
glyph.production,
glyph.category,
glyph.subCategory,
glyph.script,
)

if production_name:
# Make sure production names of bracket glyphs also get a BRACKET suffix.
bracket_glyph_name = BRACKET_GLYPH_RE.match(ufo_glyph.name)
prod_bracket_glyph_name = BRACKET_GLYPH_RE.match(production_name)
Expand All @@ -206,21 +229,19 @@ def to_ufo_glyph(self, ufo_glyph, layer, glyph, do_color_layers=True): # noqa:
if value:
ufo_glyph.lib[GLYPHLIB_PREFIX + "glyph." + key] = value

if glyph.script is not None:
ufo_glyph.lib[SCRIPT_LIB_KEY] = glyph.script
if script:
ufo_glyph.lib[SCRIPT_LIB_KEY] = script

# if glyph contains custom 'category' and 'subCategory' overrides, store
# them in the UFO glyph's lib
category = glyph.category
if category is None:
category = glyphinfo.category
else:
if category:
ufo_glyph.lib[GLYPHLIB_PREFIX + "category"] = category
subCategory = glyph.subCategory
if subCategory is None:
subCategory = glyphinfo.subCategory
else:
category = glyphinfo.category
if subCategory:
ufo_glyph.lib[GLYPHLIB_PREFIX + "subCategory"] = subCategory
else:
subCategory = glyphinfo.subCategory

# load width before background, which is loaded with lib data

Expand Down
11 changes: 11 additions & 0 deletions Lib/glyphsLib/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,16 @@ def main(args=None):
"key."
),
)
group = parser_glyphs2ufo.add_argument_group("Glyph data")
group.add_argument(
"--glyph-data",
action="append",
metavar="GLYPHDATA",
help=(
"Custom GlyphData XML file with glyph info (production name, "
"script, category, subCategory, etc.). Can be used more than once."
),
)

parser_ufo2glyphs = subparsers.add_parser("ufo2glyphs", help=ufo2glyphs.__doc__)
parser_ufo2glyphs.set_defaults(func=ufo2glyphs)
Expand Down Expand Up @@ -225,6 +235,7 @@ def glyphs2ufo(options):
write_skipexportglyphs=options.write_public_skip_export_glyphs,
ufo_module=__import__(options.ufo_module),
minimal=options.minimal,
glyph_data=options.glyph_data or None,
)


Expand Down
2 changes: 1 addition & 1 deletion Lib/glyphsLib/glyphdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def from_files(cls, *glyphdata_files):
)


def get_glyph(glyph_name, unicodes=None, data=None):
def get_glyph(glyph_name, data=None, unicodes=None):
"""Return a named tuple (Glyph) containing information derived from a glyph
name akin to GSGlyphInfo.
Expand Down
43 changes: 43 additions & 0 deletions tests/builder/builder_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2202,6 +2202,49 @@ def test_unique_masterid(self):
font_rt = to_glyphs(ufos)
assert len({m.id for m in font_rt.masters}) == 2

def test_custom_glyph_data(self):
font = generate_minimal_font()
for glyph_name in ("A", "foo", "bar", "baz"):
add_glyph(font, glyph_name)
font.glyphs["baz"].production = "bazglyph"
font.glyphs["baz"].category = "Number"
font.glyphs["baz"].subCategory = "Decimal Digit"
font.glyphs["baz"].script = "Arabic"
filename = os.path.join(
os.path.dirname(__file__), "..", "data", "CustomGlyphData.xml"
)
(ufo,) = self.to_ufos(font, minimize_glyphs_diffs=True, glyph_data=[filename])

postscriptNames = ufo.lib.get("public.postscriptNames")
categoryKey = "com.schriftgestaltung.Glyphs.category"
subCategoryKey = "com.schriftgestaltung.Glyphs.subCategory"
scriptKey = "com.schriftgestaltung.Glyphs.script"
assert postscriptNames is not None
# default, only in GlyphData.xml
assert postscriptNames.get("A") is None
lib = ufo["A"].lib
assert lib.get(categoryKey) is None
assert lib.get(subCategoryKey) is None
assert lib.get(scriptKey) is None
# from customGlyphData.xml
lib = ufo["foo"].lib
assert postscriptNames.get("foo") == "fooprod"
assert lib.get(categoryKey) == "Letter"
assert lib.get(subCategoryKey) == "Lowercase"
assert lib.get(scriptKey) == "Latin"
# from CustomGlyphData.xml instead of GlyphData.xml
lib = ufo["bar"].lib
assert postscriptNames.get("bar") == "barprod"
assert lib.get(categoryKey) == "Mark"
assert lib.get(subCategoryKey) == "Nonspacing"
assert lib.get(scriptKey) == "Latin"
# from glyph attributes instead of CustomGlyphData.xml
lib = ufo["baz"].lib
assert postscriptNames.get("baz") == "bazglyph"
assert lib.get(categoryKey) == "Number"
assert lib.get(subCategoryKey) == "Decimal Digit"
assert lib.get(scriptKey) == "Arabic"


class ToUfosTestUfoLib2(ToUfosTestBase, unittest.TestCase):
ufo_module = ufoLib2
Expand Down
6 changes: 6 additions & 0 deletions tests/data/CustomGlyphData.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<glyphData>
<glyph name="foo" category="Letter" subCategory="Lowercase" production="fooprod" script="Latin" />
<glyph name="bar" category="Mark" subCategory="Nonspacing" production="barprod" script="Latin" />
<glyph name="baz" category="Letter" subCategory="Uppercase" production="bazprod" script="Latin" />
</glyphData>
5 changes: 4 additions & 1 deletion tests/glyphdata_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,10 @@ def cat(n):

def test_category_buy_unicode(self):
def cat(n, u):
return get_glyph(n, u).category, get_glyph(n, u).subCategory
return (
get_glyph(n, unicodes=u).category,
get_glyph(n, unicodes=u).subCategory,
)

# "SignU.bn" is a non-standard name not defined in GlyphData.xml
self.assertEqual(cat("SignU.bn", ["09C1"]), ("Mark", "Nonspacing"))
Expand Down

0 comments on commit bb211c7

Please sign in to comment.