Patching Berkeley Mono with Nerd Fonts for Windows Terminal/WSL
How to patch Berkeley Mono with Nerd Fonts, fix OpenType name tables to preserve Windows font families, and get bold/italic working in Windows Terminal.
I bought Berkeley Mono v2 ages ago and finally got around to installing it on my Windows Terminal/WSL setup. When I set it as the font in Windows Terminal, my Powerlevel10k prompt rendered empty squares instead of powerline arrows and git branch glyphs. Unsurprisingly, Berkeley Mono doesn’t include the Nerd Fonts glyphs required by a lot terminal prompts.
The official Nerd Fonts patcher ships as a Docker image. Richard Ilagan’s blog post documents the standard command to patch the font. Running that command produces a font that fails to render correctly in Windows. OpenType naming rules, the Windows font cache, and font smoothing utilities require a weird Windows-specific patching approach.
Use TTF source files instead of OTF
US Graphics provides Berkeley Mono in both .otf and .ttf formats. The patcher accepts OTF files and runs without errors, but the resulting font renders garbage characters in Windows Terminal. Thankfully this is a known issue in the Nerd Fonts repository. You need to download the TTF format from the US Graphics portal and use those as your source files.
Bypassing the 31-character family name limit
Running the patcher with the standard flags triggers a name length error:
docker run --rm \
-v ./original:/in \
-v ./patched:/out \
nerdfonts/patcher \
--complete \
--single-width-glyphs \
--adjust-line-heightThe output fails with:
ERROR: ====-< Family (ID 1) too long (37 > 31): BerkeleyMono Nerd Font Mono SemiLightName ID 1 in the OpenType name table is limited to 31 characters. The patcher’s default behavior concatenates the original family name, “Nerd Font Mono”, and the variant name (“SemiLight”), exceeding the limit.
Using the patcher’s --name FORCE_NAME flag overrides the family name, but it collapses all patched weights into a single “Regular” subfamily, destroying the weight metadata. The correct flag is --makegroups 0. This keeps the family name stable across all variants and correctly pushes the weight data into the SubFamily field:
docker run --rm -v ./original:/in -v ./patched:/out \
nerdfonts/patcher --complete --single-width-glyphs \
--adjust-line-height --makegroups 0Adding bold and italic weights for Windows Terminal
With the patched TTF installed, the prompt renders correctly, but bold text appears at the same weight as regular text.
Unlike terminals that allow you to explicitly define a bold font file, Windows Terminal selects a single font family and asks the DirectWrite API for its bold variant. If you only install the SemiLight weight (weight 380), Windows cannot locate a heavier weight. It either synthesizes a faux-bold that looks identical to the original, or it just renders SemiLight.
To get working bold text, you have to download a heavier TTF weight, patch it, and install it alongside your base font. I use SemiLight for regular text and SemiBold (weight 600) for bold text. The 220-point weight difference produces visibly thicker glyphs without relying on Windows to synthesize them. You also need to patch and install the corresponding Oblique variants, or Windows will fall back to a system italic font (or just the same font).
Rewriting the SubFamily field for RIBBI compliance
After patching SemiLight, SemiLight-Oblique, SemiBold, and SemiBold-Oblique, the Windows font picker displayed two separate entries: “BerkeleyMono Nerd Font Mono” and “BerkeleyMono Nerd Font Mono SemiLight”.
Dumping the name table on the patcher output confirms the cause. Name ID 2 (Subfamily) carries the original variant string straight through from the source TTF:
$ otfinfo -n BerkeleyMonoNerdFontMono-SemiLight.ttf
Family: BerkeleyMono Nerd Font Mono
Subfamily: SemiLight
Full name: BerkeleyMono Nerd Font Mono SemiLight
PostScript name: BerkeleyMonoNerdFontMono-SemiLight
Preferred family: BerkeleyMono Nerd Font Mono
Preferred subfamily: SemiLightOpenType uses a convention called RIBBI, which stands for the four canonical subfamilies: Regular, Italic, Bold, and Bold Italic. When a font’s SubFamily (name ID 2) matches one of these four, Windows groups the variants into a single family. When the SubFamily is “SemiLight” or “Medium”, Windows often splits the fonts into separate families.
To force Windows to group them, you have to rewrite the SubFamily field on the patched fonts to use the canonical RIBBI names:
| Source variant | RIBBI slot |
|---|---|
| SemiLight | Regular |
| SemiLight Oblique | Italic |
| SemiBold | Bold |
| SemiBold Oblique | Bold Italic |
This renaming means typographic applications like Photoshop will display the weight as “Bold” instead of “SemiBold”. For terminal use, this inaccuracy is invisible.
Removing Preferred Family and Styles metadata
After applying the RIBBI subfamilies and reinstalling, the Windows font picker split the fonts into four distinct entries.
Dumping the name table on the renamed font shows the culprit. IDs 1 and 2 are correct, but IDs 16 and 17 still carry the patcher’s original strings:
$ otfinfo -n BerkeleyMonoNF-Regular.ttf
Family: BerkeleyMonoNF
Subfamily: Regular
Full name: BerkeleyMonoNF Regular
PostScript name: BerkeleyMonoNF-Regular
Preferred family: BerkeleyMono Nerd Font Mono
Preferred subfamily: SemiLightThe patcher includes name IDs 16 (Preferred Family) and 17 (Preferred Styles) in its output. These fields retained the original variant names. When IDs 16 and 17 are present, Windows constructs display names by concatenating them, ignoring the RIBBI values in IDs 1 and 2. That’s how the four-way split happens: each variant’s ID 16 + ID 17 reads as a different family.
The fix is to delete name IDs 16 and 17 entirely. Without them, Windows falls back to ID 1 and ID 2, which now contain the correct RIBBI values. You also need to remove ID 18 (Compatible Full), which carries similar legacy data.
The final state after the rewrite has only RIBBI-compliant fields:
$ otfinfo -n BerkeleyMonoNF-Regular.ttf
Family: BerkeleyMonoNF
Subfamily: Regular
Full name: BerkeleyMonoNF Regular
PostScript name: BerkeleyMonoNF-RegularClearing the Windows FontCache
Windows aggressively caches font metadata. Uninstalling the split fonts and installing the corrected ones often results in the system continuing to display the old names. The FontCache service retains the original metadata for fast boot enumeration.
You can clear the cache manually in PowerShell:
Stop-Service FontCache
Stop-Service FontCache3.0.0.0 -ErrorAction SilentlyContinue
Remove-Item -Force -Recurse $env:WinDir\ServiceProfiles\LocalService\AppData\Local\FontCache\*
Remove-Item -Force $env:WinDir\System32\FNTCACHE.DAT -ErrorAction SilentlyContinue
Start-Service FontCache
Start-Service FontCache3.0.0.0 -ErrorAction SilentlyContinueRebooting the machine achieves the same result. Windows Terminal also caches font lookups per-process, requiring you to fully close the application, not just the current tab.
To bypass the cache entirely, I renamed the font family to BerkeleyMonoNF. Because this name had never been installed, the Windows cache had no conflicting metadata to apply to it.
Excluding patched fonts from MacType
After a clean installation, I noticed faint vertical artifacts next to adjacent letters like tt and ff. I use MacType for system-wide font smoothing (oh, the troubles of OLED), and the artifacts were caused by its rendering engine.
The Nerd Fonts patcher runs ttfautohint over its output, replacing the source font’s manual hinting. MacType applies its own subpixel anti-aliasing logic on top of this. The combination of ttfautohint and MacType produces visual artifacts that do not occur on the stock fonts.
To resolve this, right-click the MacType tray icon, open the wizard, and add WindowsTerminal.exe and OpenConsole.exe to the exclusion list. Restart MacType and Windows Terminal, and the artifacts disappear.
The complete patching scripts
The entire process requires two scripts running in the same directory. Both rely exclusively on Docker.
patch-fonts.sh runs the Nerd Fonts patcher against the TTFs in the source directory, then executes the renaming script.
#!/usr/bin/env bash
# Usage: ./patch-fonts.sh <source-dir> <output-dir>
set -euo pipefail
if [ "$#" -ne 2 ]; then
echo "usage: $0 <source-dir> <output-dir>" >&2
exit 1
fi
SRC="$(cd "$1" && pwd)"
OUT="$(mkdir -p "$2" && cd "$2" && pwd)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
docker pull -q nerdfonts/patcher >/dev/null
# Patch with Nerd Font glyphs
docker run --rm -v "$SRC:/in" -v "$OUT:/out" \
nerdfonts/patcher \
--complete --single-width-glyphs --adjust-line-height \
--makegroups 0 --quiet
# Rewrite name table for RIBBI compliance
docker run --rm -v "$OUT:/out" -v "$SCRIPT_DIR/fix-font-names.py:/fix.py" \
--entrypoint fontforge nerdfonts/patcher \
-script /fix.py /out
ls "$OUT"/*.ttffix-font-names.py executes inside the patcher’s Docker image using its bundled fontforge environment. It renames the family to BerkeleyMonoNF, maps the source variants to RIBBI subfamilies, drops name IDs 16 and 17, and sets the OS/2 weight class.
#!/usr/bin/env python3
"""Rewrite the name table on patched TTFs so they form a single RIBBI family."""
import sys, os, re, glob
import fontforge
FAMILY = "BerkeleyMonoNF"
# Source variant -> (RIBBI subfamily, italic angle, OS/2 weight class)
VARIANT_MAP = {
"SemiLight": ("Regular", 0, 380),
"SemiLightOblique": ("Italic", -10, 380),
"SemiBold": ("Bold", 0, 600),
"SemiBoldOblique": ("Bold Italic", -10, 600),
}
DROP_KEYS = {"Preferred Family", "Preferred Styles"}
def fix_one(path):
m = re.match(r"BerkeleyMonoNerdFontMono-(\w+)\.ttf$", os.path.basename(path))
if not m or m.group(1) not in VARIANT_MAP:
return False
ribbi, italic_angle, weight = VARIANT_MAP[m.group(1)]
psname_subfamily = ribbi.replace(" ", "")
f = fontforge.open(path)
f.familyname = FAMILY
f.fullname = f"{FAMILY} {ribbi}"
f.fontname = f"{FAMILY}-{psname_subfamily}"
override = {
"Family": FAMILY,
"SubFamily": ribbi,
"Fullname": f"{FAMILY} {ribbi}",
"PostScriptName": f.fontname,
"Compatible Full": f"{FAMILY} {ribbi}",
"UniqueID": f"{FAMILY}-{psname_subfamily} v1",
}
f.sfnt_names = tuple(
(lang, k, override.get(k, v))
for lang, k, v in f.sfnt_names
if k not in DROP_KEYS
)
f.os2_weight = weight
style = 0
if "Italic" in ribbi: style |= 0x0001
if "Bold" in ribbi: style |= 0x0020
if ribbi == "Regular": style |= 0x0040
f.os2_stylemap = style
f.macstyle = (2 if "Italic" in ribbi else 0) | (1 if "Bold" in ribbi else 0)
if italic_angle and f.italicangle == 0:
f.italicangle = italic_angle
new_path = os.path.join(os.path.dirname(path), f"{f.fontname}.ttf")
f.generate(new_path, flags=("opentype",))
if new_path != path:
os.remove(path)
print(f"✓ {os.path.basename(new_path)} → {ribbi}, weight={weight}")
return True
if __name__ == "__main__":
out_dir = sys.argv[1] if len(sys.argv) > 1 else "."
for p in glob.glob(os.path.join(out_dir, "BerkeleyMonoNerdFontMono-*.ttf")):
fix_one(p)To run the patcher, place the TTF files in an incoming directory and execute the bash script:
chmod +x patch-fonts.sh
mkdir incoming
cp ~/Downloads/BerkeleyMono-{SemiLight,SemiLight-Oblique,SemiBold,SemiBold-Oblique}.ttf incoming/
./patch-fonts.sh incoming/ patched/The script outputs the patched and renamed fonts into the patched/ directory. Copy those files to Windows, install them, and set the Windows Terminal font to BerkeleyMonoNF.
Adjusting for other fonts
This naming table structure applies to any commercial font patched with Nerd Fonts. The patcher consistently emits non-RIBBI subfamily names, which causes Windows to fragment the font family. If you patch another font, you can use the same scripts by modifying the VARIANT_MAP dict and the filename regex in the Python file.