In the TargetPrint Inspector sidebar, the "Media type" dropdown (typePop) displays raw numeric choice codes (e.g. 0, 13, 15, 92, 145) rather than the human-readable media type names defined by the printer driver (e.g. plain papers, Epson Premium Glossy, Epson Premium Semigloss, Epson Ultra Glossy, Photo Paper Glossy).
Investigation & Root Cause Analysis
1. PPD Translation Strings Discarded during Option Parsing
In Adobe PostScript Printer Description (PPD) files (Spec v4.3 §3.2), option choices are specified in the format:
Because rest.split includes / as a delimiter and takes only .first, it extracts solely the token before / (the internal choice code 13) and completely drops the human-readable translation string (Epson Premium Glossy).
2. Raw Choice Codes Fed Directly to UI PopUpButton
In Sources/Views/InspectorView.swift:
refill(typePop,q?.mediaTypes??["Plain"])
typePop (NSPopUpButton) is populated directly from q.mediaTypes. Because q.mediaTypes contains only raw extracted tokens (["0", "92", "13", "15", "145", ...]), the dropdown presents these meaningless numbers to the user.
3. Dual-Identity Requirement (UI Title vs. Driver PPD Value)
The printer driver and CUPS filters expect the computer-readable ChoiceName (e.g. MediaType=13), not the human-readable title MediaType=Epson Premium Glossy. If the UI is changed to solely store the human-readable text in engine.mediaType, driver-level option matching will fail. Each choice must preserve both:
name: The computer-readable PPD choice code ("13") to be sent to CUPS / NSPrintInfo.
title: The human-readable label ("Epson Premium Glossy") shown in the UI.
4. Additional Discovered Edge Cases
Hex character escapes in PPD translation strings: PPDs encode special characters as hex pairs like <2F> for / and <2E> for . (e.g. *MediaType 26/CD<2F>DVD: "" in Epson drivers). These must be decoded to display CD/DVD properly.
Vendor-specific keyword prefixes: Canon drivers (e.g. Canon Pro9500 Mark II) use *CNIJMediaType instead of *MediaType (*OpenUI *CNIJMediaType/Media Type: PickOne). Because discoverPPDOptions only searches for MediaType, Canon queues currently discover 0 media types and fall back to ["Plain"].
Steps to Reproduce
Install or configure an OEM inkjet printer queue (such as an Epson XP-55 or stylus series driver).
Launch TargetPrint.
Select the printer in the Inspector sidebar.
Inspect the "Media type" dropdown.
Observed: The dropdown contains numbers: 0, 92, 13, 15, 145, etc.
Expected: The dropdown displays human-readable names: plain papers, Epson Ultra Glossy, Epson Premium Glossy, Epson Premium Semigloss, Photo Paper Glossy, etc.
Implementation Plan for Fix
Phase 1: PPD Option Choice Model & Parsing (CUPSManager.swift)
Introduce a choice representation struct in CUPSManager.swift:
Helper function decodePPDString(_:) that translates <2F> -> /, <2E> -> ., etc.
Replace optionChoices(in:keyword:) with a structured parser optionDetails(in:keywords:):
Match option lines: *<keyword> <choice>[/<translation>]:
Extract <choice> as name.
If /<translation> exists, trim whitespace, strip surrounding double-quotes if present, decode hex escapes, and use as title. If absent, use name as title.
Deduplicate choices by name.
Support media type keyword aliases in discoverPPDOptions:
Search for MediaType as well as vendor variants like CNIJMediaType (or scan *OpenUI *<kw>/Media Type).
Populate structured mediaTypeChoices: [PPDChoice] in PPDOptions and PrinterQueue (retaining mediaTypes: [String] for backward compatibility where appropriate).
Update refill helper (or add a specialized overload for [PPDChoice]):
For each choice, create an NSMenuItem with title = choice.title and representedObject = choice.name.
Update selection logic (select(typePop, ...)):
Support matching by either representedObject as? String == value (if given a PPD choice code like "13") OR title == value (if given a display name like "Epson Premium Glossy").
Update push(into engine:):
Set engine.mediaType to typePop.selectedItem?.representedObject as? String ?? typePop.titleOfSelectedItem.
This ensures the underlying numeric code is passed to the printing pipeline.
Verify that applyOptionalPPDKeys passes the resolved choice name to extras["MediaType"] (or vendor key like CNIJMediaType if applicable).
Ensure TargetJob deserialization tolerates either the PPD choice code or the display name when setting printSettings.mediaType.
Phase 4: Unit Testing & Verification
Add tests in Tests/:
Test parsing Epson PPD media type lines with integer choices and translation strings.
Test parsing Canon PPD lines with *CNIJMediaType.
Test hex decoding (CD<2F>DVD -> CD/DVD).
Test InspectorView / PrintEngine selection mapping between choice code and display title.
Verify existing test suite passes (swift test).
### Summary
In the TargetPrint Inspector sidebar, the "Media type" dropdown (`typePop`) displays raw numeric choice codes (e.g. `0`, `13`, `15`, `92`, `145`) rather than the human-readable media type names defined by the printer driver (e.g. `plain papers`, `Epson Premium Glossy`, `Epson Premium Semigloss`, `Epson Ultra Glossy`, `Photo Paper Glossy`).
---
### Investigation & Root Cause Analysis
#### 1. PPD Translation Strings Discarded during Option Parsing
In Adobe PostScript Printer Description (PPD) files (Spec v4.3 §3.2), option choices are specified in the format:
```ppd
*<OptionKeyword> <ChoiceName>[/<TranslationString>]: "<InvocationCode>"
```
Where:
- `<ChoiceName>` is the internal PostScript / CUPS choice identifier (often an integer ID in vendor inkjet drivers, e.g. `0`, `13`, `92`).
- `/<TranslationString>` is the user-facing localized display name (e.g. `/Epson Premium Glossy`).
In `Sources/Printing/CUPSManager.swift` (`optionChoices(in:keyword:)`, lines 179–193):
```swift
static func optionChoices(in ppdText: String, keyword: String) -> [String] {
var choices: [String] = []
let prefix = "*\(keyword) "
for raw in ppdText.split(whereSeparator: \.isNewline) {
let line = String(raw)
guard line.hasPrefix(prefix) else { continue }
let rest = String(line.dropFirst(prefix.count))
let token = rest.split(whereSeparator: { $0 == "/" || $0 == ":" || $0 == " " }).first
if let token = token {
let name = String(token)
if !choices.contains(name) { choices.append(name) }
}
}
return choices
}
```
Because `rest.split` includes `/` as a delimiter and takes only `.first`, it extracts solely the token before `/` (the internal choice code `13`) and completely drops the human-readable translation string (`Epson Premium Glossy`).
#### 2. Raw Choice Codes Fed Directly to UI PopUpButton
In `Sources/Views/InspectorView.swift`:
```swift
refill(typePop, q?.mediaTypes ?? ["Plain"])
```
`typePop` (`NSPopUpButton`) is populated directly from `q.mediaTypes`. Because `q.mediaTypes` contains only raw extracted tokens (`["0", "92", "13", "15", "145", ...]`), the dropdown presents these meaningless numbers to the user.
#### 3. Dual-Identity Requirement (UI Title vs. Driver PPD Value)
Crucially, when printing via `PrintEngine.swift`:
```swift
private func applyOptionalPPDKeys(to info: NSPrintInfo) {
var extras: [String: String] = [:]
if let mediaType = mediaType { extras["MediaType"] = mediaType }
...
CUPSManager.applyVendorBypass(extras, to: info)
}
```
The printer driver and CUPS filters expect the computer-readable `ChoiceName` (e.g. `MediaType=13`), not the human-readable title `MediaType=Epson Premium Glossy`. If the UI is changed to solely store the human-readable text in `engine.mediaType`, driver-level option matching will fail. Each choice must preserve both:
1. `name`: The computer-readable PPD choice code (`"13"`) to be sent to CUPS / `NSPrintInfo`.
2. `title`: The human-readable label (`"Epson Premium Glossy"`) shown in the UI.
#### 4. Additional Discovered Edge Cases
- **Hex character escapes in PPD translation strings**: PPDs encode special characters as hex pairs like `<2F>` for `/` and `<2E>` for `.` (e.g. `*MediaType 26/CD<2F>DVD: ""` in Epson drivers). These must be decoded to display `CD/DVD` properly.
- **Vendor-specific keyword prefixes**: Canon drivers (e.g. Canon Pro9500 Mark II) use `*CNIJMediaType` instead of `*MediaType` (`*OpenUI *CNIJMediaType/Media Type: PickOne`). Because `discoverPPDOptions` only searches for `MediaType`, Canon queues currently discover 0 media types and fall back to `["Plain"]`.
---
### Steps to Reproduce
1. Install or configure an OEM inkjet printer queue (such as an Epson XP-55 or stylus series driver).
2. Launch `TargetPrint`.
3. Select the printer in the Inspector sidebar.
4. Inspect the "Media type" dropdown.
5. **Observed:** The dropdown contains numbers: `0`, `92`, `13`, `15`, `145`, etc.
6. **Expected:** The dropdown displays human-readable names: `plain papers`, `Epson Ultra Glossy`, `Epson Premium Glossy`, `Epson Premium Semigloss`, `Photo Paper Glossy`, etc.
---
### Implementation Plan for Fix
#### Phase 1: PPD Option Choice Model & Parsing (`CUPSManager.swift`)
1. Introduce a choice representation struct in `CUPSManager.swift`:
```swift
struct PPDChoice: Equatable {
var name: String // Computer-readable identifier (e.g. "13")
var title: String // Human-readable display label (e.g. "Epson Premium Glossy")
}
```
2. Implement PPD hex sequence decoding:
- Helper function `decodePPDString(_:)` that translates `<2F>` -> `/`, `<2E>` -> `.`, etc.
3. Replace `optionChoices(in:keyword:)` with a structured parser `optionDetails(in:keywords:)`:
- Match option lines: `*<keyword> <choice>[/<translation>]:`
- Extract `<choice>` as `name`.
- If `/<translation>` exists, trim whitespace, strip surrounding double-quotes if present, decode hex escapes, and use as `title`. If absent, use `name` as `title`.
- Deduplicate choices by `name`.
4. Support media type keyword aliases in `discoverPPDOptions`:
- Search for `MediaType` as well as vendor variants like `CNIJMediaType` (or scan `*OpenUI *<kw>/Media Type`).
- Populate structured `mediaTypeChoices: [PPDChoice]` in `PPDOptions` and `PrinterQueue` (retaining `mediaTypes: [String]` for backward compatibility where appropriate).
#### Phase 2: Inspector UI Integration (`InspectorView.swift`)
1. Update `refill` helper (or add a specialized overload for `[PPDChoice]`):
- For each choice, create an `NSMenuItem` with `title = choice.title` and `representedObject = choice.name`.
2. Update selection logic (`select(typePop, ...)`):
- Support matching by either `representedObject as? String == value` (if given a PPD choice code like `"13"`) OR `title == value` (if given a display name like `"Epson Premium Glossy"`).
3. Update `push(into engine:)`:
- Set `engine.mediaType` to `typePop.selectedItem?.representedObject as? String ?? typePop.titleOfSelectedItem`.
- This ensures the underlying numeric code is passed to the printing pipeline.
#### Phase 3: PrintEngine & TargetJob Compatibility (`PrintEngine.swift`, `TargetJob.swift`)
1. Verify that `applyOptionalPPDKeys` passes the resolved choice name to `extras["MediaType"]` (or vendor key like `CNIJMediaType` if applicable).
2. Ensure `TargetJob` deserialization tolerates either the PPD choice code or the display name when setting `printSettings.mediaType`.
#### Phase 4: Unit Testing & Verification
1. Add tests in `Tests/`:
- Test parsing Epson PPD media type lines with integer choices and translation strings.
- Test parsing Canon PPD lines with `*CNIJMediaType`.
- Test hex decoding (`CD<2F>DVD` -> `CD/DVD`).
- Test `InspectorView` / `PrintEngine` selection mapping between choice code and display title.
2. Verify existing test suite passes (`swift test`).
gronod
added the Kind/Bug label 2026-09-07 21:38:34 +01:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
In the TargetPrint Inspector sidebar, the "Media type" dropdown (
typePop) displays raw numeric choice codes (e.g.0,13,15,92,145) rather than the human-readable media type names defined by the printer driver (e.g.plain papers,Epson Premium Glossy,Epson Premium Semigloss,Epson Ultra Glossy,Photo Paper Glossy).Investigation & Root Cause Analysis
1. PPD Translation Strings Discarded during Option Parsing
In Adobe PostScript Printer Description (PPD) files (Spec v4.3 §3.2), option choices are specified in the format:
Where:
<ChoiceName>is the internal PostScript / CUPS choice identifier (often an integer ID in vendor inkjet drivers, e.g.0,13,92)./<TranslationString>is the user-facing localized display name (e.g./Epson Premium Glossy).In
Sources/Printing/CUPSManager.swift(optionChoices(in:keyword:), lines 179–193):Because
rest.splitincludes/as a delimiter and takes only.first, it extracts solely the token before/(the internal choice code13) and completely drops the human-readable translation string (Epson Premium Glossy).2. Raw Choice Codes Fed Directly to UI PopUpButton
In
Sources/Views/InspectorView.swift:typePop(NSPopUpButton) is populated directly fromq.mediaTypes. Becauseq.mediaTypescontains only raw extracted tokens (["0", "92", "13", "15", "145", ...]), the dropdown presents these meaningless numbers to the user.3. Dual-Identity Requirement (UI Title vs. Driver PPD Value)
Crucially, when printing via
PrintEngine.swift:The printer driver and CUPS filters expect the computer-readable
ChoiceName(e.g.MediaType=13), not the human-readable titleMediaType=Epson Premium Glossy. If the UI is changed to solely store the human-readable text inengine.mediaType, driver-level option matching will fail. Each choice must preserve both:name: The computer-readable PPD choice code ("13") to be sent to CUPS /NSPrintInfo.title: The human-readable label ("Epson Premium Glossy") shown in the UI.4. Additional Discovered Edge Cases
<2F>for/and<2E>for.(e.g.*MediaType 26/CD<2F>DVD: ""in Epson drivers). These must be decoded to displayCD/DVDproperly.*CNIJMediaTypeinstead of*MediaType(*OpenUI *CNIJMediaType/Media Type: PickOne). BecausediscoverPPDOptionsonly searches forMediaType, Canon queues currently discover 0 media types and fall back to["Plain"].Steps to Reproduce
TargetPrint.0,92,13,15,145, etc.plain papers,Epson Ultra Glossy,Epson Premium Glossy,Epson Premium Semigloss,Photo Paper Glossy, etc.Implementation Plan for Fix
Phase 1: PPD Option Choice Model & Parsing (
CUPSManager.swift)CUPSManager.swift:decodePPDString(_:)that translates<2F>->/,<2E>->., etc.optionChoices(in:keyword:)with a structured parseroptionDetails(in:keywords:):*<keyword> <choice>[/<translation>]:<choice>asname./<translation>exists, trim whitespace, strip surrounding double-quotes if present, decode hex escapes, and use astitle. If absent, usenameastitle.name.discoverPPDOptions:MediaTypeas well as vendor variants likeCNIJMediaType(or scan*OpenUI *<kw>/Media Type).mediaTypeChoices: [PPDChoice]inPPDOptionsandPrinterQueue(retainingmediaTypes: [String]for backward compatibility where appropriate).Phase 2: Inspector UI Integration (
InspectorView.swift)refillhelper (or add a specialized overload for[PPDChoice]):NSMenuItemwithtitle = choice.titleandrepresentedObject = choice.name.select(typePop, ...)):representedObject as? String == value(if given a PPD choice code like"13") ORtitle == value(if given a display name like"Epson Premium Glossy").push(into engine:):engine.mediaTypetotypePop.selectedItem?.representedObject as? String ?? typePop.titleOfSelectedItem.Phase 3: PrintEngine & TargetJob Compatibility (
PrintEngine.swift,TargetJob.swift)applyOptionalPPDKeyspasses the resolved choice name toextras["MediaType"](or vendor key likeCNIJMediaTypeif applicable).TargetJobdeserialization tolerates either the PPD choice code or the display name when settingprintSettings.mediaType.Phase 4: Unit Testing & Verification
Tests/:*CNIJMediaType.CD<2F>DVD->CD/DVD).InspectorView/PrintEngineselection mapping between choice code and display title.swift test).