forked from mirrors/gecko-dev
This allows us to avoid instantiating separate gfxFont objects when content is tagged with different 'lang' attributes, yet ends up using the same fonts (e.g. Wikipedia may use a default font such as Arial for language names/links that are tagged with several dozen different languages). Differential Revision: https://phabricator.services.mozilla.com/D96978
717 lines
23 KiB
C++
717 lines
23 KiB
C++
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set sw=2 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 <algorithm> // for max
|
|
#include "gfxASurface.h" // for gfxASurface, etc
|
|
#include "gfxContext.h"
|
|
#include "gfxImageSurface.h" // for gfxImageSurface
|
|
#include "gfxPoint.h" // for gfxSize
|
|
#include "gfxTextRun.h" // for gfxFontGroup
|
|
#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_ASSERTION, etc
|
|
#include "nsFont.h" // for nsFont
|
|
#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 "nsIScreen.h" // for nsIScreen
|
|
#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::services::GetObserverService;
|
|
using mozilla::widget::ScreenManager;
|
|
|
|
class nsFontCache final : public nsIObserver {
|
|
public:
|
|
nsFontCache() : mContext(nullptr) {}
|
|
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
NS_DECL_NSIOBSERVER
|
|
|
|
void Init(nsDeviceContext* aContext);
|
|
void Destroy();
|
|
|
|
already_AddRefed<nsFontMetrics> GetMetricsFor(
|
|
const nsFont& aFont, const nsFontMetrics::Params& aParams);
|
|
|
|
void FontMetricsDeleted(const nsFontMetrics* aFontMetrics);
|
|
void Compact();
|
|
|
|
// Flush aFlushCount oldest entries, or all if aFlushCount is negative
|
|
void Flush(int32_t aFlushCount = -1);
|
|
|
|
void UpdateUserFonts(gfxUserFontSet* aUserFontSet);
|
|
|
|
protected:
|
|
// If the array of cached entries is about to exceed this threshold,
|
|
// we'll discard the oldest ones so as to keep the size reasonable.
|
|
// In practice, the great majority of cache hits are among the last
|
|
// few entries; keeping thousands of older entries becomes counter-
|
|
// productive because it can then take too long to scan the cache.
|
|
static const int32_t kMaxCacheEntries = 128;
|
|
|
|
~nsFontCache() = default;
|
|
|
|
nsDeviceContext* mContext; // owner
|
|
RefPtr<nsAtom> mLocaleLanguage;
|
|
|
|
// We may not flush older entries immediately the array reaches
|
|
// kMaxCacheEntries length, because this usually happens on a stylo
|
|
// thread where we can't safely delete metrics objects. So we allocate an
|
|
// oversized autoarray buffer here, so that we're unlikely to overflow
|
|
// it and need separate heap allocation before the flush happens on the
|
|
// main thread.
|
|
AutoTArray<nsFontMetrics*, kMaxCacheEntries * 2> mFontMetrics;
|
|
|
|
bool mFlushPending = false;
|
|
|
|
class FlushFontMetricsTask : public mozilla::Runnable {
|
|
public:
|
|
explicit FlushFontMetricsTask(nsFontCache* aCache)
|
|
: mozilla::Runnable("FlushFontMetricsTask"), mCache(aCache) {}
|
|
NS_IMETHOD Run() override {
|
|
// Partially flush the cache, leaving the kMaxCacheEntries/2 most
|
|
// recent entries.
|
|
mCache->Flush(mCache->mFontMetrics.Length() - kMaxCacheEntries / 2);
|
|
mCache->mFlushPending = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
RefPtr<nsFontCache> mCache;
|
|
};
|
|
};
|
|
|
|
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<nsIObserverService> obs = GetObserverService();
|
|
if (obs) obs->AddObserver(this, "memory-pressure", false);
|
|
|
|
mLocaleLanguage = nsLanguageAtomService::GetService()->GetLocaleLanguage();
|
|
if (!mLocaleLanguage) {
|
|
mLocaleLanguage = NS_Atomize("x-western");
|
|
}
|
|
}
|
|
|
|
void nsFontCache::Destroy() {
|
|
nsCOMPtr<nsIObserverService> 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<nsFontMetrics> nsFontCache::GetMetricsFor(
|
|
const nsFont& aFont, const nsFontMetrics::Params& aParams) {
|
|
nsAtom* language = aParams.language && !aParams.language->IsEmpty()
|
|
? aParams.language
|
|
: mLocaleLanguage.get();
|
|
|
|
// First check our cache
|
|
// start from the end, which is where we put the most-recent-used element
|
|
const 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 &&
|
|
fm->ExplicitLanguage() == aParams.explicitLanguage) {
|
|
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.
|
|
// If the cache has reached its size limit, drop the older half of the
|
|
// entries; but if we're on a stylo thread (the usual case), we have
|
|
// to post a task back to the main thread to do the flush.
|
|
if (n >= kMaxCacheEntries - 1 && !mFlushPending) {
|
|
if (NS_IsMainThread()) {
|
|
Flush(mFontMetrics.Length() - kMaxCacheEntries / 2);
|
|
} else {
|
|
mFlushPending = true;
|
|
nsCOMPtr<nsIRunnable> flushTask = new FlushFontMetricsTask(this);
|
|
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(flushTask));
|
|
}
|
|
}
|
|
|
|
nsFontMetrics::Params params = aParams;
|
|
params.language = language;
|
|
RefPtr<nsFontMetrics> 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).take());
|
|
return fm.forget();
|
|
}
|
|
|
|
void nsFontCache::UpdateUserFonts(gfxUserFontSet* aUserFontSet) {
|
|
for (nsFontMetrics* fm : mFontMetrics) {
|
|
gfxFontGroup* fg = fm->GetThebesFontGroup();
|
|
if (fg->GetUserFontSet() == aUserFontSet) {
|
|
fg->UpdateUserFonts();
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flush the aFlushCount oldest entries, or all if (aFlushCount < 0)
|
|
void nsFontCache::Flush(int32_t aFlushCount) {
|
|
int32_t n = aFlushCount < 0
|
|
? mFontMetrics.Length()
|
|
: std::min<int32_t>(aFlushCount, mFontMetrics.Length());
|
|
for (int32_t i = n - 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.RemoveElementsAt(0, n);
|
|
}
|
|
|
|
nsDeviceContext::nsDeviceContext()
|
|
: mWidth(0),
|
|
mHeight(0),
|
|
mAppUnitsPerDevPixel(-1),
|
|
mAppUnitsPerDevPixelAtUnitFullZoom(-1),
|
|
mAppUnitsPerPhysicalInch(-1),
|
|
mFullZoom(1.0f),
|
|
mPrintingScale(1.0f),
|
|
mPrintingTranslate(gfxPoint(0, 0)),
|
|
mIsCurrentlyPrintingDoc(false)
|
|
#ifdef DEBUG
|
|
,
|
|
mIsInitialized(false)
|
|
#endif
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "nsDeviceContext created off main thread");
|
|
}
|
|
|
|
nsDeviceContext::~nsDeviceContext() {
|
|
if (mFontCache) {
|
|
mFontCache->Destroy();
|
|
}
|
|
}
|
|
|
|
void nsDeviceContext::InitFontCache() {
|
|
if (!mFontCache) {
|
|
mFontCache = new nsFontCache();
|
|
mFontCache->Init(this);
|
|
}
|
|
}
|
|
|
|
void nsDeviceContext::UpdateFontCacheUserFonts(gfxUserFontSet* aUserFontSet) {
|
|
if (mFontCache) {
|
|
mFontCache->UpdateUserFonts(aUserFontSet);
|
|
}
|
|
}
|
|
|
|
already_AddRefed<nsFontMetrics> nsDeviceContext::GetMetricsFor(
|
|
const nsFont& aFont, const nsFontMetrics::Params& aParams) {
|
|
InitFontCache();
|
|
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; }
|
|
|
|
void nsDeviceContext::SetDPI(double* aScale) {
|
|
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 {
|
|
nsCOMPtr<nsIScreen> primaryScreen;
|
|
ScreenManager& screenManager = ScreenManager::GetSingleton();
|
|
screenManager.GetPrimaryScreen(getter_AddRefs(primaryScreen));
|
|
MOZ_ASSERT(primaryScreen);
|
|
|
|
// 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) {
|
|
// PuppetWidget could return -1 if the value's not available yet.
|
|
dpi = mWidget->GetDPI();
|
|
// In case that the widget returns -1, use the primary screen's
|
|
// value as default.
|
|
if (dpi < 0) {
|
|
primaryScreen->GetDpi(&dpi);
|
|
}
|
|
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;
|
|
// In case that the widget returns -1, use the primary screen's
|
|
// value as default.
|
|
if (devPixelsPerCSSPixel < 0) {
|
|
primaryScreen->GetDefaultCSSScaleFactor(&devPixelsPerCSSPixel);
|
|
}
|
|
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<gfxContext> nsDeviceContext::CreateRenderingContext() {
|
|
return CreateRenderingContextCommon(/* not a reference context */ false);
|
|
}
|
|
|
|
already_AddRefed<gfxContext>
|
|
nsDeviceContext::CreateReferenceRenderingContext() {
|
|
return CreateRenderingContextCommon(/* a reference context */ true);
|
|
}
|
|
|
|
already_AddRefed<gfxContext> nsDeviceContext::CreateRenderingContextCommon(
|
|
bool aWantReferenceContext) {
|
|
MOZ_ASSERT(IsPrinterContext());
|
|
MOZ_ASSERT(mWidth > 0 && mHeight > 0);
|
|
|
|
RefPtr<gfx::DrawTarget> dt;
|
|
if (aWantReferenceContext) {
|
|
dt = mPrintTarget->GetReferenceDrawTarget();
|
|
} else {
|
|
// This will be null if e10s is disabled or print.print_via_parent=false.
|
|
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;
|
|
}
|
|
|
|
#ifdef XP_MACOSX
|
|
// The CGContextRef provided by PMSessionGetCGGraphicsContext is
|
|
// write-only, so we need to prevent gfxContext::PushGroupAndCopyBackground
|
|
// trying to read from it or else we'll crash.
|
|
// XXXjwatt Consider adding a MakeDrawTarget override to PrintTargetCG and
|
|
// moving this AddUserData call there.
|
|
dt->AddUserData(&gfxContext::sDontUseAsSourceKey, dt, nullptr);
|
|
#endif
|
|
dt->AddUserData(&sDisablePixelSnapping, (void*)0x1, nullptr);
|
|
|
|
RefPtr<gfxContext> pContext = gfxContext::CreateOrNull(dt);
|
|
MOZ_ASSERT(pContext); // already checked draw target above
|
|
|
|
gfxMatrix transform;
|
|
transform.PreTranslate(mPrintingTranslate);
|
|
if (mPrintTarget->RotateNeededForLandscape()) {
|
|
// Rotate page 90 degrees to draw landscape page on portrait paper
|
|
IntSize size = mPrintTarget->GetSize();
|
|
transform.PreTranslate(gfxPoint(0, size.width));
|
|
gfxMatrix rotate(0, -1, 1, 0, 0, 0);
|
|
transform = rotate * transform;
|
|
}
|
|
transform.PreScale(mPrintingScale, mPrintingScale);
|
|
|
|
pContext->SetMatrixDouble(transform);
|
|
return pContext.forget();
|
|
}
|
|
|
|
nsresult nsDeviceContext::GetDepth(uint32_t& aDepth) {
|
|
nsCOMPtr<nsIScreen> screen;
|
|
FindScreen(getter_AddRefs(screen));
|
|
if (!screen) {
|
|
ScreenManager& screenManager = ScreenManager::GetSingleton();
|
|
screenManager.GetPrimaryScreen(getter_AddRefs(screen));
|
|
MOZ_ASSERT(screen);
|
|
}
|
|
screen->GetColorDepth(reinterpret_cast<int32_t*>(&aDepth));
|
|
|
|
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.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_ASSERT(!mIsCurrentlyPrintingDoc,
|
|
"Mismatched BeginDocument/EndDocument calls");
|
|
|
|
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;
|
|
}
|
|
|
|
nsresult nsDeviceContext::EndDocument(void) {
|
|
MOZ_ASSERT(mIsCurrentlyPrintingDoc,
|
|
"Mismatched BeginDocument/EndDocument calls");
|
|
|
|
mIsCurrentlyPrintingDoc = false;
|
|
|
|
nsresult rv = mPrintTarget->EndPrinting();
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mPrintTarget->Finish();
|
|
}
|
|
|
|
if (mDeviceContextSpec) mDeviceContextSpec->EndDocument();
|
|
|
|
mPrintTarget = nullptr;
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsDeviceContext::AbortDocument(void) {
|
|
MOZ_ASSERT(mIsCurrentlyPrintingDoc,
|
|
"Mismatched BeginDocument/EndDocument calls");
|
|
|
|
nsresult rv = mPrintTarget->AbortPrinting();
|
|
|
|
mIsCurrentlyPrintingDoc = false;
|
|
|
|
if (mDeviceContextSpec) mDeviceContextSpec->EndDocument();
|
|
|
|
mPrintTarget = nullptr;
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsDeviceContext::BeginPage(void) {
|
|
nsresult rv = NS_OK;
|
|
|
|
if (mDeviceContextSpec) rv = mDeviceContextSpec->BeginPage();
|
|
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return mPrintTarget->BeginPage();
|
|
}
|
|
|
|
nsresult nsDeviceContext::EndPage(void) {
|
|
nsresult rv = mPrintTarget->EndPage();
|
|
|
|
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<nsIScreen> screen;
|
|
FindScreen(getter_AddRefs(screen));
|
|
if (screen) {
|
|
int32_t x, y, width, height;
|
|
screen->GetAvailRect(&x, &y, &width, &height);
|
|
|
|
// convert to device units
|
|
outRect->SetRect(NSIntPixelsToAppUnits(x, AppUnitsPerDevPixel()),
|
|
NSIntPixelsToAppUnits(y, AppUnitsPerDevPixel()),
|
|
NSIntPixelsToAppUnits(width, AppUnitsPerDevPixel()),
|
|
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<nsIScreen> screen;
|
|
FindScreen(getter_AddRefs(screen));
|
|
if (screen) {
|
|
int32_t x, y, width, height;
|
|
screen->GetRect(&x, &y, &width, &height);
|
|
|
|
// convert to device units
|
|
outRect->SetRect(NSIntPixelsToAppUnits(x, AppUnitsPerDevPixel()),
|
|
NSIntPixelsToAppUnits(y, AppUnitsPerDevPixel()),
|
|
NSIntPixelsToAppUnits(width, AppUnitsPerDevPixel()),
|
|
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();
|
|
|
|
nsCOMPtr<nsIScreen> screen = mWidget->GetWidgetScreen();
|
|
screen.forget(outScreen);
|
|
|
|
if (!(*outScreen)) {
|
|
mScreenManager->GetPrimaryScreen(outScreen);
|
|
}
|
|
}
|
|
|
|
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(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) {
|
|
MOZ_ASSERT_UNREACHABLE("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<nsIScreen> screen;
|
|
FindScreen(getter_AddRefs(screen));
|
|
|
|
if (screen) {
|
|
double scale;
|
|
screen->GetContentsScaleFactor(&scale);
|
|
return DesktopToLayoutDeviceScale(scale);
|
|
}
|
|
|
|
return DesktopToLayoutDeviceScale(1.0);
|
|
}
|
|
|
|
bool nsDeviceContext::IsSyncPagePrinting() const {
|
|
MOZ_ASSERT(mPrintTarget);
|
|
return mPrintTarget->IsSyncPagePrinting();
|
|
}
|
|
|
|
void nsDeviceContext::RegisterPageDoneCallback(
|
|
PrintTarget::PageDoneCallback&& aCallback) {
|
|
MOZ_ASSERT(mPrintTarget && aCallback && !IsSyncPagePrinting());
|
|
mPrintTarget->RegisterPageDoneCallback(std::move(aCallback));
|
|
}
|
|
void nsDeviceContext::UnregisterPageDoneCallback() {
|
|
if (mPrintTarget) {
|
|
mPrintTarget->UnregisterPageDoneCallback();
|
|
}
|
|
}
|