forked from mirrors/gecko-dev
		
	 b2d4c86823
			
		
	
	
		b2d4c86823
		
	
	
	
	
		
			
			--HG-- extra : rebase_source : 56f2cc017632bf27115490ae05254019108c6179 extra : amend_source : 98ea6c3c02a9f7650d2cf65deaf5085cf9a2efa4
		
			
				
	
	
		
			2405 lines
		
	
	
	
		
			71 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2405 lines
		
	
	
	
		
			71 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "DrawTargetCairo.h"
 | |
| 
 | |
| #include "SourceSurfaceCairo.h"
 | |
| #include "PathCairo.h"
 | |
| #include "HelpersCairo.h"
 | |
| #include "ScaledFontBase.h"
 | |
| #include "BorrowedContext.h"
 | |
| #include "FilterNodeSoftware.h"
 | |
| #include "mozilla/Scoped.h"
 | |
| #include "mozilla/UniquePtr.h"
 | |
| #include "mozilla/Vector.h"
 | |
| #include "gfxPrefs.h"
 | |
| 
 | |
| #include "cairo.h"
 | |
| #include "cairo-tee.h"
 | |
| #include <string.h>
 | |
| 
 | |
| #include "Blur.h"
 | |
| #include "Logging.h"
 | |
| #include "Tools.h"
 | |
| 
 | |
| #ifdef CAIRO_HAS_QUARTZ_SURFACE
 | |
| #include "cairo-quartz.h"
 | |
| #ifdef MOZ_WIDGET_COCOA
 | |
| #include <ApplicationServices/ApplicationServices.h>
 | |
| #endif
 | |
| #endif
 | |
| 
 | |
| #ifdef CAIRO_HAS_XLIB_SURFACE
 | |
| #include "cairo-xlib.h"
 | |
| #include "cairo-xlib-xrender.h"
 | |
| #endif
 | |
| 
 | |
| #ifdef CAIRO_HAS_WIN32_SURFACE
 | |
| #include "cairo-win32.h"
 | |
| #endif
 | |
| 
 | |
| #define PIXMAN_DONT_DEFINE_STDINT
 | |
| #include "pixman.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| 
 | |
| // 2^23
 | |
| #define CAIRO_COORD_MAX (Float(0x7fffff))
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCairoSurface, cairo_surface_t, cairo_surface_destroy);
 | |
| 
 | |
| namespace gfx {
 | |
| 
 | |
| cairo_surface_t *DrawTargetCairo::mDummySurface;
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| // An RAII class to prepare to draw a context and optional path. Saves and
 | |
| // restores the context on construction/destruction.
 | |
| class AutoPrepareForDrawing
 | |
| {
 | |
| public:
 | |
|   AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx)
 | |
|     : mCtx(ctx)
 | |
|   {
 | |
|     dt->PrepareForDrawing(ctx);
 | |
|     cairo_save(mCtx);
 | |
|     MOZ_ASSERT(cairo_status(mCtx) || dt->GetTransform().FuzzyEquals(GetTransform()));
 | |
|   }
 | |
| 
 | |
|   AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx, const Path* path)
 | |
|     : mCtx(ctx)
 | |
|   {
 | |
|     dt->PrepareForDrawing(ctx, path);
 | |
|     cairo_save(mCtx);
 | |
|     MOZ_ASSERT(cairo_status(mCtx) || dt->GetTransform().FuzzyEquals(GetTransform()));
 | |
|   }
 | |
| 
 | |
|   ~AutoPrepareForDrawing()
 | |
|   {
 | |
|     cairo_restore(mCtx);
 | |
|     cairo_status_t status = cairo_status(mCtx);
 | |
|     if (status) {
 | |
|       gfxWarning() << "DrawTargetCairo context in error state: " << cairo_status_to_string(status) << "(" << status << ")";
 | |
|     }
 | |
|   }
 | |
| 
 | |
| private:
 | |
| #ifdef DEBUG
 | |
|   Matrix GetTransform()
 | |
|   {
 | |
|     cairo_matrix_t mat;
 | |
|     cairo_get_matrix(mCtx, &mat);
 | |
|     return Matrix(mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   cairo_t* mCtx;
 | |
| };
 | |
| 
 | |
| /* Clamp r to (0,0) (2^23,2^23)
 | |
|  * these are to be device coordinates.
 | |
|  *
 | |
|  * Returns false if the rectangle is completely out of bounds,
 | |
|  * true otherwise.
 | |
|  *
 | |
|  * This function assumes that it will be called with a rectangle being
 | |
|  * drawn into a surface with an identity transformation matrix; that
 | |
|  * is, anything above or to the left of (0,0) will be offscreen.
 | |
|  *
 | |
|  * First it checks if the rectangle is entirely beyond
 | |
|  * CAIRO_COORD_MAX; if so, it can't ever appear on the screen --
 | |
|  * false is returned.
 | |
|  *
 | |
|  * Then it shifts any rectangles with x/y < 0 so that x and y are = 0,
 | |
|  * and adjusts the width and height appropriately.  For example, a
 | |
|  * rectangle from (0,-5) with dimensions (5,10) will become a
 | |
|  * rectangle from (0,0) with dimensions (5,5).
 | |
|  *
 | |
|  * If after negative x/y adjustment to 0, either the width or height
 | |
|  * is negative, then the rectangle is completely offscreen, and
 | |
|  * nothing is drawn -- false is returned.
 | |
|  *
 | |
|  * Finally, if x+width or y+height are greater than CAIRO_COORD_MAX,
 | |
|  * the width and height are clamped such x+width or y+height are equal
 | |
|  * to CAIRO_COORD_MAX, and true is returned.
 | |
|  */
 | |
| static bool
 | |
| ConditionRect(Rect& r) {
 | |
|   // if either x or y is way out of bounds;
 | |
|   // note that we don't handle negative w/h here
 | |
|   if (r.X() > CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX)
 | |
|     return false;
 | |
| 
 | |
|   if (r.X() < 0.f) {
 | |
|     r.SetWidth(r.XMost());
 | |
|     if (r.Width() < 0.f)
 | |
|       return false;
 | |
|     r.MoveToX(0.f);
 | |
|   }
 | |
| 
 | |
|   if (r.XMost() > CAIRO_COORD_MAX) {
 | |
|     r.SetRightEdge(CAIRO_COORD_MAX);
 | |
|   }
 | |
| 
 | |
|   if (r.Y() < 0.f) {
 | |
|     r.SetHeight(r.YMost());
 | |
|     if (r.Height() < 0.f)
 | |
|       return false;
 | |
| 
 | |
|     r.MoveToY(0.f);
 | |
|   }
 | |
| 
 | |
|   if (r.YMost() > CAIRO_COORD_MAX) {
 | |
|     r.SetBottomEdge(CAIRO_COORD_MAX);
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| } // end anonymous namespace
 | |
| 
 | |
| static bool
 | |
| SupportsSelfCopy(cairo_surface_t* surface)
 | |
| {
 | |
|   switch (cairo_surface_get_type(surface))
 | |
|   {
 | |
| #ifdef CAIRO_HAS_QUARTZ_SURFACE
 | |
|     case CAIRO_SURFACE_TYPE_QUARTZ:
 | |
|       return true;
 | |
| #endif
 | |
| #ifdef CAIRO_HAS_WIN32_SURFACE
 | |
|     case CAIRO_SURFACE_TYPE_WIN32:
 | |
|     case CAIRO_SURFACE_TYPE_WIN32_PRINTING:
 | |
|       return true;
 | |
| #endif
 | |
|     default:
 | |
|       return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool
 | |
| PatternIsCompatible(const Pattern& aPattern)
 | |
| {
 | |
|   switch (aPattern.GetType())
 | |
|   {
 | |
|     case PatternType::LINEAR_GRADIENT:
 | |
|     {
 | |
|       const LinearGradientPattern& pattern = static_cast<const LinearGradientPattern&>(aPattern);
 | |
|       return pattern.mStops->GetBackendType() == BackendType::CAIRO;
 | |
|     }
 | |
|     case PatternType::RADIAL_GRADIENT:
 | |
|     {
 | |
|       const RadialGradientPattern& pattern = static_cast<const RadialGradientPattern&>(aPattern);
 | |
|       return pattern.mStops->GetBackendType() == BackendType::CAIRO;
 | |
|     }
 | |
|     default:
 | |
|       return true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static cairo_user_data_key_t surfaceDataKey;
 | |
| 
 | |
| void
 | |
| ReleaseData(void* aData)
 | |
| {
 | |
|   DataSourceSurface *data = static_cast<DataSourceSurface*>(aData);
 | |
|   data->Unmap();
 | |
|   data->Release();
 | |
| }
 | |
| 
 | |
| cairo_surface_t*
 | |
| CopyToImageSurface(unsigned char *aData,
 | |
|                    const IntRect &aRect,
 | |
|                    int32_t aStride,
 | |
|                    SurfaceFormat aFormat)
 | |
| {
 | |
|   MOZ_ASSERT(aData);
 | |
| 
 | |
|   auto aRectWidth = aRect.Width();
 | |
|   auto aRectHeight = aRect.Height();
 | |
| 
 | |
|   cairo_surface_t* surf = cairo_image_surface_create(GfxFormatToCairoFormat(aFormat),
 | |
|                                                      aRectWidth,
 | |
|                                                      aRectHeight);
 | |
|   // In certain scenarios, requesting larger than 8k image fails.  Bug 803568
 | |
|   // covers the details of how to run into it, but the full detailed
 | |
|   // investigation hasn't been done to determine the underlying cause.  We
 | |
|   // will just handle the failure to allocate the surface to avoid a crash.
 | |
|   if (cairo_surface_status(surf)) {
 | |
|     gfxWarning() << "Invalid surface DTC " << cairo_surface_status(surf);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   unsigned char* surfData = cairo_image_surface_get_data(surf);
 | |
|   int surfStride = cairo_image_surface_get_stride(surf);
 | |
|   int32_t pixelWidth = BytesPerPixel(aFormat);
 | |
| 
 | |
|   unsigned char* source = aData +
 | |
|                           aRect.Y() * aStride +
 | |
|                           aRect.X() * pixelWidth;
 | |
| 
 | |
|   MOZ_ASSERT(aStride >= aRectWidth * pixelWidth);
 | |
|   for (int32_t y = 0; y < aRectHeight; ++y) {
 | |
|     memcpy(surfData + y * surfStride,
 | |
|            source + y * aStride,
 | |
|            aRectWidth * pixelWidth);
 | |
|   }
 | |
|   cairo_surface_mark_dirty(surf);
 | |
|   return surf;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * If aSurface can be represented as a surface of type
 | |
|  * CAIRO_SURFACE_TYPE_IMAGE then returns that surface. Does
 | |
|  * not add a reference.
 | |
|  */
 | |
| cairo_surface_t* GetAsImageSurface(cairo_surface_t* aSurface)
 | |
| {
 | |
|   if (cairo_surface_get_type(aSurface) == CAIRO_SURFACE_TYPE_IMAGE) {
 | |
|     return aSurface;
 | |
| #ifdef CAIRO_HAS_WIN32_SURFACE
 | |
|   } else if (cairo_surface_get_type(aSurface) == CAIRO_SURFACE_TYPE_WIN32) {
 | |
|     return cairo_win32_surface_get_image(aSurface);
 | |
| #endif
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| cairo_surface_t* CreateSubImageForData(unsigned char* aData,
 | |
|                                        const IntRect& aRect,
 | |
|                                        int aStride,
 | |
|                                        SurfaceFormat aFormat)
 | |
| {
 | |
|   if (!aData) {
 | |
|     gfxWarning() << "DrawTargetCairo.CreateSubImageForData null aData";
 | |
|     return nullptr;
 | |
|   }
 | |
|   unsigned char *data = aData +
 | |
|                         aRect.Y() * aStride +
 | |
|                         aRect.X() * BytesPerPixel(aFormat);
 | |
| 
 | |
|   cairo_surface_t *image =
 | |
|     cairo_image_surface_create_for_data(data,
 | |
|                                         GfxFormatToCairoFormat(aFormat),
 | |
|                                         aRect.Width(),
 | |
|                                         aRect.Height(),
 | |
|                                         aStride);
 | |
|   cairo_surface_set_device_offset(image, -aRect.X(), -aRect.Y());
 | |
|   return image;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns a referenced cairo_surface_t representing the
 | |
|  * sub-image specified by aSubImage.
 | |
|  */
 | |
| cairo_surface_t* ExtractSubImage(cairo_surface_t* aSurface,
 | |
|                                  const IntRect& aSubImage,
 | |
|                                  SurfaceFormat aFormat)
 | |
| {
 | |
|   // No need to worry about retaining a reference to the original
 | |
|   // surface since the only caller of this function guarantees
 | |
|   // that aSurface will stay alive as long as the result
 | |
| 
 | |
|   cairo_surface_t* image = GetAsImageSurface(aSurface);
 | |
|   if (image) {
 | |
|     image = CreateSubImageForData(cairo_image_surface_get_data(image),
 | |
|                                   aSubImage,
 | |
|                                   cairo_image_surface_get_stride(image),
 | |
|                                   aFormat);
 | |
|     return image;
 | |
|   }
 | |
| 
 | |
|   cairo_surface_t* similar =
 | |
|     cairo_surface_create_similar(aSurface,
 | |
|                                  cairo_surface_get_content(aSurface),
 | |
|                                  aSubImage.Width(), aSubImage.Height());
 | |
| 
 | |
|   cairo_t* ctx = cairo_create(similar);
 | |
|   cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
 | |
|   cairo_set_source_surface(ctx, aSurface, -aSubImage.X(), -aSubImage.Y());
 | |
|   cairo_paint(ctx);
 | |
|   cairo_destroy(ctx);
 | |
| 
 | |
|   cairo_surface_set_device_offset(similar, -aSubImage.X(), -aSubImage.Y());
 | |
|   return similar;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns cairo surface for the given SourceSurface.
 | |
|  * If possible, it will use the cairo_surface associated with aSurface,
 | |
|  * otherwise, it will create a new cairo_surface.
 | |
|  * In either case, the caller must call cairo_surface_destroy on the
 | |
|  * result when it is done with it.
 | |
|  */
 | |
| cairo_surface_t*
 | |
| GetCairoSurfaceForSourceSurface(SourceSurface *aSurface,
 | |
|                                 bool aExistingOnly = false,
 | |
|                                 const IntRect& aSubImage = IntRect())
 | |
| {
 | |
|   if (!aSurface) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   IntRect subimage = IntRect(IntPoint(), aSurface->GetSize());
 | |
|   if (!aSubImage.IsEmpty()) {
 | |
|     MOZ_ASSERT(!aExistingOnly);
 | |
|     MOZ_ASSERT(subimage.Contains(aSubImage));
 | |
|     subimage = aSubImage;
 | |
|   }
 | |
| 
 | |
|   if (aSurface->GetType() == SurfaceType::CAIRO) {
 | |
|     cairo_surface_t* surf = static_cast<SourceSurfaceCairo*>(aSurface)->GetSurface();
 | |
|     if (aSubImage.IsEmpty()) {
 | |
|       cairo_surface_reference(surf);
 | |
|     } else {
 | |
|       surf = ExtractSubImage(surf, subimage, aSurface->GetFormat());
 | |
|     }
 | |
|     return surf;
 | |
|   }
 | |
| 
 | |
|   if (aSurface->GetType() == SurfaceType::CAIRO_IMAGE) {
 | |
|     cairo_surface_t* surf =
 | |
|       static_cast<const DataSourceSurfaceCairo*>(aSurface)->GetSurface();
 | |
|     if (aSubImage.IsEmpty()) {
 | |
|       cairo_surface_reference(surf);
 | |
|     } else {
 | |
|       surf = ExtractSubImage(surf, subimage, aSurface->GetFormat());
 | |
|     }
 | |
|     return surf;
 | |
|   }
 | |
| 
 | |
|   if (aExistingOnly) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
 | |
|   if (!data) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   DataSourceSurface::MappedSurface map;
 | |
|   if (!data->Map(DataSourceSurface::READ, &map)) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   cairo_surface_t* surf =
 | |
|     CreateSubImageForData(map.mData, subimage,
 | |
|                           map.mStride, data->GetFormat());
 | |
| 
 | |
|   // In certain scenarios, requesting larger than 8k image fails.  Bug 803568
 | |
|   // covers the details of how to run into it, but the full detailed
 | |
|   // investigation hasn't been done to determine the underlying cause.  We
 | |
|   // will just handle the failure to allocate the surface to avoid a crash.
 | |
|   if (!surf || cairo_surface_status(surf)) {
 | |
|     if (surf && (cairo_surface_status(surf) == CAIRO_STATUS_INVALID_STRIDE)) {
 | |
|       // If we failed because of an invalid stride then copy into
 | |
|       // a new surface with a stride that cairo chooses. No need to
 | |
|       // set user data since we're not dependent on the original
 | |
|       // data.
 | |
|       cairo_surface_t* result =
 | |
|         CopyToImageSurface(map.mData,
 | |
|                            subimage,
 | |
|                            map.mStride,
 | |
|                            data->GetFormat());
 | |
|       data->Unmap();
 | |
|       return result;
 | |
|     }
 | |
|     data->Unmap();
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   cairo_surface_set_user_data(surf,
 | |
|                               &surfaceDataKey,
 | |
|                               data.forget().take(),
 | |
|                               ReleaseData);
 | |
|   return surf;
 | |
| }
 | |
| 
 | |
| // An RAII class to temporarily clear any device offset set
 | |
| // on a surface. Note that this does not take a reference to the
 | |
| // surface.
 | |
| class AutoClearDeviceOffset
 | |
| {
 | |
| public:
 | |
|   explicit AutoClearDeviceOffset(SourceSurface* aSurface)
 | |
|     : mSurface(nullptr)
 | |
|     , mX(0)
 | |
|     , mY(0)
 | |
|   {
 | |
|     Init(aSurface);
 | |
|   }
 | |
| 
 | |
|   explicit AutoClearDeviceOffset(const Pattern& aPattern)
 | |
|     : mSurface(nullptr)
 | |
|     , mX(0.0)
 | |
|     , mY(0.0)
 | |
|   {
 | |
|     if (aPattern.GetType() == PatternType::SURFACE) {
 | |
|       const SurfacePattern& pattern = static_cast<const SurfacePattern&>(aPattern);
 | |
|       Init(pattern.mSurface);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ~AutoClearDeviceOffset()
 | |
|   {
 | |
|     if (mSurface) {
 | |
|       cairo_surface_set_device_offset(mSurface, mX, mY);
 | |
|     }
 | |
|   }
 | |
| 
 | |
| private:
 | |
|   void Init(SourceSurface* aSurface)
 | |
|   {
 | |
|     cairo_surface_t* surface = GetCairoSurfaceForSourceSurface(aSurface, true);
 | |
|     if (surface) {
 | |
|       Init(surface);
 | |
|       cairo_surface_destroy(surface);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void Init(cairo_surface_t *aSurface)
 | |
|   {
 | |
|     mSurface = aSurface;
 | |
|     cairo_surface_get_device_offset(mSurface, &mX, &mY);
 | |
|     cairo_surface_set_device_offset(mSurface, 0, 0);
 | |
|   }
 | |
| 
 | |
|   cairo_surface_t* mSurface;
 | |
|   double mX;
 | |
|   double mY;
 | |
| };
 | |
| 
 | |
| static inline void
 | |
| CairoPatternAddGradientStop(cairo_pattern_t* aPattern,
 | |
|                             const GradientStop &aStop,
 | |
|                             Float aNudge = 0)
 | |
| {
 | |
|   cairo_pattern_add_color_stop_rgba(aPattern, aStop.offset + aNudge,
 | |
|                                     aStop.color.r, aStop.color.g, aStop.color.b,
 | |
|                                     aStop.color.a);
 | |
| 
 | |
| }
 | |
| 
 | |
| // Never returns nullptr. As such, you must always pass in Cairo-compatible
 | |
| // patterns, most notably gradients with a GradientStopCairo.
 | |
| // The pattern returned must have cairo_pattern_destroy() called on it by the
 | |
| // caller.
 | |
| // As the cairo_pattern_t returned may depend on the Pattern passed in, the
 | |
| // lifetime of the cairo_pattern_t returned must not exceed the lifetime of the
 | |
| // Pattern passed in.
 | |
| static cairo_pattern_t*
 | |
| GfxPatternToCairoPattern(const Pattern& aPattern,
 | |
|                          Float aAlpha,
 | |
|                          const Matrix& aTransform)
 | |
| {
 | |
|   cairo_pattern_t* pat;
 | |
|   const Matrix* matrix = nullptr;
 | |
| 
 | |
|   switch (aPattern.GetType())
 | |
|   {
 | |
|     case PatternType::COLOR:
 | |
|     {
 | |
|       Color color = static_cast<const ColorPattern&>(aPattern).mColor;
 | |
|       pat = cairo_pattern_create_rgba(color.r, color.g, color.b, color.a * aAlpha);
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     case PatternType::SURFACE:
 | |
|     {
 | |
|       const SurfacePattern& pattern = static_cast<const SurfacePattern&>(aPattern);
 | |
|       cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(pattern.mSurface,
 | |
|                                                               false,
 | |
|                                                               pattern.mSamplingRect);
 | |
|       if (!surf)
 | |
|         return nullptr;
 | |
| 
 | |
|       pat = cairo_pattern_create_for_surface(surf);
 | |
| 
 | |
|       matrix = &pattern.mMatrix;
 | |
| 
 | |
|       cairo_pattern_set_filter(pat, GfxSamplingFilterToCairoFilter(pattern.mSamplingFilter));
 | |
|       cairo_pattern_set_extend(pat, GfxExtendToCairoExtend(pattern.mExtendMode));
 | |
| 
 | |
|       cairo_surface_destroy(surf);
 | |
|       break;
 | |
|     }
 | |
|     case PatternType::LINEAR_GRADIENT:
 | |
|     {
 | |
|       const LinearGradientPattern& pattern = static_cast<const LinearGradientPattern&>(aPattern);
 | |
| 
 | |
|       pat = cairo_pattern_create_linear(pattern.mBegin.x, pattern.mBegin.y,
 | |
|                                         pattern.mEnd.x, pattern.mEnd.y);
 | |
| 
 | |
|       MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO);
 | |
|       GradientStopsCairo* cairoStops = static_cast<GradientStopsCairo*>(pattern.mStops.get());
 | |
|       cairo_pattern_set_extend(pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode()));
 | |
| 
 | |
|       matrix = &pattern.mMatrix;
 | |
| 
 | |
|       const std::vector<GradientStop>& stops = cairoStops->GetStops();
 | |
|       for (size_t i = 0; i < stops.size(); ++i) {
 | |
|         CairoPatternAddGradientStop(pat, stops[i]);
 | |
|       }
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|     case PatternType::RADIAL_GRADIENT:
 | |
|     {
 | |
|       const RadialGradientPattern& pattern = static_cast<const RadialGradientPattern&>(aPattern);
 | |
| 
 | |
|       pat = cairo_pattern_create_radial(pattern.mCenter1.x, pattern.mCenter1.y, pattern.mRadius1,
 | |
|                                         pattern.mCenter2.x, pattern.mCenter2.y, pattern.mRadius2);
 | |
| 
 | |
|       MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO);
 | |
|       GradientStopsCairo* cairoStops = static_cast<GradientStopsCairo*>(pattern.mStops.get());
 | |
|       cairo_pattern_set_extend(pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode()));
 | |
| 
 | |
|       matrix = &pattern.mMatrix;
 | |
| 
 | |
|       const std::vector<GradientStop>& stops = cairoStops->GetStops();
 | |
|       for (size_t i = 0; i < stops.size(); ++i) {
 | |
|         CairoPatternAddGradientStop(pat, stops[i]);
 | |
|       }
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|     default:
 | |
|     {
 | |
|       // We should support all pattern types!
 | |
|       MOZ_ASSERT(false);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // The pattern matrix is a matrix that transforms the pattern into user
 | |
|   // space. Cairo takes a matrix that converts from user space to pattern
 | |
|   // space. Cairo therefore needs the inverse.
 | |
|   if (matrix) {
 | |
|     cairo_matrix_t mat;
 | |
|     GfxMatrixToCairoMatrix(*matrix, mat);
 | |
|     cairo_matrix_invert(&mat);
 | |
|     cairo_pattern_set_matrix(pat, &mat);
 | |
|   }
 | |
| 
 | |
|   return pat;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| NeedIntermediateSurface(const Pattern& aPattern, const DrawOptions& aOptions)
 | |
| {
 | |
|   // We pre-multiply colours' alpha by the global alpha, so we don't need to
 | |
|   // use an intermediate surface for them.
 | |
|   if (aPattern.GetType() == PatternType::COLOR)
 | |
|     return false;
 | |
| 
 | |
|   if (aOptions.mAlpha == 1.0)
 | |
|     return false;
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| DrawTargetCairo::DrawTargetCairo()
 | |
|   : mContext(nullptr)
 | |
|   , mSurface(nullptr)
 | |
|   , mTransformSingular(false)
 | |
|   , mLockedBits(nullptr)
 | |
|   , mFontOptions(nullptr)
 | |
| {
 | |
| }
 | |
| 
 | |
| DrawTargetCairo::~DrawTargetCairo()
 | |
| {
 | |
|   cairo_destroy(mContext);
 | |
|   if (mSurface) {
 | |
|     cairo_surface_destroy(mSurface);
 | |
|     mSurface = nullptr;
 | |
|   }
 | |
|   if (mFontOptions) {
 | |
|     cairo_font_options_destroy(mFontOptions);
 | |
|     mFontOptions = nullptr;
 | |
|   }
 | |
|   MOZ_ASSERT(!mLockedBits);
 | |
| }
 | |
| 
 | |
| bool
 | |
| DrawTargetCairo::IsValid() const
 | |
| {
 | |
|   return mSurface && !cairo_surface_status(mSurface) &&
 | |
|          mContext && !cairo_surface_status(cairo_get_group_target(mContext));
 | |
| }
 | |
| 
 | |
| DrawTargetType
 | |
| DrawTargetCairo::GetType() const
 | |
| {
 | |
|   if (mContext) {
 | |
|     cairo_surface_type_t type = cairo_surface_get_type(mSurface);
 | |
|     if (type == CAIRO_SURFACE_TYPE_TEE) {
 | |
|       type = cairo_surface_get_type(cairo_tee_surface_index(mSurface, 0));
 | |
|       MOZ_ASSERT(type != CAIRO_SURFACE_TYPE_TEE, "C'mon!");
 | |
|       MOZ_ASSERT(type == cairo_surface_get_type(cairo_tee_surface_index(mSurface, 1)),
 | |
|                  "What should we do here?");
 | |
|     }
 | |
|     switch (type) {
 | |
|     case CAIRO_SURFACE_TYPE_PDF:
 | |
|     case CAIRO_SURFACE_TYPE_PS:
 | |
|     case CAIRO_SURFACE_TYPE_SVG:
 | |
|     case CAIRO_SURFACE_TYPE_WIN32_PRINTING:
 | |
|     case CAIRO_SURFACE_TYPE_XML:
 | |
|       return DrawTargetType::VECTOR;
 | |
| 
 | |
|     case CAIRO_SURFACE_TYPE_VG:
 | |
|     case CAIRO_SURFACE_TYPE_GL:
 | |
|     case CAIRO_SURFACE_TYPE_GLITZ:
 | |
|     case CAIRO_SURFACE_TYPE_QUARTZ:
 | |
|     case CAIRO_SURFACE_TYPE_DIRECTFB:
 | |
|       return DrawTargetType::HARDWARE_RASTER;
 | |
| 
 | |
|     case CAIRO_SURFACE_TYPE_SKIA:
 | |
|     case CAIRO_SURFACE_TYPE_QT:
 | |
|       MOZ_FALLTHROUGH_ASSERT("Can't determine actual DrawTargetType for DrawTargetCairo - assuming SOFTWARE_RASTER");
 | |
|     case CAIRO_SURFACE_TYPE_IMAGE:
 | |
|     case CAIRO_SURFACE_TYPE_XLIB:
 | |
|     case CAIRO_SURFACE_TYPE_XCB:
 | |
|     case CAIRO_SURFACE_TYPE_WIN32:
 | |
|     case CAIRO_SURFACE_TYPE_BEOS:
 | |
|     case CAIRO_SURFACE_TYPE_OS2:
 | |
|     case CAIRO_SURFACE_TYPE_QUARTZ_IMAGE:
 | |
|     case CAIRO_SURFACE_TYPE_SCRIPT:
 | |
|     case CAIRO_SURFACE_TYPE_RECORDING:
 | |
|     case CAIRO_SURFACE_TYPE_DRM:
 | |
|     case CAIRO_SURFACE_TYPE_SUBSURFACE:
 | |
|     case CAIRO_SURFACE_TYPE_TEE: // included to silence warning about unhandled enum value
 | |
|       return DrawTargetType::SOFTWARE_RASTER;
 | |
|     default:
 | |
|       MOZ_CRASH("GFX: Unsupported cairo surface type");
 | |
|     }
 | |
|   }
 | |
|   MOZ_ASSERT(false, "Could not determine DrawTargetType for DrawTargetCairo");
 | |
|   return DrawTargetType::SOFTWARE_RASTER;
 | |
| }
 | |
| 
 | |
| IntSize
 | |
| DrawTargetCairo::GetSize() const
 | |
| {
 | |
|   return mSize;
 | |
| }
 | |
| 
 | |
| SurfaceFormat
 | |
| GfxFormatForCairoSurface(cairo_surface_t* surface)
 | |
| {
 | |
|   cairo_surface_type_t type = cairo_surface_get_type(surface);
 | |
|   if (type == CAIRO_SURFACE_TYPE_IMAGE) {
 | |
|     return CairoFormatToGfxFormat(cairo_image_surface_get_format(surface));
 | |
|   }
 | |
| #ifdef CAIRO_HAS_XLIB_SURFACE
 | |
|   // xlib is currently the only Cairo backend that creates 16bpp surfaces
 | |
|   if (type == CAIRO_SURFACE_TYPE_XLIB &&
 | |
|       cairo_xlib_surface_get_depth(surface) == 16) {
 | |
|     return SurfaceFormat::R5G6B5_UINT16;
 | |
|   }
 | |
| #endif
 | |
|   return CairoContentToGfxFormat(cairo_surface_get_content(surface));
 | |
| }
 | |
| 
 | |
| already_AddRefed<SourceSurface>
 | |
| DrawTargetCairo::Snapshot()
 | |
| {
 | |
|   if (!IsValid()) {
 | |
|     gfxCriticalNote << "DrawTargetCairo::Snapshot with bad surface " << hexa(mSurface)
 | |
|                     << ", context " << hexa(mContext)
 | |
|                     << ", status " << (mSurface ? cairo_surface_status(mSurface) : -1);
 | |
|     return nullptr;
 | |
|   }
 | |
|   if (mSnapshot) {
 | |
|     RefPtr<SourceSurface> snapshot(mSnapshot);
 | |
|     return snapshot.forget();
 | |
|   }
 | |
| 
 | |
|   IntSize size = GetSize();
 | |
| 
 | |
|   mSnapshot = new SourceSurfaceCairo(mSurface,
 | |
|                                      size,
 | |
|                                      GfxFormatForCairoSurface(mSurface),
 | |
|                                      this);
 | |
|   RefPtr<SourceSurface> snapshot(mSnapshot);
 | |
|   return snapshot.forget();
 | |
| }
 | |
| 
 | |
| bool
 | |
| DrawTargetCairo::LockBits(uint8_t** aData, IntSize* aSize,
 | |
|                           int32_t* aStride, SurfaceFormat* aFormat,
 | |
|                           IntPoint* aOrigin)
 | |
| {
 | |
|   cairo_surface_t* target = cairo_get_group_target(mContext);
 | |
|   cairo_surface_t* surf = target;
 | |
| #ifdef CAIRO_HAS_WIN32_SURFACE
 | |
|   if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_WIN32) {
 | |
|     cairo_surface_t* imgsurf = cairo_win32_surface_get_image(surf);
 | |
|     if (imgsurf) {
 | |
|       surf = imgsurf;
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
|   if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_IMAGE &&
 | |
|       cairo_surface_status(surf) == CAIRO_STATUS_SUCCESS) {
 | |
|     PointDouble offset;
 | |
|     cairo_surface_get_device_offset(target, &offset.x, &offset.y);
 | |
|     // verify the device offset can be converted to integers suitable for a bounds rect
 | |
|     IntPoint origin(int32_t(-offset.x), int32_t(-offset.y));
 | |
|     if (-PointDouble(origin) != offset ||
 | |
|         (!aOrigin && origin != IntPoint())) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     WillChange();
 | |
|     Flush();
 | |
| 
 | |
|     mLockedBits = cairo_image_surface_get_data(surf);
 | |
|     *aData = mLockedBits;
 | |
|     *aSize = IntSize(cairo_image_surface_get_width(surf),
 | |
|                      cairo_image_surface_get_height(surf));
 | |
|     *aStride = cairo_image_surface_get_stride(surf);
 | |
|     *aFormat = CairoFormatToGfxFormat(cairo_image_surface_get_format(surf));
 | |
|     if (aOrigin) {
 | |
|       *aOrigin = origin;
 | |
|     }
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::ReleaseBits(uint8_t* aData)
 | |
| {
 | |
|   MOZ_ASSERT(mLockedBits == aData);
 | |
|   mLockedBits = nullptr;
 | |
|   cairo_surface_t* surf = cairo_get_group_target(mContext);
 | |
| #ifdef CAIRO_HAS_WIN32_SURFACE
 | |
|   if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_WIN32) {
 | |
|     cairo_surface_t* imgsurf = cairo_win32_surface_get_image(surf);
 | |
|     if (imgsurf) {
 | |
|       cairo_surface_mark_dirty(imgsurf);
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
|   cairo_surface_mark_dirty(surf);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::Flush()
 | |
| {
 | |
|   cairo_surface_t* surf = cairo_get_group_target(mContext);
 | |
|   cairo_surface_flush(surf);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::PrepareForDrawing(cairo_t* aContext, const Path* aPath /* = nullptr */)
 | |
| {
 | |
|   WillChange(aPath);
 | |
| }
 | |
| 
 | |
| cairo_surface_t*
 | |
| DrawTargetCairo::GetDummySurface()
 | |
| {
 | |
|   if (mDummySurface) {
 | |
|     return mDummySurface;
 | |
|   }
 | |
| 
 | |
|   mDummySurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
 | |
| 
 | |
|   return mDummySurface;
 | |
| }
 | |
| 
 | |
| static void
 | |
| PaintWithAlpha(cairo_t* aContext, const DrawOptions& aOptions)
 | |
| {
 | |
|   if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE) {
 | |
|     // Cairo treats the source operator like a lerp when alpha is < 1.
 | |
|     // Approximate the desired operator by: out = 0; out += src*alpha;
 | |
|     if (aOptions.mAlpha == 1) {
 | |
|       cairo_set_operator(aContext, CAIRO_OPERATOR_SOURCE);
 | |
|       cairo_paint(aContext);
 | |
|     } else {
 | |
|       cairo_set_operator(aContext, CAIRO_OPERATOR_CLEAR);
 | |
|       cairo_paint(aContext);
 | |
|       cairo_set_operator(aContext, CAIRO_OPERATOR_ADD);
 | |
|       cairo_paint_with_alpha(aContext, aOptions.mAlpha);
 | |
|     }
 | |
|   } else {
 | |
|     cairo_set_operator(aContext, GfxOpToCairoOp(aOptions.mCompositionOp));
 | |
|     cairo_paint_with_alpha(aContext, aOptions.mAlpha);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::DrawSurface(SourceSurface *aSurface,
 | |
|                              const Rect &aDest,
 | |
|                              const Rect &aSource,
 | |
|                              const DrawSurfaceOptions &aSurfOptions,
 | |
|                              const DrawOptions &aOptions)
 | |
| {
 | |
|   if (mTransformSingular || aDest.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!IsValid() || !aSurface) {
 | |
|     gfxCriticalNote << "DrawSurface with bad surface " << cairo_surface_status(cairo_get_group_target(mContext));
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoPrepareForDrawing prep(this, mContext);
 | |
|   AutoClearDeviceOffset clear(aSurface);
 | |
| 
 | |
|   float sx = aSource.Width() / aDest.Width();
 | |
|   float sy = aSource.Height() / aDest.Height();
 | |
| 
 | |
|   cairo_matrix_t src_mat;
 | |
|   cairo_matrix_init_translate(&src_mat, aSource.X(), aSource.Y());
 | |
|   cairo_matrix_scale(&src_mat, sx, sy);
 | |
| 
 | |
|   cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aSurface);
 | |
|   if (!surf) {
 | |
|     gfxWarning() << "Failed to create cairo surface for DrawTargetCairo::DrawSurface";
 | |
|     return;
 | |
|   }
 | |
|   cairo_pattern_t* pat = cairo_pattern_create_for_surface(surf);
 | |
|   cairo_surface_destroy(surf);
 | |
| 
 | |
|   cairo_pattern_set_matrix(pat, &src_mat);
 | |
|   cairo_pattern_set_filter(pat, GfxSamplingFilterToCairoFilter(aSurfOptions.mSamplingFilter));
 | |
|   cairo_pattern_set_extend(pat, CAIRO_EXTEND_PAD);
 | |
| 
 | |
|   cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
 | |
| 
 | |
|   // If the destination rect covers the entire clipped area, then unbounded and bounded
 | |
|   // operations are identical, and we don't need to push a group.
 | |
|   bool needsGroup = !IsOperatorBoundByMask(aOptions.mCompositionOp) &&
 | |
|                     !aDest.Contains(GetUserSpaceClip());
 | |
| 
 | |
|   cairo_translate(mContext, aDest.X(), aDest.Y());
 | |
| 
 | |
|   if (needsGroup) {
 | |
|     cairo_push_group(mContext);
 | |
|       cairo_new_path(mContext);
 | |
|       cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height());
 | |
|       cairo_set_source(mContext, pat);
 | |
|       cairo_fill(mContext);
 | |
|     cairo_pop_group_to_source(mContext);
 | |
|   } else {
 | |
|     cairo_new_path(mContext);
 | |
|     cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height());
 | |
|     cairo_clip(mContext);
 | |
|     cairo_set_source(mContext, pat);
 | |
|   }
 | |
| 
 | |
|   PaintWithAlpha(mContext, aOptions);
 | |
| 
 | |
|   cairo_pattern_destroy(pat);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::DrawFilter(FilterNode *aNode,
 | |
|                             const Rect &aSourceRect,
 | |
|                             const Point &aDestPoint,
 | |
|                             const DrawOptions &aOptions)
 | |
| {
 | |
|   FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode);
 | |
|   filter->Draw(this, aSourceRect, aDestPoint, aOptions);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::DrawSurfaceWithShadow(SourceSurface *aSurface,
 | |
|                                        const Point &aDest,
 | |
|                                        const Color &aColor,
 | |
|                                        const Point &aOffset,
 | |
|                                        Float aSigma,
 | |
|                                        CompositionOp aOperator)
 | |
| {
 | |
|   if (aSurface->GetType() != SurfaceType::CAIRO) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoClearDeviceOffset clear(aSurface);
 | |
| 
 | |
|   Float width = Float(aSurface->GetSize().width);
 | |
|   Float height = Float(aSurface->GetSize().height);
 | |
| 
 | |
|   SourceSurfaceCairo* source = static_cast<SourceSurfaceCairo*>(aSurface);
 | |
|   cairo_surface_t* sourcesurf = source->GetSurface();
 | |
|   cairo_surface_t* blursurf;
 | |
|   cairo_surface_t* surf;
 | |
| 
 | |
|   // We only use the A8 surface for blurred shadows. Unblurred shadows can just
 | |
|   // use the RGBA surface directly.
 | |
|   if (cairo_surface_get_type(sourcesurf) == CAIRO_SURFACE_TYPE_TEE) {
 | |
|     blursurf = cairo_tee_surface_index(sourcesurf, 0);
 | |
|     surf = cairo_tee_surface_index(sourcesurf, 1);
 | |
|   } else {
 | |
|     blursurf = sourcesurf;
 | |
|     surf = sourcesurf;
 | |
|   }
 | |
| 
 | |
|   if (aSigma != 0.0f) {
 | |
|     MOZ_ASSERT(cairo_surface_get_type(blursurf) == CAIRO_SURFACE_TYPE_IMAGE);
 | |
|     Rect extents(0, 0, width, height);
 | |
|     AlphaBoxBlur blur(extents,
 | |
|                       cairo_image_surface_get_stride(blursurf),
 | |
|                       aSigma, aSigma);
 | |
|     blur.Blur(cairo_image_surface_get_data(blursurf));
 | |
|   }
 | |
| 
 | |
|   WillChange();
 | |
|   ClearSurfaceForUnboundedSource(aOperator);
 | |
| 
 | |
|   cairo_save(mContext);
 | |
|   cairo_set_operator(mContext, GfxOpToCairoOp(aOperator));
 | |
|   cairo_identity_matrix(mContext);
 | |
|   cairo_translate(mContext, aDest.x, aDest.y);
 | |
| 
 | |
|   bool needsGroup = !IsOperatorBoundByMask(aOperator);
 | |
|   if (needsGroup) {
 | |
|     cairo_push_group(mContext);
 | |
|   }
 | |
| 
 | |
|   cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a);
 | |
|   cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y);
 | |
| 
 | |
|   if (blursurf != surf ||
 | |
|       aSurface->GetFormat() != SurfaceFormat::A8) {
 | |
|     // Now that the shadow has been drawn, we can draw the surface on top.
 | |
|     cairo_set_source_surface(mContext, surf, 0, 0);
 | |
|     cairo_new_path(mContext);
 | |
|     cairo_rectangle(mContext, 0, 0, width, height);
 | |
|     cairo_fill(mContext);
 | |
|   }
 | |
| 
 | |
|   if (needsGroup) {
 | |
|     cairo_pop_group_to_source(mContext);
 | |
|     cairo_paint(mContext);
 | |
|   }
 | |
| 
 | |
|   cairo_restore(mContext);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::DrawPattern(const Pattern& aPattern,
 | |
|                              const StrokeOptions& aStrokeOptions,
 | |
|                              const DrawOptions& aOptions,
 | |
|                              DrawPatternType aDrawType,
 | |
|                              bool aPathBoundsClip)
 | |
| {
 | |
|   if (!PatternIsCompatible(aPattern)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoClearDeviceOffset clear(aPattern);
 | |
| 
 | |
|   cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform());
 | |
|   if (!pat) {
 | |
|     return;
 | |
|   }
 | |
|   if (cairo_pattern_status(pat)) {
 | |
|     cairo_pattern_destroy(pat);
 | |
|     gfxWarning() << "Invalid pattern";
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   cairo_set_source(mContext, pat);
 | |
| 
 | |
|   cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
 | |
| 
 | |
|   if (NeedIntermediateSurface(aPattern, aOptions) ||
 | |
|       (!IsOperatorBoundByMask(aOptions.mCompositionOp) && !aPathBoundsClip)) {
 | |
|     cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA);
 | |
| 
 | |
|     // Don't want operators to be applied twice
 | |
|     cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
 | |
| 
 | |
|     if (aDrawType == DRAW_STROKE) {
 | |
|       SetCairoStrokeOptions(mContext, aStrokeOptions);
 | |
|       cairo_stroke_preserve(mContext);
 | |
|     } else {
 | |
|       cairo_fill_preserve(mContext);
 | |
|     }
 | |
| 
 | |
|     cairo_pop_group_to_source(mContext);
 | |
| 
 | |
|     // Now draw the content using the desired operator
 | |
|     PaintWithAlpha(mContext, aOptions);
 | |
|   } else {
 | |
|     cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
 | |
| 
 | |
|     if (aDrawType == DRAW_STROKE) {
 | |
|       SetCairoStrokeOptions(mContext, aStrokeOptions);
 | |
|       cairo_stroke_preserve(mContext);
 | |
|     } else {
 | |
|       cairo_fill_preserve(mContext);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   cairo_pattern_destroy(pat);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::FillRect(const Rect &aRect,
 | |
|                           const Pattern &aPattern,
 | |
|                           const DrawOptions &aOptions)
 | |
| {
 | |
|   if (mTransformSingular) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoPrepareForDrawing prep(this, mContext);
 | |
| 
 | |
|   bool restoreTransform = false;
 | |
|   Matrix mat;
 | |
|   Rect r = aRect;
 | |
| 
 | |
|   /* Clamp coordinates to work around a design bug in cairo */
 | |
|   if (r.Width() > CAIRO_COORD_MAX ||
 | |
|       r.Height() > CAIRO_COORD_MAX ||
 | |
|       r.X() < -CAIRO_COORD_MAX ||
 | |
|       r.X() > CAIRO_COORD_MAX ||
 | |
|       r.Y() < -CAIRO_COORD_MAX ||
 | |
|       r.Y() > CAIRO_COORD_MAX)
 | |
|   {
 | |
|     if (!mat.IsRectilinear()) {
 | |
|       gfxWarning() << "DrawTargetCairo::FillRect() misdrawing huge Rect "
 | |
|                       "with non-rectilinear transform";
 | |
|     }
 | |
| 
 | |
|     mat = GetTransform();
 | |
|     r = mat.TransformBounds(r);
 | |
| 
 | |
|     if (!ConditionRect(r)) {
 | |
|       gfxWarning() << "Ignoring DrawTargetCairo::FillRect() call with "
 | |
|                       "out-of-bounds Rect";
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     restoreTransform = true;
 | |
|     SetTransform(Matrix());
 | |
|   }
 | |
| 
 | |
|   cairo_new_path(mContext);
 | |
|   cairo_rectangle(mContext, r.X(), r.Y(), r.Width(), r.Height());
 | |
| 
 | |
|   bool pathBoundsClip = false;
 | |
| 
 | |
|   if (r.Contains(GetUserSpaceClip())) {
 | |
|     pathBoundsClip = true;
 | |
|   }
 | |
| 
 | |
|   DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL, pathBoundsClip);
 | |
| 
 | |
|   if (restoreTransform) {
 | |
|     SetTransform(mat);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::CopySurfaceInternal(cairo_surface_t* aSurface,
 | |
|                                      const IntRect &aSource,
 | |
|                                      const IntPoint &aDest)
 | |
| {
 | |
|   if (cairo_surface_status(aSurface)) {
 | |
|     gfxWarning() << "Invalid surface" << cairo_surface_status(aSurface);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   cairo_identity_matrix(mContext);
 | |
| 
 | |
|   cairo_set_source_surface(mContext, aSurface, aDest.x - aSource.X(), aDest.y - aSource.Y());
 | |
|   cairo_set_operator(mContext, CAIRO_OPERATOR_SOURCE);
 | |
|   cairo_set_antialias(mContext, CAIRO_ANTIALIAS_NONE);
 | |
| 
 | |
|   cairo_reset_clip(mContext);
 | |
|   cairo_new_path(mContext);
 | |
|   cairo_rectangle(mContext, aDest.x, aDest.y, aSource.Width(), aSource.Height());
 | |
|   cairo_fill(mContext);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::CopySurface(SourceSurface *aSurface,
 | |
|                              const IntRect &aSource,
 | |
|                              const IntPoint &aDest)
 | |
| {
 | |
|   if (mTransformSingular) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoPrepareForDrawing prep(this, mContext);
 | |
|   AutoClearDeviceOffset clear(aSurface);
 | |
| 
 | |
|   if (!aSurface) {
 | |
|     gfxWarning() << "Unsupported surface type specified";
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aSurface);
 | |
|   if (!surf) {
 | |
|     gfxWarning() << "Unsupported surface type specified";
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   CopySurfaceInternal(surf, aSource, aDest);
 | |
|   cairo_surface_destroy(surf);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::CopyRect(const IntRect &aSource,
 | |
|                           const IntPoint &aDest)
 | |
| {
 | |
|   if (mTransformSingular) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoPrepareForDrawing prep(this, mContext);
 | |
| 
 | |
|   IntRect source = aSource;
 | |
|   cairo_surface_t* surf = mSurface;
 | |
| 
 | |
|   if (!SupportsSelfCopy(mSurface) &&
 | |
|       aSource.ContainsY(aDest.y)) {
 | |
|     cairo_surface_t* similar = cairo_surface_create_similar(mSurface,
 | |
|                                                             GfxFormatToCairoContent(GetFormat()),
 | |
|                                                             aSource.Width(), aSource.Height());
 | |
|     cairo_t* ctx = cairo_create(similar);
 | |
|     cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
 | |
|     cairo_set_source_surface(ctx, surf, -aSource.X(), -aSource.Y());
 | |
|     cairo_paint(ctx);
 | |
|     cairo_destroy(ctx);
 | |
| 
 | |
|     source.MoveTo(0, 0);
 | |
|     surf = similar;
 | |
|   }
 | |
| 
 | |
|   CopySurfaceInternal(surf, source, aDest);
 | |
| 
 | |
|   if (surf != mSurface) {
 | |
|     cairo_surface_destroy(surf);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::ClearRect(const Rect& aRect)
 | |
| {
 | |
|   if (mTransformSingular) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoPrepareForDrawing prep(this, mContext);
 | |
| 
 | |
|   if (!mContext || aRect.Width() < 0 || aRect.Height() < 0 ||
 | |
|       !IsFinite(aRect.X()) || !IsFinite(aRect.Width()) ||
 | |
|       !IsFinite(aRect.Y()) || !IsFinite(aRect.Height())) {
 | |
|     gfxCriticalNote << "ClearRect with invalid argument " << gfx::hexa(mContext) << " with " << aRect.Width() << "x" << aRect.Height() << " [" << aRect.X() << ", " << aRect.Y() << "]";
 | |
|   }
 | |
| 
 | |
|   cairo_set_antialias(mContext, CAIRO_ANTIALIAS_NONE);
 | |
|   cairo_new_path(mContext);
 | |
|   cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR);
 | |
|   cairo_rectangle(mContext, aRect.X(), aRect.Y(),
 | |
|                   aRect.Width(), aRect.Height());
 | |
|   cairo_fill(mContext);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::StrokeRect(const Rect &aRect,
 | |
|                             const Pattern &aPattern,
 | |
|                             const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
 | |
|                             const DrawOptions &aOptions /* = DrawOptions() */)
 | |
| {
 | |
|   if (mTransformSingular) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoPrepareForDrawing prep(this, mContext);
 | |
| 
 | |
|   cairo_new_path(mContext);
 | |
|   cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
 | |
| 
 | |
|   DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::StrokeLine(const Point &aStart,
 | |
|                             const Point &aEnd,
 | |
|                             const Pattern &aPattern,
 | |
|                             const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
 | |
|                             const DrawOptions &aOptions /* = DrawOptions() */)
 | |
| {
 | |
|   if (mTransformSingular) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoPrepareForDrawing prep(this, mContext);
 | |
| 
 | |
|   cairo_new_path(mContext);
 | |
|   cairo_move_to(mContext, aStart.x, aStart.y);
 | |
|   cairo_line_to(mContext, aEnd.x, aEnd.y);
 | |
| 
 | |
|   DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::Stroke(const Path *aPath,
 | |
|                         const Pattern &aPattern,
 | |
|                         const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
 | |
|                         const DrawOptions &aOptions /* = DrawOptions() */)
 | |
| {
 | |
|   if (mTransformSingular) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoPrepareForDrawing prep(this, mContext, aPath);
 | |
| 
 | |
|   if (aPath->GetBackendType() != BackendType::CAIRO)
 | |
|     return;
 | |
| 
 | |
|   PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
 | |
|   path->SetPathOnContext(mContext);
 | |
| 
 | |
|   DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::Fill(const Path *aPath,
 | |
|                       const Pattern &aPattern,
 | |
|                       const DrawOptions &aOptions /* = DrawOptions() */)
 | |
| {
 | |
|   if (mTransformSingular) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoPrepareForDrawing prep(this, mContext, aPath);
 | |
| 
 | |
|   if (aPath->GetBackendType() != BackendType::CAIRO)
 | |
|     return;
 | |
| 
 | |
|   PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
 | |
|   path->SetPathOnContext(mContext);
 | |
| 
 | |
|   DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL);
 | |
| }
 | |
| 
 | |
| bool
 | |
| DrawTargetCairo::IsCurrentGroupOpaque()
 | |
| {
 | |
|   cairo_surface_t* surf = cairo_get_group_target(mContext);
 | |
| 
 | |
|   if (!surf) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return cairo_surface_get_content(surf) == CAIRO_CONTENT_COLOR;
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::SetFontOptions()
 | |
| {
 | |
|   //   This will attempt to detect if the currently set scaled font on the
 | |
|   // context has enabled subpixel AA. If it is not permitted, then it will
 | |
|   // downgrade to grayscale AA.
 | |
|   //   This only currently works effectively for the cairo-ft backend relative
 | |
|   // to system defaults, as only cairo-ft reflect system defaults in the scaled
 | |
|   // font state. However, this will work for cairo-ft on both tree Cairo and
 | |
|   // system Cairo.
 | |
|   //   Other backends leave the CAIRO_ANTIALIAS_DEFAULT setting untouched while
 | |
|   // potentially interpreting it as subpixel or even other types of AA that
 | |
|   // can't be safely equivocated with grayscale AA. For this reason we don't
 | |
|   // try to also detect and modify the default AA setting, only explicit
 | |
|   // subpixel AA. These other backends must instead rely on tree Cairo's
 | |
|   // cairo_surface_set_subpixel_antialiasing extension.
 | |
| 
 | |
|   // If allowing subpixel AA, then leave Cairo's default AA state.
 | |
|   if (mPermitSubpixelAA) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!mFontOptions) {
 | |
|     mFontOptions = cairo_font_options_create();
 | |
|     if (!mFontOptions) {
 | |
|       gfxWarning() << "Failed allocating Cairo font options";
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If the current font requests subpixel AA, force it to gray since we don't
 | |
|   // allow subpixel AA.
 | |
|   cairo_get_font_options(mContext, mFontOptions);
 | |
|   cairo_antialias_t antialias = cairo_font_options_get_antialias(mFontOptions);
 | |
|   if (antialias == CAIRO_ANTIALIAS_SUBPIXEL) {
 | |
|     cairo_font_options_set_antialias(mFontOptions, CAIRO_ANTIALIAS_GRAY);
 | |
|     cairo_set_font_options(mContext, mFontOptions);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::SetPermitSubpixelAA(bool aPermitSubpixelAA)
 | |
| {
 | |
|   DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA);
 | |
| #ifdef MOZ_TREE_CAIRO
 | |
|   cairo_surface_set_subpixel_antialiasing(cairo_get_group_target(mContext),
 | |
|     aPermitSubpixelAA ? CAIRO_SUBPIXEL_ANTIALIASING_ENABLED : CAIRO_SUBPIXEL_ANTIALIASING_DISABLED);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| static bool
 | |
| SupportsVariationSettings(cairo_surface_t* surface)
 | |
| {
 | |
|   switch (cairo_surface_get_type(surface))
 | |
|   {
 | |
|     case CAIRO_SURFACE_TYPE_PDF:
 | |
|     case CAIRO_SURFACE_TYPE_PS:
 | |
|       return false;
 | |
|     default:
 | |
|       return true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::FillGlyphs(ScaledFont *aFont,
 | |
|                             const GlyphBuffer &aBuffer,
 | |
|                             const Pattern &aPattern,
 | |
|                             const DrawOptions &aOptions)
 | |
| {
 | |
|   if (mTransformSingular) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!IsValid()) {
 | |
|     gfxDebug() << "FillGlyphs bad surface " << cairo_surface_status(cairo_get_group_target(mContext));
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!aFont) {
 | |
|     gfxDevCrash(LogReason::InvalidFont) << "Invalid scaled font";
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoPrepareForDrawing prep(this, mContext);
 | |
|   AutoClearDeviceOffset clear(aPattern);
 | |
| 
 | |
|   ScaledFontBase* scaledFont = static_cast<ScaledFontBase*>(aFont);
 | |
|   cairo_set_scaled_font(mContext, scaledFont->GetCairoScaledFont());
 | |
| 
 | |
|   cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform());
 | |
|   if (!pat)
 | |
|     return;
 | |
| 
 | |
|   cairo_set_source(mContext, pat);
 | |
|   cairo_pattern_destroy(pat);
 | |
| 
 | |
|   cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
 | |
| 
 | |
|   // Override any font-specific options as necessary.
 | |
|   SetFontOptions();
 | |
| 
 | |
|   // Convert our GlyphBuffer into a vector of Cairo glyphs. This code can
 | |
|   // execute millions of times in short periods, so we want to avoid heap
 | |
|   // allocation whenever possible. So we use an inline vector capacity of 1024
 | |
|   // bytes (the maximum allowed by mozilla::Vector), which gives an inline
 | |
|   // length of 1024 / 24 = 42 elements, which is enough to typically avoid heap
 | |
|   // allocation in ~99% of cases.
 | |
|   Vector<cairo_glyph_t, 1024 / sizeof(cairo_glyph_t)> glyphs;
 | |
|   if (!glyphs.resizeUninitialized(aBuffer.mNumGlyphs)) {
 | |
|     gfxDevCrash(LogReason::GlyphAllocFailedCairo) << "glyphs allocation failed";
 | |
|     return;
 | |
|   }
 | |
|   for (uint32_t i = 0; i < aBuffer.mNumGlyphs; ++i) {
 | |
|     glyphs[i].index = aBuffer.mGlyphs[i].mIndex;
 | |
|     glyphs[i].x = aBuffer.mGlyphs[i].mPosition.x;
 | |
|     glyphs[i].y = aBuffer.mGlyphs[i].mPosition.y;
 | |
|   }
 | |
| 
 | |
|   if (!SupportsVariationSettings(mSurface) &&
 | |
|       aFont->HasVariationSettings() &&
 | |
|       gfxPrefs::PrintFontVariationsAsPaths()) {
 | |
|     cairo_set_fill_rule(mContext, CAIRO_FILL_RULE_WINDING);
 | |
|     cairo_new_path(mContext);
 | |
|     cairo_glyph_path(mContext, &glyphs[0], aBuffer.mNumGlyphs);
 | |
|     cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
 | |
|     cairo_fill(mContext);
 | |
|   } else {
 | |
|     cairo_show_glyphs(mContext, &glyphs[0], aBuffer.mNumGlyphs);
 | |
|   }
 | |
| 
 | |
|   if (cairo_surface_status(cairo_get_group_target(mContext))) {
 | |
|     gfxDebug() << "Ending FillGlyphs with a bad surface " << cairo_surface_status(cairo_get_group_target(mContext));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::Mask(const Pattern &aSource,
 | |
|                       const Pattern &aMask,
 | |
|                       const DrawOptions &aOptions /* = DrawOptions() */)
 | |
| {
 | |
|   if (mTransformSingular) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoPrepareForDrawing prep(this, mContext);
 | |
|   AutoClearDeviceOffset clearSource(aSource);
 | |
|   AutoClearDeviceOffset clearMask(aMask);
 | |
| 
 | |
|   cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
 | |
| 
 | |
|   cairo_pattern_t* source = GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform());
 | |
|   if (!source) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   cairo_pattern_t* mask = GfxPatternToCairoPattern(aMask, aOptions.mAlpha, GetTransform());
 | |
|   if (!mask) {
 | |
|     cairo_pattern_destroy(source);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (cairo_pattern_status(source) || cairo_pattern_status(mask)) {
 | |
|     cairo_pattern_destroy(source);
 | |
|     cairo_pattern_destroy(mask);
 | |
|     gfxWarning() << "Invalid pattern";
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   cairo_set_source(mContext, source);
 | |
|   cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
 | |
|   cairo_mask(mContext, mask);
 | |
| 
 | |
|   cairo_pattern_destroy(mask);
 | |
|   cairo_pattern_destroy(source);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::MaskSurface(const Pattern &aSource,
 | |
|                              SourceSurface *aMask,
 | |
|                              Point aOffset,
 | |
|                              const DrawOptions &aOptions)
 | |
| {
 | |
|   if (mTransformSingular) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoPrepareForDrawing prep(this, mContext);
 | |
|   AutoClearDeviceOffset clearSource(aSource);
 | |
|   AutoClearDeviceOffset clearMask(aMask);
 | |
| 
 | |
|   if (!PatternIsCompatible(aSource)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
 | |
| 
 | |
|   cairo_pattern_t* pat = GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform());
 | |
|   if (!pat) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (cairo_pattern_status(pat)) {
 | |
|     cairo_pattern_destroy(pat);
 | |
|     gfxWarning() << "Invalid pattern";
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   cairo_set_source(mContext, pat);
 | |
| 
 | |
|   if (NeedIntermediateSurface(aSource, aOptions)) {
 | |
|     cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA);
 | |
| 
 | |
|     // Don't want operators to be applied twice
 | |
|     cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
 | |
| 
 | |
|     // Now draw the content using the desired operator
 | |
|     cairo_paint_with_alpha(mContext, aOptions.mAlpha);
 | |
| 
 | |
|     cairo_pop_group_to_source(mContext);
 | |
|   }
 | |
| 
 | |
|   cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aMask);
 | |
|   if (!surf) {
 | |
|     cairo_pattern_destroy(pat);
 | |
|     return;
 | |
|   }
 | |
|   cairo_pattern_t* mask = cairo_pattern_create_for_surface(surf);
 | |
|   cairo_matrix_t matrix;
 | |
| 
 | |
|   cairo_matrix_init_translate (&matrix, -aOffset.x, -aOffset.y);
 | |
|   cairo_pattern_set_matrix (mask, &matrix);
 | |
| 
 | |
|   cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
 | |
| 
 | |
|   cairo_mask(mContext, mask);
 | |
| 
 | |
|   cairo_surface_destroy(surf);
 | |
|   cairo_pattern_destroy(mask);
 | |
|   cairo_pattern_destroy(pat);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::PushClip(const Path *aPath)
 | |
| {
 | |
|   if (aPath->GetBackendType() != BackendType::CAIRO) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   WillChange(aPath);
 | |
|   cairo_save(mContext);
 | |
| 
 | |
|   PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
 | |
| 
 | |
|   if (mTransformSingular) {
 | |
|     cairo_new_path(mContext);
 | |
|     cairo_rectangle(mContext, 0, 0, 0, 0);
 | |
|   } else {
 | |
|     path->SetPathOnContext(mContext);
 | |
|   }
 | |
|   cairo_clip_preserve(mContext);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::PushClipRect(const Rect& aRect)
 | |
| {
 | |
|   WillChange();
 | |
|   cairo_save(mContext);
 | |
| 
 | |
|   cairo_new_path(mContext);
 | |
|   if (mTransformSingular) {
 | |
|     cairo_rectangle(mContext, 0, 0, 0, 0);
 | |
|   } else {
 | |
|     cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
 | |
|   }
 | |
|   cairo_clip_preserve(mContext);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::PopClip()
 | |
| {
 | |
|   // save/restore does not affect the path, so no need to call WillChange()
 | |
| 
 | |
|   // cairo_restore will restore the transform too and we don't want to do that
 | |
|   // so we'll save it now and restore it after the cairo_restore
 | |
|   cairo_matrix_t mat;
 | |
|   cairo_get_matrix(mContext, &mat);
 | |
| 
 | |
|   cairo_restore(mContext);
 | |
| 
 | |
|   cairo_set_matrix(mContext, &mat);
 | |
| }
 | |
|  
 | |
| void
 | |
| DrawTargetCairo::PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask,
 | |
|                           const Matrix& aMaskTransform, const IntRect& aBounds,
 | |
|                           bool aCopyBackground)
 | |
| {
 | |
|   cairo_content_t content = CAIRO_CONTENT_COLOR_ALPHA;
 | |
| 
 | |
|   if (mFormat == SurfaceFormat::A8) {
 | |
|     content = CAIRO_CONTENT_ALPHA;
 | |
|   } else if (aOpaque) {
 | |
|     content = CAIRO_CONTENT_COLOR;
 | |
|   }
 | |
| 
 | |
|   if (aCopyBackground) {
 | |
|     cairo_surface_t* source = cairo_get_group_target(mContext);
 | |
|     cairo_push_group_with_content(mContext, content);
 | |
|     cairo_surface_t* dest = cairo_get_group_target(mContext);
 | |
|     cairo_t* ctx = cairo_create(dest);
 | |
|     cairo_set_source_surface(ctx, source, 0, 0);
 | |
|     cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
 | |
|     cairo_paint(ctx);
 | |
|     cairo_destroy(ctx);
 | |
|   } else {
 | |
|     cairo_push_group_with_content(mContext, content);
 | |
|   }
 | |
| 
 | |
|   PushedLayer layer(aOpacity, mPermitSubpixelAA);
 | |
| 
 | |
|   if (aMask) {
 | |
|     cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aMask);
 | |
|     if (surf) {
 | |
|       layer.mMaskPattern = cairo_pattern_create_for_surface(surf);
 | |
|       cairo_matrix_t mat;
 | |
|       GfxMatrixToCairoMatrix(aMaskTransform, mat);
 | |
|       cairo_matrix_invert(&mat);
 | |
|       cairo_pattern_set_matrix(layer.mMaskPattern, &mat);
 | |
|       cairo_surface_destroy(surf);
 | |
|     } else {
 | |
|       gfxCriticalError() << "Failed to get cairo surface for mask surface!";
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mPushedLayers.push_back(layer);
 | |
| 
 | |
|   SetPermitSubpixelAA(aOpaque);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::PopLayer()
 | |
| {
 | |
|   MOZ_ASSERT(mPushedLayers.size());
 | |
| 
 | |
|   cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
 | |
| 
 | |
|   cairo_pop_group_to_source(mContext);
 | |
| 
 | |
|   PushedLayer layer = mPushedLayers.back();
 | |
|   mPushedLayers.pop_back();
 | |
| 
 | |
|   if (!layer.mMaskPattern) {
 | |
|     cairo_paint_with_alpha(mContext, layer.mOpacity);
 | |
|   } else {
 | |
|     if (layer.mOpacity != Float(1.0)) {
 | |
|       cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA);
 | |
| 
 | |
|       // Now draw the content using the desired operator
 | |
|       cairo_paint_with_alpha(mContext, layer.mOpacity);
 | |
| 
 | |
|       cairo_pop_group_to_source(mContext);
 | |
|     }
 | |
|     cairo_mask(mContext, layer.mMaskPattern);
 | |
|   }
 | |
| 
 | |
|   cairo_matrix_t mat;
 | |
|   GfxMatrixToCairoMatrix(mTransform, mat);
 | |
|   cairo_set_matrix(mContext, &mat);
 | |
| 
 | |
|   cairo_pattern_destroy(layer.mMaskPattern);
 | |
|   SetPermitSubpixelAA(layer.mWasPermittingSubpixelAA);
 | |
| }
 | |
| 
 | |
| already_AddRefed<PathBuilder>
 | |
| DrawTargetCairo::CreatePathBuilder(FillRule aFillRule /* = FillRule::FILL_WINDING */) const
 | |
| {
 | |
|   return MakeAndAddRef<PathBuilderCairo>(aFillRule);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::ClearSurfaceForUnboundedSource(const CompositionOp &aOperator)
 | |
| {
 | |
|   if (aOperator != CompositionOp::OP_SOURCE)
 | |
|     return;
 | |
|   cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR);
 | |
|   // It doesn't really matter what the source is here, since Paint
 | |
|   // isn't bounded by the source and the mask covers the entire clip
 | |
|   // region.
 | |
|   cairo_paint(mContext);
 | |
| }
 | |
| 
 | |
| 
 | |
| already_AddRefed<GradientStops>
 | |
| DrawTargetCairo::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops,
 | |
|                                      ExtendMode aExtendMode) const
 | |
| {
 | |
|   return MakeAndAddRef<GradientStopsCairo>(aStops, aNumStops, aExtendMode);
 | |
| }
 | |
| 
 | |
| already_AddRefed<FilterNode>
 | |
| DrawTargetCairo::CreateFilter(FilterType aType)
 | |
| {
 | |
|   return FilterNodeSoftware::Create(aType);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::GetGlyphRasterizationMetrics(ScaledFont *aScaledFont, const uint16_t* aGlyphIndices,
 | |
|                                               uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics)
 | |
| {
 | |
|   for (uint32_t i = 0; i < aNumGlyphs; i++) {
 | |
|     cairo_glyph_t glyph;
 | |
|     cairo_text_extents_t extents;
 | |
|     glyph.index = aGlyphIndices[i];
 | |
|     glyph.x = 0;
 | |
|     glyph.y = 0;
 | |
|     cairo_glyph_extents(mContext, &glyph, 1, &extents);
 | |
| 
 | |
|     aGlyphMetrics[i].mXBearing = extents.x_bearing;
 | |
|     aGlyphMetrics[i].mXAdvance = extents.x_advance;
 | |
|     aGlyphMetrics[i].mYBearing = extents.y_bearing;
 | |
|     aGlyphMetrics[i].mYAdvance = extents.y_advance;
 | |
|     aGlyphMetrics[i].mWidth = extents.width;
 | |
|     aGlyphMetrics[i].mHeight = extents.height;
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<SourceSurface>
 | |
| DrawTargetCairo::CreateSourceSurfaceFromData(unsigned char *aData,
 | |
|                                              const IntSize &aSize,
 | |
|                                              int32_t aStride,
 | |
|                                              SurfaceFormat aFormat) const
 | |
| {
 | |
|   if (!aData) {
 | |
|     gfxWarning() << "DrawTargetCairo::CreateSourceSurfaceFromData null aData";
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   cairo_surface_t* surf = CopyToImageSurface(aData, IntRect(IntPoint(), aSize),
 | |
|                                              aStride, aFormat);
 | |
|   if (!surf) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   RefPtr<SourceSurfaceCairo> source_surf = new SourceSurfaceCairo(surf, aSize, aFormat);
 | |
|   cairo_surface_destroy(surf);
 | |
| 
 | |
|   return source_surf.forget();
 | |
| }
 | |
| 
 | |
| #ifdef CAIRO_HAS_XLIB_SURFACE
 | |
| static cairo_user_data_key_t gDestroyPixmapKey;
 | |
| 
 | |
| struct DestroyPixmapClosure {
 | |
|   DestroyPixmapClosure(Drawable d, Screen *s) : mPixmap(d), mScreen(s) {}
 | |
|   ~DestroyPixmapClosure() {
 | |
|     XFreePixmap(DisplayOfScreen(mScreen), mPixmap);
 | |
|   }
 | |
|   Drawable mPixmap;
 | |
|   Screen *mScreen;
 | |
| };
 | |
| 
 | |
| static void
 | |
| DestroyPixmap(void *data)
 | |
| {
 | |
|   delete static_cast<DestroyPixmapClosure*>(data);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| already_AddRefed<SourceSurface>
 | |
| DrawTargetCairo::OptimizeSourceSurface(SourceSurface *aSurface) const
 | |
| {
 | |
|   RefPtr<SourceSurface> surface(aSurface);
 | |
| #ifdef CAIRO_HAS_XLIB_SURFACE
 | |
|   cairo_surface_type_t ctype = cairo_surface_get_type(mSurface);
 | |
|   if (aSurface->GetType() == SurfaceType::CAIRO &&
 | |
|       cairo_surface_get_type(
 | |
|         static_cast<SourceSurfaceCairo*>(aSurface)->GetSurface()) == ctype) {
 | |
|     return surface.forget();
 | |
|   }
 | |
| 
 | |
|   if (ctype != CAIRO_SURFACE_TYPE_XLIB) {
 | |
|     return surface.forget();
 | |
|   }
 | |
| 
 | |
|   IntSize size = aSurface->GetSize();
 | |
|   if (!size.width || !size.height) {
 | |
|     return surface.forget();
 | |
|   }
 | |
| 
 | |
|   // Although the dimension parameters in the xCreatePixmapReq wire protocol are
 | |
|   // 16-bit unsigned integers, the server's CreatePixmap returns BadAlloc if
 | |
|   // either dimension cannot be represented by a 16-bit *signed* integer.
 | |
|   #define XLIB_IMAGE_SIDE_SIZE_LIMIT 0x7fff
 | |
| 
 | |
|   if (size.width > XLIB_IMAGE_SIDE_SIZE_LIMIT ||
 | |
|       size.height > XLIB_IMAGE_SIDE_SIZE_LIMIT) {
 | |
|     return surface.forget();
 | |
|   }
 | |
| 
 | |
|   SurfaceFormat format = aSurface->GetFormat();
 | |
|   Screen *screen = cairo_xlib_surface_get_screen(mSurface);
 | |
|   Display *dpy = DisplayOfScreen(screen);
 | |
|   XRenderPictFormat* xrenderFormat = nullptr;
 | |
|   switch (format) {
 | |
|   case SurfaceFormat::A8R8G8B8_UINT32:
 | |
|     xrenderFormat = XRenderFindStandardFormat(dpy, PictStandardARGB32);
 | |
|     break;
 | |
|   case SurfaceFormat::X8R8G8B8_UINT32:
 | |
|     xrenderFormat = XRenderFindStandardFormat(dpy, PictStandardRGB24);
 | |
|     break;
 | |
|   case SurfaceFormat::A8:
 | |
|     xrenderFormat = XRenderFindStandardFormat(dpy, PictStandardA8);
 | |
|     break;
 | |
|   default:
 | |
|     return surface.forget();
 | |
|   }
 | |
|   if (!xrenderFormat) {
 | |
|     return surface.forget();
 | |
|   }
 | |
| 
 | |
|   Drawable pixmap = XCreatePixmap(dpy, RootWindowOfScreen(screen),
 | |
|                                   size.width, size.height,
 | |
|                                   xrenderFormat->depth);
 | |
|   if (!pixmap) {
 | |
|     return surface.forget();
 | |
|   }
 | |
| 
 | |
|   auto closure = MakeUnique<DestroyPixmapClosure>(pixmap, screen);
 | |
| 
 | |
|   ScopedCairoSurface csurf(
 | |
|     cairo_xlib_surface_create_with_xrender_format(dpy, pixmap,
 | |
|                                                   screen, xrenderFormat,
 | |
|                                                   size.width, size.height));
 | |
|   if (!csurf || cairo_surface_status(csurf)) {
 | |
|     return surface.forget();
 | |
|   }
 | |
| 
 | |
|   cairo_surface_set_user_data(csurf, &gDestroyPixmapKey,
 | |
|                               closure.release(), DestroyPixmap);
 | |
| 
 | |
|   RefPtr<DrawTargetCairo> dt = new DrawTargetCairo();
 | |
|   if (!dt->Init(csurf, size, &format)) {
 | |
|     return surface.forget();
 | |
|   }
 | |
| 
 | |
|   dt->CopySurface(aSurface,
 | |
|                   IntRect(0, 0, size.width, size.height),
 | |
|                   IntPoint(0, 0));
 | |
|   dt->Flush();
 | |
| 
 | |
|   surface = new SourceSurfaceCairo(csurf, size, format);
 | |
| #endif
 | |
| 
 | |
|   return surface.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<SourceSurface>
 | |
| DrawTargetCairo::CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const
 | |
| {
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| already_AddRefed<DrawTarget>
 | |
| DrawTargetCairo::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
 | |
| {
 | |
|   if (cairo_surface_status(cairo_get_group_target(mContext))) {
 | |
|     RefPtr<DrawTargetCairo> target = new DrawTargetCairo();
 | |
|     if (target->Init(aSize, aFormat)) {
 | |
|       return target.forget();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   cairo_surface_t* similar;
 | |
|   switch (cairo_surface_get_type(mSurface)) {
 | |
| #ifdef CAIRO_HAS_WIN32_SURFACE
 | |
|     case CAIRO_SURFACE_TYPE_WIN32:
 | |
|       similar = cairo_win32_surface_create_with_dib(
 | |
|         GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height);
 | |
|       break;
 | |
| #endif
 | |
| #ifdef CAIRO_HAS_QUARTZ_SURFACE
 | |
|     case CAIRO_SURFACE_TYPE_QUARTZ:
 | |
|       similar = cairo_quartz_surface_create_cg_layer(
 | |
|         mSurface, GfxFormatToCairoContent(aFormat), aSize.width, aSize.height);
 | |
|       break;
 | |
| #endif
 | |
|     default:
 | |
|       similar = cairo_surface_create_similar(mSurface,
 | |
|                                              GfxFormatToCairoContent(aFormat),
 | |
|                                              aSize.width, aSize.height);
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   if (!cairo_surface_status(similar)) {
 | |
|     RefPtr<DrawTargetCairo> target = new DrawTargetCairo();
 | |
|     if (target->InitAlreadyReferenced(similar, aSize)) {
 | |
|       return target.forget();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize))) << "Failed to create similar cairo surface! Size: " << aSize << " Status: " << cairo_surface_status(similar) << cairo_surface_status(cairo_get_group_target(mContext)) << " format " << (int)aFormat;
 | |
|   cairo_surface_destroy(similar);
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| bool
 | |
| DrawTargetCairo::InitAlreadyReferenced(cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat* aFormat)
 | |
| {
 | |
|   if (cairo_surface_status(aSurface)) {
 | |
|     gfxCriticalNote
 | |
|       << "Attempt to create DrawTarget for invalid surface. "
 | |
|       << aSize << " Cairo Status: " << cairo_surface_status(aSurface);
 | |
|     cairo_surface_destroy(aSurface);
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   mContext = cairo_create(aSurface);
 | |
|   mSurface = aSurface;
 | |
|   mSize = aSize;
 | |
|   mFormat = aFormat ? *aFormat : GfxFormatForCairoSurface(aSurface);
 | |
| 
 | |
|   // Cairo image surface have a bug where they will allocate a mask surface (for clipping)
 | |
|   // the size of the clip extents, and don't take the surface extents into account.
 | |
|   // Add a manual clip to the surface extents to prevent this.
 | |
|   cairo_new_path(mContext);
 | |
|   cairo_rectangle(mContext, 0, 0, mSize.width, mSize.height);
 | |
|   cairo_clip(mContext);
 | |
| 
 | |
|   if (mFormat == SurfaceFormat::A8R8G8B8_UINT32 ||
 | |
|       mFormat == SurfaceFormat::R8G8B8A8) {
 | |
|     SetPermitSubpixelAA(false);
 | |
|   } else {
 | |
|     SetPermitSubpixelAA(true);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| already_AddRefed<DrawTarget>
 | |
| DrawTargetCairo::CreateShadowDrawTarget(const IntSize &aSize, SurfaceFormat aFormat,
 | |
|                                         float aSigma) const
 | |
| {
 | |
|   cairo_surface_t* similar = cairo_surface_create_similar(cairo_get_target(mContext),
 | |
|                                                           GfxFormatToCairoContent(aFormat),
 | |
|                                                           aSize.width, aSize.height);
 | |
| 
 | |
|   if (cairo_surface_status(similar)) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // If we don't have a blur then we can use the RGBA mask and keep all the
 | |
|   // operations in graphics memory.
 | |
|   if (aSigma == 0.0f || aFormat == SurfaceFormat::A8) {
 | |
|     RefPtr<DrawTargetCairo> target = new DrawTargetCairo();
 | |
|     if (target->InitAlreadyReferenced(similar, aSize)) {
 | |
|       return target.forget();
 | |
|     } else {
 | |
|       return nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   cairo_surface_t* blursurf = cairo_image_surface_create(CAIRO_FORMAT_A8,
 | |
|                                                          aSize.width,
 | |
|                                                          aSize.height);
 | |
| 
 | |
|   if (cairo_surface_status(blursurf)) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   cairo_surface_t* tee = cairo_tee_surface_create(blursurf);
 | |
|   cairo_surface_destroy(blursurf);
 | |
|   if (cairo_surface_status(tee)) {
 | |
|     cairo_surface_destroy(similar);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   cairo_tee_surface_add(tee, similar);
 | |
|   cairo_surface_destroy(similar);
 | |
| 
 | |
|   RefPtr<DrawTargetCairo> target = new DrawTargetCairo();
 | |
|   if (target->InitAlreadyReferenced(tee, aSize)) {
 | |
|     return target.forget();
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| static inline pixman_format_code_t
 | |
| GfxFormatToPixmanFormat(SurfaceFormat aFormat)
 | |
| {
 | |
|   switch (aFormat) {
 | |
|   case SurfaceFormat::A8R8G8B8_UINT32:
 | |
|     return PIXMAN_a8r8g8b8;
 | |
|   case SurfaceFormat::X8R8G8B8_UINT32:
 | |
|     return PIXMAN_x8r8g8b8;
 | |
|   case SurfaceFormat::R5G6B5_UINT16:
 | |
|     return PIXMAN_r5g6b5;
 | |
|   case SurfaceFormat::A8:
 | |
|     return PIXMAN_a8;
 | |
|   default:
 | |
|     // Allow both BGRA and ARGB formats to be passed through unmodified,
 | |
|     // even though even though we are actually rendering to A8R8G8B8_UINT32.
 | |
|     if (aFormat == SurfaceFormat::B8G8R8A8 ||
 | |
|         aFormat == SurfaceFormat::A8R8G8B8) {
 | |
|       return PIXMAN_a8r8g8b8;
 | |
|     }
 | |
|     return (pixman_format_code_t)0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static inline bool
 | |
| GfxMatrixToPixmanTransform(const Matrix4x4 &aMatrix, pixman_transform* aResult)
 | |
| {
 | |
|   pixman_f_transform fTransform = {{
 | |
|     { aMatrix._11, aMatrix._21, aMatrix._41 },
 | |
|     { aMatrix._12, aMatrix._22, aMatrix._42 },
 | |
|     { aMatrix._14, aMatrix._24, aMatrix._44 }
 | |
|   }};
 | |
|   return pixman_transform_from_pixman_f_transform(aResult, &fTransform);
 | |
| }
 | |
| 
 | |
| #ifndef USE_SKIA
 | |
| bool
 | |
| DrawTarget::Draw3DTransformedSurface(SourceSurface* aSurface, const Matrix4x4& aMatrix)
 | |
| {
 | |
|   // Composite the 3D transform with the DT's transform.
 | |
|   Matrix4x4 fullMat = aMatrix * Matrix4x4::From2D(mTransform);
 | |
|   // Transform the surface bounds and clip to this DT.
 | |
|   IntRect xformBounds =
 | |
|     RoundedOut(
 | |
|       fullMat.TransformAndClipBounds(Rect(Point(0, 0), Size(aSurface->GetSize())),
 | |
|                                      Rect(Point(0, 0), Size(GetSize()))));
 | |
|   if (xformBounds.IsEmpty()) {
 | |
|     return true;
 | |
|   }
 | |
|   // Offset the matrix by the transformed origin.
 | |
|   fullMat.PostTranslate(-xformBounds.x, -xformBounds.y, 0);
 | |
|   // Invert the matrix into a pattern matrix for pixman.
 | |
|   if (!fullMat.Invert()) {
 | |
|     return false;
 | |
|   }
 | |
|   pixman_transform xform;
 | |
|   if (!GfxMatrixToPixmanTransform(fullMat, &xform)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Read in the source data.
 | |
|   RefPtr<DataSourceSurface> srcSurf = aSurface->GetDataSurface();
 | |
|   pixman_format_code_t srcFormat = GfxFormatToPixmanFormat(srcSurf->GetFormat());
 | |
|   if (!srcFormat) {
 | |
|     return false;
 | |
|   }
 | |
|   DataSourceSurface::ScopedMap srcMap(srcSurf, DataSourceSurface::READ);
 | |
|   if (!srcMap.IsMapped()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Set up an intermediate destination surface only the size of the transformed bounds.
 | |
|   // Try to pass through the source's format unmodified in both the BGRA and ARGB cases.
 | |
|   RefPtr<DataSourceSurface> dstSurf =
 | |
|     Factory::CreateDataSourceSurface(xformBounds.Size(),
 | |
|                                      srcFormat == PIXMAN_a8r8g8b8 ?
 | |
|                                        srcSurf->GetFormat() : SurfaceFormat::A8R8G8B8_UINT32);
 | |
|   if (!dstSurf) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Wrap the surfaces in pixman images and do the transform.
 | |
|   pixman_image_t* dst =
 | |
|     pixman_image_create_bits(PIXMAN_a8r8g8b8,
 | |
|                              xformBounds.width, xformBounds.height,
 | |
|                              (uint32_t*)dstSurf->GetData(), dstSurf->Stride());
 | |
|   if (!dst) {
 | |
|     return false;
 | |
|   }
 | |
|   pixman_image_t* src =
 | |
|     pixman_image_create_bits(srcFormat,
 | |
|                              srcSurf->GetSize().width, srcSurf->GetSize().height,
 | |
|                              (uint32_t*)srcMap.GetData(), srcMap.GetStride());
 | |
|   if (!src) {
 | |
|     pixman_image_unref(dst);
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   pixman_image_set_filter(src, PIXMAN_FILTER_BILINEAR, nullptr, 0);
 | |
|   pixman_image_set_transform(src, &xform);
 | |
| 
 | |
|   pixman_image_composite32(PIXMAN_OP_SRC,
 | |
|                            src, nullptr, dst,
 | |
|                            0, 0, 0, 0, 0, 0,
 | |
|                            xformBounds.width, xformBounds.height);
 | |
| 
 | |
|   pixman_image_unref(dst);
 | |
|   pixman_image_unref(src);
 | |
| 
 | |
|   // Temporarily reset the DT's transform, since it has already been composed above.
 | |
|   Matrix origTransform = mTransform;
 | |
|   SetTransform(Matrix());
 | |
| 
 | |
|   // Draw the transformed surface within the transformed bounds.
 | |
|   DrawSurface(dstSurf, Rect(xformBounds), Rect(Point(0, 0), Size(xformBounds.Size())));
 | |
| 
 | |
|   SetTransform(origTransform);
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| #ifdef CAIRO_HAS_XLIB_SURFACE
 | |
| static bool gXRenderInitialized = false;
 | |
| static bool gXRenderHasTransform = false;
 | |
| 
 | |
| static bool
 | |
| SupportsXRender(cairo_surface_t* surface)
 | |
| {
 | |
|   if (!surface ||
 | |
|       cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_XLIB ||
 | |
|       !cairo_xlib_surface_get_xrender_format(surface)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (gXRenderInitialized) {
 | |
|     return true;
 | |
|   }
 | |
|   gXRenderInitialized = true;
 | |
| 
 | |
|   cairo_device_t* device = cairo_surface_get_device(surface);
 | |
|   if (cairo_device_acquire(device) != CAIRO_STATUS_SUCCESS) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   Display* display = cairo_xlib_surface_get_display(surface);
 | |
|   int major, minor;
 | |
|   if (XRenderQueryVersion(display, &major, &minor)) {
 | |
|     if (major > 0 || (major == 0 && minor >= 6)) {
 | |
|       gXRenderHasTransform = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   cairo_device_release(device);
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| bool
 | |
| DrawTargetCairo::Draw3DTransformedSurface(SourceSurface* aSurface, const Matrix4x4& aMatrix)
 | |
| {
 | |
| #if CAIRO_HAS_XLIB_SURFACE
 | |
|   cairo_surface_t* srcSurf =
 | |
|     aSurface->GetType() == SurfaceType::CAIRO ?
 | |
|       static_cast<SourceSurfaceCairo*>(aSurface)->GetSurface() : nullptr;
 | |
|   if (!SupportsXRender(srcSurf) || !gXRenderHasTransform) {
 | |
|     return DrawTarget::Draw3DTransformedSurface(aSurface, aMatrix);
 | |
|   }
 | |
| 
 | |
|   Matrix4x4 fullMat = aMatrix * Matrix4x4::From2D(mTransform);
 | |
|   IntRect xformBounds =
 | |
|     RoundedOut(
 | |
|       fullMat.TransformAndClipBounds(Rect(Point(0, 0), Size(aSurface->GetSize())),
 | |
|                                      Rect(Point(0, 0), Size(GetSize()))));
 | |
|   if (xformBounds.IsEmpty()) {
 | |
|     return true;
 | |
|   }
 | |
|   fullMat.PostTranslate(-xformBounds.X(), -xformBounds.Y(), 0);
 | |
|   if (!fullMat.Invert()) {
 | |
|     return false;
 | |
|   }
 | |
|   pixman_transform xform;
 | |
|   if (!GfxMatrixToPixmanTransform(fullMat, &xform)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   cairo_surface_t* xformSurf =
 | |
|     cairo_surface_create_similar(srcSurf, CAIRO_CONTENT_COLOR_ALPHA,
 | |
|                                  xformBounds.Width(), xformBounds.Height());
 | |
|   if (!SupportsXRender(xformSurf)) {
 | |
|     cairo_surface_destroy(xformSurf);
 | |
|     return false;
 | |
|   }
 | |
|   cairo_device_t* device = cairo_surface_get_device(xformSurf);
 | |
|   if (cairo_device_acquire(device) != CAIRO_STATUS_SUCCESS) {
 | |
|     cairo_surface_destroy(xformSurf);
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   Display* display = cairo_xlib_surface_get_display(xformSurf);
 | |
| 
 | |
|   Picture srcPict = XRenderCreatePicture(display,
 | |
|                                          cairo_xlib_surface_get_drawable(srcSurf),
 | |
|                                          cairo_xlib_surface_get_xrender_format(srcSurf),
 | |
|                                          0, nullptr);
 | |
|   XRenderSetPictureFilter(display, srcPict, FilterBilinear, nullptr, 0);
 | |
|   XRenderSetPictureTransform(display, srcPict, (XTransform*)&xform);
 | |
| 
 | |
|   Picture dstPict = XRenderCreatePicture(display,
 | |
|                                          cairo_xlib_surface_get_drawable(xformSurf),
 | |
|                                          cairo_xlib_surface_get_xrender_format(xformSurf),
 | |
|                                          0, nullptr);
 | |
| 
 | |
|   XRenderComposite(display, PictOpSrc,
 | |
|                    srcPict, X11None, dstPict,
 | |
|                    0, 0, 0, 0, 0, 0,
 | |
|                    xformBounds.Width(), xformBounds.Height());
 | |
| 
 | |
|   XRenderFreePicture(display, srcPict);
 | |
|   XRenderFreePicture(display, dstPict);
 | |
| 
 | |
|   cairo_device_release(device);
 | |
|   cairo_surface_mark_dirty(xformSurf);
 | |
| 
 | |
|   AutoPrepareForDrawing(this, mContext);
 | |
| 
 | |
|   cairo_identity_matrix(mContext);
 | |
| 
 | |
|   cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
 | |
|   cairo_set_antialias(mContext, CAIRO_ANTIALIAS_DEFAULT);
 | |
|   cairo_set_source_surface(mContext, xformSurf, xformBounds.X(), xformBounds.Y());
 | |
| 
 | |
|   cairo_new_path(mContext);
 | |
|   cairo_rectangle(mContext, xformBounds.X(), xformBounds.Y(), xformBounds.Width(), xformBounds.Height());
 | |
|   cairo_fill(mContext);
 | |
| 
 | |
|   cairo_surface_destroy(xformSurf);
 | |
| 
 | |
|   return true;
 | |
| #else
 | |
|   return DrawTarget::Draw3DTransformedSurface(aSurface, aMatrix);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| bool
 | |
| DrawTargetCairo::Init(cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat* aFormat)
 | |
| {
 | |
|   cairo_surface_reference(aSurface);
 | |
|   return InitAlreadyReferenced(aSurface, aSize, aFormat);
 | |
| }
 | |
| 
 | |
| bool
 | |
| DrawTargetCairo::Init(const IntSize& aSize, SurfaceFormat aFormat)
 | |
| {
 | |
|   cairo_surface_t *surf = cairo_image_surface_create(GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height);
 | |
|   return InitAlreadyReferenced(surf, aSize);
 | |
| }
 | |
| 
 | |
| bool
 | |
| DrawTargetCairo::Init(unsigned char* aData, const IntSize &aSize, int32_t aStride, SurfaceFormat aFormat)
 | |
| {
 | |
|   cairo_surface_t* surf =
 | |
|     cairo_image_surface_create_for_data(aData,
 | |
|                                         GfxFormatToCairoFormat(aFormat),
 | |
|                                         aSize.width,
 | |
|                                         aSize.height,
 | |
|                                         aStride);
 | |
|   return InitAlreadyReferenced(surf, aSize);
 | |
| }
 | |
| 
 | |
| void *
 | |
| DrawTargetCairo::GetNativeSurface(NativeSurfaceType aType)
 | |
| {
 | |
|   if (aType == NativeSurfaceType::CAIRO_CONTEXT) {
 | |
|     return mContext;
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::MarkSnapshotIndependent()
 | |
| {
 | |
|   if (mSnapshot) {
 | |
|     if (mSnapshot->refCount() > 1) {
 | |
|       // We only need to worry about snapshots that someone else knows about
 | |
|       mSnapshot->DrawTargetWillChange();
 | |
|     }
 | |
|     mSnapshot = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::WillChange(const Path* aPath /* = nullptr */)
 | |
| {
 | |
|   MarkSnapshotIndependent();
 | |
|   MOZ_ASSERT(!mLockedBits);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetCairo::SetTransform(const Matrix& aTransform)
 | |
| {
 | |
|   DrawTarget::SetTransform(aTransform);
 | |
| 
 | |
|   mTransformSingular = aTransform.IsSingular();
 | |
|   if (!mTransformSingular) {
 | |
|     cairo_matrix_t mat;
 | |
|     GfxMatrixToCairoMatrix(mTransform, mat);
 | |
|     cairo_set_matrix(mContext, &mat);
 | |
|   }
 | |
| }
 | |
| 
 | |
| Rect
 | |
| DrawTargetCairo::GetUserSpaceClip()
 | |
| {
 | |
|   double clipX1, clipY1, clipX2, clipY2;
 | |
|   cairo_clip_extents(mContext, &clipX1, &clipY1, &clipX2, &clipY2);
 | |
|   return Rect(clipX1, clipY1, clipX2 - clipX1, clipY2 - clipY1); // Narrowing of doubles to floats
 | |
| }
 | |
| 
 | |
| cairo_t*
 | |
| BorrowedCairoContext::BorrowCairoContextFromDrawTarget(DrawTarget* aDT)
 | |
| {
 | |
|   if (aDT->GetBackendType() != BackendType::CAIRO ||
 | |
|       aDT->IsDualDrawTarget() ||
 | |
|       aDT->IsTiledDrawTarget() ||
 | |
|       aDT->IsCaptureDT()) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(aDT);
 | |
| 
 | |
|   cairoDT->WillChange();
 | |
| 
 | |
|   // save the state to make it easier for callers to avoid mucking with things
 | |
|   cairo_save(cairoDT->mContext);
 | |
| 
 | |
|   // Neuter the DrawTarget while the context is being borrowed
 | |
|   cairo_t* cairo = cairoDT->mContext;
 | |
|   cairoDT->mContext = nullptr;
 | |
| 
 | |
|   return cairo;
 | |
| }
 | |
| 
 | |
| void
 | |
| BorrowedCairoContext::ReturnCairoContextToDrawTarget(DrawTarget* aDT,
 | |
|                                                      cairo_t* aCairo)
 | |
| {
 | |
|   if (aDT->GetBackendType() != BackendType::CAIRO ||
 | |
|       aDT->IsDualDrawTarget() ||
 | |
|       aDT->IsTiledDrawTarget()) {
 | |
|     return;
 | |
|   }
 | |
|   DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(aDT);
 | |
| 
 | |
|   cairo_restore(aCairo);
 | |
|   cairoDT->mContext = aCairo;
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_X11
 | |
| bool
 | |
| BorrowedXlibDrawable::Init(DrawTarget* aDT)
 | |
| {
 | |
|   MOZ_ASSERT(aDT, "Caller should check for nullptr");
 | |
|   MOZ_ASSERT(!mDT, "Can't initialize twice!");
 | |
|   mDT = aDT;
 | |
|   mDrawable = X11None;
 | |
| 
 | |
| #ifdef CAIRO_HAS_XLIB_SURFACE
 | |
|   if (aDT->GetBackendType() != BackendType::CAIRO ||
 | |
|       aDT->IsDualDrawTarget() ||
 | |
|       aDT->IsTiledDrawTarget()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(aDT);
 | |
|   cairo_surface_t* surf = cairo_get_group_target(cairoDT->mContext);
 | |
|   if (cairo_surface_get_type(surf) != CAIRO_SURFACE_TYPE_XLIB) {
 | |
|     return false;
 | |
|   }
 | |
|   cairo_surface_flush(surf);
 | |
| 
 | |
|   cairoDT->WillChange();
 | |
| 
 | |
|   mDisplay = cairo_xlib_surface_get_display(surf);
 | |
|   mDrawable = cairo_xlib_surface_get_drawable(surf);
 | |
|   mScreen = cairo_xlib_surface_get_screen(surf);
 | |
|   mVisual = cairo_xlib_surface_get_visual(surf);
 | |
|   mXRenderFormat = cairo_xlib_surface_get_xrender_format(surf);
 | |
|   mSize.width = cairo_xlib_surface_get_width(surf);
 | |
|   mSize.height = cairo_xlib_surface_get_height(surf);
 | |
| 
 | |
|   double x = 0, y = 0;
 | |
|   cairo_surface_get_device_offset(surf, &x, &y);
 | |
|   mOffset = Point(x, y);
 | |
| 
 | |
|   return true;
 | |
| #else
 | |
|   return false;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| void
 | |
| BorrowedXlibDrawable::Finish()
 | |
| {
 | |
|   DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(mDT);
 | |
|   cairo_surface_t* surf = cairo_get_group_target(cairoDT->mContext);
 | |
|   cairo_surface_mark_dirty(surf);
 | |
|   if (mDrawable) {
 | |
|     mDrawable = X11None;
 | |
|   }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| } // namespace gfx
 | |
| } // namespace mozilla
 |