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]>
183 lines
9.7 KiB
Python
183 lines
9.7 KiB
Python
"""Drive Wonder Ink's print preview through one scenario, headless.
|
|
|
|
usage: wonderink-print-rig.py WONDERINK_COMMAND SCENARIO
|
|
|
|
Run by tools/lab/wonderink-print-check inside the lab, against the
|
|
command the package installed. It presses the preview's own buttons the
|
|
way a teacher would, and prints what happened; the check around it asks
|
|
CUPS what actually arrived.
|
|
"""
|
|
import importlib.machinery, importlib.util, sys, time, faulthandler, gc, os
|
|
faulthandler.enable()
|
|
path, scenario = sys.argv[1], sys.argv[2]
|
|
loader = importlib.machinery.SourceFileLoader("wonderink", path)
|
|
spec = importlib.util.spec_from_loader("wonderink", loader)
|
|
wi = importlib.util.module_from_spec(spec); loader.exec_module(wi)
|
|
|
|
from pathlib import Path
|
|
from PyQt6.QtCore import QTimer, QUrl
|
|
from PyQt6.QtGui import QPdfWriter, QPainter, QFont, QPageSize
|
|
from PyQt6.QtWidgets import QApplication
|
|
from PyQt6.QtWebEngineCore import QWebEngineProfile, QWebEngineSettings
|
|
|
|
notes = []
|
|
wi.notify = lambda s, b: (notes.append(s), print("NOTIFY", s, "|", b, flush=True))
|
|
app = QApplication(sys.argv)
|
|
# Wonder Ink's own profile, settings and all — not one of the rig's.
|
|
profile = wi.build_profile(app, "rig")
|
|
window = wi.WonderInk(profile)
|
|
profile.downloadRequested.connect(window.on_download)
|
|
window.resize(1280, 800); window.show()
|
|
t0 = time.monotonic()
|
|
def stamp(m): print(f"[{time.monotonic()-t0:6.2f}s] {m}", flush=True)
|
|
|
|
def lesson(view, title="Noahs Ark lesson"):
|
|
view.setHtml(f"<html><head><title>{title}</title></head><body>" + "".join(
|
|
f"<h1>{title}, part {i}</h1><p>{'Noah built an ark. ' * 80}</p>" for i in range(5)
|
|
) + "</body></html>", QUrl("https://app.wonderink.org/lesson"))
|
|
|
|
def worksheet(name="Colour the Ark"):
|
|
d = wi.DOWNLOADS; d.mkdir(parents=True, exist_ok=True)
|
|
p = d / f"{name}.pdf"
|
|
w = QPdfWriter(str(p)); w.setPageSize(QPageSize(QPageSize.PageSizeId.Letter)); w.setResolution(300)
|
|
pa = QPainter(w); pa.setFont(QFont("DejaVu Sans", 28))
|
|
pa.drawText(200, 400, f"WORKSHEET {name} page one"); pa.drawRect(200, 600, 2000, 1500)
|
|
w.newPage(); pa.drawText(200, 400, f"WORKSHEET {name} page two"); pa.end()
|
|
return p
|
|
|
|
def when(cond, then, label, limit=30):
|
|
start = time.monotonic()
|
|
def poll():
|
|
try: ok = cond()
|
|
except Exception as e: ok = False
|
|
if ok: return then()
|
|
if time.monotonic() - start > limit:
|
|
stamp(f"GAVE UP waiting for {label}"); app.exit(4); return
|
|
QTimer.singleShot(100, poll)
|
|
QTimer.singleShot(100, poll)
|
|
|
|
def preview(): return window._preview
|
|
def ready(): return preview() is not None and preview()._pdf is not None
|
|
def describe(d):
|
|
return f"picture={d._picture_box.isVisible()} shown={d._page in d._images} pages={d._pages} printers={[d._printer.itemText(i) for i in range(d._printer.count())]} paper={d.paper()} colour_offered={d._colour.isVisible()} status={d._status.text()!r}"
|
|
|
|
def finish(code=0):
|
|
stamp(f"RESULT notes={notes} preview_open={preview() is not None} tabs={window._tabs.count()}")
|
|
QTimer.singleShot(1500, lambda: app.exit(code))
|
|
|
|
def press_print(then=None):
|
|
d = preview(); stamp("PREVIEW " + describe(d))
|
|
d._print.click()
|
|
stamp(f"after Print: open={preview() is not None} status={d._status.text() if preview() else ''!r}")
|
|
(then or finish)()
|
|
|
|
actions = {
|
|
"web": lambda view: when(ready, press_print, "preview"),
|
|
"cancel": lambda view: when(ready, lambda: (stamp("PREVIEW " + describe(preview())), preview()._cancel.click(), finish()), "preview"),
|
|
"options": lambda view: when(ready, lambda: (
|
|
preview()._which.setCurrentIndex(1), preview()._range.setText("2-3"),
|
|
preview()._copies.setValue(2), preview()._colour.setCurrentIndex(1), press_print()), "preview"),
|
|
"bad-range": lambda view: when(ready, lambda: (
|
|
preview()._which.setCurrentIndex(1), preview()._range.setText("7"), preview()._print.click(),
|
|
stamp(f"bad range: open={preview() is not None} status={preview()._status.text()!r}"),
|
|
preview()._range.setText("1"), press_print()), "preview"),
|
|
"broken": lambda view: when(ready, lambda: (
|
|
preview()._printer.setCurrentText("Refusing"),
|
|
stamp(f"switched to Refusing: paper={preview().paper()} status={preview()._status.text()!r}"),
|
|
when(ready, lambda: (
|
|
stamp(f"re-rendered on {preview().paper()}: pages={preview()._pages}"),
|
|
preview()._print.click(),
|
|
stamp(f"broken: open={preview() is not None} status={preview()._status.text()!r}"),
|
|
preview()._cancel.click(), finish()), "re-render")), "preview"),
|
|
"page-closes-itself": lambda view: when(ready, lambda: (
|
|
view.page().runJavaScript("window.close()"),
|
|
when(lambda: preview() and "closed" in preview()._status.text(), lambda: (
|
|
stamp(f"after page closed: status={preview()._status.text()!r} print_enabled={preview()._print.isEnabled()}"),
|
|
preview()._cancel.click(), finish()), "closed message")), "preview"),
|
|
"close-window": lambda view: when(ready, lambda: (stamp("closing window with preview open"), window.close()), "preview"),
|
|
"page-prints-again": lambda view: when(ready, lambda: (
|
|
view.page().runJavaScript("window.print()"),
|
|
QTimer.singleShot(800, lambda: (stamp(f"second request while open: still one preview={preview() is not None}"), press_print()))), "preview"),
|
|
}
|
|
|
|
if scenario.startswith("picture:"):
|
|
# picture:FILE:SIZE:POSITION:PAPER:FILL[:color]
|
|
# Opens a picture on its own, chooses the layout with the preview's own
|
|
# controls, keeps the exact PDF the preview showed, then prints it.
|
|
_, name, size, position, paper, fill, *rest = scenario.split(":")
|
|
view = window.open_tab(); view.load(QUrl(f"http://127.0.0.1:8089/{name}"))
|
|
def ready_picture():
|
|
d = preview()
|
|
return d is not None and d._picture is not None and d._pdf is not None
|
|
def choose():
|
|
d = preview()
|
|
d._size.setCurrentIndex(d._size.findData(int(size)))
|
|
d._place.choose(position.replace("-", " "))
|
|
d._turned.setCurrentIndex(d._turned.findData(paper))
|
|
d._fill.setChecked(fill == "fill")
|
|
if rest and rest[0] == "gray":
|
|
d._colour.setCurrentIndex(1)
|
|
QTimer.singleShot(900, keep_and_print)
|
|
def keep_and_print():
|
|
import shutil
|
|
d = preview()
|
|
out = Path("/tmp/rig-out"); out.mkdir(exist_ok=True)
|
|
shutil.copy(d._pdf, out / "preview.pdf")
|
|
stamp(f"PICTURE picture_box={d._picture_box.isVisible()} size_enabled={d._size.isEnabled()} "
|
|
f"image={d._picture.width()}x{d._picture.height()} labels={d._colour_label.text()!r},{d._colour.itemText(0)!r}")
|
|
press_print()
|
|
QTimer.singleShot(2500, lambda: (QTimer.singleShot(0, lambda: window.print_view(view)),
|
|
when(ready_picture, choose, "picture preview")))
|
|
elif scenario == "dark-page":
|
|
# A lesson page with a black background. Chrome does not print page
|
|
# backgrounds unless asked, and neither may Wonder Ink.
|
|
view = window.open_tab()
|
|
view.setHtml("<html><head><title>Night sky</title></head><body style='background:#000;color:#fff'>"
|
|
+ "<h1>Stars</h1>" + "<p>God made the stars. </p>" * 40 + "</body></html>",
|
|
QUrl("https://app.wonderink.org/night"))
|
|
def keep_dark():
|
|
import shutil
|
|
out = Path("/tmp/rig-out"); out.mkdir(exist_ok=True)
|
|
shutil.copy(preview()._pdf, out / "preview.pdf")
|
|
press_print()
|
|
view.loadFinished.connect(lambda ok: (QTimer.singleShot(0, lambda: window.print_view(view)),
|
|
when(ready, keep_dark, "dark preview")))
|
|
elif scenario == "repeat":
|
|
view = window.open_tab(); lesson(view); n = [0]
|
|
def once():
|
|
if n[0] == 5: return finish()
|
|
n[0] += 1; QTimer.singleShot(0, lambda: window.print_view(view))
|
|
when(ready, lambda: press_print(then=lambda: QTimer.singleShot(200, once)), "preview")
|
|
view.loadFinished.connect(lambda ok: once() if n[0] == 0 else None)
|
|
elif scenario in ("pdf", "pdf-options"):
|
|
window.open_pdf(worksheet()); view = window.current()
|
|
if scenario == "pdf-options":
|
|
actions["pdf-options"] = lambda view: when(ready, lambda: (
|
|
preview()._which.setCurrentIndex(1), preview()._range.setText("2"),
|
|
preview()._copies.setValue(3), preview()._colour.setCurrentIndex(1), press_print()), "preview")
|
|
QTimer.singleShot(2500, lambda: (QTimer.singleShot(0, lambda: window.print_view(view)),
|
|
actions[scenario if scenario == "pdf-options" else "web"](view)))
|
|
elif scenario == "pdf-viewer-button":
|
|
# The viewer's own print button reaches the page as a print request.
|
|
window.open_pdf(worksheet()); view = window.current()
|
|
QTimer.singleShot(2500, lambda: (actions["web"](view), view.page().printRequested.emit()))
|
|
elif scenario == "inline":
|
|
view = window.open_tab(); view.load(QUrl("http://127.0.0.1:8089/ark.pdf"))
|
|
QTimer.singleShot(4000, lambda: (stamp(f"inline mime={window._mime.get(view)!r} shows_pdf={window._shows_pdf(view)}"),
|
|
QTimer.singleShot(0, lambda: window.print_view(view)),
|
|
when(ready, lambda: (stamp(f"worksheet tab opened: tabs={window._tabs.count()} current_is_pdf={window.current() in window._pdf_views}"), press_print()), "inline preview")))
|
|
else:
|
|
if scenario == "page-closes-itself":
|
|
lesson(window.open_tab(), "The other lesson")
|
|
view = window.open_tab(); lesson(view)
|
|
fired = []
|
|
def go(ok):
|
|
if fired: return
|
|
fired.append(1); QTimer.singleShot(0, lambda: window.print_view(view)); actions[scenario](view)
|
|
view.loadFinished.connect(go)
|
|
|
|
QTimer.singleShot(90000, lambda: (stamp("TIMEOUT"), app.exit(3)))
|
|
rc = app.exec()
|
|
stamp(f"EXIT {rc} scratch_left={sorted(p.name for p in Path(os.environ.get('XDG_RUNTIME_DIR','/tmp')).glob('wonderink-print-*'))}")
|
|
sys.exit(rc)
|