| #!/bin/bash | |
| trap "exit" INT | |
| dirname=`dirname "$0"` | |
| # Parse options | |
| output_dir= | |
| font_file= | |
| face_index=0 | |
| hb_info=hb-info | |
| hb_vector=hb-vector | |
| hb_svg_compare=$dirname/hb-svg-compare | |
| tolerance=0 | |
| vector_precision= | |
| precision= | |
| font_size=upem | |
| features= | |
| variations= | |
| face_loader=ot | |
| font_funcs1=ot | |
| font_funcs2=fontations | |
| clear1=false | |
| clear2=false | |
| unicodes= | |
| glyphs= | |
| quiet=false | |
| help=false | |
| max_procs= | |
| while test $# -gt 0; do | |
| case "$1" in | |
| -o|--output-dir) | |
| shift | |
| output_dir=$1 | |
| shift | |
| ;; | |
| --font-file) | |
| shift | |
| font_file=$1 | |
| shift | |
| ;; | |
| --face-index) | |
| shift | |
| face_index=$1 | |
| shift | |
| ;; | |
| --hb-vector) | |
| shift | |
| hb_vector=$1 | |
| shift | |
| ;; | |
| --hb-info) | |
| shift | |
| hb_info=$1 | |
| shift | |
| ;; | |
| --hb-svg-compare) | |
| shift | |
| hb_svg_compare=$1 | |
| shift | |
| ;; | |
| --tolerance) | |
| shift | |
| tolerance=$1 | |
| shift | |
| ;; | |
| --precision) | |
| shift | |
| precision=$1 | |
| shift | |
| ;; | |
| --max-procs) | |
| shift | |
| max_procs=$1 | |
| shift | |
| ;; | |
| --font-size) | |
| shift | |
| font_size=$1 | |
| shift | |
| ;; | |
| --features) | |
| shift | |
| features=$1 | |
| shift | |
| ;; | |
| --variations) | |
| shift | |
| variations=$1 | |
| shift | |
| ;; | |
| --unicodes) | |
| shift | |
| unicodes=$1 | |
| shift | |
| ;; | |
| --face-loader) | |
| shift | |
| face_loader=$1 | |
| shift | |
| ;; | |
| --font-funcs1) | |
| shift | |
| font_funcs1=$1 | |
| shift | |
| ;; | |
| --font-funcs2) | |
| shift | |
| font_funcs2=$1 | |
| shift | |
| ;; | |
| --clear1) | |
| shift | |
| clear1=true | |
| ;; | |
| --clear2) | |
| shift | |
| clear2=true | |
| ;; | |
| --quiet) | |
| quiet=true | |
| shift | |
| ;; | |
| --help) | |
| help=true | |
| shift | |
| ;; | |
| *) | |
| if test "x$font_file" == x; then | |
| font_file=$1 | |
| shift | |
| else | |
| glyphs="$glyphs $1" | |
| shift | |
| fi | |
| ;; | |
| esac | |
| done | |
| if $help; then | |
| cmd=`basename "$0"` | |
| echo "Usage: $cmd [OPTIONS] FONTFILE [GLYPH...]" | |
| echo "Render a font with two font backends and compare the results." | |
| echo | |
| echo "Options:" | |
| echo " -o, --output-dir DIR: Output in DIR" | |
| echo " --font-file FONTFILE: Font file to render" | |
| echo " --hb-vector HB_VECTOR: Path to hb-vector; default $hb_vector" | |
| echo " --hb-info HB_INFO: Path to hb-info; default $hb_info" | |
| echo " --hb-svg-compare HB_SVG_COMPARE: Path to hb-svg-compare; default $hb_svg_compare" | |
| echo " --tolerance TOLERANCE: Tolerance for SVG comparison; default $tolerance" | |
| echo " --precision N: hb-vector precision override; default 5" | |
| echo " --max-procs N: Max parallel processes for render/compare; default auto" | |
| echo " --font-size SIZE: Font size; default $font_size" | |
| echo " --features FEATURES: Font features; default none" | |
| echo " --variations VARIATIONS: Font variations; default none" | |
| echo " --face-loader: Face loader; default $face_loader" | |
| echo " --font-funcs1 font_funcs: First font-funcs; default $font_funcs1" | |
| echo " --font-funcs2 font_funcs: Second font-funcs; default $font_funcs2" | |
| echo " --clear1: Clear first font backend output if exists" | |
| echo " --clear2: Clear second font backend output if exists" | |
| echo " --unicodes CODES: Unicodes to render" | |
| echo " --quiet: Quiet mode" | |
| echo " --help: Print help" | |
| exit 0 | |
| fi | |
| get_num_jobs() { | |
| n=`getconf _NPROCESSORS_ONLN 2>/dev/null || true` | |
| test "x$n" == x && n=`nproc 2>/dev/null || true` | |
| test "x$n" == x && n=`sysctl -n hw.ncpu 2>/dev/null || true` | |
| case "$n" in | |
| ''|*[!0-9]*) n=1 ;; | |
| esac | |
| test "$n" -lt 1 && n=1 | |
| echo "$n" | |
| } | |
| if test "x$max_procs" != x; then | |
| echo "$max_procs" | grep -Eq '^[0-9]+$' || { | |
| echo "Invalid max-procs '$max_procs'" >&2 | |
| exit 2 | |
| } | |
| test "$max_procs" -gt 0 || { | |
| echo "Invalid max-procs '$max_procs'" >&2 | |
| exit 2 | |
| } | |
| fi | |
| if test "x$font_file" == x; then | |
| echo "No font file specified." >&2 | |
| exit 2 | |
| fi | |
| if ! which "$hb_vector" 2>/dev/null >/dev/null; then | |
| echo "'$hb_vector' not found" >&2 | |
| exit 2 | |
| fi | |
| if ! which "$hb_info" 2>/dev/null >/dev/null; then | |
| echo "'$hb_info' not found" >&2 | |
| exit 2 | |
| fi | |
| if ! test -f "$font_file"; then | |
| echo "Font file '$font_file' not found" >&2 | |
| exit 2 | |
| fi | |
| # Sanity check Unicode values | |
| if test "x$unicodes" != x; then | |
| echo "$unicodes" | | |
| tr ' ' '\n' | | |
| grep -v '^U[+][0-9a-fA-F]\+$' && | |
| { | |
| echo "Invalid Unicode values" >&2 | |
| exit 2 | |
| } | |
| fi | |
| $quiet || echo "Comparing '$font_file' with '$font_funcs1' and '$font_funcs2' font backends. " >&2 | |
| if test "x$output_dir" == x; then | |
| output_dir=`mktemp -d` | |
| echo "Output in '$output_dir'" >&2 | |
| fi | |
| mkdir -p "$output_dir" || exit 1 | |
| if test "x$precision" != x; then | |
| echo "$precision" | grep -Eq '^[0-9]+$' || { | |
| echo "Invalid precision '$precision'" >&2 | |
| exit 2 | |
| } | |
| vector_precision="$precision" | |
| else | |
| vector_precision=5 | |
| fi | |
| $quiet || echo "Using precision=$vector_precision for tolerance=$tolerance." >&2 | |
| # Populate glyphs file | |
| glyphs_file="$output_dir/glyphs" | |
| > "$glyphs_file" | |
| echo "$unicodes" | | |
| tr ' ' '\n' | | |
| sed 's/^U+//' | | |
| grep . | | |
| while read unicode; do | |
| echo "uni$unicode" | |
| done >> "$glyphs_file" | |
| echo "$glyphs" | | |
| tr ' ' '\n' | | |
| grep . >> "$glyphs_file" | |
| if test "x$unicodes" == x -a "x$glyphs" == x; then | |
| $quiet || echo "No unicodes or glyphs specified. Comparing all glyphs in the font." >&2 | |
| num_glyphs=`$hb_info --quiet --show-glyph-count "$font_file"` | |
| $quiet || echo "Font has $num_glyphs glyphs." >&2 | |
| seq 0 $((num_glyphs - 1)) | | |
| sed 's/^/gid/' >> "$glyphs_file" | |
| fi | |
| num_glyphs=`wc -l < "$glyphs_file" | tr -d '[:space:]'` | |
| if test "$num_glyphs" -lt 1; then | |
| echo "No glyphs to compare." >&2 | |
| exit 2 | |
| fi | |
| base_jobs=`get_num_jobs` | |
| if test "x$max_procs" != x; then | |
| if test "$base_jobs" -gt "$max_procs"; then | |
| base_jobs="$max_procs" | |
| fi | |
| fi | |
| compare_jobs="$base_jobs" | |
| if test "$compare_jobs" -gt "$num_glyphs"; then | |
| compare_jobs="$num_glyphs" | |
| fi | |
| $quiet || test "$base_jobs" -le 1 || echo "Using up to $base_jobs shards." >&2 | |
| flavor= | |
| if test "$features" != ""; then | |
| flavor="$flavor.features=$features" | |
| fi | |
| if test "$variations" != ""; then | |
| flavor="$flavor.variations=$variations" | |
| fi | |
| # Render with both font backends | |
| if $clear1; then | |
| funcs_prefix="$output_dir/$font_funcs1$flavor" | |
| $quiet || echo "Clearing '$funcs_prefix'... " >&2 | |
| rm -rf "$funcs_prefix" | |
| fi | |
| if $clear2; then | |
| funcs_prefix="$output_dir/$font_funcs2$flavor" | |
| $quiet || echo "Clearing '$funcs_prefix'... " >&2 | |
| rm -rf "$funcs_prefix" | |
| fi | |
| for font_funcs in "$font_funcs1" "$font_funcs2"; do | |
| test "x$font_funcs" == x && continue | |
| $quiet || echo "Rendering with font backend '$font_funcs'..." >&2 | |
| funcs_prefix="$output_dir/$font_funcs$flavor" | |
| mkdir -p "$funcs_prefix" | |
| render_cmds="$output_dir/render-cmds.$font_funcs$flavor" | |
| > "$render_cmds" | |
| count=0 | |
| while read glyph; do | |
| dir="$funcs_prefix" | |
| svg="$dir/$glyph.svg" | |
| if test -f "$svg"; then | |
| continue | |
| fi | |
| count=$((count + 1)) | |
| if test $((count % 100)) == 0; then | |
| $quiet || echo -n . >&2 | |
| fi | |
| printf '%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s\n' \ | |
| "--font-file=$font_file" \ | |
| "--face-index=$face_index" \ | |
| "--glyphs" \ | |
| "--face-loader=$face_loader" \ | |
| "--font-funcs=$font_funcs" \ | |
| "--features=$features" \ | |
| "--variations=$variations" \ | |
| "--font-size=$font_size" \ | |
| "--precision=$vector_precision" \ | |
| "--output-file=$svg" \ | |
| "$glyph" >> "$render_cmds" | |
| done < "$glyphs_file" | |
| render_cmd_count=`wc -l < "$render_cmds" | tr -d '[:space:]'` | |
| render_jobs="$base_jobs" | |
| if test "$render_jobs" -gt "$render_cmd_count"; then | |
| render_jobs="$render_cmd_count" | |
| fi | |
| if test "$render_cmd_count" -gt 0; then | |
| render_shard_dir="$output_dir/render-shards.$font_funcs$flavor" | |
| rm -rf "$render_shard_dir" | |
| mkdir -p "$render_shard_dir" || exit 1 | |
| split -d -n l/"$render_jobs" "$render_cmds" "$render_shard_dir/cmds." || exit 1 | |
| pids= | |
| for shard in "$render_shard_dir"/cmds.*; do | |
| "$hb_vector" --batch < "$shard" > "$shard.out" & | |
| pids="$pids $!" | |
| done | |
| for pid in $pids; do | |
| wait "$pid" || exit 1 | |
| done | |
| cat "$render_shard_dir"/cmds.*.out | grep -v '^success$' || true | |
| fi | |
| if test "$count" -ge 100; then | |
| $quiet || echo >&2 | |
| fi | |
| done | |
| test "x$font_funcs1" == x || test "x$font_funcs2" == x && exit 0 | |
| diff="$output_dir/diff$flavor" | |
| rm -f "$diff" | |
| > "$diff" | |
| test "x$flavor" == x || ln -f -s "diff$flavor" "$output_dir/diff" | |
| $quiet || echo "Comparing SVGs into '$diff'..." >&2 | |
| funcs1_prefix="$output_dir/$font_funcs1$flavor" | |
| funcs2_prefix="$output_dir/$font_funcs2$flavor" | |
| pairs="$output_dir/pairs$flavor" | |
| > "$pairs" | |
| count=0 | |
| while read glyph; do | |
| count=$((count + 1)) | |
| if test $((count % 100)) == 0; then | |
| $quiet || echo -n . >&2 | |
| fi | |
| svg1="$funcs1_prefix/$glyph.svg" | |
| svg2="$funcs2_prefix/$glyph.svg" | |
| if ! test -f "$svg1" || ! test -f "$svg2"; then | |
| echo -e "\n$glyph not rendered." >&2 | |
| exit 1 | |
| fi | |
| echo -e "$svg1\t$svg2" >> "$pairs" | |
| done < "$glyphs_file" | |
| if test "$count" -ge 100; then | |
| $quiet || echo >&2 | |
| fi | |
| raw_diff="$output_dir/diff.raw$flavor" | |
| shard_dir="$output_dir/compare-shards$flavor" | |
| rm -rf "$shard_dir" | |
| mkdir -p "$shard_dir" || exit 1 | |
| split -d -n l/"$compare_jobs" "$pairs" "$shard_dir/pairs." || exit 1 | |
| pids= | |
| for shard in "$shard_dir"/pairs.*; do | |
| "$hb_svg_compare" "$tolerance" < "$shard" > "$shard.out" & | |
| pids="$pids $!" | |
| done | |
| for pid in $pids; do | |
| wait "$pid" || exit 1 | |
| done | |
| cat "$shard_dir"/pairs.*.out > "$raw_diff" || exit 1 | |
| # Sort diff by error | |
| python3 - "$raw_diff" "$diff" <<'PY' || exit 1 | |
| import math | |
| import sys | |
| src, dst = sys.argv[1], sys.argv[2] | |
| rows = [] | |
| with open(src, encoding='utf-8') as f: | |
| for idx, line in enumerate(f): | |
| line = line.rstrip('\n') | |
| first = line.split('\t', 1)[0] | |
| try: | |
| value = float(first) | |
| key = (0, value, idx) if math.isfinite(value) else (1, float('inf'), idx) | |
| except ValueError: | |
| key = (1, float('inf'), idx) | |
| rows.append((key, line)) | |
| rows.sort(key=lambda row: row[0]) | |
| with open(dst, 'w', encoding='utf-8') as f: | |
| for _, line in rows: | |
| f.write(line + '\n') | |
| PY | |
| if ! $quiet; then | |
| while IFS= read -r line; do | |
| echo "$line" | |
| done < "$diff" | |
| fi | |
| # Count number of differences | |
| num_diffs=`cat "$diff" | wc -l` | |
| $quiet || echo -e "\nFound $num_diffs differences in $num_glyphs glyphs." >&2 |