react-native-nano-icons

repository·main·Indexed 20 days ago

https://github.com/software-mansion-labs/react-native-nano-icons

A high-performance icon rendering solution for React Native and Expo that converts SVG files into optimized icon fonts at build time. It renders icons as native text glyphs to minimize overhead, supporting React Native 0.74+ (New Architecture), iOS 15.1+, Android API 24+, Web, and Expo. Features include multicolor icon support, an Expo Config Plugin, and dynamic linking for Over-The-Air (OTA) updates.

Tokens
15K
Snippets
43
Records
68
Agent score
68%

What's inside react-native-nano-icons

  1. Overview of Nano Icons

    main

    Nano Icons is a high-performance icon rendering solution for React Native and Expo. It is designed to solve the performance overhead associated with rendering many small, repeated icons (such as in lists, tab bars, or badges) by converting SVGs into an optimized icon font at build time.

    Key Benefits

    • Near-zero overhead: Bypasses the React component tree by rendering icons as a single native text glyph stack.
    • Custom Icon Support: Eliminates the manual management of font files by automatically converting your own SVG folder into a font.
    • High Performance: Significantly faster than react-native-svg for large numbers of icons because it avoids the cost of parsing and reconciling full React subtrees for every icon.
  2. Understand the Font Generation Pipeline

    main

    The library converts SVGs into a high-performance font-based rendering system through a five-stage build-time pipeline:

    1. SVG optimization: Simplifies the SVG structure and removes unnecessary tags.
    2. Geometry flattening: Uses Skia/pathops (via WebAssembly) to resolve transforms, clip paths, and overlapping shapes into simple path-only geometry.
    3. Color layer extraction: Separates each distinct fill color into its own layer.
    4. Font compilation: Compiles layers into a .ttf font file, mapping each layer to a private-use Unicode codepoint.
    5. Glyphmap generation: Creates a .glyphmap.json file that maps icon names to their respective codepoints, default colors, and metrics.

    At runtime, the native component stacks these glyph layers using CoreText (iOS) or Canvas.drawText (Android). On the web, they render as stacked <span> elements.

  3. iOS Rendering Paths: Standalone vs. Inline Icons

    main

    Nano Icons uses two distinct rendering paths on iOS to optimize performance:

    Standalone Icons (Common Case)

    • Detected when the icon is not inside a <Text> component.
    • Draws directly in NanoIconView's drawRect:.
    • Uses CTFontDrawGlyphs to render glyph layers at the cached baseline position.
    • Uses a coordinate-flip transform to map CoreText's Y-up system to UIKit's Y-down frame.

    Inline Icons

    • Detected by checking the nearest 3 ancestors for RCTParagraphComponentView.
    • Uses a lazily-created CALayer sublayer instead of a UIView to avoid the overhead of the responder chain and accessibility tree.
    • The CALayer.frame.origin.y is shifted upward based on the computed baseline offset from the parent's attributedText (considering ascender, lineHeight, and NSBaselineOffsetAttributeName).
    • The offset is calculated as the distance from the Yoga frame's bottom edge to the text baseline using fmod(frameBottom, lineHeight), clamped to zero if the icon is taller than the text line.
  4. How Nano Icons renders multi-color icons

    main

    Unlike traditional color font formats (like COLRv0/COLRv1) which have inconsistent platform support and limited runtime control, Nano Icons uses a subglyph layer stacking approach.

    The Mental Model

    1. Decomposition: At build time, a multi-color SVG is decomposed into multiple single-color layers.
    2. Subglyphs: Each layer is converted into a simple glyph and assigned a unique, private-use codepoint in a standard font file.
    3. Glyph Map: A JSON metadata file maps the original icon name to its constituent layers (codepoints and default colors).
    4. Runtime Rendering: The Icon component renders these subglyphs stacked using absolute positioning within a unified view box.

    Advantages

    • Predictability: Rendering logic is consistent across platforms because it doesn't rely on complex native color-font stacks.
    • Runtime Customization: You can override the color of specific layers via props (e.g., changing a character's clothing color) without needing multiple icon assets.
    • Performance: Uses the highly optimized native text rendering pipeline, which is roughly 3x faster than react-native-svg.
    // Example of the generated glyphmap structure
    {
      "meta": {
        "fontFamily": "string",
        "upm": 1000,
        "safeZone": 10,
        "startUnicode": 65536
      },
      "icons": {
        "iconName": {
          "adv": 1000, 
          "layers": [
            { "codepoint": 65536, "color": "#FF0000" },
            { "codepoint": 65537, "color": "#00FF00" }
          ]
        }
      }
    }
  5. Requirements for a reliable SVG-to-Font pipeline

    main

    To ensure an SVG converts into a reliable font glyph, your pipeline should implement the following transformations:

    1. Resolve/Flatten Shapes: Convert all shapes into explicit filled paths so no external references are required at render time.
    2. Coordinate Normalization: Ensure all shapes reside within a consistent coordinate system (mapping the SVG viewBox to the glyph EM square).
    3. Fill Rule Handling: Correctly interpret boolean fill rules like evenodd vs nonzero.
    4. Clipping/Masking: Either flatten the geometry by applying clipping to the path or discard unsupported constructs.
    5. Reference Expansion: Expand <use>, <symbol>, and <defs> references into actual geometry.
    6. Stroke Normalization: Optionally convert strokes into filled outlines, as fonts are fundamentally fill-based.
  6. Understand the Nano Icons font generation pipeline

    main

    The pipeline converts a directory of SVG files into a TrueType font and a glyphmap. Multi-color icons are decomposed into layers: each color layer of an icon is rendered as a separate glyph, which are then layered on top of each other at runtime.

    The transformation flow:

    1. SVG Input: Raw SVG files.
    2. Flattening: picosvg flattens transforms, resolves <use> and <clipPath>, and converts strokes to fills.
    3. Path Processing:
      • Evenodd paths are extracted and later restored to prevent contour loss during simplification.
      • Evenodd paths are converted to nonzero winding using a containment-based algorithm (calculating nesting depth via ray-casting).
      • Consecutive paths with the same color are merged into single compound paths to reduce glyph count.
    4. Placement & Transformation: Paths are scaled, centered, and flipped (SVG Y-down to Font Y-up) to fit the font's upm and safeZone.
    5. Output:
      • A .ttf file containing the glyphs.
      • A .glyphmap.json file mapping icon names to their codepoints and color layers.
  7. Understand Font Metrics and Alignment in Nano Icons

    main

    Nano Icons compiles icon fonts with specific metrics to ensure consistent sizing:

    • ascent = UPM
    • descent = 0

    Visual Behavior

    • Glyphs fill the entire em square from the baseline to the top (no descender space).
    • At any given fontSize, the glyph's visual height equals the font size.
    • On iOS, CTFontGetDescent returns 0.
    • On Android, paint.fontMetrics.descent returns 0.
    • The native _fitScale is approximately 1.0, and the _baselinePosition is located at the bottom edge of the view.
  8. The Nano Icons build pipeline

    main

    The project uses a three-stage pipeline to transform complex SVGs into high-performance native font icons:

    1. Geometry Compilation (SVG → Flattened Paths): Uses PathKit (via Pyodide/WASM) to perform heavy-duty geometry math. This stage flattens transforms, resolves clipPath via boolean operations, and simplifies contours. This ensures the geometry is "font-safe" before it reaches the font compiler. Note: SVG `` resolution is currently a known limitation.

    2. Layer Extraction (Flattened Paths → Layer SVGs): A Node.js process parses the flattened SVG DOM, splits shapes into layers based on fill color, and computes placement rules (UPM scaling, safe zones). It emits one small, deterministic SVG per layer.

    3. Font & Metadata Generation (Layer SVGs → Font + Glyphmap): Compiles a simple, universal glyph font (using glyf and cmap tables) and generates a glyphmap JSON file that maps icon names to their specific subglyph codepoints and default colors.

  9. Compare font table capabilities for icon rendering

    main

    When choosing or evaluating an icon font pipeline, understand the differences in font table support. This affects how color and geometry are handled:

    • Monochrome Fonts (Fantasticon, RNNanoIcons): These do not include COLR or CPAL tables. They rely on single-color glyphs. RNNanoIcons uses runtime composition (glyphmaps) to handle multicolor/layers rather than encoding them in the font file.
    • Native Color Fonts (IcoMoon): Includes COLR and CPAL tables, allowing for native color glyphs (COLRv0 layered glyphs with a palette) directly within the font file.

    Key Table Tags:

    • glyf: Contains the actual outline/path data. This is typically the largest contributor to file size.
    • cmap: Character map linking characters to glyphs.
    • COLR / CPAL: Used for native color layers and palettes (IcoMoon only).
  10. How dynamic linking works for Expo OTA updates

    main

    By default, icons are statically bundled into the native app. If you frequently update icons and want to use Expo Over-The-Air (OTA) updates without a full native rebuild, use linking: 'dynamic'.

    1. Configure: Set linking: 'dynamic' in your iconSets config.
    2. Build: The build process will still generate the .ttf and .glyphmap.json files, but they won't be linked to the native binary.
    3. Runtime: You must pass the font file (via require or a uri) as the second argument to createNanoIconSet so the library can register it at runtime.

    This allows you to ship new .ttf and .glyphmap.json files via an OTA update, and the app will pick them up without a new App Store/Play Store submission.

    {
      "iconSets": [
        {
          "inputDir": "./assets/icons/dynamic-ota-icons",
          "linking": "dynamic"
        }
      ]
    }
    import { createNanoIconSet } from "react-native-nano-icons";
    import glyphMap from "./dynamic-ota-icons.glyphmap.json";
    
    // Using a local file
    export const Icon = createNanoIconSet(glyphMap, require("./dynamic-ota-icons.ttf"));
    
    // OR using a remote URI
    // export const Icon = createNanoIconSet(glyphMap, { uri: "https://cdn.example.com/remote-nano-icons.ttf" });
  11. How Inline Icon Positioning Works in React Native

    main

    When a NanoIconView is nested inside a <Text> component, it is treated as an inline attachment. The positioning logic depends on your React Native version:

    React Native ≤ 0.82

    Uses the layout manager's baseline directly. The view's bottom aligns with the text baseline.

    React Native ≥ 0.83

    For inline views, the font.descender evaluates to 0. The view's bottom edge aligns with the bottom of the glyphRect.

    Note on Fabric: Fabric uses a two-pass measurement. The second pass shifts the final frame approximately 2–3 pt above the typographic descender line, meaning the actual distance from the frame bottom to the baseline is slightly smaller than |UIFont.descender|.