site

personal website, served at mordaunt.dev/site
Log | Files | Refs

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"