openpencil/tools/check-text-measure.sh

76 lines
3.7 KiB
Bash
Raw Permalink Normal View History

#!/usr/bin/env bash
# tools/check-text-measure.sh — chrome text must be measured in the family
# it is painted in.
#
# `RenderBackend::measure_text` (jian's `Painter::measure_text`) is
# family-BLIND: it resolves the backend's default typeface — the bundled
# Roboto on native, the CanvasKit default in the browser — while every chrome
# string is DRAWN as a named run (`system-ui`, which macOS resolves to
# `.AppleSystemUIFont`). SF Pro is wider than Roboto at the same point size,
# so the blind call under-reports the painted width.
#
# Nothing errors when that happens. An ellipsizer believes a string fits and
# emits no `…`, so the content clip shears the last glyph in half; a centred
# label lands left of centre; a tooltip bubble sized as `measured + padding`
# is born too narrow for its own text; a caret drifts off the glyph being
# edited. And none of it reproduces in CI, because every test backend
# measures blind and family-aware identically — the bug is visible only by
# eye, on a real machine. That is what makes this worth a build gate.
#
# The sanctioned path is `crate::widgets::text_metrics` (`measure_chrome` /
# `fit_chrome` / `centered_text_x` / `measure_in_family`), which routes to
# `measure_text_family` with the run's real family.
#
# Exit semantics:
# 0 PASS — no family-blind measurement in widget code.
# 1 FAIL — a widget module called `measure_text` directly.
set -euo pipefail
WIDGETS="crates/op-editor-ui/src/widgets"
# Files that legitimately name the blind call:
# text_metrics.rs — defines the sanctioned wrappers, and its own
# tests compare blind against family-aware.
# text_input_backend.rs — a decorating `RenderBackend` that must forward
# every trait method, blind one included.
# test_family_gap_backend.rs / test_capture_backend.rs
# — test backends implementing the trait.
# text_metrics_paint_tests.rs
# — the cross-panel guard's negative control
# paints a deliberately blind fitter to prove the
# guard can fail.
ALLOWED_RE='^crates/op-editor-ui/src/widgets/(text_metrics|text_metrics_paint_tests|text_input_backend|test_family_gap_backend|test_capture_backend)\.rs:'
# `.measure_text(` as a method call, excluding the family-aware and
# weight-aware siblings (`measure_text_family`, `measure_text_family_styled`,
# `measure_text_weighted`, `measure_text_styled`) and trait-impl signatures
# (`fn measure_text(`).
hits="$(grep -RInE '\.[[:space:]]*measure_text[[:space:]]*\(' "${WIDGETS}" 2>/dev/null \
| grep -vE "${ALLOWED_RE}" \
|| true)"
if [ -n "${hits}" ]; then
printf 'FAIL: family-blind text measurement in widget code\n' >&2
printf '\n' >&2
printf '%s\n' "${hits}" >&2
printf '\n' >&2
printf 'RenderBackend::measure_text resolves the backend default font, not the\n' >&2
printf 'family these runs are painted with. Measure through\n' >&2
printf 'crate::widgets::text_metrics instead:\n' >&2
printf '\n' >&2
printf ' cx.backend.measure_text(s, size)\n' >&2
printf ' -> text_metrics::measure_chrome(cx.backend, s, size)\n' >&2
printf ' ellipsize_to_width(s, w, |t| cx.backend.measure_text(t, size))\n' >&2
printf ' -> text_metrics::fit_chrome(cx.backend, s, w, size)\n' >&2
printf ' rect.origin.x + (rect.size.x - measured) / 2.0\n' >&2
printf ' -> text_metrics::centered_text_x(cx.backend, s, size, rect)\n' >&2
printf '\n' >&2
printf 'For a run drawn in some other family (a jian component painting in\n' >&2
printf '"Inter", a monospace readout), name that family:\n' >&2
printf ' text_metrics::measure_in_family / fit_in_family.\n' >&2
exit 1
fi
echo "PASS: text measurement check"