Implement Cosimo feedback from previous pr

This commit is contained in:
Marc Foley 2021-02-16 09:36:15 +00:00 committed by Cosimo Lupo
parent 2be13d50ac
commit fcc02826b4
No known key found for this signature in database
GPG Key ID: 179A8F0895A02F4F
2 changed files with 55 additions and 107 deletions

View File

@ -1180,11 +1180,11 @@ def instantiateVariableFont(
contours and components, you can pass OverlapMode.REMOVE. Note that this
requires the skia-pathops package (available to pip install).
The overlap parameter only has effect when generating full static instances.
updateFontNames (bool): if True, update the instantiated font's nametable using
updateFontNames (bool): if True, update the instantiated font's name table using
the Axis Value Tables from the STAT table. The name table will be updated so
it conforms to the R/I/B/BI model. If the STAT table is missing or
an Axis Value table is missing for a given axis coordinate, an Error will be
raised.
an Axis Value table is missing for a given axis coordinate, a ValueError will
be raised.
"""
# 'overlap' used to be bool and is now enum; for backward compat keep accepting bool
overlap = OverlapMode(int(overlap))

View File

@ -68,99 +68,38 @@ def pruningUnusedNames(varfont):
def updateNameTable(varfont, axisLimits):
"""Update an instatiated variable font's name table using the Axis
Values from the STAT table.
"""Update an instatiated variable font's name table using the
AxisValues from the STAT table.
The updated name table will conform to the R/I/B/BI naming model.
R/I/B/BI is an acronym for (Regular, Italic, Bold, Bold Italic) font
styles.
This task can be split into two parts:
Task 1: Collect and sort the relevant AxisValues into a new list which
only includes AxisValues whose coordinates match the new default
axis locations. We also skip any AxisValues which are elided.
Task 2: Update the name table's style and family names records using the
AxisValues found in step 1. The MS spec provides further info for applying
the R/I/B/BI model to each name record:
https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids
Example: Updating a partial variable font:
| >>> ttFont = TTFont("OpenSans[wdth,wght].ttf")
| >>> updateNameTable(ttFont, {"wght": AxisRange(400, 900), "wdth": 75})
The name table records will be updated in the following manner:
NameID 1 familyName: "Open Sans" --> "Open Sans Condensed"
NameID 2 subFamilyName: "Regular" --> "Regular"
NameID 3 Unique font identifier: "3.000;GOOG;OpenSans-Regular" --> \
"3.000;GOOG;OpenSans-Condensed"
NameID 4 Full font name: "Open Sans Regular" --> "Open Sans Condensed"
NameID 6 PostScript name: "OpenSans-Regular" --> "OpenSans-Condensed"
NameID 16 Typographic Family name: None --> "Open Sans"
NameID 17 Typographic Subfamily name: None --> "Condensed"
"""
# This task can be split into two parts:
# Task 1: Collecting and sorting the relevant AxisValues:
# 1. First check the variable font has a STAT table and it contains
# AxisValues.
# 2. Create a dictionary which contains the pinned axes from the
# axisLimits dict and for the unpinned axes, we'll use the fvar
# default coordinates e.g
# axisLimits = {"wght": 500, "wdth": AxisRange(75, 100), our dict will
# be {"wght": 500, "wdth": 100} if the width axis has a default of 100.
# 3. Create a new list of AxisValues whose Values match the dict we just
# created.
# 4. Remove any AxisValues from the list which have the
# Elidable_AXIS_VALUE_NAME flag set.
# 5. Remove and sort AxisValues in the list so format 4 AxisValues take
# precedence. According to the MS Spec "if a format 1, format 2 or
# format 3 table has a (nominal) value used in a format 4 table that
# also has values for other axes, the format 4 table, being the more
# specific match, is used",
# https://docs.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-4
# Task 2: Updating a name table's style and family names from a list of
# AxisValues:
# 1. Sort AxisValues into two groups. For the first group, the names must be
# any of the following ["Regular", "Italic", "Bold", "Bold Italic"].
# This group of names is often referred to as "RIBBI" names. For the
# other group, names must be non-RIBBI e.g "Medium Italic", "Condensed"
# etc.
# 2. Repeat the next steps for each name table record platform:
# a. Create new subFamily name and Typographic subFamily name from the
# above groups.
# b. Update nameIDs 1, 2, 3, 4, 6, 16, 17 using the new name created
# in the last step.
#
# Step by step example:
# A variable font which has a width and weight axes.
# AxisValues in font (represented as simplified dicts):
# axisValues = [
# {"name": "Light", "axis": "wght", "value": 300},
# {"name": "Regular", "axis": "wght", "value": 400},
# {"name": "Medium", "axis": "wght", "value": 500},
# {"name": "Bold", "axis": "wght", "value": 600},
# {"name": "Condensed", "axis": "wdth", "value": 75},
# {"name": "Normal", "axis": "wdth", "value": 100, "flags": 0x2},
# ]
# # Let's instantiate a partial font which has a pinned wght axis and an
# unpinned width axis.
# >>> axisLimits = {"wght": 500, "width": AxisRange(75, 100)}
# >>> updateNameTable(varfont, axisLimits)
#
# AxisValues remaining after task 1.3:
# axisValues = [
# {"name": "Medium", "axis": "wght", "value": 500},
# {"name": "Normal", "axis": "wdth", "value": 100, "flags": 0x2}
# ]
#
# AxisValues remaining after completing all 1.x tasks:
# axisValues = [{"name": "Medium", "axis": "wght", "value": 500}]
# The Normal AxisValue is removed because it has the
# Elidable_AXIS_VALUE_NAME flag set.
#
# # AxisValues after separating into two groups in task 2.1:
# ribbiAxisValues = []
# nonRibbiAxisValues = [{"name": "Medium", "axis": "wght", "value": 500}]
#
# # Names created from AxisValues in task 2.2a for Win US English platform:
# subFamilyName = ""
# typoSubFamilyName = "Medium"
#
# NameRecords updated in task 2.2b for Win US English platform:
# NameID 1 familyName: "Open Sans" --> "Open Sans Medium"
# NameID 2 subFamilyName: "Regular" --> "Regular"
# NameID 3 Unique font identifier: "3.000;GOOG;OpenSans-Regular" --> \
# "3.000;GOOG;OpenSans-Medium"
# NameID 4 Full font name: "Open Sans Regular" --> "Open Sans Medium"
# NameID 6 PostScript name: "OpenSans-Regular" --> "OpenSans-Medium"
# NameID 16 Typographic Family name: None --> "Open Sans"
# NameID 17 Typographic Subfamily name: None --> "Medium"
#
# Notes on name table record updates:
# - Typographic names have been added since Medium is a non-Ribbi name.
# - Neither the before or after name records include the Width AxisValue
# names because the "Normal" AxisValue has the
# Elidable_AXIS_VALUE_NAME flag set.
# If we instantiate the same font but pin the wdth axis to 75,
# the "Condensed" AxisValue will be included.
# - For info regarding how RIBBI and non-RIBBI can be constructed see:
# https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids
from . import AxisRange, axisValuesFromAxisLimits
if "STAT" not in varfont:
@ -170,7 +109,7 @@ def updateNameTable(varfont, axisLimits):
raise ValueError("Cannot update name table since there are no STAT Axis Values")
fvar = varfont["fvar"]
# The updated name table must reflect the new 'zero origin' of the font.
# The updated name table will reflect the new 'zero origin' of the font.
# If we're instantiating a partial font, we will populate the unpinned
# axes with their default axis values.
fvarDefaults = {a.axisTag: a.defaultValue for a in fvar.axes}
@ -184,8 +123,8 @@ def updateNameTable(varfont, axisLimits):
axisValueTables = axisValuesFromAxisLimits(stat, defaultAxisCoords)
checkAxisValuesExist(stat, axisValueTables, defaultAxisCoords)
# Remove axis Values which have ELIDABLE_AXIS_VALUE_NAME flag set.
# Axis Values which have this flag enabled won't be visible in
# Ignore axis Values which have ELIDABLE_AXIS_VALUE_NAME flag set.
# AxisValues which have this flag enabled won't be visible in
# application font menus.
axisValueTables = [
v for v in axisValueTables if not v.Flags & ELIDABLE_AXIS_VALUE_NAME
@ -220,8 +159,12 @@ def checkAxisValuesExist(stat, axisValues, axisCoords):
def _sortAxisValues(axisValues):
# Sort and remove duplicates ensuring that format 4 Axis Values
# are dominant
# Sort and remove duplicates and ensure that format 4 AxisValues
# are dominant. We need format 4 AxisValues to be dominant because the
# MS Spec states, "if a format 1, format 2 or format 3 table has a
# (nominal) value used in a format 4 table that also has values for
# other axes, the format 4 table, being the more specific match, is used",
# https://docs.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-4
results = []
seenAxes = set()
# Sort format 4 axes so the tables with the most AxisValueRecords
@ -255,10 +198,10 @@ def _updateNameRecords(varfont, axisValues):
stat = varfont["STAT"].table
axisValueNameIDs = [a.ValueNameID for a in axisValues]
ribbiNameIDs = [n for n in axisValueNameIDs if nameIDIsRibbi(nametable, n)]
ribbiNameIDs = [n for n in axisValueNameIDs if _isRibbi(nametable, n)]
nonRibbiNameIDs = [n for n in axisValueNameIDs if n not in ribbiNameIDs]
elidedNameID = stat.ElidedFallbackNameID
elidedNameIsRibbi = nameIDIsRibbi(nametable, elidedNameID)
elidedNameIsRibbi = _isRibbi(nametable, elidedNameID)
getName = nametable.getName
platforms = set((r.platformID, r.platEncID, r.langID) for r in nametable.names)
@ -271,11 +214,13 @@ def _updateNameRecords(varfont, axisValues):
subFamilyName = " ".join(
getName(n, *platform).toUnicode() for n in ribbiNameIDs
)
typoSubFamilyName = " ".join(
getName(n, *platform).toUnicode()
for n in axisValueNameIDs
if nonRibbiNameIDs
)
if nonRibbiNameIDs:
typoSubFamilyName = " ".join(
getName(n, *platform).toUnicode()
for n in axisValueNameIDs
)
else:
typoSubFamilyName = None
# If neither subFamilyName and typographic SubFamilyName exist,
# we will use the STAT's elidedFallbackName
@ -298,7 +243,7 @@ def _updateNameRecords(varfont, axisValues):
)
def nameIDIsRibbi(nametable, nameID):
def _isRibbi(nametable, nameID):
engNameRecords = any(
r
for r in nametable.names
@ -306,7 +251,7 @@ def nameIDIsRibbi(nametable, nameID):
)
if not engNameRecords:
raise ValueError(
f"Canot determine if there are RIBBI Axis Value Tables "
f"Cannot determine if there are RIBBI Axis Value Tables "
"since there are no name table Records which have "
"platformID=3, platEncID=1, langID=0x409"
)
@ -340,6 +285,9 @@ def _updateNameTableStyleRecords(
NameID.TYPOGRAPHIC_SUBFAMILY_NAME, *platform
) or nametable.getName(NameID.SUBFAMILY_NAME, *platform)
if not all([currentFamilyName, currentStyleName]):
raise ValueError("Name table must have NameIDs 1 and 2")
currentFamilyName = currentFamilyName.toUnicode()
currentStyleName = currentStyleName.toUnicode()