Author SHA1 Message Date
gronod 8d69aab54d ci: fix universal binary build by archiving .app bundles with ditto and robust lipo extraction
Build macOS Packages / Build macOS (Intel) (pull_request) Successful in 34s
Build macOS Packages / Build macOS (Apple Silicon) (pull_request) Successful in 35s
Build macOS Packages / Build macOS (Universal) (pull_request) Successful in 34s
2026-09-07 23:52:03 +01:00
gronod 474888541b feat: add hardware print quality option supporting Canon 5-notch Custom slider and Epson modes (Closes #5)
Build macOS Packages / Build macOS (Intel) (pull_request) Successful in 32s
Build macOS Packages / Build macOS (Apple Silicon) (pull_request) Successful in 37s
Build macOS Packages / Build macOS (Universal) (pull_request) Failing after 10s
2026-09-07 23:37:01 +01:00
8 changed files with 350 additions and 11 deletions
+42 -9
View File
@@ -135,11 +135,18 @@ jobs:
./Scripts/package_release.sh "${{ matrix.platform.arch }}" "${PREFIX}"
(cd build/release-assets && shasum -a 256 "${PREFIX}.app.zip" "${PREFIX}.dmg" | tee "${PREFIX}.SHA256.txt")
- name: Archive ${{ matrix.platform.arch }} .app (for universal lipo)
working-directory: ${{ env.PROJECT_ROOT }}
run: |
ditto -c -k --keepParent \
"build/apps/${{ matrix.platform.arch }}/TargetPrint.app" \
"build/apps/${{ matrix.platform.arch }}/TargetPrint.app.zip"
- name: Upload ${{ matrix.platform.arch }} .app (for universal lipo)
uses: actions/upload-artifact@v3
with:
name: app-${{ matrix.platform.arch }}
path: ${{ env.PROJECT_ROOT }}/build/apps/${{ matrix.platform.arch }}/TargetPrint.app
path: ${{ env.PROJECT_ROOT }}/build/apps/${{ matrix.platform.arch }}/TargetPrint.app.zip
- name: Upload ${{ matrix.platform.arch }} packages
uses: actions/upload-artifact@v3
@@ -148,7 +155,7 @@ jobs:
path: ${{ env.PROJECT_ROOT }}/build/release-assets/${{ env.PREFIX }}.*
- name: Upload Release Assets to Gitea
if: ${{ startsWith(github.ref, 'refs/tags/') && !cancelled() }}
if: ${{ startsWith(github.ref, 'refs/tags/') && success() }}
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SERVER_URL: ${{ github.server_url }}
@@ -241,20 +248,46 @@ jobs:
MACOS_CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
run: |
export MACOS_CODESIGN_IDENTITY="${MACOS_CODESIGN_IDENTITY:--}"
# upload-artifact may nest as TargetPrint.app/ or as Contents/ at the root
for arch in x86_64 arm64; do
base="build/apps/${arch}"
if [ ! -x "${base}/TargetPrint.app/Contents/MacOS/TargetPrint" ] \
&& [ -x "${base}/Contents/MacOS/TargetPrint" ]; then
echo "==> Preparing ${arch} in ${base}"
# 1. If downloaded as zip (from ditto -c -k), unpack it cleanly
ZIP_FILE="$(find "${base}" -maxdepth 2 -name "*.zip" 2>/dev/null | head -n 1 || true)"
if [ -n "${ZIP_FILE}" ] && [ -f "${ZIP_FILE}" ]; then
echo "Extracting bundle archive: ${ZIP_FILE}"
ditto -x -k "${ZIP_FILE}" "${base}"
rm -f "${ZIP_FILE}"
fi
# 2. If uncompressed bundle contents were downloaded without TargetPrint.app folder
if [ ! -d "${base}/TargetPrint.app" ] && [ -d "${base}/Contents" ]; then
echo "Reconstructing TargetPrint.app from root Contents/ in ${base}..."
mkdir -p "${base}/TargetPrint.app"
# Move bundle contents into TargetPrint.app
shopt -s dotglob
mv "${base}/Contents" "${base}/PkgInfo" "${base}/Resources" "${base}/Info.plist" \
"${base}/_CodeSignature" "${base}/TargetPrint.app/" 2>/dev/null || true
shopt -u dotglob
fi
test -x "${base}/TargetPrint.app/Contents/MacOS/TargetPrint"
lipo -info "${base}/TargetPrint.app/Contents/MacOS/TargetPrint"
# 3. If nested inside a subfolder (e.g. app-x86_64/TargetPrint.app)
NESTED_APP="$(find "${base}" -mindepth 2 -maxdepth 3 -name "TargetPrint.app" -type d 2>/dev/null | head -n 1 || true)"
if [ -n "${NESTED_APP}" ] && [ "${NESTED_APP}" != "${base}/TargetPrint.app" ]; then
echo "Moving nested app from ${NESTED_APP} to ${base}/TargetPrint.app..."
rm -rf "${base}/TargetPrint.app"
mv "${NESTED_APP}" "${base}/TargetPrint.app"
fi
BIN="${base}/TargetPrint.app/Contents/MacOS/TargetPrint"
if [ ! -f "${BIN}" ]; then
echo "ERROR: Missing binary at ${BIN}" >&2
ls -laR "${base}" >&2
exit 1
fi
chmod +x "${BIN}"
echo "==> Verified binary for ${arch}:"
ls -l "${BIN}"
lipo -info "${BIN}"
done
./Scripts/make_universal.sh
lipo -info build/apps/universal/TargetPrint.app/Contents/MacOS/TargetPrint
@@ -288,7 +321,7 @@ jobs:
path: ${{ env.PROJECT_ROOT }}/build/release-assets/vendor-iccery
- name: Upload Release Assets to Gitea
if: ${{ startsWith(github.ref, 'refs/tags/') && !cancelled() }}
if: ${{ startsWith(github.ref, 'refs/tags/') && success() }}
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SERVER_URL: ${{ github.server_url }}
@@ -103,6 +103,7 @@ final class PreviewWindowController: NSWindowController {
engine.mediaSize = parsed.printSettings.mediaSize
engine.mediaType = parsed.printSettings.mediaType
engine.paperSource = parsed.printSettings.paperSource
engine.printQuality = parsed.printSettings.printQuality
engine.resolution = parsed.printSettings.resolution
engine.scaling = parsed.printSettings.scaling
engine.centered = parsed.printSettings.centered
+25
View File
@@ -8,9 +8,32 @@ struct PrintSettings: Equatable {
var mediaType: String?
var paperSource: String?
var resolution: String?
var printQuality: String?
var scaling: Double
var centered: Bool
var forceUnmanagedColor: Bool
init(
printerName: String,
mediaSize: String,
mediaType: String? = nil,
paperSource: String? = nil,
resolution: String? = nil,
printQuality: String? = nil,
scaling: Double,
centered: Bool,
forceUnmanagedColor: Bool
) {
self.printerName = printerName
self.mediaSize = mediaSize
self.mediaType = mediaType
self.paperSource = paperSource
self.resolution = resolution
self.printQuality = printQuality
self.scaling = scaling
self.centered = centered
self.forceUnmanagedColor = forceUnmanagedColor
}
}
struct UIPolicy: Equatable {
@@ -117,6 +140,7 @@ enum TargetJobParser {
mediaType: stringValue(obj["mediaType"]),
paperSource: stringValue(obj["paperSource"]),
resolution: stringValue(obj["resolution"]),
printQuality: stringValue(obj["printQuality"]),
scaling: scaling,
centered: centered,
forceUnmanagedColor: force
@@ -144,6 +168,7 @@ enum TargetJobParser {
if let v = job.printSettings.mediaType { ps["mediaType"] = v }
if let v = job.printSettings.paperSource { ps["paperSource"] = v }
if let v = job.printSettings.resolution { ps["resolution"] = v }
if let v = job.printSettings.printQuality { ps["printQuality"] = v }
let obj: [String: Any] = [
"version": 1,
"jobTitle": job.jobTitle,
+25 -1
View File
@@ -24,7 +24,10 @@ struct PrinterQueue: Equatable {
var trays: [String]
var trayChoices: [PPDChoice]
var trayKeyword: String?
var qualityChoices: [PPDChoice]
var qualityKeyword: String?
var resolutions: [String]
var resolutionChoices: [PPDChoice]
var ppdText: String
init(
@@ -42,7 +45,10 @@ struct PrinterQueue: Equatable {
trays: [String],
trayChoices: [PPDChoice] = [],
trayKeyword: String? = nil,
qualityChoices: [PPDChoice] = [],
qualityKeyword: String? = nil,
resolutions: [String],
resolutionChoices: [PPDChoice] = [],
ppdText: String
) {
self.name = name
@@ -59,7 +65,10 @@ struct PrinterQueue: Equatable {
self.trays = trays
self.trayChoices = trayChoices
self.trayKeyword = trayKeyword
self.qualityChoices = qualityChoices
self.qualityKeyword = qualityKeyword
self.resolutions = resolutions
self.resolutionChoices = resolutionChoices
self.ppdText = ppdText
}
}
@@ -112,7 +121,10 @@ enum CUPSManager {
trays: options.trays,
trayChoices: options.trayChoices,
trayKeyword: options.trayKeyword,
qualityChoices: options.qualityChoices,
qualityKeyword: options.qualityKeyword,
resolutions: options.resolutions,
resolutionChoices: options.resolutionChoices,
ppdText: ppdText
))
}
@@ -146,7 +158,10 @@ enum CUPSManager {
var trays: [String] = []
var trayChoices: [PPDChoice] = []
var trayKeyword: String? = nil
var qualityChoices: [PPDChoice] = []
var qualityKeyword: String? = nil
var resolutions: [String] = []
var resolutionChoices: [PPDChoice] = []
var colorChoices: [(keyword: String, choice: String)] = []
}
@@ -172,7 +187,16 @@ enum CUPSManager {
out.trayChoices = trays.choices
out.trays = trays.choices.map { $0.title }
out.resolutions = optionChoices(in: ppdText, keyword: "Resolution")
let quality = discoverOptionChoices(
in: ppdText,
candidateKeywords: ["CNIJPrintQuality", "EPIJ_Qual", "cupsPrintQuality", "PrintQuality", "CNIJPrintMode2", "Quality", "StpQuality"],
openUITargets: ["print quality", "quality"]
)
out.qualityKeyword = quality.keyword
out.qualityChoices = quality.choices
out.resolutionChoices = optionDetails(in: ppdText, keyword: "Resolution")
out.resolutions = out.resolutionChoices.map { $0.name }
// Walk every *OpenUI for Colour/Color keys (SPEC §10.3 generic).
let lines = ppdText.split(whereSeparator: \.isNewline)
var currentKeyword: String?
+42
View File
@@ -10,6 +10,7 @@ final class PrintEngine {
var mediaSize: String
var mediaType: String?
var paperSource: String?
var printQuality: String?
var resolution: String?
var scaling: Double
var centered: Bool
@@ -24,6 +25,7 @@ final class PrintEngine {
mediaSize = job.printSettings.mediaSize
mediaType = job.printSettings.mediaType
paperSource = job.printSettings.paperSource
printQuality = job.printSettings.printQuality
resolution = job.printSettings.resolution
scaling = job.printSettings.scaling
centered = job.printSettings.centered
@@ -39,6 +41,7 @@ final class PrintEngine {
mediaSize = "A4"
mediaType = nil
paperSource = nil
printQuality = nil
resolution = nil
scaling = 1.0
centered = true
@@ -48,6 +51,7 @@ final class PrintEngine {
allowBasicDriverChanges = true
}
/// Configures `NSPrintInfo` for `NSPrintOperation` (SPEC §4.4, §10.3).
func makePrintInfo() throws -> NSPrintInfo {
let info = NSPrintInfo.shared.copy() as! NSPrintInfo
info.jobDisposition = .spool
@@ -106,6 +110,44 @@ final class PrintEngine {
extras[key] = paperSource
}
}
if let printQuality = printQuality {
let key = queue?.qualityKeyword ?? "PrintQuality"
let resolvedCode: String
if let match = queue?.qualityChoices.first(where: { $0.name == printQuality || $0.title.caseInsensitiveCompare(printQuality) == .orderedSame }) {
resolvedCode = match.name
} else {
resolvedCode = printQuality
}
if key == "CNIJPrintQuality" {
switch resolvedCode {
case "0": // Super Fine (notch 5 — Custom mode)
extras["CNIJPrintQuality"] = "0"
extras["CNIJPrintMode2"] = "5"
extras["CNIJPQualitySlider"] = "5"
case "5": // Fine (notch 4 — High preset)
extras["CNIJPrintQuality"] = "5"
extras["CNIJPrintMode2"] = "1"
extras["CNIJPQualitySlider"] = "4"
case "10": // Normal(Fine) (notch 3 — Standard preset)
extras["CNIJPrintQuality"] = "10"
extras["CNIJPrintMode2"] = "2"
extras["CNIJPQualitySlider"] = "3"
case "15": // Normal(Fast) (notch 2)
extras["CNIJPrintQuality"] = "15"
extras["CNIJPrintMode2"] = "5"
extras["CNIJPQualitySlider"] = "2"
case "20": // Fast (notch 1 — Fast preset)
extras["CNIJPrintQuality"] = "20"
extras["CNIJPrintMode2"] = "3"
extras["CNIJPQualitySlider"] = "1"
default:
extras["CNIJPrintQuality"] = resolvedCode
}
} else if resolvedCode.lowercased() != "auto" || key == "cupsPrintQuality" {
extras[key] = resolvedCode
}
}
if let resolution = resolution { extras["Resolution"] = resolution }
if extras.isEmpty { return }
CUPSManager.applyVendorBypass(extras, to: info)
+12 -1
View File
@@ -22,6 +22,7 @@ final class InspectorView: NSView {
let mediaPop = NSPopUpButton()
let typePop = NSPopUpButton()
let trayPop = NSPopUpButton()
let qualityPop = NSPopUpButton()
let resolutionPop = NSPopUpButton()
let scaleSlider = NSSlider()
let scaleLabel = NSTextField(labelWithString: "100%")
@@ -46,6 +47,7 @@ final class InspectorView: NSView {
mediaPop.target = self; mediaPop.action = #selector(changed)
typePop.target = self; typePop.action = #selector(changed)
trayPop.target = self; trayPop.action = #selector(changed)
qualityPop.target = self; qualityPop.action = #selector(changed)
resolutionPop.target = self; resolutionPop.action = #selector(changed)
scaleSlider.minValue = 0.5
scaleSlider.maxValue = 1.5
@@ -79,6 +81,7 @@ final class InspectorView: NSView {
section("Media size", mediaPop),
section("Media type", typePop),
section("Tray", trayPop),
section("Print quality", qualityPop),
section("Resolution", resolutionPop),
section("Scale", scaleRow()),
centeredCheck,
@@ -101,6 +104,7 @@ final class InspectorView: NSView {
mediaPop.widthAnchor.constraint(equalTo: stack.widthAnchor),
typePop.widthAnchor.constraint(equalTo: stack.widthAnchor),
trayPop.widthAnchor.constraint(equalTo: stack.widthAnchor),
qualityPop.widthAnchor.constraint(equalTo: stack.widthAnchor),
resolutionPop.widthAnchor.constraint(equalTo: stack.widthAnchor),
colorSeg.widthAnchor.constraint(equalTo: stack.widthAnchor),
])
@@ -126,6 +130,7 @@ final class InspectorView: NSView {
select(mediaPop, engine.mediaSize)
if let t = engine.mediaType { selectChoice(typePop, t) }
if let t = engine.paperSource { selectChoice(trayPop, t) }
if let q = engine.printQuality { selectChoice(qualityPop, q) }
if let r = engine.resolution { select(resolutionPop, r) }
scaleSlider.doubleValue = engine.scaling
scaleLabel.stringValue = String(format: "%.0f%%", engine.scaling * 100)
@@ -145,6 +150,7 @@ final class InspectorView: NSView {
engine.mediaSize = mediaPop.titleOfSelectedItem ?? engine.mediaSize
engine.mediaType = (typePop.selectedItem?.representedObject as? String) ?? emptyToNil(typePop.titleOfSelectedItem)
engine.paperSource = (trayPop.selectedItem?.representedObject as? String) ?? emptyToNil(trayPop.titleOfSelectedItem)
engine.printQuality = (qualityPop.selectedItem?.representedObject as? String) ?? emptyToNil(qualityPop.titleOfSelectedItem)
engine.resolution = emptyToNil(resolutionPop.titleOfSelectedItem)
engine.scaling = scaleSlider.doubleValue
engine.centered = centeredCheck.state == .on
@@ -161,6 +167,7 @@ final class InspectorView: NSView {
mediaPop.isEnabled = mediaEnabled
typePop.isEnabled = mediaEnabled
trayPop.isEnabled = mediaEnabled
qualityPop.isEnabled = mediaEnabled && (selectedQueue?.qualityChoices.isEmpty == false)
resolutionPop.isEnabled = !jobLocked
landscapeCheck.isEnabled = mediaEnabled
scaleSlider.isEnabled = !jobLocked
@@ -188,6 +195,9 @@ final class InspectorView: NSView {
refillChoices(typePop, mediaChoices)
let trayChoices = (q?.trayChoices.isEmpty == false) ? q!.trayChoices : [PPDChoice(name: "Auto", title: "Auto")]
refillChoices(trayPop, trayChoices)
let qualityChoices = (q?.qualityChoices.isEmpty == false) ? q!.qualityChoices : [PPDChoice(name: "Auto", title: "Auto")]
refillChoices(qualityPop, qualityChoices)
qualityPop.isEnabled = allowBasicDriverChanges && !jobLocked && (q?.qualityChoices.isEmpty == false)
refill(resolutionPop, q?.resolutions ?? [])
updateAirPrint()
}
@@ -215,7 +225,8 @@ final class InspectorView: NSView {
pop.addItem(withTitle: choice.title)
pop.lastItem?.representedObject = choice.name
}
if let current = currentChoice {
if let current = currentChoice,
choices.contains(where: { $0.name == current || $0.title == current }) {
selectChoice(pop, current)
}
}
+100
View File
@@ -220,6 +220,106 @@ final class PPDOptionsTests: XCTestCase {
let canonSettings = canonPrintInfo.dictionary()[settingsKey] as? NSDictionary
XCTAssertEqual(canonSettings?["CNIJMediaType"] as? String, "92")
XCTAssertEqual(canonSettings?["CNIJMediaSupply"] as? String, "7")
// Canon 5-notch Custom quality tests
let canonQualityPPD = """
*OpenUI *CNIJPrintQuality/Print Quality: PickOne
*CNIJPrintQuality 0/Super Fine: ""
*CNIJPrintQuality 5/Fine: ""
*CNIJPrintQuality 10/Normal(Fine): ""
*CNIJPrintQuality 15/Normal(Fast): ""
*CNIJPrintQuality 20/Fast: ""
*CloseUI: *CNIJPrintQuality
"""
let canonQOpts = CUPSManager.discoverPPDOptions(canonQualityPPD)
XCTAssertEqual(canonQOpts.qualityKeyword, "CNIJPrintQuality")
XCTAssertEqual(canonQOpts.qualityChoices, [
PPDChoice(name: "0", title: "Super Fine"),
PPDChoice(name: "5", title: "Fine"),
PPDChoice(name: "10", title: "Normal(Fine)"),
PPDChoice(name: "15", title: "Normal(Fast)"),
PPDChoice(name: "20", title: "Fast")
])
let canonQQueue = PrinterQueue(
name: "TestCanonQ",
displayName: "Test Canon Quality",
uri: "usb://canon",
make: "Canon",
model: "Pro9500",
isAirPrint: false,
mediaSizes: ["A4"],
mediaTypes: [],
mediaTypeChoices: [],
mediaTypeKeyword: nil,
trays: [],
trayChoices: [],
trayKeyword: nil,
qualityChoices: canonQOpts.qualityChoices,
qualityKeyword: canonQOpts.qualityKeyword,
resolutions: [],
ppdText: canonQualityPPD
)
// Super Fine: custom mode 5, slider notch 5, quality 0
let superFineEngine = PrintEngine()
superFineEngine.printQuality = "Super Fine"
let superFineInfo = NSPrintInfo()
superFineEngine.applyOptionalPPDKeys(to: superFineInfo, queue: canonQQueue)
let superFineSettings = superFineInfo.dictionary()[settingsKey] as? NSDictionary
XCTAssertEqual(superFineSettings?["CNIJPrintQuality"] as? String, "0")
XCTAssertEqual(superFineSettings?["CNIJPrintMode2"] as? String, "5")
XCTAssertEqual(superFineSettings?["CNIJPQualitySlider"] as? String, "5")
// Fine: preset mode 1, slider notch 4, quality 5
let fineEngine = PrintEngine()
fineEngine.printQuality = "Fine"
let fineInfo = NSPrintInfo()
fineEngine.applyOptionalPPDKeys(to: fineInfo, queue: canonQQueue)
let fineSettings = fineInfo.dictionary()[settingsKey] as? NSDictionary
XCTAssertEqual(fineSettings?["CNIJPrintQuality"] as? String, "5")
XCTAssertEqual(fineSettings?["CNIJPrintMode2"] as? String, "1")
XCTAssertEqual(fineSettings?["CNIJPQualitySlider"] as? String, "4")
// Epson quality test
let epsonQualityPPD = """
*OpenUI *EPIJ_Qual/Quality: PickOne
*EPIJ_Qual 307/Best Quality: ""
*EPIJ_Qual 305/Quality: ""
*EPIJ_Qual 304/Fine: ""
*CloseUI: *EPIJ_Qual
"""
let epsonQOpts = CUPSManager.discoverPPDOptions(epsonQualityPPD)
XCTAssertEqual(epsonQOpts.qualityKeyword, "EPIJ_Qual")
XCTAssertEqual(epsonQOpts.qualityChoices.count, 3)
XCTAssertEqual(epsonQOpts.qualityChoices.first?.name, "307")
XCTAssertEqual(epsonQOpts.qualityChoices.first?.title, "Best Quality")
let epsonQQueue = PrinterQueue(
name: "TestEpsonQ",
displayName: "Test Epson Quality",
uri: "usb://epson",
make: "Epson",
model: "XP-55",
isAirPrint: false,
mediaSizes: ["A4"],
mediaTypes: [],
mediaTypeChoices: [],
mediaTypeKeyword: nil,
trays: [],
trayChoices: [],
trayKeyword: nil,
qualityChoices: epsonQOpts.qualityChoices,
qualityKeyword: epsonQOpts.qualityKeyword,
resolutions: [],
ppdText: epsonQualityPPD
)
let epsonQEngine = PrintEngine()
epsonQEngine.printQuality = "Best Quality"
let epsonQInfo = NSPrintInfo()
epsonQEngine.applyOptionalPPDKeys(to: epsonQInfo, queue: epsonQQueue)
let epsonQSettings = epsonQInfo.dictionary()[settingsKey] as? NSDictionary
XCTAssertEqual(epsonQSettings?["EPIJ_Qual"] as? String, "307")
}
}
#endif
+103
View File
@@ -199,6 +199,106 @@ struct TestRunner {
expectEqual(canonSettings?["CNIJMediaType"] as? String, Optional("92"))
expectEqual(canonSettings?["CNIJMediaSupply"] as? String, Optional("7"))
// Canon 5-notch Custom quality tests
let canonQualityPPD = """
*OpenUI *CNIJPrintQuality/Print Quality: PickOne
*CNIJPrintQuality 0/Super Fine: ""
*CNIJPrintQuality 5/Fine: ""
*CNIJPrintQuality 10/Normal(Fine): ""
*CNIJPrintQuality 15/Normal(Fast): ""
*CNIJPrintQuality 20/Fast: ""
*CloseUI: *CNIJPrintQuality
"""
let canonQOpts = CUPSManager.discoverPPDOptions(canonQualityPPD)
expectEqual(canonQOpts.qualityKeyword, "CNIJPrintQuality")
expectEqual(canonQOpts.qualityChoices, [
PPDChoice(name: "0", title: "Super Fine"),
PPDChoice(name: "5", title: "Fine"),
PPDChoice(name: "10", title: "Normal(Fine)"),
PPDChoice(name: "15", title: "Normal(Fast)"),
PPDChoice(name: "20", title: "Fast")
])
let canonQQueue = PrinterQueue(
name: "TestCanonQ",
displayName: "Test Canon Quality",
uri: "usb://canon",
make: "Canon",
model: "Pro9500",
isAirPrint: false,
mediaSizes: ["A4"],
mediaTypes: [],
mediaTypeChoices: [],
mediaTypeKeyword: nil,
trays: [],
trayChoices: [],
trayKeyword: nil,
qualityChoices: canonQOpts.qualityChoices,
qualityKeyword: canonQOpts.qualityKeyword,
resolutions: [],
ppdText: canonQualityPPD
)
// Super Fine: custom mode 5, slider notch 5, quality 0
let superFineEngine = PrintEngine()
superFineEngine.printQuality = "Super Fine"
let superFineInfo = NSPrintInfo()
superFineEngine.applyOptionalPPDKeys(to: superFineInfo, queue: canonQQueue)
let superFineSettings = superFineInfo.dictionary()[settingsKey] as? NSDictionary
expectEqual(superFineSettings?["CNIJPrintQuality"] as? String, Optional("0"))
expectEqual(superFineSettings?["CNIJPrintMode2"] as? String, Optional("5"))
expectEqual(superFineSettings?["CNIJPQualitySlider"] as? String, Optional("5"))
// Fine: preset mode 1, slider notch 4, quality 5
let fineEngine = PrintEngine()
fineEngine.printQuality = "Fine"
let fineInfo = NSPrintInfo()
fineEngine.applyOptionalPPDKeys(to: fineInfo, queue: canonQQueue)
let fineSettings = fineInfo.dictionary()[settingsKey] as? NSDictionary
expectEqual(fineSettings?["CNIJPrintQuality"] as? String, Optional("5"))
expectEqual(fineSettings?["CNIJPrintMode2"] as? String, Optional("1"))
expectEqual(fineSettings?["CNIJPQualitySlider"] as? String, Optional("4"))
// Epson quality test
let epsonQualityPPD = """
*OpenUI *EPIJ_Qual/Quality: PickOne
*EPIJ_Qual 307/Best Quality: ""
*EPIJ_Qual 305/Quality: ""
*EPIJ_Qual 304/Fine: ""
*CloseUI: *EPIJ_Qual
"""
let epsonQOpts = CUPSManager.discoverPPDOptions(epsonQualityPPD)
expectEqual(epsonQOpts.qualityKeyword, "EPIJ_Qual")
expectEqual(epsonQOpts.qualityChoices.count, 3)
expectEqual(epsonQOpts.qualityChoices.first?.name, "307")
expectEqual(epsonQOpts.qualityChoices.first?.title, "Best Quality")
let epsonQQueue = PrinterQueue(
name: "TestEpsonQ",
displayName: "Test Epson Quality",
uri: "usb://epson",
make: "Epson",
model: "XP-55",
isAirPrint: false,
mediaSizes: ["A4"],
mediaTypes: [],
mediaTypeChoices: [],
mediaTypeKeyword: nil,
trays: [],
trayChoices: [],
trayKeyword: nil,
qualityChoices: epsonQOpts.qualityChoices,
qualityKeyword: epsonQOpts.qualityKeyword,
resolutions: [],
ppdText: epsonQualityPPD
)
let epsonQEngine = PrintEngine()
epsonQEngine.printQuality = "Best Quality"
let epsonQInfo = NSPrintInfo()
epsonQEngine.applyOptionalPPDKeys(to: epsonQInfo, queue: epsonQQueue)
let epsonQSettings = epsonQInfo.dictionary()[settingsKey] as? NSDictionary
expectEqual(epsonQSettings?["EPIJ_Qual"] as? String, Optional("307"))
print(" [PASS] PPDOptionsTests")
// MARK: - 2. AirPrintTests
@@ -249,6 +349,7 @@ struct TestRunner {
"mediaType": "PremiumGlossy",
"paperSource": "Auto",
"resolution": "5760x1440dpi",
"printQuality": "Super Fine",
"scaling": 1.0,
"centered": true,
"forceUnmanagedColor": true
@@ -266,9 +367,11 @@ struct TestRunner {
expectEqual(parsed.jobTitle, "Epson_XP55_IlfordLustre_Target_P1-2")
expectEqual(parsed.files.count, 2)
expectEqual(parsed.printSettings.mediaType, "PremiumGlossy")
expectEqual(parsed.printSettings.printQuality, "Super Fine")
let serialized = try TargetJobParser.serialize(parsed)
let again = try TargetJobParser.parse(data: serialized)
expectEqual(parsed, again)
expectEqual(again.printSettings.printQuality, "Super Fine")
} catch {
fputs("TargetJob parsing error: \(error)\n", stderr)
exit(1)