Skip to content

About

A slim Nerd Font fallback, built from Monokai Pro's icons

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

MonokaIconic

Builds a small, standalone Nerd Font fallback font from custom SVG icons, for use with Ghostty + Nerd-Font-aware tools like eza and mini.icons in Neovim.

It does not patch a whole font. It builds a slim font containing only the glyphs icon-mapping.json overrides or adds, meant to sit in your terminal's font-fallback chain after your primary font - everything you don't override keeps coming from Ghostty's own bundled Nerd Font Symbols, as normal.

Why a fallback font, and not a patched one

The original approach here was to patch icons directly into a copy of the primary text font (0xProto). That turned out to be the wrong shape for Ghostty specifically:

  • Ghostty only applies its own dynamic Nerd Font icon scaling (adjust-icon-height, and the sizing logic it added in 1.2 that mirrors the official Nerd Fonts patcher's constraints) to glyphs it resolves through its own implicit fallback - never to a font you name explicitly in font-family, whether as the primary font or as an explicit fallback entry. So a fully patched primary font gets no help from Ghostty at all; icons end up exactly whatever size you baked into the font file, with no config-side lever to fix it.
  • Different Nerd-Font-aware tools render the same icon from different codepoints. For example eza draws .css files from dev-css3 (U+E749), while Neovim's mini.icons draws them from md-language_css3 (U+F031C). A classic one-codepoint-per-icon patch can only ever satisfy one of them.

So instead: icon-mapping.json can list multiple Nerd Font codepoints per icon (one per consuming app, when they differ - see its _comment), this repo builds a tiny font with just those codepoints filled in, and you add that font as a fallback. Everything else - every codepoint you didn't touch - still comes from Ghostty's built-in Nerd Font Symbols, auto-scaled exactly as if you'd never patched anything. Since Ghostty won't auto-scale our glyphs either way (see above), patch_fonts.py overscans them past the nominal cell box by a tunable factor (--shrink, default 1.35) to compensate by hand.

Requirements

  • fontforge (with its Python scripting module), python3 with the fonttools and brotli packages, and curl. Nix is the easiest way to get all four without touching your system - see below - but it's not a hard requirement if you'd rather install them another way.
  • A licensed copy of Monokai Pro, for either a JetBrains IDE or VS Code (see below) - or your own SVG icon set, substituting for it.
  • Ghostty (or another terminal that supports font-fallback chains and Nerd Font codepoints) and, optionally, Neovim with mini.icons.

Getting the tools via Nix (recommended)

Nix with flakes enabled, and optionally direnv (with nix-direnv) - run direnv allow once in this directory and fontforge, python3 (with fonttools/brotli), and curl are on PATH automatically from then on. Without direnv, run nix develop yourself.

patch-fonts.sh also works standalone, outside the dev shell: if it doesn't find fontforge/python3 on PATH already, it re-execs itself inside a nix-shell that provides them, so plain ./patch-fonts.sh works even if you never ran nix develop - as long as Nix itself is installed.

Getting the tools without Nix

Install the four tools above through your usual package manager, then just run ./patch-fonts.sh / ./extract-monokai-icons.py / ./patch_fonts.py directly - they detect that fontforge and python3 are already on PATH and skip the nix-shell re-exec entirely, no flags needed.

  • Debian/Ubuntu: sudo apt install fontforge python3-fontforge python3-fonttools python3-brotli curl
  • Fedora: sudo dnf install fontforge python3-fonttools python3-brotli curl
  • Arch: sudo pacman -S fontforge python-fonttools python-brotli curl
  • macOS (Homebrew): brew install fontforge curl, then pip install fonttools brotli (or pipx install fonttools / a venv, per your usual Python setup) - Homebrew's fontforge bundles its own Python interpreter, so the fontforge -script invocations work regardless of your system Python; only the separate python3 -c ... step patch-fonts.sh uses to produce the .woff2 needs fonttools/brotli on a python3 that's actually on PATH.

Either way, verify it worked with fontforge --version and python3 -c "import fontTools, brotli" (no output = success) before running the build.

Quick start

direnv allow                 # or: nix develop - skip both if the tools are on PATH already
cp /path/to/your/monokai-pro/icons/*.svg customicons/   # see "Supplying your own icons" below
./patch-fonts.sh

This writes fonts-patched/MonokaIconic-Fallback.ttf (+ .woff2). Install it as a font and add it to Ghostty's fallback chain (also below).

Supplying your own icons

customicons/*.svg is not included in this repository and never will be. The icon artwork this project was built around comes from the Monokai Pro VS Code theme, which is a paid product - its icons aren't freely redistributable, so committing them here isn't an option. .gitignore excludes them explicitly; icon-mapping.json (which only encodes names and Nerd Font codepoints, not artwork) is committed as normal.

To use this repo you need to supply customicons/*.svg yourself, named to match the keys in icon-mapping.json's overrides and new_icons (e.g. customicons/python.svg). Three ways to do that:

Whichever way you get them, note that multi-tone source art gets flattened either way: patch_fonts.py compiles everything into a plain glyf-table TTF with no COLR/CPAL color-glyph tables, and a glyph outline in that kind of font is just a shape - it carries no color of its own. Any per-path fill colors in the source SVG (like archive's two-tone zipper) are discarded at build time; the terminal renders the glyph in a single color, same as it does for every other Nerd Font icon (whatever foreground color the app - eza, mini.icons, etc. - applies around it). So it's not worth seeking out multi-tone source icons specifically for this project.

Option A: copy straight from your own Monokai Pro JetBrains plugin install (recommended)

If you own the Monokai Pro plugin for a JetBrains IDE (PhpStorm, IntelliJ IDEA, WebStorm, ...), it ships the icons as plain individual SVG files already - no extraction needed, just copy them in. The plugin installs unpacked onto disk as a regular directory, under your IDE's plugin data directory:

  • Linux: ~/.local/share/JetBrains/<Product><Version>/<plugin-dir>/lib/icons/<palette>/
  • macOS: ~/Library/Application Support/JetBrains/<Product><Version>/<plugin-dir>/lib/icons/<palette>/
  • Windows: %APPDATA%\JetBrains\<Product><Version>\<plugin-dir>\lib\icons\<palette>\

<Product><Version> is e.g. PhpStorm2026.2; <plugin-dir> is whatever the plugin unpacked itself into (look for a directory whose name contains monokai-pro, e.g. monokai-pro-lifetime - the exact name has varied by license tier). <palette> picks the color scheme - there's a subdirectory per variant (monokai-pro, monokai-pro-filter-octagon, monokai-pro-monochrome, monokai-pro-light, ...); each is a complete, self-contained set, already colored.

Once you've found it:

cp "<plugin-dir>/lib/icons/monokai-pro/"*.svg customicons/

That's the whole process - no script, no build step. The filenames already match icon-mapping.json's icon names 1:1. If a plugin update ever reshuffles that directory layout, find ~/.local/share/JetBrains -ipath '*monokai-pro*/lib/icons*' -name '*.svg' (adjust the base path per OS above) will usually relocate it.

Option B: extract from your own Monokai Pro VS Code extension

If you only have the VS Code extension, its icons ship differently - as a single colored webfont plus a JSON file mapping each icon name to a glyph codepoint and a hex color, not as individual SVGs. VS Code extensions are plain installed directories too, so you can read these two files straight out of your local install without needing the theme active or VS Code running:

  • <extension dir>/icon-themes/monokai-pro-icons.woff
  • <extension dir>/icon-themes/Monokai Pro icon-theme.json (or any of the other *icon-theme.json palette variants in that same directory, e.g. a "Filter" or "monochrome" variant, if you'd rather use a different color scheme)

<extension dir> is normally:

  • Linux/macOS: ~/.vscode/extensions/monokai.theme-monokai-pro-vscode-<version>/
  • Windows: %USERPROFILE%\.vscode\extensions\monokai.theme-monokai-pro-vscode-<version>\

Then run the extraction script from this repo (inside the dev shell):

./extract-monokai-icons.py \
  --woff "<extension dir>/icon-themes/monokai-pro-icons.woff" \
  --theme-json "<extension dir>/icon-themes/Monokai Pro icon-theme.json"
usage: extract-monokai-icons.py [-h] --woff WOFF --theme-json THEME_JSON
                                 [--out-dir OUT_DIR] [--mapping MAPPING]
                                 [--all] [--force]

  --woff WOFF             path to the extension's monokai-pro-icons.woff
  --theme-json THEME_JSON path to one of the extension's '*icon-theme.json'
                          variants (picks the color palette)
  --out-dir OUT_DIR       where to write <icon-name>.svg files
                          (default: ./customicons)
  --mapping MAPPING       icon-mapping.json to read the needed icon names
                          from (default: ./icon-mapping.json)
  --all                   extract every icon the theme json defines, instead
                          of only the ones icon-mapping.json references
  --force                 overwrite SVGs that already exist in --out-dir

By default it only extracts the icons icon-mapping.json actually references (skipping ones that already exist, unless --force), and warns about any it couldn't find in the theme JSON.

This gets you a single-color SVG per icon, since the VS Code webfont format only carries one color per glyph to begin with. As covered above, the font build flattens multi-tone source art to one color regardless of where the SVG came from, so this is really only a fallback for when all you have is the VS Code extension, not a lesser version of Option A for the purposes of this project.

Option C: supply your own SVG icon set

Any SVG works as long as it's a single <svg viewBox="..."><path d="..." fill="..."/></svg>-shaped file (multiple <path> elements are fine too, e.g. if that's just how your source draws the shape - but see above, their individual fill colors won't survive into the font) named <icon-name>.svg matching an icon-mapping.json key, placed in customicons/. patch_fonts.py scales and centers whatever outline it finds, so exact source dimensions don't matter.

icon-mapping.json

  • overrides: { "<icon-name>": "<nf-glyph-name>" }, or { "<icon-name>": ["<nf-glyph-name>", ...] } when different apps render that file type from different Nerd Font codepoints (css, markdown, archive, image, and most language logos among them - see the file's own _comment). <icon-name> must have a matching customicons/<icon-name>.svg. Glyph names come from Nerd Fonts' glyphnames.json (cached locally under .nf-cache/ on first build).
  • new_icons: a plain list of icon names with no existing Nerd Font equivalent. Each gets a fresh codepoint allocated in the Supplementary Private Use Area-A (starting at U+F0001, outside every range Nerd Fonts itself uses), in list order.

Edit freely and re-run ./patch-fonts.sh.

Building

Usage: patch-fonts.sh [options]

  -o, --output-dir DIR     write the built font here (default: ./fonts-patched)
  -n, --name NAME          output font basename, without extension (default: MonokaIconic-Fallback)
  -m, --mapping PATH       path to icon-mapping.json (default: ./icon-mapping.json)
  -i, --icons-dir DIR      directory of source SVGs (default: ./customicons)
  -c, --cache-dir DIR      directory to cache glyphnames.json in (default: ./.nf-cache)
      --shrink FACTOR      glyph overscan factor, see patch_fonts.py --help (default: 1.35)
      --no-woff2           skip building the .woff2 variant
  -h, --help                show this help

patch-fonts.sh is a thin wrapper that fetches glyphnames.json if needed, calls patch_fonts.py to build the .ttf, then converts it to .woff2. It re-execs itself inside nix-shell automatically if fontforge/python3 aren't already on PATH - handy for running it standalone outside the dev shell.

patch_fonts.py (invoke directly for finer control, e.g. to retune sizing without touching the shell wrapper):

usage: patch_fonts.py -o OUTPUT [--mapping MAPPING] [--icons-dir ICONS_DIR]
                       [--cache-dir CACHE_DIR] [--family-name FAMILY_NAME]
                       [--shrink SHRINK] [--em EM] [--ascent ASCENT]
                       [--descent DESCENT] [--cell-width CELL_WIDTH]

  -o, --output OUTPUT      output font path
  --mapping MAPPING        path to icon-mapping.json
  --icons-dir ICONS_DIR    directory of source SVGs
  --cache-dir CACHE_DIR    directory holding glyphnames.json
  --family-name NAME       font family name to embed (default: 'MonokaIconic Fallback')
  --shrink SHRINK          glyph overscan factor (default: 1.35)
  --em / --ascent / --descent / --cell-width
                           override the font's cell metrics directly
                           (defaults: 1000 / 800 / 200 / em*0.62)

The cell metrics are fixed, reasonable monospace proportions, not read from any real font file - terminals size their grid from the primary font and only use a fallback glyph's outline, not its declared advance width, so there's nothing to gain by tying these to a specific font. --shrink is the one you'll actually want to tune: raise it if icons still look small in your terminal, lower it if they start clipping into neighboring cells.

Installing the font + configuring Ghostty

mkdir -p ~/.local/share/fonts/MonokaIconic-Fallback
cp fonts-patched/MonokaIconic-Fallback.ttf ~/.local/share/fonts/MonokaIconic-Fallback/
fc-cache -f ~/.local/share/fonts/MonokaIconic-Fallback

Then in your Ghostty config, list it as a fallback after your primary font (order matters - this is what makes it a fallback rather than a primary):

font-family = "<your primary font>"
font-family = "MonokaIconic Fallback"

Restart Ghostty (not just ghostty +reload-config - the font-fallback chain is resolved at startup). Anything you overrode in icon-mapping.json now renders from customicons/; everything else keeps coming from Ghostty's bundled Nerd Font Symbols, unaffected.

Neovim's mini.icons needs no special configuration for this - it just writes the codepoints icon-mapping.json targets into the buffer, and however the terminal resolves and sizes those codepoints is entirely Ghostty's call, the same as for eza.

test/

A folder of empty files, one representative name/extension per icon in icon-mapping.json's overrides (file.css, Dockerfile, package.json, ...) - every overridden icon has exactly one file here that should trigger it. Handy for eyeballing icon coverage with eza --icons or opening the folder in Neovim once a build is installed. There's no script for this - if you add or rename an override in icon-mapping.json, just add or rename the matching file here by hand.

License

The code and data in this repository (patch_fonts.py, patch-fonts.sh, extract-monokai-icons.py, icon-mapping.json) are MIT-licensed - see LICENSE. That does not cover whatever you put in customicons/: if you extracted those from Monokai Pro, they're subject to that product's own license, and are yours to use locally, not to redistribute.

About

A slim Nerd Font fallback, built from Monokai Pro's icons

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages