DrawTargetCairo.cpp (68451B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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 "DrawTargetCairo.h" 8 9 #include "SourceSurfaceCairo.h" 10 #include "PathCairo.h" 11 #include "HelpersCairo.h" 12 #include "BorrowedContext.h" 13 #include "FilterNodeSoftware.h" 14 #include "mozilla/Vector.h" 15 #include "mozilla/StaticPrefs_gfx.h" 16 #include "mozilla/StaticPrefs_print.h" 17 #include "nsPrintfCString.h" 18 19 #include "cairo.h" 20 #include "cairo-tee.h" 21 #include <string.h> 22 23 #include "Blur.h" 24 #include "Logging.h" 25 #include "Tools.h" 26 27 #ifdef CAIRO_HAS_QUARTZ_SURFACE 28 # include "cairo-quartz.h" 29 # ifdef MOZ_WIDGET_COCOA 30 # include <ApplicationServices/ApplicationServices.h> 31 # endif 32 #endif 33 34 #ifdef CAIRO_HAS_XLIB_SURFACE 35 # include "cairo-xlib.h" 36 #endif 37 38 #ifdef CAIRO_HAS_WIN32_SURFACE 39 # include "cairo-win32.h" 40 #endif 41 42 #define PIXMAN_DONT_DEFINE_STDINT 43 #include "pixman.h" 44 45 // 2^23 46 #define CAIRO_COORD_MAX (Float(0x7fffff)) 47 48 namespace mozilla { 49 namespace gfx { 50 51 cairo_surface_t* DrawTargetCairo::mDummySurface; 52 53 namespace { 54 55 // An RAII class to prepare to draw a context and optional path. Saves and 56 // restores the context on construction/destruction. 57 class AutoPrepareForDrawing { 58 public: 59 AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx) : mCtx(ctx) { 60 dt->PrepareForDrawing(ctx); 61 cairo_save(mCtx); 62 MOZ_ASSERT(cairo_status(mCtx) || 63 dt->GetTransform().FuzzyEquals(GetTransform())); 64 } 65 66 AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx, const Path* path) 67 : mCtx(ctx) { 68 dt->PrepareForDrawing(ctx, path); 69 cairo_save(mCtx); 70 MOZ_ASSERT(cairo_status(mCtx) || 71 dt->GetTransform().FuzzyEquals(GetTransform())); 72 } 73 74 ~AutoPrepareForDrawing() { 75 cairo_restore(mCtx); 76 cairo_status_t status = cairo_status(mCtx); 77 if (status) { 78 gfxWarning() << "DrawTargetCairo context in error state: " 79 << cairo_status_to_string(status) << "(" << status << ")"; 80 } 81 } 82 83 private: 84 #ifdef DEBUG 85 Matrix GetTransform() { 86 cairo_matrix_t mat; 87 cairo_get_matrix(mCtx, &mat); 88 return Matrix(mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0); 89 } 90 #endif 91 92 cairo_t* mCtx; 93 }; 94 95 /* Clamp r to (0,0) (2^23,2^23) 96 * these are to be device coordinates. 97 * 98 * Returns false if the rectangle is completely out of bounds, 99 * true otherwise. 100 * 101 * This function assumes that it will be called with a rectangle being 102 * drawn into a surface with an identity transformation matrix; that 103 * is, anything above or to the left of (0,0) will be offscreen. 104 * 105 * First it checks if the rectangle is entirely beyond 106 * CAIRO_COORD_MAX; if so, it can't ever appear on the screen -- 107 * false is returned. 108 * 109 * Then it shifts any rectangles with x/y < 0 so that x and y are = 0, 110 * and adjusts the width and height appropriately. For example, a 111 * rectangle from (0,-5) with dimensions (5,10) will become a 112 * rectangle from (0,0) with dimensions (5,5). 113 * 114 * If after negative x/y adjustment to 0, either the width or height 115 * is negative, then the rectangle is completely offscreen, and 116 * nothing is drawn -- false is returned. 117 * 118 * Finally, if x+width or y+height are greater than CAIRO_COORD_MAX, 119 * the width and height are clamped such x+width or y+height are equal 120 * to CAIRO_COORD_MAX, and true is returned. 121 */ 122 static bool ConditionRect(Rect& r) { 123 // if either x or y is way out of bounds; 124 // note that we don't handle negative w/h here 125 if (r.X() > CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX) return false; 126 127 if (r.X() < 0.f) { 128 r.SetWidth(r.XMost()); 129 if (r.Width() < 0.f) return false; 130 r.MoveToX(0.f); 131 } 132 133 if (r.XMost() > CAIRO_COORD_MAX) { 134 r.SetRightEdge(CAIRO_COORD_MAX); 135 } 136 137 if (r.Y() < 0.f) { 138 r.SetHeight(r.YMost()); 139 if (r.Height() < 0.f) return false; 140 141 r.MoveToY(0.f); 142 } 143 144 if (r.YMost() > CAIRO_COORD_MAX) { 145 r.SetBottomEdge(CAIRO_COORD_MAX); 146 } 147 return true; 148 } 149 150 } // end anonymous namespace 151 152 static bool SupportsSelfCopy(cairo_surface_t* surface) { 153 switch (cairo_surface_get_type(surface)) { 154 #ifdef CAIRO_HAS_QUARTZ_SURFACE 155 case CAIRO_SURFACE_TYPE_QUARTZ: 156 return true; 157 #endif 158 #ifdef CAIRO_HAS_WIN32_SURFACE 159 case CAIRO_SURFACE_TYPE_WIN32: 160 case CAIRO_SURFACE_TYPE_WIN32_PRINTING: 161 return true; 162 #endif 163 default: 164 return false; 165 } 166 } 167 168 static bool PatternIsCompatible(const Pattern& aPattern) { 169 switch (aPattern.GetType()) { 170 case PatternType::LINEAR_GRADIENT: { 171 const LinearGradientPattern& pattern = 172 static_cast<const LinearGradientPattern&>(aPattern); 173 return pattern.mStops->GetBackendType() == BackendType::CAIRO; 174 } 175 case PatternType::RADIAL_GRADIENT: { 176 const RadialGradientPattern& pattern = 177 static_cast<const RadialGradientPattern&>(aPattern); 178 return pattern.mStops->GetBackendType() == BackendType::CAIRO; 179 } 180 case PatternType::CONIC_GRADIENT: { 181 const ConicGradientPattern& pattern = 182 static_cast<const ConicGradientPattern&>(aPattern); 183 return pattern.mStops->GetBackendType() == BackendType::CAIRO; 184 } 185 default: 186 return true; 187 } 188 } 189 190 static cairo_user_data_key_t surfaceDataKey; 191 192 static void ReleaseData(void* aData) { 193 DataSourceSurface* data = static_cast<DataSourceSurface*>(aData); 194 data->Unmap(); 195 data->Release(); 196 } 197 198 static cairo_surface_t* CopyToImageSurface(unsigned char* aData, 199 const IntSize& aSize, 200 const IntRect& aRect, 201 int32_t aStride, 202 SurfaceFormat aFormat) { 203 MOZ_ASSERT(aData); 204 205 auto aRectWidth = aRect.Width(); 206 auto aRectHeight = aRect.Height(); 207 208 cairo_surface_t* surf = cairo_image_surface_create( 209 GfxFormatToCairoFormat(aFormat), aRectWidth, aRectHeight); 210 // In certain scenarios, requesting larger than 8k image fails. Bug 803568 211 // covers the details of how to run into it, but the full detailed 212 // investigation hasn't been done to determine the underlying cause. We 213 // will just handle the failure to allocate the surface to avoid a crash. 214 if (cairo_surface_status(surf)) { 215 gfxWarning() << "Invalid surface DTC " << cairo_surface_status(surf); 216 return nullptr; 217 } 218 219 unsigned char* surfData = cairo_image_surface_get_data(surf); 220 size_t surfStride = cairo_image_surface_get_stride(surf); 221 size_t pixelWidth = BytesPerPixel(aFormat); 222 size_t rowDataWidth = size_t(aRectWidth) * pixelWidth; 223 if (rowDataWidth > surfStride || rowDataWidth > size_t(aStride) || 224 !IntRect(IntPoint(), aSize).Contains(aRect)) { 225 cairo_surface_destroy(surf); 226 return nullptr; 227 } 228 229 const unsigned char* sourceRow = aData + size_t(aRect.Y()) * size_t(aStride) + 230 size_t(aRect.X()) * pixelWidth; 231 unsigned char* destRow = surfData; 232 233 for (int32_t y = 0; y < aRectHeight; ++y) { 234 memcpy(destRow, sourceRow, rowDataWidth); 235 sourceRow += aStride; 236 destRow += surfStride; 237 } 238 cairo_surface_mark_dirty(surf); 239 return surf; 240 } 241 242 /** 243 * If aSurface can be represented as a surface of type 244 * CAIRO_SURFACE_TYPE_IMAGE then returns that surface. Does 245 * not add a reference. 246 */ 247 static cairo_surface_t* GetAsImageSurface(cairo_surface_t* aSurface) { 248 if (cairo_surface_get_type(aSurface) == CAIRO_SURFACE_TYPE_IMAGE) { 249 return aSurface; 250 #ifdef CAIRO_HAS_WIN32_SURFACE 251 } else if (cairo_surface_get_type(aSurface) == CAIRO_SURFACE_TYPE_WIN32) { 252 return cairo_win32_surface_get_image(aSurface); 253 #endif 254 } 255 256 return nullptr; 257 } 258 259 static cairo_surface_t* CreateSubImageForData(unsigned char* aData, 260 const IntSize& aSize, 261 const IntRect& aRect, int aStride, 262 SurfaceFormat aFormat) { 263 if (!aData || aStride < 0 || !IntRect(IntPoint(), aSize).Contains(aRect)) { 264 gfxWarning() << "DrawTargetCairo.CreateSubImageForData null aData"; 265 return nullptr; 266 } 267 unsigned char* data = aData + size_t(aRect.Y()) * size_t(aStride) + 268 size_t(aRect.X()) * size_t(BytesPerPixel(aFormat)); 269 270 cairo_surface_t* image = cairo_image_surface_create_for_data( 271 data, GfxFormatToCairoFormat(aFormat), aRect.Width(), aRect.Height(), 272 aStride); 273 // Set the subimage's device offset so that in remains in the same place 274 // relative to the parent 275 cairo_surface_set_device_offset(image, -aRect.X(), -aRect.Y()); 276 return image; 277 } 278 279 /** 280 * Returns a referenced cairo_surface_t representing the 281 * sub-image specified by aSubImage. 282 */ 283 static cairo_surface_t* ExtractSubImage(cairo_surface_t* aSurface, 284 const IntRect& aSubImage, 285 SurfaceFormat aFormat) { 286 // No need to worry about retaining a reference to the original 287 // surface since the only caller of this function guarantees 288 // that aSurface will stay alive as long as the result 289 290 cairo_surface_t* image = GetAsImageSurface(aSurface); 291 if (image) { 292 image = CreateSubImageForData( 293 cairo_image_surface_get_data(image), 294 IntSize(cairo_image_surface_get_width(image), 295 cairo_image_surface_get_height(image)), 296 aSubImage, cairo_image_surface_get_stride(image), aFormat); 297 return image; 298 } 299 300 cairo_surface_t* similar = cairo_surface_create_similar( 301 aSurface, cairo_surface_get_content(aSurface), aSubImage.Width(), 302 aSubImage.Height()); 303 304 cairo_t* ctx = cairo_create(similar); 305 cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE); 306 cairo_set_source_surface(ctx, aSurface, -aSubImage.X(), -aSubImage.Y()); 307 cairo_paint(ctx); 308 cairo_destroy(ctx); 309 310 cairo_surface_set_device_offset(similar, -aSubImage.X(), -aSubImage.Y()); 311 return similar; 312 } 313 314 /** 315 * Returns cairo surface for the given SourceSurface. 316 * If possible, it will use the cairo_surface associated with aSurface, 317 * otherwise, it will create a new cairo_surface. 318 * In either case, the caller must call cairo_surface_destroy on the 319 * result when it is done with it. 320 */ 321 static cairo_surface_t* GetCairoSurfaceForSourceSurface( 322 SourceSurface* aSurface, bool aExistingOnly = false, 323 const IntRect& aSubImage = IntRect()) { 324 if (!aSurface) { 325 return nullptr; 326 } 327 328 IntRect subimage = IntRect(IntPoint(), aSurface->GetSize()); 329 if (!aSubImage.IsEmpty()) { 330 MOZ_ASSERT(!aExistingOnly); 331 MOZ_ASSERT(subimage.Contains(aSubImage)); 332 subimage = aSubImage; 333 } 334 335 if (aSurface->GetType() == SurfaceType::CAIRO) { 336 cairo_surface_t* surf = 337 static_cast<SourceSurfaceCairo*>(aSurface)->GetSurface(); 338 if (aSubImage.IsEmpty()) { 339 cairo_surface_reference(surf); 340 } else { 341 surf = ExtractSubImage(surf, subimage, aSurface->GetFormat()); 342 } 343 return surf; 344 } 345 346 if (aSurface->GetType() == SurfaceType::CAIRO_IMAGE) { 347 cairo_surface_t* surf = 348 static_cast<const DataSourceSurfaceCairo*>(aSurface)->GetSurface(); 349 if (aSubImage.IsEmpty()) { 350 cairo_surface_reference(surf); 351 } else { 352 surf = ExtractSubImage(surf, subimage, aSurface->GetFormat()); 353 } 354 return surf; 355 } 356 357 if (aExistingOnly) { 358 return nullptr; 359 } 360 361 RefPtr<DataSourceSurface> data = aSurface->GetDataSurface(); 362 if (!data) { 363 return nullptr; 364 } 365 366 DataSourceSurface::MappedSurface map; 367 if (!data->Map(DataSourceSurface::READ, &map)) { 368 return nullptr; 369 } 370 371 cairo_surface_t* surf = CreateSubImageForData( 372 map.mData, data->GetSize(), subimage, map.mStride, data->GetFormat()); 373 374 // In certain scenarios, requesting larger than 8k image fails. Bug 803568 375 // covers the details of how to run into it, but the full detailed 376 // investigation hasn't been done to determine the underlying cause. We 377 // will just handle the failure to allocate the surface to avoid a crash. 378 if (!surf || cairo_surface_status(surf)) { 379 if (surf && (cairo_surface_status(surf) == CAIRO_STATUS_INVALID_STRIDE)) { 380 // If we failed because of an invalid stride then copy into 381 // a new surface with a stride that cairo chooses. No need to 382 // set user data since we're not dependent on the original 383 // data. 384 cairo_surface_t* result = CopyToImageSurface( 385 map.mData, data->GetSize(), subimage, map.mStride, data->GetFormat()); 386 data->Unmap(); 387 return result; 388 } 389 data->Unmap(); 390 return nullptr; 391 } 392 393 cairo_surface_set_user_data(surf, &surfaceDataKey, data.forget().take(), 394 ReleaseData); 395 return surf; 396 } 397 398 // An RAII class to temporarily clear any device offset set 399 // on a surface. Note that this does not take a reference to the 400 // surface. 401 class AutoClearDeviceOffset final { 402 public: 403 explicit AutoClearDeviceOffset(SourceSurface* aSurface) 404 : mSurface(nullptr), mX(0), mY(0) { 405 Init(aSurface); 406 } 407 408 explicit AutoClearDeviceOffset(const Pattern& aPattern) 409 : mSurface(nullptr), mX(0.0), mY(0.0) { 410 if (aPattern.GetType() == PatternType::SURFACE) { 411 const SurfacePattern& pattern = 412 static_cast<const SurfacePattern&>(aPattern); 413 Init(pattern.mSurface); 414 } 415 } 416 417 ~AutoClearDeviceOffset() { 418 if (mSurface) { 419 cairo_surface_set_device_offset(mSurface, mX, mY); 420 } 421 } 422 423 private: 424 void Init(SourceSurface* aSurface) { 425 cairo_surface_t* surface = GetCairoSurfaceForSourceSurface(aSurface, true); 426 if (surface) { 427 Init(surface); 428 cairo_surface_destroy(surface); 429 } 430 } 431 432 void Init(cairo_surface_t* aSurface) { 433 mSurface = aSurface; 434 cairo_surface_get_device_offset(mSurface, &mX, &mY); 435 cairo_surface_set_device_offset(mSurface, 0, 0); 436 } 437 438 cairo_surface_t* mSurface; 439 double mX; 440 double mY; 441 }; 442 443 static inline void CairoPatternAddGradientStop(cairo_pattern_t* aPattern, 444 const GradientStop& aStop, 445 Float aNudge = 0) { 446 cairo_pattern_add_color_stop_rgba(aPattern, aStop.offset + aNudge, 447 aStop.color.r, aStop.color.g, aStop.color.b, 448 aStop.color.a); 449 } 450 451 // Never returns nullptr. As such, you must always pass in Cairo-compatible 452 // patterns, most notably gradients with a GradientStopCairo. 453 // The pattern returned must have cairo_pattern_destroy() called on it by the 454 // caller. 455 // As the cairo_pattern_t returned may depend on the Pattern passed in, the 456 // lifetime of the cairo_pattern_t returned must not exceed the lifetime of the 457 // Pattern passed in. 458 static cairo_pattern_t* GfxPatternToCairoPattern(const Pattern& aPattern, 459 Float aAlpha, 460 const Matrix& aTransform) { 461 cairo_pattern_t* pat; 462 const Matrix* matrix = nullptr; 463 464 switch (aPattern.GetType()) { 465 case PatternType::COLOR: { 466 DeviceColor color = static_cast<const ColorPattern&>(aPattern).mColor; 467 pat = cairo_pattern_create_rgba(color.r, color.g, color.b, 468 color.a * aAlpha); 469 break; 470 } 471 472 case PatternType::SURFACE: { 473 const SurfacePattern& pattern = 474 static_cast<const SurfacePattern&>(aPattern); 475 cairo_surface_t* surf = GetCairoSurfaceForSourceSurface( 476 pattern.mSurface, false, pattern.mSamplingRect); 477 if (!surf) return nullptr; 478 479 pat = cairo_pattern_create_for_surface(surf); 480 481 matrix = &pattern.mMatrix; 482 483 cairo_pattern_set_filter( 484 pat, GfxSamplingFilterToCairoFilter(pattern.mSamplingFilter)); 485 cairo_pattern_set_extend(pat, 486 GfxExtendToCairoExtend(pattern.mExtendMode)); 487 488 cairo_surface_destroy(surf); 489 break; 490 } 491 case PatternType::LINEAR_GRADIENT: { 492 const LinearGradientPattern& pattern = 493 static_cast<const LinearGradientPattern&>(aPattern); 494 495 pat = cairo_pattern_create_linear(pattern.mBegin.x, pattern.mBegin.y, 496 pattern.mEnd.x, pattern.mEnd.y); 497 498 MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO); 499 GradientStopsCairo* cairoStops = 500 static_cast<GradientStopsCairo*>(pattern.mStops.get()); 501 cairo_pattern_set_extend( 502 pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode())); 503 504 matrix = &pattern.mMatrix; 505 506 const std::vector<GradientStop>& stops = cairoStops->GetStops(); 507 for (size_t i = 0; i < stops.size(); ++i) { 508 CairoPatternAddGradientStop(pat, stops[i]); 509 } 510 511 break; 512 } 513 case PatternType::RADIAL_GRADIENT: { 514 const RadialGradientPattern& pattern = 515 static_cast<const RadialGradientPattern&>(aPattern); 516 517 pat = cairo_pattern_create_radial(pattern.mCenter1.x, pattern.mCenter1.y, 518 pattern.mRadius1, pattern.mCenter2.x, 519 pattern.mCenter2.y, pattern.mRadius2); 520 521 MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO); 522 GradientStopsCairo* cairoStops = 523 static_cast<GradientStopsCairo*>(pattern.mStops.get()); 524 cairo_pattern_set_extend( 525 pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode())); 526 527 matrix = &pattern.mMatrix; 528 529 const std::vector<GradientStop>& stops = cairoStops->GetStops(); 530 for (size_t i = 0; i < stops.size(); ++i) { 531 CairoPatternAddGradientStop(pat, stops[i]); 532 } 533 534 break; 535 } 536 case PatternType::CONIC_GRADIENT: { 537 // XXX(ntim): Bug 1617039 - Implement conic-gradient for Cairo 538 pat = cairo_pattern_create_rgba(0.0, 0.0, 0.0, 0.0); 539 540 break; 541 } 542 default: { 543 // We should support all pattern types! 544 MOZ_ASSERT(false); 545 } 546 } 547 548 // The pattern matrix is a matrix that transforms the pattern into user 549 // space. Cairo takes a matrix that converts from user space to pattern 550 // space. Cairo therefore needs the inverse. 551 if (matrix) { 552 cairo_matrix_t mat; 553 GfxMatrixToCairoMatrix(*matrix, mat); 554 cairo_matrix_invert(&mat); 555 cairo_pattern_set_matrix(pat, &mat); 556 } 557 558 return pat; 559 } 560 561 static bool NeedIntermediateSurface(const Pattern& aPattern, 562 const DrawOptions& aOptions) { 563 // We pre-multiply colours' alpha by the global alpha, so we don't need to 564 // use an intermediate surface for them. 565 if (aPattern.GetType() == PatternType::COLOR) return false; 566 567 if (aOptions.mAlpha == 1.0) return false; 568 569 return true; 570 } 571 572 DrawTargetCairo::DrawTargetCairo() 573 : mContext(nullptr), 574 mSurface(nullptr), 575 mTransformSingular(false), 576 mLockedBits(nullptr), 577 mFontOptions(nullptr) {} 578 579 DrawTargetCairo::~DrawTargetCairo() { 580 cairo_destroy(mContext); 581 if (mSurface) { 582 cairo_surface_destroy(mSurface); 583 mSurface = nullptr; 584 } 585 if (mFontOptions) { 586 cairo_font_options_destroy(mFontOptions); 587 mFontOptions = nullptr; 588 } 589 MOZ_ASSERT(!mLockedBits); 590 } 591 592 bool DrawTargetCairo::IsValid() const { 593 return mSurface && !cairo_surface_status(mSurface) && mContext && 594 !cairo_surface_status(cairo_get_group_target(mContext)); 595 } 596 597 DrawTargetType DrawTargetCairo::GetType() const { 598 if (mContext) { 599 cairo_surface_type_t type = cairo_surface_get_type(mSurface); 600 if (type == CAIRO_SURFACE_TYPE_TEE) { 601 type = cairo_surface_get_type(cairo_tee_surface_index(mSurface, 0)); 602 MOZ_ASSERT(type != CAIRO_SURFACE_TYPE_TEE, "C'mon!"); 603 MOZ_ASSERT( 604 type == cairo_surface_get_type(cairo_tee_surface_index(mSurface, 1)), 605 "What should we do here?"); 606 } 607 switch (type) { 608 case CAIRO_SURFACE_TYPE_PDF: 609 case CAIRO_SURFACE_TYPE_PS: 610 case CAIRO_SURFACE_TYPE_SVG: 611 case CAIRO_SURFACE_TYPE_WIN32_PRINTING: 612 case CAIRO_SURFACE_TYPE_XML: 613 return DrawTargetType::VECTOR; 614 615 case CAIRO_SURFACE_TYPE_VG: 616 case CAIRO_SURFACE_TYPE_GL: 617 case CAIRO_SURFACE_TYPE_GLITZ: 618 case CAIRO_SURFACE_TYPE_QUARTZ: 619 case CAIRO_SURFACE_TYPE_DIRECTFB: 620 return DrawTargetType::HARDWARE_RASTER; 621 622 case CAIRO_SURFACE_TYPE_SKIA: 623 case CAIRO_SURFACE_TYPE_QT: 624 MOZ_FALLTHROUGH_ASSERT( 625 "Can't determine actual DrawTargetType for DrawTargetCairo - " 626 "assuming SOFTWARE_RASTER"); 627 case CAIRO_SURFACE_TYPE_IMAGE: 628 case CAIRO_SURFACE_TYPE_XLIB: 629 case CAIRO_SURFACE_TYPE_XCB: 630 case CAIRO_SURFACE_TYPE_WIN32: 631 case CAIRO_SURFACE_TYPE_BEOS: 632 case CAIRO_SURFACE_TYPE_OS2: 633 case CAIRO_SURFACE_TYPE_QUARTZ_IMAGE: 634 case CAIRO_SURFACE_TYPE_SCRIPT: 635 case CAIRO_SURFACE_TYPE_RECORDING: 636 case CAIRO_SURFACE_TYPE_DRM: 637 case CAIRO_SURFACE_TYPE_SUBSURFACE: 638 case CAIRO_SURFACE_TYPE_TEE: // included to silence warning about 639 // unhandled enum value 640 return DrawTargetType::SOFTWARE_RASTER; 641 default: 642 MOZ_CRASH("GFX: Unsupported cairo surface type"); 643 } 644 } 645 MOZ_ASSERT(false, "Could not determine DrawTargetType for DrawTargetCairo"); 646 return DrawTargetType::SOFTWARE_RASTER; 647 } 648 649 IntSize DrawTargetCairo::GetSize() const { return mSize; } 650 651 SurfaceFormat GfxFormatForCairoSurface(cairo_surface_t* surface) { 652 cairo_surface_type_t type = cairo_surface_get_type(surface); 653 if (type == CAIRO_SURFACE_TYPE_IMAGE) { 654 return CairoFormatToGfxFormat(cairo_image_surface_get_format(surface)); 655 } 656 #ifdef CAIRO_HAS_XLIB_SURFACE 657 // xlib is currently the only Cairo backend that creates 16bpp surfaces 658 if (type == CAIRO_SURFACE_TYPE_XLIB && 659 cairo_xlib_surface_get_depth(surface) == 16) { 660 return SurfaceFormat::R5G6B5_UINT16; 661 } 662 #endif 663 return CairoContentToGfxFormat(cairo_surface_get_content(surface)); 664 } 665 666 // We need to \-escape any single-quotes in the destination and URI strings, 667 // in order to pass them via the attributes arg to cairo_tag_begin. 668 // 669 // We also need to escape any backslashes (bug 1748077), as per doc at 670 // https://www.cairographics.org/manual/cairo-Tags-and-Links.html#cairo-tag-begin 671 // 672 // (Encoding of non-ASCII chars etc gets handled later by the PDF backend.) 673 static void EscapeForCairo(nsACString& aStr) { 674 for (size_t i = aStr.Length(); i > 0;) { 675 --i; 676 if (aStr[i] == '\'') { 677 aStr.ReplaceLiteral(i, 1, "\\'"); 678 } else if (aStr[i] == '\\') { 679 aStr.ReplaceLiteral(i, 1, "\\\\"); 680 } 681 } 682 } 683 684 void DrawTargetCairo::Link(const char* aDest, const char* aURI, 685 const Rect& aRect) { 686 if ((!aURI || !*aURI) && (!aDest || !*aDest)) { 687 // No destination? Just bail out. 688 return; 689 } 690 691 if (!IsValid()) { 692 gfxCriticalNote << "Link with bad surface " 693 << cairo_surface_status(cairo_get_group_target(mContext)); 694 return; 695 } 696 697 double x = aRect.x, y = aRect.y, w = aRect.width, h = aRect.height; 698 cairo_user_to_device(mContext, &x, &y); 699 cairo_user_to_device_distance(mContext, &w, &h); 700 nsPrintfCString attributes("rect=[%f %f %f %f]", x, y, w, h); 701 702 if (aDest && *aDest) { 703 nsAutoCString dest(aDest); 704 EscapeForCairo(dest); 705 attributes.AppendPrintf(" dest='%s'", dest.get()); 706 } 707 if (aURI && *aURI) { 708 nsAutoCString uri(aURI); 709 EscapeForCairo(uri); 710 attributes.AppendPrintf(" uri='%s'", uri.get()); 711 } 712 713 // We generate a begin/end pair with no content in between, because we are 714 // using the rect attribute of the begin tag to specify the link region 715 // rather than depending on cairo to accumulate the painted area. 716 cairo_tag_begin(mContext, CAIRO_TAG_LINK, attributes.get()); 717 cairo_tag_end(mContext, CAIRO_TAG_LINK); 718 } 719 720 void DrawTargetCairo::Destination(const char* aDestination, 721 const Point& aPoint) { 722 if (!aDestination || !*aDestination) { 723 // No destination? Just bail out. 724 return; 725 } 726 727 if (!IsValid()) { 728 gfxCriticalNote << "Destination with bad surface " 729 << cairo_surface_status(cairo_get_group_target(mContext)); 730 return; 731 } 732 733 nsAutoCString dest(aDestination); 734 EscapeForCairo(dest); 735 736 double x = aPoint.x, y = aPoint.y; 737 cairo_user_to_device(mContext, &x, &y); 738 739 nsPrintfCString attributes("name='%s' x=%f y=%f internal", dest.get(), x, y); 740 cairo_tag_begin(mContext, CAIRO_TAG_DEST, attributes.get()); 741 cairo_tag_end(mContext, CAIRO_TAG_DEST); 742 } 743 744 already_AddRefed<SourceSurface> DrawTargetCairo::Snapshot() { 745 if (!IsValid()) { 746 gfxCriticalNote << "DrawTargetCairo::Snapshot with bad surface " 747 << hexa(mSurface) << ", context " << hexa(mContext) 748 << ", status " 749 << (mSurface ? cairo_surface_status(mSurface) : -1); 750 return nullptr; 751 } 752 753 if (mSnapshot) { 754 RefPtr<SourceSurface> snapshot(mSnapshot); 755 return snapshot.forget(); 756 } 757 758 IntSize size = GetSize(); 759 760 mSnapshot = new SourceSurfaceCairo(mSurface, size, 761 GfxFormatForCairoSurface(mSurface), this); 762 RefPtr<SourceSurface> snapshot(mSnapshot); 763 return snapshot.forget(); 764 } 765 766 bool DrawTargetCairo::LockBits(uint8_t** aData, IntSize* aSize, 767 int32_t* aStride, SurfaceFormat* aFormat, 768 IntPoint* aOrigin) { 769 if (!IsValid()) { 770 gfxCriticalNote << "LockBits with bad surface " 771 << cairo_surface_status(cairo_get_group_target(mContext)); 772 return false; 773 } 774 775 cairo_surface_t* target = cairo_get_group_target(mContext); 776 cairo_surface_t* surf = target; 777 #ifdef CAIRO_HAS_WIN32_SURFACE 778 if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_WIN32) { 779 cairo_surface_t* imgsurf = cairo_win32_surface_get_image(surf); 780 if (imgsurf) { 781 surf = imgsurf; 782 } 783 } 784 #endif 785 if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_IMAGE && 786 cairo_surface_status(surf) == CAIRO_STATUS_SUCCESS) { 787 PointDouble offset; 788 cairo_surface_get_device_offset(target, &offset.x.value, &offset.y.value); 789 // verify the device offset can be converted to integers suitable for a 790 // bounds rect 791 IntPoint origin(int32_t(-offset.x), int32_t(-offset.y)); 792 if (-PointDouble(origin) != offset || (!aOrigin && origin != IntPoint())) { 793 return false; 794 } 795 796 WillChange(); 797 Flush(); 798 799 mLockedBits = cairo_image_surface_get_data(surf); 800 *aData = mLockedBits; 801 *aSize = IntSize(cairo_image_surface_get_width(surf), 802 cairo_image_surface_get_height(surf)); 803 *aStride = cairo_image_surface_get_stride(surf); 804 *aFormat = CairoFormatToGfxFormat(cairo_image_surface_get_format(surf)); 805 if (aOrigin) { 806 *aOrigin = origin; 807 } 808 return true; 809 } 810 811 return false; 812 } 813 814 void DrawTargetCairo::ReleaseBits(uint8_t* aData) { 815 MOZ_ASSERT(mLockedBits == aData); 816 mLockedBits = nullptr; 817 cairo_surface_t* surf = cairo_get_group_target(mContext); 818 #ifdef CAIRO_HAS_WIN32_SURFACE 819 if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_WIN32) { 820 cairo_surface_t* imgsurf = cairo_win32_surface_get_image(surf); 821 if (imgsurf) { 822 cairo_surface_mark_dirty(imgsurf); 823 } 824 } 825 #endif 826 cairo_surface_mark_dirty(surf); 827 } 828 829 void DrawTargetCairo::Flush() { 830 if (!IsValid()) { 831 gfxCriticalNote << "Flush with bad surface " 832 << cairo_surface_status(cairo_get_group_target(mContext)); 833 return; 834 } 835 836 cairo_surface_t* surf = cairo_get_group_target(mContext); 837 cairo_surface_flush(surf); 838 } 839 840 void DrawTargetCairo::PrepareForDrawing(cairo_t* aContext, 841 const Path* aPath /* = nullptr */) { 842 WillChange(aPath); 843 } 844 845 cairo_surface_t* DrawTargetCairo::GetDummySurface() { 846 if (mDummySurface) { 847 return mDummySurface; 848 } 849 850 mDummySurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); 851 852 return mDummySurface; 853 } 854 855 static void PaintWithAlpha(cairo_t* aContext, const DrawOptions& aOptions) { 856 if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE) { 857 // Cairo treats the source operator like a lerp when alpha is < 1. 858 // Approximate the desired operator by: out = 0; out += src*alpha; 859 if (aOptions.mAlpha == 1) { 860 cairo_set_operator(aContext, CAIRO_OPERATOR_SOURCE); 861 cairo_paint(aContext); 862 } else { 863 cairo_set_operator(aContext, CAIRO_OPERATOR_CLEAR); 864 cairo_paint(aContext); 865 cairo_set_operator(aContext, CAIRO_OPERATOR_ADD); 866 cairo_paint_with_alpha(aContext, aOptions.mAlpha); 867 } 868 } else { 869 cairo_set_operator(aContext, GfxOpToCairoOp(aOptions.mCompositionOp)); 870 cairo_paint_with_alpha(aContext, aOptions.mAlpha); 871 } 872 } 873 874 void DrawTargetCairo::DrawSurface(SourceSurface* aSurface, const Rect& aDest, 875 const Rect& aSource, 876 const DrawSurfaceOptions& aSurfOptions, 877 const DrawOptions& aOptions) { 878 if (mTransformSingular || aDest.IsEmpty()) { 879 return; 880 } 881 882 if (!IsValid() || !aSurface) { 883 gfxCriticalNote << "DrawSurface with bad surface " 884 << cairo_surface_status(cairo_get_group_target(mContext)); 885 return; 886 } 887 888 AutoPrepareForDrawing prep(this, mContext); 889 AutoClearDeviceOffset clear(aSurface); 890 891 float sx = aSource.Width() / aDest.Width(); 892 float sy = aSource.Height() / aDest.Height(); 893 894 cairo_matrix_t src_mat; 895 cairo_matrix_init_translate(&src_mat, aSource.X() - aSurface->GetRect().x, 896 aSource.Y() - aSurface->GetRect().y); 897 cairo_matrix_scale(&src_mat, sx, sy); 898 899 cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aSurface); 900 if (!surf) { 901 gfxWarning() 902 << "Failed to create cairo surface for DrawTargetCairo::DrawSurface"; 903 return; 904 } 905 cairo_pattern_t* pat = cairo_pattern_create_for_surface(surf); 906 cairo_surface_destroy(surf); 907 908 cairo_pattern_set_matrix(pat, &src_mat); 909 cairo_pattern_set_filter( 910 pat, GfxSamplingFilterToCairoFilter(aSurfOptions.mSamplingFilter)); 911 // For PDF output, we avoid using EXTEND_PAD here because floating-point 912 // error accumulation may lead cairo_pdf_surface to conclude that padding 913 // is needed due to an apparent one- or two-pixel mismatch between source 914 // pattern and destination rect sizes when we're rendering a pdf.js page, 915 // and this forces undesirable fallback to the rasterization codepath 916 // instead of simply replaying the recording. 917 // (See bug 1777209.) 918 cairo_pattern_set_extend( 919 pat, cairo_surface_get_type(mSurface) == CAIRO_SURFACE_TYPE_PDF 920 ? CAIRO_EXTEND_NONE 921 : CAIRO_EXTEND_PAD); 922 923 cairo_set_antialias(mContext, 924 GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode)); 925 926 // If the destination rect covers the entire clipped area, then unbounded and 927 // bounded operations are identical, and we don't need to push a group. 928 bool needsGroup = !IsOperatorBoundByMask(aOptions.mCompositionOp) && 929 !aDest.Contains(GetUserSpaceClip()); 930 931 cairo_translate(mContext, aDest.X(), aDest.Y()); 932 933 if (needsGroup) { 934 cairo_push_group(mContext); 935 cairo_new_path(mContext); 936 cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height()); 937 cairo_set_source(mContext, pat); 938 cairo_fill(mContext); 939 cairo_pop_group_to_source(mContext); 940 } else { 941 cairo_new_path(mContext); 942 cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height()); 943 cairo_clip(mContext); 944 cairo_set_source(mContext, pat); 945 } 946 947 PaintWithAlpha(mContext, aOptions); 948 949 cairo_pattern_destroy(pat); 950 } 951 952 void DrawTargetCairo::DrawFilter(FilterNode* aNode, const Rect& aSourceRect, 953 const Point& aDestPoint, 954 const DrawOptions& aOptions) { 955 if (!IsValid() || !aNode) { 956 gfxCriticalNote << "DrawFilter with bad surface " 957 << cairo_surface_status(cairo_get_group_target(mContext)); 958 return; 959 } 960 FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode); 961 filter->Draw(this, aSourceRect, aDestPoint, aOptions); 962 } 963 964 void DrawTargetCairo::DrawSurfaceWithShadow(SourceSurface* aSurface, 965 const Point& aDest, 966 const ShadowOptions& aShadow, 967 CompositionOp aOperator) { 968 if (!IsValid() || !aSurface) { 969 gfxCriticalNote << "DrawSurfaceWithShadow with bad surface " 970 << cairo_surface_status(cairo_get_group_target(mContext)); 971 return; 972 } 973 974 if (aSurface->GetType() != SurfaceType::CAIRO) { 975 return; 976 } 977 978 AutoClearDeviceOffset clear(aSurface); 979 980 IntSize size = aSurface->GetSize(); 981 Float width = Float(size.width); 982 Float height = Float(size.height); 983 984 SourceSurfaceCairo* source = static_cast<SourceSurfaceCairo*>(aSurface); 985 cairo_surface_t* sourcesurf = source->GetSurface(); 986 cairo_surface_t* blursurf; 987 cairo_surface_t* surf; 988 989 // We only use the A8 surface for blurred shadows. Unblurred shadows can just 990 // use the RGBA surface directly. 991 if (cairo_surface_get_type(sourcesurf) == CAIRO_SURFACE_TYPE_TEE) { 992 blursurf = cairo_tee_surface_index(sourcesurf, 0); 993 surf = cairo_tee_surface_index(sourcesurf, 1); 994 } else { 995 blursurf = sourcesurf; 996 surf = sourcesurf; 997 } 998 999 if (aShadow.mSigma != 0.0f) { 1000 MOZ_ASSERT(cairo_surface_get_type(blursurf) == CAIRO_SURFACE_TYPE_IMAGE); 1001 GaussianBlur blur(Point(aShadow.mSigma, aShadow.mSigma)); 1002 blur.Blur(cairo_image_surface_get_data(blursurf), 1003 cairo_image_surface_get_stride(blursurf), size, 1004 aSurface->GetFormat()); 1005 } 1006 1007 WillChange(); 1008 ClearSurfaceForUnboundedSource(aOperator); 1009 1010 cairo_save(mContext); 1011 cairo_set_operator(mContext, GfxOpToCairoOp(aOperator)); 1012 cairo_identity_matrix(mContext); 1013 cairo_translate(mContext, aDest.x, aDest.y); 1014 1015 bool needsGroup = !IsOperatorBoundByMask(aOperator); 1016 if (needsGroup) { 1017 cairo_push_group(mContext); 1018 } 1019 1020 cairo_set_source_rgba(mContext, aShadow.mColor.r, aShadow.mColor.g, 1021 aShadow.mColor.b, aShadow.mColor.a); 1022 cairo_mask_surface(mContext, blursurf, aShadow.mOffset.x, aShadow.mOffset.y); 1023 1024 if (blursurf != surf || aSurface->GetFormat() != SurfaceFormat::A8) { 1025 // Now that the shadow has been drawn, we can draw the surface on top. 1026 cairo_set_source_surface(mContext, surf, 0, 0); 1027 cairo_new_path(mContext); 1028 cairo_rectangle(mContext, 0, 0, width, height); 1029 cairo_fill(mContext); 1030 } 1031 1032 if (needsGroup) { 1033 cairo_pop_group_to_source(mContext); 1034 cairo_paint(mContext); 1035 } 1036 1037 cairo_restore(mContext); 1038 } 1039 1040 void DrawTargetCairo::DrawPattern(const Pattern& aPattern, 1041 const StrokeOptions& aStrokeOptions, 1042 const DrawOptions& aOptions, 1043 DrawPatternType aDrawType, 1044 bool aPathBoundsClip) { 1045 if (!IsValid()) { 1046 gfxCriticalNote << "DrawPattern with bad surface " 1047 << cairo_surface_status(cairo_get_group_target(mContext)); 1048 return; 1049 } 1050 1051 if (!PatternIsCompatible(aPattern)) { 1052 return; 1053 } 1054 1055 AutoClearDeviceOffset clear(aPattern); 1056 1057 cairo_pattern_t* pat = 1058 GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform()); 1059 if (!pat) { 1060 return; 1061 } 1062 if (cairo_pattern_status(pat)) { 1063 cairo_pattern_destroy(pat); 1064 gfxWarning() << "Invalid pattern"; 1065 return; 1066 } 1067 1068 cairo_set_source(mContext, pat); 1069 1070 cairo_set_antialias(mContext, 1071 GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode)); 1072 1073 if (NeedIntermediateSurface(aPattern, aOptions) || 1074 (!IsOperatorBoundByMask(aOptions.mCompositionOp) && !aPathBoundsClip)) { 1075 cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA); 1076 1077 // Don't want operators to be applied twice 1078 cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); 1079 1080 if (aDrawType == DRAW_STROKE) { 1081 SetCairoStrokeOptions(mContext, aStrokeOptions); 1082 cairo_stroke_preserve(mContext); 1083 } else { 1084 cairo_fill_preserve(mContext); 1085 } 1086 1087 cairo_pop_group_to_source(mContext); 1088 1089 // Now draw the content using the desired operator 1090 PaintWithAlpha(mContext, aOptions); 1091 } else { 1092 cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); 1093 1094 if (aDrawType == DRAW_STROKE) { 1095 SetCairoStrokeOptions(mContext, aStrokeOptions); 1096 cairo_stroke_preserve(mContext); 1097 } else { 1098 cairo_fill_preserve(mContext); 1099 } 1100 } 1101 1102 cairo_pattern_destroy(pat); 1103 } 1104 1105 void DrawTargetCairo::FillRect(const Rect& aRect, const Pattern& aPattern, 1106 const DrawOptions& aOptions) { 1107 if (mTransformSingular) { 1108 return; 1109 } 1110 1111 if (!IsValid()) { 1112 gfxCriticalNote << "FillRect with bad surface " 1113 << cairo_surface_status(cairo_get_group_target(mContext)); 1114 return; 1115 } 1116 1117 AutoPrepareForDrawing prep(this, mContext); 1118 1119 bool restoreTransform = false; 1120 Matrix mat; 1121 Rect r = aRect; 1122 1123 /* Clamp coordinates to work around a design bug in cairo */ 1124 if (r.Width() > CAIRO_COORD_MAX || r.Height() > CAIRO_COORD_MAX || 1125 r.X() < -CAIRO_COORD_MAX || r.X() > CAIRO_COORD_MAX || 1126 r.Y() < -CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX) { 1127 if (!mat.IsRectilinear()) { 1128 gfxWarning() << "DrawTargetCairo::FillRect() misdrawing huge Rect " 1129 "with non-rectilinear transform"; 1130 } 1131 1132 mat = GetTransform(); 1133 r = mat.TransformBounds(r); 1134 1135 if (!ConditionRect(r)) { 1136 gfxWarning() << "Ignoring DrawTargetCairo::FillRect() call with " 1137 "out-of-bounds Rect"; 1138 return; 1139 } 1140 1141 restoreTransform = true; 1142 SetTransform(Matrix()); 1143 } 1144 1145 cairo_new_path(mContext); 1146 cairo_rectangle(mContext, r.X(), r.Y(), r.Width(), r.Height()); 1147 1148 bool pathBoundsClip = false; 1149 1150 if (r.Contains(GetUserSpaceClip())) { 1151 pathBoundsClip = true; 1152 } 1153 1154 DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL, pathBoundsClip); 1155 1156 if (restoreTransform) { 1157 SetTransform(mat); 1158 } 1159 } 1160 1161 void DrawTargetCairo::CopySurfaceInternal(cairo_surface_t* aSurface, 1162 const IntRect& aSource, 1163 const IntPoint& aDest) { 1164 if (!IsValid()) { 1165 gfxCriticalNote << "CopySurfaceInternal with bad surface " 1166 << cairo_surface_status(cairo_get_group_target(mContext)); 1167 return; 1168 } 1169 1170 if (cairo_surface_status(aSurface)) { 1171 gfxWarning() << "Invalid surface" << cairo_surface_status(aSurface); 1172 return; 1173 } 1174 1175 cairo_identity_matrix(mContext); 1176 1177 cairo_set_source_surface(mContext, aSurface, aDest.x - aSource.X(), 1178 aDest.y - aSource.Y()); 1179 cairo_set_operator(mContext, CAIRO_OPERATOR_SOURCE); 1180 cairo_set_antialias(mContext, CAIRO_ANTIALIAS_NONE); 1181 1182 cairo_reset_clip(mContext); 1183 cairo_new_path(mContext); 1184 cairo_rectangle(mContext, aDest.x, aDest.y, aSource.Width(), 1185 aSource.Height()); 1186 cairo_fill(mContext); 1187 } 1188 1189 void DrawTargetCairo::CopySurface(SourceSurface* aSurface, 1190 const IntRect& aSource, 1191 const IntPoint& aDest) { 1192 if (mTransformSingular) { 1193 return; 1194 } 1195 1196 if (!IsValid()) { 1197 gfxCriticalNote << "CopySurface with bad surface " 1198 << cairo_surface_status(cairo_get_group_target(mContext)); 1199 return; 1200 } 1201 1202 AutoPrepareForDrawing prep(this, mContext); 1203 AutoClearDeviceOffset clear(aSurface); 1204 1205 if (!aSurface) { 1206 gfxWarning() << "Unsupported surface type specified"; 1207 return; 1208 } 1209 1210 cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aSurface); 1211 if (!surf) { 1212 gfxWarning() << "Unsupported surface type specified"; 1213 return; 1214 } 1215 1216 CopySurfaceInternal(surf, aSource - aSurface->GetRect().TopLeft(), aDest); 1217 cairo_surface_destroy(surf); 1218 } 1219 1220 void DrawTargetCairo::CopyRect(const IntRect& aSource, const IntPoint& aDest) { 1221 if (mTransformSingular) { 1222 return; 1223 } 1224 1225 if (!IsValid()) { 1226 gfxCriticalNote << "CopyRect with bad surface " 1227 << cairo_surface_status(cairo_get_group_target(mContext)); 1228 return; 1229 } 1230 1231 AutoPrepareForDrawing prep(this, mContext); 1232 1233 IntRect source = aSource; 1234 cairo_surface_t* surf = mSurface; 1235 1236 if (!SupportsSelfCopy(mSurface) && aSource.ContainsY(aDest.y)) { 1237 cairo_surface_t* similar = cairo_surface_create_similar( 1238 mSurface, GfxFormatToCairoContent(GetFormat()), aSource.Width(), 1239 aSource.Height()); 1240 cairo_t* ctx = cairo_create(similar); 1241 cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE); 1242 cairo_set_source_surface(ctx, surf, -aSource.X(), -aSource.Y()); 1243 cairo_paint(ctx); 1244 cairo_destroy(ctx); 1245 1246 source.MoveTo(0, 0); 1247 surf = similar; 1248 } 1249 1250 CopySurfaceInternal(surf, source, aDest); 1251 1252 if (surf != mSurface) { 1253 cairo_surface_destroy(surf); 1254 } 1255 } 1256 1257 void DrawTargetCairo::ClearRect(const Rect& aRect) { 1258 if (mTransformSingular) { 1259 return; 1260 } 1261 1262 if (!IsValid()) { 1263 gfxCriticalNote << "ClearRect with bad surface " 1264 << cairo_surface_status(cairo_get_group_target(mContext)); 1265 return; 1266 } 1267 1268 AutoPrepareForDrawing prep(this, mContext); 1269 1270 if (!mContext || aRect.Width() < 0 || aRect.Height() < 0 || 1271 !std::isfinite(aRect.X()) || !std::isfinite(aRect.Width()) || 1272 !std::isfinite(aRect.Y()) || !std::isfinite(aRect.Height())) { 1273 gfxCriticalNote << "ClearRect with invalid argument " << gfx::hexa(mContext) 1274 << " with " << aRect.Width() << "x" << aRect.Height() 1275 << " [" << aRect.X() << ", " << aRect.Y() << "]"; 1276 } 1277 1278 cairo_set_antialias(mContext, CAIRO_ANTIALIAS_NONE); 1279 cairo_new_path(mContext); 1280 cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR); 1281 cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), 1282 aRect.Height()); 1283 cairo_fill(mContext); 1284 } 1285 1286 void DrawTargetCairo::StrokeRect( 1287 const Rect& aRect, const Pattern& aPattern, 1288 const StrokeOptions& aStrokeOptions /* = StrokeOptions() */, 1289 const DrawOptions& aOptions /* = DrawOptions() */) { 1290 if (mTransformSingular) { 1291 return; 1292 } 1293 1294 if (!IsValid()) { 1295 gfxCriticalNote << "StrokeRect with bad surface " 1296 << cairo_surface_status(cairo_get_group_target(mContext)); 1297 return; 1298 } 1299 1300 AutoPrepareForDrawing prep(this, mContext); 1301 1302 cairo_new_path(mContext); 1303 cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), 1304 aRect.Height()); 1305 1306 DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE); 1307 } 1308 1309 void DrawTargetCairo::StrokeLine( 1310 const Point& aStart, const Point& aEnd, const Pattern& aPattern, 1311 const StrokeOptions& aStrokeOptions /* = StrokeOptions() */, 1312 const DrawOptions& aOptions /* = DrawOptions() */) { 1313 if (mTransformSingular) { 1314 return; 1315 } 1316 1317 if (!IsValid()) { 1318 gfxCriticalNote << "StrokeLine with bad surface " 1319 << cairo_surface_status(cairo_get_group_target(mContext)); 1320 return; 1321 } 1322 1323 AutoPrepareForDrawing prep(this, mContext); 1324 1325 cairo_new_path(mContext); 1326 cairo_move_to(mContext, aStart.x, aStart.y); 1327 cairo_line_to(mContext, aEnd.x, aEnd.y); 1328 1329 DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE); 1330 } 1331 1332 void DrawTargetCairo::Stroke( 1333 const Path* aPath, const Pattern& aPattern, 1334 const StrokeOptions& aStrokeOptions /* = StrokeOptions() */, 1335 const DrawOptions& aOptions /* = DrawOptions() */) { 1336 if (mTransformSingular) { 1337 return; 1338 } 1339 1340 if (!IsValid()) { 1341 gfxCriticalNote << "Stroke with bad surface " 1342 << cairo_surface_status(cairo_get_group_target(mContext)); 1343 return; 1344 } 1345 1346 AutoPrepareForDrawing prep(this, mContext, aPath); 1347 1348 if (aPath->GetBackendType() != BackendType::CAIRO) return; 1349 1350 PathCairo* path = 1351 const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath)); 1352 path->SetPathOnContext(mContext); 1353 1354 DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE); 1355 } 1356 1357 void DrawTargetCairo::Fill(const Path* aPath, const Pattern& aPattern, 1358 const DrawOptions& aOptions /* = DrawOptions() */) { 1359 if (mTransformSingular) { 1360 return; 1361 } 1362 1363 if (!IsValid()) { 1364 gfxCriticalNote << "Fill with bad surface " 1365 << cairo_surface_status(cairo_get_group_target(mContext)); 1366 return; 1367 } 1368 1369 AutoPrepareForDrawing prep(this, mContext, aPath); 1370 1371 if (aPath->GetBackendType() != BackendType::CAIRO) return; 1372 1373 PathCairo* path = 1374 const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath)); 1375 path->SetPathOnContext(mContext); 1376 1377 DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL); 1378 } 1379 1380 bool DrawTargetCairo::IsCurrentGroupOpaque() { 1381 cairo_surface_t* surf = cairo_get_group_target(mContext); 1382 1383 if (!surf) { 1384 return false; 1385 } 1386 1387 return cairo_surface_get_content(surf) == CAIRO_CONTENT_COLOR; 1388 } 1389 1390 void DrawTargetCairo::SetFontOptions(cairo_antialias_t aAAMode) { 1391 // This will attempt to detect if the currently set scaled font on the 1392 // context has enabled subpixel AA. If it is not permitted, then it will 1393 // downgrade to grayscale AA. 1394 // This only currently works effectively for the cairo-ft backend relative 1395 // to system defaults, as only cairo-ft reflect system defaults in the scaled 1396 // font state. However, this will work for cairo-ft on both tree Cairo and 1397 // system Cairo. 1398 // Other backends leave the CAIRO_ANTIALIAS_DEFAULT setting untouched while 1399 // potentially interpreting it as subpixel or even other types of AA that 1400 // can't be safely equivocated with grayscale AA. For this reason we don't 1401 // try to also detect and modify the default AA setting, only explicit 1402 // subpixel AA. These other backends must instead rely on tree Cairo's 1403 // cairo_surface_set_subpixel_antialiasing extension. 1404 1405 // If allowing subpixel AA, then leave Cairo's default AA state. 1406 if (mPermitSubpixelAA && aAAMode == CAIRO_ANTIALIAS_DEFAULT) { 1407 return; 1408 } 1409 1410 if (!IsValid()) { 1411 gfxCriticalNote << "SetFontOptions with bad surface " 1412 << cairo_surface_status(cairo_get_group_target(mContext)); 1413 return; 1414 } 1415 1416 if (!mFontOptions) { 1417 mFontOptions = cairo_font_options_create(); 1418 if (!mFontOptions) { 1419 gfxWarning() << "Failed allocating Cairo font options"; 1420 return; 1421 } 1422 } 1423 1424 cairo_get_font_options(mContext, mFontOptions); 1425 cairo_antialias_t oldAA = cairo_font_options_get_antialias(mFontOptions); 1426 cairo_antialias_t newAA = 1427 aAAMode == CAIRO_ANTIALIAS_DEFAULT ? oldAA : aAAMode; 1428 // Nothing to change if switching to default AA. 1429 if (newAA == CAIRO_ANTIALIAS_DEFAULT) { 1430 return; 1431 } 1432 // If the current font requests subpixel AA, force it to gray since we don't 1433 // allow subpixel AA. 1434 if (!mPermitSubpixelAA && newAA == CAIRO_ANTIALIAS_SUBPIXEL) { 1435 newAA = CAIRO_ANTIALIAS_GRAY; 1436 } 1437 // Only override old AA with lower levels of AA. 1438 if (oldAA == CAIRO_ANTIALIAS_DEFAULT || (int)newAA < (int)oldAA) { 1439 cairo_font_options_set_antialias(mFontOptions, newAA); 1440 cairo_set_font_options(mContext, mFontOptions); 1441 } 1442 } 1443 1444 void DrawTargetCairo::SetPermitSubpixelAA(bool aPermitSubpixelAA) { 1445 DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA); 1446 cairo_surface_set_subpixel_antialiasing( 1447 cairo_get_group_target(mContext), 1448 aPermitSubpixelAA ? CAIRO_SUBPIXEL_ANTIALIASING_ENABLED 1449 : CAIRO_SUBPIXEL_ANTIALIASING_DISABLED); 1450 } 1451 1452 static bool SupportsVariationSettings(cairo_surface_t* surface) { 1453 switch (cairo_surface_get_type(surface)) { 1454 case CAIRO_SURFACE_TYPE_PDF: 1455 case CAIRO_SURFACE_TYPE_PS: 1456 return false; 1457 default: 1458 return true; 1459 } 1460 } 1461 1462 void DrawTargetCairo::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, 1463 const Pattern& aPattern, 1464 const DrawOptions& aOptions) { 1465 if (mTransformSingular) { 1466 return; 1467 } 1468 1469 if (!IsValid()) { 1470 gfxDebug() << "FillGlyphs bad surface " 1471 << cairo_surface_status(cairo_get_group_target(mContext)); 1472 return; 1473 } 1474 1475 cairo_scaled_font_t* cairoScaledFont = 1476 aFont ? aFont->GetCairoScaledFont() : nullptr; 1477 if (!cairoScaledFont) { 1478 gfxDevCrash(LogReason::InvalidFont) << "Invalid scaled font"; 1479 return; 1480 } 1481 1482 AutoPrepareForDrawing prep(this, mContext); 1483 AutoClearDeviceOffset clear(aPattern); 1484 1485 cairo_set_scaled_font(mContext, cairoScaledFont); 1486 1487 cairo_pattern_t* pat = 1488 GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform()); 1489 if (!pat) return; 1490 1491 cairo_set_source(mContext, pat); 1492 cairo_pattern_destroy(pat); 1493 1494 cairo_antialias_t aa = GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode); 1495 cairo_set_antialias(mContext, aa); 1496 1497 // Override any font-specific options as necessary. 1498 SetFontOptions(aa); 1499 1500 // Convert our GlyphBuffer into a vector of Cairo glyphs. This code can 1501 // execute millions of times in short periods, so we want to avoid heap 1502 // allocation whenever possible. So we use an inline vector capacity of 1024 1503 // bytes (the maximum allowed by mozilla::Vector), which gives an inline 1504 // length of 1024 / 24 = 42 elements, which is enough to typically avoid heap 1505 // allocation in ~99% of cases. 1506 Vector<cairo_glyph_t, 1024 / sizeof(cairo_glyph_t)> glyphs; 1507 if (!glyphs.resizeUninitialized(aBuffer.mNumGlyphs)) { 1508 gfxDevCrash(LogReason::GlyphAllocFailedCairo) << "glyphs allocation failed"; 1509 return; 1510 } 1511 for (uint32_t i = 0; i < aBuffer.mNumGlyphs; ++i) { 1512 glyphs[i].index = aBuffer.mGlyphs[i].mIndex; 1513 glyphs[i].x = aBuffer.mGlyphs[i].mPosition.x; 1514 glyphs[i].y = aBuffer.mGlyphs[i].mPosition.y; 1515 } 1516 1517 if (!SupportsVariationSettings(mSurface) && aFont->HasVariationSettings() && 1518 StaticPrefs::print_font_variations_as_paths()) { 1519 cairo_set_fill_rule(mContext, CAIRO_FILL_RULE_WINDING); 1520 cairo_new_path(mContext); 1521 cairo_glyph_path(mContext, &glyphs[0], aBuffer.mNumGlyphs); 1522 cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); 1523 cairo_fill(mContext); 1524 } else { 1525 cairo_show_glyphs(mContext, &glyphs[0], aBuffer.mNumGlyphs); 1526 } 1527 1528 if (cairo_surface_status(cairo_get_group_target(mContext))) { 1529 gfxDebug() << "Ending FillGlyphs with a bad surface " 1530 << cairo_surface_status(cairo_get_group_target(mContext)); 1531 } 1532 } 1533 1534 void DrawTargetCairo::Mask(const Pattern& aSource, const Pattern& aMask, 1535 const DrawOptions& aOptions /* = DrawOptions() */) { 1536 if (mTransformSingular) { 1537 return; 1538 } 1539 1540 if (!IsValid()) { 1541 gfxCriticalNote << "Mask with bad surface " 1542 << cairo_surface_status(cairo_get_group_target(mContext)); 1543 return; 1544 } 1545 1546 AutoPrepareForDrawing prep(this, mContext); 1547 AutoClearDeviceOffset clearSource(aSource); 1548 AutoClearDeviceOffset clearMask(aMask); 1549 1550 cairo_set_antialias(mContext, 1551 GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode)); 1552 1553 cairo_pattern_t* source = 1554 GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform()); 1555 if (!source) { 1556 return; 1557 } 1558 1559 cairo_pattern_t* mask = 1560 GfxPatternToCairoPattern(aMask, aOptions.mAlpha, GetTransform()); 1561 if (!mask) { 1562 cairo_pattern_destroy(source); 1563 return; 1564 } 1565 1566 if (cairo_pattern_status(source) || cairo_pattern_status(mask)) { 1567 cairo_pattern_destroy(source); 1568 cairo_pattern_destroy(mask); 1569 gfxWarning() << "Invalid pattern"; 1570 return; 1571 } 1572 1573 cairo_set_source(mContext, source); 1574 cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); 1575 cairo_mask(mContext, mask); 1576 1577 cairo_pattern_destroy(mask); 1578 cairo_pattern_destroy(source); 1579 } 1580 1581 void DrawTargetCairo::MaskSurface(const Pattern& aSource, SourceSurface* aMask, 1582 Point aOffset, const DrawOptions& aOptions) { 1583 if (mTransformSingular) { 1584 return; 1585 } 1586 1587 if (!IsValid()) { 1588 gfxCriticalNote << "MaskSurface with bad surface " 1589 << cairo_surface_status(cairo_get_group_target(mContext)); 1590 return; 1591 } 1592 1593 AutoPrepareForDrawing prep(this, mContext); 1594 AutoClearDeviceOffset clearSource(aSource); 1595 AutoClearDeviceOffset clearMask(aMask); 1596 1597 if (!PatternIsCompatible(aSource)) { 1598 return; 1599 } 1600 1601 cairo_set_antialias(mContext, 1602 GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode)); 1603 1604 cairo_pattern_t* pat = 1605 GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform()); 1606 if (!pat) { 1607 return; 1608 } 1609 1610 if (cairo_pattern_status(pat)) { 1611 cairo_pattern_destroy(pat); 1612 gfxWarning() << "Invalid pattern"; 1613 return; 1614 } 1615 1616 cairo_set_source(mContext, pat); 1617 1618 if (NeedIntermediateSurface(aSource, aOptions)) { 1619 cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA); 1620 1621 // Don't want operators to be applied twice 1622 cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); 1623 1624 // Now draw the content using the desired operator 1625 cairo_paint_with_alpha(mContext, aOptions.mAlpha); 1626 1627 cairo_pop_group_to_source(mContext); 1628 } 1629 1630 cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aMask); 1631 if (!surf) { 1632 cairo_pattern_destroy(pat); 1633 return; 1634 } 1635 cairo_pattern_t* mask = cairo_pattern_create_for_surface(surf); 1636 cairo_matrix_t matrix; 1637 1638 cairo_matrix_init_translate(&matrix, -aOffset.x - aMask->GetRect().x, 1639 -aOffset.y - aMask->GetRect().y); 1640 cairo_pattern_set_matrix(mask, &matrix); 1641 1642 cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); 1643 1644 cairo_mask(mContext, mask); 1645 1646 cairo_surface_destroy(surf); 1647 cairo_pattern_destroy(mask); 1648 cairo_pattern_destroy(pat); 1649 } 1650 1651 void DrawTargetCairo::PushClip(const Path* aPath) { 1652 if (aPath->GetBackendType() != BackendType::CAIRO) { 1653 return; 1654 } 1655 1656 if (!IsValid()) { 1657 gfxCriticalNote << "PushClip with bad surface " 1658 << cairo_surface_status(cairo_get_group_target(mContext)); 1659 return; 1660 } 1661 1662 WillChange(aPath); 1663 cairo_save(mContext); 1664 1665 PathCairo* path = 1666 const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath)); 1667 1668 if (mTransformSingular) { 1669 cairo_new_path(mContext); 1670 cairo_rectangle(mContext, 0, 0, 0, 0); 1671 } else { 1672 path->SetPathOnContext(mContext); 1673 } 1674 cairo_clip_preserve(mContext); 1675 1676 ++mClipDepth; 1677 } 1678 1679 void DrawTargetCairo::PushClipRect(const Rect& aRect) { 1680 if (!IsValid()) { 1681 gfxCriticalNote << "PushClipRect with bad surface " 1682 << cairo_surface_status(cairo_get_group_target(mContext)); 1683 return; 1684 } 1685 1686 WillChange(); 1687 cairo_save(mContext); 1688 1689 cairo_new_path(mContext); 1690 if (mTransformSingular) { 1691 cairo_rectangle(mContext, 0, 0, 0, 0); 1692 } else { 1693 cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), 1694 aRect.Height()); 1695 } 1696 cairo_clip_preserve(mContext); 1697 1698 ++mClipDepth; 1699 } 1700 1701 void DrawTargetCairo::PopClip() { 1702 if (NS_WARN_IF(mClipDepth <= 0)) { 1703 return; 1704 } 1705 1706 if (!IsValid()) { 1707 gfxCriticalNote << "PopClip with bad surface " 1708 << cairo_surface_status(cairo_get_group_target(mContext)); 1709 return; 1710 } 1711 1712 // save/restore does not affect the path, so no need to call WillChange() 1713 1714 // cairo_restore will restore the transform too and we don't want to do that 1715 // so we'll save it now and restore it after the cairo_restore 1716 cairo_matrix_t mat; 1717 cairo_get_matrix(mContext, &mat); 1718 1719 cairo_restore(mContext); 1720 1721 cairo_set_matrix(mContext, &mat); 1722 1723 --mClipDepth; 1724 } 1725 1726 bool DrawTargetCairo::RemoveAllClips() { 1727 while (mClipDepth > 0) { 1728 PopClip(); 1729 } 1730 return true; 1731 } 1732 1733 void DrawTargetCairo::PushLayer(bool aOpaque, Float aOpacity, 1734 SourceSurface* aMask, 1735 const Matrix& aMaskTransform, 1736 const IntRect& aBounds, bool aCopyBackground) { 1737 PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds, 1738 aCopyBackground, CompositionOp::OP_OVER); 1739 } 1740 1741 void DrawTargetCairo::PushLayerWithBlend(bool aOpaque, Float aOpacity, 1742 SourceSurface* aMask, 1743 const Matrix& aMaskTransform, 1744 const IntRect& aBounds, 1745 bool aCopyBackground, 1746 CompositionOp aCompositionOp) { 1747 if (!IsValid()) { 1748 gfxCriticalNote << "PushLayerWithBlend with bad surface " 1749 << cairo_surface_status(cairo_get_group_target(mContext)); 1750 return; 1751 } 1752 1753 cairo_content_t content = CAIRO_CONTENT_COLOR_ALPHA; 1754 1755 if (mFormat == SurfaceFormat::A8) { 1756 content = CAIRO_CONTENT_ALPHA; 1757 } else if (aOpaque) { 1758 content = CAIRO_CONTENT_COLOR; 1759 } 1760 1761 if (aCopyBackground) { 1762 cairo_surface_t* source = cairo_get_group_target(mContext); 1763 cairo_push_group_with_content(mContext, content); 1764 cairo_surface_t* dest = cairo_get_group_target(mContext); 1765 cairo_t* ctx = cairo_create(dest); 1766 cairo_set_source_surface(ctx, source, 0, 0); 1767 cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE); 1768 cairo_paint(ctx); 1769 cairo_destroy(ctx); 1770 } else { 1771 cairo_push_group_with_content(mContext, content); 1772 } 1773 1774 PushedLayer layer(aOpacity, aCompositionOp, mPermitSubpixelAA); 1775 1776 if (aMask) { 1777 cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aMask); 1778 if (surf) { 1779 layer.mMaskPattern = cairo_pattern_create_for_surface(surf); 1780 Matrix maskTransform = aMaskTransform; 1781 maskTransform.PreTranslate(aMask->GetRect().X(), aMask->GetRect().Y()); 1782 cairo_matrix_t mat; 1783 GfxMatrixToCairoMatrix(maskTransform, mat); 1784 cairo_matrix_invert(&mat); 1785 cairo_pattern_set_matrix(layer.mMaskPattern, &mat); 1786 cairo_surface_destroy(surf); 1787 } else { 1788 gfxCriticalError() << "Failed to get cairo surface for mask surface!"; 1789 } 1790 } 1791 1792 mPushedLayers.push_back(layer); 1793 1794 SetPermitSubpixelAA(aOpaque); 1795 } 1796 1797 void DrawTargetCairo::PopLayer() { 1798 if (!IsValid()) { 1799 gfxCriticalNote << "PopLayer with bad surface " 1800 << cairo_surface_status(cairo_get_group_target(mContext)); 1801 return; 1802 } 1803 1804 MOZ_RELEASE_ASSERT(!mPushedLayers.empty()); 1805 1806 cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); 1807 1808 cairo_pop_group_to_source(mContext); 1809 1810 PushedLayer layer = mPushedLayers.back(); 1811 mPushedLayers.pop_back(); 1812 1813 if (!layer.mMaskPattern) { 1814 cairo_set_operator(mContext, GfxOpToCairoOp(layer.mCompositionOp)); 1815 cairo_paint_with_alpha(mContext, layer.mOpacity); 1816 } else { 1817 if (layer.mOpacity != Float(1.0)) { 1818 cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA); 1819 1820 // Now draw the content using the desired operator 1821 cairo_paint_with_alpha(mContext, layer.mOpacity); 1822 1823 cairo_pop_group_to_source(mContext); 1824 } 1825 cairo_set_operator(mContext, GfxOpToCairoOp(layer.mCompositionOp)); 1826 cairo_mask(mContext, layer.mMaskPattern); 1827 } 1828 1829 cairo_matrix_t mat; 1830 GfxMatrixToCairoMatrix(mTransform, mat); 1831 cairo_set_matrix(mContext, &mat); 1832 1833 cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); 1834 1835 cairo_pattern_destroy(layer.mMaskPattern); 1836 SetPermitSubpixelAA(layer.mWasPermittingSubpixelAA); 1837 } 1838 1839 void DrawTargetCairo::ClearSurfaceForUnboundedSource( 1840 const CompositionOp& aOperator) { 1841 if (aOperator != CompositionOp::OP_SOURCE) { 1842 return; 1843 } 1844 1845 if (!IsValid()) { 1846 gfxCriticalNote << "ClearSurfaceForUnboundedSource with bad surface " 1847 << cairo_surface_status(cairo_get_group_target(mContext)); 1848 return; 1849 } 1850 1851 cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR); 1852 // It doesn't really matter what the source is here, since Paint 1853 // isn't bounded by the source and the mask covers the entire clip 1854 // region. 1855 cairo_paint(mContext); 1856 } 1857 1858 already_AddRefed<GradientStops> DrawTargetCairo::CreateGradientStops( 1859 GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) const { 1860 return MakeAndAddRef<GradientStopsCairo>(aStops, aNumStops, aExtendMode); 1861 } 1862 1863 already_AddRefed<FilterNode> DrawTargetCairo::CreateFilter(FilterType aType) { 1864 return FilterNodeSoftware::Create(aType); 1865 } 1866 1867 already_AddRefed<SourceSurface> DrawTargetCairo::CreateSourceSurfaceFromData( 1868 unsigned char* aData, const IntSize& aSize, int32_t aStride, 1869 SurfaceFormat aFormat) const { 1870 if (!aData) { 1871 gfxWarning() << "DrawTargetCairo::CreateSourceSurfaceFromData null aData"; 1872 return nullptr; 1873 } 1874 1875 cairo_surface_t* surf = CopyToImageSurface( 1876 aData, aSize, IntRect(IntPoint(), aSize), aStride, aFormat); 1877 if (!surf) { 1878 return nullptr; 1879 } 1880 1881 RefPtr<SourceSurfaceCairo> source_surf = 1882 new SourceSurfaceCairo(surf, aSize, aFormat); 1883 cairo_surface_destroy(surf); 1884 1885 return source_surf.forget(); 1886 } 1887 1888 already_AddRefed<SourceSurface> DrawTargetCairo::OptimizeSourceSurface( 1889 SourceSurface* aSurface) const { 1890 RefPtr<SourceSurface> surface(aSurface); 1891 return surface.forget(); 1892 } 1893 1894 already_AddRefed<SourceSurface> 1895 DrawTargetCairo::CreateSourceSurfaceFromNativeSurface( 1896 const NativeSurface& aSurface) const { 1897 return nullptr; 1898 } 1899 1900 already_AddRefed<DrawTarget> DrawTargetCairo::CreateSimilarDrawTarget( 1901 const IntSize& aSize, SurfaceFormat aFormat) const { 1902 if (cairo_surface_status(cairo_get_group_target(mContext))) { 1903 RefPtr<DrawTargetCairo> target = new DrawTargetCairo(); 1904 if (target->Init(aSize, aFormat)) { 1905 return target.forget(); 1906 } 1907 } 1908 1909 cairo_surface_t* similar; 1910 switch (cairo_surface_get_type(mSurface)) { 1911 #ifdef CAIRO_HAS_WIN32_SURFACE 1912 case CAIRO_SURFACE_TYPE_WIN32: 1913 similar = cairo_win32_surface_create_with_dib( 1914 GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height); 1915 break; 1916 #endif 1917 #ifdef CAIRO_HAS_QUARTZ_SURFACE 1918 case CAIRO_SURFACE_TYPE_QUARTZ: 1919 if (StaticPrefs::gfx_cairo_quartz_cg_layer_enabled()) { 1920 similar = cairo_quartz_surface_create_cg_layer( 1921 mSurface, GfxFormatToCairoContent(aFormat), aSize.width, 1922 aSize.height); 1923 break; 1924 } 1925 [[fallthrough]]; 1926 #endif 1927 default: 1928 similar = cairo_surface_create_similar(mSurface, 1929 GfxFormatToCairoContent(aFormat), 1930 aSize.width, aSize.height); 1931 break; 1932 } 1933 1934 if (!cairo_surface_status(similar)) { 1935 RefPtr<DrawTargetCairo> target = new DrawTargetCairo(); 1936 if (target->InitAlreadyReferenced(similar, aSize)) { 1937 return target.forget(); 1938 } 1939 } 1940 1941 gfxCriticalError( 1942 CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize))) 1943 << "Failed to create similar cairo surface! Size: " << aSize 1944 << " Status: " << cairo_surface_status(similar) 1945 << cairo_surface_status(cairo_get_group_target(mContext)) << " format " 1946 << (int)aFormat; 1947 cairo_surface_destroy(similar); 1948 1949 return nullptr; 1950 } 1951 1952 RefPtr<DrawTarget> DrawTargetCairo::CreateClippedDrawTarget( 1953 const Rect& aBounds, SurfaceFormat aFormat) { 1954 RefPtr<DrawTarget> result; 1955 // Doing this save()/restore() dance is wasteful 1956 cairo_save(mContext); 1957 1958 if (!aBounds.IsEmpty()) { 1959 cairo_new_path(mContext); 1960 cairo_rectangle(mContext, aBounds.X(), aBounds.Y(), aBounds.Width(), 1961 aBounds.Height()); 1962 cairo_clip(mContext); 1963 } 1964 cairo_identity_matrix(mContext); 1965 IntRect clipBounds = IntRect::RoundOut(GetUserSpaceClip()); 1966 if (!clipBounds.IsEmpty()) { 1967 RefPtr<DrawTarget> dt = CreateSimilarDrawTarget( 1968 IntSize(clipBounds.width, clipBounds.height), aFormat); 1969 if (dt) { 1970 result = gfx::Factory::CreateOffsetDrawTarget( 1971 dt, IntPoint(clipBounds.x, clipBounds.y)); 1972 if (result) { 1973 result->SetTransform(mTransform); 1974 } 1975 } 1976 } else { 1977 // Everything is clipped but we still want some kind of surface 1978 result = CreateSimilarDrawTarget(IntSize(1, 1), aFormat); 1979 } 1980 1981 cairo_restore(mContext); 1982 return result; 1983 } 1984 1985 bool DrawTargetCairo::InitAlreadyReferenced(cairo_surface_t* aSurface, 1986 const IntSize& aSize, 1987 SurfaceFormat* aFormat) { 1988 if (cairo_surface_status(aSurface)) { 1989 gfxCriticalNote << "Attempt to create DrawTarget for invalid surface. " 1990 << aSize 1991 << " Cairo Status: " << cairo_surface_status(aSurface); 1992 cairo_surface_destroy(aSurface); 1993 return false; 1994 } 1995 1996 mContext = cairo_create(aSurface); 1997 mSurface = aSurface; 1998 mSize = aSize; 1999 mFormat = aFormat ? *aFormat : GfxFormatForCairoSurface(aSurface); 2000 2001 // Cairo image surface have a bug where they will allocate a mask surface (for 2002 // clipping) the size of the clip extents, and don't take the surface extents 2003 // into account. Add a manual clip to the surface extents to prevent this. 2004 cairo_new_path(mContext); 2005 cairo_rectangle(mContext, 0, 0, mSize.width, mSize.height); 2006 cairo_clip(mContext); 2007 2008 if (mFormat == SurfaceFormat::A8R8G8B8_UINT32 || 2009 mFormat == SurfaceFormat::R8G8B8A8) { 2010 SetPermitSubpixelAA(false); 2011 } else { 2012 SetPermitSubpixelAA(true); 2013 } 2014 2015 return true; 2016 } 2017 2018 already_AddRefed<DrawTarget> DrawTargetCairo::CreateShadowDrawTarget( 2019 const IntSize& aSize, SurfaceFormat aFormat, float aSigma) const { 2020 cairo_surface_t* similar = cairo_surface_create_similar( 2021 cairo_get_target(mContext), GfxFormatToCairoContent(aFormat), aSize.width, 2022 aSize.height); 2023 2024 if (cairo_surface_status(similar)) { 2025 return nullptr; 2026 } 2027 2028 // If we don't have a blur then we can use the RGBA mask and keep all the 2029 // operations in graphics memory. 2030 if (aSigma == 0.0f || aFormat == SurfaceFormat::A8) { 2031 RefPtr<DrawTargetCairo> target = new DrawTargetCairo(); 2032 if (target->InitAlreadyReferenced(similar, aSize)) { 2033 return target.forget(); 2034 } else { 2035 return nullptr; 2036 } 2037 } 2038 2039 cairo_surface_t* blursurf = 2040 cairo_image_surface_create(CAIRO_FORMAT_A8, aSize.width, aSize.height); 2041 2042 if (cairo_surface_status(blursurf)) { 2043 return nullptr; 2044 } 2045 2046 cairo_surface_t* tee = cairo_tee_surface_create(blursurf); 2047 cairo_surface_destroy(blursurf); 2048 if (cairo_surface_status(tee)) { 2049 cairo_surface_destroy(similar); 2050 return nullptr; 2051 } 2052 2053 cairo_tee_surface_add(tee, similar); 2054 cairo_surface_destroy(similar); 2055 2056 RefPtr<DrawTargetCairo> target = new DrawTargetCairo(); 2057 if (target->InitAlreadyReferenced(tee, aSize)) { 2058 return target.forget(); 2059 } 2060 return nullptr; 2061 } 2062 2063 bool DrawTargetCairo::Draw3DTransformedSurface(SourceSurface* aSurface, 2064 const Matrix4x4& aMatrix) { 2065 return DrawTarget::Draw3DTransformedSurface(aSurface, aMatrix); 2066 } 2067 2068 bool DrawTargetCairo::Init(cairo_surface_t* aSurface, const IntSize& aSize, 2069 SurfaceFormat* aFormat) { 2070 cairo_surface_reference(aSurface); 2071 return InitAlreadyReferenced(aSurface, aSize, aFormat); 2072 } 2073 2074 bool DrawTargetCairo::Init(const IntSize& aSize, SurfaceFormat aFormat) { 2075 cairo_surface_t* surf = cairo_image_surface_create( 2076 GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height); 2077 return InitAlreadyReferenced(surf, aSize); 2078 } 2079 2080 bool DrawTargetCairo::Init(unsigned char* aData, const IntSize& aSize, 2081 int32_t aStride, SurfaceFormat aFormat) { 2082 cairo_surface_t* surf = cairo_image_surface_create_for_data( 2083 aData, GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height, 2084 aStride); 2085 return InitAlreadyReferenced(surf, aSize); 2086 } 2087 2088 void* DrawTargetCairo::GetNativeSurface(NativeSurfaceType aType) { 2089 if (aType == NativeSurfaceType::CAIRO_CONTEXT) { 2090 return mContext; 2091 } 2092 2093 return nullptr; 2094 } 2095 2096 void DrawTargetCairo::MarkSnapshotIndependent() { 2097 if (mSnapshot) { 2098 if (mSnapshot->refCount() > 1) { 2099 // We only need to worry about snapshots that someone else knows about 2100 mSnapshot->DrawTargetWillChange(); 2101 } 2102 mSnapshot = nullptr; 2103 } 2104 } 2105 2106 void DrawTargetCairo::WillChange(const Path* aPath /* = nullptr */) { 2107 MarkSnapshotIndependent(); 2108 MOZ_ASSERT(!mLockedBits); 2109 } 2110 2111 void DrawTargetCairo::SetTransform(const Matrix& aTransform) { 2112 DrawTarget::SetTransform(aTransform); 2113 2114 mTransformSingular = aTransform.IsSingular(); 2115 if (!mTransformSingular) { 2116 cairo_matrix_t mat; 2117 GfxMatrixToCairoMatrix(mTransform, mat); 2118 cairo_set_matrix(mContext, &mat); 2119 } 2120 } 2121 2122 Rect DrawTargetCairo::GetUserSpaceClip() const { 2123 double clipX1, clipY1, clipX2, clipY2; 2124 cairo_clip_extents(mContext, &clipX1, &clipY1, &clipX2, &clipY2); 2125 return Rect(clipX1, clipY1, clipX2 - clipX1, 2126 clipY2 - clipY1); // Narrowing of doubles to floats 2127 } 2128 2129 } // namespace gfx 2130 } // namespace mozilla