You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
265 lines
8.9 KiB
Bash
265 lines
8.9 KiB
Bash
#!/usr/bin/env bash -e
|
|
#
|
|
# Copyright 2020 Nathan L. Conrad
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it under
|
|
# the terms of the GNU General Public License as published by the Free Software
|
|
# Foundation, either version 3 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# this program. If not, see http://www.gnu.org/licenses.
|
|
|
|
# Resolve the project directory before altering the arguments
|
|
prj_dir=$(cd "$(dirname "$0")" && pwd)
|
|
|
|
# Parse arguments
|
|
dirty=0
|
|
while [[ $# -gt 0 ]]
|
|
do
|
|
# If the argument is a single hyphen followed by multiple single-character
|
|
# options, pop the first option from the argument and leave the rest for
|
|
# the next iteration. Otherwise, pop the entire argument.
|
|
arg=$1
|
|
if (( ${#arg} > 2 )) && [[ $arg =~ ^-[^-]*$ ]]
|
|
then
|
|
set -- "-${arg:2}" "${@:2}"
|
|
arg=${arg::2}
|
|
else
|
|
shift
|
|
fi
|
|
|
|
# Handle the argument
|
|
case $arg in
|
|
-h|--help)
|
|
IFS= read -rd '' usage << __eof__ || true
|
|
Usage: build [-hd]
|
|
|
|
Downloads and patches Inconsolata with Nerd Font glyphs
|
|
|
|
Optional arguments:
|
|
-h, --help Show this help message and exit
|
|
-d, --dirty Allow cached source archives instead of forcing clean downloads
|
|
__eof__
|
|
echo -n "$usage"
|
|
exit 0
|
|
;;
|
|
-d|--dirty)
|
|
dirty=1
|
|
;;
|
|
*)
|
|
echo "Invalid argument '$arg'. Use -h for help." >&2
|
|
exit 1
|
|
esac
|
|
done
|
|
|
|
# If output files exist, delete them
|
|
rm -rf "$prj_dir/"{*.ttf,*.css}
|
|
|
|
# Initialize globals used by handle-trap
|
|
signals='INT TERM'
|
|
unset tmp_dir
|
|
|
|
# Usage: handle-trap [exit_code]
|
|
#
|
|
# Deletes temporary files upon script exit or signal
|
|
#
|
|
# Positional arguments:
|
|
# exit_code Optional exit code for which to trigger a script exit
|
|
function handle-trap {
|
|
trap - EXIT $signals
|
|
rm -rf "$tmp_dir"
|
|
if [[ $1 ]]
|
|
then
|
|
exit "$1"
|
|
fi
|
|
}
|
|
|
|
# Set the trap and create a temporary build directory
|
|
trap handle-trap EXIT
|
|
trap 'handle-trap 1' $signals
|
|
tmp_dir=$(mktemp -d)
|
|
|
|
# Usage: prep-src account_name repo_name commit
|
|
#
|
|
# Downloads (if necessary) and extracts a GitHub source archive
|
|
#
|
|
# Positional arguments:
|
|
# account_name GitHub organization or user name
|
|
# repo_name Git repository name
|
|
# commit Git commit hash
|
|
function prep-src {
|
|
local archive_basename=$2_$3.tar.gz
|
|
local archive=$prj_dir/$archive_basename
|
|
if (( dirty )) && [[ -f $archive ]]
|
|
then
|
|
echo "Extracting cached $archive_basename..."
|
|
else
|
|
local url=https://github.com/$1/$2/tarball/$3
|
|
echo "Downloading $url..."
|
|
local dl_archive=$tmp_dir/$archive_basename
|
|
curl -Lo "$dl_archive" "$url"
|
|
echo "Caching $archive_basename..."
|
|
rm -rf "$archive"
|
|
cp "$dl_archive" "$prj_dir/"
|
|
echo "Extracting $archive_basename..."
|
|
fi
|
|
local src_dir=$tmp_dir/$2
|
|
mkdir "$src_dir"
|
|
tar -xC "$src_dir" -f "$archive" --strip-components 1
|
|
}
|
|
|
|
# Download (if necessary) and extract source archives
|
|
inconsolata_repo_name=Inconsolata
|
|
inconsolata_commit=d7269b53386323faad00cc7eebb3d99d26d103ff
|
|
prep-src googlefonts "$inconsolata_repo_name" "$inconsolata_commit"
|
|
nf_repo_name=nerd-fonts
|
|
nf_commit=c41890f82b4d346cbfffff25ae29d1e145690d13
|
|
prep-src ryanoasis "$nf_repo_name" "$nf_commit"
|
|
|
|
# The Nerd Font patcher likes to use the OS/2 Windows ascent and descent rather
|
|
# than the HHea or OS/2 typographic values, and unfortunately, Inconsolata has
|
|
# much larger Windows values. Without the '-l' or '--adjust-line-height'
|
|
# option, the Nerd Font patcher will use the Windows values to size the Nerd
|
|
# Font glyphs resulting in glyphs that are too tall and misaligned when the
|
|
# font is rendered using the HHea or typographic values. With the '-l' or
|
|
# '--adjust-line-height' option, the Nerd Font patcher will also override the
|
|
# HHea with the Windows values. Even though this fixes the Powerline glyph
|
|
# alignment when rendering using the HHea values, it results in a line height
|
|
# that, in my opinion, is much too tall for the font. Adjust the ascent and
|
|
# descent to vertically center the 'E' glyph of the semi-expanded TTF while
|
|
# maintaining a reasonable line height. Apply the same adjustments to the
|
|
# semi-expanded extra-bold TTF.
|
|
src_dir="$tmp_dir/$inconsolata_repo_name/fonts/ttf"
|
|
vert_aligned_dir="$tmp_dir/vert-aligned"
|
|
mkdir "$vert_aligned_dir"
|
|
python3 << __eof__
|
|
import fontforge
|
|
from pathlib import Path
|
|
|
|
# Calculate the ascent which vertically centers the semi-expanded 'E' glyph
|
|
# while maintaining a reasonable line height
|
|
style = 'SemiExpanded'
|
|
glyph = 'E'
|
|
descent = -256
|
|
line_gap = 0
|
|
file_basename = f'Inconsolata-{style}.ttf'
|
|
print(f'Using {repr(glyph)} glyph from {file_basename} for vertical '
|
|
'alignment...')
|
|
font = fontforge.open(str(Path(f'$src_dir/{file_basename}')))
|
|
ascent = round(font[glyph].boundingBox()[3]) - descent
|
|
if (ascent - descent) % 2:
|
|
descent += 1
|
|
print(f' OS/2 Win OS/2 typo HHea Aligned\n Ascent '
|
|
f'{font.os2_winascent:>4} {font.os2_typoascent:>4} '
|
|
f'{font.hhea_ascent:>4} {ascent:>4}\n Descent '
|
|
f'{-font.os2_windescent:>4} {font.os2_typodescent:>4} '
|
|
f'{font.hhea_descent:>4} {descent:>4}\n Line gap '
|
|
f'{font.os2_typolinegap:>4} {font.hhea_linegap:>4} {line_gap:>4}')
|
|
font.close()
|
|
|
|
# Vertically align the semi-expanded and semi-expanded extra-bold TTFs
|
|
styles = style, 'SemiExpandedExtraBold'
|
|
flags = 'opentype', 'PfEd-comments'
|
|
for style in styles:
|
|
file_basename = f'Inconsolata-{style}.ttf'
|
|
print(f'Vertically aligning {file_basename}...')
|
|
font = fontforge.open(str(Path(f'$src_dir/{file_basename}')))
|
|
font.os2_winascent = ascent
|
|
font.os2_windescent = -descent
|
|
font.os2_typoascent = ascent
|
|
font.os2_typodescent = descent
|
|
font.os2_typolinegap = line_gap
|
|
font.hhea_ascent = ascent
|
|
font.hhea_descent = descent
|
|
font.hhea_linegap = line_gap
|
|
font.generate(str(Path(f'$vert_aligned_dir/{file_basename}')), flags=flags)
|
|
font.close()
|
|
__eof__
|
|
|
|
# Patch with Nerd Font glyphs
|
|
nf_patched_dir="$tmp_dir/nf-patched"
|
|
mkdir "$nf_patched_dir"
|
|
for file in "$vert_aligned_dir/"*
|
|
do
|
|
file_basename="$(basename "$file")"
|
|
echo "Patching $file_basename with Nerd Font glyphs..."
|
|
python3 "$tmp_dir/$nf_repo_name/font-patcher" -c --progressbars \
|
|
-out "$nf_patched_dir" "$file"
|
|
done
|
|
|
|
# Rename family and recategorize styles
|
|
python3 << __eof__
|
|
import fontforge
|
|
from pathlib import Path
|
|
|
|
family = 'Iconsolata'
|
|
styles = {'Semi Expanded': 'Regular', 'Semi Expanded ExtraBold': 'Bold'}
|
|
weights = {'Regular': 'Book', 'Bold': 'Bold'}
|
|
os2_width = 5
|
|
os2_weights = {'Regular': 400, 'Bold': 700}
|
|
lang = 'English (US)'
|
|
id_suffix = '${inconsolata_commit::6}${nf_commit::6}'
|
|
flags = 'opentype', 'PfEd-comments'
|
|
for src_style, style in styles.items():
|
|
postscript_name = f'{family}-{style}'
|
|
file_basename = f'{postscript_name}.ttf'
|
|
print(f'Generating {file_basename}...')
|
|
src_file_basename = f'Inconsolata {src_style} Nerd Font Complete.ttf'
|
|
font = fontforge.open(str(Path(f'$nf_patched_dir/{src_file_basename}')))
|
|
font.familyname = family
|
|
font.fullname = f'{family} {style}'
|
|
font.fontname = postscript_name
|
|
font.weight = weights[style]
|
|
font.os2_width = os2_width
|
|
font.os2_weight = os2_weights[style]
|
|
font.appendSFNTName(lang, 'SubFamily', None)
|
|
font.appendSFNTName(lang, 'UniqueID', f'{postscript_name}_{id_suffix}')
|
|
font.appendSFNTName(lang, 'Preferred Styles', None)
|
|
font.appendSFNTName(lang, 'Preferred Family', None)
|
|
font.appendSFNTName(lang, 'Compatible Full', None)
|
|
sfnt_names = {name: value for prop_lang, name, value in font.sfnt_names
|
|
if prop_lang == lang}
|
|
print(f' Weight is {repr(font.weight)} (PostScript) and '
|
|
f'{font.os2_weight} (OS/2)\n Subfamily is '
|
|
f"{repr(sfnt_names['SubFamily'])}\n Unique ID is "
|
|
f"{repr(sfnt_names['UniqueID'])}")
|
|
font.generate(str(Path(f'$prj_dir/{file_basename}')), flags=flags)
|
|
font.close()
|
|
__eof__
|
|
|
|
# Generate CSS
|
|
css_file_basename=Iconsolata.css
|
|
echo "Generating $css_file_basename..."
|
|
IFS= read -rd '' snippet << __eof__ || true
|
|
@font-face {
|
|
font-family: 'Iconsolata';
|
|
src: url(data:font/ttf;base64,
|
|
__eof__
|
|
css_file=$tmp_dir/$css_file_basename
|
|
echo -n "${snippet%?}" > "$css_file"
|
|
base64 -w0 "$prj_dir/Iconsolata-Regular.ttf" >> "$css_file"
|
|
IFS= read -rd '' snippet << __eof__ || true
|
|
) format('truetype');
|
|
}
|
|
@font-face {
|
|
font-family: 'Iconsolata';
|
|
font-weight: bold;
|
|
src: url(data:font/ttf;base64,
|
|
__eof__
|
|
echo -n "${snippet%?}" >> "$css_file"
|
|
base64 -w0 "$prj_dir/Iconsolata-Bold.ttf" >> "$css_file"
|
|
cat << __eof__ >> "$css_file"
|
|
) format('truetype');
|
|
}
|
|
__eof__
|
|
cp "$css_file" "$prj_dir/"
|
|
|
|
# Indicate success
|
|
echo 'Done'
|