nsDeviceContext.cpp (13166B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set sw=2 ts=2 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/. */ 6 7 #include "nsDeviceContext.h" 8 #include <algorithm> // for max 9 #include "gfxContext.h" 10 #include "gfxPoint.h" // for gfxSize 11 #include "gfxTextRun.h" // for gfxFontGroup 12 #include "mozilla/LookAndFeel.h" 13 #include "mozilla/gfx/PathHelpers.h" 14 #include "mozilla/gfx/PrintTarget.h" 15 #include "mozilla/ProfilerMarkers.h" 16 #include "mozilla/StaticPrefs_layout.h" 17 #include "mozilla/Try.h" // for MOZ_TRY 18 #include "mozilla/widget/Screen.h" // for Screen 19 #include "nsDebug.h" // for NS_ASSERTION, etc 20 #include "nsFontMetrics.h" // for nsFontMetrics 21 #include "nsIDeviceContextSpec.h" // for nsIDeviceContextSpec 22 #include "nsIWidget.h" // for nsIWidget, NS_NATIVE_WINDOW 23 #include "nsRect.h" // for nsRect 24 #include "nsTArray.h" // for nsTArray, nsTArray_Impl 25 #include "mozilla/gfx/Logging.h" 26 #include "mozilla/widget/ScreenManager.h" // for ScreenManager 27 28 using namespace mozilla; 29 using namespace mozilla::gfx; 30 using mozilla::widget::ScreenManager; 31 32 nsDeviceContext::nsDeviceContext() 33 : mWidth(0), 34 mHeight(0), 35 mAppUnitsPerDevPixel(-1), 36 mAppUnitsPerDevPixelAtUnitFullZoom(-1), 37 mAppUnitsPerPhysicalInch(-1), 38 mFullZoom(1.0f), 39 mPrintingScale(1.0f), 40 mPrintingTranslate(gfxPoint(0, 0)), 41 mIsCurrentlyPrintingDoc(false) { 42 MOZ_ASSERT(NS_IsMainThread(), "nsDeviceContext created off main thread"); 43 } 44 45 nsDeviceContext::~nsDeviceContext() = default; 46 47 int32_t nsDeviceContext::ComputeAppUnitsPerDevPixelForWidgetScale( 48 CSSToLayoutDeviceScale aScale) { 49 return std::max(1, NS_lround(AppUnitsPerCSSPixel() / aScale.scale)); 50 } 51 52 int32_t nsDeviceContext::ApplyFullZoomToAPD(int32_t aUnzoomedAppUnits, 53 float aFullZoom) { 54 if (aFullZoom == 1.0f) { 55 return aUnzoomedAppUnits; 56 } 57 return std::max(1, NSToIntRound(float(aUnzoomedAppUnits) / aFullZoom)); 58 } 59 60 void nsDeviceContext::SetDPI() { 61 float dpi; 62 63 // Use the printing DC to determine DPI values, if we have one. 64 if (mDeviceContextSpec) { 65 dpi = mDeviceContextSpec->GetDPI(); 66 mPrintingScale = mDeviceContextSpec->GetPrintingScale(); 67 mPrintingTranslate = mDeviceContextSpec->GetPrintingTranslate(); 68 mAppUnitsPerDevPixelAtUnitFullZoom = 69 ComputeAppUnitsPerDevPixelForWidgetScale( 70 CSSToLayoutDeviceScale(dpi / 96.0)); 71 } else { 72 // A value of -1 means use the maximum of 96 and the system DPI. 73 // A value of 0 means use the system DPI. A positive value is used as the 74 // DPI. This sets the physical size of a device pixel and thus controls the 75 // interpretation of physical units. 76 int32_t prefDPI = StaticPrefs::layout_css_dpi(); 77 if (prefDPI > 0) { 78 dpi = prefDPI; 79 } else if (mWidget) { 80 dpi = mWidget->GetDPI(); 81 MOZ_ASSERT(dpi > 0); 82 if (prefDPI < 0) { 83 dpi = std::max(96.0f, dpi); 84 } 85 } else { 86 dpi = 96.0f; 87 } 88 89 CSSToLayoutDeviceScale scale = 90 mWidget ? mWidget->GetDefaultScale() : CSSToLayoutDeviceScale(1.0); 91 MOZ_ASSERT(scale.scale > 0.0); 92 mAppUnitsPerDevPixelAtUnitFullZoom = 93 ComputeAppUnitsPerDevPixelForWidgetScale(scale); 94 } 95 96 NS_ASSERTION(dpi != -1.0, "no dpi set"); 97 98 mAppUnitsPerPhysicalInch = 99 NS_lround(dpi * mAppUnitsPerDevPixelAtUnitFullZoom); 100 UpdateAppUnitsForFullZoom(); 101 } 102 103 void nsDeviceContext::Init(nsIWidget* aWidget) { 104 if (mIsInitialized && mWidget == aWidget) { 105 return; 106 } 107 108 // We can't assert |!mIsInitialized| here since EndSwapDocShellsForDocument 109 // re-initializes nsDeviceContext objects. We can only assert in 110 // InitForPrinting (below). 111 mIsInitialized = true; 112 113 mWidget = aWidget; 114 SetDPI(); 115 } 116 117 // XXX This is only for printing. We should make that obvious in the name. 118 UniquePtr<gfxContext> nsDeviceContext::CreateRenderingContext() { 119 return CreateRenderingContextCommon(/* not a reference context */ false); 120 } 121 122 UniquePtr<gfxContext> nsDeviceContext::CreateReferenceRenderingContext() { 123 return CreateRenderingContextCommon(/* a reference context */ true); 124 } 125 126 UniquePtr<gfxContext> nsDeviceContext::CreateRenderingContextCommon( 127 bool aWantReferenceContext) { 128 MOZ_ASSERT(IsPrinterContext()); 129 MOZ_ASSERT(mWidth > 0 && mHeight > 0); 130 131 if (NS_WARN_IF(!mPrintTarget)) { 132 // Printing canceled already. 133 return nullptr; 134 } 135 136 RefPtr<gfx::DrawTarget> dt; 137 if (aWantReferenceContext) { 138 dt = mPrintTarget->GetReferenceDrawTarget(); 139 } else { 140 // This will be null if printing a page from the parent process. 141 RefPtr<DrawEventRecorder> recorder; 142 mDeviceContextSpec->GetDrawEventRecorder(getter_AddRefs(recorder)); 143 dt = mPrintTarget->MakeDrawTarget(gfx::IntSize(mWidth, mHeight), recorder); 144 } 145 146 if (!dt || !dt->IsValid()) { 147 gfxCriticalNote << "Failed to create draw target in device context sized " 148 << mWidth << "x" << mHeight << " and pointer " 149 << hexa(mPrintTarget); 150 return nullptr; 151 } 152 153 dt->AddUserData(&sDisablePixelSnapping, (void*)0x1, nullptr); 154 155 auto pContext = MakeUnique<gfxContext>(dt); 156 157 gfxMatrix transform; 158 transform.PreTranslate(mPrintingTranslate); 159 transform.PreScale(mPrintingScale, mPrintingScale); 160 pContext->SetMatrixDouble(transform); 161 return pContext; 162 } 163 164 uint32_t nsDeviceContext::GetDepth() { 165 RefPtr<widget::Screen> screen = FindScreen(); 166 int32_t depth = 0; 167 screen->GetColorDepth(&depth); 168 return uint32_t(depth); 169 } 170 171 dom::ScreenColorGamut nsDeviceContext::GetColorGamut() { 172 RefPtr<widget::Screen> screen = FindScreen(); 173 dom::ScreenColorGamut colorGamut; 174 screen->GetColorGamut(&colorGamut); 175 return colorGamut; 176 } 177 178 hal::ScreenOrientation nsDeviceContext::GetScreenOrientationType() { 179 RefPtr<widget::Screen> screen = FindScreen(); 180 return screen->GetOrientationType(); 181 } 182 183 uint16_t nsDeviceContext::GetScreenOrientationAngle() { 184 RefPtr<widget::Screen> screen = FindScreen(); 185 return screen->GetOrientationAngle(); 186 } 187 188 bool nsDeviceContext::GetScreenIsHDR() { 189 RefPtr<widget::Screen> screen = FindScreen(); 190 return screen->GetIsHDR(); 191 } 192 193 nsSize nsDeviceContext::GetDeviceSurfaceDimensions() { 194 return GetRect().Size(); 195 } 196 197 nsRect nsDeviceContext::GetRect() { 198 if (IsPrinterContext()) { 199 return {0, 0, mWidth, mHeight}; 200 } 201 RefPtr<widget::Screen> screen = FindScreen(); 202 return LayoutDeviceIntRect::ToAppUnits(screen->GetRect(), 203 AppUnitsPerDevPixel()); 204 } 205 206 nsRect nsDeviceContext::GetClientRect() { 207 if (IsPrinterContext()) { 208 return {0, 0, mWidth, mHeight}; 209 } 210 RefPtr<widget::Screen> screen = FindScreen(); 211 return LayoutDeviceIntRect::ToAppUnits(screen->GetAvailRect(), 212 AppUnitsPerDevPixel()); 213 } 214 215 nsresult nsDeviceContext::InitForPrinting(nsIDeviceContextSpec* aDevice) { 216 NS_ENSURE_ARG_POINTER(aDevice); 217 218 MOZ_ASSERT(!mIsInitialized, 219 "Only initialize once, immediately after construction"); 220 221 // We don't set mIsInitialized here. The Init() call below does that. 222 223 mPrintTarget = aDevice->MakePrintTarget(); 224 if (!mPrintTarget) { 225 return NS_ERROR_FAILURE; 226 } 227 228 mDeviceContextSpec = aDevice; 229 230 Init(nullptr); 231 232 if (!CalcPrintingSize()) { 233 return NS_ERROR_FAILURE; 234 } 235 236 return NS_OK; 237 } 238 239 nsresult nsDeviceContext::BeginDocument(const nsAString& aTitle, 240 const nsAString& aPrintToFileName, 241 int32_t aStartPage, int32_t aEndPage) { 242 MOZ_DIAGNOSTIC_ASSERT(!mIsCurrentlyPrintingDoc, 243 "Mismatched BeginDocument/EndDocument calls"); 244 AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {}, 245 "nsDeviceContext::BeginDocument"_ns); 246 247 nsresult rv = mPrintTarget->BeginPrinting(aTitle, aPrintToFileName, 248 aStartPage, aEndPage); 249 250 if (NS_SUCCEEDED(rv)) { 251 if (mDeviceContextSpec) { 252 rv = mDeviceContextSpec->BeginDocument(aTitle, aPrintToFileName, 253 aStartPage, aEndPage); 254 } 255 mIsCurrentlyPrintingDoc = true; 256 } 257 258 // Warn about any failure (except user cancelling): 259 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv) || rv == NS_ERROR_ABORT, 260 "nsDeviceContext::BeginDocument failed"); 261 262 return rv; 263 } 264 265 RefPtr<PrintEndDocumentPromise> nsDeviceContext::EndDocument() { 266 MOZ_DIAGNOSTIC_ASSERT(mIsCurrentlyPrintingDoc, 267 "Mismatched BeginDocument/EndDocument calls"); 268 MOZ_DIAGNOSTIC_ASSERT(mPrintTarget); 269 AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {}, 270 "nsDeviceContext::EndDocument"_ns); 271 272 mIsCurrentlyPrintingDoc = false; 273 274 if (mPrintTarget) { 275 auto result = mPrintTarget->EndPrinting(); 276 if (NS_FAILED(result)) { 277 return PrintEndDocumentPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, 278 __func__); 279 } 280 mPrintTarget->Finish(); 281 mPrintTarget = nullptr; 282 } 283 284 if (mDeviceContextSpec) { 285 return mDeviceContextSpec->EndDocument(); 286 } 287 288 return PrintEndDocumentPromise::CreateAndResolve(true, __func__); 289 } 290 291 nsresult nsDeviceContext::AbortDocument() { 292 MOZ_DIAGNOSTIC_ASSERT(mIsCurrentlyPrintingDoc, 293 "Mismatched BeginDocument/EndDocument calls"); 294 AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {}, 295 "nsDeviceContext::AbortDocument"_ns); 296 297 nsresult rv = mPrintTarget->AbortPrinting(); 298 mIsCurrentlyPrintingDoc = false; 299 300 if (mDeviceContextSpec) { 301 (void)mDeviceContextSpec->EndDocument(); 302 } 303 304 mPrintTarget = nullptr; 305 306 return rv; 307 } 308 309 nsresult nsDeviceContext::BeginPage(const IntSize& aSizeInPoints) { 310 MOZ_DIAGNOSTIC_ASSERT(!mIsCurrentlyPrintingDoc || mPrintTarget, 311 "What nulled out our print target while printing?"); 312 AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {}, 313 "nsDeviceContext::BeginPage"_ns); 314 315 if (mDeviceContextSpec) { 316 MOZ_TRY(mDeviceContextSpec->BeginPage(aSizeInPoints)); 317 } 318 if (mPrintTarget) { 319 MOZ_TRY(mPrintTarget->BeginPage(aSizeInPoints)); 320 } 321 return NS_OK; 322 } 323 324 nsresult nsDeviceContext::EndPage() { 325 MOZ_DIAGNOSTIC_ASSERT(!mIsCurrentlyPrintingDoc || mPrintTarget, 326 "What nulled out our print target while printing?"); 327 AUTO_PROFILER_MARKER_TEXT("DeviceContext Printing", LAYOUT_Printing, {}, 328 "nsDeviceContext::EndPage"_ns); 329 330 if (mPrintTarget) { 331 MOZ_TRY(mPrintTarget->EndPage()); 332 } 333 if (mDeviceContextSpec) { 334 MOZ_TRY(mDeviceContextSpec->EndPage()); 335 } 336 return NS_OK; 337 } 338 339 already_AddRefed<widget::Screen> nsDeviceContext::FindScreen() { 340 if (mWidget) { 341 CheckDPIChange(); 342 if (RefPtr<widget::Screen> screen = mWidget->GetWidgetScreen()) { 343 return screen.forget(); 344 } 345 } 346 return ScreenManager::GetSingleton().GetPrimaryScreen(); 347 } 348 349 bool nsDeviceContext::CalcPrintingSize() { 350 gfxSize size(mPrintTarget->GetSize()); 351 // For printing, CSS inches and physical inches are identical 352 // so it doesn't matter which we use here 353 mWidth = NSToCoordRound(size.width * AppUnitsPerPhysicalInch() / 354 POINTS_PER_INCH_FLOAT); 355 mHeight = NSToCoordRound(size.height * AppUnitsPerPhysicalInch() / 356 POINTS_PER_INCH_FLOAT); 357 358 return (mWidth > 0 && mHeight > 0); 359 } 360 361 bool nsDeviceContext::CheckDPIChange() { 362 int32_t oldDevPixels = mAppUnitsPerDevPixelAtUnitFullZoom; 363 int32_t oldInches = mAppUnitsPerPhysicalInch; 364 365 SetDPI(); 366 367 return oldDevPixels != mAppUnitsPerDevPixelAtUnitFullZoom || 368 oldInches != mAppUnitsPerPhysicalInch; 369 } 370 371 bool nsDeviceContext::SetFullZoom(float aScale) { 372 if (aScale <= 0) { 373 MOZ_ASSERT_UNREACHABLE("Invalid full zoom value"); 374 return false; 375 } 376 int32_t oldAppUnitsPerDevPixel = mAppUnitsPerDevPixel; 377 mFullZoom = aScale; 378 UpdateAppUnitsForFullZoom(); 379 return oldAppUnitsPerDevPixel != mAppUnitsPerDevPixel; 380 } 381 382 int32_t nsDeviceContext::AppUnitsPerDevPixelInTopLevelChromePage() const { 383 // The only zoom that applies to chrome pages is the system zoom, if any. 384 return ApplyFullZoomToAPD(mAppUnitsPerDevPixelAtUnitFullZoom, 385 LookAndFeel::SystemZoomSettings().mFullZoom); 386 } 387 388 void nsDeviceContext::UpdateAppUnitsForFullZoom() { 389 mAppUnitsPerDevPixel = 390 ApplyFullZoomToAPD(mAppUnitsPerDevPixelAtUnitFullZoom, mFullZoom); 391 // adjust mFullZoom to reflect appunit rounding 392 mFullZoom = float(mAppUnitsPerDevPixelAtUnitFullZoom) / mAppUnitsPerDevPixel; 393 } 394 395 DesktopToLayoutDeviceScale nsDeviceContext::GetDesktopToDeviceScale() { 396 if (mWidget) { 397 RefPtr<widget::Screen> screen = FindScreen(); 398 return screen->GetDesktopToLayoutDeviceScale(); 399 } 400 return DesktopToLayoutDeviceScale(1.0); 401 }