1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsDeviceContext.h"
7 #include <algorithm> // for max
8 #include "gfxASurface.h" // for gfxASurface, etc
9 #include "gfxFont.h" // for gfxFontGroup
10 #include "gfxImageSurface.h" // for gfxImageSurface
11 #include "gfxPoint.h" // for gfxSize
12 #include "mozilla/Attributes.h" // for MOZ_FINAL
13 #include "mozilla/Preferences.h" // for Preferences
14 #include "mozilla/Services.h" // for GetObserverService
15 #include "mozilla/mozalloc.h" // for operator new
16 #include "nsCRT.h" // for nsCRT
17 #include "nsDebug.h" // for NS_NOTREACHED, NS_ASSERTION, etc
18 #include "nsFont.h" // for nsFont
19 #include "nsFontMetrics.h" // for nsFontMetrics
20 #include "nsIAtom.h" // for nsIAtom, do_GetAtom
22 #include "nsIDeviceContextSpec.h" // for nsIDeviceContextSpec
23 #include "nsILanguageAtomService.h" // for nsILanguageAtomService, etc
24 #include "nsIObserver.h" // for nsIObserver, etc
25 #include "nsIObserverService.h" // for nsIObserverService
26 #include "nsIScreen.h" // for nsIScreen
27 #include "nsIScreenManager.h" // for nsIScreenManager
28 #include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc
29 #include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE
30 #include "nsIWidget.h" // for nsIWidget, NS_NATIVE_WINDOW
31 #include "nsRect.h" // for nsRect
32 #include "nsRenderingContext.h" // for nsRenderingContext
33 #include "nsServiceManagerUtils.h" // for do_GetService
34 #include "nsString.h" // for nsDependentString
35 #include "nsTArray.h" // for nsTArray, nsTArray_Impl
36 #include "nsThreadUtils.h" // for NS_IsMainThread
39 #include "gfxPDFSurface.h"
43 #include "gfxPSSurface.h"
45 #include "gfxWindowsSurface.h"
47 #include "gfxQuartzSurface.h"
50 using namespace mozilla
;
51 using mozilla::services::GetObserverService
;
53 class nsFontCache MOZ_FINAL
: public nsIObserver
56 nsFontCache() { MOZ_COUNT_CTOR(nsFontCache
); }
61 void Init(nsDeviceContext
* aContext
);
64 nsresult
GetMetricsFor(const nsFont
& aFont
, nsIAtom
* aLanguage
,
65 gfxUserFontSet
* aUserFontSet
,
66 gfxTextPerfMetrics
* aTextPerf
,
67 nsFontMetrics
*& aMetrics
);
69 void FontMetricsDeleted(const nsFontMetrics
* aFontMetrics
);
74 ~nsFontCache() { MOZ_COUNT_DTOR(nsFontCache
); }
76 nsDeviceContext
* mContext
; // owner
77 nsCOMPtr
<nsIAtom
> mLocaleLanguage
;
78 nsTArray
<nsFontMetrics
*> mFontMetrics
;
81 NS_IMPL_ISUPPORTS(nsFontCache
, nsIObserver
)
83 // The Init and Destroy methods are necessary because it's not
84 // safe to call AddObserver from a constructor or RemoveObserver
85 // from a destructor. That should be fixed.
87 nsFontCache::Init(nsDeviceContext
* aContext
)
90 // register as a memory-pressure observer to free font resources
91 // in low-memory situations.
92 nsCOMPtr
<nsIObserverService
> obs
= GetObserverService();
94 obs
->AddObserver(this, "memory-pressure", false);
96 nsCOMPtr
<nsILanguageAtomService
> langService
;
97 langService
= do_GetService(NS_LANGUAGEATOMSERVICE_CONTRACTID
);
99 mLocaleLanguage
= langService
->GetLocaleLanguage();
101 if (!mLocaleLanguage
) {
102 mLocaleLanguage
= do_GetAtom("x-western");
107 nsFontCache::Destroy()
109 nsCOMPtr
<nsIObserverService
> obs
= GetObserverService();
111 obs
->RemoveObserver(this, "memory-pressure");
116 nsFontCache::Observe(nsISupports
*, const char* aTopic
, const char16_t
*)
118 if (!nsCRT::strcmp(aTopic
, "memory-pressure"))
124 nsFontCache::GetMetricsFor(const nsFont
& aFont
, nsIAtom
* aLanguage
,
125 gfxUserFontSet
* aUserFontSet
,
126 gfxTextPerfMetrics
* aTextPerf
,
127 nsFontMetrics
*& aMetrics
)
130 aLanguage
= mLocaleLanguage
;
132 // First check our cache
133 // start from the end, which is where we put the most-recent-used element
136 int32_t n
= mFontMetrics
.Length() - 1;
137 for (int32_t i
= n
; i
>= 0; --i
) {
138 fm
= mFontMetrics
[i
];
139 if (fm
->Font().Equals(aFont
) && fm
->GetUserFontSet() == aUserFontSet
&&
140 fm
->Language() == aLanguage
) {
142 // promote it to the end of the cache
143 mFontMetrics
.RemoveElementAt(i
);
144 mFontMetrics
.AppendElement(fm
);
146 fm
->GetThebesFontGroup()->UpdateFontList();
147 NS_ADDREF(aMetrics
= fm
);
152 // It's not in the cache. Get font metrics and then cache them.
154 fm
= new nsFontMetrics();
156 nsresult rv
= fm
->Init(aFont
, aLanguage
, mContext
, aUserFontSet
, aTextPerf
);
157 if (NS_SUCCEEDED(rv
)) {
158 // the mFontMetrics list has the "head" at the end, because append
159 // is cheaper than insert
160 mFontMetrics
.AppendElement(fm
);
168 // One reason why Init() fails is because the system is running out of
169 // resources. e.g., on Win95/98 only a very limited number of GDI
170 // objects are available. Compact the cache and try again.
173 fm
= new nsFontMetrics();
175 rv
= fm
->Init(aFont
, aLanguage
, mContext
, aUserFontSet
, aTextPerf
);
176 if (NS_SUCCEEDED(rv
)) {
177 mFontMetrics
.AppendElement(fm
);
184 // could not setup a new one, send an old one (XXX search a "best
187 n
= mFontMetrics
.Length() - 1; // could have changed in Compact()
189 aMetrics
= mFontMetrics
[n
];
194 NS_POSTCONDITION(NS_SUCCEEDED(rv
),
195 "font metrics should not be null - bug 136248");
200 nsFontCache::FontMetricsDeleted(const nsFontMetrics
* aFontMetrics
)
202 mFontMetrics
.RemoveElement(aFontMetrics
);
206 nsFontCache::Compact()
208 // Need to loop backward because the running element can be removed on
210 for (int32_t i
= mFontMetrics
.Length()-1; i
>= 0; --i
) {
211 nsFontMetrics
* fm
= mFontMetrics
[i
];
212 nsFontMetrics
* oldfm
= fm
;
213 // Destroy() isn't here because we want our device context to be
215 NS_RELEASE(fm
); // this will reset fm to nullptr
216 // if the font is really gone, it would have called back in
217 // FontMetricsDeleted() and would have removed itself
218 if (mFontMetrics
.IndexOf(oldfm
) != mFontMetrics
.NoIndex
) {
219 // nope, the font is still there, so let's hold onto it too
228 for (int32_t i
= mFontMetrics
.Length()-1; i
>= 0; --i
) {
229 nsFontMetrics
* fm
= mFontMetrics
[i
];
230 // Destroy() will unhook our device context from the fm so that we
231 // won't waste time in triggering the notification of
232 // FontMetricsDeleted() in the subsequent release
236 mFontMetrics
.Clear();
239 nsDeviceContext::nsDeviceContext()
240 : mWidth(0), mHeight(0), mDepth(0),
241 mAppUnitsPerDevPixel(-1), mAppUnitsPerDevNotScaledPixel(-1),
242 mAppUnitsPerPhysicalInch(-1),
243 mPixelScale(1.0f
), mPrintingScale(1.0f
),
246 MOZ_ASSERT(NS_IsMainThread(), "nsDeviceContext created off main thread");
249 // Note: we use a bare pointer for mFontCache so that nsFontCache
250 // can be an incomplete type in nsDeviceContext.h.
251 // Therefore we have to do all the refcounting by hand.
252 nsDeviceContext::~nsDeviceContext()
255 mFontCache
->Destroy();
256 NS_RELEASE(mFontCache
);
261 nsDeviceContext::GetMetricsFor(const nsFont
& aFont
,
263 gfxUserFontSet
* aUserFontSet
,
264 gfxTextPerfMetrics
* aTextPerf
,
265 nsFontMetrics
*& aMetrics
)
268 mFontCache
= new nsFontCache();
269 NS_ADDREF(mFontCache
);
270 mFontCache
->Init(this);
273 return mFontCache
->GetMetricsFor(aFont
, aLanguage
, aUserFontSet
,
274 aTextPerf
, aMetrics
);
278 nsDeviceContext::FlushFontCache(void)
286 nsDeviceContext::FontMetricsDeleted(const nsFontMetrics
* aFontMetrics
)
289 mFontCache
->FontMetricsDeleted(aFontMetrics
);
295 nsDeviceContext::IsPrinterSurface()
297 return mPrintingSurface
!= nullptr;
301 nsDeviceContext::SetDPI()
305 // PostScript, PDF and Mac (when printing) all use 72 dpi
306 // Use a printing DC to determine the other dpi values
307 if (mPrintingSurface
) {
308 switch (mPrintingSurface
->GetType()) {
309 case gfxSurfaceType::PDF
:
310 case gfxSurfaceType::PS
:
311 case gfxSurfaceType::Quartz
:
315 case gfxSurfaceType::Win32
:
316 case gfxSurfaceType::Win32Printing
: {
317 HDC dc
= reinterpret_cast<gfxWindowsSurface
*>(mPrintingSurface
.get())->GetDC();
318 int32_t OSVal
= GetDeviceCaps(dc
, LOGPIXELSY
);
320 mPrintingScale
= float(OSVal
) / dpi
;
325 NS_NOTREACHED("Unexpected printing surface type");
329 mAppUnitsPerDevNotScaledPixel
=
330 NS_lround((AppUnitsPerCSSPixel() * 96) / dpi
);
332 // A value of -1 means use the maximum of 96 and the system DPI.
333 // A value of 0 means use the system DPI. A positive value is used as the DPI.
334 // This sets the physical size of a device pixel and thus controls the
335 // interpretation of physical units.
336 int32_t prefDPI
= Preferences::GetInt("layout.css.dpi", -1);
340 } else if (mWidget
) {
341 dpi
= mWidget
->GetDPI();
344 dpi
= std::max(96.0f
, dpi
);
350 CSSToLayoutDeviceScale scale
= mWidget
? mWidget
->GetDefaultScale()
351 : CSSToLayoutDeviceScale(1.0);
352 double devPixelsPerCSSPixel
= scale
.scale
;
354 mAppUnitsPerDevNotScaledPixel
=
355 std::max(1, NS_lround(AppUnitsPerCSSPixel() / devPixelsPerCSSPixel
));
358 NS_ASSERTION(dpi
!= -1.0, "no dpi set");
360 mAppUnitsPerPhysicalInch
= NS_lround(dpi
* mAppUnitsPerDevNotScaledPixel
);
361 UpdateScaledAppUnits();
365 nsDeviceContext::Init(nsIWidget
*aWidget
)
367 if (mScreenManager
&& mWidget
== aWidget
)
376 mScreenManager
= do_GetService("@mozilla.org/gfx/screenmanager;1");
381 already_AddRefed
<nsRenderingContext
>
382 nsDeviceContext::CreateRenderingContext()
384 nsRefPtr
<gfxASurface
> printingSurface
= mPrintingSurface
;
386 // CreateRenderingContext() can be called (on reflow) after EndPage()
387 // but before BeginPage(). On OS X (and only there) mPrintingSurface
388 // will in this case be null, because OS X printing surfaces are
389 // per-page, and therefore only truly valid between calls to BeginPage()
390 // and EndPage(). But we can get away with fudging things here, if need
391 // be, by using a cached copy.
392 if (!printingSurface
) {
393 printingSurface
= mCachedPrintingSurface
;
396 nsRefPtr
<nsRenderingContext
> pContext
= new nsRenderingContext();
398 RefPtr
<gfx::DrawTarget
> dt
=
399 gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(printingSurface
,
400 gfx::IntSize(mWidth
, mHeight
));
403 dt
->AddUserData(&gfxContext::sDontUseAsSourceKey
, dt
, nullptr);
406 pContext
->Init(this, dt
);
407 pContext
->ThebesContext()->SetFlag(gfxContext::FLAG_DISABLE_SNAPPING
);
408 pContext
->Scale(mPrintingScale
, mPrintingScale
);
410 return pContext
.forget();
414 nsDeviceContext::GetDepth(uint32_t& aDepth
)
417 nsCOMPtr
<nsIScreen
> primaryScreen
;
418 mScreenManager
->GetPrimaryScreen(getter_AddRefs(primaryScreen
));
419 primaryScreen
->GetColorDepth(reinterpret_cast<int32_t *>(&mDepth
));
427 nsDeviceContext::GetDeviceSurfaceDimensions(nscoord
&aWidth
, nscoord
&aHeight
)
429 if (mPrintingSurface
) {
430 // we have a printer device
435 ComputeFullAreaUsingScreen(&area
);
437 aHeight
= area
.height
;
444 nsDeviceContext::GetRect(nsRect
&aRect
)
446 if (mPrintingSurface
) {
447 // we have a printer device
450 aRect
.width
= mWidth
;
451 aRect
.height
= mHeight
;
453 ComputeFullAreaUsingScreen ( &aRect
);
459 nsDeviceContext::GetClientRect(nsRect
&aRect
)
461 if (mPrintingSurface
) {
462 // we have a printer device
465 aRect
.width
= mWidth
;
466 aRect
.height
= mHeight
;
469 ComputeClientRectUsingScreen(&aRect
);
475 nsDeviceContext::InitForPrinting(nsIDeviceContextSpec
*aDevice
)
477 NS_ENSURE_ARG_POINTER(aDevice
);
479 mDeviceContextSpec
= aDevice
;
481 nsresult rv
= aDevice
->GetSurfaceForPrinter(getter_AddRefs(mPrintingSurface
));
483 return NS_ERROR_FAILURE
;
493 nsDeviceContext::BeginDocument(const nsAString
& aTitle
,
494 char16_t
* aPrintToFileName
,
498 static const char16_t kEmpty
[] = { '\0' };
501 rv
= mPrintingSurface
->BeginPrinting(aTitle
,
502 nsDependentString(aPrintToFileName
? aPrintToFileName
: kEmpty
));
504 if (NS_SUCCEEDED(rv
) && mDeviceContextSpec
)
505 rv
= mDeviceContextSpec
->BeginDocument(aTitle
, aPrintToFileName
, aStartPage
, aEndPage
);
512 nsDeviceContext::EndDocument(void)
516 if (mPrintingSurface
) {
517 rv
= mPrintingSurface
->EndPrinting();
518 if (NS_SUCCEEDED(rv
))
519 mPrintingSurface
->Finish();
522 if (mDeviceContextSpec
)
523 mDeviceContextSpec
->EndDocument();
530 nsDeviceContext::AbortDocument(void)
532 nsresult rv
= mPrintingSurface
->AbortPrinting();
534 if (mDeviceContextSpec
)
535 mDeviceContextSpec
->EndDocument();
542 nsDeviceContext::BeginPage(void)
546 if (mDeviceContextSpec
)
547 rv
= mDeviceContextSpec
->BeginPage();
549 if (NS_FAILED(rv
)) return rv
;
552 // We need to get a new surface for each page on the Mac, as the
553 // CGContextRefs are only good for one page.
554 mDeviceContextSpec
->GetSurfaceForPrinter(getter_AddRefs(mPrintingSurface
));
557 rv
= mPrintingSurface
->BeginPage();
563 nsDeviceContext::EndPage(void)
565 nsresult rv
= mPrintingSurface
->EndPage();
568 // We need to release the CGContextRef in the surface here, plus it's
569 // not something you would want anyway, as these CGContextRefs are only
570 // good for one page. But we need to keep a cached reference to it, since
571 // CreateRenderingContext() may try to access it when mPrintingSurface
572 // would normally be null. See bug 665218. If we just stop nulling out
573 // mPrintingSurface here (and thereby make that our cached copy), we'll
574 // break all our null checks on mPrintingSurface. See bug 684622.
575 mCachedPrintingSurface
= mPrintingSurface
;
576 mPrintingSurface
= nullptr;
579 if (mDeviceContextSpec
)
580 mDeviceContextSpec
->EndPage();
586 nsDeviceContext::ComputeClientRectUsingScreen(nsRect
* outRect
)
588 // we always need to recompute the clientRect
589 // because the window may have moved onto a different screen. In the single
590 // monitor case, we only need to do the computation if we haven't done it
591 // once already, and remember that we have because we're assured it won't change.
592 nsCOMPtr
<nsIScreen
> screen
;
593 FindScreen (getter_AddRefs(screen
));
595 int32_t x
, y
, width
, height
;
596 screen
->GetAvailRect(&x
, &y
, &width
, &height
);
598 // convert to device units
599 outRect
->y
= NSIntPixelsToAppUnits(y
, AppUnitsPerDevPixel());
600 outRect
->x
= NSIntPixelsToAppUnits(x
, AppUnitsPerDevPixel());
601 outRect
->width
= NSIntPixelsToAppUnits(width
, AppUnitsPerDevPixel());
602 outRect
->height
= NSIntPixelsToAppUnits(height
, AppUnitsPerDevPixel());
607 nsDeviceContext::ComputeFullAreaUsingScreen(nsRect
* outRect
)
609 // if we have more than one screen, we always need to recompute the clientRect
610 // because the window may have moved onto a different screen. In the single
611 // monitor case, we only need to do the computation if we haven't done it
612 // once already, and remember that we have because we're assured it won't change.
613 nsCOMPtr
<nsIScreen
> screen
;
614 FindScreen ( getter_AddRefs(screen
) );
616 int32_t x
, y
, width
, height
;
617 screen
->GetRect ( &x
, &y
, &width
, &height
);
619 // convert to device units
620 outRect
->y
= NSIntPixelsToAppUnits(y
, AppUnitsPerDevPixel());
621 outRect
->x
= NSIntPixelsToAppUnits(x
, AppUnitsPerDevPixel());
622 outRect
->width
= NSIntPixelsToAppUnits(width
, AppUnitsPerDevPixel());
623 outRect
->height
= NSIntPixelsToAppUnits(height
, AppUnitsPerDevPixel());
625 mWidth
= outRect
->width
;
626 mHeight
= outRect
->height
;
633 // Determines which screen intersects the largest area of the given surface.
636 nsDeviceContext::FindScreen(nsIScreen
** outScreen
)
638 if (mWidget
&& mWidget
->GetOwningTabChild()) {
639 mScreenManager
->ScreenForNativeWidget((void *)mWidget
->GetOwningTabChild(),
642 else if (mWidget
&& mWidget
->GetNativeData(NS_NATIVE_WINDOW
)) {
643 mScreenManager
->ScreenForNativeWidget(mWidget
->GetNativeData(NS_NATIVE_WINDOW
),
647 mScreenManager
->GetPrimaryScreen(outScreen
);
652 nsDeviceContext::CalcPrintingSize()
654 if (!mPrintingSurface
)
657 bool inPoints
= true;
660 switch (mPrintingSurface
->GetType()) {
661 case gfxSurfaceType::Image
:
663 size
= reinterpret_cast<gfxImageSurface
*>(mPrintingSurface
.get())->GetSize();
666 #if defined(MOZ_PDF_PRINTING)
667 case gfxSurfaceType::PDF
:
669 size
= reinterpret_cast<gfxPDFSurface
*>(mPrintingSurface
.get())->GetSize();
673 #ifdef MOZ_WIDGET_GTK
674 case gfxSurfaceType::PS
:
676 size
= reinterpret_cast<gfxPSSurface
*>(mPrintingSurface
.get())->GetSize();
681 case gfxSurfaceType::Quartz
:
682 inPoints
= true; // this is really only true when we're printing
683 size
= reinterpret_cast<gfxQuartzSurface
*>(mPrintingSurface
.get())->GetSize();
688 case gfxSurfaceType::Win32
:
689 case gfxSurfaceType::Win32Printing
:
692 HDC dc
= reinterpret_cast<gfxWindowsSurface
*>(mPrintingSurface
.get())->GetDC();
694 dc
= GetDC((HWND
)mWidget
->GetNativeData(NS_NATIVE_WIDGET
));
695 size
.width
= NSFloatPixelsToAppUnits(::GetDeviceCaps(dc
, HORZRES
)/mPrintingScale
, AppUnitsPerDevPixel());
696 size
.height
= NSFloatPixelsToAppUnits(::GetDeviceCaps(dc
, VERTRES
)/mPrintingScale
, AppUnitsPerDevPixel());
697 mDepth
= (uint32_t)::GetDeviceCaps(dc
, BITSPIXEL
);
698 if (dc
!= reinterpret_cast<gfxWindowsSurface
*>(mPrintingSurface
.get())->GetDC())
699 ReleaseDC((HWND
)mWidget
->GetNativeData(NS_NATIVE_WIDGET
), dc
);
705 NS_ERROR("trying to print to unknown surface type");
709 // For printing, CSS inches and physical inches are identical
710 // so it doesn't matter which we use here
711 mWidth
= NSToCoordRound(float(size
.width
) * AppUnitsPerPhysicalInch() / 72);
712 mHeight
= NSToCoordRound(float(size
.height
) * AppUnitsPerPhysicalInch() / 72);
714 mWidth
= NSToIntRound(size
.width
);
715 mHeight
= NSToIntRound(size
.height
);
719 bool nsDeviceContext::CheckDPIChange() {
720 int32_t oldDevPixels
= mAppUnitsPerDevNotScaledPixel
;
721 int32_t oldInches
= mAppUnitsPerPhysicalInch
;
725 return oldDevPixels
!= mAppUnitsPerDevNotScaledPixel
||
726 oldInches
!= mAppUnitsPerPhysicalInch
;
730 nsDeviceContext::SetPixelScale(float aScale
)
733 NS_NOTREACHED("Invalid pixel scale value");
736 int32_t oldAppUnitsPerDevPixel
= mAppUnitsPerDevPixel
;
737 mPixelScale
= aScale
;
738 UpdateScaledAppUnits();
739 return oldAppUnitsPerDevPixel
!= mAppUnitsPerDevPixel
;
743 nsDeviceContext::UpdateScaledAppUnits()
745 mAppUnitsPerDevPixel
=
746 std::max(1, NSToIntRound(float(mAppUnitsPerDevNotScaledPixel
) / mPixelScale
));
747 // adjust mPixelScale to reflect appunit rounding
748 mPixelScale
= float(mAppUnitsPerDevNotScaledPixel
) / mAppUnitsPerDevPixel
;