summaryrefslogtreecommitdiffstats
path: root/gfx/2d/DrawTargetCairo.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/2d/DrawTargetCairo.cpp')
-rw-r--r--gfx/2d/DrawTargetCairo.cpp2374
1 files changed, 2374 insertions, 0 deletions
diff --git a/gfx/2d/DrawTargetCairo.cpp b/gfx/2d/DrawTargetCairo.cpp
new file mode 100644
index 000000000..c0e4f0af2
--- /dev/null
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -0,0 +1,2374 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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 "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() == 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() == 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.width += r.X();
+ if (r.width < 0.f)
+ return false;
+ r.x = 0.f;
+ }
+
+ if (r.XMost() > CAIRO_COORD_MAX) {
+ r.width = CAIRO_COORD_MAX - r.X();
+ }
+
+ if (r.Y() < 0.f) {
+ r.height += r.Y();
+ if (r.Height() < 0.f)
+ return false;
+
+ r.y = 0.f;
+ }
+
+ if (r.YMost() > CAIRO_COORD_MAX) {
+ r.height = CAIRO_COORD_MAX - r.Y();
+ }
+ 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);
+
+ cairo_surface_t* surf = cairo_image_surface_create(GfxFormatToCairoFormat(aFormat),
+ aRect.width,
+ aRect.height);
+ // 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 >= aRect.width * pixelWidth);
+ for (int32_t y = 0; y < aRect.height; ++y) {
+ memcpy(surfData + y * surfStride,
+ source + y * aStride,
+ aRect.width * 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)
+ {
+ 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()
+{
+ 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 " << cairo_surface_status(mSurface);
+ 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);
+
+ 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));
+ } else {
+ blursurf = sourcesurf;
+ surf = sourcesurf;
+ }
+
+ WillChange();
+ ClearSurfaceForUnboundedSource(aOperator);
+
+ cairo_save(mContext);
+ cairo_set_operator(mContext, GfxOpToCairoOp(aOperator));
+ cairo_identity_matrix(mContext);
+ cairo_translate(mContext, aDest.x, aDest.y);
+
+ if (IsOperatorBoundByMask(aOperator)){
+ cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a);
+ cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y);
+
+ // 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);
+ } else {
+ 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);
+
+ // 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);
+ 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) &&
+ aDest.y >= aSource.y &&
+ aDest.y < aSource.YMost()) {
+ 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.x = 0;
+ source.y = 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
+}
+
+void
+DrawTargetCairo::FillGlyphs(ScaledFont *aFont,
+ const GlyphBuffer &aBuffer,
+ const Pattern &aPattern,
+ const DrawOptions &aOptions,
+ const GlyphRenderingOptions*)
+{
+ 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;
+ }
+
+ 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) {
+ 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()) {
+ 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