subset-fonts (3140B)
1 #!/bin/sh 2 # subset-fonts - cut FontAwesome down to the icons this site actually uses. 3 # 4 # The theme ships the whole families: 158 KB of fa-solid and 119 KB of 5 # fa-brands, as woff2, for the two dozen glyphs on the site. Both are on 6 # the critical path of every page, so they cost more than everything else 7 # put together. Subsetted they are about 2 KB each. 8 # 9 # The full families in themes/hugo-coder/static/fonts are the input and 10 # are never modified. The output goes to this project's static/fonts, 11 # which Hugo serves in preference to the theme's copy at the same path. 12 # 13 # TRAP: the subset holds the icons that were in use when this last ran. 14 # Use an icon that was not and the glyph is simply absent from the font. 15 # Hugo does not read fonts, so nothing fails at build time. Run this 16 # after adding an icon. 17 set -eu 18 19 cd "$(dirname "$0")/.." 20 21 for t in hugo pyftsubset woff2_compress; do 22 command -v "$t" >/dev/null || { echo "subset-fonts: need $t" >&2; exit 1; } 23 done 24 25 full=themes/hugo-coder/static/fonts 26 out=static/fonts 27 work=$(mktemp -d) 28 trap 'rm -rf "$work"' EXIT 29 30 # Class names are resolved to codepoints through the built stylesheet 31 # rather than the theme's SCSS, because the SCSS keeps them in one file 32 # and the v4 aliases the site uses - fa-file-text, fa-question - in 33 # another, and only the compiled CSS has applied the second to the first. 34 hugo --quiet --destination "$work/public" --cleanDestinationDir 35 css=$(find "$work/public/css" -name 'coder.min.*.css' | head -1) 36 [ -n "$css" ] || { echo "subset-fonts: no built stylesheet to read codepoints from" >&2; exit 1; } 37 38 # Everything the site can name an icon in. A class assembled at runtime 39 # from parts would not be found here; nothing does that today. 40 icons=$(grep -rhoE 'fa-[a-z0-9-]+' content layouts hugo.toml themes/hugo-coder/layouts 2>/dev/null | 41 sort -u | 42 grep -vE '^fa-(solid|brands|regular|fw|ul|li|stack|[1-9]x|spin|pull-left|pull-right|border|inverse|rotate|flip|lg|sm|xs|[a-z-]+-400|[a-z-]+-900)$') 43 44 unicodes="" 45 for icon in $icons; do 46 point=$(grep -oE "\.$icon\{--fa:\"\\\\[0-9a-f]+\"" "$css" | head -1 | grep -oE '\\[0-9a-f]+' | tr -d '\\') 47 [ -n "$point" ] || continue 48 unicodes="$unicodes,U+$point" 49 done 50 unicodes=${unicodes#,} 51 [ -n "$unicodes" ] || { echo "subset-fonts: no icons found" >&2; exit 1; } 52 53 echo "subset-fonts: keeping $(printf '%s' "$unicodes" | tr ',' '\n' | wc -l) codepoints" 54 55 mkdir -p "$out" 56 for f in fa-solid-900 fa-brands-400 fa-regular-400; do 57 # Subsetting writes ttf and woff2_compress makes the woff2 from it. 58 # The direct route, pyftsubset --flavor=woff2, wants a brotli module 59 # that this machine's fonttools did not have; woff2_compress ships 60 # with the reference implementation and is one package either way. 61 pyftsubset "$full/$f.ttf" --unicodes="$unicodes" \ 62 --layout-features='' --no-hinting --desubroutinize \ 63 --output-file="$work/$f.ttf" 64 woff2_compress "$work/$f.ttf" >/dev/null 65 cp "$work/$f.ttf" "$out/$f.ttf" 66 cp "$work/$f.woff2" "$out/$f.woff2" 67 printf ' %-15s %7s -> %6s bytes\n' "$f.woff2" \ 68 "$(stat -c%s "$full/$f.woff2")" "$(stat -c%s "$out/$f.woff2")" 69 done 70 71 cp "$full/LICENSE.txt" "$out/LICENSE.txt"