1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=4 expandtab: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsDeviceContext.h"
8 #include <algorithm> // for max
9 #include "gfxContext.h"
10 #include "gfxImageSurface.h" // for gfxImageSurface
11 #include "gfxPoint.h" // for gfxSize
12 #include "gfxTextRun.h" // for gfxFontGroup
13 #include "mozilla/Attributes.h" // for final
14 #include "mozilla/gfx/PathHelpers.h"
15 #include "mozilla/gfx/PrintTarget.h"
16 #include "mozilla/Preferences.h" // for Preferences
17 #include "mozilla/Services.h" // for GetObserverService
18 #include "mozilla/mozalloc.h" // for operator new
19 #include "nsCRT.h" // for nsCRT
20 #include "nsDebug.h" // for NS_ASSERTION, etc
21 #include "nsFont.h" // for nsFont
22 #include "nsFontCache.h" // for nsFontCache
23 #include "nsFontMetrics.h" // for nsFontMetrics
24 #include "nsAtom.h" // for nsAtom, NS_Atomize
26 #include "nsIDeviceContextSpec.h" // for nsIDeviceContextSpec
27 #include "nsLanguageAtomService.h" // for nsLanguageAtomService
28 #include "nsIObserver.h" // for nsIObserver, etc
29 #include "nsIObserverService.h" // for nsIObserverService
30 #include "nsIScreen.h" // for nsIScreen
31 #include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc
32 #include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE
33 #include "nsIWidget.h" // for nsIWidget, NS_NATIVE_WINDOW
34 #include "nsRect.h" // for nsRect
35 #include "nsServiceManagerUtils.h" // for do_GetService
36 #include "nsString.h" // for nsDependentString
37 #include "nsTArray.h" // for nsTArray, nsTArray_Impl
38 #include "nsThreadUtils.h" // for NS_IsMainThread
39 #include "mozilla/gfx/Logging.h"
40 #include "mozilla/widget/ScreenManager.h" // for ScreenManager
42 using namespace mozilla
;
43 using namespace mozilla::gfx
;
44 using mozilla::services::GetObserverService
;
45 using mozilla::widget::ScreenManager
;
47 nsDeviceContext::nsDeviceContext()
50 mAppUnitsPerDevPixel(-1),
51 mAppUnitsPerDevPixelAtUnitFullZoom(-1),
52 mAppUnitsPerPhysicalInch(-1),
55 mPrintingTranslate(gfxPoint(0, 0)),
56 mIsCurrentlyPrintingDoc(false)
62 MOZ_ASSERT(NS_IsMainThread(), "nsDeviceContext created off main thread");
65 nsDeviceContext::~nsDeviceContext() = default;
67 bool nsDeviceContext::IsPrinterContext() { return mPrintTarget
!= nullptr; }
69 void nsDeviceContext::SetDPI(double* aScale
) {
72 // Use the printing DC to determine DPI values, if we have one.
73 if (mDeviceContextSpec
) {
74 dpi
= mDeviceContextSpec
->GetDPI();
75 mPrintingScale
= mDeviceContextSpec
->GetPrintingScale();
76 mPrintingTranslate
= mDeviceContextSpec
->GetPrintingTranslate();
77 mAppUnitsPerDevPixelAtUnitFullZoom
=
78 NS_lround((AppUnitsPerCSSPixel() * 96) / dpi
);
80 nsCOMPtr
<nsIScreen
> primaryScreen
;
81 ScreenManager
& screenManager
= ScreenManager::GetSingleton();
82 screenManager
.GetPrimaryScreen(getter_AddRefs(primaryScreen
));
83 MOZ_ASSERT(primaryScreen
);
85 // A value of -1 means use the maximum of 96 and the system DPI.
86 // A value of 0 means use the system DPI. A positive value is used as the
87 // DPI. This sets the physical size of a device pixel and thus controls the
88 // interpretation of physical units.
89 int32_t prefDPI
= Preferences::GetInt("layout.css.dpi", -1);
94 // PuppetWidget could return -1 if the value's not available yet.
95 dpi
= mWidget
->GetDPI();
96 // In case that the widget returns -1, use the primary screen's
99 primaryScreen
->GetDpi(&dpi
);
102 dpi
= std::max(96.0f
, dpi
);
108 double devPixelsPerCSSPixel
;
109 if (aScale
&& *aScale
> 0.0) {
110 // if caller provided a scale, we just use it
111 devPixelsPerCSSPixel
= *aScale
;
113 // otherwise get from the widget, and return it in aScale for
114 // the caller to pass to child contexts if needed
115 CSSToLayoutDeviceScale scale
=
116 mWidget
? mWidget
->GetDefaultScale() : CSSToLayoutDeviceScale(1.0);
117 devPixelsPerCSSPixel
= scale
.scale
;
118 // In case that the widget returns -1, use the primary screen's
120 if (devPixelsPerCSSPixel
< 0) {
121 primaryScreen
->GetDefaultCSSScaleFactor(&devPixelsPerCSSPixel
);
124 *aScale
= devPixelsPerCSSPixel
;
128 mAppUnitsPerDevPixelAtUnitFullZoom
=
129 std::max(1, NS_lround(AppUnitsPerCSSPixel() / devPixelsPerCSSPixel
));
132 NS_ASSERTION(dpi
!= -1.0, "no dpi set");
134 mAppUnitsPerPhysicalInch
=
135 NS_lround(dpi
* mAppUnitsPerDevPixelAtUnitFullZoom
);
136 UpdateAppUnitsForFullZoom();
139 nsresult
nsDeviceContext::Init(nsIWidget
* aWidget
) {
141 // We can't assert |!mIsInitialized| here since EndSwapDocShellsForDocument
142 // re-initializes nsDeviceContext objects. We can only assert in
143 // InitForPrinting (below).
144 mIsInitialized
= true;
148 if (mScreenManager
&& mWidget
== aWidget
) return rv
;
153 if (mScreenManager
) return rv
;
155 mScreenManager
= do_GetService("@mozilla.org/gfx/screenmanager;1", &rv
);
160 // XXX This is only for printing. We should make that obvious in the name.
161 already_AddRefed
<gfxContext
> nsDeviceContext::CreateRenderingContext() {
162 return CreateRenderingContextCommon(/* not a reference context */ false);
165 already_AddRefed
<gfxContext
>
166 nsDeviceContext::CreateReferenceRenderingContext() {
167 return CreateRenderingContextCommon(/* a reference context */ true);
170 already_AddRefed
<gfxContext
> nsDeviceContext::CreateRenderingContextCommon(
171 bool aWantReferenceContext
) {
172 MOZ_ASSERT(IsPrinterContext());
173 MOZ_ASSERT(mWidth
> 0 && mHeight
> 0);
175 RefPtr
<gfx::DrawTarget
> dt
;
176 if (aWantReferenceContext
) {
177 dt
= mPrintTarget
->GetReferenceDrawTarget();
179 // This will be null if e10s is disabled or print.print_via_parent=false.
180 RefPtr
<DrawEventRecorder
> recorder
;
181 mDeviceContextSpec
->GetDrawEventRecorder(getter_AddRefs(recorder
));
182 dt
= mPrintTarget
->MakeDrawTarget(gfx::IntSize(mWidth
, mHeight
), recorder
);
185 if (!dt
|| !dt
->IsValid()) {
186 gfxCriticalNote
<< "Failed to create draw target in device context sized "
187 << mWidth
<< "x" << mHeight
<< " and pointer "
188 << hexa(mPrintTarget
);
192 dt
->AddUserData(&sDisablePixelSnapping
, (void*)0x1, nullptr);
194 RefPtr
<gfxContext
> pContext
= gfxContext::CreateOrNull(dt
);
195 MOZ_ASSERT(pContext
); // already checked draw target above
198 transform
.PreTranslate(mPrintingTranslate
);
199 if (mPrintTarget
->RotateNeededForLandscape()) {
200 // Rotate page 90 degrees to draw landscape page on portrait paper
201 IntSize size
= mPrintTarget
->GetSize();
202 transform
.PreTranslate(gfxPoint(0, size
.width
));
203 gfxMatrix
rotate(0, -1, 1, 0, 0, 0);
204 transform
= rotate
* transform
;
206 transform
.PreScale(mPrintingScale
, mPrintingScale
);
208 pContext
->SetMatrixDouble(transform
);
209 return pContext
.forget();
212 nsresult
nsDeviceContext::GetDepth(uint32_t& aDepth
) {
213 nsCOMPtr
<nsIScreen
> screen
;
214 FindScreen(getter_AddRefs(screen
));
216 ScreenManager
& screenManager
= ScreenManager::GetSingleton();
217 screenManager
.GetPrimaryScreen(getter_AddRefs(screen
));
220 screen
->GetColorDepth(reinterpret_cast<int32_t*>(&aDepth
));
225 nsresult
nsDeviceContext::GetDeviceSurfaceDimensions(nscoord
& aWidth
,
227 if (IsPrinterContext()) {
232 ComputeFullAreaUsingScreen(&area
);
233 aWidth
= area
.Width();
234 aHeight
= area
.Height();
240 nsresult
nsDeviceContext::GetRect(nsRect
& aRect
) {
241 if (IsPrinterContext()) {
242 aRect
.SetRect(0, 0, mWidth
, mHeight
);
244 ComputeFullAreaUsingScreen(&aRect
);
249 nsresult
nsDeviceContext::GetClientRect(nsRect
& aRect
) {
250 if (IsPrinterContext()) {
251 aRect
.SetRect(0, 0, mWidth
, mHeight
);
253 ComputeClientRectUsingScreen(&aRect
);
258 nsresult
nsDeviceContext::InitForPrinting(nsIDeviceContextSpec
* aDevice
) {
259 NS_ENSURE_ARG_POINTER(aDevice
);
261 MOZ_ASSERT(!mIsInitialized
,
262 "Only initialize once, immediately after construction");
264 // We don't set mIsInitialized here. The Init() call below does that.
266 mPrintTarget
= aDevice
->MakePrintTarget();
268 return NS_ERROR_FAILURE
;
271 mDeviceContextSpec
= aDevice
;
275 if (!CalcPrintingSize()) {
276 return NS_ERROR_FAILURE
;
282 nsresult
nsDeviceContext::BeginDocument(const nsAString
& aTitle
,
283 const nsAString
& aPrintToFileName
,
284 int32_t aStartPage
, int32_t aEndPage
) {
285 MOZ_ASSERT(!mIsCurrentlyPrintingDoc
,
286 "Mismatched BeginDocument/EndDocument calls");
288 nsresult rv
= mPrintTarget
->BeginPrinting(aTitle
, aPrintToFileName
,
289 aStartPage
, aEndPage
);
291 if (NS_SUCCEEDED(rv
)) {
292 if (mDeviceContextSpec
) {
293 rv
= mDeviceContextSpec
->BeginDocument(aTitle
, aPrintToFileName
,
294 aStartPage
, aEndPage
);
296 mIsCurrentlyPrintingDoc
= true;
299 // Warn about any failure (except user cancelling):
300 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
) || rv
== NS_ERROR_ABORT
,
301 "nsDeviceContext::BeginDocument failed");
306 nsresult
nsDeviceContext::EndDocument(void) {
307 MOZ_ASSERT(mIsCurrentlyPrintingDoc
,
308 "Mismatched BeginDocument/EndDocument calls");
310 mIsCurrentlyPrintingDoc
= false;
312 nsresult rv
= mPrintTarget
->EndPrinting();
313 if (NS_SUCCEEDED(rv
)) {
314 mPrintTarget
->Finish();
317 if (mDeviceContextSpec
) mDeviceContextSpec
->EndDocument();
319 mPrintTarget
= nullptr;
324 nsresult
nsDeviceContext::AbortDocument(void) {
325 MOZ_ASSERT(mIsCurrentlyPrintingDoc
,
326 "Mismatched BeginDocument/EndDocument calls");
328 nsresult rv
= mPrintTarget
->AbortPrinting();
330 mIsCurrentlyPrintingDoc
= false;
332 if (mDeviceContextSpec
) mDeviceContextSpec
->EndDocument();
334 mPrintTarget
= nullptr;
339 nsresult
nsDeviceContext::BeginPage(void) {
342 if (mDeviceContextSpec
) rv
= mDeviceContextSpec
->BeginPage();
344 if (NS_FAILED(rv
)) return rv
;
346 return mPrintTarget
->BeginPage();
349 nsresult
nsDeviceContext::EndPage(void) {
350 nsresult rv
= mPrintTarget
->EndPage();
352 if (mDeviceContextSpec
) mDeviceContextSpec
->EndPage();
357 void nsDeviceContext::ComputeClientRectUsingScreen(nsRect
* outRect
) {
358 // we always need to recompute the clientRect
359 // because the window may have moved onto a different screen. In the single
360 // monitor case, we only need to do the computation if we haven't done it
361 // once already, and remember that we have because we're assured it won't
363 nsCOMPtr
<nsIScreen
> screen
;
364 FindScreen(getter_AddRefs(screen
));
366 int32_t x
, y
, width
, height
;
367 screen
->GetAvailRect(&x
, &y
, &width
, &height
);
369 // convert to device units
370 outRect
->SetRect(NSIntPixelsToAppUnits(x
, AppUnitsPerDevPixel()),
371 NSIntPixelsToAppUnits(y
, AppUnitsPerDevPixel()),
372 NSIntPixelsToAppUnits(width
, AppUnitsPerDevPixel()),
373 NSIntPixelsToAppUnits(height
, AppUnitsPerDevPixel()));
377 void nsDeviceContext::ComputeFullAreaUsingScreen(nsRect
* outRect
) {
378 // if we have more than one screen, we always need to recompute the clientRect
379 // because the window may have moved onto a different screen. In the single
380 // monitor case, we only need to do the computation if we haven't done it
381 // once already, and remember that we have because we're assured it won't
383 nsCOMPtr
<nsIScreen
> screen
;
384 FindScreen(getter_AddRefs(screen
));
386 int32_t x
, y
, width
, height
;
387 screen
->GetRect(&x
, &y
, &width
, &height
);
389 // convert to device units
390 outRect
->SetRect(NSIntPixelsToAppUnits(x
, AppUnitsPerDevPixel()),
391 NSIntPixelsToAppUnits(y
, AppUnitsPerDevPixel()),
392 NSIntPixelsToAppUnits(width
, AppUnitsPerDevPixel()),
393 NSIntPixelsToAppUnits(height
, AppUnitsPerDevPixel()));
394 mWidth
= outRect
->Width();
395 mHeight
= outRect
->Height();
402 // Determines which screen intersects the largest area of the given surface.
404 void nsDeviceContext::FindScreen(nsIScreen
** outScreen
) {
405 if (!mWidget
|| !mScreenManager
) {
411 nsCOMPtr
<nsIScreen
> screen
= mWidget
->GetWidgetScreen();
412 screen
.forget(outScreen
);
415 mScreenManager
->GetPrimaryScreen(outScreen
);
419 bool nsDeviceContext::CalcPrintingSize() {
420 gfxSize
size(mPrintTarget
->GetSize());
421 // For printing, CSS inches and physical inches are identical
422 // so it doesn't matter which we use here
423 mWidth
= NSToCoordRound(size
.width
* AppUnitsPerPhysicalInch() /
424 POINTS_PER_INCH_FLOAT
);
425 mHeight
= NSToCoordRound(size
.height
* AppUnitsPerPhysicalInch() /
426 POINTS_PER_INCH_FLOAT
);
428 return (mWidth
> 0 && mHeight
> 0);
431 bool nsDeviceContext::CheckDPIChange(double* aScale
) {
432 int32_t oldDevPixels
= mAppUnitsPerDevPixelAtUnitFullZoom
;
433 int32_t oldInches
= mAppUnitsPerPhysicalInch
;
437 return oldDevPixels
!= mAppUnitsPerDevPixelAtUnitFullZoom
||
438 oldInches
!= mAppUnitsPerPhysicalInch
;
441 bool nsDeviceContext::SetFullZoom(float aScale
) {
443 MOZ_ASSERT_UNREACHABLE("Invalid full zoom value");
446 int32_t oldAppUnitsPerDevPixel
= mAppUnitsPerDevPixel
;
448 UpdateAppUnitsForFullZoom();
449 return oldAppUnitsPerDevPixel
!= mAppUnitsPerDevPixel
;
452 void nsDeviceContext::UpdateAppUnitsForFullZoom() {
453 mAppUnitsPerDevPixel
= std::max(
454 1, NSToIntRound(float(mAppUnitsPerDevPixelAtUnitFullZoom
) / mFullZoom
));
455 // adjust mFullZoom to reflect appunit rounding
456 mFullZoom
= float(mAppUnitsPerDevPixelAtUnitFullZoom
) / mAppUnitsPerDevPixel
;
459 DesktopToLayoutDeviceScale
nsDeviceContext::GetDesktopToDeviceScale() {
460 nsCOMPtr
<nsIScreen
> screen
;
461 FindScreen(getter_AddRefs(screen
));
465 screen
->GetContentsScaleFactor(&scale
);
466 return DesktopToLayoutDeviceScale(scale
);
469 return DesktopToLayoutDeviceScale(1.0);
472 bool nsDeviceContext::IsSyncPagePrinting() const {
473 MOZ_ASSERT(mPrintTarget
);
474 return mPrintTarget
->IsSyncPagePrinting();
477 void nsDeviceContext::RegisterPageDoneCallback(
478 PrintTarget::PageDoneCallback
&& aCallback
) {
479 MOZ_ASSERT(mPrintTarget
&& aCallback
&& !IsSyncPagePrinting());
480 mPrintTarget
->RegisterPageDoneCallback(std::move(aCallback
));
482 void nsDeviceContext::UnregisterPageDoneCallback() {
484 mPrintTarget
->UnregisterPageDoneCallback();