designspaceLib: add loadSourceFonts method with custom opener

Allows to load the SourceDescriptor.font attribute from its path, using a custom callable (e.g. defcon.Font or ttLib.TTFont, etc.).
This commit is contained in:
Cosimo Lupo 2019-05-10 16:32:11 +01:00
parent 85c3f85526
commit 44f74dc8bb
No known key found for this signature in database
GPG Key ID: 20D4A261E4A0E642
2 changed files with 78 additions and 0 deletions

View File

@ -1262,3 +1262,41 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
newConditions.append(dict(name=cond['name'], minimum=minimum, maximum=maximum)) newConditions.append(dict(name=cond['name'], minimum=minimum, maximum=maximum))
newConditionSets.append(newConditions) newConditionSets.append(newConditions)
rule.conditionSets = newConditionSets rule.conditionSets = newConditionSets
def loadSourceFonts(self, opener, **kwargs):
"""Ensure SourceDescriptor.font attributes are loaded, and return list of fonts.
Takes a callable which initializes a new font object (e.g. TTFont, or
defcon.Font, etc.) from the SourceDescriptor.path, and sets the
SourceDescriptor.font attribute.
If the font attribute is already not None, it is not loaded again.
Fonts with the same path are only loaded once and shared among SourceDescriptors.
Args:
opener (Callable): takes one required positional argument, the source.path,
and an optional list of keyword arguments, and returns a new font object
loaded from the path.
**kwargs: extra options passed on to the opener function.
Returns:
List of font objects in the order they appear in the sources list.
"""
# we load fonts with the same source.path only once
loaded = {}
fonts = []
for source in self.sources:
if source.font is not None: # font already loaded
fonts.append(source.font)
continue
if source.path in loaded:
source.font = loaded[source.path]
else:
if source.path is None:
raise DesignSpaceDocumentError(
"Designspace source '%s' has no 'path' attribute"
% (source.name or "<Unknown>")
)
source.font = opener(source.path, **kwargs)
loaded[source.path] = source.font
fonts.append(source.font)
return fonts

View File

@ -13,6 +13,7 @@ from fontTools.misc import plistlib
from fontTools.designspaceLib import ( from fontTools.designspaceLib import (
DesignSpaceDocument, SourceDescriptor, AxisDescriptor, RuleDescriptor, DesignSpaceDocument, SourceDescriptor, AxisDescriptor, RuleDescriptor,
InstanceDescriptor, evaluateRule, processRules, posix, DesignSpaceDocumentError) InstanceDescriptor, evaluateRule, processRules, posix, DesignSpaceDocumentError)
from fontTools import ttLib
def _axesAsDict(axes): def _axesAsDict(axes):
""" """
@ -909,3 +910,42 @@ def test_findDefault_axis_mapping():
designspace.axes[1].default = 0 designspace.axes[1].default = 0
assert designspace.findDefault().filename == "Font-Regular.ufo" assert designspace.findDefault().filename == "Font-Regular.ufo"
def test_loadSourceFonts():
def opener(path):
font = ttLib.TTFont()
font.importXML(path)
return font
# this designspace file contains .TTX source paths
path = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
"varLib",
"data",
"SparseMasters.designspace"
)
designspace = DesignSpaceDocument.fromfile(path)
# force two source descriptors to have the same path
designspace.sources[1].path = designspace.sources[0].path
fonts = designspace.loadSourceFonts(opener)
assert len(fonts) == 3
assert all(isinstance(font, ttLib.TTFont) for font in fonts)
assert fonts[0] is fonts[1] # same path, identical font object
fonts2 = designspace.loadSourceFonts(opener)
for font1, font2 in zip(fonts, fonts2):
assert font1 is font2
def test_loadSourceFonts_no_required_path():
designspace = DesignSpaceDocument()
designspace.sources.append(SourceDescriptor())
with pytest.raises(DesignSpaceDocumentError, match="no 'path' attribute"):
designspace.loadSourceFonts(lambda p: p)