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"
8 #include "nsFontMetrics.h"
9 #include "nsRenderingContext.h"
10 #include "nsIWidget.h"
12 #include "mozilla/Attributes.h"
13 #include "mozilla/Services.h"
14 #include "mozilla/Preferences.h"
15 #include "nsIServiceManager.h"
16 #include "nsILanguageAtomService.h"
17 #include "nsIObserver.h"
18 #include "nsIObserverService.h"
20 #include "gfxImageSurface.h"
24 #include "gfxPDFSurface.h"
28 #include "gfxPSSurface.h"
30 #include "gfxWindowsSurface.h"
32 #include "gfxOS2Surface.h"
34 #include "gfxQuartzSurface.h"
37 using namespace mozilla
;
38 using mozilla::services::GetObserverService
;
40 class nsFontCache MOZ_FINAL
: public nsIObserver
43 nsFontCache() { MOZ_COUNT_CTOR(nsFontCache
); }
44 ~nsFontCache() { MOZ_COUNT_DTOR(nsFontCache
); }
49 void Init(nsDeviceContext
* aContext
);
52 nsresult
GetMetricsFor(const nsFont
& aFont
, nsIAtom
* aLanguage
,
53 gfxUserFontSet
* aUserFontSet
,
54 nsFontMetrics
*& aMetrics
);
56 void FontMetricsDeleted(const nsFontMetrics
* aFontMetrics
);
61 nsDeviceContext
* mContext
; // owner
62 nsCOMPtr
<nsIAtom
> mLocaleLanguage
;
63 nsTArray
<nsFontMetrics
*> mFontMetrics
;
66 NS_IMPL_ISUPPORTS1(nsFontCache
, nsIObserver
)
68 // The Init and Destroy methods are necessary because it's not
69 // safe to call AddObserver from a constructor or RemoveObserver
70 // from a destructor. That should be fixed.
72 nsFontCache::Init(nsDeviceContext
* aContext
)
75 // register as a memory-pressure observer to free font resources
76 // in low-memory situations.
77 nsCOMPtr
<nsIObserverService
> obs
= GetObserverService();
79 obs
->AddObserver(this, "memory-pressure", false);
81 nsCOMPtr
<nsILanguageAtomService
> langService
;
82 langService
= do_GetService(NS_LANGUAGEATOMSERVICE_CONTRACTID
);
84 mLocaleLanguage
= langService
->GetLocaleLanguage();
86 if (!mLocaleLanguage
) {
87 mLocaleLanguage
= do_GetAtom("x-western");
92 nsFontCache::Destroy()
94 nsCOMPtr
<nsIObserverService
> obs
= GetObserverService();
96 obs
->RemoveObserver(this, "memory-pressure");
101 nsFontCache::Observe(nsISupports
*, const char* aTopic
, const PRUnichar
*)
103 if (!nsCRT::strcmp(aTopic
, "memory-pressure"))
109 nsFontCache::GetMetricsFor(const nsFont
& aFont
, nsIAtom
* aLanguage
,
110 gfxUserFontSet
* aUserFontSet
,
111 nsFontMetrics
*& aMetrics
)
114 aLanguage
= mLocaleLanguage
;
116 // First check our cache
117 // start from the end, which is where we put the most-recent-used element
120 int32_t n
= mFontMetrics
.Length() - 1;
121 for (int32_t i
= n
; i
>= 0; --i
) {
122 fm
= mFontMetrics
[i
];
123 if (fm
->Font().Equals(aFont
) && fm
->GetUserFontSet() == aUserFontSet
&&
124 fm
->Language() == aLanguage
) {
126 // promote it to the end of the cache
127 mFontMetrics
.RemoveElementAt(i
);
128 mFontMetrics
.AppendElement(fm
);
130 fm
->GetThebesFontGroup()->UpdateFontList();
131 NS_ADDREF(aMetrics
= fm
);
136 // It's not in the cache. Get font metrics and then cache them.
138 fm
= new nsFontMetrics();
140 nsresult rv
= fm
->Init(aFont
, aLanguage
, mContext
, aUserFontSet
);
141 if (NS_SUCCEEDED(rv
)) {
142 // the mFontMetrics list has the "head" at the end, because append
143 // is cheaper than insert
144 mFontMetrics
.AppendElement(fm
);
152 // One reason why Init() fails is because the system is running out of
153 // resources. e.g., on Win95/98 only a very limited number of GDI
154 // objects are available. Compact the cache and try again.
157 fm
= new nsFontMetrics();
159 rv
= fm
->Init(aFont
, aLanguage
, mContext
, aUserFontSet
);
160 if (NS_SUCCEEDED(rv
)) {
161 mFontMetrics
.AppendElement(fm
);
168 // could not setup a new one, send an old one (XXX search a "best
171 n
= mFontMetrics
.Length() - 1; // could have changed in Compact()
173 aMetrics
= mFontMetrics
[n
];
178 NS_POSTCONDITION(NS_SUCCEEDED(rv
),
179 "font metrics should not be null - bug 136248");
184 nsFontCache::FontMetricsDeleted(const nsFontMetrics
* aFontMetrics
)
186 mFontMetrics
.RemoveElement(aFontMetrics
);
190 nsFontCache::Compact()
192 // Need to loop backward because the running element can be removed on
194 for (int32_t i
= mFontMetrics
.Length()-1; i
>= 0; --i
) {
195 nsFontMetrics
* fm
= mFontMetrics
[i
];
196 nsFontMetrics
* oldfm
= fm
;
197 // Destroy() isn't here because we want our device context to be
199 NS_RELEASE(fm
); // this will reset fm to nullptr
200 // if the font is really gone, it would have called back in
201 // FontMetricsDeleted() and would have removed itself
202 if (mFontMetrics
.IndexOf(oldfm
) != mFontMetrics
.NoIndex
) {
203 // nope, the font is still there, so let's hold onto it too
212 for (int32_t i
= mFontMetrics
.Length()-1; i
>= 0; --i
) {
213 nsFontMetrics
* fm
= mFontMetrics
[i
];
214 // Destroy() will unhook our device context from the fm so that we
215 // won't waste time in triggering the notification of
216 // FontMetricsDeleted() in the subsequent release
220 mFontMetrics
.Clear();
223 nsDeviceContext::nsDeviceContext()
224 : mWidth(0), mHeight(0), mDepth(0),
225 mAppUnitsPerDevPixel(-1), mAppUnitsPerDevNotScaledPixel(-1),
226 mAppUnitsPerPhysicalInch(-1),
227 mPixelScale(1.0f
), mPrintingScale(1.0f
),
230 MOZ_ASSERT(NS_IsMainThread(), "nsDeviceContext created off main thread");
233 // Note: we use a bare pointer for mFontCache so that nsFontCache
234 // can be an incomplete type in nsDeviceContext.h.
235 // Therefore we have to do all the refcounting by hand.
236 nsDeviceContext::~nsDeviceContext()
239 mFontCache
->Destroy();
240 NS_RELEASE(mFontCache
);
245 nsDeviceContext::GetMetricsFor(const nsFont
& aFont
,
247 gfxUserFontSet
* aUserFontSet
,
248 nsFontMetrics
*& aMetrics
)
251 mFontCache
= new nsFontCache();
252 NS_ADDREF(mFontCache
);
253 mFontCache
->Init(this);
256 return mFontCache
->GetMetricsFor(aFont
, aLanguage
, aUserFontSet
, aMetrics
);
260 nsDeviceContext::FlushFontCache(void)
268 nsDeviceContext::FontMetricsDeleted(const nsFontMetrics
* aFontMetrics
)
271 mFontCache
->FontMetricsDeleted(aFontMetrics
);
277 nsDeviceContext::IsPrinterSurface()
279 return(mPrintingSurface
!= NULL
);
283 nsDeviceContext::SetDPI()
287 // PostScript, PDF and Mac (when printing) all use 72 dpi
288 // Use a printing DC to determine the other dpi values
289 if (mPrintingSurface
) {
290 switch (mPrintingSurface
->GetType()) {
291 case gfxASurface::SurfaceTypePDF
:
292 case gfxASurface::SurfaceTypePS
:
293 case gfxASurface::SurfaceTypeQuartz
:
297 case gfxASurface::SurfaceTypeWin32
:
298 case gfxASurface::SurfaceTypeWin32Printing
: {
299 HDC dc
= reinterpret_cast<gfxWindowsSurface
*>(mPrintingSurface
.get())->GetDC();
300 int32_t OSVal
= GetDeviceCaps(dc
, LOGPIXELSY
);
302 mPrintingScale
= float(OSVal
) / dpi
;
307 case gfxASurface::SurfaceTypeOS2
: {
309 HDC dc
= GpiQueryDevice(reinterpret_cast<gfxOS2Surface
*>(mPrintingSurface
.get())->GetPS());
310 if (DevQueryCaps(dc
, CAPS_VERTICAL_FONT_RES
, 1, &lDPI
))
316 NS_NOTREACHED("Unexpected printing surface type");
320 mAppUnitsPerDevNotScaledPixel
=
321 NS_lround((AppUnitsPerCSSPixel() * 96) / dpi
);
323 // A value of -1 means use the maximum of 96 and the system DPI.
324 // A value of 0 means use the system DPI. A positive value is used as the DPI.
325 // This sets the physical size of a device pixel and thus controls the
326 // interpretation of physical units.
327 int32_t prefDPI
= Preferences::GetInt("layout.css.dpi", -1);
331 } else if (mWidget
) {
332 dpi
= mWidget
->GetDPI();
335 dpi
= std::max(96.0f
, dpi
);
341 double devPixelsPerCSSPixel
= mWidget
? mWidget
->GetDefaultScale() : 1.0;
343 mAppUnitsPerDevNotScaledPixel
=
344 std::max(1, NS_lround(AppUnitsPerCSSPixel() / devPixelsPerCSSPixel
));
347 NS_ASSERTION(dpi
!= -1.0, "no dpi set");
349 mAppUnitsPerPhysicalInch
= NS_lround(dpi
* mAppUnitsPerDevNotScaledPixel
);
350 UpdateScaledAppUnits();
354 nsDeviceContext::Init(nsIWidget
*aWidget
)
356 if (mScreenManager
&& mWidget
== aWidget
)
365 mScreenManager
= do_GetService("@mozilla.org/gfx/screenmanager;1");
371 nsDeviceContext::CreateRenderingContext(nsRenderingContext
*&aContext
)
373 nsRefPtr
<gfxASurface
> printingSurface
= mPrintingSurface
;
375 // CreateRenderingContext() can be called (on reflow) after EndPage()
376 // but before BeginPage(). On OS X (and only there) mPrintingSurface
377 // will in this case be null, because OS X printing surfaces are
378 // per-page, and therefore only truly valid between calls to BeginPage()
379 // and EndPage(). But we can get away with fudging things here, if need
380 // be, by using a cached copy.
381 if (!printingSurface
) {
382 printingSurface
= mCachedPrintingSurface
;
385 nsRefPtr
<nsRenderingContext
> pContext
= new nsRenderingContext();
387 pContext
->Init(this, printingSurface
);
388 pContext
->Scale(mPrintingScale
, mPrintingScale
);
396 nsDeviceContext::GetDepth(uint32_t& aDepth
)
399 nsCOMPtr
<nsIScreen
> primaryScreen
;
400 mScreenManager
->GetPrimaryScreen(getter_AddRefs(primaryScreen
));
401 primaryScreen
->GetColorDepth(reinterpret_cast<int32_t *>(&mDepth
));
409 nsDeviceContext::GetDeviceSurfaceDimensions(nscoord
&aWidth
, nscoord
&aHeight
)
411 if (mPrintingSurface
) {
412 // we have a printer device
417 ComputeFullAreaUsingScreen(&area
);
419 aHeight
= area
.height
;
426 nsDeviceContext::GetRect(nsRect
&aRect
)
428 if (mPrintingSurface
) {
429 // we have a printer device
432 aRect
.width
= mWidth
;
433 aRect
.height
= mHeight
;
435 ComputeFullAreaUsingScreen ( &aRect
);
441 nsDeviceContext::GetClientRect(nsRect
&aRect
)
443 if (mPrintingSurface
) {
444 // we have a printer device
447 aRect
.width
= mWidth
;
448 aRect
.height
= mHeight
;
451 ComputeClientRectUsingScreen(&aRect
);
457 nsDeviceContext::InitForPrinting(nsIDeviceContextSpec
*aDevice
)
459 NS_ENSURE_ARG_POINTER(aDevice
);
461 mDeviceContextSpec
= aDevice
;
463 nsresult rv
= aDevice
->GetSurfaceForPrinter(getter_AddRefs(mPrintingSurface
));
465 return NS_ERROR_FAILURE
;
475 nsDeviceContext::BeginDocument(PRUnichar
* aTitle
,
476 PRUnichar
* aPrintToFileName
,
480 static const PRUnichar kEmpty
[] = { '\0' };
483 rv
= mPrintingSurface
->BeginPrinting(nsDependentString(aTitle
? aTitle
: kEmpty
),
484 nsDependentString(aPrintToFileName
? aPrintToFileName
: kEmpty
));
486 if (NS_SUCCEEDED(rv
) && mDeviceContextSpec
)
487 rv
= mDeviceContextSpec
->BeginDocument(aTitle
, aPrintToFileName
, aStartPage
, aEndPage
);
494 nsDeviceContext::EndDocument(void)
498 if (mPrintingSurface
) {
499 rv
= mPrintingSurface
->EndPrinting();
500 if (NS_SUCCEEDED(rv
))
501 mPrintingSurface
->Finish();
504 if (mDeviceContextSpec
)
505 mDeviceContextSpec
->EndDocument();
512 nsDeviceContext::AbortDocument(void)
514 nsresult rv
= mPrintingSurface
->AbortPrinting();
516 if (mDeviceContextSpec
)
517 mDeviceContextSpec
->EndDocument();
524 nsDeviceContext::BeginPage(void)
528 if (mDeviceContextSpec
)
529 rv
= mDeviceContextSpec
->BeginPage();
531 if (NS_FAILED(rv
)) return rv
;
534 // We need to get a new surface for each page on the Mac, as the
535 // CGContextRefs are only good for one page.
536 mDeviceContextSpec
->GetSurfaceForPrinter(getter_AddRefs(mPrintingSurface
));
539 rv
= mPrintingSurface
->BeginPage();
545 nsDeviceContext::EndPage(void)
547 nsresult rv
= mPrintingSurface
->EndPage();
550 // We need to release the CGContextRef in the surface here, plus it's
551 // not something you would want anyway, as these CGContextRefs are only
552 // good for one page. But we need to keep a cached reference to it, since
553 // CreateRenderingContext() may try to access it when mPrintingSurface
554 // would normally be null. See bug 665218. If we just stop nulling out
555 // mPrintingSurface here (and thereby make that our cached copy), we'll
556 // break all our null checks on mPrintingSurface. See bug 684622.
557 mCachedPrintingSurface
= mPrintingSurface
;
558 mPrintingSurface
= nullptr;
561 if (mDeviceContextSpec
)
562 mDeviceContextSpec
->EndPage();
568 nsDeviceContext::ComputeClientRectUsingScreen(nsRect
* outRect
)
570 // we always need to recompute the clientRect
571 // because the window may have moved onto a different screen. In the single
572 // monitor case, we only need to do the computation if we haven't done it
573 // once already, and remember that we have because we're assured it won't change.
574 nsCOMPtr
<nsIScreen
> screen
;
575 FindScreen (getter_AddRefs(screen
));
577 int32_t x
, y
, width
, height
;
578 screen
->GetAvailRect(&x
, &y
, &width
, &height
);
580 // convert to device units
581 outRect
->y
= NSIntPixelsToAppUnits(y
, AppUnitsPerDevPixel());
582 outRect
->x
= NSIntPixelsToAppUnits(x
, AppUnitsPerDevPixel());
583 outRect
->width
= NSIntPixelsToAppUnits(width
, AppUnitsPerDevPixel());
584 outRect
->height
= NSIntPixelsToAppUnits(height
, AppUnitsPerDevPixel());
589 nsDeviceContext::ComputeFullAreaUsingScreen(nsRect
* outRect
)
591 // if we have more than one screen, we always need to recompute the clientRect
592 // because the window may have moved onto a different screen. In the single
593 // monitor case, we only need to do the computation if we haven't done it
594 // once already, and remember that we have because we're assured it won't change.
595 nsCOMPtr
<nsIScreen
> screen
;
596 FindScreen ( getter_AddRefs(screen
) );
598 int32_t x
, y
, width
, height
;
599 screen
->GetRect ( &x
, &y
, &width
, &height
);
601 // convert to device units
602 outRect
->y
= NSIntPixelsToAppUnits(y
, AppUnitsPerDevPixel());
603 outRect
->x
= NSIntPixelsToAppUnits(x
, AppUnitsPerDevPixel());
604 outRect
->width
= NSIntPixelsToAppUnits(width
, AppUnitsPerDevPixel());
605 outRect
->height
= NSIntPixelsToAppUnits(height
, AppUnitsPerDevPixel());
607 mWidth
= outRect
->width
;
608 mHeight
= outRect
->height
;
615 // Determines which screen intersects the largest area of the given surface.
618 nsDeviceContext::FindScreen(nsIScreen
** outScreen
)
620 if (mWidget
&& mWidget
->GetNativeData(NS_NATIVE_WINDOW
))
621 mScreenManager
->ScreenForNativeWidget(mWidget
->GetNativeData(NS_NATIVE_WINDOW
),
624 mScreenManager
->GetPrimaryScreen(outScreen
);
628 nsDeviceContext::CalcPrintingSize()
630 if (!mPrintingSurface
)
633 bool inPoints
= true;
636 switch (mPrintingSurface
->GetType()) {
637 case gfxASurface::SurfaceTypeImage
:
639 size
= reinterpret_cast<gfxImageSurface
*>(mPrintingSurface
.get())->GetSize();
642 #if defined(MOZ_PDF_PRINTING)
643 case gfxASurface::SurfaceTypePDF
:
645 size
= reinterpret_cast<gfxPDFSurface
*>(mPrintingSurface
.get())->GetSize();
649 #ifdef MOZ_WIDGET_GTK
650 case gfxASurface::SurfaceTypePS
:
652 size
= reinterpret_cast<gfxPSSurface
*>(mPrintingSurface
.get())->GetSize();
657 case gfxASurface::SurfaceTypeQuartz
:
658 inPoints
= true; // this is really only true when we're printing
659 size
= reinterpret_cast<gfxQuartzSurface
*>(mPrintingSurface
.get())->GetSize();
664 case gfxASurface::SurfaceTypeWin32
:
665 case gfxASurface::SurfaceTypeWin32Printing
:
668 HDC dc
= reinterpret_cast<gfxWindowsSurface
*>(mPrintingSurface
.get())->GetDC();
670 dc
= GetDC((HWND
)mWidget
->GetNativeData(NS_NATIVE_WIDGET
));
671 size
.width
= NSFloatPixelsToAppUnits(::GetDeviceCaps(dc
, HORZRES
)/mPrintingScale
, AppUnitsPerDevPixel());
672 size
.height
= NSFloatPixelsToAppUnits(::GetDeviceCaps(dc
, VERTRES
)/mPrintingScale
, AppUnitsPerDevPixel());
673 mDepth
= (uint32_t)::GetDeviceCaps(dc
, BITSPIXEL
);
674 if (dc
!= reinterpret_cast<gfxWindowsSurface
*>(mPrintingSurface
.get())->GetDC())
675 ReleaseDC((HWND
)mWidget
->GetNativeData(NS_NATIVE_WIDGET
), dc
);
681 case gfxASurface::SurfaceTypeOS2
:
684 // we already set the size in the surface constructor we set for
685 // printing, so just get those values here
686 size
= reinterpret_cast<gfxOS2Surface
*>(mPrintingSurface
.get())->GetSize();
687 // as they are in pixels we need to scale them to app units
688 size
.width
= NSFloatPixelsToAppUnits(size
.width
, AppUnitsPerDevPixel());
689 size
.height
= NSFloatPixelsToAppUnits(size
.height
, AppUnitsPerDevPixel());
690 // still need to get the depth from the device context
691 HDC dc
= GpiQueryDevice(reinterpret_cast<gfxOS2Surface
*>(mPrintingSurface
.get())->GetPS());
693 if (DevQueryCaps(dc
, CAPS_COLOR_BITCOUNT
, 1, &value
))
696 mDepth
= 8; // default to 8bpp, should be enough for printers
701 NS_ERROR("trying to print to unknown surface type");
705 // For printing, CSS inches and physical inches are identical
706 // so it doesn't matter which we use here
707 mWidth
= NSToCoordRound(float(size
.width
) * AppUnitsPerPhysicalInch() / 72);
708 mHeight
= NSToCoordRound(float(size
.height
) * AppUnitsPerPhysicalInch() / 72);
710 mWidth
= NSToIntRound(size
.width
);
711 mHeight
= NSToIntRound(size
.height
);
715 bool nsDeviceContext::CheckDPIChange() {
716 int32_t oldDevPixels
= mAppUnitsPerDevNotScaledPixel
;
717 int32_t oldInches
= mAppUnitsPerPhysicalInch
;
721 return oldDevPixels
!= mAppUnitsPerDevNotScaledPixel
||
722 oldInches
!= mAppUnitsPerPhysicalInch
;
726 nsDeviceContext::SetPixelScale(float aScale
)
729 NS_NOTREACHED("Invalid pixel scale value");
732 int32_t oldAppUnitsPerDevPixel
= mAppUnitsPerDevPixel
;
733 mPixelScale
= aScale
;
734 UpdateScaledAppUnits();
735 return oldAppUnitsPerDevPixel
!= mAppUnitsPerDevPixel
;
739 nsDeviceContext::UpdateScaledAppUnits()
741 mAppUnitsPerDevPixel
=
742 std::max(1, NSToIntRound(float(mAppUnitsPerDevNotScaledPixel
) / mPixelScale
));
743 // adjust mPixelScale to reflect appunit rounding
744 mPixelScale
= float(mAppUnitsPerDevNotScaledPixel
) / mAppUnitsPerDevPixel
;