Skip to content

Instantly share code, notes, and snippets.

@Thomashighbaugh
Created April 28, 2026 01:56
Show Gist options
  • Select an option

  • Save Thomashighbaugh/aebd0465b3be540f4a97df646aabb930 to your computer and use it in GitHub Desktop.

Select an option

Save Thomashighbaugh/aebd0465b3be540f4a97df646aabb930 to your computer and use it in GitHub Desktop.
Justfile + icon font -> HTML page allowing you preview and copy the icon from the comfort of your browser. Also creates a odepointd list and markdown file.
#!/usr/bin/env bash
if [ "$#" -lt 1 ]; then
echo "Usage: $0 fontfile1.ttf [fontfile2.otf ...]"
echo
exit 1
fi
# Check for required commands
command -v ttx >/dev/null 2>&1 || { echo >&2 "Error: ttx (fonttools) is required but not installed. Install with: pip install fonttools"; exit 1; }
command -v xmllint >/dev/null 2>&1 || { echo >&2 "Error: xmllint is required but not installed."; exit 1; }
for fontfile in "$@"; do
if [[ ! -f "$fontfile" ]]; then
echo "File not found: $fontfile"
continue
fi
echo "Processing font: $fontfile"
# Convert font to XML temporarily
ttx -o temp.ttx "$fontfile" >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Failed to convert font to XML using ttx."
continue
fi
# Extract cmap info with xmllint
# The cmap table contains unicode to glyph mappings.
# We parse the <map> elements inside the <cmap_format_4> or <cmap_format_12> subtables.
echo "Unicode Codepoints and Glyph Names:"
# Extract codepoint + name pairs:
xmllint --xpath '//cmap/cmap_format_12/map | //cmap/cmap_format_4/map' temp.ttx 2>/dev/null | \
sed -n 's/.*code="0x\([0-9A-Fa-f]\+\)".*name="\([^"]*\)".*/U+\1 \2/p' | \
while read -r codepoint_u glyph_name; do
# Strip 'U+' to get the raw hex using bash substitution
codepoint_hex="${codepoint_u#U+}"
# Convert hex codepoint to decimal for logic checks
codepoint_dec=$((16#$codepoint_hex))
# Print codepoint, glyph name and (if possible) the glyph character
# Use 65535 instead of 0xFFFF for POSIX [ ... ] compatibility
if [ "$codepoint_dec" -le 65535 ]; then
# Format hex to 4 digits, then feed dynamically to printf's unicode escape sequence
hex_padded=$(printf "%04X" "$codepoint_dec")
printf -v char "\\u$hex_padded"
else
# Format hex to 8 digits, then feed dynamically to printf's unicode escape sequence
hex_padded=$(printf "%08X" "$codepoint_dec")
printf -v char "\\U$hex_padded"
fi
# Some terminals might not render the char correctly, so proceed carefully.
echo -e "$codepoint_u $glyph_name \t $char"
done
# Cleanup
rm -f temp.ttx
echo "---------------------------------------"
done
# By default, list available commands when just running `just`
default:
@just --list
# Extract font codepoints to a markdown file (Usage: just make-md font.ttf)
make-md font_file:
#!/usr/bin/env bash
font="{{font_file}}"
if [[ ! -x "./codepoints.sh" ]]; then
echo "Error: ./codepoints.sh not found or not executable. Run 'chmod +x codepoints.sh'."
exit 1
fi
if [[ ! -f "$font" ]]; then
echo "Error: Font file '$font' not found!"
exit 1
fi
out="${font%.*}.md"
echo "Generating $out..."
./codepoints.sh "$font" > "$out"
echo "Done! Saved to $out"
# Open icons in rofi and copy selection to clipboard (Usage: just rofi font.ttf)
rofi font_file:
#!/usr/bin/env bash
font="{{font_file}}"
if [[ ! -x "./codepoints.sh" ]]; then
echo "Error: ./codepoints.sh not found. Run 'chmod +x codepoints.sh'."
exit 1
fi
if [[ ! -f "$font" ]]; then
echo "Error: Font file '$font' not found!"
exit 1
fi
selection=$(./codepoints.sh "$font" | grep '^U+' | rofi -dmenu -i -p "Copy Icon:")
if [[ -n "$selection" ]]; then
icon=$(echo "$selection" | awk '{print $NF}' | tr -d '\n')
if command -v wl-copy >/dev/null 2>&1; then
echo -n "$icon" | wl-copy
echo "Copied '$icon' to clipboard (Wayland: wl-copy)"
elif command -v xclip >/dev/null 2>&1; then
echo -n "$icon" | xclip -selection clipboard
echo "Copied '$icon' to clipboard (X11: xclip)"
else
echo "Selected icon: $icon"
echo "(Note: 'wl-copy' or 'xclip' is required to automatically copy to clipboard)"
fi
fi
# Create a responsive dark-theme HTML grid with embedded font + Duotone support (Usage: just make-html font.ttf)
make-html font_file:
#!/usr/bin/env bash
font="{{font_file}}"
if [[ ! -x "./codepoints.sh" ]]; then
echo "Error: ./codepoints.sh not found. Run 'chmod +x codepoints.sh'."
exit 1
fi
if [[ ! -f "$font" ]]; then
echo "Error: Font file '$font' not found!"
exit 1
fi
out="${font%.*}.html"
echo "Generating HTML visualizer at $out..."
# Determine the CSS format tag
ext="${font##*.}"
format="truetype"
[[ "${ext,,}" == "otf" ]] && format="opentype"
# Base64 encode the font file directly
b64_font=$(base64 "$font" | tr -d '\n')
# Generate HTML Head, CSS, JS, and start Grid
cat <<EOF > "$out"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Icon Reference: $font</title>
<style>
@font-face {
font-family: 'EmbeddedFont';
src: url('data:font/$ext;charset=utf-8;base64,$b64_font') format('$format');
font-weight: normal; font-style: normal;
}
:root {
--bg: #121212; --fg: #e0e0e0; --accent: #60a5fa;
--card-bg: #1e1e1e; --card-border: #333; --card-hover: #262626;
--hex-bg: #2d2d2d; --hex-fg: #9ca3af;
--btn-bg: #374151; --btn-hover: #4b5563; --btn-active: #1f2937;
}
body { font-family: system-ui, -apple-system, sans-serif; background: var(--bg); color: var(--fg); margin: 0; padding: 2rem; }
h1 { text-align: center; font-size: 2rem; color: #fff; margin-bottom: 2rem; }
/* Responsive Grid System */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 1.5rem;
max-width: 1800px;
margin: 0 auto;
}
/* Icon Cards */
.card {
background: var(--card-bg); border: 1px solid var(--card-border); border-radius: 12px;
padding: 1.5rem; display: flex; flex-direction: column; align-items: center; text-align: center;
transition: transform 0.2s, border-color 0.2s, background 0.2s; box-shadow: 0 4px 6px rgba(0,0,0,0.3);
}
.card:hover { transform: translateY(-3px); border-color: var(--accent); background: var(--card-hover); }
/* Card Contents */
.icon {
font-family: 'EmbeddedFont'; font-size: 3.5rem; color: var(--fg);
margin-bottom: 1rem; line-height: 1; position: relative;
display: flex; justify-content: center; align-items: center;
width: 1.2em; height: 1.2em; transition: color 0.2s;
}
.card:hover .icon { color: var(--accent); }
/* Duotone Layer Support */
.icon.duotone .primary { position: relative; z-index: 2; }
.icon.duotone .secondary { position: absolute; opacity: 0.4; z-index: 1; }
.name { font-size: 0.95rem; font-weight: 500; margin-bottom: 0.5rem; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; height: 2.3em; }
.hex { font-family: monospace; font-size: 0.85rem; color: var(--hex-fg); background: var(--hex-bg); padding: 0.2rem 0.5rem; border-radius: 4px; margin-bottom: 1rem; }
/* Button */
.copy-btn { cursor: pointer; background: var(--btn-bg); color: #fff; border: none; padding: 0.6rem 1rem; border-radius: 6px; font-size: 0.9rem; font-weight: 500; width: 100%; transition: all 0.2s; }
.copy-btn:hover { background: var(--btn-hover); }
.copy-btn:active { background: var(--btn-active); transform: scale(0.97); }
</style>
<script>
// Automatically copy and show a green "Copied!" feedback
function copyText(button, text) {
navigator.clipboard.writeText(text).then(() => {
const originalText = button.innerText;
button.innerText = "Copied!";
button.style.background = "#059669";
setTimeout(() => {
button.innerText = originalText;
button.style.background = "";
}, 1500);
});
}
</script>
</head>
<body>
<h1>Icon Reference: $font</h1>
<div class="grid">
EOF
# Parse codepoints.sh output and append HTML Cards with Awk
# We load everything into memory first so we can intelligently pair Duotone layers
./codepoints.sh "$font" | grep '^U+' | awk -F '\t' '
BEGIN { count = 0; }
{
char_val = $2;
gsub(/^[ \t]+|[ \t]+$/, "", char_val);
str = $1;
gsub(/^[ \t]+|[ \t]+$/, "", str);
idx = index(str, " ");
hex_val = substr(str, 1, idx-1);
name_val = substr(str, idx+1);
# Store properties in memory
hex_map[hex_val] = char_val;
name_map[hex_val] = name_val;
keys[count++] = hex_val;
}
END {
for (i = 0; i < count; i++) {
h = keys[i];
# Skip rendering this as a standalone card if it is a secondary duotone layer.
# (Secondary layers in FA Duotone start with "10" and map back to a base hex)
if (length(h) >= 5 && substr(h, 1, 2) == "10") {
base_hex = substr(h, 3);
if (base_hex in hex_map) { continue; }
}
char_val = hex_map[h];
name_val = name_map[h];
# Check if a secondary layer exists for this base icon
sec_hex = "10" h;
sec_char = "";
if (sec_hex in hex_map) { sec_char = hex_map[sec_hex]; }
# Output card structure (\x27 is the safe ASCII hex for single quotes inside awk)
print " <div class=\"card\">"
if (sec_char != "") {
# Render a combined Duotone layered icon
print " <div class=\"icon duotone\" title=\"" name_val "\">"
print " <span class=\"primary\">" char_val "</span>"
print " <span class=\"secondary\">" sec_char "</span>"
print " </div>"
} else {
# Render a standard icon
print " <div class=\"icon\" title=\"" name_val "\">" char_val "</div>"
}
print " <div class=\"name\" title=\"" name_val "\">" name_val "</div>"
print " <div class=\"hex\">" h "</div>"
print " <button class=\"copy-btn\" onclick=\"copyText(this, \x27" char_val "\x27)\">Copy Icon</button>"
print " </div>"
}
}' >> "$out"
# Close HTML Tags
cat <<EOF >> "$out"
</div>
</body>
</html>
EOF
echo "Done! Open $out in your web browser."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment