From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- gfx/src/nsDeviceContext.cpp | 725 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 725 insertions(+) create mode 100644 gfx/src/nsDeviceContext.cpp (limited to 'gfx/src/nsDeviceContext.cpp') diff --git a/gfx/src/nsDeviceContext.cpp b/gfx/src/nsDeviceContext.cpp new file mode 100644 index 000000000..8a5c16973 --- /dev/null +++ b/gfx/src/nsDeviceContext.cpp @@ -0,0 +1,725 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set sw=4 ts=4 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 "gfxASurface.h" // for gfxASurface, etc +#include "gfxContext.h" +#include "gfxFont.h" // for gfxFontGroup +#include "gfxImageSurface.h" // for gfxImageSurface +#include "gfxPoint.h" // for gfxSize +#include "mozilla/Attributes.h" // for final +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/gfx/PrintTarget.h" +#include "mozilla/Preferences.h" // for Preferences +#include "mozilla/Services.h" // for GetObserverService +#include "mozilla/mozalloc.h" // for operator new +#include "nsCRT.h" // for nsCRT +#include "nsDebug.h" // for NS_NOTREACHED, NS_ASSERTION, etc +#include "nsFont.h" // for nsFont +#include "nsFontMetrics.h" // for nsFontMetrics +#include "nsIAtom.h" // for nsIAtom, NS_Atomize +#include "nsID.h" +#include "nsIDeviceContextSpec.h" // for nsIDeviceContextSpec +#include "nsILanguageAtomService.h" // for nsILanguageAtomService, etc +#include "nsIObserver.h" // for nsIObserver, etc +#include "nsIObserverService.h" // for nsIObserverService +#include "nsIScreen.h" // for nsIScreen +#include "nsIScreenManager.h" // for nsIScreenManager +#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" + +using namespace mozilla; +using namespace mozilla::gfx; +using mozilla::services::GetObserverService; + +class nsFontCache final : public nsIObserver +{ +public: + nsFontCache() { MOZ_COUNT_CTOR(nsFontCache); } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + void Init(nsDeviceContext* aContext); + void Destroy(); + + already_AddRefed GetMetricsFor( + const nsFont& aFont, const nsFontMetrics::Params& aParams); + + void FontMetricsDeleted(const nsFontMetrics* aFontMetrics); + void Compact(); + void Flush(); + +protected: + ~nsFontCache() { MOZ_COUNT_DTOR(nsFontCache); } + + nsDeviceContext* mContext; // owner + nsCOMPtr mLocaleLanguage; + nsTArray mFontMetrics; +}; + +NS_IMPL_ISUPPORTS(nsFontCache, nsIObserver) + +// The Init and Destroy methods are necessary because it's not +// safe to call AddObserver from a constructor or RemoveObserver +// from a destructor. That should be fixed. +void +nsFontCache::Init(nsDeviceContext* aContext) +{ + mContext = aContext; + // register as a memory-pressure observer to free font resources + // in low-memory situations. + nsCOMPtr obs = GetObserverService(); + if (obs) + obs->AddObserver(this, "memory-pressure", false); + + nsCOMPtr langService; + langService = do_GetService(NS_LANGUAGEATOMSERVICE_CONTRACTID); + if (langService) { + mLocaleLanguage = langService->GetLocaleLanguage(); + } + if (!mLocaleLanguage) { + mLocaleLanguage = NS_Atomize("x-western"); + } +} + +void +nsFontCache::Destroy() +{ + nsCOMPtr obs = GetObserverService(); + if (obs) + obs->RemoveObserver(this, "memory-pressure"); + Flush(); +} + +NS_IMETHODIMP +nsFontCache::Observe(nsISupports*, const char* aTopic, const char16_t*) +{ + if (!nsCRT::strcmp(aTopic, "memory-pressure")) + Compact(); + return NS_OK; +} + +already_AddRefed +nsFontCache::GetMetricsFor(const nsFont& aFont, + const nsFontMetrics::Params& aParams) +{ + nsIAtom* language = aParams.language ? aParams.language + : mLocaleLanguage.get(); + + // First check our cache + // start from the end, which is where we put the most-recent-used element + + int32_t n = mFontMetrics.Length() - 1; + for (int32_t i = n; i >= 0; --i) { + nsFontMetrics* fm = mFontMetrics[i]; + if (fm->Font().Equals(aFont) && + fm->GetUserFontSet() == aParams.userFontSet && + fm->Language() == language && + fm->Orientation() == aParams.orientation) { + if (i != n) { + // promote it to the end of the cache + mFontMetrics.RemoveElementAt(i); + mFontMetrics.AppendElement(fm); + } + fm->GetThebesFontGroup()->UpdateUserFonts(); + return do_AddRef(fm); + } + } + + // It's not in the cache. Get font metrics and then cache them. + + nsFontMetrics::Params params = aParams; + params.language = language; + RefPtr fm = new nsFontMetrics(aFont, params, mContext); + // the mFontMetrics list has the "head" at the end, because append + // is cheaper than insert + mFontMetrics.AppendElement(do_AddRef(fm.get()).take()); + return fm.forget(); +} + +void +nsFontCache::FontMetricsDeleted(const nsFontMetrics* aFontMetrics) +{ + mFontMetrics.RemoveElement(aFontMetrics); +} + +void +nsFontCache::Compact() +{ + // Need to loop backward because the running element can be removed on + // the way + for (int32_t i = mFontMetrics.Length()-1; i >= 0; --i) { + nsFontMetrics* fm = mFontMetrics[i]; + nsFontMetrics* oldfm = fm; + // Destroy() isn't here because we want our device context to be + // notified + NS_RELEASE(fm); // this will reset fm to nullptr + // if the font is really gone, it would have called back in + // FontMetricsDeleted() and would have removed itself + if (mFontMetrics.IndexOf(oldfm) != mFontMetrics.NoIndex) { + // nope, the font is still there, so let's hold onto it too + NS_ADDREF(oldfm); + } + } +} + +void +nsFontCache::Flush() +{ + for (int32_t i = mFontMetrics.Length()-1; i >= 0; --i) { + nsFontMetrics* fm = mFontMetrics[i]; + // Destroy() will unhook our device context from the fm so that we + // won't waste time in triggering the notification of + // FontMetricsDeleted() in the subsequent release + fm->Destroy(); + NS_RELEASE(fm); + } + mFontMetrics.Clear(); +} + +nsDeviceContext::nsDeviceContext() + : mWidth(0), mHeight(0), mDepth(0), + mAppUnitsPerDevPixel(-1), mAppUnitsPerDevPixelAtUnitFullZoom(-1), + mAppUnitsPerPhysicalInch(-1), + mFullZoom(1.0f), mPrintingScale(1.0f) +#ifdef DEBUG + , mIsInitialized(false) +#endif +{ + MOZ_ASSERT(NS_IsMainThread(), "nsDeviceContext created off main thread"); +} + +nsDeviceContext::~nsDeviceContext() +{ + if (mFontCache) { + mFontCache->Destroy(); + } +} + +already_AddRefed +nsDeviceContext::GetMetricsFor(const nsFont& aFont, + const nsFontMetrics::Params& aParams) +{ + if (!mFontCache) { + mFontCache = new nsFontCache(); + mFontCache->Init(this); + } + + return mFontCache->GetMetricsFor(aFont, aParams); +} + +nsresult +nsDeviceContext::FlushFontCache(void) +{ + if (mFontCache) + mFontCache->Flush(); + return NS_OK; +} + +nsresult +nsDeviceContext::FontMetricsDeleted(const nsFontMetrics* aFontMetrics) +{ + if (mFontCache) { + mFontCache->FontMetricsDeleted(aFontMetrics); + } + return NS_OK; +} + +bool +nsDeviceContext::IsPrinterContext() +{ + return mPrintTarget != nullptr +#ifdef XP_MACOSX + || mCachedPrintTarget != nullptr +#endif + ; +} + +void +nsDeviceContext::SetDPI(double* aScale) +{ + float dpi = -1.0f; + + // Use the printing DC to determine DPI values, if we have one. + if (mDeviceContextSpec) { + dpi = mDeviceContextSpec->GetDPI(); + mPrintingScale = mDeviceContextSpec->GetPrintingScale(); + 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 = Preferences::GetInt("layout.css.dpi", -1); + + if (prefDPI > 0) { + dpi = prefDPI; + } else if (mWidget) { + dpi = mWidget->GetDPI(); + + if (prefDPI < 0) { + dpi = std::max(96.0f, dpi); + } + } else { + dpi = 96.0f; + } + + double devPixelsPerCSSPixel; + if (aScale && *aScale > 0.0) { + // if caller provided a scale, we just use it + devPixelsPerCSSPixel = *aScale; + } else { + // otherwise get from the widget, and return it in aScale for + // the caller to pass to child contexts if needed + CSSToLayoutDeviceScale scale = + mWidget ? mWidget->GetDefaultScale() + : CSSToLayoutDeviceScale(1.0); + devPixelsPerCSSPixel = scale.scale; + if (aScale) { + *aScale = devPixelsPerCSSPixel; + } + } + + mAppUnitsPerDevPixelAtUnitFullZoom = + std::max(1, NS_lround(AppUnitsPerCSSPixel() / devPixelsPerCSSPixel)); + } + + NS_ASSERTION(dpi != -1.0, "no dpi set"); + + mAppUnitsPerPhysicalInch = NS_lround(dpi * mAppUnitsPerDevPixelAtUnitFullZoom); + UpdateAppUnitsForFullZoom(); +} + +nsresult +nsDeviceContext::Init(nsIWidget *aWidget) +{ +#ifdef DEBUG + // We can't assert |!mIsInitialized| here since EndSwapDocShellsForDocument + // re-initializes nsDeviceContext objects. We can only assert in + // InitForPrinting (below). + mIsInitialized = true; +#endif + + nsresult rv = NS_OK; + if (mScreenManager && mWidget == aWidget) + return rv; + + mWidget = aWidget; + SetDPI(); + + if (mScreenManager) + return rv; + + mScreenManager = do_GetService("@mozilla.org/gfx/screenmanager;1", &rv); + + return rv; +} + +// XXX This is only for printing. We should make that obvious in the name. +already_AddRefed +nsDeviceContext::CreateRenderingContext() +{ + return CreateRenderingContextCommon(/* not a reference context */ false); +} + +already_AddRefed +nsDeviceContext::CreateReferenceRenderingContext() +{ + return CreateRenderingContextCommon(/* a reference context */ true); +} + +already_AddRefed +nsDeviceContext::CreateRenderingContextCommon(bool aWantReferenceContext) +{ + MOZ_ASSERT(IsPrinterContext()); + MOZ_ASSERT(mWidth > 0 && mHeight > 0); + + RefPtr printingTarget = mPrintTarget; +#ifdef XP_MACOSX + // CreateRenderingContext() can be called (on reflow) after EndPage() + // but before BeginPage(). On OS X (and only there) mPrintTarget + // will in this case be null, because OS X printing surfaces are + // per-page, and therefore only truly valid between calls to BeginPage() + // and EndPage(). But we can get away with fudging things here, if need + // be, by using a cached copy. + if (!printingTarget) { + printingTarget = mCachedPrintTarget; + } +#endif + + // This will usually be null, depending on the pref print.print_via_parent. + RefPtr recorder; + mDeviceContextSpec->GetDrawEventRecorder(getter_AddRefs(recorder)); + + RefPtr dt; + if (aWantReferenceContext) { + dt = printingTarget->GetReferenceDrawTarget(recorder); + } else { + dt = printingTarget->MakeDrawTarget(gfx::IntSize(mWidth, mHeight), recorder); + } + + if (!dt || !dt->IsValid()) { + gfxCriticalNote + << "Failed to create draw target in device context sized " + << mWidth << "x" << mHeight << " and pointers " + << hexa(mPrintTarget) << " and " << hexa(printingTarget); + return nullptr; + } + +#ifdef XP_MACOSX + dt->AddUserData(&gfxContext::sDontUseAsSourceKey, dt, nullptr); +#endif + dt->AddUserData(&sDisablePixelSnapping, (void*)0x1, nullptr); + + RefPtr pContext = gfxContext::CreateOrNull(dt); + MOZ_ASSERT(pContext); // already checked draw target above + + gfxMatrix transform; + if (printingTarget->RotateNeededForLandscape()) { + // Rotate page 90 degrees to draw landscape page on portrait paper + IntSize size = printingTarget->GetSize(); + transform.Translate(gfxPoint(0, size.width)); + gfxMatrix rotate(0, -1, + 1, 0, + 0, 0); + transform = rotate * transform; + } + transform.Scale(mPrintingScale, mPrintingScale); + + pContext->SetMatrix(transform); + return pContext.forget(); +} + +nsresult +nsDeviceContext::GetDepth(uint32_t& aDepth) +{ + if (mDepth == 0 && mScreenManager) { + nsCOMPtr primaryScreen; + mScreenManager->GetPrimaryScreen(getter_AddRefs(primaryScreen)); + primaryScreen->GetColorDepth(reinterpret_cast(&mDepth)); + } + + aDepth = mDepth; + return NS_OK; +} + +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.x = 0; + aRect.y = 0; + aRect.width = mWidth; + aRect.height = mHeight; + } else + ComputeFullAreaUsingScreen ( &aRect ); + + return NS_OK; +} + +nsresult +nsDeviceContext::GetClientRect(nsRect &aRect) +{ + if (IsPrinterContext()) { + aRect.x = 0; + aRect.y = 0; + aRect.width = mWidth; + aRect.height = 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) +{ + nsresult rv = mPrintTarget->BeginPrinting(aTitle, aPrintToFileName); + + if (NS_SUCCEEDED(rv) && mDeviceContextSpec) { + rv = mDeviceContextSpec->BeginDocument(aTitle, aPrintToFileName, + aStartPage, aEndPage); + } + + return rv; +} + + +nsresult +nsDeviceContext::EndDocument(void) +{ + nsresult rv = NS_OK; + + if (mPrintTarget) { + rv = mPrintTarget->EndPrinting(); + if (NS_SUCCEEDED(rv)) + mPrintTarget->Finish(); + } + + if (mDeviceContextSpec) + mDeviceContextSpec->EndDocument(); + + return rv; +} + + +nsresult +nsDeviceContext::AbortDocument(void) +{ + nsresult rv = mPrintTarget->AbortPrinting(); + + if (mDeviceContextSpec) + mDeviceContextSpec->EndDocument(); + + return rv; +} + + +nsresult +nsDeviceContext::BeginPage(void) +{ + nsresult rv = NS_OK; + + if (mDeviceContextSpec) + rv = mDeviceContextSpec->BeginPage(); + + if (NS_FAILED(rv)) return rv; + +#ifdef XP_MACOSX + // We need to get a new surface for each page on the Mac, as the + // CGContextRefs are only good for one page. + mPrintTarget = mDeviceContextSpec->MakePrintTarget(); +#endif + + rv = mPrintTarget->BeginPage(); + + return rv; +} + +nsresult +nsDeviceContext::EndPage(void) +{ + nsresult rv = mPrintTarget->EndPage(); + +#ifdef XP_MACOSX + // We need to release the CGContextRef in the surface here, plus it's + // not something you would want anyway, as these CGContextRefs are only + // good for one page. But we need to keep a cached reference to it, since + // CreateRenderingContext() may try to access it when mPrintTarget + // would normally be null. See bug 665218. If we just stop nulling out + // mPrintTarget here (and thereby make that our cached copy), we'll + // break all our null checks on mPrintTarget. See bug 684622. + mCachedPrintTarget = mPrintTarget; + mPrintTarget = nullptr; +#endif + + if (mDeviceContextSpec) + mDeviceContextSpec->EndPage(); + + return rv; +} + +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. + nsCOMPtr screen; + FindScreen (getter_AddRefs(screen)); + if (screen) { + int32_t x, y, width, height; + screen->GetAvailRect(&x, &y, &width, &height); + + // convert to device units + outRect->y = NSIntPixelsToAppUnits(y, AppUnitsPerDevPixel()); + outRect->x = NSIntPixelsToAppUnits(x, AppUnitsPerDevPixel()); + outRect->width = NSIntPixelsToAppUnits(width, AppUnitsPerDevPixel()); + outRect->height = NSIntPixelsToAppUnits(height, 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. + nsCOMPtr screen; + FindScreen ( getter_AddRefs(screen) ); + if ( screen ) { + int32_t x, y, width, height; + screen->GetRect ( &x, &y, &width, &height ); + + // convert to device units + outRect->y = NSIntPixelsToAppUnits(y, AppUnitsPerDevPixel()); + outRect->x = NSIntPixelsToAppUnits(x, AppUnitsPerDevPixel()); + outRect->width = NSIntPixelsToAppUnits(width, AppUnitsPerDevPixel()); + outRect->height = NSIntPixelsToAppUnits(height, AppUnitsPerDevPixel()); + + mWidth = outRect->width; + mHeight = outRect->height; + } +} + +// +// FindScreen +// +// Determines which screen intersects the largest area of the given surface. +// +void +nsDeviceContext::FindScreen(nsIScreen** outScreen) +{ + if (!mWidget || !mScreenManager) { + return; + } + + CheckDPIChange(); + + if (mWidget->GetOwningTabChild()) { + mScreenManager->ScreenForNativeWidget((void *)mWidget->GetOwningTabChild(), + outScreen); + } + else if (mWidget->GetNativeData(NS_NATIVE_WINDOW)) { + mScreenManager->ScreenForNativeWidget(mWidget->GetNativeData(NS_NATIVE_WINDOW), + outScreen); + } + +#ifdef MOZ_WIDGET_ANDROID + if (!(*outScreen)) { + nsCOMPtr screen = mWidget->GetWidgetScreen(); + screen.forget(outScreen); + } +#endif + + if (!(*outScreen)) { + mScreenManager->GetPrimaryScreen(outScreen); + } +} + +bool +nsDeviceContext::CalcPrintingSize() +{ + if (!mPrintTarget) { + return (mWidth > 0 && mHeight > 0); + } + + 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(double* aScale) +{ + int32_t oldDevPixels = mAppUnitsPerDevPixelAtUnitFullZoom; + int32_t oldInches = mAppUnitsPerPhysicalInch; + + SetDPI(aScale); + + return oldDevPixels != mAppUnitsPerDevPixelAtUnitFullZoom || + oldInches != mAppUnitsPerPhysicalInch; +} + +bool +nsDeviceContext::SetFullZoom(float aScale) +{ + if (aScale <= 0) { + NS_NOTREACHED("Invalid full zoom value"); + return false; + } + int32_t oldAppUnitsPerDevPixel = mAppUnitsPerDevPixel; + mFullZoom = aScale; + UpdateAppUnitsForFullZoom(); + return oldAppUnitsPerDevPixel != mAppUnitsPerDevPixel; +} + +void +nsDeviceContext::UpdateAppUnitsForFullZoom() +{ + mAppUnitsPerDevPixel = + std::max(1, NSToIntRound(float(mAppUnitsPerDevPixelAtUnitFullZoom) / mFullZoom)); + // adjust mFullZoom to reflect appunit rounding + mFullZoom = float(mAppUnitsPerDevPixelAtUnitFullZoom) / mAppUnitsPerDevPixel; +} + +DesktopToLayoutDeviceScale +nsDeviceContext::GetDesktopToDeviceScale() +{ + nsCOMPtr screen; + FindScreen(getter_AddRefs(screen)); + + if (screen) { + double scale; + screen->GetContentsScaleFactor(&scale); + return DesktopToLayoutDeviceScale(scale); + } + + return DesktopToLayoutDeviceScale(1.0); +} -- cgit v1.2.3