Search code examples
c++winapiprintinggdi

Get print job's color/monochrome setting from its device context


Given a handle to a device context (or information context) for a printer, how can I determine whether printing will be in color or not?

Here's a reduced repro of the situation:

#include <Windows.h>

#include <iostream>
#include <string>
#include <vector>

int main() {
    // Find the default printer.
    DWORD cchName = 0;
    ::GetDefaultPrinterW(nullptr, &cchName);
    std::wstring name(cchName, L'\0');
    ::GetDefaultPrinterW(name.data(), &cchName);

    // Is it a color printer?
    const bool color_printer =
        ::DeviceCapabilitiesW(name.c_str(), nullptr, DC_COLORDEVICE, nullptr,
                              nullptr) != 0;
    if (color_printer) {
        std::cout << "DeviceCapabilities says it's a color printer.\n";
    } else {
        return 0;  // this case is not interesting for us.
    }

    // Open the printer and get its default DEVMODE.
    HANDLE hPrinter = INVALID_HANDLE_VALUE;
    PRINTER_DEFAULTSW defaults = {nullptr, nullptr, PRINTER_ACCESS_USE};
    ::OpenPrinterW(name.data(), &hPrinter, &defaults);
    const auto cbNeeded =
        ::DocumentPropertiesW(NULL, hPrinter, name.data(), nullptr, nullptr,
                              0);
    std::vector<BYTE> buffer(cbNeeded, static_cast<BYTE>(0));
    auto *pdmDefault = reinterpret_cast<DEVMODEW *>(buffer.data());
    ::DocumentPropertiesW(NULL, hPrinter, name.data(), pdmDefault, nullptr,
                          DM_OUT_BUFFER);

    if (pdmDefault->dmFields & DM_COLOR) {
        std::cout << "Default DEVMODE says we can select color/monochrome.\n";
        if (pdmDefault->dmColor == DMCOLOR_COLOR) {
            std::cout << "Default DEVMODE says it's set to color.\n";
        }
    } else {
        std::cout << "DEVMODE cannot select between color and mono.\n";
        return 0;
    }

    std::cout << "Making a monochrome DEVMODE.\n";
    std::vector<byte> bufferMono = buffer;
    auto *pdmMono = reinterpret_cast<DEVMODEW *>(bufferMono.data());
    pdmMono->dmColor = DMCOLOR_MONOCHROME;
    // Let the driver validate our monochrome devmode.
    ::DocumentPropertiesW(NULL, hPrinter, name.data(), pdmMono, pdmMono,
                          DM_OUT_BUFFER | DM_IN_BUFFER);


    // With our monochrome DEVMODE, ask again whether it's a color printer.
    const bool will_do_color =
        ::DeviceCapabilities(name.c_str(), nullptr, DC_COLORDEVICE, nullptr,
                             pdmMono) != 0;
    if (will_do_color) {
        std::cout << "With mono DEVMODE, DeviceCapabilities says color!\n";
    }

    // Let's try an IC created with our mono DEVMODE.
    HDC hicPrinter = ::CreateICW(nullptr, name.c_str(), nullptr, pdmMono);
    const auto bppIC = ::GetDeviceCaps(hicPrinter, BITSPIXEL);
    std::cout << "GetDeviceCaps on IC says " << bppIC << " bits per pixel.\n";
    ::DeleteDC(hicPrinter);

    // How about with a true DC?
    HDC hdcPrinter = ::CreateDCW(nullptr, name.c_str(), nullptr, pdmMono);
    const auto bppDC = ::GetDeviceCaps(hdcPrinter, BITSPIXEL);
    std::cout << "GetDeviceCaps on DC says " << bppDC << " bits per pixel.\n";

#ifdef PRINT_SAMPLE
    DOCINFOW docinfo = {sizeof(docinfo), L"Test", nullptr, nullptr, 0};
    ::StartDocW(hdcPrinter, &docinfo);
    ::StartPage(hdcPrinter);
    ::SetTextColor(hdcPrinter, RGB(0x00, 0x99, 0x00));
    const std::wstring text = L"This green text should print as gray.";
    const auto cchText = static_cast<int>(text.size());
    ::TextOutW(hdcPrinter, 300, 300, text.c_str(), cchText);
    ::EndPage(hdcPrinter);
    ::EndDoc(hdcPrinter);
#endif

    ::DeleteDC(hdcPrinter);
    ::ClosePrinter(hPrinter);
    return 0;
}

I haven't found a way to determine whether the device is in monochrome mode from the DC itself. Do I have to keep track of the dmColor field in the DEVMODE?

Edited Updated code sample for clarity and to show that DeviceCapabilities always says whether the printer can print color, regardless of the DEVMODE specified.


Solution

  • The only solution I found was to keep a copy of the DEVMODE used when the device context was created or last reset.

    If the DEVMODE's dmFields bitmask contains DM_COLOR then you can check the dmColor member for DMCOLOR_COLOR or DMCOLOR_MONOCHROME.

    If the DM_COLOR bit is not set, then you can query DeviceCapabilities for DC_COLORDEVICE. If that returns non-zero then the printer is capable of color, and since the DEVMODE isn't forcing it to monochrome, it's a good guess that it's in a color mode. Otherwise, it seems safe to assume the output would be monochrome.

    It's a shame that there doesn't seem to be a query using GetDeviceCaps on the HDC to determine this.