no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / widget / cocoa / nsPrintSettingsX.mm
blob80da7309e4e678c2b69aadb222e7e4f361427217
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsPrintSettingsX.h"
7 #include "nsObjCExceptions.h"
9 #include "plbase64.h"
11 #include "nsCocoaUtils.h"
12 #include "nsXULAppAPI.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/StaticPrefs_print.h"
16 #include "nsPrinterCUPS.h"
18 using namespace mozilla;
20 NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsX, nsPrintSettings, nsPrintSettingsX)
22 nsPrintSettingsX::nsPrintSettingsX() {
23   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
25   mDestination = kPMDestinationInvalid;
27   NS_OBJC_END_TRY_IGNORE_BLOCK;
30 already_AddRefed<nsIPrintSettings> CreatePlatformPrintSettings(
31     const PrintSettingsInitializer& aSettings) {
32   RefPtr<nsPrintSettings> settings = new nsPrintSettingsX();
33   settings->InitWithInitializer(aSettings);
34   settings->SetDefaultFileName();
35   return settings.forget();
38 nsPrintSettingsX& nsPrintSettingsX::operator=(const nsPrintSettingsX& rhs) {
39   if (this == &rhs) {
40     return *this;
41   }
43   nsPrintSettings::operator=(rhs);
45   mDestination = rhs.mDestination;
46   mDisposition = rhs.mDisposition;
48   // We don't copy mSystemPrintInfo here, so any copied printSettings will start
49   // out without a wrapped printInfo, just using our internal settings. The
50   // system printInfo is used *only* by the nsPrintSettingsX to which it was
51   // originally passed (when the user ran a system print UI dialog).
53   return *this;
56 nsresult nsPrintSettingsX::_Clone(nsIPrintSettings** _retval) {
57   NS_ENSURE_ARG_POINTER(_retval);
58   auto newSettings = MakeRefPtr<nsPrintSettingsX>();
59   *newSettings = *this;
60   newSettings.forget(_retval);
61   return NS_OK;
64 NS_IMETHODIMP nsPrintSettingsX::_Assign(nsIPrintSettings* aPS) {
65   nsPrintSettingsX* printSettingsX = static_cast<nsPrintSettingsX*>(aPS);
66   if (!printSettingsX) {
67     return NS_ERROR_UNEXPECTED;
68   }
69   *this = *printSettingsX;
70   return NS_OK;
73 struct KnownMonochromeSetting {
74   const NSString* mName;
75   const NSString* mValue;
78 #define DECLARE_KNOWN_MONOCHROME_SETTING(key_, value_) \
79   { @key_, @value_ }                                   \
80   ,
81 static const KnownMonochromeSetting kKnownMonochromeSettings[] = {
82     CUPS_EACH_MONOCHROME_PRINTER_SETTING(DECLARE_KNOWN_MONOCHROME_SETTING)};
83 #undef DECLARE_KNOWN_MONOCHROME_SETTING
85 NSPrintInfo* nsPrintSettingsX::CreateOrCopyPrintInfo(bool aWithScaling) {
86   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
88   // If we have a printInfo that came from the system print UI, use it so that
89   // any printer-specific settings we don't know about will still be used.
90   if (mSystemPrintInfo) {
91     NSPrintInfo* sysPrintInfo = [mSystemPrintInfo copy];
92     // Any required scaling will be done by Gecko, so we don't want it here.
93     [sysPrintInfo setScalingFactor:1.0f];
94     return sysPrintInfo;
95   }
97   // Note that the app shared `sharedPrintInfo` object is special!  The system
98   // print dialog and print settings dialog update it with the values chosen
99   // by the user.  Using that object here to initialize new nsPrintSettingsX
100   // objects could mask bugs in our code where we fail to save and/or restore
101   // print settings ourselves (e.g., bug 1636725).  On other platforms we only
102   // initialize new nsPrintSettings objects from the settings that we save to
103   // prefs.  Perhaps we should stop using sharedPrintInfo here for consistency?
104   NSPrintInfo* printInfo = [[NSPrintInfo sharedPrintInfo] copy];
106   NSSize paperSize;
107   if (GetSheetOrientation() == kPortraitOrientation) {
108     paperSize.width = CocoaPointsFromPaperSize(mPaperWidth);
109     paperSize.height = CocoaPointsFromPaperSize(mPaperHeight);
110   } else {
111     paperSize.width = CocoaPointsFromPaperSize(mPaperHeight);
112     paperSize.height = CocoaPointsFromPaperSize(mPaperWidth);
113   }
114   [printInfo setPaperSize:paperSize];
116   if (paperSize.width > paperSize.height) {
117     [printInfo setOrientation:NSPaperOrientationLandscape];
118   } else {
119     [printInfo setOrientation:NSPaperOrientationPortrait];
120   }
122   [printInfo setTopMargin:mUnwriteableMargin.top];
123   [printInfo setRightMargin:mUnwriteableMargin.right];
124   [printInfo setBottomMargin:mUnwriteableMargin.bottom];
125   [printInfo setLeftMargin:mUnwriteableMargin.left];
127   // If the printer name is the name of our pseudo print-to-PDF printer, the
128   // following `setPrinter` call will fail silently, since macOS doesn't know
129   // anything about it. That's OK, because mPrinter is our canonical source of
130   // truth.
131   // Actually, it seems Mac OS X 10.12 (the oldest version of Mac that we
132   // support) hangs if the printer name is not recognized. For now we explicitly
133   // check for our print-to-PDF printer, but that is not ideal since we should
134   // really localize the name of this printer at some point. Once we drop
135   // support for 10.12 we should remove this check.
136   if (!mPrinter.EqualsLiteral("Mozilla Save to PDF")) {
137     [printInfo setPrinter:[NSPrinter printerWithName:nsCocoaUtils::ToNSString(
138                                                          mPrinter)]];
139   }
141   // Scaling is handled by gecko, we do NOT want the cocoa printing system to
142   // add a second scaling on top of that. So we only set the true scaling factor
143   // here if the caller explicitly asked for it.
144   [printInfo setScalingFactor:CGFloat(aWithScaling ? mScaling : 1.0f)];
146   const bool allPages = mPageRanges.IsEmpty();
148   NSMutableDictionary* dict = [printInfo dictionary];
149   [dict setObject:[NSNumber numberWithInt:mNumCopies] forKey:NSPrintCopies];
150   [dict setObject:[NSNumber numberWithBool:allPages] forKey:NSPrintAllPages];
152   int32_t start = 1;
153   int32_t end = 1;
154   for (size_t i = 0; i < mPageRanges.Length(); i += 2) {
155     start = std::min(start, mPageRanges[i]);
156     end = std::max(end, mPageRanges[i + 1]);
157   }
159   [dict setObject:[NSNumber numberWithInt:start] forKey:NSPrintFirstPage];
160   [dict setObject:[NSNumber numberWithInt:end] forKey:NSPrintLastPage];
162   NSURL* jobSavingURL = nullptr;
163   if (!mToFileName.IsEmpty()) {
164     jobSavingURL =
165         [NSURL fileURLWithPath:nsCocoaUtils::ToNSString(mToFileName)];
166     if (jobSavingURL) {
167       // Note: the PMPrintSettingsSetJobName call in nsPrintDialogServiceX::Show
168       // seems to mean that we get a sensible file name pre-populated in the
169       // dialog there, although our mToFileName is expected to be a full path,
170       // and it's less clear where the rest of the path (the directory to save
171       // to) in nsPrintDialogServiceX::Show comes from (perhaps from the use
172       // of `sharedPrintInfo` to initialize new nsPrintSettingsX objects).
173       [dict setObject:jobSavingURL forKey:NSPrintJobSavingURL];
174     }
175   }
177   if (mDisposition.IsEmpty()) {
178     // NOTE: It's unclear what to do for kOutputDestinationStream but this is
179     // only for the native print dialog where that can't happen.
180     if (mOutputDestination == kOutputDestinationFile) {
181       [printInfo setJobDisposition:NSPrintSaveJob];
182     } else {
183       [printInfo setJobDisposition:NSPrintSpoolJob];
184     }
185   } else {
186     [printInfo setJobDisposition:nsCocoaUtils::ToNSString(mDisposition)];
187   }
189   PMDuplexMode duplexSetting;
190   switch (mDuplex) {
191     default:
192       // This can't happen :) but if it does, we treat it as "none".
193       MOZ_FALLTHROUGH_ASSERT("Unknown duplex value");
194     case kDuplexNone:
195       duplexSetting = kPMDuplexNone;
196       break;
197     case kDuplexFlipOnLongEdge:
198       duplexSetting = kPMDuplexNoTumble;
199       break;
200     case kDuplexFlipOnShortEdge:
201       duplexSetting = kPMDuplexTumble;
202       break;
203   }
205   NSMutableDictionary* printSettings = [printInfo printSettings];
206   [printSettings setObject:[NSNumber numberWithUnsignedShort:duplexSetting]
207                     forKey:@"com_apple_print_PrintSettings_PMDuplexing"];
209   if (mDestination != kPMDestinationInvalid) {
210     // Required to support PDF-workflow destinations such as Save to Web
211     // Receipts.
212     [printSettings
213         setObject:[NSNumber numberWithUnsignedShort:mDestination]
214            forKey:@"com_apple_print_PrintSettings_PMDestinationType"];
215     if (jobSavingURL) {
216       [printSettings
217           setObject:[jobSavingURL absoluteString]
218              forKey:@"com_apple_print_PrintSettings_PMOutputFilename"];
219     }
220   }
222   if (StaticPrefs::print_cups_monochrome_enabled() && !GetPrintInColor()) {
223     for (const auto& setting : kKnownMonochromeSettings) {
224       [printSettings setObject:setting.mValue forKey:setting.mName];
225     }
226     auto applySetting = [&](const nsACString& aKey, const nsACString& aValue) {
227       [printSettings setObject:nsCocoaUtils::ToNSString(aValue)
228                         forKey:nsCocoaUtils::ToNSString(aKey)];
229     };
230     nsPrinterCUPS::ForEachExtraMonochromeSetting(applySetting);
231   }
233   return printInfo;
235   NS_OBJC_END_TRY_BLOCK_RETURN(nullptr);
238 void nsPrintSettingsX::SetFromPrintInfo(NSPrintInfo* aPrintInfo,
239                                         bool aAdoptPrintInfo) {
240   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
242   // Set page-size/margins.
243   NSSize paperSize = [aPrintInfo paperSize];
244   const bool areSheetsOfPaperPortraitMode =
245       ([aPrintInfo orientation] == NSPaperOrientationPortrait);
247   // If our MacOS print settings say that we're producing portrait-mode sheets
248   // of paper, then our page format must also be portrait-mode; unless we've
249   // got a pages-per-sheet value with orthogonal pages/sheets, in which case
250   // it's reversed.
251   const bool arePagesPortraitMode =
252       (areSheetsOfPaperPortraitMode != HasOrthogonalSheetsAndPages());
254   if (arePagesPortraitMode) {
255     mOrientation = nsIPrintSettings::kPortraitOrientation;
256     SetPaperWidth(PaperSizeFromCocoaPoints(paperSize.width));
257     SetPaperHeight(PaperSizeFromCocoaPoints(paperSize.height));
258   } else {
259     mOrientation = nsIPrintSettings::kLandscapeOrientation;
260     SetPaperWidth(PaperSizeFromCocoaPoints(paperSize.height));
261     SetPaperHeight(PaperSizeFromCocoaPoints(paperSize.width));
262   }
264   mUnwriteableMargin.top = static_cast<int32_t>([aPrintInfo topMargin]);
265   mUnwriteableMargin.right = static_cast<int32_t>([aPrintInfo rightMargin]);
266   mUnwriteableMargin.bottom = static_cast<int32_t>([aPrintInfo bottomMargin]);
267   mUnwriteableMargin.left = static_cast<int32_t>([aPrintInfo leftMargin]);
269   if (aAdoptPrintInfo) {
270     // Keep a reference to the printInfo; it may have settings that we don't
271     // know how to handle otherwise.
272     if (mSystemPrintInfo != aPrintInfo) {
273       if (mSystemPrintInfo) {
274         [mSystemPrintInfo release];
275       }
276       mSystemPrintInfo = aPrintInfo;
277       [mSystemPrintInfo retain];
278     }
279   } else {
280     // Clear any stored printInfo.
281     if (mSystemPrintInfo) {
282       [mSystemPrintInfo release];
283       mSystemPrintInfo = nullptr;
284     }
285   }
287   nsCocoaUtils::GetStringForNSString([[aPrintInfo printer] name], mPrinter);
289   // Only get the scaling value if shrink-to-fit is not selected:
290   bool isShrinkToFitChecked;
291   GetShrinkToFit(&isShrinkToFitChecked);
292   if (!isShrinkToFitChecked) {
293     // Limit scaling precision to whole percentage values.
294     mScaling = round(double([aPrintInfo scalingFactor]) * 100.0) / 100.0;
295   }
297   mOutputDestination = [&] {
298     if ([aPrintInfo jobDisposition] == NSPrintSaveJob) {
299       return kOutputDestinationFile;
300     }
301     return kOutputDestinationPrinter;
302   }();
304   NSDictionary* dict = [aPrintInfo dictionary];
305   const char* filePath =
306       [[dict objectForKey:NSPrintJobSavingURL] fileSystemRepresentation];
307   if (filePath && *filePath) {
308     CopyUTF8toUTF16(Span(filePath, strlen(filePath)), mToFileName);
309   }
311   nsCocoaUtils::GetStringForNSString([aPrintInfo jobDisposition], mDisposition);
313   mNumCopies = [[dict objectForKey:NSPrintCopies] intValue];
314   mPageRanges.Clear();
315   if (![[dict objectForKey:NSPrintAllPages] boolValue]) {
316     mPageRanges.AppendElement([[dict objectForKey:NSPrintFirstPage] intValue]);
317     mPageRanges.AppendElement([[dict objectForKey:NSPrintLastPage] intValue]);
318   }
320   NSDictionary* printSettings = [aPrintInfo printSettings];
321   NSNumber* value =
322       [printSettings objectForKey:@"com_apple_print_PrintSettings_PMDuplexing"];
323   if (value) {
324     PMDuplexMode duplexSetting = [value unsignedShortValue];
325     switch (duplexSetting) {
326       default:
327         // An unknown value is treated as None.
328         MOZ_FALLTHROUGH_ASSERT("Unknown duplex value");
329       case kPMDuplexNone:
330         mDuplex = kDuplexNone;
331         break;
332       case kPMDuplexNoTumble:
333         mDuplex = kDuplexFlipOnLongEdge;
334         break;
335       case kPMDuplexTumble:
336         mDuplex = kDuplexFlipOnShortEdge;
337         break;
338     }
339   } else {
340     // By default a printSettings dictionary doesn't initially contain the
341     // duplex key at all, so this is not an error; its absence just means no
342     // duplexing has been requested, so we return kDuplexNone.
343     mDuplex = kDuplexNone;
344   }
346   value = [printSettings
347       objectForKey:@"com_apple_print_PrintSettings_PMDestinationType"];
348   if (value) {
349     mDestination = [value unsignedShortValue];
350   }
352   const bool color = [&] {
353     if (StaticPrefs::print_cups_monochrome_enabled()) {
354       for (const auto& setting : kKnownMonochromeSettings) {
355         NSString* value = [printSettings objectForKey:setting.mName];
356         if (!value) {
357           continue;
358         }
359         if ([setting.mValue isEqualToString:value]) {
360           return false;
361         }
362       }
363     }
364     return true;
365   }();
367   SetPrintInColor(color);
369   SetIsInitializedFromPrinter(true);
371   NS_OBJC_END_TRY_IGNORE_BLOCK;