| #!/bin/bash |
| |
| set -euo pipefail |
| |
| usage() { |
| cat <<EOF |
| Usage: $0 [-o TESTS_FILE] [-n TEST_NAME] [-e EXPECTED_FILE] [--expected-dir DIR] [--subset-dir DIR] HB_VECTOR FONT_FILE [VECTOR_OPTIONS] [TEXT] |
| |
| Subset font, verify visual equivalence with hb-view, and append vector test line: |
| font-file;options;unicodes;expected-file |
| |
| Options: |
| -o TESTS_FILE Append test line to this file (default: stdout). |
| -n TEST_NAME Test name used for default expected filename. |
| -e EXPECTED_FILE Expected output file path. |
| --expected-dir DIR Directory for auto-generated expected files, relative |
| to TESTS_FILE directory (default: ../expected). |
| --subset-dir DIR Directory for subset fonts, relative to TESTS_FILE |
| directory (default: ../fonts). |
| EOF |
| } |
| |
| sha1_file() { |
| if [ "$SHA1SUM_KIND" = "sha1sum" ]; then |
| sha1sum "$1" | cut -d' ' -f1 |
| elif [ "$SHA1SUM_KIND" = "shasum" ]; then |
| shasum -a 1 "$1" | cut -d' ' -f1 |
| else |
| digest -a sha1 "$1" | cut -d' ' -f1 |
| fi |
| } |
| |
| is_vector_only_option() { |
| case "$1" in |
| -q|--quiet|--ned|--precision|--precision=*|-o|--output-file|--output-file=*|-O|--output-format|--output-format=*) |
| return 0 |
| ;; |
| *) |
| return 1 |
| ;; |
| esac |
| } |
| |
| if which sha1sum >/dev/null 2>/dev/null; then |
| SHA1SUM_KIND=sha1sum |
| elif which shasum >/dev/null 2>/dev/null; then |
| SHA1SUM_KIND=shasum |
| elif which digest >/dev/null 2>/dev/null; then |
| SHA1SUM_KIND=digest |
| else |
| echo "'sha1sum' not found" >&2 |
| exit 2 |
| fi |
| |
| out=/dev/stdout |
| test_name= |
| expected_file= |
| expected_dir=../expected |
| expected_dir_explicit=false |
| subset_dir=../fonts |
| |
| while [ $# -gt 0 ]; do |
| case "$1" in |
| -o) |
| out=$2 |
| shift 2 |
| ;; |
| -n|--test-name) |
| test_name=$2 |
| shift 2 |
| ;; |
| -e|--expected-file) |
| expected_file=$2 |
| shift 2 |
| ;; |
| --expected-dir) |
| expected_dir=$2 |
| expected_dir_explicit=true |
| shift 2 |
| ;; |
| --subset-dir) |
| subset_dir=$2 |
| shift 2 |
| ;; |
| -h|--help) |
| usage |
| exit 0 |
| ;; |
| --) |
| shift |
| break |
| ;; |
| *) |
| break |
| ;; |
| esac |
| done |
| |
| if [ $# -lt 2 ]; then |
| usage >&2 |
| exit 1 |
| fi |
| |
| hb_vector=$1 |
| shift |
| fontfile=$1 |
| shift |
| |
| if [ "x${fontfile:0:1}" = "x-" ]; then |
| echo "Specify font file before vector options." >&2 |
| exit 1 |
| fi |
| if [ ! -f "$fontfile" ]; then |
| echo "Font file not found: '$fontfile'." >&2 |
| exit 1 |
| fi |
| if ! echo "$hb_vector" | grep -q 'vector'; then |
| echo "Specify hb-vector as first positional argument: got '$hb_vector'." >&2 |
| exit 1 |
| fi |
| |
| if [[ "$hb_vector" == */* ]]; then |
| hb_vector_path="$hb_vector" |
| else |
| hb_vector_path="$(command -v "$hb_vector" 2>/dev/null || true)" |
| fi |
| if [ -z "${hb_vector_path:-}" ] || [ ! -x "$hb_vector_path" ]; then |
| echo "hb-vector not found or not executable: '$hb_vector'." >&2 |
| exit 1 |
| fi |
| hb_vector="$hb_vector_path" |
| tool_dir="$(cd "$(dirname "$hb_vector")" && pwd)" |
| hb_shape="$tool_dir/hb-shape" |
| hb_view="$tool_dir/hb-view" |
| hb_subset="$tool_dir/hb-subset" |
| for tool in "$hb_shape" "$hb_view" "$hb_subset"; do |
| if [ ! -x "$tool" ]; then |
| echo "Required sibling tool not found or not executable: '$tool'." >&2 |
| exit 1 |
| fi |
| done |
| |
| if [ "x${fontfile:0:1}" = "x/" ]; then |
| fontfile_path="$fontfile" |
| else |
| fontfile_path="$PWD/$fontfile" |
| fi |
| fontfile="$fontfile_path" |
| |
| options=() |
| shape_view_options=() |
| have_text=false |
| text= |
| for arg in "$@"; do |
| if ! $have_text && [ "x${arg:0:1}" = "x-" ]; then |
| if echo "$arg" | grep -q ' '; then |
| echo "Space in argument is not supported: '$arg'." >&2 |
| exit 1 |
| fi |
| options+=("$arg") |
| if ! is_vector_only_option "$arg"; then |
| shape_view_options+=("$arg") |
| fi |
| continue |
| fi |
| |
| if $have_text; then |
| echo "Too many arguments found. Use '=' notation for options, eg --font-size=64." >&2 |
| exit 1 |
| fi |
| text=$arg |
| have_text=true |
| done |
| |
| options_len=${#options[@]} |
| shape_view_options_len=${#shape_view_options[@]} |
| |
| if ! $have_text; then |
| text=$(cat) |
| fi |
| |
| if [[ "$text" == *';'* ]]; then |
| echo "Text cannot contain ';' in .tests format." >&2 |
| exit 1 |
| fi |
| if [[ "$text" == *$'\n'* ]]; then |
| echo "Multiline text is not supported by .tests format." >&2 |
| exit 1 |
| fi |
| if [[ "$test_name" == *';'* ]]; then |
| echo "Test name cannot contain ';'." >&2 |
| exit 1 |
| fi |
| if [[ "$test_name" == *$'\n'* ]]; then |
| echo "Test name cannot contain newlines." >&2 |
| exit 1 |
| fi |
| unicodes=$(/usr/bin/env python3 -c 'import sys; s=sys.argv[1]; print(",".join(f"U+{ord(c):04X}" for c in s))' "$text") |
| |
| options_str= |
| if [ "$options_len" -gt 0 ]; then |
| for arg in "${options[@]}"; do |
| options_str="${options_str}${options_str:+ }${arg}" |
| done |
| fi |
| |
| ext=svg |
| if [ "$options_len" -gt 0 ]; then |
| for arg in "${options[@]}"; do |
| if [ "x$arg" = "x-q" ] || [ "x$arg" = "x--quiet" ]; then |
| ext=txt |
| break |
| fi |
| done |
| fi |
| |
| if [ "$out" = "/dev/stdout" ] || [ "$out" = "-" ]; then |
| out_dir=$PWD |
| else |
| mkdir -p "$(dirname "$out")" |
| out_dir=$(cd "$(dirname "$out")" && pwd) |
| fi |
| |
| tmpdir=$(mktemp -d) |
| |
| # Collect gids from shaping result and subset. |
| if [ "$shape_view_options_len" -gt 0 ]; then |
| glyph_ids=$( |
| printf '%s\n' "$text" | "$hb_shape" "${shape_view_options[@]}" --no-glyph-names --no-clusters --no-positions "$fontfile" | |
| sed 's/[][]//g; s/|/,/g' |
| ) |
| else |
| glyph_ids=$( |
| printf '%s\n' "$text" | "$hb_shape" --no-glyph-names --no-clusters --no-positions "$fontfile" | |
| sed 's/[][]//g; s/|/,/g' |
| ) |
| fi |
| cp "$fontfile" "$tmpdir/font.ttf" |
| "$hb_subset" \ |
| --glyph-names \ |
| --no-hinting \ |
| --layout-features='*' \ |
| --gids="$glyph_ids" \ |
| --text="$text" \ |
| --output-file="$tmpdir/font.subset.ttf" \ |
| "$tmpdir/font.ttf" |
| if ! test -s "$tmpdir/font.subset.ttf"; then |
| echo "Subsetter did not produce nonempty subset font in $tmpdir/font.subset.ttf." >&2 |
| exit 2 |
| fi |
| |
| # Verify visual parity via hb-view. |
| if [ "$shape_view_options_len" -gt 0 ]; then |
| printf '%s\n' "$text" | "$hb_view" "${shape_view_options[@]}" "$fontfile" --output-format=png --output-file="$tmpdir/orig.png" |
| printf '%s\n' "$text" | "$hb_view" "${shape_view_options[@]}" "$tmpdir/font.subset.ttf" --output-format=png --output-file="$tmpdir/subset.png" |
| else |
| printf '%s\n' "$text" | "$hb_view" "$fontfile" --output-format=png --output-file="$tmpdir/orig.png" |
| printf '%s\n' "$text" | "$hb_view" "$tmpdir/font.subset.ttf" --output-format=png --output-file="$tmpdir/subset.png" |
| fi |
| if ! cmp -s "$tmpdir/orig.png" "$tmpdir/subset.png"; then |
| echo "Subset font produced different rendering. Please inspect $tmpdir/orig.png and $tmpdir/subset.png." >&2 |
| exit 2 |
| fi |
| |
| if [ "x${subset_dir:0:1}" = "x/" ]; then |
| subset_dir_path="$subset_dir" |
| else |
| subset_dir_path="$out_dir/$subset_dir" |
| fi |
| mkdir -p "$subset_dir_path" |
| subset_hash=$(sha1_file "$tmpdir/font.subset.ttf") |
| subset_path="$subset_dir_path/$subset_hash.ttf" |
| if ! test -f "$subset_path"; then |
| mv "$tmpdir/font.subset.ttf" "$subset_path" |
| fi |
| |
| if [ -z "$expected_file" ]; then |
| if [ -z "$test_name" ]; then |
| echo "Specify --test-name NAME (or --expected-file PATH)." >&2 |
| exit 1 |
| fi |
| |
| if ! $expected_dir_explicit && [ "$out" != "/dev/stdout" ] && [ "$out" != "-" ]; then |
| tests_base="$(basename "$out")" |
| tests_stem="${tests_base%.tests}" |
| if [ -z "$tests_stem" ]; then |
| tests_stem="$tests_base" |
| fi |
| expected_dir="$expected_dir/$tests_stem" |
| fi |
| |
| expected_file="$expected_dir/$test_name.$ext" |
| fi |
| if [ "x${expected_file:0:1}" = "x/" ]; then |
| expected_path="$expected_file" |
| else |
| expected_path="$out_dir/$expected_file" |
| fi |
| |
| mkdir -p "$(dirname "$expected_path")" |
| if [ "$options_len" -gt 0 ]; then |
| "$hb_vector" "${options[@]}" "$subset_path" "$text" > "$expected_path" |
| else |
| "$hb_vector" "$subset_path" "$text" > "$expected_path" |
| fi |
| |
| if [ "$out" = "/dev/stdout" ] || [ "$out" = "-" ]; then |
| font_field=$(/usr/bin/env python3 -c 'import os,sys; print(os.path.relpath(sys.argv[1], sys.argv[2]))' "$subset_path" "$PWD") |
| expected_field=$(/usr/bin/env python3 -c 'import os,sys; print(os.path.relpath(sys.argv[1], sys.argv[2]))' "$expected_path" "$PWD") |
| out_file=/dev/stdout |
| else |
| font_field=$(/usr/bin/env python3 -c 'import os,sys; print(os.path.relpath(sys.argv[1], sys.argv[2]))' "$subset_path" "$out_dir") |
| expected_field=$(/usr/bin/env python3 -c 'import os,sys; print(os.path.relpath(sys.argv[1], sys.argv[2]))' "$expected_path" "$out_dir") |
| out_file="$out" |
| fi |
| |
| echo "${font_field};${options_str};${unicodes};${expected_field}" >> "$out_file" |
| |
| rm -rf "$tmpdir" |