Source code for bin.calculateEngravingDefaults

#!/usr/bin/env python3
"""Calculate engraving defaults from glyph contours.

This script calculates and sets attribute values for the :class:`.EngravingDefaults`
class based on glyph dimensions, registration, metrics, and point placement.

It offers options to override automatic contour measurements and reassign attributes to
different ruler functions or glyphs. Refer to :data:`.ENGRAVING_DEFAULTS_MAPPING` for a
comprehensive list of attributes and their default corresponding glyphs and assigned
ruler functions.

.. note::

    The setting for :attr:`.textFontFamily` must be set manually within the `override`
    parameter.

The script requires SMufoLib to be installed in its execution environment. It can be
used from the command line or as a Python module. See the
:ref:`calculate-engraving-defaults-cli` and :ref:`calculate-engraving-defaults-python`
sections below for usage details.

.. versionchanged:: 0.5.0

    The `referenceIndex` parameter has been removed from all ruler functions and is no
    longer needed when providing remappings.

    The ruler functions have been removed from this script and are now available in the
    :mod:`.rulers` module.

"""

from __future__ import annotations
from collections.abc import Callable
import argparse
import json
import textwrap
from pathlib import Path

from tqdm import tqdm

from smufolib import (
    Font,
    Glyph,
    cli,
    config,
    error,
    normalizers,
    stdUtils,
)
from smufolib.utils.rulers import ENGRAVING_DEFAULTS_MAPPING, DISPATCHER
from smufolib.utils.scriptUtils import normalizeFont as _normalizeFont


Exclude = tuple[str] | list[str]
OverrideValue = int | float | tuple[str, ...] | None
Override = dict[str, OverrideValue]
Remapping = dict[str, dict[str, str]]
RulerType = Callable[[Glyph], int | float | None]

CONFIG = config.load()

# Parameter defaults
EXCLUDE = None
OVERRIDE = None
SPACES = False
REMAP = None
VERBOSE = False


# pylint: disable=R0913, C0103, R0914


[docs] def calculateEngravingDefaults( font: Font | Path | str, exclude: Exclude | None = EXCLUDE, override: Override | None = OVERRIDE, remap: Remapping | None = REMAP, spaces: bool = SPACES, verbose: bool = VERBOSE, ) -> None: """Calculate engraving defaults from glyph contours (Python API). :param font: Target :class:`.Font` object or path to font file. :param exclude: :class:`.EngravingDefaults` attributes to exclude. Defaults to :obj:`None`. :param override: :class:`.EngravingDefaults` attributes to manually override mapped to their values. Defaults to :obj:`None`. :param remap: :class:`.EngravingDefaults` attributes mapped to remappings :class:`dict`, e.g.: .. code-block:: python r = { "arrowShaftThickness": { "ruler": "glyphBoundsXMinAbs", "glyph": "uniEB60" } } Defaults to :obj:`None`. :param spaces: Whether values for overrides are given in staff spaces instead of font units. Defaults to :obj:`False`. :param verbose: Make output verbose. Defaults to :obj:`False`. :raises TypeError: If any parameter value is not the expected type. :raises ValueError: If any parameter value item is not the expected type or value. """ print("Starting...") font = _normalizeFont(font) exclude = _normalizeExclude(exclude) override = _normalizeOverride(override) remap = _normalizeRemap(remap) font.smufl.spaces = False iterator = ENGRAVING_DEFAULTS_MAPPING.items() if not verbose: iterator = tqdm(ENGRAVING_DEFAULTS_MAPPING.items()) stdUtils.verbosePrint("\nSetting attributes:", verbose) for key, mapping in iterator: if exclude and key in exclude: continue if key == "textFontFamily": if override and key in override: value = override[key] setattr(font.smufl.engravingDefaults, key, value) stdUtils.verbosePrint(f"\t'{key}': {value}", verbose) continue rulerName = mapping.get("ruler", "") glyphName = mapping.get("glyph", "") remapping = remap.get(key, {}) if remap else {} rulerName = remapping.get("ruler", rulerName) ruler: RulerType = DISPATCHER[rulerName] glyphName = remapping.get("glyph", glyphName) rulerValue = _getValue( font=font, key=key, glyphName=glyphName, ruler=ruler, verbose=verbose, ) if rulerValue is None: continue if override and key in override: value = override[key] else: value = rulerValue if spaces and isinstance(value, (int, float)): value = font.smufl.toUnits(value) setattr(font.smufl.engravingDefaults, key, value) if spaces and value and isinstance(value, (int, float)): value = font.smufl.toSpaces(value) stdUtils.verbosePrint(f"\t'{key}': {value}", verbose) stdUtils.verbosePrint("\nSaving font...", verbose) font.save() print("\nDone!")
def main() -> None: """Command line entry point.""" args = _parseArgs() calculateEngravingDefaults( args.font, exclude=args.exclude, override=args.override, remap=args.remap, spaces=args.spaces, verbose=args.verbose, ) # ------- # Helpers # ------- def _normalizeExclude(exclude: Exclude | None) -> Exclude | None: # Noralize `exclude`. if exclude is None: return None error.validateType(exclude, (tuple, list), "exclude") for item in exclude: error.suggestValue( item, list(ENGRAVING_DEFAULTS_MAPPING.keys()), "exclude", items=True ) return exclude def _normalizeOverride(override: Override | None) -> Override | None: # Normalize override dictionary. if override is None: return None error.validateType(override, dict, "override") for key, value in override.items(): attributes = list(ENGRAVING_DEFAULTS_MAPPING.keys()) error.suggestValue(key, attributes, "override", items=True) normalizers.normalizeEngravingDefaultsAttr(key, value) return override def _normalizeRemap(remap: Remapping | None) -> Remapping | None: # Normalize remap dictionary. if remap is None: return None error.validateType(remap, dict, "remap") for key, value in remap.items(): error.suggestValue( key, list(ENGRAVING_DEFAULTS_MAPPING.keys()), "remap", items=True ) for k, v in value.items(): error.suggestValue( k, ("ruler", "glyph"), f"'remap'['{value}']", items=True, ) error.validateType( v, (str, int, type(None)), f"'remap'['{value}']", items=True ) return remap def _getValue( font: Font, key: str, glyphName: str, ruler: RulerType, verbose: bool, ) -> int | float | None: # Get value from ruler function and print error message. try: glyph = font[glyphName] return ruler(glyph) except KeyError: stdUtils.verbosePrint( "Skipping attribute assigned to non-" f"existent glyph: '{key}' ('{glyphName}')", verbose, ) return None def _parseArgs() -> argparse.Namespace: # Parse command line arguments and options. parser = argparse.ArgumentParser( description=stdUtils.getSummary(__doc__), parents=[ cli.commonParser( "font", exclude=EXCLUDE, spaces=SPACES, verbose=VERBOSE, addHelp=False ) ], formatter_class=cli.createHelpFormatter( ("ArgumentDefaultsHelpFormatter", "RawDescriptionHelpFormatter") ), ) parser.add_argument( "-o", "--override", default=OVERRIDE, type=json.loads, help=textwrap.dedent( """JSON string of attributes and values to manually override in the format: '{"<attributeName>": <value>, ...}'""" ), ) parser.add_argument( "-r", "--remap", default=REMAP, type=json.loads, help=textwrap.dedent( """JSON string of ruler and glyph remappings in the format: '{"<attributeName>": {"ruler": "<functionName>", "glyph": "<glyphName>"}, ...}'""" ), ) return parser.parse_args() if __name__ == "__main__": main()