Skip to content

Commit

Permalink
Merge pull request #827 from the-type-founders/bs-name-table-entries
Browse files Browse the repository at this point in the history
[review] Add support for roundtripping Glyphs/UFO custom name table entries.
  • Loading branch information
anthrotype authored Nov 7, 2022
2 parents 5ca3c30 + ccc662f commit b116f98
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 13 deletions.
101 changes: 88 additions & 13 deletions Lib/glyphsLib/builder/custom_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,23 +601,98 @@ def to_glyphs_color_palettes(value):
)
)

# TODO: (jany) look at
# https://forum.glyphsapp.com/t/name-table-entry-win-id4/3811/10
# Use Name Table Entry for the next param

class NameRecordParamHandler(AbstractParamHandler):
def to_entry(self, record):
identifiers = [
record["nameID"],
record["platformID"],
record["encodingID"],
record["languageID"],
]
encoding = " ".join(map(str, identifiers))
string = record["string"]

return f"{encoding}; {string}"

def parse_decimal(self, string):
# In Python octal strings must start with a prefix. Glyphs
# uses AFDKO decimal number specification which allows
# octals starting with "0".
if string.startswith("0x"):
return int(string, 16)
elif string.startswith("0"):
return int(string, 8)
else:
return int(string, 10)

# See the Glyphs manual for the Name Table Entry format:
# https://glyphsapp.com/media/pages/learn/3ec528a11c-1634835554/glyphs-3-handbook.pdf
def to_record(self, entry):
# Split only on the first semicolon occurance. Glyphs doesn't
# have any special escaping, so anything after the first
# semicolon is treated as part of the name table entry.
parts = entry.split(";", 1)

if len(parts) != 2:
logger.warning(f"Invalid Name Table Entry '{entry}' ignored.")
else:
identifiers = parts[0].split(" ")
# Strip whitespace. This behaviour is undefined, but
# it seems sensible to remove leading and trailing spaces.
string = parts[1].strip()

try:
name_id = self.parse_decimal(identifiers[0])
platform_id = 3 # Unicode
encoding_id = 1 # Unicode
language_id = 0x49 # Windows English

if len(identifiers) >= 2:
platform_id = self.parse_decimal(identifiers[1])

if len(identifiers) >= 3:
encoding_id = self.parse_decimal(identifiers[2])

if len(identifiers) >= 4:
language_id = self.parse_decimal(identifiers[3])

return {
"nameID": name_id,
"platformID": platform_id,
"encodingID": encoding_id,
"languageID": language_id,
"string": string,
}
except ValueError:
logger.warning(f"Invalid name table identifiers '{parts[0]}'.")

def to_glyphs_opentype_name_records(value):
# In ufoLib2, font.info.openTypeNameRecords is a list of NameRecord objects,
# while in defcon it is a list of dicts; reduce both to dicts.
return [dict(r) for r in value]
def to_glyphs(self, glyphs, ufo):
if glyphs.is_font():
records = ufo.get_info_value("openTypeNameRecords")
if records:
entries = [self.to_entry(record) for record in records]

glyphs.set_custom_values("Name Table Entry", entries)

def to_ufo(self, builder, glyphs, ufo):
if glyphs.is_font():
entries = glyphs.get_custom_values("Name Table Entry")

if entries:
records = []

for entry in entries:
record = self.to_record(entry)

if record is not None:
records.append(record)

ufo.set_info_value("openTypeNameRecords", records)


register(NameRecordParamHandler())

register(
ParamHandler(
glyphs_name="openTypeNameRecords",
value_to_glyphs=to_glyphs_opentype_name_records,
)
)

register(ParamHandler(glyphs_name="Disable Last Change", ufo_name="disablesLastChange"))

Expand Down
2 changes: 2 additions & 0 deletions Lib/glyphsLib/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,8 @@ def __getitem__(self, key):
def _get_parameter_by_key(self, key):
if key == "Axes" and isinstance(self._owner, GSFont):
return self._owner._get_custom_parameter_from_axes()
if key == "Name Table Entry":
return None
for customParameter in self._owner._customParameters:
if customParameter.name == key:
return customParameter
Expand Down
69 changes: 69 additions & 0 deletions tests/builder/custom_params_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,75 @@ def test_meta_table(self):
font = glyphsLib.to_glyphs([self.ufo])
self.assertEqual(font.customParameters["meta Table"], glyphs_meta)

def test_name_table_entry(self):
self.font.customParameters.append(
GSCustomParameter("Name Table Entry", "1024; FOO; BAZ")
)
self.font.customParameters.append(
GSCustomParameter("Name Table Entry", "2048 1; FOO")
)
self.font.customParameters.append(
GSCustomParameter("Name Table Entry", "4096 1 2; FOO")
)
self.font.customParameters.append(
GSCustomParameter("Name Table Entry", "8192 1 2 3; FOO")
)
self.font.customParameters.append(
GSCustomParameter("Name Table Entry", "0x4000 074; BAZ")
)

self.set_custom_params()

ufo_records = [
{
"nameID": 1024,
"platformID": 3,
"encodingID": 1,
"languageID": 0x49,
"string": "FOO; BAZ",
},
{
"nameID": 2048,
"platformID": 1,
"encodingID": 1,
"languageID": 0x49,
"string": "FOO",
},
{
"nameID": 4096,
"platformID": 1,
"encodingID": 2,
"languageID": 0x49,
"string": "FOO",
},
{
"nameID": 8192,
"platformID": 1,
"encodingID": 2,
"languageID": 3,
"string": "FOO",
},
{
"nameID": 16384,
"platformID": 60,
"encodingID": 1,
"languageID": 0x49,
"string": "BAZ",
},
]

self.assertEqual(
[dict(r) for r in self.ufo.info.openTypeNameRecords], ufo_records
)

font = glyphsLib.to_glyphs([self.ufo])

self.assertEqual(font.customParameters[0].value, "1024 3 1 73; FOO; BAZ")
self.assertEqual(font.customParameters[1].value, "2048 1 1 73; FOO")
self.assertEqual(font.customParameters[2].value, "4096 1 2 73; FOO")
self.assertEqual(font.customParameters[3].value, "8192 1 2 3; FOO")
self.assertEqual(font.customParameters[4].value, "16384 60 1 73; BAZ")


class SetCustomParamsTestUfoLib2(SetCustomParamsTestBase, unittest.TestCase):
ufo_module = ufoLib2
Expand Down

0 comments on commit b116f98

Please sign in to comment.