fix pale colours and map driverless print options
ci/woodpecker/push/build Pipeline was successful

Set *DefaultBRColorMode: None in the vendor PPD so every job sends
<</UseCIEColor false>>: Brother's Normal/Vivid modes enable the printer's
own CIE conversion, which washed the colours out.

Add six APPrinterPreset entries mapping print-color-mode and
print-quality from driverless (AirPrint/IPP) clients onto the Brother
device options; they use the notation CUPS generates itself (Apple
metadata as 'key value' pairs, PPD options as '*Name Value'), because
key=value strings are silently discarded. print-quality is 3/4/5.

Add the PRINTER_OPTIONS env var: whitespace-separated key=value pairs
applied as queue defaults on every start via lpadmin -o, validated
against the queue's PPD, with warnings instead of a failed startup.

Document all of it in the README. The VividNoCIE candidate was dropped
after test prints showed no difference from None. Archive the OpenSpec
change and sync the printserver/cups spec with the two new
requirements.
This commit is contained in:
juergen committed 2026-10-20 08:28:08 +02:00
1 parent 75b68fe438
commit 30670de93b
9 files changed
+319 -33

No files matched your search

+99 -1
View File
@@ -85,11 +85,26 @@ podman run -d --name printserver \
| `PRINTER_NAME` | `HL4150CDN` | CUPS queue name. Also used as the name advertised via AirPrint/DNS-SD. | | `PRINTER_NAME` | `HL4150CDN` | CUPS queue name. Also used as the name advertised via AirPrint/DNS-SD. |
| `PRINTER_DESCRIPTION` | *(empty)* | CUPS printer description (`lpadmin -D`), shown in the CUPS web UI. | | `PRINTER_DESCRIPTION` | *(empty)* | CUPS printer description (`lpadmin -D`), shown in the CUPS web UI. |
| `PPD_FILE` | `/usr/share/cups/model/BR4150_2.PPD` | PPD used for the queue. | | `PPD_FILE` | `/usr/share/cups/model/BR4150_2.PPD` | PPD used for the queue. |
| `PRINTER_OPTIONS` | *(empty)* | Space-separated PPD option assignments (`key=value`) applied as queue defaults on every start, e.g. `BRColorMode=Vivid TonerSaveMode=False`. Discover the names with `lpoptions -p <queue> -l`. |
| `CERT_EXTRA_NAMES` | *(empty)* | Comma-separated extra DNS names and/or IP addresses to put into the generated `_ipps` certificate. | | `CERT_EXTRA_NAMES` | *(empty)* | Comma-separated extra DNS names and/or IP addresses to put into the generated `_ipps` certificate. |
| `CERT_DAYS` | `3650` | Validity of the generated certificate, in days. | | `CERT_DAYS` | `3650` | Validity of the generated certificate, in days. |
No queue is created when neither `PRINTER_IP` nor `PRINTER_URI` is set. No queue is created when neither `PRINTER_IP` nor `PRINTER_URI` is set.
`PRINTER_OPTIONS` sets queue defaults that survive the recreation of the queue,
because they are re-applied on every start:
```sh
podman run -d --name printserver \
--network host \
-e PRINTER_IP=192.168.100.7 \
-e PRINTER_OPTIONS="BRMediaType=Plain BRColorMode=Vivid TonerSaveMode=False" \
printserver:latest
```
Entries that the PPD does not know are logged as a warning and ignored; the
remaining ones are applied and the container starts normally.
### Choosing the connection ### Choosing the connection
The HL-4150CDN accepts PostScript directly, so the default is raw AppSocket. The HL-4150CDN accepts PostScript directly, so the default is raw AppSocket.
@@ -187,7 +202,7 @@ The BR-Script3 PPD exposes the HL-4150CDN's hardware options. Common ones:
| Paper source | `InputSlot` | `AutoSelect`, `Tray1`, `Tray2`, `MPTray` | | Paper source | `InputSlot` | `AutoSelect`, `Tray1`, `Tray2`, `MPTray` |
| Two-sided printing | `Duplex` | `None`, `DuplexNoTumble` (long edge), `DuplexTumble` (short edge) | | Two-sided printing | `Duplex` | `None`, `DuplexNoTumble` (long edge), `DuplexTumble` (short edge) |
| Colour / mono | `BRPrintQuality` | `Auto`, `Color`, `Black` | | Colour / mono | `BRPrintQuality` | `Auto`, `Color`, `Black` |
| Colour mode | `BRColorMode` | `Normal`, `Vivid`, `None` | | Colour mode | `BRColorMode` | `Normal`, `Vivid`, `None` (default) |
| Media type | `BRMediaType` | `Plain`, `Thin`, `Thick`, `Recycled`, `Glossy`, `Label`, envelopes, … | | Media type | `BRMediaType` | `Plain`, `Thin`, `Thick`, `Recycled`, `Glossy`, `Label`, envelopes, … |
| Print quality | `CAPT` | `Fine` (600×600), `SuperFine` (2400 dpi class) | | Print quality | `CAPT` | `Fine` (600×600), `SuperFine` (2400 dpi class) |
| Toner save | `TonerSaveMode` | `True`, `False` | | Toner save | `TonerSaveMode` | `True`, `False` |
@@ -200,6 +215,57 @@ podman exec printserver lp -d HL4150CDN \
-o Duplex=DuplexNoTumble -o BRPrintQuality=Color -o InputSlot=Tray1 report.pdf -o Duplex=DuplexNoTumble -o BRPrintQuality=Color -o InputSlot=Tray1 report.pdf
``` ```
### Why the macOS/iOS dialog shows fewer options
macOS and iOS print driverless (AirPrint / IPP Everywhere), so the dialog shows
only IPP-standard options: media size, duplex, pages per sheet, the
black-and-white/colour toggle and the quality slider. Brother-specific options
(`BRColorMode`, `BRMediaType`, `TonerSaveMode`, `CAPT`, …) have no IPP
equivalent and can never appear there. Set them server-side instead:
- as queue defaults at container start via `PRINTER_OPTIONS` (see
*Environment variables*),
- per job with `lp -o …`, or
- interactively in the CUPS web UI (`http://<host>:631/admin`, *Printing Options*).
The dialog's black-and-white/colour toggle maps to `print-color-mode` and the
quality slider to `print-quality`; the PPD presets documented below translate
both onto the Brother options, so they now really take effect:
| Dialog / IPP | PPD option it becomes |
| --- | --- |
| `print-color-mode=monochrome` | `BRPrintQuality=Black` |
| `print-color-mode=color` | `BRPrintQuality=Auto` |
| `print-quality=3` (draft) | `TonerSaveMode=True` |
| `print-quality=4` (normal) | defaults (`Fine`, toner save off) |
| `print-quality=5` (high) | `CAPT=SuperFine` (2400 dpi class) |
Client-specified values always win over the preset.
### Changes to the vendor PPD
`assets/Brother HL-4150CDN/ppd/BR4150_2.PPD` is Brother's original file plus
two additions, each marked with a `%` comment in the file:
- **`*DefaultBRColorMode: None` instead of `Normal`.** `Normal` and `Vivid` both
set `<</UseCIEColor true>>`; the printer's own CIE conversion washes the
colours out (see [Colours are printed pale / washed out](#colours-are-printed-pale--washed-out)).
- **Six `APPrinterPreset` entries** mapping `print-color-mode`
(color/monochrome) and `print-quality` (draft/normal/high) onto
`BRPrintQuality`, `CAPT` and `TonerSaveMode`. CUPS applies such presets for
driverless clients and always lets the client's own option values win. In
particular `print-color-mode=monochrome` becomes `BRPrintQuality=Black`, so
black-and-white printing works: the server cannot do the greyscale conversion
itself, because `pdftops` uses Poppler for Brother printers and Poppler
cannot produce greyscale (`WARNING: Grayscale/monochrome could be printed as
color with Poppler` in the CUPS log). The presets use the notation CUPS
generates itself — Apple metadata as `key value` pairs *without* `=`, PPD
options as `*Name Value` pairs on separate lines inside a multiline
`"…"` string. With `key=value` on a single line CUPS silently ignores the
whole preset (verified against libcups 2.4.2: `_ppdParseOptions()` splits on
whitespace, so `key=value` becomes the option *name* and the lookup fails).
Note that IPP `print-quality` is `3` (draft), `4` (normal), `5` (high).
## Client support ## Client support
| Platform | Path | Notes | | Platform | Path | Notes |
@@ -384,6 +450,38 @@ still being rejected — fix that, or add the queue from the command line as
above. macOS caches the failed attempt, so remove and re-add the printer after above. macOS caches the failed attempt, so remove and re-add the printer after
changing anything on the server. changing anything on the server.
### Colours are printed pale / washed out
Symptom: colour printouts look desaturated compared to the same PDF printed
directly from the Mac, or with Brother's own driver.
Cause: the PPD colour mode `Normal` (Brother's default) makes the printer's
PostScript interpreter apply its own CIE conversion (`<</UseCIEColor true>>`),
which washes colours out. `Vivid` sets `UseCIEColor` as well, so it does not
help either; `BRPrintQuality` (`BRColorAdapt`) and `TonerSaveMode` made no
visible difference in a side-by-side test. The container therefore ships the
PPD with `*DefaultBRColorMode: None` (`UseCIEColor` off), which produces
saturated output.
Check and, if needed, override the current default:
```sh
podman exec printserver lpoptions -p <queue> -l | grep BRColorMode # expect *None
podman exec printserver lp -d <queue> -o BRColorMode=None test.pdf
```
If output is still pale with `BRColorMode=None`, check the printer's own menu
(Toner → Toner Save off) and make sure `BRMediaType` matches the media in the
tray.
Black-and-white is a related but separate path: `print-color-mode=monochrome`
from a driverless client is mapped by the PPD presets onto
`BRPrintQuality=Black` (the printer renders mono). It is deliberately *not* done
by the server — with its Poppler renderer, `pdftops` cannot convert to
greyscale. Forcing Ghostscript with `-o pdftops-renderer=gs` would, but that
path is not recommended for Brother printers (bugs in their PostScript
interpreters).
## CUPS 3 and the future of PPDs ## CUPS 3 and the future of PPDs
Adding the queue with a PPD makes `lpadmin` print: Adding the queue with a PPD makes `lpadmin` print:
+46 -1
View File
@@ -374,9 +374,12 @@ restore
*CloseUI: *BRPrintQuality *CloseUI: *BRPrintQuality
*% Color Mode Information =============== *% Color Mode Information ===============
*% Modified by this repo (see README, "Changes to the vendor PPD"):
*% - default switched from Normal to None: with UseCIEColor off the printer
*% skips its CIE conversion, which otherwise washes out the colours.
*OpenUI *BRColorMode/Colour Mode: PickOne *OpenUI *BRColorMode/Colour Mode: PickOne
*OrderDependency: 131.0 AnySetup *BRColorMode *OrderDependency: 131.0 AnySetup *BRColorMode
*DefaultBRColorMode: Normal *DefaultBRColorMode: None
*BRColorMode Normal/Normal: " *BRColorMode Normal/Normal: "
<</UseCIEColor true>>setpagedevice <</UseCIEColor true>>setpagedevice
<</BRColorMode 0>>setpagedevice <</BRColorMode 0>>setpagedevice
@@ -393,6 +396,48 @@ restore
*End *End
*CloseUI: *BRColorMode *CloseUI: *BRColorMode
*% Added by this repo: presets mapping the IPP attributes of driverless
*% (AirPrint/IPP Everywhere) clients onto the device options below. CUPS applies
*% them automatically when a job carries print-color-mode / print-quality;
*% options the client states explicitly always take precedence. Format follows
*% the presets CUPS generates itself: Apple metadata as "key value" pairs
*% (no '='), PPD options as "*Name Value" pairs.
*APPrinterPreset ColorDraft/Colour, Draft: "
com.apple.print.preset.output-mode color
com.apple.print.preset.quality low
*TonerSaveMode True
"
*APPrinterPreset ColorNormal/Colour, Normal: "
com.apple.print.preset.output-mode color
com.apple.print.preset.quality normal
*TonerSaveMode False
"
*APPrinterPreset ColorHigh/Colour, High: "
com.apple.print.preset.output-mode color
com.apple.print.preset.quality high
*CAPT SuperFine
*TonerSaveMode False
"
*APPrinterPreset MonoDraft/B&W, Draft: "
com.apple.print.preset.output-mode monochrome
com.apple.print.preset.quality low
*BRPrintQuality Black
*TonerSaveMode True
"
*APPrinterPreset MonoNormal/B&W, Normal: "
com.apple.print.preset.output-mode monochrome
com.apple.print.preset.quality normal
*BRPrintQuality Black
*TonerSaveMode False
"
*APPrinterPreset MonoHigh/B&W, High: "
com.apple.print.preset.output-mode monochrome
com.apple.print.preset.quality high
*BRPrintQuality Black
*CAPT SuperFine
*TonerSaveMode False
"
*% Improve Gray Color Information =============== *% Improve Gray Color Information ===============
*OpenUI *BRImprovedGray/Improve Grey Colour: PickOne *OpenUI *BRImprovedGray/Improve Grey Colour: PickOne
*OrderDependency: 132.0 AnySetup *BRImprovedGray *OrderDependency: 132.0 AnySetup *BRImprovedGray
+48
View File
@@ -18,6 +18,9 @@
# PRINTER_NAME CUPS queue name and advertised (DNS-SD) name. # PRINTER_NAME CUPS queue name and advertised (DNS-SD) name.
# PRINTER_DESCRIPTION CUPS Description (lpadmin -D) shown in the web UI. # PRINTER_DESCRIPTION CUPS Description (lpadmin -D) shown in the web UI.
# PPD_FILE PPD used for the queue. # PPD_FILE PPD used for the queue.
# PRINTER_OPTIONS Space-separated PPD option assignments (key=value)
# applied as queue defaults on every start, e.g.
# "BRColorMode=Vivid TonerSaveMode=False".
# CERT_EXTRA_NAMES Comma-separated extra DNS names / IP addresses to put # CERT_EXTRA_NAMES Comma-separated extra DNS names / IP addresses to put
# into the generated IPPS certificate (server SANs). # into the generated IPPS certificate (server SANs).
# CERT_DAYS Validity of the generated certificate (default 3650). # CERT_DAYS Validity of the generated certificate (default 3650).
@@ -29,6 +32,7 @@ PRINTER_URI="${PRINTER_URI:-}"
PRINTER_PROTOCOL="${PRINTER_PROTOCOL:-socket}" PRINTER_PROTOCOL="${PRINTER_PROTOCOL:-socket}"
PRINTER_DESCRIPTION="${PRINTER_DESCRIPTION:-}" PRINTER_DESCRIPTION="${PRINTER_DESCRIPTION:-}"
PPD_FILE="${PPD_FILE:-/usr/share/cups/model/BR4150_2.PPD}" PPD_FILE="${PPD_FILE:-/usr/share/cups/model/BR4150_2.PPD}"
PRINTER_OPTIONS="${PRINTER_OPTIONS:-}"
LOG_PREFIX="[entrypoint]" LOG_PREFIX="[entrypoint]"
log() { echo "${LOG_PREFIX} $*"; } log() { echo "${LOG_PREFIX} $*"; }
@@ -331,6 +335,50 @@ if lpstat -p "${PRINTER_NAME}" >/dev/null 2>&1; then
log "queue ${PRINTER_NAME} is shared and set as default" log "queue ${PRINTER_NAME} is shared and set as default"
fi fi
# Apply optional queue defaults from PRINTER_OPTIONS (see README). Runs on every
# start so the settings survive the (re)creation of the queue; an unusable entry
# only logs a warning instead of aborting the startup ("set -eu"). Names and
# values are checked against the queue's options first, because lpadmin would
# happily store unknown assignments as queue defaults.
apply_printer_options() {
[ -n "${PRINTER_OPTIONS}" ] || return 0
if ! lpstat -p "${PRINTER_NAME}" >/dev/null 2>&1; then
log "warning: queue ${PRINTER_NAME} does not exist; skipping PRINTER_OPTIONS"
return 0
fi
ppd_options="$(lpoptions -p "${PRINTER_NAME}" -l 2>/dev/null || true)"
for opt in ${PRINTER_OPTIONS}; do
case "${opt}" in
?*=*) ;;
*)
log "warning: ignoring malformed PRINTER_OPTIONS entry '${opt}' (expected key=value)"
continue
;;
esac
name="${opt%%=*}"
value="${opt#*=}"
line="$(printf '%s\n' "${ppd_options}" | grep "^${name}/")" || {
log "warning: PPD has no option '${name}'; ignoring (names/values: 'lpoptions -p ${PRINTER_NAME} -l')"
continue
}
# Option lines look like "Name/Text: value1 value2 ..." ('*' = default).
values="$(printf '%s\n' "${line#*: }" | tr ' ' '\n' | sed 's/^\*//')"
if ! printf '%s\n' "${values}" | grep -qxF "${value}" \
&& ! { case "${value}" in Custom.*)
printf '%s\n' "${values}" | grep -q 'Custom\.'
;; *) false ;; esac; }; then
log "warning: PPD option '${name}' has no value '${value}'; ignoring"
continue
fi
if lpadmin -p "${PRINTER_NAME}" -o "${opt}" >/dev/null 2>&1; then
log "queue default ${opt} applied"
else
log "warning: lpadmin -o ${opt} failed; ignoring"
fi
done
}
apply_printer_options
# --- Verify CUPS is reachable on the network -------------------------------- # --- Verify CUPS is reachable on the network --------------------------------
# cupsd also runs with nothing but its domain socket, e.g. when it cannot bind # cupsd also runs with nothing but its domain socket, e.g. when it cannot bind
# the privileged port 631 (rootless Podman with host networking). It then still # the privileged port 631 (rootless Podman with host networking). It then still
@@ -85,6 +85,30 @@ geschickt hat – clientseitige Auswahl gewinnt also weiterhin
(`ppd-mark.c`: „as long as the corresponding names are not already defined (`ppd-mark.c`: „as long as the corresponding names are not already defined
in the IPP request"). in the IPP request").
**Preset-Notation (2026-10-20 empirisch geklärt).** Die Preset-Werte müssen so
geschrieben werden, wie CUPS sie selbst erzeugt (`ppd-cache.c`, Funktion
`cupsFilePrintf fp "*APPrinterPreset %s: \""`): eine mehrzeilige Zeichenkette,
in der Apple-Metadaten als `key value`-**Paare ohne `=`** und PPD-Optionen als
`*Name Value`-Paare stehen, jeweils durch Leerzeichen getrennt:
```
*APPrinterPreset MonoNormal/B&W, Normal: "
com.apple.print.preset.output-mode monochrome
com.apple.print.preset.quality normal
*BRPrintQuality Black
*TonerSaveMode False
"
```
`_ppdCacheCreateWithPPD()` parst den Wert mit `_ppdParseOptions()`, das an
Whitespace trennt – `key=value` in einer Zeile würde den *Namen*
`key=value` ergeben und der Lookup (`cupsGetOption("com.apple.print.preset.
quality", …)`) schlüge fehl. CUPS verwirft das Preset dann **stillschweigend**
(`num_presets[…]=0`), was im Test zunächst genau passierte. Verifiziert mit
einem libcups-2.4.2-Harness (`_ppd_cache_t` inspiziert) und im
`pstops`-Filterpfad. Zusätzlich: IPP-`print-quality` ist **3/4/5**
(Draft/Normal/High), nicht 2/5.
**2. PPD direkt im Repository ändern (nicht zur Build-Zeit patchen).** **2. PPD direkt im Repository ändern (nicht zur Build-Zeit patchen).**
Die Asset-Datei `assets/Brother HL-4150CDN/ppd/BR4150_2.PPD` wird bereits Die Asset-Datei `assets/Brother HL-4150CDN/ppd/BR4150_2.PPD` wird bereits
@@ -112,13 +136,12 @@ damit gilt es auch für eine persistente Queue. Fehler pro Token abfangen
(`|| log warning`), da `set -eu` sonst den Start abbricht; unbekannte Namen (`|| log warning`), da `set -eu` sonst den Start abbricht; unbekannte Namen
werden verworfen, ohne die übrigen zu blockieren. werden verworfen, ohne die übrigen zu blockieren.
**5. Neue `BRColorMode`-Choice `VividNoCIE` nur unter Vorbehalt.** **5. Neue `BRColorMode`-Choice `VividNoCIE` verworfen.**
`<</UseCIEColor false /BRColorMode 1>>` – Kombi, die es bisher nicht gibt `<</UseCIEColor false /BRColorMode 1>>` – Kombi, die es bisher nicht gibt
(`None` lässt `/BRColorMode` unangetastet, `Vivid` erzwingt CIE). Wird als (`None` lässt `/BRColorMode` unangetastet, `Vivid` erzwingt CIE). Als
Testdruck gegen `None` verglichen; nur übernommen, wenn der Ausdruck Testdruck gegen `None` verglichen (2026-10-20): **nicht unterscheidbar**,
deutlich besser ist. Spezifikation hängt nicht davon ab (Default bleibt daher nicht übernommen. `None` bleibt der einzige geänderte Default.
`None`, Überschreibung ist ohnehin möglich).
## Risks / Trade-offs ## Risks / Trade-offs
@@ -151,8 +174,6 @@ deutlich besser ist. Spezifikation hängt nicht davon ab (Default bleibt
## Open Questions ## Open Questions
- Ob `VividNoCIE` mehr Sättigung liefert als `None` – wird im Tasks-Test
entschieden; ändert weder Spec (Default bleibt `None`) noch Aufbau.
- Ob macOS/iOS den Qualitätsregler als `print-quality` sendet – falls nicht, - Ob macOS/iOS den Qualitätsregler als `print-quality` sendet – falls nicht,
bleiben die `print-quality`-Presets inert, `print-color-mode`-Presets bleiben die `print-quality`-Presets inert, `print-color-mode`-Presets
wirken unabhängig davon (Server-seitig per `lp -o print-quality=…` wirken unabhängig davon (Server-seitig per `lp -o print-quality=…`
@@ -41,16 +41,16 @@ Drucker-Queue):
Start als Queue-Defaults gesetzt werden – damit überleben Start als Queue-Defaults gesetzt werden – damit überleben
PPD-Optionen (z. B. `BRMediaType`, `TonerSaveMode`, `BRColorMode=Vivid`) PPD-Optionen (z. B. `BRMediaType`, `TonerSaveMode`, `BRColorMode=Vivid`)
die Neuerzeugung der Queue. die Neuerzeugung der Queue.
- **Optionale neue PPD-Choice `BRColorMode=VividNoCIE`**
(`<</UseCIEColor false /BRColorMode 1>>`) als Testkandidat für noch
gesättigtere Ausgabe als `None`; nur behalten, wenn der Testdruck
besser ist als `None`.
- **README**: neuer Abschnitt, warum der AirPrint-Dialog nur - **README**: neuer Abschnitt, warum der AirPrint-Dialog nur
IPP-Standardoptionen zeigt und wie PPD-Optionen gesetzt werden (Env-Var, IPP-Standardoptionen zeigt und wie PPD-Optionen gesetzt werden (Env-Var,
CUPS-Web-UI, `lp`); Troubleshooting „Farben sind blass" mit der CUPS-Web-UI, `lp`); Troubleshooting „Farben sind blass" mit der
durchgeführten Testreihe; Dokumentation der funktionierenden durchgeführten Testreihe; Dokumentation der funktionierenden
S/W-Abbildung. S/W-Abbildung.
Verworfen: eine zusätzliche Choice `BRColorMode=VividNoCIE`
(`UseCIEColor false` + `/BRColorMode 1`) – die Testdrucke waren von `None`
nicht unterscheidbar, siehe `design.md`.
Kein Bestandteil: Änderungen am Containerfile (die PPD wird bereits Kein Bestandteil: Änderungen am Containerfile (die PPD wird bereits
installiert), am Verbindungs-/Zertifikatsverhalten oder eine installiert), am Verbindungs-/Zertifikatsverhalten oder eine
Server-seitige Erzwingung von Graustufen über `pdftops-renderer=gs` Server-seitige Erzwingung von Graustufen über `pdftops-renderer=gs`
@@ -2,29 +2,35 @@
## 1. PPD: Farbmodus-Default, Presets und Dokumentation ## 1. PPD: Farbmodus-Default, Presets und Dokumentation
- [ ] 1.1 `assets/Brother HL-4150CDN/ppd/BR4150_2.PPD`: `*DefaultBRColorMode` - [x] 1.1 `assets/Brother HL-4150CDN/ppd/BR4150_2.PPD`: `*DefaultBRColorMode`
von `Normal` auf `None` ändern, alle eigenen Ergänzungen mit von `Normal` auf `None` ändern, alle eigenen Ergänzungen mit
`%`-Kommentar (Herkunft/Zweck) markieren. `%`-Kommentar (Herkunft/Zweck) markieren.
Verifikation: `grep -n 'DefaultBRColorMode\|^% Modified' "assets/Brother HL-4150CDN/ppd/BR4150_2.PPD"` Verifikation: `grep -n 'DefaultBRColorMode\|^% Modified' "assets/Brother HL-4150CDN/ppd/BR4150_2.PPD"`
zeigt `*DefaultBRColorMode: None` und den Kommentar. zeigt `*DefaultBRColorMode: None` und den Kommentar.
- [ ] 1.2 PPD: sechs `APPrinterPreset`-Einträge ergänzen (Kombinationen - [x] 1.2 PPD: sechs `APPrinterPreset`-Einträge ergänzen (Kombinationen
color/monochrome × low/normal/high gemäß Tabelle in `design.md`, color/monochrome × low/normal/high gemäß Tabelle in `design.md`).
z. B. `*APPrinterPreset MonoNormal/Schwarz-Weiß Normal:
"*com.apple.print.preset.output-mode=monochrome
*com.apple.print.preset.quality=normal *BRPrintQuality=Black
*TonerSaveMode=False"`).
Verifikation: `grep -c '^\*APPrinterPreset' "assets/Brother HL-4150CDN/ppd/BR4150_2.PPD"` Verifikation: `grep -c '^\*APPrinterPreset' "assets/Brother HL-4150CDN/ppd/BR4150_2.PPD"`
ergibt 6, und nach einem Build listet ergibt 6, und nach einem Build listet
`lpoptions -p <queue> -l` weiterhin alle Optionen ohne `lpoptions -p <queue> -l` weiterhin alle Optionen ohne
PPD-Warnung in `/var/log/cups/error_log`. PPD-Warnung in `/var/log/cups/error_log`.
- [ ] 1.3 PPD: Choice `VividNoCIE` unter `BRColorMode` ergänzen Ergebnis (2026-10-20): Zunächst als einzeilige `key=value`-Strings
geschrieben – CUPS verwarf alle sechs stillschweigend
(`_ppdParseOptions()` trennt an Whitespace, `key=value` wurde zum
*Namen*). Mit libcups-2.4.2-Harness (`_ppd_cache_t`-Inspektion) belegt
und auf das von CUPS selbst erzeugte Format umgestellt (mehrzeilig,
Apple-Metadaten als `key value`, PPD-Optionen als `*Name Value`).
Danach greifen alle sechs im `pstops`-Pfad (siehe 3.x).
- [x] 1.3 PPD: Choice `VividNoCIE` unter `BRColorMode` ergänzen
(`<</UseCIEColor false /BRColorMode 1>>setpagedevice`) und per (`<</UseCIEColor false /BRColorMode 1>>setpagedevice`) und per
Testdruck gegen `None` vergleichen; nur behalten, wenn der Ausdruck Testdruck gegen `None` vergleichen; nur behalten, wenn der Ausdruck
deutlich gesättigter ist. deutlich gesättigter ist.
Verifikation: `lpoptions -p <queue> -l` zeigt die Choice, die Verifikation: `lpoptions -p <queue> -l` zeigt die Choice, die
Entscheidung (behalten/entfernen) ist im README-Auswertungs-Abschnitt Entscheidung (behalten/entfernen) ist im README-Auswertungs-Abschnitt
notiert. notiert.
- [ ] 1.4 README: Abschnitt „Farben sind blass" (Ursache `UseCIEColor`, Ergebnis (2026-10-20): Testdrucke `BRColorMode=VividNoCIE` (Job 5) und
`BRColorMode=None` (Job 6) sind **nicht unterscheidbar** → Choice
wieder entfernt (README/Tabelle bereinigt).
- [x] 1.4 README: Abschnitt „Farben sind blass" (Ursache `UseCIEColor`,
durchgeführte Testreihe mit `lp -o BRColorMode=…`, durchgeführte Testreihe mit `lp -o BRColorMode=…`,
Ergebnis `None`), Abschnitt „Änderungen an der Hersteller-PPD" Ergebnis `None`), Abschnitt „Änderungen an der Hersteller-PPD"
(welche Einträge ergänzt wurden und warum) und Abschnitt (welche Einträge ergänzt wurden und warum) und Abschnitt
@@ -35,7 +41,7 @@
## 2. entrypoint: `PRINTER_OPTIONS` ## 2. entrypoint: `PRINTER_OPTIONS`
- [ ] 2.1 `entrypoint.sh`: `PRINTER_OPTIONS` (Standard leer) nach der - [x] 2.1 `entrypoint.sh`: `PRINTER_OPTIONS` (Standard leer) nach der
Queue-Erzeugung in Tokens `key=value` zerlegen und jedes per Queue-Erzeugung in Tokens `key=value` zerlegen und jedes per
`lpadmin -o` setzen, an derselben Stelle wie `lpadmin -o` setzen, an derselben Stelle wie
`printer-is-shared`/`-D`/`-d`, bei jedem Start. `printer-is-shared`/`-D`/`-d`, bei jedem Start.
@@ -43,12 +49,12 @@
`PRINTER_OPTIONS="BRColorMode=Vivid TonerSaveMode=False"` und `PRINTER_OPTIONS="BRColorMode=Vivid TonerSaveMode=False"` und
`lpoptions -p <queue> -l` zeigt `Vivid` und `True` als Default `lpoptions -p <queue> -l` zeigt `Vivid` und `True` als Default
(`*Vivid`, `*True`). (`*Vivid`, `*True`).
- [ ] 2.2 Fehler pro Token abfangen, damit `set -eu` den Start nicht - [x] 2.2 Fehler pro Token abfangen, damit `set -eu` den Start nicht
abbricht, und warnen. abbricht, und warnen.
Verifikation: Start mit `PRINTER_OPTIONS="NoSuchOption=Foo"` erzeugt Verifikation: Start mit `PRINTER_OPTIONS="NoSuchOption=Foo"` erzeugt
eine Warning-Zeile im Log, der Container erreicht eine Warning-Zeile im Log, der Container erreicht
„ready: CUPS on 631/tcp" und `lpstat -r` meldet „scheduler is running". „ready: CUPS on 631/tcp" und `lpstat -r` meldet „scheduler is running".
- [ ] 2.3 README: neue Zeile in der Env-Var-Tabelle plus Kurzbeispiel unter - [x] 2.3 README: neue Zeile in der Env-Var-Tabelle plus Kurzbeispiel unter
„Environment variables" (inkl. Hinweis `lpoptions -p <queue> -l` zur „Environment variables" (inkl. Hinweis `lpoptions -p <queue> -l` zur
Entdeckbarkeit der Namen). Entdeckbarkeit der Namen).
Verifikation: das Beispiel aus der Tabelle in einen `podman run`-Befehl Verifikation: das Beispiel aus der Tabelle in einen `podman run`-Befehl
@@ -56,21 +62,38 @@
## 3. Integrationstests am laufenden System ## 3. Integrationstests am laufenden System
- [ ] 3.1 Image neu bauen und Container starten. - [x] 3.1 Image neu bauen und Container starten.
Verifikation: `podman build -t printserver:latest .` endet ohne Fehler, Verifikation: `podman build -t printserver:latest .` endet ohne Fehler,
das Log enthält „ready: CUPS on 631/tcp, mDNS on 5353/udp". das Log enthält „ready: CUPS on 631/tcp, mDNS on 5353/udp".
- [ ] 3.2 S/W-Druck aus dem macOS-Dialog (derselbe Farbjob wie zuvor). Ergebnis (2026-10-20, podman 6.1.4/macOS-VM): Build und Start ok; der
Container bindet 631 nur nach
`podman machine ssh -- sudo sysctl -w net.ipv4.ip_unprivileged_port_start=631`
(rootless VM) – danach „ready".
- [x] 3.2 S/W-Druck aus dem macOS-Dialog (derselbe Farbjob wie zuvor).
Verifikation: Ausdruck ist schwarz/weiß – vorher kam er farbig heraus. Verifikation: Ausdruck ist schwarz/weiß – vorher kam er farbig heraus.
- [ ] 3.3 Farbdruck ohne weitere Optionen aus dem Dialog und per Server-seitig verifiziert (2026-10-20): `print-color-mode=monochrome`
→ `<</BRProcessColor 1>>`. Sichtprüfung durch Nutzer (2026-10-20):
Testseite 2 ist schwarz/weiß. **Bestanden.**
- [x] 3.3 Farbdruck ohne weitere Optionen aus dem Dialog und per
`lp -d <queue> test.pdf`. `lp -d <queue> test.pdf`.
Verifikation: Ausdruck ist so gesättigt wie der bisherige Verifikation: Ausdruck ist so gesättigt wie der bisherige
`BRColorMode=None`-Testdruck; `lpoptions -p <queue> -l` zeigt `*None` `BRColorMode=None`-Testdruck; `lpoptions -p <queue> -l` zeigt `*None`
als Farbmodus-Default. als Farbmodus-Default.
- [ ] 3.4 Qualitätsstufen prüfen. Verifiziert (2026-10-20): `lpoptions` zeigt `*None`, `pstops` emittiert
Verifikation: `lp -o print-quality=2 test.pdf` (draft, sichtbar `<</UseCIEColor false>>` auch ohne Job-Optionen. Sichtprüfung durch
tonersparender) und `lp -o print-quality=5 test.pdf` (high, `CAPT` Nutzer: Testseite 1 ist kräftig/gesättigt (nicht mehr blass).
SuperFine) erzeugen unterscheidbare Ausdrucke gegenüber **Bestanden.**
`print-quality=3`. - [x] 3.4 Qualitätsstufen prüfen.
Verifikation: `lp -o print-quality=3 test.pdf` (IPP draft, sichtbar
tonersparender: `true tonersave`) und `lp -o print-quality=5 test.pdf`
(IPP high, `CAPT SuperFine`, `<</BRApt 2 /HWResolution [600 600]>>`)
erzeugen unterscheidbare Ausdrucke gegenüber `print-quality=4`
(IPP normal). Hinweis: IPP-`print-quality` ist 3/4/5 – nicht 2/5.
Verifiziert (2026-10-20): serverseitig im `pstops`-Output (3 →
`true tonersave`, 5 → `BRApt 2`); Sichtprüfung durch Nutzer: Seite 3
(draft) blass/tonersparend, klar unterscheidbar von 1/4. Seite 1 = 4
erwartungsgemäß (SuperFine vs. Fine bei diesem Testmotiv optisch
gleich, serverseitig aber gesetzt). **Bestanden.**
## Workflow follow-up ## Workflow follow-up
+51
View File
@@ -54,6 +54,57 @@ The system SHALL translate the PPD's device options into PostScript
- **WHEN** a job selects colour or mono - **WHEN** a job selects colour or mono
- **THEN** the emitted PostScript sets the corresponding colour/mono device parameters - **THEN** the emitted PostScript sets the corresponding colour/mono device parameters
#### Scenario: A driverless client selects black and white
- **WHEN** an AirPrint/IPP client sends `print-color-mode=monochrome`
- **THEN** the emitted PostScript sets the mono device parameters (`/BRProcessColor 1`) and the job prints in black and white, independent of the PDF renderer used by the server
#### Scenario: A driverless client selects colour
- **WHEN** an AirPrint/IPP client sends `print-color-mode=color`
- **THEN** the job prints in colour and the emitted PostScript disables toner saving
#### Scenario: A driverless client selects the print quality
- **WHEN** an AirPrint/IPP client sends `print-quality=draft`, `normal` or `high`
- **THEN** the emitted PostScript selects the mapped device settings: toner saving for draft, full toner for normal, and the `CAPT` high-resolution setting for high
### Requirement: Queue defaults are configurable at runtime
The container SHALL accept a `PRINTER_OPTIONS` environment variable holding
space-separated PPD option assignments (`key=value`) and apply each assignment
as a queue default on every start, so PPD-specific device options survive the
recreation of the queue.
#### Scenario: Options are applied on start
- **WHEN** the container is started with `PRINTER_OPTIONS="BRColorMode=Vivid TonerSaveMode=False"`
- **THEN** after startup the queue defaults include `BRColorMode=Vivid` and `TonerSaveMode=False`
#### Scenario: Defaults apply to driverless clients
- **WHEN** a macOS or iOS client prints a job that does not specify that PPD option
- **THEN** the job is printed with the configured queue default
#### Scenario: An unusable option must not stop startup
- **WHEN** `PRINTER_OPTIONS` contains an option name or value the PPD does not provide
- **THEN** the entrypoint logs a warning, applies the remaining assignments and continues starting
#### Scenario: Nothing configured
- **WHEN** `PRINTER_OPTIONS` is unset or empty
- **THEN** the queue keeps the PPD defaults and the container starts as before
### Requirement: Colour output is saturated by default
Unless overridden through `PRINTER_OPTIONS`, the queue SHALL default to the
colour mode `None`, which leaves the printer's CIE colour conversion
(`UseCIEColor`) switched off so that printed colours are not washed out.
#### Scenario: Default colour mode
- **WHEN** a colour job is printed without any colour-mode options
- **THEN** the emitted PostScript contains `<</UseCIEColor false>>` rather than `UseCIEColor true`
#### Scenario: Default is overridable
- **WHEN** the container is started with `PRINTER_OPTIONS` containing `BRColorMode=Vivid`
- **THEN** the queue default is `Vivid` instead of `None`
#### Scenario: A job's own selection wins
- **WHEN** a job explicitly selects another `BRColorMode` value
- **THEN** that value is used for the job instead of the queue default
### Requirement: Jobs are printed via direct PostScript ### Requirement: Jobs are printed via direct PostScript
The container SHALL forward the converted job as PostScript to the printer The container SHALL forward the converted job as PostScript to the printer
without a server-side vendor filter or host-side rasterisation. without a server-side vendor filter or host-side rasterisation.