summaryrefslogtreecommitdiffstats
path: root/widget/cocoa/nsPrintSettingsX.mm
blob: 6e45b97433233a137c75064bdbc10b64f2bc086a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsPrintSettingsX.h"
#include "nsObjCExceptions.h"

#include "plbase64.h"
#include "plstr.h"

#include "nsCocoaUtils.h"
#include "nsXULAppAPI.h"

#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_print.h"
#include "nsPrinterCUPS.h"

using namespace mozilla;

NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsX, nsPrintSettings, nsPrintSettingsX)

nsPrintSettingsX::nsPrintSettingsX() {
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  mDestination = kPMDestinationInvalid;

  /*
   * Don't save print settings after the user cancels out of the
   * print dialog. For saving print settings after a cancellation
   * to work properly, in addition to changing |mSaveOnCancel|,
   * the print dialog implementation must be updated to save changed
   * settings and serialize them back to the child process.
   */
  mSaveOnCancel = false;

  NS_OBJC_END_TRY_ABORT_BLOCK;
}

already_AddRefed<nsIPrintSettings> CreatePlatformPrintSettings(
    const PrintSettingsInitializer& aSettings) {
  RefPtr<nsPrintSettings> settings = new nsPrintSettingsX();
  settings->InitWithInitializer(aSettings);
  settings->SetDefaultFileName();
  return settings.forget();
}

nsPrintSettingsX& nsPrintSettingsX::operator=(const nsPrintSettingsX& rhs) {
  if (this == &rhs) {
    return *this;
  }

  nsPrintSettings::operator=(rhs);

  mDestination = rhs.mDestination;
  mDisposition = rhs.mDisposition;

  // We don't copy mSystemPrintInfo here, so any copied printSettings will start out
  // without a wrapped printInfo, just using our internal settings. The system
  // printInfo is used *only* by the nsPrintSettingsX to which it was originally
  // passed (when the user ran a system print UI dialog).

  return *this;
}

nsresult nsPrintSettingsX::_Clone(nsIPrintSettings** _retval) {
  NS_ENSURE_ARG_POINTER(_retval);
  auto newSettings = MakeRefPtr<nsPrintSettingsX>();
  *newSettings = *this;
  newSettings.forget(_retval);
  return NS_OK;
}

NS_IMETHODIMP nsPrintSettingsX::_Assign(nsIPrintSettings* aPS) {
  nsPrintSettingsX* printSettingsX = static_cast<nsPrintSettingsX*>(aPS);
  if (!printSettingsX) {
    return NS_ERROR_UNEXPECTED;
  }
  *this = *printSettingsX;
  return NS_OK;
}

struct KnownMonochromeSetting {
  const NSString* mName;
  const NSString* mValue;
};

#define DECLARE_KNOWN_MONOCHROME_SETTING(key_, value_) \
  { @key_, @value_ }                                   \
  ,
static const KnownMonochromeSetting kKnownMonochromeSettings[] = {
    CUPS_EACH_MONOCHROME_PRINTER_SETTING(DECLARE_KNOWN_MONOCHROME_SETTING)};
#undef DECLARE_KNOWN_MONOCHROME_SETTING

NSPrintInfo* nsPrintSettingsX::CreateOrCopyPrintInfo(bool aWithScaling) {
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;

  // If we have a printInfo that came from the system print UI, use it so that
  // any printer-specific settings we don't know about will still be used.
  if (mSystemPrintInfo) {
    NSPrintInfo* sysPrintInfo = [mSystemPrintInfo copy];
    // Any required scaling will be done by Gecko, so we don't want it here.
    [sysPrintInfo setScalingFactor:1.0f];
    return sysPrintInfo;
  }

  // Note that the app shared `sharedPrintInfo` object is special!  The system
  // print dialog and print settings dialog update it with the values chosen
  // by the user.  Using that object here to initialize new nsPrintSettingsX
  // objects could mask bugs in our code where we fail to save and/or restore
  // print settings ourselves (e.g., bug 1636725).  On other platforms we only
  // initialize new nsPrintSettings objects from the settings that we save to
  // prefs.  Perhaps we should stop using sharedPrintInfo here for consistency?
  NSPrintInfo* printInfo = [[NSPrintInfo sharedPrintInfo] copy];

  NSSize paperSize;
  if (GetSheetOrientation() == kPortraitOrientation) {
    [printInfo setOrientation:NSPaperOrientationPortrait];
    paperSize.width = CocoaPointsFromPaperSize(mPaperWidth);
    paperSize.height = CocoaPointsFromPaperSize(mPaperHeight);
    [printInfo setPaperSize:paperSize];
  } else {
    [printInfo setOrientation:NSPaperOrientationLandscape];
    paperSize.width = CocoaPointsFromPaperSize(mPaperHeight);
    paperSize.height = CocoaPointsFromPaperSize(mPaperWidth);
    [printInfo setPaperSize:paperSize];
  }

  [printInfo setTopMargin:mUnwriteableMargin.top];
  [printInfo setRightMargin:mUnwriteableMargin.right];
  [printInfo setBottomMargin:mUnwriteableMargin.bottom];
  [printInfo setLeftMargin:mUnwriteableMargin.left];

  // If the name is our pseudo-printer "Mozilla Save to PDF", this will silently fail
  // as no such printer is known. That's OK, because mPrinter will remain correct
  // and is our canonical source of truth here.
  [printInfo setPrinter:[NSPrinter printerWithName:nsCocoaUtils::ToNSString(mPrinter)]];

  // Scaling is handled by gecko, we do NOT want the cocoa printing system to add
  // a second scaling on top of that. So we only set the true scaling factor here
  // if the caller explicitly asked for it.
  [printInfo setScalingFactor:CGFloat(aWithScaling ? mScaling : 1.0f)];

  const bool allPages = mPageRanges.IsEmpty();

  NSMutableDictionary* dict = [printInfo dictionary];
  [dict setObject:[NSNumber numberWithInt:mNumCopies] forKey:NSPrintCopies];
  [dict setObject:[NSNumber numberWithBool:allPages] forKey:NSPrintAllPages];

  int32_t start = 1;
  int32_t end = 1;
  for (size_t i = 0; i < mPageRanges.Length(); i += 2) {
    start = std::min(start, mPageRanges[i]);
    end = std::max(end, mPageRanges[i + 1]);
  }

  [dict setObject:[NSNumber numberWithInt:start] forKey:NSPrintFirstPage];
  [dict setObject:[NSNumber numberWithInt:end] forKey:NSPrintLastPage];

  NSURL* jobSavingURL = nullptr;
  if (!mToFileName.IsEmpty()) {
    jobSavingURL = [NSURL fileURLWithPath:nsCocoaUtils::ToNSString(mToFileName)];
    if (jobSavingURL) {
      // Note: the PMPrintSettingsSetJobName call in nsPrintDialogServiceX::Show
      // seems to mean that we get a sensible file name pre-populated in the
      // dialog there, although our mToFileName is expected to be a full path,
      // and it's less clear where the rest of the path (the directory to save
      // to) in nsPrintDialogServiceX::Show comes from (perhaps from the use
      // of `sharedPrintInfo` to initialize new nsPrintSettingsX objects).
      [dict setObject:jobSavingURL forKey:NSPrintJobSavingURL];
    }
  }

  if (mDisposition.IsEmpty()) {
    if (mPrintToFile) {
      [printInfo setJobDisposition:NSPrintSaveJob];
    } else {
      [printInfo setJobDisposition:NSPrintSpoolJob];
    }
  } else {
    [printInfo setJobDisposition:nsCocoaUtils::ToNSString(mDisposition)];
  }

  PMDuplexMode duplexSetting;
  switch (mDuplex) {
    default:
      // This can't happen :) but if it does, we treat it as "none".
      MOZ_FALLTHROUGH_ASSERT("Unknown duplex value");
    case kSimplex:
      duplexSetting = kPMDuplexNone;
      break;
    case kDuplexVertical:
      duplexSetting = kPMDuplexTumble;
      break;
    case kDuplexHorizontal:
      duplexSetting = kPMDuplexNoTumble;
      break;
  }

  NSMutableDictionary* printSettings = [printInfo printSettings];
  [printSettings setObject:[NSNumber numberWithUnsignedShort:duplexSetting]
                    forKey:@"com_apple_print_PrintSettings_PMDuplexing"];

  if (mDestination != kPMDestinationInvalid) {
    // Required to support PDF-workflow destinations such as Save to Web Receipts.
    [printSettings setObject:[NSNumber numberWithUnsignedShort:mDestination]
                      forKey:@"com_apple_print_PrintSettings_PMDestinationType"];
    if (jobSavingURL) {
      [printSettings setObject:[jobSavingURL absoluteString]
                        forKey:@"com_apple_print_PrintSettings_PMOutputFilename"];
    }
  }

  if (StaticPrefs::print_cups_monochrome_enabled() && !GetPrintInColor()) {
    for (const auto& setting : kKnownMonochromeSettings) {
      [printSettings setObject:setting.mValue forKey:setting.mName];
    }
    auto applySetting = [&](const nsACString& aKey, const nsACString& aValue) {
      [printSettings setObject:nsCocoaUtils::ToNSString(aValue)
                        forKey:nsCocoaUtils::ToNSString(aKey)];
    };
    nsPrinterCUPS::ForEachExtraMonochromeSetting(applySetting);
  }

  return printInfo;

  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr);
}

void nsPrintSettingsX::SetFromPrintInfo(NSPrintInfo* aPrintInfo, bool aAdoptPrintInfo) {
  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

  // Set page-size/margins.
  NSSize paperSize = [aPrintInfo paperSize];
  const bool areSheetsOfPaperPortraitMode =
      ([aPrintInfo orientation] == NSPaperOrientationPortrait);

  // If our MacOS print settings say that we're producing portrait-mode sheets
  // of paper, then our page format must also be portrait-mode; unless we've
  // got a pages-per-sheet value with orthogonal pages/sheets, in which case
  // it's reversed.
  const bool arePagesPortraitMode = (areSheetsOfPaperPortraitMode != HasOrthogonalSheetsAndPages());

  if (arePagesPortraitMode) {
    mOrientation = nsIPrintSettings::kPortraitOrientation;
    SetPaperWidth(PaperSizeFromCocoaPoints(paperSize.width));
    SetPaperHeight(PaperSizeFromCocoaPoints(paperSize.height));
  } else {
    mOrientation = nsIPrintSettings::kLandscapeOrientation;
    SetPaperWidth(PaperSizeFromCocoaPoints(paperSize.height));
    SetPaperHeight(PaperSizeFromCocoaPoints(paperSize.width));
  }

  mUnwriteableMargin.top = [aPrintInfo topMargin];
  mUnwriteableMargin.right = [aPrintInfo rightMargin];
  mUnwriteableMargin.bottom = [aPrintInfo bottomMargin];
  mUnwriteableMargin.left = [aPrintInfo leftMargin];

  if (aAdoptPrintInfo) {
    // Keep a reference to the printInfo; it may have settings that we don't know how to handle
    // otherwise.
    if (mSystemPrintInfo != aPrintInfo) {
      if (mSystemPrintInfo) {
        [mSystemPrintInfo release];
      }
      mSystemPrintInfo = aPrintInfo;
      [mSystemPrintInfo retain];
    }
  } else {
    // Clear any stored printInfo.
    if (mSystemPrintInfo) {
      [mSystemPrintInfo release];
      mSystemPrintInfo = nullptr;
    }
  }

  nsCocoaUtils::GetStringForNSString([[aPrintInfo printer] name], mPrinter);

  // Only get the scaling value if shrink-to-fit is not selected:
  bool isShrinkToFitChecked;
  GetShrinkToFit(&isShrinkToFitChecked);
  if (!isShrinkToFitChecked) {
    // Limit scaling precision to whole percentage values.
    mScaling = round(double([aPrintInfo scalingFactor]) * 100.0) / 100.0;
  }

  mPrintToFile = [aPrintInfo jobDisposition] == NSPrintSaveJob;

  NSDictionary* dict = [aPrintInfo dictionary];
  const char* filePath = [[dict objectForKey:NSPrintJobSavingURL] fileSystemRepresentation];
  if (filePath && *filePath) {
    CopyUTF8toUTF16(Span(filePath, strlen(filePath)), mToFileName);
  }

  nsCocoaUtils::GetStringForNSString([aPrintInfo jobDisposition], mDisposition);

  mNumCopies = [[dict objectForKey:NSPrintCopies] intValue];
  mPageRanges.Clear();
  if (![[dict objectForKey:NSPrintAllPages] boolValue]) {
    mPageRanges.AppendElement([[dict objectForKey:NSPrintFirstPage] intValue]);
    mPageRanges.AppendElement([[dict objectForKey:NSPrintLastPage] intValue]);
  }

  NSDictionary* printSettings = [aPrintInfo printSettings];
  NSNumber* value = [printSettings objectForKey:@"com_apple_print_PrintSettings_PMDuplexing"];
  if (value) {
    PMDuplexMode duplexSetting = [value unsignedShortValue];
    switch (duplexSetting) {
      default:
        // An unknown value is treated as None.
        MOZ_FALLTHROUGH_ASSERT("Unknown duplex value");
      case kPMDuplexNone:
        mDuplex = kSimplex;
        break;
      case kPMDuplexNoTumble:
        mDuplex = kDuplexHorizontal;
        break;
      case kPMDuplexTumble:
        mDuplex = kDuplexVertical;
        break;
    }
  } else {
    // By default a printSettings dictionary doesn't initially contain the
    // duplex key at all, so this is not an error; its absence just means no
    // duplexing has been requested, so we return kSimplex.
    mDuplex = kSimplex;
  }

  value = [printSettings objectForKey:@"com_apple_print_PrintSettings_PMDestinationType"];
  if (value) {
    mDestination = [value unsignedShortValue];
  }

  const bool color = [&] {
    if (StaticPrefs::print_cups_monochrome_enabled()) {
      for (const auto& setting : kKnownMonochromeSettings) {
        NSString* value = [printSettings objectForKey:setting.mName];
        if (!value) {
          continue;
        }
        if ([setting.mValue isEqualToString:value]) {
          return false;
        }
      }
    }
    return true;
  }();

  SetPrintInColor(color);

  SetIsInitializedFromPrinter(true);

  NS_OBJC_END_TRY_ABORT_BLOCK;
}