[Bug/Medium] Canon printer media types display Thai characters instead of English labels #181

Open
opened 2026-09-15 10:04:06 +01:00 by gronod · 2 comments
Owner

Summary

The media type list for Canon Pro9500 Mark II shows Thai characters instead of English:

  • กระดาษธรรมดา instead of "Plain Paper"
  • กระดาษภาพถ่ายมันเงา+ II instead of "Photo Paper Plus Glossy II"
  • กระดาษกึ่งเงา ภาพถ่ายพิเศษ instead of "Photo Paper Plus Semi-gloss"
  • กระดาษ Fine Art อื่น instead of "Other Fine Art Paper"
  • แผ่นกระดาษแข็ง instead of "Board Paper"
  • ดิสก์ที่พิมพ์ได้ instead of "Printable disc"
  • ดิสก์ที่พิมพ์ได้ (กันซึม) instead of "Printable disc (bleed-proof)"

The correct list (as seen in TargetPrint) should be the full CNIJMediaType choice set in English — 18 types are enumerated in the acceptance criteria below; the true count is verified against the real PPD (do not hardcode).

Root Cause Analysis

PPD Locale Translation Order

The Canon PPD contains locale-specific translations for CNIJMediaType:

  • Line 278: *CNIJMediaType 0/Plain Paper: "" (base, no locale prefix)
  • Lines 676-5104: Various locales (ja, nl, fr, de, it, es, zh_CN, pl, ru, pt, zh_TW, ko, da, no, sv, fi, cs, ar, tr)
  • Line 5350: *th.CNIJMediaType 0/กระดาษธรรมดา: "" (Thai — LAST)

The Bug in CupsParsers.ppdChoiceLabels()

(Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsParsers.swift:177-211)

public static func ppdChoiceLabels(_ ppd: String, key: String) -> [String: String] {
    var map: [String: String] = [:]
    for rawLine in ppd.split(separator: "\n") {
        // ... locale stripping logic ...
        if !id.isEmpty { map[id] = human.isEmpty ? id : human }
    }
    return map
}

Problem: The function processes lines sequentially and uses a dictionary map[id] = human. Since Thai translation comes LAST (line 5350), it overwrites the English label.

Locale stripping logic exists but doesn't prioritize:

if let dot = line.firstIndex(of: ".") {
    let prefix = line[..<dot]
    let looksLikeLocale = (2...5).contains(prefix.count)
        && prefix.allSatisfy { $0.isLetter || $0 == "_" }
        && (prefix.count == 2 || prefix.contains("_"))
    // ...
}

For th.CNIJMediaType, prefix is th (2 chars, all letters) → looksLikeLocale = true. The stripping works, but the function doesn't skip locale-qualified lines — it just processes them and the last write wins.

PPD Evidence (Canon Pro9500 Mark II)

Line 278:  *CNIJMediaType 0/Plain Paper: ""
Line 5350: *th.CNIJMediaType 0/กระดาษธรรมดา: ""

All media types affected (Thai lines 5350-5600+)

Comparison with ICCery-CPU

ICCery-CPU's CUPSManager.optionDetails() (Sources/Printing/CUPSManager.swift:312-341) also parses PPD choice lines but:

  • Uses decodePPDString() to handle PPD hex escapes (<2F>/, etc.)
  • Reads PPD as ISO-Latin-1 (not UTF-8) — do not copy verbatim: this Canon PPD's Thai labels are UTF-8, and a blanket ISO-Latin-1 read produces mojibake that breaks the very labels under test
  • Doesn't explicitly handle locale priority either

Proposed Fix Options

Option 1: Deterministic locale precedence (Recommended)

Replace last-write-wins in ppdChoiceLabels with deterministic precedence — unqualified *Key > en_US > en > first-qualified-seen:

  • A locale-qualified line never overwrites an unqualified one
  • A key whose only labels are locale-qualified still gets its first-seen qualified label
  • Implement via two passes or tracked precedence; plain "first write wins" (map[id] == nil guard) is fragile — it silently depends on base lines preceding locale lines in file order

Option 2: Add Locale Priority Parameter

Accept preferred locale (e.g., en, en_US) and prioritize those translations — subsumed by Option 1's fixed precedence unless configurability is needed.

Option 3: PPD Encoding + Hex Escape Decoding

  • loadPPD reads UTF-8 first, ISO-Latin-1 only on decode failure (CupsService.swift:168-171 is currently UTF-8-only via try?) — a blanket ISO-Latin-1 read would garble this PPD's UTF-8 Thai labels
  • Add <XX> hex-escape decoding (<2F>/, <20> → space) in the label parser — needed for proper PPD parsing anyway

Acceptance Criteria

  • Canon media types display in English (or user's system locale)
  • Every CNIJMediaType choice from lpoptions -l has a non-Thai label — the 18 known labels below must all appear correctly; hardware verification confirms the true count against the real PPD:
    • Plain Paper
    • Photo Paper Plus Glossy II
    • Photo Paper Pro Platinum N
    • Photo Paper Pro Platinum
    • Photo Paper Pro Luster
    • Photo Paper Plus Semi-gloss
    • Matte Photo Paper
    • Fine Art "Photo Rag"
    • Fine Art "Museum Etching"
    • Photo Paper Pro Premium Matte
    • Fine Art Premium Matte
    • Other Fine Art Paper
    • Canvas
    • Board Paper
    • Ink Jet Hagaki
    • Hagaki
    • Printable disc
    • Printable disc (bleed-proof)
  • No Thai (or other non-preferred locale) characters appear unless explicitly selected

Dependencies

  • Depends on: #183 (Stage 2 media type picker must exist before this bug fix can show correct labels)
  • Blocks: #186 (printer properties dialog needs correct media type labels to pre-select)

References

  • ICCery-CPU: Sources/Printing/CUPSManager.swift (optionDetails, decodePPDString)
  • ICCery v2 Mac: Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsParsers.swift (ppdChoiceLabels)
  • ICCery v2 Mac: Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsService.swift (loadPPD uses UTF-8)
  • Test: Tests/ICCeryCoreTests/CupsParserTests.swift (testPpdLabels)
## Summary The media type list for Canon Pro9500 Mark II shows Thai characters instead of English: - `กระดาษธรรมดา` instead of "Plain Paper" - `กระดาษภาพถ่ายมันเงา+ II` instead of "Photo Paper Plus Glossy II" - `กระดาษกึ่งเงา ภาพถ่ายพิเศษ` instead of "Photo Paper Plus Semi-gloss" - `กระดาษ Fine Art อื่น` instead of "Other Fine Art Paper" - `แผ่นกระดาษแข็ง` instead of "Board Paper" - `ดิสก์ที่พิมพ์ได้` instead of "Printable disc" - `ดิสก์ที่พิมพ์ได้ (กันซึม)` instead of "Printable disc (bleed-proof)" The correct list (as seen in TargetPrint) should be the full `CNIJMediaType` choice set in English — 18 types are enumerated in the acceptance criteria below; the true count is verified against the real PPD (do not hardcode). ## Root Cause Analysis ### PPD Locale Translation Order The Canon PPD contains locale-specific translations for `CNIJMediaType`: - Line 278: `*CNIJMediaType 0/Plain Paper: ""` (base, no locale prefix) - Lines 676-5104: Various locales (ja, nl, fr, de, it, es, zh_CN, pl, ru, pt, zh_TW, ko, da, no, sv, fi, cs, ar, tr) - **Line 5350: `*th.CNIJMediaType 0/กระดาษธรรมดา: ""` (Thai — LAST)** ### The Bug in `CupsParsers.ppdChoiceLabels()` (Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsParsers.swift:177-211) ```swift public static func ppdChoiceLabels(_ ppd: String, key: String) -> [String: String] { var map: [String: String] = [:] for rawLine in ppd.split(separator: "\n") { // ... locale stripping logic ... if !id.isEmpty { map[id] = human.isEmpty ? id : human } } return map } ``` **Problem:** The function processes lines sequentially and uses a dictionary `map[id] = human`. Since Thai translation comes LAST (line 5350), it overwrites the English label. **Locale stripping logic exists but doesn't prioritize:** ```swift if let dot = line.firstIndex(of: ".") { let prefix = line[..<dot] let looksLikeLocale = (2...5).contains(prefix.count) && prefix.allSatisfy { $0.isLetter || $0 == "_" } && (prefix.count == 2 || prefix.contains("_")) // ... } ``` For `th.CNIJMediaType`, prefix is `th` (2 chars, all letters) → `looksLikeLocale = true`. The stripping works, but the function doesn't skip locale-qualified lines — it just processes them and the last write wins. ### PPD Evidence (Canon Pro9500 Mark II) ``` Line 278: *CNIJMediaType 0/Plain Paper: "" Line 5350: *th.CNIJMediaType 0/กระดาษธรรมดา: "" All media types affected (Thai lines 5350-5600+) ``` ### Comparison with ICCery-CPU ICCery-CPU's `CUPSManager.optionDetails()` (Sources/Printing/CUPSManager.swift:312-341) also parses PPD choice lines but: - Uses `decodePPDString()` to handle PPD hex escapes (`<2F>` → `/`, etc.) - Reads PPD as ISO-Latin-1 (not UTF-8) — **do not copy verbatim**: this Canon PPD's Thai labels are UTF-8, and a blanket ISO-Latin-1 read produces mojibake that breaks the very labels under test - Doesn't explicitly handle locale priority either ## Proposed Fix Options ### Option 1: Deterministic locale precedence (Recommended) Replace last-write-wins in `ppdChoiceLabels` with deterministic precedence — **unqualified `*Key` > `en_US` > `en` > first-qualified-seen**: - A locale-qualified line never overwrites an unqualified one - A key whose only labels are locale-qualified still gets its first-seen qualified label - Implement via two passes or tracked precedence; plain "first write wins" (`map[id] == nil` guard) is fragile — it silently depends on base lines preceding locale lines in file order ### Option 2: Add Locale Priority Parameter Accept preferred locale (e.g., `en`, `en_US`) and prioritize those translations — subsumed by Option 1's fixed precedence unless configurability is needed. ### Option 3: PPD Encoding + Hex Escape Decoding - `loadPPD` reads **UTF-8 first, ISO-Latin-1 only on decode failure** (CupsService.swift:168-171 is currently UTF-8-only via `try?`) — a blanket ISO-Latin-1 read would garble this PPD's UTF-8 Thai labels - Add `<XX>` hex-escape decoding (`<2F>` → `/`, `<20>` → space) in the label parser — needed for proper PPD parsing anyway ## Acceptance Criteria - [ ] Canon media types display in English (or user's system locale) - [ ] Every `CNIJMediaType` choice from `lpoptions -l` has a non-Thai label — the 18 known labels below must all appear correctly; hardware verification confirms the true count against the real PPD: - Plain Paper - Photo Paper Plus Glossy II - Photo Paper Pro Platinum N - Photo Paper Pro Platinum - Photo Paper Pro Luster - Photo Paper Plus Semi-gloss - Matte Photo Paper - Fine Art "Photo Rag" - Fine Art "Museum Etching" - Photo Paper Pro Premium Matte - Fine Art Premium Matte - Other Fine Art Paper - Canvas - Board Paper - Ink Jet Hagaki - Hagaki - Printable disc - Printable disc (bleed-proof) - [ ] No Thai (or other non-preferred locale) characters appear unless explicitly selected ## Dependencies - **Depends on:** #183 (Stage 2 media type picker must exist before this bug fix can show correct labels) - **Blocks:** #186 (printer properties dialog needs correct media type labels to pre-select) ## References - ICCery-CPU: `Sources/Printing/CUPSManager.swift` (optionDetails, decodePPDString) - ICCery v2 Mac: `Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsParsers.swift` (ppdChoiceLabels) - ICCery v2 Mac: `Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsService.swift` (loadPPD uses UTF-8) - Test: `Tests/ICCeryCoreTests/CupsParserTests.swift` (testPpdLabels)
gronod added the Kind/Bug
Priority
Medium
3
Project/ICCery-v2Bug/Backend
labels 2026-09-15 10:04:06 +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 says "19 media types" but the body lists 18 named labels — do not hardcode 19. Assert "every CNIJMediaType choice from lpoptions -l has a non-Thai label" + spot-check the 18 named; the hardware gate verifies the true count against the real PPD.
  2. Option 3 hazard: the Canon PPD's Thai labels are UTF-8 — a blanket ISO-Latin-1 read produces mojibake and breaks the very labels under test. Correct plan: loadPPD reads UTF-8 first, ISO-Latin-1 only on decode failure (CupsService.swift:168–171 currently UTF-8-only via try?); add <XX> hex-escape decoding (<2F>/, <20>→space) in the label parser.
  3. Option 1 hardened: "first write wins" depends on base lines preceding locale lines — fragile. Deterministic precedence: unqualified *Key > en_US > en > first-qualified-seen — a locale-qualified line never overwrites an unqualified one, and a qualified-only key still gets its first-seen qualified label.
Planning errata (verified on develop@736fd08): 1. AC says "**19** media types" but the body lists **18** named labels — do not hardcode 19. Assert "every `CNIJMediaType` choice from `lpoptions -l` has a non-Thai label" + spot-check the 18 named; the hardware gate verifies the true count against the real PPD. 2. Option 3 hazard: the Canon PPD's Thai labels are **UTF-8** — a blanket ISO-Latin-1 read produces mojibake and breaks the very labels under test. Correct plan: `loadPPD` reads UTF-8 first, ISO-Latin-1 **only on decode failure** (CupsService.swift:168–171 currently UTF-8-only via `try?`); add `<XX>` hex-escape decoding (`<2F>`→`/`, `<20>`→space) in the label parser. 3. Option 1 hardened: "first write wins" depends on base lines preceding locale lines — fragile. Deterministic precedence: **unqualified `*Key` > `en_US` > `en` > first-qualified-seen** — a locale-qualified line never overwrites an unqualified one, and a qualified-only key still gets its first-seen qualified label.
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.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: gronod/iccery-v2-mac#181