/* -*- 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 // 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 nsDeviceContext::CreateRenderingContext() { return CreateRenderingContextCommon(/* not a reference context */ false); } UniquePtr nsDeviceContext::CreateReferenceRenderingContext() { return CreateRenderingContextCommon(/* a reference context */ true); } UniquePtr nsDeviceContext::CreateRenderingContextCommon( bool aWantReferenceContext) { MOZ_ASSERT(IsPrinterContext()); MOZ_ASSERT(mWidth > 0 && mHeight > 0); if (NS_WARN_IF(!mPrintTarget)) { // Printing canceled already. return nullptr; } RefPtr dt; if (aWantReferenceContext) { dt = mPrintTarget->GetReferenceDrawTarget(); } else { // This will be null if printing a page from the parent process. RefPtr 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(dt); gfxMatrix transform; transform.PreTranslate(mPrintingTranslate); transform.PreScale(mPrintingScale, mPrintingScale); pContext->SetMatrixDouble(transform); return pContext; } uint32_t nsDeviceContext::GetDepth() { RefPtr 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 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 screen = FindScreen(); if (!screen) { auto& screenManager = ScreenManager::GetSingleton(); screen = screenManager.GetPrimaryScreen(); MOZ_ASSERT(screen); } return screen->GetOrientationType(); } uint16_t nsDeviceContext::GetScreenOrientationAngle() { RefPtr screen = FindScreen(); if (!screen) { auto& screenManager = ScreenManager::GetSingleton(); screen = screenManager.GetPrimaryScreen(); MOZ_ASSERT(screen); } return screen->GetOrientationAngle(); } bool nsDeviceContext::GetScreenIsHDR() { RefPtr screen = FindScreen(); if (!screen) { auto& screenManager = ScreenManager::GetSingleton(); screen = screenManager.GetPrimaryScreen(); MOZ_ASSERT(screen); } return screen->GetIsHDR(); } 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 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 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 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 nsDeviceContext::FindScreen() { if (!mWidget) { return nullptr; } CheckDPIChange(); if (RefPtr 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 screen = FindScreen()) { return screen->GetDesktopToLayoutDeviceScale(); } return DesktopToLayoutDeviceScale(1.0); }