From 9ccf09f58f2a351b2eac425ac6783f17af799afe Mon Sep 17 00:00:00 2001 From: Deep Shekhar Singh Date: Fri, 13 Nov 2026 18:38:14 +0530 Subject: [PATCH 1/2] ppd-cache.c: Map FastGray to bi-level when the printer supports it ppdConvertOptions() turned the ColorModel choices Gray, FastGray and DeviceGray all into print-color-mode=monochrome. FastGray is the 1-bit mode which ppdCreatePPDFromIPP2() generates for "bi-level", so a job with ColorModel=FastGray (or a PPD defaulting to it) never requested bi-level, even when the printer lists it in print-color-mode-supported. Send "bi-level" for FastGray when the printer supports it and keep "monochrome" otherwise. Gray and DeviceGray are unchanged. Add ppdConvertOptions() checks for this to test_ppd_cache. Fixes #91 --- ppd/ppd-cache.c | 20 ++++++-- ppd/test_ppd_cache.c | 108 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 121 insertions(+), 7 deletions(-) diff --git a/ppd/ppd-cache.c b/ppd/ppd-cache.c index d3b6eb1c..ebcf6bad 100644 --- a/ppd/ppd-cache.c +++ b/ppd/ppd-cache.c @@ -386,6 +386,10 @@ ppdConvertOptions( // prefer it to don't break color/grayscale support for PPDs, either classic // or the ones generated from IPP Get-Printer-Attributes response. // + // FastGray is the 1-bit mode which ppdCreatePPDFromIPP2() generates for + // "bi-level", so map it back to "bi-level" if the printer supports it + // instead of turning it into "monochrome". + // if ((keyword = cupsGetOption("ColorModel", num_options, options)) == NULL) { @@ -399,9 +403,12 @@ ppdConvertOptions( // ColorModel is taken from PPD as its default option. // - if (!strcmp(choice->choice, "Gray") || - !strcmp(choice->choice, "FastGray") || - !strcmp(choice->choice, "DeviceGray")) + if (!strcmp(choice->choice, "FastGray") && + ippContainsString(print_color_mode_sup, "bi-level")) + keyword = "bi-level"; + else if (!strcmp(choice->choice, "Gray") || + !strcmp(choice->choice, "FastGray") || + !strcmp(choice->choice, "DeviceGray")) keyword = "monochrome"; else keyword = "color"; @@ -420,8 +427,11 @@ ppdConvertOptions( // ColorModel found in options... // - if (!strcmp(keyword, "Gray") || !strcmp(keyword, "FastGray") || - !strcmp(keyword, "DeviceGray")) + if (!strcmp(keyword, "FastGray") && + ippContainsString(print_color_mode_sup, "bi-level")) + keyword = "bi-level"; + else if (!strcmp(keyword, "Gray") || !strcmp(keyword, "FastGray") || + !strcmp(keyword, "DeviceGray")) keyword = "monochrome"; else keyword = "color"; diff --git a/ppd/test_ppd_cache.c b/ppd/test_ppd_cache.c index e4ca9f5f..d700d6f4 100644 --- a/ppd/test_ppd_cache.c +++ b/ppd/test_ppd_cache.c @@ -6,7 +6,7 @@ // Licensed under Apache License v2.0. See the file "LICENSE" for more // information. // -// Tests covered (52 assertions across 10 groups): +// Tests covered (56 assertions across 11 groups): // // Group 1 (T01-T07) NULL-pc guard checks — every public function that // accepts a ppd_cache_t* must return NULL/no-crash when @@ -34,6 +34,8 @@ // Group 10 (T47-T52) ppdCacheGetSize()/ppdCacheGetSize2() — PPD name, // PWG name, custom-size in points, custom-size in // inches, bare-"Custom" (no ppd_size → NULL), miss. +// Group 11 (T53-T56) ppdConvertOptions() — ColorModel FastGray/Gray to +// print-color-mode, with and without bi-level support. // // Design: entirely hermetic — the PPD is loaded from a static string via // tmpfile() + ppdOpen() so no external files are required at build or CI @@ -142,7 +144,55 @@ static const char test_ppd_text[] = "*DefaultOutputBin: StandardBin\n" "*OutputBin StandardBin/Standard Bin: \"\"\n" "*OutputBin FaceUp/Face Up: \"\"\n" - "*CloseUI: *OutputBin\n"; + "*CloseUI: *OutputBin\n" + // Color models — FastGray is what ppdCreatePPDFromIPP2() emits for + // "bi-level", used by the ppdConvertOptions() checks in Group 11 + "*OpenUI *ColorModel/Color Mode: PickOne\n" + "*OrderDependency: 50 AnySetup *ColorModel\n" + "*DefaultColorModel: FastGray\n" + "*ColorModel Gray/Grayscale: \"\"\n" + "*ColorModel FastGray/Text: \"\"\n" + "*CloseUI: *ColorModel\n"; + + +// +// 'color_mode_for()' - Run ppdConvertOptions() and return the +// print-color-mode value it sends. +// + +static const char * // O - print-color-mode or "(none)" +color_mode_for( + ppd_file_t *ppd, // I - PPD file + ppd_cache_t *pc, // I - PPD cache + ipp_attribute_t *supported, // I - print-color-mode-supported + const char *color_model) // I - ColorModel option or NULL +{ + static char value[64]; // Returned value + ipp_t *request; // Print-Job request + ipp_attribute_t *attr; // print-color-mode attribute + int num_options = 0; // Number of options + cups_option_t *options = NULL; // Options + + + if (color_model) + num_options = cupsAddOption("ColorModel", color_model, num_options, + &options); + + request = ippNewRequest(IPP_OP_PRINT_JOB); + ppdConvertOptions(request, ppd, pc, NULL, NULL, supported, "user", + "image/pwg-raster", 1, num_options, options); + + if ((attr = ippFindAttribute(request, "print-color-mode", + IPP_TAG_KEYWORD)) != NULL) + snprintf(value, sizeof(value), "%s", ippGetString(attr, 0, NULL)); + else + snprintf(value, sizeof(value), "(none)"); + + ippDelete(request); + cupsFreeOptions(num_options, options); + + return (value); +} // @@ -161,6 +211,17 @@ main(void) const char *val; // Return value from lookup functions pwg_size_t *sz; // Return value from size-record lookups int exact; // Exact-match flag for ppdCacheGetPageSize + ipp_t *attrs; // Printer attributes for Group 11 + ipp_attribute_t *sup_bilevel, // print-color-mode-supported with bi-level + *sup_mono; // print-color-mode-supported without it + static const char * const bilevel_modes[] = + { // Modes of a printer with 1-bit support + "bi-level", "monochrome", "color" + }; + static const char * const mono_modes[] = + { // Modes of a printer without 1-bit support + "monochrome" + }; // ========================================================================= @@ -557,6 +618,49 @@ main(void) testEnd(sz == NULL); + // ========================================================================= + // Group 11: ppdConvertOptions() ColorModel → print-color-mode (T53–T56) + // + // FastGray is the 1-bit mode, so it has to become "bi-level" when the + // printer supports it and "monochrome" otherwise (issue #91). Gray + // stays "monochrome". T56 uses the marked PPD default instead of an + // option. + // ========================================================================= + + attrs = ippNew(); + sup_bilevel = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, + "print-color-mode-supported", + 3, NULL, bilevel_modes); + sup_mono = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, + "print-color-mode-supported", + 1, NULL, mono_modes); + + // T53 — FastGray with bi-level supported. + testBegin("ppdConvertOptions(ColorModel=FastGray) sends \"bi-level\""); + val = color_mode_for(ppd, pc, sup_bilevel, "FastGray"); + testEndMessage(!strcmp(val, "bi-level"), "got \"%s\"", val); + + // T54 — FastGray without bi-level support falls back to monochrome. + testBegin("ppdConvertOptions(ColorModel=FastGray, no bi-level) sends " + "\"monochrome\""); + val = color_mode_for(ppd, pc, sup_mono, "FastGray"); + testEndMessage(!strcmp(val, "monochrome"), "got \"%s\"", val); + + // T55 — Gray is unchanged. + testBegin("ppdConvertOptions(ColorModel=Gray) sends \"monochrome\""); + val = color_mode_for(ppd, pc, sup_bilevel, "Gray"); + testEndMessage(!strcmp(val, "monochrome"), "got \"%s\"", val); + + // T56 — No ColorModel option, PPD default FastGray is marked. + testBegin("ppdConvertOptions(default ColorModel FastGray) sends " + "\"bi-level\""); + ppdMarkDefaults(ppd); + val = color_mode_for(ppd, pc, sup_bilevel, NULL); + testEndMessage(!strcmp(val, "bi-level"), "got \"%s\"", val); + + ippDelete(attrs); + + // ========================================================================= // Cleanup // ========================================================================= From 802625a6e1dab9c806a28f6a3c5cd76a31255715 Mon Sep 17 00:00:00 2001 From: Deep Shekhar Singh Date: Sat, 14 Nov 2026 13:08:08 +0530 Subject: [PATCH 2/2] test_ppd_cache: take the value counts from the arrays Use sizeof() for the print-color-mode-supported value counts instead of hardcoding them, and describe the ColorModel choices together with the other options of the test PPD. --- ppd/test_ppd_cache.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ppd/test_ppd_cache.c b/ppd/test_ppd_cache.c index d700d6f4..7c9f3d51 100644 --- a/ppd/test_ppd_cache.c +++ b/ppd/test_ppd_cache.c @@ -75,6 +75,10 @@ // "StandardBin" → "standard-bin" (CamelCase, two words) // "FaceUp" → "face-up" (CamelCase, two words) // +// ColorModel Gray / FastGray (default FastGray): +// FastGray is the choice ppdCreatePPDFromIPP2() emits for "bi-level"; +// used by the ppdConvertOptions() checks in Group 11. +// static const char test_ppd_text[] = "*PPD-Adobe: \"4.3\"\n" @@ -145,8 +149,7 @@ static const char test_ppd_text[] = "*OutputBin StandardBin/Standard Bin: \"\"\n" "*OutputBin FaceUp/Face Up: \"\"\n" "*CloseUI: *OutputBin\n" - // Color models — FastGray is what ppdCreatePPDFromIPP2() emits for - // "bi-level", used by the ppdConvertOptions() checks in Group 11 + // Color models — exercises the ColorModel mapping in ppdConvertOptions() "*OpenUI *ColorModel/Color Mode: PickOne\n" "*OrderDependency: 50 AnySetup *ColorModel\n" "*DefaultColorModel: FastGray\n" @@ -630,10 +633,13 @@ main(void) attrs = ippNew(); sup_bilevel = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "print-color-mode-supported", - 3, NULL, bilevel_modes); + (int)(sizeof(bilevel_modes) / + sizeof(bilevel_modes[0])), + NULL, bilevel_modes); sup_mono = ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "print-color-mode-supported", - 1, NULL, mono_modes); + (int)(sizeof(mono_modes) / sizeof(mono_modes[0])), + NULL, mono_modes); // T53 — FastGray with bi-level supported. testBegin("ppdConvertOptions(ColorModel=FastGray) sends \"bi-level\"");