Bug 1732219 - Add API for fetching the preview image. r=geckoview-reviewers,agi,mconley
[gecko.git] / gfx / src / nsDeviceContext.cpp
blobb37b408808786e848ba2961e43112c15e83b97c6
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
25 #include "nsID.h"
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()
48 : mWidth(0),
49 mHeight(0),
50 mAppUnitsPerDevPixel(-1),
51 mAppUnitsPerDevPixelAtUnitFullZoom(-1),
52 mAppUnitsPerPhysicalInch(-1),
53 mFullZoom(1.0f),
54 mPrintingScale(1.0f),
55 mPrintingTranslate(gfxPoint(0, 0)),
56 mIsCurrentlyPrintingDoc(false)
57 #ifdef DEBUG
59 mIsInitialized(false)
60 #endif
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) {
70 float dpi;
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);
79 } else {
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);
91 if (prefDPI > 0) {
92 dpi = prefDPI;
93 } else if (mWidget) {
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
97 // value as default.
98 if (dpi < 0) {
99 primaryScreen->GetDpi(&dpi);
101 if (prefDPI < 0) {
102 dpi = std::max(96.0f, dpi);
104 } else {
105 dpi = 96.0f;
108 double devPixelsPerCSSPixel;
109 if (aScale && *aScale > 0.0) {
110 // if caller provided a scale, we just use it
111 devPixelsPerCSSPixel = *aScale;
112 } else {
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
119 // value as default.
120 if (devPixelsPerCSSPixel < 0) {
121 primaryScreen->GetDefaultCSSScaleFactor(&devPixelsPerCSSPixel);
123 if (aScale) {
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) {
140 #ifdef DEBUG
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;
145 #endif
147 nsresult rv = NS_OK;
148 if (mScreenManager && mWidget == aWidget) return rv;
150 mWidget = aWidget;
151 SetDPI();
153 if (mScreenManager) return rv;
155 mScreenManager = do_GetService("@mozilla.org/gfx/screenmanager;1", &rv);
157 return 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();
178 } else {
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);
189 return nullptr;
192 dt->AddUserData(&sDisablePixelSnapping, (void*)0x1, nullptr);
194 RefPtr<gfxContext> pContext = gfxContext::CreateOrNull(dt);
195 MOZ_ASSERT(pContext); // already checked draw target above
197 gfxMatrix transform;
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));
215 if (!screen) {
216 ScreenManager& screenManager = ScreenManager::GetSingleton();
217 screenManager.GetPrimaryScreen(getter_AddRefs(screen));
218 MOZ_ASSERT(screen);
220 screen->GetColorDepth(reinterpret_cast<int32_t*>(&aDepth));
222 return NS_OK;
225 nsresult nsDeviceContext::GetDeviceSurfaceDimensions(nscoord& aWidth,
226 nscoord& aHeight) {
227 if (IsPrinterContext()) {
228 aWidth = mWidth;
229 aHeight = mHeight;
230 } else {
231 nsRect area;
232 ComputeFullAreaUsingScreen(&area);
233 aWidth = area.Width();
234 aHeight = area.Height();
237 return NS_OK;
240 nsresult nsDeviceContext::GetRect(nsRect& aRect) {
241 if (IsPrinterContext()) {
242 aRect.SetRect(0, 0, mWidth, mHeight);
243 } else
244 ComputeFullAreaUsingScreen(&aRect);
246 return NS_OK;
249 nsresult nsDeviceContext::GetClientRect(nsRect& aRect) {
250 if (IsPrinterContext()) {
251 aRect.SetRect(0, 0, mWidth, mHeight);
252 } else
253 ComputeClientRectUsingScreen(&aRect);
255 return NS_OK;
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();
267 if (!mPrintTarget) {
268 return NS_ERROR_FAILURE;
271 mDeviceContextSpec = aDevice;
273 Init(nullptr);
275 if (!CalcPrintingSize()) {
276 return NS_ERROR_FAILURE;
279 return NS_OK;
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");
303 return rv;
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;
321 return rv;
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;
336 return rv;
339 nsresult nsDeviceContext::BeginPage(void) {
340 nsresult rv = NS_OK;
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();
354 return rv;
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
362 // change.
363 nsCOMPtr<nsIScreen> screen;
364 FindScreen(getter_AddRefs(screen));
365 if (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
382 // change.
383 nsCOMPtr<nsIScreen> screen;
384 FindScreen(getter_AddRefs(screen));
385 if (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();
400 // FindScreen
402 // Determines which screen intersects the largest area of the given surface.
404 void nsDeviceContext::FindScreen(nsIScreen** outScreen) {
405 if (!mWidget || !mScreenManager) {
406 return;
409 CheckDPIChange();
411 nsCOMPtr<nsIScreen> screen = mWidget->GetWidgetScreen();
412 screen.forget(outScreen);
414 if (!(*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;
435 SetDPI(aScale);
437 return oldDevPixels != mAppUnitsPerDevPixelAtUnitFullZoom ||
438 oldInches != mAppUnitsPerPhysicalInch;
441 bool nsDeviceContext::SetFullZoom(float aScale) {
442 if (aScale <= 0) {
443 MOZ_ASSERT_UNREACHABLE("Invalid full zoom value");
444 return false;
446 int32_t oldAppUnitsPerDevPixel = mAppUnitsPerDevPixel;
447 mFullZoom = aScale;
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));
463 if (screen) {
464 double scale;
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() {
483 if (mPrintTarget) {
484 mPrintTarget->UnregisterPageDoneCallback();