. "$BST_LIB" # BST-TIMEOUT: 300 # ~130 cases, one probe run # harfbuzz 8.3.0 -- the text-shaping engine, as poky ships it from # meta/recipes-graphics/harfbuzz with its backported CVE-2026-22693 patch. # # WHAT THIS IMAGE ACTUALLY HAS, AND WHAT IT DOES NOT. harfbuzz's five # command-line utilities -- hb-shape, hb-subset, hb-info, hb-ot-shape-closure, # hb-view -- are ALL wrapped in `if conf.get('HAVE_GLIB', 0) == 1` in # util/meson.build, and glib-2.0 cannot be built for this target (it DEPENDS on # libffi, whose wasm32 backend is Emscripten-only and waits on the HWJS # dynamic-linking milestone). So the deliverable here is /usr/lib/libharfbuzz.a # plus the headers, and there is no hb-shape to drive. That absence is asserted # below rather than left implicit, so an image that one day grows the utilities # reports the change instead of quietly passing. # # data/harfbuzz-probe is the consumer that stands in for hb-shape: it drives the # same hb_shape() entry point through the same hb_buffer_serialize_glyphs() # TEXT format hb-shape prints, so every R1-shape line below can be pasted # straight into hb-shape on a workstation. data/harfbuzz-native.txt is THAT SAME # SOURCE built against THAT SAME harfbuzz release on x86-64, so a disagreement # is a disagreement between the two machines and nothing else. # # THE FIXTURE FONT IS PINNED BY THE RECIPE, NOT BY THIS FILE. # /usr/libexec/harfbuzz-tests/harfbuzz-font.ttf is # test/subset/data/fonts/NotoIKEAHebrewLatin-Regular.ttf, installed straight out # of the harfbuzz tarball the recipe fixes by SRC_URI[sha256sum] -- so fixture # and shaper cannot drift apart. One face carries all four cases the port has to # answer for: Latin with GPOS kerning, GSUB f_f_i/f_f_l ligatures, combining # marks (Latin composition and Hebrew niqqud attachment), and a right-to-left # Hebrew run with real glyphs. # # WHY THE COMPARISON IS EXACT. Shaping is integer arithmetic on font units all # the way through: cmap lookup, GSUB substitution, GPOS value records and mark # anchors are table reads and integer adds, with no floating point and no libm # call anywhere between the font file and the serialized string. There is # nothing here for an arm to round differently, so the output on a correct port # is not close to the reference, it is identical to it. A tolerance would be # measuring nothing. # # READ A RED IN TAG ORDER -- the first tag to disagree localises the defect: # # R0 version, upem, glyph count the file was opened and its head parsed # R4 raw sfnt table lengths pure I/O -- every table was found # R5 GSUB/GPOS script+feature tags the layout tables parse # R2 cmap lookups and glyph names cmap and post parsing # R3 glyph extents and advances glyf/hmtx, integer, no shaping yet # R1 the shaped strings the whole pipeline # R6 the refusals a bad font is refused, not guessed at # # THE ANCHORED CASES CONSULT NO CONTROL. They are properties of the FONT FILE # (its upem, its glyph count, the two characters it does not carry) or of the # ALGORITHM (kerning off must change the answer; ligatures off must produce more # glyphs; an RTL run must come out in the opposite order from the same text # forced LTR), and every one is true by construction. A control captured from an # already-broken build cannot make any of them pass. D=/usr/libexec/harfbuzz-tests PROBE=$D/harfbuzz-probe FONT=$D/harfbuzz-font.ttf NATIVE=$BST_DATA/harfbuzz-native.txt ARCHIVE=/usr/lib/libharfbuzz.a INCDIR=/usr/include/harfbuzz if [ ! -f "$ARCHIVE" ]; then bst_skip harfbuzz/present "harfbuzz not installed on this image" exit 0 fi bst_ok harfbuzz/present # --- the package is whole ---------------------------------------------------- check harfbuzz/staticlib test -f "$ARCHIVE" check harfbuzz/staticlib-subset test -f /usr/lib/libharfbuzz-subset.a check harfbuzz/hdr-hb test -f "$INCDIR/hb.h" check harfbuzz/hdr-hb-ot test -f "$INCDIR/hb-ot.h" check harfbuzz/hdr-hb-blob test -f "$INCDIR/hb-blob.h" check harfbuzz/hdr-hb-buffer test -f "$INCDIR/hb-buffer.h" check harfbuzz/hdr-hb-subset test -f "$INCDIR/hb-subset.h" check harfbuzz/pkgconfig test -f /usr/lib/pkgconfig/harfbuzz.pc check_out harfbuzz/pc-libs "-lharfbuzz" cat /usr/lib/pkgconfig/harfbuzz.pc # THE FREETYPE ARM, DECLARED IN THE FIELD THAT WORKS FOR A STATIC LINK, and on # this image that is the only link there is. meson writes freetype2 into # `Requires:` rather than `Requires.private:`, so pkg-config resolves it for a # plain --libs as well as for --libs --static, and a consumer of this port cannot # end up with the phantom-import failure the qrencode bbappend documents. It is # also the configure answer preserved into the rootfs: the field is absent # entirely when the arm is off. check_out harfbuzz/pc-requires-freetype "Requires: freetype2" cat /usr/lib/pkgconfig/harfbuzz.pc # THE FREETYPE ARM IS ON, AND hb-ft.h IS HOW THE IMAGE CAN TELL. src/meson.build # installs that header only when the freetype dependency was found, so its # presence is the configure answer preserved into the rootfs -- not a claim this # file makes about the recipe. check harfbuzz/hdr-hb-ft test -f "$INCDIR/hb-ft.h" # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape harfbuzz/no-shared "/usr/lib/libharfbuzz*.so*" # THE UTILITIES ARE ABSENT, AND THAT IS THE PORT'S ONE STATED GAP. Asserting it # keeps the gap visible: if glib ever lands and hb-shape appears, this case goes # red and whoever sees it gets to delete it and cover the tool properly. missing_utils= for u in hb-shape hb-subset hb-info hb-view hb-ot-shape-closure; do [ -x "/usr/bin/$u" ] && missing_utils="$missing_utils $u" done if [ -z "$missing_utils" ]; then bst_ok harfbuzz/utils-absent-as-expected else bst_notok harfbuzz/utils-absent-as-expected "utilities now present:$missing_utils -- glib arrived; cover them and drop this case" fi # --- the fixtures ship ------------------------------------------------------- check harfbuzz/probe-present test -f "$PROBE" check harfbuzz/font-present test -f "$FONT" check harfbuzz/native-present test -f "$NATIVE" if [ ! -f "$PROBE" ] || [ ! -f "$FONT" ] || [ ! -f "$NATIVE" ]; then bst_notok harfbuzz/probe-run "fixtures missing; cannot drive the shaper" exit 0 fi # --- run the probe ----------------------------------------------------------- # The probe is installed 0755 into libexecdir by the recipe, so unlike a data/ # payload it runs where it sits; nothing has to be staged into /tmp. out=${TMPDIR:-/tmp}/bst-harfbuzz-out.$$ "$PROBE" "$FONT" > "$out" 2>&1 rc=$? if [ "$rc" -eq 0 ]; then bst_ok harfbuzz/probe-rc else bst_notok harfbuzz/probe-rc "rc=$rc $(_bst_1line "$(cat "$out" 2>/dev/null)")"; fi if grep -q '^ALL-HARFBUZZ-OK$' "$out" 2>/dev/null; then bst_ok harfbuzz/probe-allok else bst_notok harfbuzz/probe-allok "$(_bst_1line "$(cat "$out" 2>/dev/null)")" fi lines=$(grep -c '^R[0-9]' "$out" 2>/dev/null || echo 0) if [ "$lines" -gt 0 ]; then bst_ok harfbuzz/probe-output else bst_notok harfbuzz/probe-output "probe emitted no tagged lines"; fi # --- compare every tagged line against the native control -------------------- # One case per reference line. The key is the tag alone for whole-face facts and # `tag item` for per-string / per-table / per-codepoint ones, so a red names the # exact thing that disagreed rather than reporting one giant diff. awk ' function keyof(f1, f2) { if (f1 ~ /^(R1-shape|R1-dir|R1-script|R2-cmap|R3-extents|R4-table)$/) return f1 " " f2 return f1 } function valof( i, s, start) { start = (keyof($1,$2) ~ / /) ? 3 : 2 s = "" for (i = start; i <= NF; i++) s = s (s == "" ? "" : " ") $i return s } FNR == NR { if ($1 ~ /^R[0-9]/) { k = keyof($1,$2); want[k] = valof(); order[++n] = k } next } { if ($1 ~ /^R[0-9]/) { k = keyof($1,$2); got[k] = valof(); seen[k] = 1 } } END { for (i = 1; i <= n; i++) { k = order[i] id = k; gsub(/[^A-Za-z0-9+-]+/, "-", id) if (!seen[k]) printf "FAIL %s missing-from-probe\n", id else if (got[k] == want[k]) printf "PASS %s\n", id else printf "FAIL %s want=[%s] got=[%s]\n", id, want[k], got[k] } } ' "$NATIVE" "$out" | while read -r verdict id detail; do case "$verdict" in PASS) bst_ok "harfbuzz/$id" ;; *) bst_notok "harfbuzz/$id" "$detail" ;; esac done # --- anchored: properties of the font file ----------------------------------- check_eq harfbuzz/anchor-version 8.3.0 "$(awk '/^R0-version /{print $2}' "$out")" check_eq harfbuzz/anchor-upem 1000 "$(awk '/^R0-upem /{print $2}' "$out")" check_eq harfbuzz/anchor-numglyphs 2200 "$(awk '/^R0-numglyphs /{print $2}' "$out")" check_eq harfbuzz/anchor-facecount 1 "$(awk '/^R0-facecount /{print $2}' "$out")" # The font carries no CJK. A nominal-glyph lookup returning 0 for an absent # character is an ANSWER, and a cmap walker that invented a glyph would be broken # in a way no present-character case can see. check_eq harfbuzz/anchor-absent-cjk 0 "$(awk '/^R2-cmap U\+4E00 /{print $3}' "$out")" # It DOES carry Cyrillic -- asserted alongside, so the case above cannot pass by # a lookup that returns 0 for everything. gid_zhe=$(awk '/^R2-cmap U\+0416 /{print $3}' "$out") if [ -n "$gid_zhe" ] && [ "$gid_zhe" -gt 0 ]; then bst_ok harfbuzz/anchor-present-cyrillic else bst_notok harfbuzz/anchor-present-cyrillic "U+0416 resolved to gid $gid_zhe"; fi # The layout tables the shaping cases depend on are actually there and non-empty. for t in cmap glyf GSUB GPOS GDEF; do len=$(awk -v T="$t" '$1 == "R4-table" && $2 == T {print $3}' "$out") if [ -n "$len" ] && [ "$len" -gt 0 ]; then bst_ok "harfbuzz/anchor-table-$t" else bst_notok "harfbuzz/anchor-table-$t" "length=$len"; fi done # The space glyph has a zero-area outline and a non-zero advance -- an empty # outline is not an error, and a shaper that gave space a width of 0 would # collapse every word boundary on the image. check_eq harfbuzz/anchor-space-extents "xb=0 yb=0 w=0 h=0 adv=260" \ "$(awk '/^R3-extents space /{print $4, $5, $6, $7, $8}' "$out")" # --- anchored: properties of the algorithm ----------------------------------- # Each of these pairs a feature with its contrast. Asserting only the positive # half would still pass if the switch had silently stopped reaching the shaper. _shape() { awk -v T="$1" '$1 == "R1-shape" && $2 == T { $1=""; $2=""; sub(/^ /,""); print }' "$out"; } kern_on=$(_shape latin); kern_off=$(_shape latin-nokern) if [ -n "$kern_on" ] && [ "$kern_on" != "$kern_off" ]; then bst_ok harfbuzz/anchor-kern-applied else bst_notok harfbuzz/anchor-kern-applied "kerning on and off give the same positions; GPOS kern is not reaching the shaper"; fi liga_on=$(_shape liga); liga_off=$(_shape liga-off) n_on=$(echo "$liga_on" | awk '{print $1}'); n_off=$(echo "$liga_off" | awk '{print $1}') if [ -n "$n_on" ] && [ -n "$n_off" ] && [ "$n_on" -lt "$n_off" ]; then bst_ok harfbuzz/anchor-liga-applied else bst_notok harfbuzz/anchor-liga-applied "liga on gives $n_on glyphs, off gives $n_off; GSUB liga is not reducing the run"; fi case "$liga_on" in *f_f_i*) bst_ok harfbuzz/anchor-liga-ffi ;; *) bst_notok harfbuzz/anchor-liga-ffi "no f_f_i glyph in the shaped ligature run" ;; esac case "$liga_on" in *f_f_l*) bst_ok harfbuzz/anchor-liga-ffl ;; *) bst_notok harfbuzz/anchor-liga-ffl "no f_f_l glyph in the shaped ligature run" ;; esac # A right-to-left run comes out in visual order, so its FIRST glyph carries the # LAST cluster of the text -- while the same bytes forced LTR start at cluster 0. check_eq harfbuzz/anchor-rtl-direction rtl "$(awk '/^R1-dir rtl /{print $3}' "$out")" rtl_first=$(_shape rtl | awk '{print $2}' | sed 's/^\[//;s/|.*//') ltr_first=$(_shape rtl-as-ltr | awk '{print $2}' | sed 's/^\[//;s/|.*//') case "$rtl_first" in *=6+*) bst_ok harfbuzz/anchor-rtl-reversed ;; *) bst_notok harfbuzz/anchor-rtl-reversed "first RTL glyph is [$rtl_first]; expected the last cluster (6) first" ;; esac case "$ltr_first" in *=0+*) bst_ok harfbuzz/anchor-ltr-not-reversed ;; *) bst_notok harfbuzz/anchor-ltr-not-reversed "first forced-LTR glyph is [$ltr_first]; expected cluster 0 first" ;; esac # Combining marks: the Latin case composes a+U+0301 into ONE precomposed glyph # (harfbuzz's own normalisation, not the font's), and the Hebrew case attaches # niqqud with a GPOS anchor -- which shows up as an @x,y offset on a zero-advance # glyph. Neither is reachable without the mark machinery working. check_eq harfbuzz/anchor-marks-composed 2 "$(awk '/^R1-shape marks /{print $3}' "$out")" case "$(_shape marks)" in *aacute*) bst_ok harfbuzz/anchor-marks-aacute ;; *) bst_notok harfbuzz/anchor-marks-aacute "a + U+0301 did not compose to aacute" ;; esac case "$(_shape rtl-niqqud)" in *@*,*+0*) bst_ok harfbuzz/anchor-mark-attached ;; *) bst_notok harfbuzz/anchor-mark-attached "no anchored zero-advance mark in the niqqud run" ;; esac # --- anchored: the unsupported-font case is a REFUSAL, at three layers -------- # A path that is not there, a blob that is not a font, and shaping through the # empty face the second one yields. Each must refuse rather than crash or return # something a caller would mistake for shaped text. check_eq harfbuzz/anchor-missing-file refused "$(awk '/^R6-missing-file /{print $2}' "$out")" check_eq harfbuzz/anchor-junk-facecount 0 "$(awk '/^R6-junk-facecount /{print $2}' "$out")" check_eq harfbuzz/anchor-junk-glyphs 0 "$(awk '/^R6-junk-glyphs /{print $2}' "$out")" case "$(awk '/^R6-junk-shape /{ $1=""; print }' "$out")" in *gid0*) bst_ok harfbuzz/anchor-junk-notdef ;; *) bst_notok harfbuzz/anchor-junk-notdef "shaping through a junk face did not yield .notdef" ;; esac # --- the FreeType arm, anchored against the built-in backend ------------------ # These consult no control by design: they compare two font backends inside ONE # run, so they are true on any machine and cannot be captured wrong. The native # reference is built without FreeType (the build host has no freetype2 headers), # which is why these are not in it. ft_init=$(awk '/^F1-ft-init /{print $2}' "$out") if [ "$ft_init" = "ok" ]; then bst_ok harfbuzz/ft-init else bst_notok harfbuzz/ft-init "FT_Init_FreeType/FT_New_Face reported [$ft_init]"; fi check_eq harfbuzz/ft-vs-ot-glyphs same "$(awk '/^F2-ft-vs-ot-glyphs /{print $2}' "$out")" check_eq harfbuzz/ft-vs-ot-advances same "$(awk '/^F3-ft-vs-ot-advances /{print $2}' "$out")" rm -f "$out"