up follow livre
This commit is contained in:
parent
b4b4398bb0
commit
3a7a3849ae
12242 changed files with 2564461 additions and 6914 deletions
|
|
@ -0,0 +1,260 @@
|
|||
"""Compute name information for a given location in user-space coordinates
|
||||
using STAT data. This can be used to fill-in automatically the names of an
|
||||
instance:
|
||||
|
||||
.. code:: python
|
||||
|
||||
instance = doc.instances[0]
|
||||
names = getStatNames(doc, instance.getFullUserLocation(doc))
|
||||
print(names.styleNames)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Literal, Optional, Tuple, Union
|
||||
import logging
|
||||
|
||||
from fontTools.designspaceLib import (
|
||||
AxisDescriptor,
|
||||
AxisLabelDescriptor,
|
||||
DesignSpaceDocument,
|
||||
DiscreteAxisDescriptor,
|
||||
SimpleLocationDict,
|
||||
SourceDescriptor,
|
||||
)
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
RibbiStyleName = Union[
|
||||
Literal["regular"],
|
||||
Literal["bold"],
|
||||
Literal["italic"],
|
||||
Literal["bold italic"],
|
||||
]
|
||||
|
||||
BOLD_ITALIC_TO_RIBBI_STYLE = {
|
||||
(False, False): "regular",
|
||||
(False, True): "italic",
|
||||
(True, False): "bold",
|
||||
(True, True): "bold italic",
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class StatNames:
|
||||
"""Name data generated from the STAT table information."""
|
||||
|
||||
familyNames: Dict[str, str]
|
||||
styleNames: Dict[str, str]
|
||||
postScriptFontName: Optional[str]
|
||||
styleMapFamilyNames: Dict[str, str]
|
||||
styleMapStyleName: Optional[RibbiStyleName]
|
||||
|
||||
|
||||
def getStatNames(
|
||||
doc: DesignSpaceDocument, userLocation: SimpleLocationDict
|
||||
) -> StatNames:
|
||||
"""Compute the family, style, PostScript names of the given ``userLocation``
|
||||
using the document's STAT information.
|
||||
|
||||
Also computes localizations.
|
||||
|
||||
If not enough STAT data is available for a given name, either its dict of
|
||||
localized names will be empty (family and style names), or the name will be
|
||||
None (PostScript name).
|
||||
|
||||
Note: this method does not consider info attached to the instance, like
|
||||
family name. The user needs to override all names on an instance that STAT
|
||||
information would compute differently than desired.
|
||||
|
||||
.. versionadded:: 5.0
|
||||
"""
|
||||
familyNames: Dict[str, str] = {}
|
||||
defaultSource: Optional[SourceDescriptor] = doc.findDefault()
|
||||
if defaultSource is None:
|
||||
LOGGER.warning("Cannot determine default source to look up family name.")
|
||||
elif defaultSource.familyName is None:
|
||||
LOGGER.warning(
|
||||
"Cannot look up family name, assign the 'familyname' attribute to the default source."
|
||||
)
|
||||
else:
|
||||
familyNames = {
|
||||
"en": defaultSource.familyName,
|
||||
**defaultSource.localisedFamilyName,
|
||||
}
|
||||
|
||||
styleNames: Dict[str, str] = {}
|
||||
# If a free-standing label matches the location, use it for name generation.
|
||||
label = doc.labelForUserLocation(userLocation)
|
||||
if label is not None:
|
||||
styleNames = {"en": label.name, **label.labelNames}
|
||||
# Otherwise, scour the axis labels for matches.
|
||||
else:
|
||||
# Gather all languages in which at least one translation is provided
|
||||
# Then build names for all these languages, but fallback to English
|
||||
# whenever a translation is missing.
|
||||
labels = _getAxisLabelsForUserLocation(doc.axes, userLocation)
|
||||
if labels:
|
||||
languages = set(
|
||||
language for label in labels for language in label.labelNames
|
||||
)
|
||||
languages.add("en")
|
||||
for language in languages:
|
||||
styleName = " ".join(
|
||||
label.labelNames.get(language, label.defaultName)
|
||||
for label in labels
|
||||
if not label.elidable
|
||||
)
|
||||
if not styleName and doc.elidedFallbackName is not None:
|
||||
styleName = doc.elidedFallbackName
|
||||
styleNames[language] = styleName
|
||||
|
||||
if "en" not in familyNames or "en" not in styleNames:
|
||||
# Not enough information to compute PS names of styleMap names
|
||||
return StatNames(
|
||||
familyNames=familyNames,
|
||||
styleNames=styleNames,
|
||||
postScriptFontName=None,
|
||||
styleMapFamilyNames={},
|
||||
styleMapStyleName=None,
|
||||
)
|
||||
|
||||
postScriptFontName = f"{familyNames['en']}-{styleNames['en']}".replace(" ", "")
|
||||
|
||||
styleMapStyleName, regularUserLocation = _getRibbiStyle(doc, userLocation)
|
||||
|
||||
styleNamesForStyleMap = styleNames
|
||||
if regularUserLocation != userLocation:
|
||||
regularStatNames = getStatNames(doc, regularUserLocation)
|
||||
styleNamesForStyleMap = regularStatNames.styleNames
|
||||
|
||||
styleMapFamilyNames = {}
|
||||
for language in set(familyNames).union(styleNames.keys()):
|
||||
familyName = familyNames.get(language, familyNames["en"])
|
||||
styleName = styleNamesForStyleMap.get(language, styleNamesForStyleMap["en"])
|
||||
styleMapFamilyNames[language] = (familyName + " " + styleName).strip()
|
||||
|
||||
return StatNames(
|
||||
familyNames=familyNames,
|
||||
styleNames=styleNames,
|
||||
postScriptFontName=postScriptFontName,
|
||||
styleMapFamilyNames=styleMapFamilyNames,
|
||||
styleMapStyleName=styleMapStyleName,
|
||||
)
|
||||
|
||||
|
||||
def _getSortedAxisLabels(
|
||||
axes: list[Union[AxisDescriptor, DiscreteAxisDescriptor]],
|
||||
) -> Dict[str, list[AxisLabelDescriptor]]:
|
||||
"""Returns axis labels sorted by their ordering, with unordered ones appended as
|
||||
they are listed."""
|
||||
|
||||
# First, get the axis labels with explicit ordering...
|
||||
sortedAxes = sorted(
|
||||
(axis for axis in axes if axis.axisOrdering is not None),
|
||||
key=lambda a: a.axisOrdering,
|
||||
)
|
||||
sortedLabels: Dict[str, list[AxisLabelDescriptor]] = {
|
||||
axis.name: axis.axisLabels for axis in sortedAxes
|
||||
}
|
||||
|
||||
# ... then append the others in the order they appear.
|
||||
# NOTE: This relies on Python 3.7+ dict's preserved insertion order.
|
||||
for axis in axes:
|
||||
if axis.axisOrdering is None:
|
||||
sortedLabels[axis.name] = axis.axisLabels
|
||||
|
||||
return sortedLabels
|
||||
|
||||
|
||||
def _getAxisLabelsForUserLocation(
|
||||
axes: list[Union[AxisDescriptor, DiscreteAxisDescriptor]],
|
||||
userLocation: SimpleLocationDict,
|
||||
) -> list[AxisLabelDescriptor]:
|
||||
labels: list[AxisLabelDescriptor] = []
|
||||
|
||||
allAxisLabels = _getSortedAxisLabels(axes)
|
||||
if allAxisLabels.keys() != userLocation.keys():
|
||||
LOGGER.warning(
|
||||
f"Mismatch between user location '{userLocation.keys()}' and available "
|
||||
f"labels for '{allAxisLabels.keys()}'."
|
||||
)
|
||||
|
||||
for axisName, axisLabels in allAxisLabels.items():
|
||||
userValue = userLocation[axisName]
|
||||
label: Optional[AxisLabelDescriptor] = next(
|
||||
(
|
||||
l
|
||||
for l in axisLabels
|
||||
if l.userValue == userValue
|
||||
or (
|
||||
l.userMinimum is not None
|
||||
and l.userMaximum is not None
|
||||
and l.userMinimum <= userValue <= l.userMaximum
|
||||
)
|
||||
),
|
||||
None,
|
||||
)
|
||||
if label is None:
|
||||
LOGGER.debug(
|
||||
f"Document needs a label for axis '{axisName}', user value '{userValue}'."
|
||||
)
|
||||
else:
|
||||
labels.append(label)
|
||||
|
||||
return labels
|
||||
|
||||
|
||||
def _getRibbiStyle(
|
||||
self: DesignSpaceDocument, userLocation: SimpleLocationDict
|
||||
) -> Tuple[RibbiStyleName, SimpleLocationDict]:
|
||||
"""Compute the RIBBI style name of the given user location,
|
||||
return the location of the matching Regular in the RIBBI group.
|
||||
|
||||
.. versionadded:: 5.0
|
||||
"""
|
||||
regularUserLocation = {}
|
||||
axes_by_tag = {axis.tag: axis for axis in self.axes}
|
||||
|
||||
bold: bool = False
|
||||
italic: bool = False
|
||||
|
||||
axis = axes_by_tag.get("wght")
|
||||
if axis is not None:
|
||||
for regular_label in axis.axisLabels:
|
||||
if (
|
||||
regular_label.linkedUserValue == userLocation[axis.name]
|
||||
# In the "recursive" case where both the Regular has
|
||||
# linkedUserValue pointing the Bold, and the Bold has
|
||||
# linkedUserValue pointing to the Regular, only consider the
|
||||
# first case: Regular (e.g. 400) has linkedUserValue pointing to
|
||||
# Bold (e.g. 700, higher than Regular)
|
||||
and regular_label.userValue < regular_label.linkedUserValue
|
||||
):
|
||||
regularUserLocation[axis.name] = regular_label.userValue
|
||||
bold = True
|
||||
break
|
||||
|
||||
axis = axes_by_tag.get("ital") or axes_by_tag.get("slnt")
|
||||
if axis is not None:
|
||||
for upright_label in axis.axisLabels:
|
||||
if (
|
||||
upright_label.linkedUserValue == userLocation[axis.name]
|
||||
# In the "recursive" case where both the Upright has
|
||||
# linkedUserValue pointing the Italic, and the Italic has
|
||||
# linkedUserValue pointing to the Upright, only consider the
|
||||
# first case: Upright (e.g. ital=0, slant=0) has
|
||||
# linkedUserValue pointing to Italic (e.g ital=1, slant=-12 or
|
||||
# slant=12 for backwards italics, in any case higher than
|
||||
# Upright in absolute value, hence the abs() below.
|
||||
and abs(upright_label.userValue) < abs(upright_label.linkedUserValue)
|
||||
):
|
||||
regularUserLocation[axis.name] = upright_label.userValue
|
||||
italic = True
|
||||
break
|
||||
|
||||
return BOLD_ITALIC_TO_RIBBI_STYLE[bold, italic], {
|
||||
**userLocation,
|
||||
**regularUserLocation,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue