diff --git a/help/C/org.udpscope.app/figures/main-window.png b/help/C/org.udpscope.app/figures/main-window.png index d64e7d0..449791c 100644 Binary files a/help/C/org.udpscope.app/figures/main-window.png and b/help/C/org.udpscope.app/figures/main-window.png differ diff --git a/help/C/org.udpscope.app/figures/math-editor.png b/help/C/org.udpscope.app/figures/math-editor.png index d158c2c..e7d800c 100644 Binary files a/help/C/org.udpscope.app/figures/math-editor.png and b/help/C/org.udpscope.app/figures/math-editor.png differ diff --git a/help/C/org.udpscope.app/figures/trigger-capture.png b/help/C/org.udpscope.app/figures/trigger-capture.png index f46ed81..299392e 100644 Binary files a/help/C/org.udpscope.app/figures/trigger-capture.png and b/help/C/org.udpscope.app/figures/trigger-capture.png differ diff --git a/tools/check_help.sh b/tools/check_help.sh index cbedb59..3c1496d 100755 --- a/tools/check_help.sh +++ b/tools/check_help.sh @@ -3,9 +3,9 @@ # SPDX-FileCopyrightText: 2026 Martino Ferrari # # The manual's check, the way tools/check_ui.py is the .ui files' check: it runs the four -# yelp-check subcommands that need no network, checks the structural rules below, then renders -# the whole set to HTML with yelp-build so a page that only fails at the transform step is -# caught too. +# yelp-check subcommands that need no network, checks the structural rules below, checks the +# figures, then renders the whole set to HTML with yelp-build so a page that only fails at the +# transform step is caught too. # # `yelp-check validate` is deliberately absent, and it is worth being exact about why: it # builds the RelaxNG schema locally with `xsltproc` and `mal-rng.xsl`, but the schema it @@ -15,11 +15,10 @@ # What that leaves unverified is *structure*, and that is not a small gap: three pages # carrying a `
` whose `` was a sentence and which had no `<desc>` opened in # nothing, while links, media, orphans, ids and the HTML render all passed. So the rules this -# project has actually needed are checked here, with xmllint's XPath and no schema at all. -# Add a rule when a page *teaches* one -- and do not add one you have not seen broken: a rule -# that fails a legal page is worse than no rule. +# project has actually needed are checked here. Add a rule when a page *teaches* one -- and do +# not add one you have not seen broken: a rule that fails a legal page is worse than no rule. # -# Every expression uses local-name() on purpose. A Mallard page declares +# Every XPath expression uses local-name() on purpose. A Mallard page declares # `xmlns="http://projectmallard.org/1.0/"`, and XPath 1.0 has no default namespace, so # `//figure` matches *nothing* and every rule below silently passes. Written naively, this # check reported "ok" for the very defect it was written for. @@ -58,11 +57,51 @@ no text sits directly under page|count(/*[local-name()='page']/text()[normalize- a media names its src|count(//*[local-name()='media'][not(@src)]) RULES done + +# The figures, and this one is a *decoder's* rule rather than ours: a `bKGD` chunk is forbidden +# by the PNG specification on an image with an alpha channel, and `import` writes one because it +# records the root window's background. ImageMagick both writes and reads it, so a round trip +# through it proves nothing -- which is how three figures passed every check while WebKit +# refused them, showing a figure that never loaded and, over the `help:` URI, a page that spun +# forever. Only the chunks PNG allows for the colour type are accepted; a whitelist, because a +# decoder is one. +python3 - "$dir" <<'PY' || failed=1 +import glob, os, struct, sys +seen = 0 +for path in sorted(glob.glob(os.path.join(sys.argv[1], 'figures', '*.png'))): + seen += 1 + with open(path, 'rb') as f: + if f.read(8) != b'\x89PNG\r\n\x1a\n': + print(f" {os.path.basename(path)}: not a PNG", file=sys.stderr) + continue + chunks = [] + while True: + head = f.read(8) + if len(head) < 8: + break + length, kind = struct.unpack('>I4s', head) + f.read(length) + f.read(4) + chunks.append(kind) + # Colour type 4 (grey+alpha) and 6 (RGB+alpha) have an alpha channel; bKGD is not permitted + # on either. Anything else is left alone: this checks the rule a page was broken by. + colour_type = None + with open(path, 'rb') as f: + f.read(16) + colour_type = f.read(1)[0] + if colour_type in (4, 6) and b'bKGD' in chunks: + print(f" {os.path.basename(path)}: bKGD on an image with alpha (colour type {colour_type})" + " -- PNG forbids it and a strict decoder refuses the file", file=sys.stderr) + sys.exit(1) +if seen == 0: + print(" no figures to check", file=sys.stderr) +PY + if [ "$failed" != 0 ]; then - echo "check_help: structural findings above" >&2 + echo "check_help: findings above" >&2 exit 1 fi -printf 'structure %s: ok\n' "$dir" +printf 'structure %s: ok; figures: ok\n' "$dir" mkdir -p "$tmp/html" yelp-build html -o "$tmp/html" "$dir" diff --git a/tools/make-help-shots.sh b/tools/make-help-shots.sh index 14cc0cf..e6419b6 100755 --- a/tools/make-help-shots.sh +++ b/tools/make-help-shots.sh @@ -221,4 +221,16 @@ APP_ARGS="--plots 1" start_app "$tmp/math-editor.log" \ sleep 10 grab math-editor "$tmp/math-editor.log" "$boxes_math" pick_other_window +# The last step, and it is not cosmetic: `import` records the root window's background, so the +# PNG leaves ImageMagick with a `bKGD` chunk -- forbidden by the PNG specification on an image +# with an alpha channel (colour types 4 and 6), which these are. ImageMagick writes it and reads +# it back happily, so a round-trip through `magick` proves nothing; a stricter decoder refuses +# the file. In Yelp that showed as a figure that never loaded, and over the `help:` URI as a page +# that spun forever, because the load event never fired. Normalise every figure to the chunks a +# decoder is guaranteed to accept, whatever the grab produced. +for f in "$out"/*.png; do + magick "$f" -strip -define png:exclude-chunk=bKGD,cHRM,tIME,tEXt,tXMP,exif \ + -define png:compression-level=9 "$f" +done + echo "make-help-shots: done"