[Bug/Critical] Epson printer quality options limited to default only — only "Normal" shown, 6 other options missing #180

Closed
opened 2026-09-15 10:03:25 +01:00 by gronod · 3 comments
Owner

Summary

The Epson driver (e.g., Epson XP-55 Series) offers 7 quality options — Fast Economy (301), Economy (302), Normal (303), Draft (308), Fine (304), Quality (305), Best Quality (307) — but ICCery v2 Mac does not enumerate them: CupsService.capabilities(from:ppd:) performs no quality extraction at all, so only the driver default ("Normal" / 303) can ever apply.

Note: no quality picker exists in Stage 2 until #183 lands — the enumeration gap becomes user-visible once it does.

This same limitation isn't present in TargetPrint (ICCery-CPU) which doesn't restrict available quality settings and correctly shows all 7 options.

Root Cause Analysis

What lpoptions -l Actually Returns

EPIJ_Qual/Print Quality: 301 302 *303 308 304 305 307

All 7 options ARE present in the CUPS lpoptions -l output. The issue is not with CUPS — it's with ICCery v2 Mac's parsing code.

Where the Bug Lives

  1. CupsService.capabilities(from:ppd:) (Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsService.swift:105-136) — the actual blocker

    • Only handles InputSlot/MediaSource, PageSize/MediaSize, and CupsParsers.mediaTypeKeys
    • No quality option extraction at all
  2. No quality-key detection roster exists in CupsParsers

    • CupsOptionsFilter.relevantKeys (CupsOptionsFilter.swift:16-32) is a capture-relevance roster — it decides which -o options survive into captured cupsOptions, not which options get enumerated. EPIJ_Qual already survives isRelevant via the permissive non-com.* rule (CupsOptionsFilter.swift:44-52), so the roster is not the blocker — a new detection roster + extraction is
    • EPIJ_Quality (in relevantKeys, CupsOptionsFilter.swift:28) is a phantom/alias — real Epson PPDs use EPIJ_Qual. Keep EPIJ_Quality (harmless for capture); treat EPIJ_Qual as canonical in the new quality-key roster
  3. Test fixture mismatch (Tests/ICCeryCoreTests/CupsParserTests.swift:33)

    • Uses cupsPrintQuality/cupsPrintQuality: Draft *Normal High (3 options)
    • Real Epson driver uses EPIJ_Qual with 7 options

Comparison with ICCery-CPU (TargetPrint)

ICCery-CPU correctly handles this in CUPSManager.discoverPPDOptions() (Sources/Printing/CUPSManager.swift:190-196):

let quality = discoverOptionChoices(
    in: ppdText,
    candidateKeywords: ["CNIJPrintQuality", "EPIJ_Qual", "cupsPrintQuality", "PrintQuality", "CNIJPrintMode2", "Quality", "StpQuality"],
    openUITargets: ["print quality", "quality"]
)

It includes EPIJ_Qual in candidate keywords AND scans *OpenUI translations.

PPD Evidence (Epson XP-55 Series)

/etc/cups/ppd/Epson_XP_55_Series.ppd:
*OpenUI *EPIJ_Qual/Print Quality: PickOne
*DefaultEPIJ_Qual: 303
*EPIJ_Qual 301/Fast Economy: ""
*EPIJ_Qual 302/Economy: ""
*EPIJ_Qual 303/Normal: ""
*EPIJ_Qual 308/Draft: ""
*EPIJ_Qual 304/Fine: ""
*EPIJ_Qual 305/Quality: ""
*EPIJ_Qual 307/Best Quality: ""

Proposed Fix Options

Option 1: Minimal Fix — quality extraction + detection roster

  • Add quality option extraction to CupsService.capabilities() (or a new function) — this is the actual blocker
  • Add a quality-key detection roster with EPIJ_Qual canonical, ordered vendor-first / weakest-last (OutputMode/Resolution last — Epson OutputMode is a colour-mode key, misdetect risk); EPIJ_Qual may also be added to CupsOptionsFilter.relevantKeys for explicitness (it already survives isRelevant)
  • Allows existing lpoptions -l path to capture all quality options

Option 2: Full PPD Parsing (aligns with ICCery-CPU)

  • Parse PPD directly for quality choices with human labels
  • More accurate but adds complexity

Option 3: Hybrid Approach (Recommended)

  • Use lpoptions -l for basic enumeration
  • Add PPD parsing specifically for vendor-specific quality/media keys
  • Matches ICCery-CPU's discoverOptionChoices approach

Acceptance Criteria

  • All 7 Epson quality options appear in ICCery's quality UI — codes 301, 302, 303, 304, 305, 307, 308 (no 306 exists; 308 = Draft)
  • Quality options display with human-readable labels (Fast Economy, Economy, Normal, Draft, Fine, Quality, Best Quality)
  • Works for all Epson printers using EPIJ_Qual key
  • Test fixture updated to reflect real EPIJ_Qual output

Dependencies

  • Depends on: #183 (Stage 2 quality picker UI must exist before this bug fix can show all options)
  • Blocks: #186 (printer properties dialog needs all quality options to pre-select)

References

  • ICCery-CPU: Sources/Printing/CUPSManager.swift (discoverOptionChoices, optionDetails)
  • ICCery v2 Mac: Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsOptionsFilter.swift
  • ICCery v2 Mac: Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsService.swift
  • ICCery v2 Mac: Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsParsers.swift
  • Test: Tests/ICCeryCoreTests/CupsParserTests.swift
## Summary The Epson driver (e.g., Epson XP-55 Series) offers 7 quality options — Fast Economy (301), Economy (302), Normal (303), Draft (308), Fine (304), Quality (305), Best Quality (307) — but ICCery v2 Mac does not enumerate them: `CupsService.capabilities(from:ppd:)` performs **no quality extraction at all**, so only the driver default ("Normal" / 303) can ever apply. Note: no quality picker exists in Stage 2 until #183 lands — the enumeration gap becomes user-visible once it does. This same limitation isn't present in TargetPrint (ICCery-CPU) which doesn't restrict available quality settings and correctly shows all 7 options. ## Root Cause Analysis ### What `lpoptions -l` Actually Returns ``` EPIJ_Qual/Print Quality: 301 302 *303 308 304 305 307 ``` All 7 options ARE present in the CUPS `lpoptions -l` output. The issue is not with CUPS — it's with ICCery v2 Mac's parsing code. ### Where the Bug Lives 1. **`CupsService.capabilities(from:ppd:)`** (Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsService.swift:105-136) — **the actual blocker** - Only handles `InputSlot`/`MediaSource`, `PageSize`/`MediaSize`, and `CupsParsers.mediaTypeKeys` - No quality option extraction at all 2. **No quality-key detection roster exists in `CupsParsers`** - `CupsOptionsFilter.relevantKeys` (CupsOptionsFilter.swift:16-32) is a **capture-relevance** roster — it decides which `-o` options survive into captured `cupsOptions`, not which options get enumerated. `EPIJ_Qual` already survives `isRelevant` via the permissive non-`com.*` rule (CupsOptionsFilter.swift:44-52), so the roster is not the blocker — a new detection roster + extraction is - `EPIJ_Quality` (in `relevantKeys`, CupsOptionsFilter.swift:28) is a **phantom/alias** — real Epson PPDs use `EPIJ_Qual`. Keep `EPIJ_Quality` (harmless for capture); treat `EPIJ_Qual` as canonical in the new quality-key roster 3. **Test fixture mismatch** (Tests/ICCeryCoreTests/CupsParserTests.swift:33) - Uses `cupsPrintQuality/cupsPrintQuality: Draft *Normal High` (3 options) - Real Epson driver uses `EPIJ_Qual` with 7 options ### Comparison with ICCery-CPU (TargetPrint) ICCery-CPU correctly handles this in `CUPSManager.discoverPPDOptions()` (Sources/Printing/CUPSManager.swift:190-196): ```swift let quality = discoverOptionChoices( in: ppdText, candidateKeywords: ["CNIJPrintQuality", "EPIJ_Qual", "cupsPrintQuality", "PrintQuality", "CNIJPrintMode2", "Quality", "StpQuality"], openUITargets: ["print quality", "quality"] ) ``` It includes `EPIJ_Qual` in candidate keywords AND scans `*OpenUI` translations. ### PPD Evidence (Epson XP-55 Series) ``` /etc/cups/ppd/Epson_XP_55_Series.ppd: *OpenUI *EPIJ_Qual/Print Quality: PickOne *DefaultEPIJ_Qual: 303 *EPIJ_Qual 301/Fast Economy: "" *EPIJ_Qual 302/Economy: "" *EPIJ_Qual 303/Normal: "" *EPIJ_Qual 308/Draft: "" *EPIJ_Qual 304/Fine: "" *EPIJ_Qual 305/Quality: "" *EPIJ_Qual 307/Best Quality: "" ``` ## Proposed Fix Options ### Option 1: Minimal Fix — quality extraction + detection roster - Add quality option extraction to `CupsService.capabilities()` (or a new function) — this is the actual blocker - Add a quality-key detection roster with `EPIJ_Qual` canonical, ordered vendor-first / weakest-last (`OutputMode`/`Resolution` last — Epson `OutputMode` is a colour-mode key, misdetect risk); `EPIJ_Qual` may also be added to `CupsOptionsFilter.relevantKeys` for explicitness (it already survives `isRelevant`) - Allows existing `lpoptions -l` path to capture all quality options ### Option 2: Full PPD Parsing (aligns with ICCery-CPU) - Parse PPD directly for quality choices with human labels - More accurate but adds complexity ### Option 3: Hybrid Approach (Recommended) - Use `lpoptions -l` for basic enumeration - Add PPD parsing specifically for vendor-specific quality/media keys - Matches ICCery-CPU's `discoverOptionChoices` approach ## Acceptance Criteria - [x] All 7 Epson quality options appear in ICCery's quality UI — codes **301, 302, 303, 304, 305, 307, 308** (no 306 exists; 308 = Draft) - [ ] Quality options display with human-readable labels (Fast Economy, Economy, Normal, Draft, Fine, Quality, Best Quality) - [ ] Works for all Epson printers using `EPIJ_Qual` key - [ ] Test fixture updated to reflect real `EPIJ_Qual` output ## Dependencies - **Depends on:** #183 (Stage 2 quality picker UI must exist before this bug fix can show all options) - **Blocks:** #186 (printer properties dialog needs all quality options to pre-select) ## References - ICCery-CPU: `Sources/Printing/CUPSManager.swift` (discoverOptionChoices, optionDetails) - ICCery v2 Mac: `Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsOptionsFilter.swift` - ICCery v2 Mac: `Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsService.swift` - ICCery v2 Mac: `Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsParsers.swift` - Test: `Tests/ICCeryCoreTests/CupsParserTests.swift`
gronod added the Kind/Bug
Priority
Critical
1
Project/ICCery-v2Bug/Backend
labels 2026-09-15 10:03:25 +01:00
gronod added this to the M11 — Printer settings completeness & dialog binding milestone 2026-09-15 11:58:22 +01:00
Author
Owner

Planning errata (verified on develop@736fd08):

  1. AC "(301–307)" — the seven codes are 301, 302, 303, 304, 305, 307, 308; no 306 exists and 308 (Draft) is included (matches the body's own lpoptions -l evidence: EPIJ_Qual/Print Quality: 301 302 *303 308 304 305 307).
  2. RCA framing: CupsOptionsFilter.relevantKeys is a capture-relevance roster, not an enumeration roster — EPIJ_Qual already survives isRelevant via the permissive non-com.* rule (CupsOptionsFilter.swift:44–52). The actual blockers are: CupsService.capabilities(from:ppd:) has no quality extraction at all (CupsService.swift:105–136 handles InputSlot/MediaSource, PageSize/MediaSize, and mediaTypeKeys only), and no quality-key detection roster exists in CupsParsers.
  3. "only 'Normal' shown in ICCery" — no quality picker exists in Stage 2 until #183 lands; the enumeration gap becomes user-visible only then.
  4. EPIJ_Quality (in relevantKeys, CupsOptionsFilter.swift:28) is a phantom/alias — real Epson PPDs use EPIJ_Qual. Keep EPIJ_Quality (harmless for capture); treat EPIJ_Qual as canonical in the new quality-key roster.
Planning errata (verified on develop@736fd08): 1. AC "(301–307)" — the seven codes are **301, 302, 303, 304, 305, 307, 308**; no 306 exists and 308 (Draft) is included (matches the body's own `lpoptions -l` evidence: `EPIJ_Qual/Print Quality: 301 302 *303 308 304 305 307`). 2. RCA framing: `CupsOptionsFilter.relevantKeys` is a **capture-relevance** roster, not an enumeration roster — `EPIJ_Qual` already survives `isRelevant` via the permissive non-`com.*` rule (CupsOptionsFilter.swift:44–52). The actual blockers are: `CupsService.capabilities(from:ppd:)` has **no quality extraction at all** (CupsService.swift:105–136 handles InputSlot/MediaSource, PageSize/MediaSize, and mediaTypeKeys only), and no quality-key detection roster exists in `CupsParsers`. 3. "only 'Normal' shown in ICCery" — no quality picker exists in Stage 2 until #183 lands; the enumeration gap becomes user-visible only then. 4. `EPIJ_Quality` (in `relevantKeys`, CupsOptionsFilter.swift:28) is a phantom/alias — real Epson PPDs use `EPIJ_Qual`. Keep `EPIJ_Quality` (harmless for capture); treat `EPIJ_Qual` as canonical in the new quality-key roster.
Author
Owner

Errata above incorporated into the issue body — the ticket itself is now the corrected contract.

Errata above incorporated into the issue body — the ticket itself is now the corrected contract.
Author
Owner

Implementation landed in PR #194 at c9b423b on milestone/m11-print-settings.

Acceptance evidence:

  • All 7 Epson EPIJ_Qual options appear in the quality UI — codes 301, 302, 303, 304, 305, 307, 308 in driver order (no sort) — recorded Epson XP-55 fixture EPIJ_Qual/Print Quality: 301 302 *303 308 304 305 307; UI test testQualityPickerListsAllSevenDriverOptions, core testCapabilitiesQualityEpsonDetection (CupsParserTests)
  • Quality options display human-readable labels from the PPD (Fast Economy, Economy, Normal, Draft, Fine, Quality, Best Quality) with qualityDefault == "303" — testCapabilitiesQualityPpdLabels (CupsParserTests)
  • Works for all Epson printers using EPIJ_Qual; EPIJ_Qual is canonical in the quality-key roster and is chosen over OutputMode/Resolution when both exist — testQualityKeyRosterOrder (CupsParserTests)
  • Captured EPIJ_Qual= survives CupsOptionsFilter.filter and suppresses the derived emit — testKeepsQualityKeys (CupsOptionsFilterTests), testCapturedWinsQuality, testCapturedQualityCaseInsensitiveDedup (LpArgsTests)
  • EPIJ_Quality retained as a harmless capture alias in relevantKeys (R16) — roster unchanged, covered by testKeepsRelevant
  • Test fixture updated to reflect real EPIJ_Qual output — synthetic 3-option fixture replaced by the recorded XP-55 enumeration

Verification at milestone/m11-print-settings 6adbf99bd9:

  • xcodebuild test -scheme ICCery -destination 'platform=macOS' -derivedDataPath build/DerivedData-m11-final ARCHS="$(uname -m)" CODE_SIGNING_ALLOWED=YES CODE_SIGN_IDENTITY='-' (x86_64): passed
  • full host-arch ICCeryCoreTests: 483 passed, 0 failed
  • full ICCeryUITests: 67 passed, 0 failed (genuine xctest pass summary; single allowed clean rerun consumed for an app-activation attach flake in run 1 — testAboutDialogShowsVersionAndBuildDate, no assertion failure)

Hardware item remains a milestone gate (not this issue's merge gate).
Closing manually only after code and tests are present on the milestone branch.

Implementation landed in PR #194 at c9b423b on milestone/m11-print-settings. Acceptance evidence: - [x] All 7 Epson `EPIJ_Qual` options appear in the quality UI — codes 301, 302, 303, 304, 305, 307, 308 in driver order (no sort) — recorded Epson XP-55 fixture `EPIJ_Qual/Print Quality: 301 302 *303 308 304 305 307`; UI test `testQualityPickerListsAllSevenDriverOptions`, core `testCapabilitiesQualityEpsonDetection` (CupsParserTests) - [x] Quality options display human-readable labels from the PPD (Fast Economy, Economy, Normal, Draft, Fine, Quality, Best Quality) with `qualityDefault == "303"` — `testCapabilitiesQualityPpdLabels` (CupsParserTests) - [x] Works for all Epson printers using `EPIJ_Qual`; `EPIJ_Qual` is canonical in the quality-key roster and is chosen over `OutputMode`/`Resolution` when both exist — `testQualityKeyRosterOrder` (CupsParserTests) - [x] Captured `EPIJ_Qual=` survives `CupsOptionsFilter.filter` and suppresses the derived emit — `testKeepsQualityKeys` (CupsOptionsFilterTests), `testCapturedWinsQuality`, `testCapturedQualityCaseInsensitiveDedup` (LpArgsTests) - [x] `EPIJ_Quality` retained as a harmless capture alias in `relevantKeys` (R16) — roster unchanged, covered by `testKeepsRelevant` - [x] Test fixture updated to reflect real `EPIJ_Qual` output — synthetic 3-option fixture replaced by the recorded XP-55 enumeration Verification at milestone/m11-print-settings 6adbf99bd9aaefd84a55a35442f1bd18bf8ab2c3: - `xcodebuild test -scheme ICCery -destination 'platform=macOS' -derivedDataPath build/DerivedData-m11-final ARCHS="$(uname -m)" CODE_SIGNING_ALLOWED=YES CODE_SIGN_IDENTITY='-'` (x86_64): passed - full host-arch ICCeryCoreTests: 483 passed, 0 failed - full ICCeryUITests: 67 passed, 0 failed (genuine xctest pass summary; single allowed clean rerun consumed for an app-activation attach flake in run 1 — `testAboutDialogShowsVersionAndBuildDate`, no assertion failure) Hardware item remains a milestone gate (not this issue's merge gate). Closing manually only after code and tests are present on the milestone branch.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: gronod/iccery-v2-mac#180