Source code for bin.cleanFont

#!/usr/bin/env python3
"""Remove SMuFL font data.

.. warning::

    This script will permanently delete data. Always back up your font file before use.

This script deletes SMuFL-specific metadata and glyph anchors from a UFO font file.

The following attribute and anchor names may be specified for inclusion or exclusion:

    Attributes
        - `designSize`
        - `sizeRange`
        - `engravingDefaults`
        - `name`
        - `classes`
        - `description`
        - `spaces`

    Anchors
        - `splitStemUpSE`
        - `splitStemUpSW`
        - `splitStemDownNE`
        - `splitStemDownNW`
        - `stemUpSE`
        - `stemDownNW`
        - `stemUpNW`
        - `stemDownSW`
        - `nominalWidth`
        - `numeralTop`
        - `numeralBottom`
        - `cutOutNE`
        - `cutOutSE`
        - `cutOutSW`
        - `cutOutNW`
        - `graceNoteSlashSW`
        - `graceNoteSlashNE`
        - `graceNoteSlashNW`
        - `graceNoteSlashSE`
        - `repeatOffset`
        - `noteheadOrigin`
        - `opticalCenter`

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:`clean-font-cli` and
:ref:`clean-font-python` sections below for usage details.

"""

from collections.abc import Iterable
import argparse
from pathlib import Path

from tqdm import tqdm

from smufolib import Font
from smufolib.objects.smufl import ANCHOR_NAMES, FONT_ATTRIBUTES, GLYPH_ATTRIBUTES
from smufolib.cli import REQUIRED, commonParser
from smufolib.utils import error, stdUtils
from smufolib.utils.scriptUtils import normalizeFont as _normalizeFont


# Parameter defaults
EXCLUDE = None
VERBOSE = False

# pylint: disable=C0103


[docs] def cleanFont( font: Font | Path | str, include: Iterable, exclude: Iterable | None = EXCLUDE, verbose: bool = VERBOSE, ): """Delete Smufl-specific attribute values (Python API). Use the `include` and `exclude` keyword arguments to control which attributes and anchors are removed. To avoid unintentional data loss, `include` must be specified. Setting `include="*"` will delete all SMuFL metadata and anchors. Refer to the list of valid keys in the main docstring. :param font: Target :class:`.Font` object or path to font file. :param include: Items to be deleted. May be ``"*"`` (all), an individual attribute or anchor name as a :class:`str` or :class:`tuple` of several. :param exclude: Items to be preserved if ``ìnclude="*"``. Defaults to :obj:`None`. :param verbose: Make output verbose. Defaults to :obj:`False`. :raises TypeError: If any parameter value is not the expected type. :raises ValueError: If any item in `include` or `exclude` is invalid. """ print("Starting...") font = _normalizeFont(font) itemsToClean = _buildItemsDict(include, exclude) # Clean font attributes stdUtils.verbosePrint("\nCleaning attributes for font:", verbose) for attr in itemsToClean["fontAttributes"]: if font.smufl.engravingDefaults and attr == "engravingDefaults": font.smufl.engravingDefaults.clear() elif attr == "spaces": setattr(font.smufl, attr, False) else: setattr(font.smufl, attr, None) stdUtils.verbosePrint(f"\t'{attr}'", verbose) for glyph in font if verbose else tqdm(font): # Clean glyph attributes glyphAttributesCleaned = False for attr in itemsToClean["glyphAttributes"]: if not getattr(glyph.smufl, attr): continue if not glyphAttributesCleaned: stdUtils.verbosePrint( f"\nCleaning attributes from glyph '{glyph.name}':", verbose ) glyphAttributesCleaned = True setattr(glyph.smufl, attr, None) stdUtils.verbosePrint(f"\t'{attr}'", verbose) # Clean Anchors anchorsCleaned = False for anchor in glyph.anchors: if anchor.name not in itemsToClean["anchors"]: continue if not anchorsCleaned: stdUtils.verbosePrint( f"\nCleaning anchors from glyph '{glyph.name}':", verbose ) anchorsCleaned = True glyph.removeAnchor(anchor.index) stdUtils.verbosePrint(f"\t'{anchor.name}'", verbose) stdUtils.verbosePrint("\nSaving font...", verbose) font.save() print("\nDone.")
def main() -> None: """Command line entry point.""" args = _parseArgs() cleanFont(args.font, args.include, exclude=args.exclude, verbose=args.verbose) def _buildItemsDict(include, exclude): # Build dict of attribute and anchor items to remove. itemsToClean = {"fontAttributes": [], "glyphAttributes": [], "anchors": []} allItems = FONT_ATTRIBUTES | GLYPH_ATTRIBUTES | ANCHOR_NAMES exclude = () if exclude is None else exclude exclude = (exclude,) if isinstance(exclude, str) else exclude for item in exclude: error.suggestValue(item, allItems, "exclude") if include in ("*", ["*"]): include = allItems elif isinstance(include, str): include = (include,) for item in include: if item in exclude: continue if item in FONT_ATTRIBUTES: itemsToClean["fontAttributes"].append(item) elif item in GLYPH_ATTRIBUTES: itemsToClean["glyphAttributes"].append(item) elif item in ANCHOR_NAMES: itemsToClean["anchors"].append(item) else: error.suggestValue(item, allItems, "include") return itemsToClean def _parseArgs() -> argparse.Namespace: # Parse command line arguments and options. parser = commonParser( "font", include=REQUIRED, description=stdUtils.getSummary(__doc__), exclude=EXCLUDE, verbose=VERBOSE, ) return parser.parse_args() if __name__ == "__main__": main()