1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim: set sw=4 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 "gfxASurface.h" // for gfxASurface, etc
10 #include "gfxContext.h"
11 #include "gfxFont.h" // for gfxFontGroup
12 #include "gfxImageSurface.h" // for gfxImageSurface
13 #include "gfxPoint.h" // for gfxSize
14 #include "mozilla/Attributes.h" // for final
15 #include "mozilla/gfx/PathHelpers.h"
16 #include "mozilla/gfx/PrintTarget.h"
17 #include "mozilla/Preferences.h" // for Preferences
18 #include "mozilla/Services.h" // for GetObserverService
19 #include "mozilla/mozalloc.h" // for operator new
20 #include "nsCRT.h" // for nsCRT
21 #include "nsDebug.h" // for NS_ASSERTION, etc
22 #include "nsFont.h" // for nsFont
23 #include "nsFontMetrics.h" // for nsFontMetrics
24 #include "nsAtom.h" // for nsAtom, NS_Atomize
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 class nsFontCache final
: public nsIObserver
50 nsFontCache(): mContext(nullptr) {}
55 void Init(nsDeviceContext
* aContext
);
58 already_AddRefed
<nsFontMetrics
> GetMetricsFor(
59 const nsFont
& aFont
, const nsFontMetrics::Params
& aParams
);
61 void FontMetricsDeleted(const nsFontMetrics
* aFontMetrics
);
65 void UpdateUserFonts(gfxUserFontSet
* aUserFontSet
);
70 nsDeviceContext
* mContext
; // owner
71 RefPtr
<nsAtom
> mLocaleLanguage
;
72 nsTArray
<nsFontMetrics
*> mFontMetrics
;
75 NS_IMPL_ISUPPORTS(nsFontCache
, nsIObserver
)
77 // The Init and Destroy methods are necessary because it's not
78 // safe to call AddObserver from a constructor or RemoveObserver
79 // from a destructor. That should be fixed.
81 nsFontCache::Init(nsDeviceContext
* aContext
)
84 // register as a memory-pressure observer to free font resources
85 // in low-memory situations.
86 nsCOMPtr
<nsIObserverService
> obs
= GetObserverService();
88 obs
->AddObserver(this, "memory-pressure", false);
90 mLocaleLanguage
= nsLanguageAtomService::GetService()->GetLocaleLanguage();
91 if (!mLocaleLanguage
) {
92 mLocaleLanguage
= NS_Atomize("x-western");
97 nsFontCache::Destroy()
99 nsCOMPtr
<nsIObserverService
> obs
= GetObserverService();
101 obs
->RemoveObserver(this, "memory-pressure");
106 nsFontCache::Observe(nsISupports
*, const char* aTopic
, const char16_t
*)
108 if (!nsCRT::strcmp(aTopic
, "memory-pressure"))
113 already_AddRefed
<nsFontMetrics
>
114 nsFontCache::GetMetricsFor(const nsFont
& aFont
,
115 const nsFontMetrics::Params
& aParams
)
117 nsAtom
* language
= aParams
.language
? aParams
.language
118 : mLocaleLanguage
.get();
120 // First check our cache
121 // start from the end, which is where we put the most-recent-used element
123 int32_t n
= mFontMetrics
.Length() - 1;
124 for (int32_t i
= n
; i
>= 0; --i
) {
125 nsFontMetrics
* fm
= mFontMetrics
[i
];
126 if (fm
->Font().Equals(aFont
) &&
127 fm
->GetUserFontSet() == aParams
.userFontSet
&&
128 fm
->Language() == language
&&
129 fm
->Orientation() == aParams
.orientation
) {
131 // promote it to the end of the cache
132 mFontMetrics
.RemoveElementAt(i
);
133 mFontMetrics
.AppendElement(fm
);
135 fm
->GetThebesFontGroup()->UpdateUserFonts();
136 return do_AddRef(fm
);
140 // It's not in the cache. Get font metrics and then cache them.
142 nsFontMetrics::Params params
= aParams
;
143 params
.language
= language
;
144 RefPtr
<nsFontMetrics
> fm
= new nsFontMetrics(aFont
, params
, mContext
);
145 // the mFontMetrics list has the "head" at the end, because append
146 // is cheaper than insert
147 mFontMetrics
.AppendElement(do_AddRef(fm
).take());
152 nsFontCache::UpdateUserFonts(gfxUserFontSet
* aUserFontSet
)
154 for (nsFontMetrics
* fm
: mFontMetrics
) {
155 gfxFontGroup
* fg
= fm
->GetThebesFontGroup();
156 if (fg
->GetUserFontSet() == aUserFontSet
) {
157 fg
->UpdateUserFonts();
163 nsFontCache::FontMetricsDeleted(const nsFontMetrics
* aFontMetrics
)
165 mFontMetrics
.RemoveElement(aFontMetrics
);
169 nsFontCache::Compact()
171 // Need to loop backward because the running element can be removed on
173 for (int32_t i
= mFontMetrics
.Length()-1; i
>= 0; --i
) {
174 nsFontMetrics
* fm
= mFontMetrics
[i
];
175 nsFontMetrics
* oldfm
= fm
;
176 // Destroy() isn't here because we want our device context to be
178 NS_RELEASE(fm
); // this will reset fm to nullptr
179 // if the font is really gone, it would have called back in
180 // FontMetricsDeleted() and would have removed itself
181 if (mFontMetrics
.IndexOf(oldfm
) != mFontMetrics
.NoIndex
) {
182 // nope, the font is still there, so let's hold onto it too
191 for (int32_t i
= mFontMetrics
.Length()-1; i
>= 0; --i
) {
192 nsFontMetrics
* fm
= mFontMetrics
[i
];
193 // Destroy() will unhook our device context from the fm so that we
194 // won't waste time in triggering the notification of
195 // FontMetricsDeleted() in the subsequent release
199 mFontMetrics
.Clear();
202 nsDeviceContext::nsDeviceContext()
203 : mWidth(0), mHeight(0),
204 mAppUnitsPerDevPixel(-1), mAppUnitsPerDevPixelAtUnitFullZoom(-1),
205 mAppUnitsPerPhysicalInch(-1),
206 mFullZoom(1.0f
), mPrintingScale(1.0f
),
207 mPrintingTranslate(gfxPoint(0, 0)),
208 mIsCurrentlyPrintingDoc(false)
210 , mIsInitialized(false)
213 MOZ_ASSERT(NS_IsMainThread(), "nsDeviceContext created off main thread");
216 nsDeviceContext::~nsDeviceContext()
219 mFontCache
->Destroy();
224 nsDeviceContext::InitFontCache()
227 mFontCache
= new nsFontCache();
228 mFontCache
->Init(this);
233 nsDeviceContext::UpdateFontCacheUserFonts(gfxUserFontSet
* aUserFontSet
)
236 mFontCache
->UpdateUserFonts(aUserFontSet
);
240 already_AddRefed
<nsFontMetrics
>
241 nsDeviceContext::GetMetricsFor(const nsFont
& aFont
,
242 const nsFontMetrics::Params
& aParams
)
245 return mFontCache
->GetMetricsFor(aFont
, aParams
);
249 nsDeviceContext::FlushFontCache(void)
257 nsDeviceContext::FontMetricsDeleted(const nsFontMetrics
* aFontMetrics
)
260 mFontCache
->FontMetricsDeleted(aFontMetrics
);
266 nsDeviceContext::IsPrinterContext()
268 return mPrintTarget
!= nullptr;
272 nsDeviceContext::SetDPI(double* aScale
)
276 // Use the printing DC to determine DPI values, if we have one.
277 if (mDeviceContextSpec
) {
278 dpi
= mDeviceContextSpec
->GetDPI();
279 mPrintingScale
= mDeviceContextSpec
->GetPrintingScale();
280 mPrintingTranslate
= mDeviceContextSpec
->GetPrintingTranslate();
281 mAppUnitsPerDevPixelAtUnitFullZoom
=
282 NS_lround((AppUnitsPerCSSPixel() * 96) / dpi
);
284 nsCOMPtr
<nsIScreen
> primaryScreen
;
285 ScreenManager
& screenManager
= ScreenManager::GetSingleton();
286 screenManager
.GetPrimaryScreen(getter_AddRefs(primaryScreen
));
287 MOZ_ASSERT(primaryScreen
);
289 // A value of -1 means use the maximum of 96 and the system DPI.
290 // A value of 0 means use the system DPI. A positive value is used as the DPI.
291 // This sets the physical size of a device pixel and thus controls the
292 // interpretation of physical units.
293 int32_t prefDPI
= Preferences::GetInt("layout.css.dpi", -1);
297 } else if (mWidget
) {
298 // PuppetWidget could return -1 if the value's not available yet.
299 dpi
= mWidget
->GetDPI();
300 // In case that the widget returns -1, use the primary screen's
303 primaryScreen
->GetDpi(&dpi
);
306 dpi
= std::max(96.0f
, dpi
);
312 double devPixelsPerCSSPixel
;
313 if (aScale
&& *aScale
> 0.0) {
314 // if caller provided a scale, we just use it
315 devPixelsPerCSSPixel
= *aScale
;
317 // otherwise get from the widget, and return it in aScale for
318 // the caller to pass to child contexts if needed
319 CSSToLayoutDeviceScale scale
=
320 mWidget
? mWidget
->GetDefaultScale()
321 : CSSToLayoutDeviceScale(1.0);
322 devPixelsPerCSSPixel
= scale
.scale
;
323 // In case that the widget returns -1, use the primary screen's
325 if (devPixelsPerCSSPixel
< 0) {
326 primaryScreen
->GetDefaultCSSScaleFactor(&devPixelsPerCSSPixel
);
329 *aScale
= devPixelsPerCSSPixel
;
333 mAppUnitsPerDevPixelAtUnitFullZoom
=
334 std::max(1, NS_lround(AppUnitsPerCSSPixel() / devPixelsPerCSSPixel
));
337 NS_ASSERTION(dpi
!= -1.0, "no dpi set");
339 mAppUnitsPerPhysicalInch
= NS_lround(dpi
* mAppUnitsPerDevPixelAtUnitFullZoom
);
340 UpdateAppUnitsForFullZoom();
344 nsDeviceContext::Init(nsIWidget
*aWidget
)
347 // We can't assert |!mIsInitialized| here since EndSwapDocShellsForDocument
348 // re-initializes nsDeviceContext objects. We can only assert in
349 // InitForPrinting (below).
350 mIsInitialized
= true;
354 if (mScreenManager
&& mWidget
== aWidget
)
363 mScreenManager
= do_GetService("@mozilla.org/gfx/screenmanager;1", &rv
);
368 // XXX This is only for printing. We should make that obvious in the name.
369 already_AddRefed
<gfxContext
>
370 nsDeviceContext::CreateRenderingContext()
372 return CreateRenderingContextCommon(/* not a reference context */ false);
375 already_AddRefed
<gfxContext
>
376 nsDeviceContext::CreateReferenceRenderingContext()
378 return CreateRenderingContextCommon(/* a reference context */ true);
381 already_AddRefed
<gfxContext
>
382 nsDeviceContext::CreateRenderingContextCommon(bool aWantReferenceContext
)
384 MOZ_ASSERT(IsPrinterContext());
385 MOZ_ASSERT(mWidth
> 0 && mHeight
> 0);
387 RefPtr
<gfx::DrawTarget
> dt
;
388 if (aWantReferenceContext
) {
389 dt
= mPrintTarget
->GetReferenceDrawTarget();
391 // This will be null if e10s is disabled or print.print_via_parent=false.
392 RefPtr
<DrawEventRecorder
> recorder
;
393 mDeviceContextSpec
->GetDrawEventRecorder(getter_AddRefs(recorder
));
394 dt
= mPrintTarget
->MakeDrawTarget(gfx::IntSize(mWidth
, mHeight
), recorder
);
397 if (!dt
|| !dt
->IsValid()) {
399 << "Failed to create draw target in device context sized "
400 << mWidth
<< "x" << mHeight
<< " and pointer "
401 << hexa(mPrintTarget
);
406 // The CGContextRef provided by PMSessionGetCGGraphicsContext is
407 // write-only, so we need to prevent gfxContext::PushGroupAndCopyBackground
408 // trying to read from it or else we'll crash.
409 // XXXjwatt Consider adding a MakeDrawTarget override to PrintTargetCG and
410 // moving this AddUserData call there.
411 dt
->AddUserData(&gfxContext::sDontUseAsSourceKey
, dt
, nullptr);
413 dt
->AddUserData(&sDisablePixelSnapping
, (void*)0x1, nullptr);
415 RefPtr
<gfxContext
> pContext
= gfxContext::CreateOrNull(dt
);
416 MOZ_ASSERT(pContext
); // already checked draw target above
419 transform
.PreTranslate(mPrintingTranslate
);
420 if (mPrintTarget
->RotateNeededForLandscape()) {
421 // Rotate page 90 degrees to draw landscape page on portrait paper
422 IntSize size
= mPrintTarget
->GetSize();
423 transform
.PreTranslate(gfxPoint(0, size
.width
));
424 gfxMatrix
rotate(0, -1,
427 transform
= rotate
* transform
;
429 transform
.PreScale(mPrintingScale
, mPrintingScale
);
431 pContext
->SetMatrixDouble(transform
);
432 return pContext
.forget();
436 nsDeviceContext::GetDepth(uint32_t& aDepth
)
438 nsCOMPtr
<nsIScreen
> screen
;
439 FindScreen(getter_AddRefs(screen
));
441 ScreenManager
& screenManager
= ScreenManager::GetSingleton();
442 screenManager
.GetPrimaryScreen(getter_AddRefs(screen
));
445 screen
->GetColorDepth(reinterpret_cast<int32_t *>(&aDepth
));
451 nsDeviceContext::GetDeviceSurfaceDimensions(nscoord
&aWidth
, nscoord
&aHeight
)
453 if (IsPrinterContext()) {
458 ComputeFullAreaUsingScreen(&area
);
459 aWidth
= area
.Width();
460 aHeight
= area
.Height();
467 nsDeviceContext::GetRect(nsRect
&aRect
)
469 if (IsPrinterContext()) {
470 aRect
.SetRect(0, 0, mWidth
, mHeight
);
472 ComputeFullAreaUsingScreen ( &aRect
);
478 nsDeviceContext::GetClientRect(nsRect
&aRect
)
480 if (IsPrinterContext()) {
481 aRect
.SetRect(0, 0, mWidth
, mHeight
);
484 ComputeClientRectUsingScreen(&aRect
);
490 nsDeviceContext::InitForPrinting(nsIDeviceContextSpec
*aDevice
)
492 NS_ENSURE_ARG_POINTER(aDevice
);
494 MOZ_ASSERT(!mIsInitialized
,
495 "Only initialize once, immediately after construction");
497 // We don't set mIsInitialized here. The Init() call below does that.
499 mPrintTarget
= aDevice
->MakePrintTarget();
501 return NS_ERROR_FAILURE
;
504 mDeviceContextSpec
= aDevice
;
508 if (!CalcPrintingSize()) {
509 return NS_ERROR_FAILURE
;
516 nsDeviceContext::BeginDocument(const nsAString
& aTitle
,
517 const nsAString
& aPrintToFileName
,
521 MOZ_ASSERT(!mIsCurrentlyPrintingDoc
,
522 "Mismatched BeginDocument/EndDocument calls");
524 nsresult rv
= mPrintTarget
->BeginPrinting(aTitle
, aPrintToFileName
,
525 aStartPage
, aEndPage
);
527 if (NS_SUCCEEDED(rv
)) {
528 if (mDeviceContextSpec
) {
529 rv
= mDeviceContextSpec
->BeginDocument(aTitle
, aPrintToFileName
,
530 aStartPage
, aEndPage
);
532 mIsCurrentlyPrintingDoc
= true;
535 // Warn about any failure (except user cancelling):
536 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
) || rv
== NS_ERROR_ABORT
,
537 "nsDeviceContext::BeginDocument failed");
544 nsDeviceContext::EndDocument(void)
546 MOZ_ASSERT(mIsCurrentlyPrintingDoc
,
547 "Mismatched BeginDocument/EndDocument calls");
549 mIsCurrentlyPrintingDoc
= false;
551 nsresult rv
= mPrintTarget
->EndPrinting();
552 if (NS_SUCCEEDED(rv
)) {
553 mPrintTarget
->Finish();
556 if (mDeviceContextSpec
)
557 mDeviceContextSpec
->EndDocument();
559 mPrintTarget
= nullptr;
566 nsDeviceContext::AbortDocument(void)
568 MOZ_ASSERT(mIsCurrentlyPrintingDoc
,
569 "Mismatched BeginDocument/EndDocument calls");
571 nsresult rv
= mPrintTarget
->AbortPrinting();
573 mIsCurrentlyPrintingDoc
= false;
575 if (mDeviceContextSpec
)
576 mDeviceContextSpec
->EndDocument();
578 mPrintTarget
= nullptr;
585 nsDeviceContext::BeginPage(void)
589 if (mDeviceContextSpec
)
590 rv
= mDeviceContextSpec
->BeginPage();
592 if (NS_FAILED(rv
)) return rv
;
594 return mPrintTarget
->BeginPage();
598 nsDeviceContext::EndPage(void)
600 nsresult rv
= mPrintTarget
->EndPage();
602 if (mDeviceContextSpec
)
603 mDeviceContextSpec
->EndPage();
609 nsDeviceContext::ComputeClientRectUsingScreen(nsRect
* outRect
)
611 // we always need to recompute the clientRect
612 // because the window may have moved onto a different screen. In the single
613 // monitor case, we only need to do the computation if we haven't done it
614 // once already, and remember that we have because we're assured it won't change.
615 nsCOMPtr
<nsIScreen
> screen
;
616 FindScreen (getter_AddRefs(screen
));
618 int32_t x
, y
, width
, height
;
619 screen
->GetAvailRect(&x
, &y
, &width
, &height
);
621 // convert to device units
622 outRect
->SetRect(NSIntPixelsToAppUnits(x
, AppUnitsPerDevPixel()),
623 NSIntPixelsToAppUnits(y
, AppUnitsPerDevPixel()),
624 NSIntPixelsToAppUnits(width
, AppUnitsPerDevPixel()),
625 NSIntPixelsToAppUnits(height
, AppUnitsPerDevPixel()));
630 nsDeviceContext::ComputeFullAreaUsingScreen(nsRect
* outRect
)
632 // if we have more than one screen, we always need to recompute the clientRect
633 // because the window may have moved onto a different screen. In the single
634 // monitor case, we only need to do the computation if we haven't done it
635 // once already, and remember that we have because we're assured it won't change.
636 nsCOMPtr
<nsIScreen
> screen
;
637 FindScreen ( getter_AddRefs(screen
) );
639 int32_t x
, y
, width
, height
;
640 screen
->GetRect ( &x
, &y
, &width
, &height
);
642 // convert to device units
643 outRect
->SetRect(NSIntPixelsToAppUnits(x
, AppUnitsPerDevPixel()),
644 NSIntPixelsToAppUnits(y
, AppUnitsPerDevPixel()),
645 NSIntPixelsToAppUnits(width
, AppUnitsPerDevPixel()),
646 NSIntPixelsToAppUnits(height
, AppUnitsPerDevPixel()));
647 mWidth
= outRect
->Width();
648 mHeight
= outRect
->Height();
655 // Determines which screen intersects the largest area of the given surface.
658 nsDeviceContext::FindScreen(nsIScreen
** outScreen
)
660 if (!mWidget
|| !mScreenManager
) {
666 nsCOMPtr
<nsIScreen
> screen
= mWidget
->GetWidgetScreen();
667 screen
.forget(outScreen
);
670 mScreenManager
->GetPrimaryScreen(outScreen
);
675 nsDeviceContext::CalcPrintingSize()
677 gfxSize
size(mPrintTarget
->GetSize());
678 // For printing, CSS inches and physical inches are identical
679 // so it doesn't matter which we use here
680 mWidth
= NSToCoordRound(size
.width
* AppUnitsPerPhysicalInch()
681 / POINTS_PER_INCH_FLOAT
);
682 mHeight
= NSToCoordRound(size
.height
* AppUnitsPerPhysicalInch()
683 / POINTS_PER_INCH_FLOAT
);
685 return (mWidth
> 0 && mHeight
> 0);
688 bool nsDeviceContext::CheckDPIChange(double* aScale
)
690 int32_t oldDevPixels
= mAppUnitsPerDevPixelAtUnitFullZoom
;
691 int32_t oldInches
= mAppUnitsPerPhysicalInch
;
695 return oldDevPixels
!= mAppUnitsPerDevPixelAtUnitFullZoom
||
696 oldInches
!= mAppUnitsPerPhysicalInch
;
700 nsDeviceContext::SetFullZoom(float aScale
)
703 MOZ_ASSERT_UNREACHABLE("Invalid full zoom value");
706 int32_t oldAppUnitsPerDevPixel
= mAppUnitsPerDevPixel
;
708 UpdateAppUnitsForFullZoom();
709 return oldAppUnitsPerDevPixel
!= mAppUnitsPerDevPixel
;
713 nsDeviceContext::UpdateAppUnitsForFullZoom()
715 mAppUnitsPerDevPixel
=
716 std::max(1, NSToIntRound(float(mAppUnitsPerDevPixelAtUnitFullZoom
) / mFullZoom
));
717 // adjust mFullZoom to reflect appunit rounding
718 mFullZoom
= float(mAppUnitsPerDevPixelAtUnitFullZoom
) / mAppUnitsPerDevPixel
;
721 DesktopToLayoutDeviceScale
722 nsDeviceContext::GetDesktopToDeviceScale()
724 nsCOMPtr
<nsIScreen
> screen
;
725 FindScreen(getter_AddRefs(screen
));
729 screen
->GetContentsScaleFactor(&scale
);
730 return DesktopToLayoutDeviceScale(scale
);
733 return DesktopToLayoutDeviceScale(1.0);
737 nsDeviceContext::IsSyncPagePrinting() const
739 MOZ_ASSERT(mPrintTarget
);
740 return mPrintTarget
->IsSyncPagePrinting();
744 nsDeviceContext::RegisterPageDoneCallback(PrintTarget::PageDoneCallback
&& aCallback
)
746 MOZ_ASSERT(mPrintTarget
&& aCallback
&& !IsSyncPagePrinting());
747 mPrintTarget
->RegisterPageDoneCallback(std::move(aCallback
));
750 nsDeviceContext::UnregisterPageDoneCallback()
753 mPrintTarget
->UnregisterPageDoneCallback();