Files
Classroom_Desktop/tests/test-classroom-printing.sh
T
ekklesiaandClaude Opus 5.5 940f63e0e5 fix: a picture prints as the picture, where and as large as the teacher asks
The black bars above and below a printed picture were neither in the
picture nor in the layout. A PNG or JPEG in a tab of its own is shown by
the engine on a viewer page whose background is rgb(14, 14, 14), and Qt
prints page backgrounds by default; 0.2.6.1 printed that page.

A picture is now taken as itself — drawn onto white, capped at 300 dpi
across Letter — and laid out on the printer's paper by the preview, with
Size (as large as fits down to 25%), Position (nine squares), Paper
(automatic, upright, sideways) and Fill the whole page, which says it
cuts the edges off and is never the default. The preview is the PDF that
prints, laid out again after each change. Worksheet PDFs and lesson pages
get no picture controls and are unchanged.

Page backgrounds are no longer printed for lesson pages either, as in
Chrome; Blink darkens light text when it does that, so a white-on-black
page prints readable grey on white. "Colour" is "Color" in the print
interface and the manual.

Lab: 135 print checks, now including eight picture layouts measured on
the job file CUPS kept — byte-identical to the preview, within 1 point of
where asked, no ink anywhere else. Printed from the real session with the
pointer: 50% top left, Elmer-66, the same. Not yet on paper.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-23 21:41:15 -05:00

265 lines
11 KiB
Bash
Executable File

#!/bin/bash
# Wonder Ink's printing: which printers, which options, what is sent.
#
# CUPS is not on the development host, so lpstat, lpoptions, lp and
# pdfinfo are stand-ins that answer with the output the real ones gave in
# the lab — Elmer's own PPD options included. Whether a job really goes
# through Elmer's filters is tools/lab/wonderink-print-check's question.
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
mkdir -p "$WORK/bin"
cat > "$WORK/bin/lpstat" <<EOF
#!/bin/bash
[[ -f "$WORK/cups-down" ]] && { echo "lpstat: Scheduler is not running." >&2; exit 1; }
case "\$1" in
-e) cat "$WORK/printers" 2>/dev/null ;;
-d) cat "$WORK/default" 2>/dev/null || echo "no system default destination" ;;
esac
EOF
# lpoptions -p NAME -l, as the real one printed it for Elmer's PPD.
cat > "$WORK/bin/lpoptions" <<EOF
#!/bin/bash
cat "$WORK/options-\$2" 2>/dev/null
EOF
cat > "$WORK/bin/lp" <<EOF
#!/bin/bash
printf '%s\n' "\$@" > "$WORK/lp-args"
if [[ -f "$WORK/lp-refuses" ]]; then
echo 'lp: Destination "Elmer" is not accepting jobs.' >&2
exit 1
fi
echo "request id is Elmer-42 (1 file(s))"
EOF
cat > "$WORK/bin/pdfinfo" <<'EOF'
#!/bin/bash
[[ "$(head -c 5 "$1" 2>/dev/null)" == "%PDF-" ]] || { echo "Syntax Error" >&2; exit 1; }
echo "Producer: test"
echo "Pages: $(grep -c '^page' "$1")"
EOF
chmod +x "$WORK"/bin/*
printf 'Elmer\n' > "$WORK/printers"
echo "system default destination: Elmer" > "$WORK/default"
cat > "$WORK/options-Elmer" <<'EOF'
PrintoutMode/Print Quality: Draft Draft.Gray *Normal Normal.Gray High High.Gray
PageSize/Page Size: Custom.WIDTHxHEIGHT *Letter A4 11x17 A3 A5 B5 Env10 EnvC5 EnvDL EnvISOB5 EnvMonarch Executive Legal
Duplex/Double-Sided Printing: DuplexNoTumble DuplexTumble *None
ColorModel/Color Mode: FromPrintoutMode *Color Grayscale
PrinterResolution/Resolution: *FromPrintoutMode 300x300dpi 600x600dpi 1200x600dpi 1200x1200dpi
EOF
cat > "$WORK/options-Office" <<'EOF'
PageSize/Media Size: Letter *A4 Legal
EOF
PATH="$WORK/bin:$PATH" WORK="$WORK" PYTHONPATH="$ROOT/lib" python3 - "$ROOT" <<'PY'
import os
import re
import sys
from pathlib import Path
import classroom_printing as printing
root = Path(sys.argv[1])
work = Path(os.environ["WORK"])
passed = failed = 0
def check(label, condition, detail=""):
global passed, failed
if condition:
print(f" ok {label}")
passed += 1
else:
print(f" FAIL {label}")
if detail:
print(f" {detail}")
failed += 1
def lp_args():
return (work / "lp-args").read_text().splitlines()
print("1. which printers a teacher is offered")
check("Elmer, and Elmer is the default", printing.printers() == (["Elmer"], "Elmer"))
(work / "printers").write_text("Elmer\nOffice\n")
(work / "default").write_text("system default destination: Office\n")
check("the system default is preselected, whichever it is",
printing.printers() == (["Elmer", "Office"], "Office"))
(work / "default").write_text("no system default destination\n")
check("with no default, the first printer",
printing.printers() == (["Elmer", "Office"], "Elmer"))
(work / "default").write_text("system default destination: Gone\n")
check("a default that is not in the list is not offered",
printing.printers() == (["Elmer", "Office"], "Elmer"))
(work / "printers").write_text("")
check("no printers at all is an empty list, not an error",
printing.printers() == ([], None))
(work / "cups-down").touch()
check("and so is CUPS not running", printing.printers() == ([], None))
(work / "cups-down").unlink()
(work / "printers").write_text("Elmer\n")
(work / "default").write_text("system default destination: Elmer\n")
print()
print("2. what each printer's driver offers")
elmer = printing.options("Elmer")
check("Elmer's paper is Letter", elmer["paper"] == "Letter", elmer)
# Measured: this driver ignores print-color-mode and prints in colour.
check("black and white is asked for in the driver's own terms",
elmer["gray"] == ("ColorModel", "Grayscale"), elmer)
office = printing.options("Office")
check("an A4 printer is A4", office["paper"] == "A4", office)
check("a printer with no grey choice offers none", office["gray"] is None, office)
unknown = printing.options("Nobody")
check("a printer that says nothing is Letter, colour only",
unknown == {"paper": "Letter", "gray": None}, unknown)
print()
print("3. a teacher's page list")
for text, expected in (("", None), ("all", None), (" ", None), ("2", "2"),
("1-3", "1-3"), ("1, 3, 5-6", "1,3,5-6"), ("3-3", "3"),
("1 - 2", "1-2"), ("6", "6")):
try:
got = printing.parse_pages(text, 6)
except ValueError as error:
got = f"refused: {error}"
check(f"{text!r} is {expected!r}", got == expected, got)
for text in ("7", "0", "3-1", "1-9", "one", "1;lp -d x", "-2", "1--2", "2,", "1.5"):
try:
printing.parse_pages(text, 6)
check(f"{text!r} is refused", False)
except ValueError as error:
check(f"{text!r} is refused, and says why: {error}", bool(str(error)))
print()
print("4. what is handed to CUPS")
pdf = work / "Colour the Ark.pdf"
pdf.write_text("%PDF-1.4\npage\npage\n")
check("a PDF with two pages has two", printing.page_count(pdf) == 2)
check("something that is not a PDF has none",
printing.page_count(work / "printers") == 0)
ok, answer = printing.submit(pdf, "Elmer")
args = lp_args()
check("a job is accepted, with its id", (ok, answer) == (True, "Elmer-42"), answer)
check("to Elmer, one copy, named for the lesson",
args[:6] == ["-d", "Elmer", "-n", "1", "-t", "Wonder Ink"], args)
check("the file comes after --, so no name can be read as an option",
args[-2:] == ["--", str(pdf)], args)
check("and nothing else is asked for", "-P" not in args and "-o" not in args, args)
printing.submit(pdf, "Elmer", copies=3, pages="2-3", gray=("ColorModel", "Grayscale"),
title="Noah")
args = lp_args()
check("copies, pages and black and white arrive as lp options",
args[args.index("-n") + 1] == "3"
and args[args.index("-P") + 1] == "2-3"
and args[args.index("-o") + 1] == "ColorModel=Grayscale"
and args[args.index("-t") + 1] == "Noah", args)
printing.submit(pdf, "Elmer", copies=500)
check(f"copies are capped at {printing.MAX_COPIES}",
lp_args()[lp_args().index("-n") + 1] == str(printing.MAX_COPIES))
printing.submit(pdf, "Elmer", copies=0)
check("and never fewer than one", lp_args()[lp_args().index("-n") + 1] == "1")
(work / "lp-refuses").touch()
ok, answer = printing.submit(pdf, "Elmer")
check("a refused job is a failure, in CUPS's own words",
not ok and answer == 'Destination "Elmer" is not accepting jobs.', answer)
print()
print("4b. where a picture goes on the paper")
LETTER, MARGIN = (612, 792), 28.35
area = (MARGIN, MARGIN, 612 - 2 * MARGIN, 792 - 2 * MARGIN)
close = lambda a, b: all(abs(x - y) < 0.01 for x, y in zip(a, b))
x, y, w, h = printing.picture_rect(LETTER, MARGIN, (1600, 1000))
check("a wide picture fits the width and keeps its shape",
abs(w - area[2]) < 0.01 and abs(w / h - 1.6) < 0.001, (x, y, w, h))
check("and is centred on the page", abs(y + h / 2 - 396) < 0.01)
check("nothing of it falls outside the margins",
x >= MARGIN - 0.01 and y >= MARGIN - 0.01
and x + w <= 612 - MARGIN + 0.01 and y + h <= 792 - MARGIN + 0.01)
x, y, w, h = printing.picture_rect(LETTER, MARGIN, (1600, 1000), 50, "top left")
check("50% in the top left is half the fitted size, at the margin",
close((x, y, w), (MARGIN, MARGIN, area[2] / 2)), (x, y, w, h))
x, y, w, h = printing.picture_rect(LETTER, MARGIN, (1600, 1000), 25, "bottom right")
check("25% in the bottom right ends at the margin",
close((x + w, y + h), (612 - MARGIN, 792 - MARGIN)), (x, y, w, h))
x, y, w, h = printing.picture_rect(LETTER, MARGIN, (900, 1400), 100, "right")
check("a tall picture fits the height and sits at the right",
abs(h - area[3]) < 0.01 and abs(x + w - (612 - MARGIN)) < 0.01, (x, y, w, h))
x, y, w, h = printing.picture_rect(LETTER, MARGIN, (1600, 1000), fill=True)
check("fill covers the whole printable area",
x <= MARGIN and y <= MARGIN + 0.01 and x + w >= 612 - MARGIN and y + h >= 792 - MARGIN - 0.01)
check("and keeps the picture's shape, cutting off what overhangs",
abs(w / h - 1.6) < 0.001 and w > area[2])
check("fill ignores size and position",
printing.picture_rect(LETTER, MARGIN, (1600, 1000), 25, "top left", True)
== printing.picture_rect(LETTER, MARGIN, (1600, 1000), fill=True))
check("an unknown position is the centre",
printing.picture_rect(LETTER, MARGIN, (1600, 1000), 50, "upside down")
== printing.picture_rect(LETTER, MARGIN, (1600, 1000), 50, "centre"))
try:
printing.picture_rect(LETTER, MARGIN, (0, 10))
check("a picture with no size is refused", False)
except ValueError:
check("a picture with no size is refused", True)
check("automatic paper is landscape for a wide picture",
printing.picture_landscape("auto", 1600, 1000))
check("and portrait for a tall or square one",
not printing.picture_landscape("auto", 900, 1400)
and not printing.picture_landscape("auto", 1000, 1000))
check("and the teacher's choice overrides it",
not printing.picture_landscape("portrait", 1600, 1000)
and printing.picture_landscape("landscape", 900, 1400))
check("every position is on a three by three grid",
len(printing.PICTURE_POSITIONS) == 9
and sorted(printing.PICTURE_POSITIONS.values()) == [(c, r) for c in range(3) for r in range(3)])
print()
print("5. Wonder Ink prints through this, and only this")
ink = (root / "scripts/classroom-wonderink").read_text()
code = "\n".join(line for line in ink.splitlines() if not line.lstrip().startswith("#"))
# The 0.2.2 crash, and the blank worksheet, both came from Qt's print path.
check("no QPrinter", "QPrinter(" not in code and "QPrintDialog" not in code)
check("no QWebEngineView.print()", not re.search(r"\bview\.print\(", code))
check("a lesson page becomes a PDF by printToPdf", "printToPdf(" in code)
check("which is given up on if the engine never answers",
"PREPARE_TIMEOUT_MS" in code and "self._timer.start(PREPARE_TIMEOUT_MS)" in code)
check("the preview submits with classroom_printing.submit",
"classroom_printing.submit(" in code)
check("there is no file chooser in it", "QFileDialog" not in code)
check("a worksheet tab is printed from its file",
"self._pdf_views[view] = resolved" in code)
check("an inline PDF is fetched as a file first", "view.page().download(view.url())" in code)
check("a page's background is not printed, as in Chrome",
"PrintElementBackgrounds, False" in code)
check("a picture on its own is laid out, not printed as the viewer page",
"dialog.show_picture(image)" in code and "document.contentType" in ink)
preview_code = code[code.index("class PrintPreview"):code.index("\ndef notify")]
check("the print interface says Color, not Colour",
'"Color"' in preview_code and '"Colour"' not in preview_code)
check("an exception in a handler is logged, not fatal",
"sys.excepthook = keep_going" in code)
check("the module is packaged",
"lib/classroom_printing.py" in (root / "tools/build-release.sh").read_text())
print()
print(f"{passed} passed, {failed} failed")
sys.exit(1 if failed else 0)
PY