Add a font with the branch drawing symbols for the demo recordings

The terminal that vhs records is xterm.js running in a browser, and the
xterm.js that ttyd bundles doesn't draw the branch drawing symbols
itself. So they have to come from a font, and SauceCodePro NF doesn't
have them, nor does any font that macOS comes with. The detailed commit
graph shows up as garbage.

Newer versions of xterm.js do draw the symbols, but only in the WebGL
renderer. vhs forces the canvas renderer, and xterm.js has removed that
since, so moving to a newer xterm.js would mean patching both ttyd and
vhs.

The Flog Symbols font has the symbols, but it draws its lines for a
smaller cell than the one xterm.js uses at our font settings. As a
fallback font, it leaves a gap at the edges of every cell, and each line
of the graph comes out dashed.

Add a copy of the font that fits the cells of the recordings. xterm.js
clips each character to its row, so the vertical strokes reach well
past the top and bottom of the cell and end exactly at the row's edges.
It doesn't clip a character to its cell horizontally, so the horizontal
strokes reach only a little way into the neighbouring cells. Any
further, and they would show past the start of a bend in the next cell.

Give the font a bold face with the same outlines. lazygit draws the
lines of the selected commit in bold, and without a bold face the
browser makes the symbols bold itself by thickening them. That leaves
gaps where they meet.

Add the script that made the font, and list the font after
SauceCodePro NF in demo/settings.tape so that the browser takes the
symbols from it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller
2026-10-01 17:28:31 +02:00
co-authored by Claude Opus 5.5
parent db33521c1b
commit 83b144ccc1
6 changed files with 144 additions and 2 deletions
Binary file not shown.
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 rbong
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+116
View File
@@ -0,0 +1,116 @@
"""Make a copy of the Flog Symbols font whose lines fill a terminal cell.
The demo recordings draw the commit graph with the branch drawing symbols, and
the terminal that vhs records takes them from the Flog Symbols Demo font in
demo/fonts. This script made its regular and bold faces from FlogSymbols.ttf of
https://github.com/rbong/flog-symbols (see LICENSE-FlogSymbols):
pip install fonttools
python3 demo/fonts/fit_flog_symbols.py FlogSymbols.ttf "Flog Symbols Demo" \\
demo/fonts
Flog Symbols draws its lines for a cell that is 620 units wide and reaches from
-206 to 1006 units. A terminal that takes the symbols from a fallback font
draws them in the cells of its main font, and if those are larger, the lines
stop short of the cell edges and leave gaps between neighbouring cells.
So move the ends of the strokes that run to an edge of the cell out to the
terminal's cell edges, and centre everything else in the cell. The circles and
bends in the middle of the cell keep their shape.
"""
import os
import sys
from fontTools.ttLib import TTFont
# The cell of the symbols, in font units
LEFT, RIGHT, BOTTOM, TOP = -6, 626, -206, 1006
# The width of the terminal cells in the recordings, in font units: at the font
# size in demo/settings.tape they are 16 pixels wide (SauceCodePro's 14.4
# pixels, plus the pixel of letter spacing that vhs adds, rounded up), which is
# 16/24 of an em. Regenerate the font if the font size changes.
CELL_WIDTH = 667
# How far the horizontal strokes reach into the neighbouring cells, in font
# units. xterm.js doesn't clip a character to its cell horizontally, so this
# has to stay short of where the bends in a neighbouring cell begin. In the
# recordings, less than 30 leaves a dim line where two cells meet, and 35 or
# more opens a dark gap there.
HORIZONTAL_OVERLAP = 30
# How far past the top and bottom of the symbols' cell the vertical strokes
# reach, in font units. xterm.js clips a character to its row, so this only
# has to be more than the terminal's row sticks out beyond the symbols' cell.
VERTICAL_REACH = 400
# Points this close to an edge of the symbols' cell belong to the end of a
# stroke that runs to that edge
EDGE_TOLERANCE = 30
def main():
flog_path, family, output_dir = sys.argv[1:4]
font = TTFont(flog_path)
fit_to_cell(font)
# The bold face has the same outlines. Without one, the browser makes the
# symbols of bold text bold itself by thickening them, and that leaves gaps
# where they meet.
for style in ("Regular", "Bold"):
set_style(font, family, style)
font.save(os.path.join(output_dir, f"{family.replace(' ', '')}-{style}.ttf"))
def fit_to_cell(font):
glyf = font["glyf"]
# Centre the symbols in the terminal's cell
dx = round((CELL_WIDTH - (RIGHT + LEFT)) / 2)
for name in font.getGlyphOrder():
glyph = glyf[name]
if glyph.numberOfContours <= 0:
continue
coordinates = glyph.coordinates
for i, (x, y) in enumerate(coordinates):
if x <= LEFT + EDGE_TOLERANCE:
x = -HORIZONTAL_OVERLAP
elif x >= RIGHT - EDGE_TOLERANCE:
x = CELL_WIDTH + HORIZONTAL_OVERLAP
else:
x += dx
if y <= BOTTOM + EDGE_TOLERANCE:
y -= VERTICAL_REACH
elif y >= TOP - EDGE_TOLERANCE:
y += VERTICAL_REACH
coordinates[i] = (x, y)
glyph.recalcBounds(glyf)
font["hmtx"][name] = (CELL_WIDTH, glyph.xMin)
def set_style(font, family, style):
bold = style == "Bold"
postscript_name = f"{family.replace(' ', '')}-{style}"
full_name = family if not bold else f"{family} {style}"
for record in font["name"].names:
if record.nameID == 1:
record.string = family
elif record.nameID == 2:
record.string = style
elif record.nameID == 4:
record.string = full_name
elif record.nameID in (3, 6):
record.string = postscript_name
os2 = font["OS/2"]
os2.usWeightClass = 700 if bold else 400
fs_bold, fs_regular = 1 << 5, 1 << 6
os2.fsSelection &= ~(fs_bold | fs_regular)
os2.fsSelection |= fs_bold if bold else fs_regular
font["head"].macStyle = 1 if bold else 0
if __name__ == "__main__":
main()
+4 -2
View File
@@ -2,8 +2,10 @@
# demo/record_demo.sh generates for a demo sources this file.
# Use the non-Mono Nerd Font variant; the Mono variant renders the icons too
# small.
Set FontFamily "SauceCodePro NF"
# small. The terminal that vhs records doesn't draw the branch drawing symbols
# of the commit graph itself, so they come from the second font, which is in
# demo/fonts.
Set FontFamily "SauceCodePro NF,Flog Symbols Demo"
Set FontSize 24
Set LineHeight 1.0
Set Padding 20
+3
View File
@@ -20,6 +20,9 @@ go install github.com/charmbracelet/vhs@v0.11.0
wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/SourceCodePro.tar.xz && \
tar -xf SourceCodePro.tar.xz -C ~/Library/Fonts && \
rm SourceCodePro.tar.xz
# font with the branch drawing symbols of the commit graph
cp demo/fonts/FlogSymbolsDemo-*.ttf ~/Library/Fonts
```
## Creating a demo