nanoemoji

repository·main·Indexed 43 days ago

https://github.com/googlefonts/nanoemoji

A tool for building color fonts, including support for the COLRv1 specification. It utilizes Skia (via picosvg) and resvg for vector and bitmap color table generation. It provides a CLI to build fonts from SVG files, a `maximum_color` command to add missing color tables (SVG, COLR, or CBDT) to existing fonts, and utilities for performing QA image diffs between SVGs and compiled fonts.

Tokens
4.9K
Snippets
15
Records
24
Agent score
56%

What's inside nanoemoji

  1. Perform QA image diffs with nanoemoji

    main

    To verify the quality of the output, nanoemoji can perform image diffs between the original SVGs (rendered by Chrome) and the compiled font.

    Requirement: Chrome must be installed in its standard location on your system.

    To run a diff check on a set of SVGs:

    nanoemoji --gen_svg_font_diffs $(find <path_to_svgs> -name '<pattern>.svg')
    nanoemoji --gen_svg_font_diffs \
    	$(find color-fonts/font-srcs/noto-emoji/svg -name 'emoji_u270b*.svg')
  2. Install nanoemoji

    main

    To use nanoemoji, you must have Python 3.10 or greater installed. It is recommended to install it within a virtual environment (venv).

    Run the following command to install the package in editable mode from the local repository:

    pip install -e .
  3. Add color tables to existing fonts with maximum_color

    main

    The maximum_color command allows you to add missing color tables to an existing font. If you provide at least one vector color table (COLR or SVG), it can generate the other vector table and bitmap tables.

    Note: This feature is under active development and may contain bugs.

    Add COLR and SVG tables

    To add COLR to a font that has SVG (and vice versa):

    maximum_color my_colr_font.ttf

    Add COLR, SVG, and CBDT tables

    To add COLR/SVG and also generate a CBDT bitmap table:

    maximum_color --bitmaps my_colr_font.ttf

    Target Browser Compatibility

    Color tableTarget browserNotes
    COLRChrome 98+
    SVGFirefox, Safari
    CBDTChrome <98Only generated if --bitmaps is passed to maximum_color.
    # Adds COLR to a font with SVG and vice versa
    maximum_color my_colr_font.ttf
    
    # Adds COLR to a font with SVG and vice versa, and generates a CBDT table
    maximum_color --bitmaps my_colr_font.ttf
  4. Define COLRv1 paint properties and colors

    main

    The nanoemoji.paint module provides a hierarchy of classes for defining COLRv1 paint properties, including solid colors, gradients, glyph-based paints, and various transformations. These classes are designed to be used when constructing or manipulating COLRv1 font data.

    Key components include:

    • Paint: The abstract base class for all paint types.
    • PaintSolid: A single color.
    • PaintLinearGradient and PaintRadialGradient: Gradient fills with color stops.
    • PaintGlyph: A paint that uses another glyph.
    • PaintTransform family: Classes like PaintTranslate, PaintScale, PaintRotate, and PaintSkew that apply geometric transformations to a child paint.
    • PaintComposite: Combines a source and backdrop paint using a CompositeMode (Porter-Duff modes).
  5. ReuseCache configuration and behavior

    main

    The ReuseCache class manages the lifecycle of reusable SVG elements (paths and gradients) during the font building process.

    Key Attributes:

    • reuse_tolerance: A float used to determine if two shapes or gradients are similar enough to be reused.
    • glyph_cache: A GlyphReuseCache for tracking shapes.
    • glyph_elements: A mapping of glyph names to their corresponding etree.Element (the actual SVG nodes).
    • gradient_ids: A mapping of GradientReuseKey to SVG gradient IDs, allowing multiple glyphs to share the same <linearGradient> or <radialGradient> definitions.
    • SKIP_REUSE: A class-level sentinel value (ReuseResult('', Affine2D.identity())) used to explicitly instruct the builder to skip reuse for a specific glyph (e.g., for .notdef).
  6. Understand ReorderRule types: ReorderCoverage and ReorderList

    main

    The reorder_glyphs logic uses two primary internal abstractions to maintain table integrity when glyph IDs change:

    ReorderCoverage

    Used for tables that define coverage (a list of glyphs). It can optionally reorder a parallel_list that is tied to the coverage entries.

    • coverage_attr: The attribute name on the table object containing the glyph coverage (defaults to "Coverage").
    • parallel_list_attr: An optional attribute name for a list that must be reordered in lockstep with the coverage glyphs.

    ReorderList

    Used for lists that are ordered by coverage itself but contain items that need to be sorted by a specific glyph key.

    • list_attr: The attribute name of the list to reorder.
    • key: The attribute name on the items within the list that refers to a glyph name (used to look up the new glyph ID).
  7. How SVG shape reuse works in nanoemoji

    main

    When picosvg=True is used in make_svg_table, the library performs the following optimization steps:

    1. Grouping: It uses a DisjointSet to group glyphs that share identical shapes. Glyphs in the same group are bundled into a single SVG document.
    2. Caching: A ReuseCache tracks previously encountered shapes and gradients.
    3. <use> Elements: Instead of duplicating path data, the library inserts <use> elements pointing to IDs in the <defs> section of the shared SVG document.
    4. Attribute Migration: To ensure compatibility and minimize redundancy, attributes like fill or opacity that are identical across all uses of a shape are migrated from the <use> element to the target shape in <defs>.
    5. Ordering: To maintain valid SVG document structures, glyphs are reordered so that shared shapes appear sequentially in the font's glyph order.

    Note on .notdef: The .notdef glyph is treated specially; it is kept as the first glyph and is not grouped with other glyphs to prevent reshuffling issues.

  8. Build a color font from SVGs with nanoemoji

    main

    You can use the nanoemoji CLI to build color fonts (including COLRv1) from a collection of SVG files.

    To build a font using the glyf_colr_1 format (COLRv1) from a set of SVGs, use the following pattern:

    nanoemoji --color_format glyf_colr_1 $(find <path_to_svgs> -name '<pattern>.svg')

    Example for building a font from Noto Emoji SVGs focusing on specific characters:

    nanoemoji --color_format glyf_colr_1 $(find ../noto-emoji/svg -name 'emoji_u270d*.svg')
  9. Configuration schema for mergeable fonts (TOML)

    main

    The configuration file generated by write_config_for_mergeable.py follows this structure:

    output_file = "COLR.ttf"
    color_format = "<format>"
    upem = <int>
    width = 0  # from input width
    ascender = <int>
    descender = <int>
    keep_glyph_names = true
    
    fea_file = ""
    
    [axis.wght]
    name = "Weight"
    default = 400
    
    [master.regular]
    style_name = "Regular"
    
    [master.regular.position]
    wght = 400

    Key fields:

    • output_file: The name of the resulting font file.
    • color_format: The color format to be used.
    • upem: Units per Em extracted from the source font.
    • ascender: TypoAscender value from the source font.
    • descender: TypoDescender value from the source font.
    • keep_glyph_names: Boolean indicating whether to preserve glyph names.
    • [axis.<name>]: Defines variable font axes (e.g., wght for weight).
    • [master.<style>]: Defines font masters and their corresponding axis positions.
  10. Supported color font formats

    main

    nanoemoji supports several color table formats for building fonts:

    FormatSupportNotes
    COLRv1YesSupports x-glyph reuse.
    COLRv0YesSupports limited x-glyph reuse; no gradients.
    SVGYesSupports x-glyph reuse.
    sbixYesOnly for Mac Safari. Uses square bitmaps via resvg.
    CBDTYesOnly square bitmaps via resvg.
  11. Reorder glyphs in a font using reorder_glyphs()

    main

    The reorder_glyphs function updates the glyph order of a fontTools.ttLib.TTFont object and automatically synchronizes related OpenType tables (GPOS, GSUB, GDEF, and MATH) to ensure they remain consistent with the new glyph order.

    Requirements:

    • The new_glyph_order must contain the exact same set of glyphs as the original font.
    • The font must be fully loaded (the function calls require_fully_loaded internally).

    Behavior:

    1. It validates that the new order has the same length and glyph set as the current order.
    2. It updates the font's glyph order using font.setGlyphOrder().
    3. It traverses the GPOS, GSUB, GDEF, and MATH tables to find coverage-based entries and applies specific ReorderRules to reorder them based on the new glyph IDs.
    from fontTools.ttLib import TTFont
    from nanoemoji.reorder_glyphs import reorder_glyphs
    
    font = TTFont("path/to/font.ttf")
    new_order = ["glyph_b", "glyph_a", "glyph_c"] # Must be same set as original
    
    reorder_glyphs(font, new_order)
    font.save("path/to/font_reordered.ttf")
  12. Apply an Affine2D transform to a Paint

    main

    The transformed(transform: Affine2D, target: Paint) -> Paint function attempts to wrap a target paint in the most efficient COLRv1 transformation possible based on the provided Affine2D matrix.

    It performs optimizations such as:

    • If the transform is just a translation that fits in int16, it returns a PaintTranslate.
    • If the transform is a uniform scale and translation, it returns PaintScaleUniform or PaintScaleUniformAroundCenter.
    • If the transform is a non-uniform scale, it returns PaintScale or PaintScaleAroundCenter.
    • Otherwise, it falls back to a general PaintTransform containing the full 6-element matrix.