summaryrefslogtreecommitdiffstats
path: root/gfx/src/nsDeviceContext.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--gfx/src/nsDeviceContext.cpp472
1 files changed, 472 insertions, 0 deletions
diff --git a/gfx/src/nsDeviceContext.cpp b/gfx/src/nsDeviceContext.cpp
new file mode 100644
index 0000000000..28b2c34652
--- /dev/null
+++ b/gfx/src/nsDeviceContext.cpp
@@ -0,0 +1,472 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=2 expandtab: */
+/* 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 "nsDeviceContext.h"
+#include <algorithm> // for max
+#include "gfxContext.h"
+#include "gfxImageSurface.h" // for gfxImageSurface
+#include "gfxPoint.h" // for gfxSize
+#include "gfxTextRun.h" // for gfxFontGroup
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/gfx/PrintTarget.h"
+#include "mozilla/Preferences.h" // for Preferences
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/Services.h" // for GetObserverService
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/Try.h" // for MOZ_TRY
+#include "mozilla/mozalloc.h" // for operator new
+#include "mozilla/widget/Screen.h" // for Screen
+#include "nsCRT.h" // for nsCRT
+#include "nsDebug.h" // for NS_ASSERTION, etc
+#include "nsFont.h" // for nsFont
+#include "nsFontCache.h" // for nsFontCache
+#include "nsFontMetrics.h" // for nsFontMetrics
+#include "nsAtom.h" // for nsAtom, NS_Atomize
+#include "nsID.h"
+#include "nsIDeviceContextSpec.h" // for nsIDeviceContextSpec
+#include "nsLanguageAtomService.h" // for nsLanguageAtomService
+#include "nsIObserver.h" // for nsIObserver, etc
+#include "nsIObserverService.h" // for nsIObserverService
+#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc
+#include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE
+#include "nsIWidget.h" // for nsIWidget, NS_NATIVE_WINDOW
+#include "nsRect.h" // for nsRect
+#include "nsServiceManagerUtils.h" // for do_GetService
+#include "nsString.h" // for nsDependentString
+#include "nsTArray.h" // for nsTArray, nsTArray_Impl
+#include "nsThreadUtils.h" // for NS_IsMainThread
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/widget/ScreenManager.h" // for ScreenManager
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using mozilla::widget::ScreenManager;
+
+nsDeviceContext::nsDeviceContext()
+ : mWidth(0),
+ mHeight(0),
+ mAppUnitsPerDevPixel(-1),
+ mAppUnitsPerDevPixelAtUnitFullZoom(-1),
+ mAppUnitsPerPhysicalInch(-1),
+ mFullZoom(1.0f),
+ mPrintingScale(1.0f),
+ mPrintingTranslate(gfxPoint(0, 0)),
+ mIsCurrentlyPrintingDoc(false) {
+ MOZ_ASSERT(NS_IsMainThread(), "nsDeviceContext created off main thread");
+}
+
+nsDeviceContext::~nsDeviceContext() = default;
+
+void nsDeviceContext::SetDPI() {
+ float dpi;
+
+ // Use the printing DC to determine DPI values, if we have one.
+ if (mDeviceContextSpec) {
+ dpi = mDeviceContextSpec->GetDPI();
+ mPrintingScale = mDeviceContextSpec->GetPrintingScale();
+ mPrintingTranslate = mDeviceContextSpec->GetPrintingTranslate();
+ mAppUnitsPerDevPixelAtUnitFullZoom =
+ NS_lround((AppUnitsPerCSSPixel() * 96) / dpi);
+ } else {
+ // A value of -1 means use the maximum of 96 and the system DPI.
+ // A value of 0 means use the system DPI. A positive value is used as the
+ // DPI. This sets the physical size of a device pixel and thus controls the
+ // interpretation of physical units.
+ int32_t prefDPI = StaticPrefs::layout_css_dpi();
+ if (prefDPI > 0) {
+ dpi = prefDPI;
+ } else if (mWidget) {
+ dpi = mWidget->GetDPI();
+ MOZ_ASSERT(dpi > 0);
+ if (prefDPI < 0) {
+ dpi = std::max(96.0f, dpi);
+ }
+ } else {
+ dpi = 96.0f;
+ }
+
+ CSSToLayoutDeviceScale scale =
+ mWidget ? mWidget->GetDefaultScale() : CSSToLayoutDeviceScale(1.0);
+ MOZ_ASSERT(scale.scale > 0.0);
+ mAppUnitsPerDevPixelAtUnitFullZoom =
+ std::max(1, NS_lround(AppUnitsPerCSSPixel() / scale.scale));
+ }
+
+ NS_ASSERTION(dpi != -1.0, "no dpi set");
+
+ mAppUnitsPerPhysicalInch =
+ NS_lround(dpi * mAppUnitsPerDevPixelAtUnitFullZoom);
+ UpdateAppUnitsForFullZoom();
+}
+
+void nsDeviceContext::Init(nsIWidget* aWidget) {
+ if (mIsInitialized && mWidget == aWidget) {
+ return;
+ }
+
+ // We can't assert |!mIsInitialized| here since EndSwapDocShellsForDocument
+ // re-initializes nsDeviceContext objects. We can only assert in
+ // InitForPrinting (below).
+ mIsInitialized = true;
+
+ mWidget = aWidget;
+ SetDPI();
+}
+
+// XXX This is only for printing. We should make that obvious in the name.
+UniquePtr<gfxContext> nsDeviceContext::CreateRenderingContext() {
+ return CreateRenderingContextCommon(/* not a reference context */ false);
+}
+
+UniquePtr<gfxContext> nsDeviceContext::CreateReferenceRenderingContext() {
+ return CreateRenderingContextCommon(/* a reference context */ true);
+}
+
+UniquePtr<gfxContext> nsDeviceContext::CreateRenderingContextCommon(
+ bool aWantReferenceContext) {
+ MOZ_ASSERT(IsPrinterContext());
+ MOZ_ASSERT(mWidth > 0 && mHeight > 0);
+
+ if (NS_WARN_IF(!mPrintTarget)) {
+ // Printing canceled already.
+ return nullptr;
+ }
+
+ RefPtr<gfx::DrawTarget> dt;
+ if (aWantReferenceContext) {
+ dt = mPrintTarget->GetReferenceDrawTarget();
+ } else {
+ // This will be null if printing a page from the parent process.
+ RefPtr<DrawEventRecorder> recorder;
+ mDeviceContextSpec->GetDrawEventRecorder(getter_AddRefs(recorder));
+ dt = mPrintTarget->MakeDrawTarget(gfx::IntSize(mWidth, mHeight), recorder);
+ }
+
+ if (!dt || !dt->IsValid()) {
+ gfxCriticalNote << "Failed to create draw target in device context sized "
+ << mWidth << "x" << mHeight << " and pointer "
+ << hexa(mPrintTarget);
+ return nullptr;
+ }
+
+ dt->AddUserData(&sDisablePixelSnapping, (void*)0x1, nullptr);
+
+ auto pContext = MakeUnique<gfxContext>(dt);
+
+ gfxMatrix transform;
+ transform.PreTranslate(mPrintingTranslate);
+ transform.PreScale(mPrintingScale, mPrintingScale);
+ pContext->SetMatrixDouble(transform);
+ return pContext;
+}
+
+uint32_t nsDeviceContext::GetDepth() {
+ RefPtr<widget::Screen> screen = FindScreen();
+ if (!screen) {
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ screen = screenManager.GetPrimaryScreen();
+ MOZ_ASSERT(screen);
+ }
+ int32_t depth = 0;
+ screen->GetColorDepth(&depth);
+ return uint32_t(depth);
+}
+
+dom::ScreenColorGamut nsDeviceContext::GetColorGamut() {
+ RefPtr<widget::Screen> screen = FindScreen();
+ if (!screen) {
+ auto& screenManager = ScreenManager::GetSingleton();
+ screen = screenManager.GetPrimaryScreen();
+ MOZ_ASSERT(screen);
+ }
+ dom::ScreenColorGamut colorGamut;
+ screen->GetColorGamut(&colorGamut);
+ return colorGamut;
+}
+
+hal::ScreenOrientation nsDeviceContext::GetScreenOrientationType() {
+ RefPtr<widget::Screen> screen = FindScreen();
+ if (!screen) {
+ auto& screenManager = ScreenManager::GetSingleton();
+ screen = screenManager.GetPrimaryScreen();
+ MOZ_ASSERT(screen);
+ }
+ return screen->GetOrientationType();
+}
+
+uint16_t nsDeviceContext::GetScreenOrientationAngle() {
+ RefPtr<widget::Screen> screen = FindScreen();
+ if (!screen) {
+ auto& screenManager = ScreenManager::GetSingleton();
+ screen = screenManager.GetPrimaryScreen();
+ MOZ_ASSERT(screen);
+ }
+ return screen->GetOrientationAngle();
+}
+
+nsresult nsDeviceContext::GetDeviceSurfaceDimensions(nscoord& aWidth,
+ nscoord& aHeight) {
+ if (IsPrinterContext()) {
+ aWidth = mWidth;
+ aHeight = mHeight;
+ } else {
+ nsRect area;
+ ComputeFullAreaUsingScreen(&area);
+ aWidth = area.Width();
+ aHeight = area.Height();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDeviceContext::GetRect(nsRect& aRect) {
+ if (IsPrinterContext()) {
+ aRect.SetRect(0, 0, mWidth, mHeight);
+ } else
+ ComputeFullAreaUsingScreen(&aRect);
+
+ return NS_OK;
+}
+
+nsresult nsDeviceContext::GetClientRect(nsRect& aRect) {
+ if (IsPrinterContext()) {
+ aRect.SetRect(0, 0, mWidth, mHeight);
+ } else
+ ComputeClientRectUsingScreen(&aRect);
+
+ return NS_OK;
+}
+
+nsresult nsDeviceContext::InitForPrinting(nsIDeviceContextSpec* aDevice) {
+ NS_ENSURE_ARG_POINTER(aDevice);
+
+ MOZ_ASSERT(!mIsInitialized,
+ "Only initialize once, immediately after construction");
+
+ // We don't set mIsInitialized here. The Init() call below does that.
+
+ mPrintTarget = aDevice->MakePrintTarget();
+ if (!mPrintTarget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mDeviceContextSpec = aDevice;
+
+ Init(nullptr);
+
+ if (!CalcPrintingSize()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDeviceContext::BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) {
+ MOZ_DIAGNOSTIC_ASSERT(!mIsCurrentlyPrintingDoc,
+ "Mismatched BeginDocument/EndDocument calls");
+ AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {},
+ "nsDeviceContext::BeginDocument"_ns);
+
+ nsresult rv = mPrintTarget->BeginPrinting(aTitle, aPrintToFileName,
+ aStartPage, aEndPage);
+
+ if (NS_SUCCEEDED(rv)) {
+ if (mDeviceContextSpec) {
+ rv = mDeviceContextSpec->BeginDocument(aTitle, aPrintToFileName,
+ aStartPage, aEndPage);
+ }
+ mIsCurrentlyPrintingDoc = true;
+ }
+
+ // Warn about any failure (except user cancelling):
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv) || rv == NS_ERROR_ABORT,
+ "nsDeviceContext::BeginDocument failed");
+
+ return rv;
+}
+
+RefPtr<PrintEndDocumentPromise> nsDeviceContext::EndDocument() {
+ MOZ_DIAGNOSTIC_ASSERT(mIsCurrentlyPrintingDoc,
+ "Mismatched BeginDocument/EndDocument calls");
+ MOZ_DIAGNOSTIC_ASSERT(mPrintTarget);
+ AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {},
+ "nsDeviceContext::EndDocument"_ns);
+
+ mIsCurrentlyPrintingDoc = false;
+
+ if (mPrintTarget) {
+ auto result = mPrintTarget->EndPrinting();
+ if (NS_FAILED(result)) {
+ return PrintEndDocumentPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
+ __func__);
+ }
+ mPrintTarget->Finish();
+ mPrintTarget = nullptr;
+ }
+
+ if (mDeviceContextSpec) {
+ return mDeviceContextSpec->EndDocument();
+ }
+
+ return PrintEndDocumentPromise::CreateAndResolve(true, __func__);
+}
+
+nsresult nsDeviceContext::AbortDocument() {
+ MOZ_DIAGNOSTIC_ASSERT(mIsCurrentlyPrintingDoc,
+ "Mismatched BeginDocument/EndDocument calls");
+ AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {},
+ "nsDeviceContext::AbortDocument"_ns);
+
+ nsresult rv = mPrintTarget->AbortPrinting();
+ mIsCurrentlyPrintingDoc = false;
+
+ if (mDeviceContextSpec) {
+ Unused << mDeviceContextSpec->EndDocument();
+ }
+
+ mPrintTarget = nullptr;
+
+ return rv;
+}
+
+nsresult nsDeviceContext::BeginPage(const IntSize& aSizeInPoints) {
+ MOZ_DIAGNOSTIC_ASSERT(!mIsCurrentlyPrintingDoc || mPrintTarget,
+ "What nulled out our print target while printing?");
+ AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {},
+ "nsDeviceContext::BeginPage"_ns);
+
+ if (mDeviceContextSpec) {
+ MOZ_TRY(mDeviceContextSpec->BeginPage(aSizeInPoints));
+ }
+ if (mPrintTarget) {
+ MOZ_TRY(mPrintTarget->BeginPage(aSizeInPoints));
+ }
+ return NS_OK;
+}
+
+nsresult nsDeviceContext::EndPage() {
+ MOZ_DIAGNOSTIC_ASSERT(!mIsCurrentlyPrintingDoc || mPrintTarget,
+ "What nulled out our print target while printing?");
+ AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {},
+ "nsDeviceContext::EndPage"_ns);
+
+ if (mPrintTarget) {
+ MOZ_TRY(mPrintTarget->EndPage());
+ }
+ if (mDeviceContextSpec) {
+ MOZ_TRY(mDeviceContextSpec->EndPage());
+ }
+ return NS_OK;
+}
+
+void nsDeviceContext::ComputeClientRectUsingScreen(nsRect* outRect) {
+ // we always need to recompute the clientRect
+ // because the window may have moved onto a different screen. In the single
+ // monitor case, we only need to do the computation if we haven't done it
+ // once already, and remember that we have because we're assured it won't
+ // change.
+ if (RefPtr<widget::Screen> screen = FindScreen()) {
+ *outRect = LayoutDeviceIntRect::ToAppUnits(screen->GetAvailRect(),
+ AppUnitsPerDevPixel());
+ }
+}
+
+void nsDeviceContext::ComputeFullAreaUsingScreen(nsRect* outRect) {
+ // if we have more than one screen, we always need to recompute the clientRect
+ // because the window may have moved onto a different screen. In the single
+ // monitor case, we only need to do the computation if we haven't done it
+ // once already, and remember that we have because we're assured it won't
+ // change.
+ if (RefPtr<widget::Screen> screen = FindScreen()) {
+ *outRect = LayoutDeviceIntRect::ToAppUnits(screen->GetRect(),
+ AppUnitsPerDevPixel());
+ mWidth = outRect->Width();
+ mHeight = outRect->Height();
+ }
+}
+
+//
+// FindScreen
+//
+// Determines which screen intersects the largest area of the given surface.
+//
+already_AddRefed<widget::Screen> nsDeviceContext::FindScreen() {
+ if (!mWidget) {
+ return nullptr;
+ }
+
+ CheckDPIChange();
+
+ if (RefPtr<widget::Screen> screen = mWidget->GetWidgetScreen()) {
+ return screen.forget();
+ }
+
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ return screenManager.GetPrimaryScreen();
+}
+
+bool nsDeviceContext::CalcPrintingSize() {
+ gfxSize size(mPrintTarget->GetSize());
+ // For printing, CSS inches and physical inches are identical
+ // so it doesn't matter which we use here
+ mWidth = NSToCoordRound(size.width * AppUnitsPerPhysicalInch() /
+ POINTS_PER_INCH_FLOAT);
+ mHeight = NSToCoordRound(size.height * AppUnitsPerPhysicalInch() /
+ POINTS_PER_INCH_FLOAT);
+
+ return (mWidth > 0 && mHeight > 0);
+}
+
+bool nsDeviceContext::CheckDPIChange() {
+ int32_t oldDevPixels = mAppUnitsPerDevPixelAtUnitFullZoom;
+ int32_t oldInches = mAppUnitsPerPhysicalInch;
+
+ SetDPI();
+
+ return oldDevPixels != mAppUnitsPerDevPixelAtUnitFullZoom ||
+ oldInches != mAppUnitsPerPhysicalInch;
+}
+
+bool nsDeviceContext::SetFullZoom(float aScale) {
+ if (aScale <= 0) {
+ MOZ_ASSERT_UNREACHABLE("Invalid full zoom value");
+ return false;
+ }
+ int32_t oldAppUnitsPerDevPixel = mAppUnitsPerDevPixel;
+ mFullZoom = aScale;
+ UpdateAppUnitsForFullZoom();
+ return oldAppUnitsPerDevPixel != mAppUnitsPerDevPixel;
+}
+
+static int32_t ApplyFullZoom(int32_t aUnzoomedAppUnits, float aFullZoom) {
+ if (aFullZoom == 1.0f) {
+ return aUnzoomedAppUnits;
+ }
+ return std::max(1, NSToIntRound(float(aUnzoomedAppUnits) / aFullZoom));
+}
+
+int32_t nsDeviceContext::AppUnitsPerDevPixelInTopLevelChromePage() const {
+ // The only zoom that applies to chrome pages is the system zoom, if any.
+ return ApplyFullZoom(mAppUnitsPerDevPixelAtUnitFullZoom,
+ LookAndFeel::SystemZoomSettings().mFullZoom);
+}
+
+void nsDeviceContext::UpdateAppUnitsForFullZoom() {
+ mAppUnitsPerDevPixel =
+ ApplyFullZoom(mAppUnitsPerDevPixelAtUnitFullZoom, mFullZoom);
+ // adjust mFullZoom to reflect appunit rounding
+ mFullZoom = float(mAppUnitsPerDevPixelAtUnitFullZoom) / mAppUnitsPerDevPixel;
+}
+
+DesktopToLayoutDeviceScale nsDeviceContext::GetDesktopToDeviceScale() {
+ if (RefPtr<widget::Screen> screen = FindScreen()) {
+ return screen->GetDesktopToLayoutDeviceScale();
+ }
+ return DesktopToLayoutDeviceScale(1.0);
+}