jdk
jdk copied to clipboard
8315113: Print request Chromaticity.MONOCHROME attribute does not work on macOS
The fix provides ability to print Black & White pages on macOS.
Cocoa API has PMSetColorMode function but it is marked as deprecated and really does nothing.
There is no replacement; this function was included to facilitate porting legacy applications to macOS,
but it serves no useful purpose.
Dumping NSPrintInfo print settings which were set by the native print dialog on macOS shows that the keys and values used for Black & White printing depend on the used printer type.
For example, the tested
HP Color LaserJet MFP M180n printer uses ColorModel key andGray value, and
HP Ink Tank 110 series uses HPColorMode key and grayscale value.
Printing all NSPrintInfo presets shows that they do not contain key/value pairs for Black&White settings.
This is the code snippet used to print NSPrintInfo presets:
PMPrinter pr;
PMPrintSession printSession = (PMPrintSession)[printInfo PMPrintSession];
OSStatus status = PMSessionGetCurrentPrinter(printSession, &pr);
CFArrayRef presetsList = nil;
status = PMPrinterCopyPresets(pr, &presetsList);
CFIndex arrayCount = CFArrayGetCount(presetsList);
for (CFIndex index = 0; index < arrayCount; index++) {
PMPreset preset = (PMPreset)CFArrayGetValueAtIndex(presetsList, index);
CFStringRef presetName = nil;
if (PMPresetCopyName(preset, &presetName) == noErr && CFStringGetLength(presetName) > 0) {
NSLog(@" presetName: '%@'", presetName);
NSDictionary* dict = nil;
if (PMPresetGetAttributes(preset, (CFDictionaryRef*)(&dict)) == noErr) {
// print preset dict
The idea of the proposed fix is to store printer dependent key/value pairs in the <jdk-home>/conf/printer.properties property file.
The property file has the format:
print-attribute.print-attribute-value.key=value
where print-attribute is the java print attribute, print-attribute-value is the corresponding attribute value, and key and value is the key/value pair used by a specific printer.
For example, for Chromaticity attribute the property file could look like:
Chromaticity.MONOCHROME.ColorModel=Gray
Chromaticity.COLOR.ColorModel=CMYK
Chromaticity.MONOCHROME.HPColorMode=grayscale
Chromaticity.COLOR.HPColorMode=color
where Chromaticity.MONOCHROME key prefix corresponds to Chromaticity.MONOCHOROME print attribute constant with (ColorModel, Gray) and (HPColorMode, grayscale) key/value pairs.
Similarly Chromaticity.COLOR key prefix with (ColorModel, CMYK) and (HPColorMode, color) key/value pairs correspond to Chromaticity.COLOR print attribute.
https://www.openprinting.org/download/PPD/ site has been used to collect monochrome and color constants from ppd files and save them in <jdk-home>/conf/printer.properties file.
For example, for the Brother printer the https://www.openprinting.org/download/PPD/Brother/BR9440_2_GPL.ppd ppd file defines BRPrintQuality option:
*OpenUI *BRPrintQuality/Color/Mono: PickOne
*OrderDependency: 15 AnySetup *BRPrintQuality
*DefaultBRPrintQuality: Auto
*BRPrintQuality Auto/Auto: "
<</BRProcessColor 4 /BRHrc 0>>setpagedevice
<</BRColorAdapt true>>setpagedevice
<</BRRenderMode false>>setpagedevice"
*BRPrintQuality Color/Color: "
<</BRProcessColor 4 /BRHrc 0>>setpagedevice
<</BRColorAdapt false>>setpagedevice
<</BRRenderMode false>>setpagedevice"
*BRPrintQuality Black/Mono: "
<</BRProcessColor 1>>setpagedevice
<</BRColorAdapt false>>setpagedevice
<</BRRenderMode true>>setpagedevice"
...
*CloseUI: *BRPrintQuality
which is listed in the printer.properties file as:
Chromaticity.MONOCHROME.BRPrintQuality=Black
Chromaticity.COLOR.BRPrintQuality=Color
and for the Epson printer the https://www.openprinting.org/download/PPD/Epson/epal2600.ppd ppd file defines EPRendering option:
*OpenUI *EPRendering/Color Mode: PickOne
*% EPRendering must be after EPColorModel
*OrderDependency: 50 AnySetup *EPRendering
*DefaultEPRendering: None
*EPRendering None/Mono: "
<</ProcessColorModel (DeviceGray)
/DeviceRenderingInfo <</Type 29 /ValuesPerColorComponent 256>> >>
setpagedevice"
*End
*% ProcessColorModel is already set in EPColorModel
*EPRendering RGB/Color: "
<< /ProcessColorModel (DeviceRGB) >> setpagedevice
<</DeviceRenderingInfo <</Type 29 /ValuesPerColorComponent 256>> >>
setpagedevice"
...
*CloseUI: *EPRendering
which is listed in the printer.properties file as:
Chromaticity.MONOCHROME.EPRendering=None
Chromaticity.COLOR.EPRendering=RGB
The full list of constants from https://www.openprinting.org/download/PPD/ used in printer.properties is
# Brother
Chromaticity.MONOCHROME.BRPrintQuality=Black
Chromaticity.COLOR.BRPrintQuality=Color
# Dell
Chromaticity.MONOCHROME.DLColorMode=Black
Chromaticity.COLOR.DLColorMode=Color
# Epson
Chromaticity.MONOCHROME.EPRendering=None
Chromaticity.COLOR.EPRendering=RGB
# Gestener
# Kyocera
# Lanier
# NRG
# Ricoh
# Savin
# Utax
Chromaticity.MONOCHROME.ColorModel=Gray
Chromaticity.COLOR.ColorModel=CMYK
# HP
Chromaticity.MONOCHROME.HPColorAsGray=Yes
Chromaticity.COLOR.HPColorAsGray=No
# KONICA_MINOLTA
Chromaticity.MONOCHROME.SelectColor=Grayscale
Chromaticity.COLOR.SelectColor=Color
# Lexmark
Chromaticity.MONOCHROME.ColorMode=FalseM
Chromaticity.COLOR.ColorMode=TrueM
Chromaticity.MONOCHROME.BLW=FalseM
Chromaticity.COLOR.BLW=TrueM
# Oki
Chromaticity.MONOCHROME.OKControl=Gray
Chromaticity.COLOR.OKControl=Auto
# Sharp
Chromaticity.MONOCHROME.ARCMode=CMBW
Chromaticity.COLOR.ARCMode=CMColor
# Xerox
Chromaticity.MONOCHROME.XRXColor=BW
Chromaticity.COLOR.XRXColor=Automatic
Chromaticity.MONOCHROME.XRColorMode=Black
Chromaticity.COLOR.XRColorMode=Color
Chromaticity.MONOCHROME.XRColorModeSetting=Black
Chromaticity.COLOR.XRColorModeSetting=Color
Chromaticity.MONOCHROME.EFColorMode=GRAY
Chromaticity.COLOR.EFColorMode=CMYK
There are printers with the same color attributes and different values.
For example, Kyocera defines ColorModel Color option as CMYK in https://www.openprinting.org/download/PPD/Kyocera/en/Kyocera_CS-C4035E_en.ppd file:
*OpenUI *ColorModel/Color Mode: PickOne
*OrderDependency: 10 AnySetup *ColorModel
*DefaultColorModel: CMYK
*ColorModel CMYK/Color (CMYK): "<< /ProcessColorModel /DeviceCMYK >> setpagedevice"
*ColorModel Gray/Monochrome: "<< /ProcessColorModel /DeviceGray >> setpagedevice"
...
*CloseUI: *ColorModel
and Samsung defines the same ColorModel Color option as Color in https://www.openprinting.org/download/PPD/Samsung/PS/Samsung_C2670_Series.ppd
*OrderDependency: 70 AnySetup *ColorModel
*DefaultColorModel: Color
*ColorModel Gray/Grayscale: "<</ProcessColorModel /DeviceGray>> setpagedevice"
*ColorModel Color/Color: "<</ProcessColorModel /DeviceCMYK>> setpagedevice"
*CloseUI: *ColorModel
The fix has been tested with 2 color printers HP Color LaserJet MFP M180n and HP Ink Tank 110 series.
HP Color LaserJet MFP M180n uses ColorModel key and Gray value for black & white printing.
HP Ink Tank 110 series uses HPColorMode key and grayscale value.
Only HPColorAsGray option with Yes and No values is listed in https://www.openprinting.org/download/PPD/HP files.
For example, https://www.openprinting.org/download/PPD/HP/HP_DesignJet_3500CP_PS3.ppd
*OpenUI *HPColorAsGray/Print Color as Gray: PickOne
*OrderDependency: 5.0 AnySetup *HPColorAsGray
*DefaultHPColorAsGray: No
*HPColorAsGray Yes/Yes: "<< /ProcessColorModel /DeviceGray >> setpagedevice"
*HPColorAsGray No/No: "<< /ProcessColorModel /DeviceCMYK >> setpagedevice"
...
*CloseUI: *HPColorAsGray
Both HPColorAsGray and HPColorMode options are added to the printer.properties file as:
Chromaticity.MONOCHROME.HPColorAsGray=Yes
Chromaticity.COLOR.HPColorAsGray=No
Chromaticity.MONOCHROME.HPColorMode=grayscale
Chromaticity.COLOR.HPColorMode=color
Progress
- [x] Change must not contain extraneous whitespace
- [x] Commit message must refer to an issue
- [x] Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)
- [ ] Change requires a CSR request matching fixVersion 23 to be approved (needs to be created)
Issue
- JDK-8315113: Print request Chromaticity.MONOCHROME attribute does not work on macOS (Bug - P4)
Reviewers
- Erik Joelsson (@erikj79 - Reviewer)
- Magnus Ihse Bursie (@magicus - Reviewer)
Reviewing
Using git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/18195/head:pull/18195
$ git checkout pull/18195
Update a local copy of the PR:
$ git checkout pull/18195
$ git pull https://git.openjdk.org/jdk.git pull/18195/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 18195
View PR using the GUI difftool:
$ git pr show -t 18195
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/18195.diff
Webrev
:wave: Welcome back alexsch! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.
@AlexanderScherbatiy The following labels will be automatically applied to this pull request:
buildclient
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command.
@AlexanderScherbatiy This change is no longer ready for integration - check the PR body for details.
@erikj79 The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).
Build changes look good. However, I think there is some kind of movement trying to remove stuff from the conf directory, rather than adding new files there. @AlanBateman might want to chime in on this.
Hi, as a regular JDK user and a stakeholder to this getting fixed, is there any insight as to where they're moving to?
/Reviewers 2
/Reviewers 4
This needs at least two client Capital R reviewers to approve. And a CSR. But I am really surprised to see a PR rather than a discussion first as it is making a lot of policy decisions and implies we need to support something I am not sure we want to support.
/csr
@prrace has indicated that a compatibility and specification (CSR) request is needed for this pull request.
@AlexanderScherbatiy please create a CSR request for issue JDK-8315113 with the correct fix version. This pull request cannot be integrated until the CSR request is approved.
Yeah this looks like a complete mis-feature. Meaning it is very unlikely we will ever agree to integrate this.
I wonder how this is supposed to be documented ? And who is supposed to maintain this file ? I think this PR should be withdrawn.
But I am really surprised to see a PR rather than a discussion first as it is making a lot of policy decisions and implies we need to support something I am not sure we want to support.
There was an email with the issue discussion on the client-libs-dev alias: https://mail.openjdk.org/pipermail/client-libs-dev/2023-August/014497.html
Setting Chromaticity.MONOCHROME print request attribute on macOS
What is the right way to use different key/values (ColorModel: Gray,
HPColorMode: grayscale, ...) in code to set Black & White settings for
NSPrintInfo?
Could it be a configuration file which contains all color model
key/values for all known printers?
But I am really surprised to see a PR rather than a discussion first as it is making a lot of policy decisions and implies we need to support something I am not sure we want to support.
There was an email with the issue discussion on the client-libs-dev alias: https://mail.openjdk.org/pipermail/client-libs-dev/2023-August/014497.html
Setting Chromaticity.MONOCHROME print request attribute on macOS What is the right way to use different key/values (ColorModel: Gray, HPColorMode: grayscale, ...) in code to set Black & White settings for NSPrintInfo? Could it be a configuration file which contains all color model key/values for all known printers?
No replies to that. Clearly it was not noticed. A problem with all the PR notifications to the mailing lists is that real emails get overlooked. You should have re-sent it.
I don't know what the right answer is for this. Not off the top of my head. I just know that this smells like the Linux font configuration files which were always wrong and/or late as well as being a serious maintenance burden.
Since the user can always select greyscale in the user dialog, I presume you have some requirement to do this printing without user intervention. Perhaps there's no user there (server-side printing), or the app would like to make sure the user always defaults to B&W.
As far as I can tell, this isn't supported via any kind of API on macOS, so it is not a Java problem. You could be printing from an Objective-C app written directly to Cocoa APIs and still have the same problem. So a Java-specific workaround like this seems inappropriate.
I've poked around to help me understand what is going on.
When I do "Print to File" on macOS - meaning use the native printer dialog's "Save as PDF" option, then the generated PDF is always colour even if I select the printer dialog's "Grayscale Printing" option. This isn't just true for Java apps, the same happens if I print a web page from Safari.
And when I query the Chromaticity support for a couple of colour printers - one Canon, one HP, both report they support it but only the value COLOR.
So I conclude the grayscale printing option is something handled by the printer driver at the time it is converted to the printer-specific format and sent to the physical printer and it isn't changing the rendering.
So in general without an API, to get greyscale it requires the end-user to set an option in a print dialog and as mentioned above, what you are trying o enable isn't possible even if you wrote the app in Objective-C or Swift directly as a macOS native app.
But what works for me to get greyscale by default is to set that up as the default for the print queue. It is easy to add a printer twice - with two queues for it. One has the default set to greyscale, the other to colour. Then the app just needs to select the required queue. This is clearly more site-specific than having the app just specify MONOCHROME but is a lot lower cost all round.
I don't understand why PMSetColorMode is deprecated and non-functional since it seems like it would be the right option here, but then internally macOS would need to be able to map it to the right option. Doubtless that would be easier if MONOCHROME were supported by all these drivers and I don't understand that omission either. In fact if it were we probably could just specify that directly without macOS needing to understand anything - like I expect it doesn't understand the key/value pairs your code is sending it. I note that you send ALL known key/value pairs and hope one works .. and that points out that all it takes is some vendor API change or a different printer and it just won't work again. These points and the implied maintenance cost are some of my bigger concerns with this.
Perhaps you should engage with Apple and get them to properly support MONOCHROME. Or maybe it would be enough if the printer vendors do ? FWIW, I don't think it is an Apple-specific issue, the free drivers for Linux behave similarly. Perhaps there's a clue in there somewhere. But similarly you can set up a separate queue.
The other idea that crossed my mind is that instead of relying on a printing solution, we could explicitly render in greyscale. Then we could always declare MONOCHROME support and just emulate it. But I am not sure if this is really possible - it would require changes to the rendering code and I'm not sure if or how if we'd be able to do that properly and safely. And the risks of that clearly extend beyond printing.
@prrace thanks for the detailed thoughts on this problem. As the stakeholder in this issue, I wanted to offer some feedback to your thoughts. I understand that the desire for OpenJDK is to NOT maintain a list of proprietary printer settings, but since many of the thoughts above get into use-case scenarios, I feel compelled to share my perspective. I hope these are well received.
When I do "Print to File" on macOS - meaning use the native printer dialog's "Save as PDF" option, then the generated PDF is always colour even if I select the printer dialog's "Grayscale Printing" option. This isn't just true for Java apps, the same happens if I print a web page from Safari.
Several PDF printers (on several OSs) ignore grayscale/b&w settings. I'm not sure why, but I've observed the same.
As far as I can tell, this isn't supported via any kind of API on macOS, so it is not a Java problem. You could be printing from an Objective-C app written directly to Cocoa APIs and still have the same problem.
Agreed.
So a Java-specific workaround like this seems inappropriate.
As the stakeholder (biased) it's hard for me to agree, however allowing the implementing application to maintain (and thus offer) this workaround would certainly suffice. Since AWT (quite graciously) abstracts these attributes away from the user, "wontfix" status naturally causes a feature gap, albeit not the fault of OpenJDK, but a parity that -- at a bare minimum -- could live on as -- for example -- a workaround on Stackoverflow, etc.
But what works for me to get greyscale by default is to set that up as the default for the print queue. It is easy to add a printer twice - with two queues for it.
Easy is subjective (and prescriptive). Again, biased, but setting up additional printer queues is a viable workaround, but comes at a factor of 2 queues for each color printer added to a system. This also offers support redundancies when removing and re-adding a printer (e.g. IP, port or driver change).
I don't understand why
PMSetColorModeis deprecated and non-functional since it seems like it would be the right option here, but then internally macOS would need to be able to map it to the right option.
Agreed.
[...] all it takes is some vendor API change or a different printer and it just won't work again. These points and the implied maintenance cost are some of my bigger concerns with this.
Understood.
The other idea that crossed my mind is that instead of relying on a printing solution, we could explicitly render in greyscale. Then we could always declare MONOCHROME support and just emulate it. But I am not sure if this is really possible - it would require changes to the rendering code and I'm not sure if or how if we'd be able to do that properly and safely. And the risks of that clearly extend beyond printing.
Since printers can emulate grayscale using color cartridges, I do not believe this will yield the same results as expected on all printers, but I do believe this to be a creative stop-gap/workaround which may be suitable for some use-cases. Naturally, I would expect some performance implications as a result. As the stakeholder, I can say that this will be trickier with prints using vector graphics than those already rastered/pixels. Speculating, vector graphics will likely be less performance-affecting, but require diving into the document modifications (e.g. PDFBOX, JavaFX) albeit more performant, will require some creative code to desaturate all color data in all objects prior to sending (or requests to add APIs thereo to each specific project).
Perhaps there's no user there (server-side printing), or the app would like to make sure the user always defaults to B&W.
I'm not sure how much interest there is in understanding the use-case that discovered this issue, but it's part of a (pretty popular) Java app that helps print documents. It does so through a WebSocket interface... so in that sense it is server-like (effectively headless), although it's predominantly used by ordinary, non-technical end-users and runs as a sort of background service. Much like AWT offers a (rather) standardized API for printing, so does the Websocket interface, so "Black & White" or "Monochrome" are offered as common, selectable options. "Always default to B&W" would be a case-by-case situation, depending on how the websocket API is offered up (e.g. web app) to the end-user.
@prrace thanks for the detailed thoughts on this problem. As the stakeholder in this issue, I wanted to offer some feedback to your thoughts. I understand that the desire for OpenJDK is to NOT maintain a list of proprietary printer settings, but since many of the thoughts above get into use-case scenarios, I feel compelled to share my perspective. I hope these are well received.
When I do "Print to File" on macOS - meaning use the native printer dialog's "Save as PDF" option, then the generated PDF is always colour even if I select the printer dialog's "Grayscale Printing" option. This isn't just true for Java apps, the same happens if I print a web page from Safari.
Several PDF printers (on several OSs) ignore grayscale/b&w settings. I'm not sure why, but I've observed the same.
As far as I can tell, this isn't supported via any kind of API on macOS, so it is not a Java problem. You could be printing from an Objective-C app written directly to Cocoa APIs and still have the same problem.
Agreed.
So a Java-specific workaround like this seems inappropriate.
As the stakeholder (biased) it's hard for me to agree, however allowing the implementing application to maintain (and thus offer) this workaround would certainly suffice. Since AWT (quite graciously) abstracts these attributes away from the user, "wontfix" status naturally causes a feature gap, albeit not the fault of OpenJDK, but a parity that -- at a bare minimum -- could live on as -- for example -- a workaround on Stackoverflow, etc.
But what works for me to get greyscale by default is to set that up as the default for the print queue. It is easy to add a printer twice - with two queues for it.
Easy is subjective (and prescriptive). Again, biased, but setting up additional printer queues is a viable workaround, but comes at a factor of 2 queues for each color printer added to a system. This also offers support redundancies when removing and re-adding a printer (e.g. IP, port or driver change).
I don't understand why
PMSetColorModeis deprecated and non-functional since it seems like it would be the right option here, but then internally macOS would need to be able to map it to the right option.Agreed.
[...] all it takes is some vendor API change or a different printer and it just won't work again. These points and the implied maintenance cost are some of my bigger concerns with this.
Understood.
The other idea that crossed my mind is that instead of relying on a printing solution, we could explicitly render in greyscale. Then we could always declare MONOCHROME support and just emulate it. But I am not sure if this is really possible - it would require changes to the rendering code and I'm not sure if or how if we'd be able to do that properly and safely. And the risks of that clearly extend beyond printing.
Since printers can emulate grayscale using color cartridges, I do not believe this will yield the same results as expected on all printers, but I do believe this to be a creative stop-gap/workaround which may be suitable for some use-cases. Naturally, I would expect some performance implications as a result. As the stakeholder, I can say that this will be trickier with prints using vector graphics than those already rastered/pixels. Speculating, vector graphics will likely be less performance-affecting, but require diving into the document modifications (e.g. PDFBOX, JavaFX) albeit more performant, will require some creative code to desaturate all color data in all objects prior to sending (or requests to add APIs thereo to each specific project).
Perhaps there's no user there (server-side printing), or the app would like to make sure the user always defaults to B&W.
I'm not sure how much interest there is in understanding the use-case that discovered this issue, but it's part of a (pretty popular) Java app that helps print documents. It does so through a WebSocket interface... so in that sense it is server-like (effectively headless), although it's predominantly used by ordinary, non-technical end-users and runs as a sort of background service. Much like AWT offers a (rather) standardized API for printing, so does the Websocket interface, so "Black & White" or "Monochrome" are offered as common, selectable options. "Always default to B&W" would be a case-by-case situation, depending on how the websocket API is offered up (e.g. web app) to the end-user.
Use cases are always interesting.
The idea about changing our rendering to be greyscale would be easier if the printing API explicitly supported it. It could be quite challenging and risky to actually do it otherwise, so it is just an idea.
Windows/GDI printing lets you set whether you want color or not with a standard field in the struct which controls printing https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-devmodew SFAIK that isn't using some driver-specific way of rendering and it seems to work fine.
Although I've yet to find any evidence of it being available, it is possible that printers using IPP on macOS will support print-color-mode as documented here : https://www.pwg.org/ipp/ippguide.html If that becomes standard and widely available it would be a better way to do this. But I need to understand it better, which would start with finding anything that supports it.
@AlexanderScherbatiy This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!
@AlexanderScherbatiy this pull request can not be integrated into master due to one or more merge conflicts. To resolve these merge conflicts and update this pull request you can run the following commands in the local repository for your personal fork:
git checkout JDK-8315113
git fetch https://git.openjdk.org/jdk.git master
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge master"
git push
@AlexanderScherbatiy This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!
@AlexanderScherbatiy This pull request has been inactive for more than 8 weeks and will now be automatically closed. If you would like to continue working on this pull request in the future, feel free to reopen it! This can be done using the /open pull request command.