1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=2 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/LookAndFeel.h"
14 #include "mozilla/gfx/PathHelpers.h"
15 #include "mozilla/gfx/PrintTarget.h"
16 #include "mozilla/Preferences.h" // for Preferences
17 #include "mozilla/ProfilerMarkers.h"
18 #include "mozilla/Services.h" // for GetObserverService
19 #include "mozilla/StaticPrefs_layout.h"
20 #include "mozilla/Try.h" // for MOZ_TRY
21 #include "mozilla/mozalloc.h" // for operator new
22 #include "mozilla/widget/Screen.h" // for Screen
23 #include "nsCRT.h" // for nsCRT
24 #include "nsDebug.h" // for NS_ASSERTION, etc
25 #include "nsFont.h" // for nsFont
26 #include "nsFontCache.h" // for nsFontCache
27 #include "nsFontMetrics.h" // for nsFontMetrics
28 #include "nsAtom.h" // for nsAtom, NS_Atomize
30 #include "nsIDeviceContextSpec.h" // for nsIDeviceContextSpec
31 #include "nsLanguageAtomService.h" // for nsLanguageAtomService
32 #include "nsIObserver.h" // for nsIObserver, etc
33 #include "nsIObserverService.h" // for nsIObserverService
34 #include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc
35 #include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE
36 #include "nsIWidget.h" // for nsIWidget, NS_NATIVE_WINDOW
37 #include "nsRect.h" // for nsRect
38 #include "nsServiceManagerUtils.h" // for do_GetService
39 #include "nsString.h" // for nsDependentString
40 #include "nsTArray.h" // for nsTArray, nsTArray_Impl
41 #include "nsThreadUtils.h" // for NS_IsMainThread
42 #include "mozilla/gfx/Logging.h"
43 #include "mozilla/widget/ScreenManager.h" // for ScreenManager
45 using namespace mozilla
;
46 using namespace mozilla::gfx
;
47 using mozilla::widget::ScreenManager
;
49 nsDeviceContext::nsDeviceContext()
52 mAppUnitsPerDevPixel(-1),
53 mAppUnitsPerDevPixelAtUnitFullZoom(-1),
54 mAppUnitsPerPhysicalInch(-1),
57 mPrintingTranslate(gfxPoint(0, 0)),
58 mIsCurrentlyPrintingDoc(false) {
59 MOZ_ASSERT(NS_IsMainThread(), "nsDeviceContext created off main thread");
62 nsDeviceContext::~nsDeviceContext() = default;
64 void nsDeviceContext::SetDPI() {
67 // Use the printing DC to determine DPI values, if we have one.
68 if (mDeviceContextSpec
) {
69 dpi
= mDeviceContextSpec
->GetDPI();
70 mPrintingScale
= mDeviceContextSpec
->GetPrintingScale();
71 mPrintingTranslate
= mDeviceContextSpec
->GetPrintingTranslate();
72 mAppUnitsPerDevPixelAtUnitFullZoom
=
73 NS_lround((AppUnitsPerCSSPixel() * 96) / dpi
);
75 // A value of -1 means use the maximum of 96 and the system DPI.
76 // A value of 0 means use the system DPI. A positive value is used as the
77 // DPI. This sets the physical size of a device pixel and thus controls the
78 // interpretation of physical units.
79 int32_t prefDPI
= StaticPrefs::layout_css_dpi();
83 dpi
= mWidget
->GetDPI();
86 dpi
= std::max(96.0f
, dpi
);
92 CSSToLayoutDeviceScale scale
=
93 mWidget
? mWidget
->GetDefaultScale() : CSSToLayoutDeviceScale(1.0);
94 MOZ_ASSERT(scale
.scale
> 0.0);
95 mAppUnitsPerDevPixelAtUnitFullZoom
=
96 std::max(1, NS_lround(AppUnitsPerCSSPixel() / scale
.scale
));
99 NS_ASSERTION(dpi
!= -1.0, "no dpi set");
101 mAppUnitsPerPhysicalInch
=
102 NS_lround(dpi
* mAppUnitsPerDevPixelAtUnitFullZoom
);
103 UpdateAppUnitsForFullZoom();
106 void nsDeviceContext::Init(nsIWidget
* aWidget
) {
107 if (mIsInitialized
&& mWidget
== aWidget
) {
111 // We can't assert |!mIsInitialized| here since EndSwapDocShellsForDocument
112 // re-initializes nsDeviceContext objects. We can only assert in
113 // InitForPrinting (below).
114 mIsInitialized
= true;
120 // XXX This is only for printing. We should make that obvious in the name.
121 UniquePtr
<gfxContext
> nsDeviceContext::CreateRenderingContext() {
122 return CreateRenderingContextCommon(/* not a reference context */ false);
125 UniquePtr
<gfxContext
> nsDeviceContext::CreateReferenceRenderingContext() {
126 return CreateRenderingContextCommon(/* a reference context */ true);
129 UniquePtr
<gfxContext
> nsDeviceContext::CreateRenderingContextCommon(
130 bool aWantReferenceContext
) {
131 MOZ_ASSERT(IsPrinterContext());
132 MOZ_ASSERT(mWidth
> 0 && mHeight
> 0);
134 if (NS_WARN_IF(!mPrintTarget
)) {
135 // Printing canceled already.
139 RefPtr
<gfx::DrawTarget
> dt
;
140 if (aWantReferenceContext
) {
141 dt
= mPrintTarget
->GetReferenceDrawTarget();
143 // This will be null if printing a page from the parent process.
144 RefPtr
<DrawEventRecorder
> recorder
;
145 mDeviceContextSpec
->GetDrawEventRecorder(getter_AddRefs(recorder
));
146 dt
= mPrintTarget
->MakeDrawTarget(gfx::IntSize(mWidth
, mHeight
), recorder
);
149 if (!dt
|| !dt
->IsValid()) {
150 gfxCriticalNote
<< "Failed to create draw target in device context sized "
151 << mWidth
<< "x" << mHeight
<< " and pointer "
152 << hexa(mPrintTarget
);
156 dt
->AddUserData(&sDisablePixelSnapping
, (void*)0x1, nullptr);
158 auto pContext
= MakeUnique
<gfxContext
>(dt
);
161 transform
.PreTranslate(mPrintingTranslate
);
162 transform
.PreScale(mPrintingScale
, mPrintingScale
);
163 pContext
->SetMatrixDouble(transform
);
167 uint32_t nsDeviceContext::GetDepth() {
168 RefPtr
<widget::Screen
> screen
= FindScreen();
170 ScreenManager
& screenManager
= ScreenManager::GetSingleton();
171 screen
= screenManager
.GetPrimaryScreen();
175 screen
->GetColorDepth(&depth
);
176 return uint32_t(depth
);
179 dom::ScreenColorGamut
nsDeviceContext::GetColorGamut() {
180 RefPtr
<widget::Screen
> screen
= FindScreen();
182 auto& screenManager
= ScreenManager::GetSingleton();
183 screen
= screenManager
.GetPrimaryScreen();
186 dom::ScreenColorGamut colorGamut
;
187 screen
->GetColorGamut(&colorGamut
);
191 hal::ScreenOrientation
nsDeviceContext::GetScreenOrientationType() {
192 RefPtr
<widget::Screen
> screen
= FindScreen();
194 auto& screenManager
= ScreenManager::GetSingleton();
195 screen
= screenManager
.GetPrimaryScreen();
198 return screen
->GetOrientationType();
201 uint16_t nsDeviceContext::GetScreenOrientationAngle() {
202 RefPtr
<widget::Screen
> screen
= FindScreen();
204 auto& screenManager
= ScreenManager::GetSingleton();
205 screen
= screenManager
.GetPrimaryScreen();
208 return screen
->GetOrientationAngle();
211 nsresult
nsDeviceContext::GetDeviceSurfaceDimensions(nscoord
& aWidth
,
213 if (IsPrinterContext()) {
218 ComputeFullAreaUsingScreen(&area
);
219 aWidth
= area
.Width();
220 aHeight
= area
.Height();
226 nsresult
nsDeviceContext::GetRect(nsRect
& aRect
) {
227 if (IsPrinterContext()) {
228 aRect
.SetRect(0, 0, mWidth
, mHeight
);
230 ComputeFullAreaUsingScreen(&aRect
);
235 nsresult
nsDeviceContext::GetClientRect(nsRect
& aRect
) {
236 if (IsPrinterContext()) {
237 aRect
.SetRect(0, 0, mWidth
, mHeight
);
239 ComputeClientRectUsingScreen(&aRect
);
244 nsresult
nsDeviceContext::InitForPrinting(nsIDeviceContextSpec
* aDevice
) {
245 NS_ENSURE_ARG_POINTER(aDevice
);
247 MOZ_ASSERT(!mIsInitialized
,
248 "Only initialize once, immediately after construction");
250 // We don't set mIsInitialized here. The Init() call below does that.
252 mPrintTarget
= aDevice
->MakePrintTarget();
254 return NS_ERROR_FAILURE
;
257 mDeviceContextSpec
= aDevice
;
261 if (!CalcPrintingSize()) {
262 return NS_ERROR_FAILURE
;
268 nsresult
nsDeviceContext::BeginDocument(const nsAString
& aTitle
,
269 const nsAString
& aPrintToFileName
,
270 int32_t aStartPage
, int32_t aEndPage
) {
271 MOZ_DIAGNOSTIC_ASSERT(!mIsCurrentlyPrintingDoc
,
272 "Mismatched BeginDocument/EndDocument calls");
273 AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing
, {},
274 "nsDeviceContext::BeginDocument"_ns
);
276 nsresult rv
= mPrintTarget
->BeginPrinting(aTitle
, aPrintToFileName
,
277 aStartPage
, aEndPage
);
279 if (NS_SUCCEEDED(rv
)) {
280 if (mDeviceContextSpec
) {
281 rv
= mDeviceContextSpec
->BeginDocument(aTitle
, aPrintToFileName
,
282 aStartPage
, aEndPage
);
284 mIsCurrentlyPrintingDoc
= true;
287 // Warn about any failure (except user cancelling):
288 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
) || rv
== NS_ERROR_ABORT
,
289 "nsDeviceContext::BeginDocument failed");
294 RefPtr
<PrintEndDocumentPromise
> nsDeviceContext::EndDocument() {
295 MOZ_DIAGNOSTIC_ASSERT(mIsCurrentlyPrintingDoc
,
296 "Mismatched BeginDocument/EndDocument calls");
297 MOZ_DIAGNOSTIC_ASSERT(mPrintTarget
);
298 AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing
, {},
299 "nsDeviceContext::EndDocument"_ns
);
301 mIsCurrentlyPrintingDoc
= false;
304 auto result
= mPrintTarget
->EndPrinting();
305 if (NS_FAILED(result
)) {
306 return PrintEndDocumentPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE
,
309 mPrintTarget
->Finish();
310 mPrintTarget
= nullptr;
313 if (mDeviceContextSpec
) {
314 return mDeviceContextSpec
->EndDocument();
317 return PrintEndDocumentPromise::CreateAndResolve(true, __func__
);
320 nsresult
nsDeviceContext::AbortDocument() {
321 MOZ_DIAGNOSTIC_ASSERT(mIsCurrentlyPrintingDoc
,
322 "Mismatched BeginDocument/EndDocument calls");
323 AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing
, {},
324 "nsDeviceContext::AbortDocument"_ns
);
326 nsresult rv
= mPrintTarget
->AbortPrinting();
327 mIsCurrentlyPrintingDoc
= false;
329 if (mDeviceContextSpec
) {
330 Unused
<< mDeviceContextSpec
->EndDocument();
333 mPrintTarget
= nullptr;
338 nsresult
nsDeviceContext::BeginPage(const IntSize
& aSizeInPoints
) {
339 MOZ_DIAGNOSTIC_ASSERT(!mIsCurrentlyPrintingDoc
|| mPrintTarget
,
340 "What nulled out our print target while printing?");
341 AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing
, {},
342 "nsDeviceContext::BeginPage"_ns
);
344 if (mDeviceContextSpec
) {
345 MOZ_TRY(mDeviceContextSpec
->BeginPage(aSizeInPoints
));
348 MOZ_TRY(mPrintTarget
->BeginPage(aSizeInPoints
));
353 nsresult
nsDeviceContext::EndPage() {
354 MOZ_DIAGNOSTIC_ASSERT(!mIsCurrentlyPrintingDoc
|| mPrintTarget
,
355 "What nulled out our print target while printing?");
356 AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing
, {},
357 "nsDeviceContext::EndPage"_ns
);
360 MOZ_TRY(mPrintTarget
->EndPage());
362 if (mDeviceContextSpec
) {
363 MOZ_TRY(mDeviceContextSpec
->EndPage());
368 void nsDeviceContext::ComputeClientRectUsingScreen(nsRect
* outRect
) {
369 // we always need to recompute the clientRect
370 // because the window may have moved onto a different screen. In the single
371 // monitor case, we only need to do the computation if we haven't done it
372 // once already, and remember that we have because we're assured it won't
374 if (RefPtr
<widget::Screen
> screen
= FindScreen()) {
375 *outRect
= LayoutDeviceIntRect::ToAppUnits(screen
->GetAvailRect(),
376 AppUnitsPerDevPixel());
380 void nsDeviceContext::ComputeFullAreaUsingScreen(nsRect
* outRect
) {
381 // if we have more than one screen, we always need to recompute the clientRect
382 // because the window may have moved onto a different screen. In the single
383 // monitor case, we only need to do the computation if we haven't done it
384 // once already, and remember that we have because we're assured it won't
386 if (RefPtr
<widget::Screen
> screen
= FindScreen()) {
387 *outRect
= LayoutDeviceIntRect::ToAppUnits(screen
->GetRect(),
388 AppUnitsPerDevPixel());
389 mWidth
= outRect
->Width();
390 mHeight
= outRect
->Height();
397 // Determines which screen intersects the largest area of the given surface.
399 already_AddRefed
<widget::Screen
> nsDeviceContext::FindScreen() {
406 if (RefPtr
<widget::Screen
> screen
= mWidget
->GetWidgetScreen()) {
407 return screen
.forget();
410 ScreenManager
& screenManager
= ScreenManager::GetSingleton();
411 return screenManager
.GetPrimaryScreen();
414 bool nsDeviceContext::CalcPrintingSize() {
415 gfxSize
size(mPrintTarget
->GetSize());
416 // For printing, CSS inches and physical inches are identical
417 // so it doesn't matter which we use here
418 mWidth
= NSToCoordRound(size
.width
* AppUnitsPerPhysicalInch() /
419 POINTS_PER_INCH_FLOAT
);
420 mHeight
= NSToCoordRound(size
.height
* AppUnitsPerPhysicalInch() /
421 POINTS_PER_INCH_FLOAT
);
423 return (mWidth
> 0 && mHeight
> 0);
426 bool nsDeviceContext::CheckDPIChange() {
427 int32_t oldDevPixels
= mAppUnitsPerDevPixelAtUnitFullZoom
;
428 int32_t oldInches
= mAppUnitsPerPhysicalInch
;
432 return oldDevPixels
!= mAppUnitsPerDevPixelAtUnitFullZoom
||
433 oldInches
!= mAppUnitsPerPhysicalInch
;
436 bool nsDeviceContext::SetFullZoom(float aScale
) {
438 MOZ_ASSERT_UNREACHABLE("Invalid full zoom value");
441 int32_t oldAppUnitsPerDevPixel
= mAppUnitsPerDevPixel
;
443 UpdateAppUnitsForFullZoom();
444 return oldAppUnitsPerDevPixel
!= mAppUnitsPerDevPixel
;
447 static int32_t ApplyFullZoom(int32_t aUnzoomedAppUnits
, float aFullZoom
) {
448 if (aFullZoom
== 1.0f
) {
449 return aUnzoomedAppUnits
;
451 return std::max(1, NSToIntRound(float(aUnzoomedAppUnits
) / aFullZoom
));
454 int32_t nsDeviceContext::AppUnitsPerDevPixelInTopLevelChromePage() const {
455 // The only zoom that applies to chrome pages is the system zoom, if any.
456 return ApplyFullZoom(mAppUnitsPerDevPixelAtUnitFullZoom
,
457 LookAndFeel::SystemZoomSettings().mFullZoom
);
460 void nsDeviceContext::UpdateAppUnitsForFullZoom() {
461 mAppUnitsPerDevPixel
=
462 ApplyFullZoom(mAppUnitsPerDevPixelAtUnitFullZoom
, mFullZoom
);
463 // adjust mFullZoom to reflect appunit rounding
464 mFullZoom
= float(mAppUnitsPerDevPixelAtUnitFullZoom
) / mAppUnitsPerDevPixel
;
467 DesktopToLayoutDeviceScale
nsDeviceContext::GetDesktopToDeviceScale() {
468 if (RefPtr
<widget::Screen
> screen
= FindScreen()) {
469 return screen
->GetDesktopToLayoutDeviceScale();
471 return DesktopToLayoutDeviceScale(1.0);