diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/2d/DrawTargetCairo.cpp | 2269 |
1 files changed, 2269 insertions, 0 deletions
diff --git a/gfx/2d/DrawTargetCairo.cpp b/gfx/2d/DrawTargetCairo.cpp new file mode 100644 index 0000000000..7a4d4ad95a --- /dev/null +++ b/gfx/2d/DrawTargetCairo.cpp @@ -0,0 +1,2269 @@ +/* -*- 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 "BorrowedContext.h" +#include "FilterNodeSoftware.h" +#include "mozilla/Scoped.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" +#include "mozilla/StaticPrefs_print.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; + } + case PatternType::CONIC_GRADIENT: { + const ConicGradientPattern& pattern = + static_cast<const ConicGradientPattern&>(aPattern); + return pattern.mStops->GetBackendType() == BackendType::CAIRO; + } + default: + return true; + } +} + +static cairo_user_data_key_t surfaceDataKey; + +static void ReleaseData(void* aData) { + DataSourceSurface* data = static_cast<DataSourceSurface*>(aData); + data->Unmap(); + data->Release(); +} + +static 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. + */ +static 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; +} + +static 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. + */ +static 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. + */ +static 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 final { + 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: { + DeviceColor 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; + } + case PatternType::CONIC_GRADIENT: { + // XXX(ntim): Bug 1617039 - Implement conic-gradient for Cairo + pat = cairo_pattern_create_rgba(0.0, 0.0, 0.0, 0.0); + + 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 DeviceColor& aColor, + const Point& aOffset, Float aSigma, + CompositionOp aOperator) { + if (!IsValid() || !aSurface) { + gfxCriticalNote << "DrawSurfaceWithShadow with bad surface " + << cairo_surface_status(cairo_get_group_target(mContext)); + return; + } + + 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(cairo_antialias_t aAAMode) { + // 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 && aAAMode == CAIRO_ANTIALIAS_DEFAULT) { + return; + } + + if (!mFontOptions) { + mFontOptions = cairo_font_options_create(); + if (!mFontOptions) { + gfxWarning() << "Failed allocating Cairo font options"; + return; + } + } + + cairo_get_font_options(mContext, mFontOptions); + cairo_antialias_t oldAA = cairo_font_options_get_antialias(mFontOptions); + cairo_antialias_t newAA = + aAAMode == CAIRO_ANTIALIAS_DEFAULT ? oldAA : aAAMode; + // Nothing to change if switching to default AA. + if (newAA == CAIRO_ANTIALIAS_DEFAULT) { + return; + } + // If the current font requests subpixel AA, force it to gray since we don't + // allow subpixel AA. + if (!mPermitSubpixelAA && newAA == CAIRO_ANTIALIAS_SUBPIXEL) { + newAA = CAIRO_ANTIALIAS_GRAY; + } + // Only override old AA with lower levels of AA. + if (oldAA == CAIRO_ANTIALIAS_DEFAULT || (int)newAA < (int)oldAA) { + cairo_font_options_set_antialias(mFontOptions, newAA); + 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; + } + + cairo_scaled_font_t* cairoScaledFont = + aFont ? aFont->GetCairoScaledFont() : nullptr; + if (!cairoScaledFont) { + gfxDevCrash(LogReason::InvalidFont) << "Invalid scaled font"; + return; + } + + AutoPrepareForDrawing prep(this, mContext); + AutoClearDeviceOffset clear(aPattern); + + cairo_set_scaled_font(mContext, cairoScaledFont); + + cairo_pattern_t* pat = + GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform()); + if (!pat) return; + + cairo_set_source(mContext, pat); + cairo_pattern_destroy(pat); + + cairo_antialias_t aa = GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode); + cairo_set_antialias(mContext, aa); + + // Override any font-specific options as necessary. + SetFontOptions(aa); + + // 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() && + StaticPrefs::print_font_variations_as_paths()) { + 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 - aMask->GetRect().x, + -aOffset.y - aMask->GetRect().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); + Matrix maskTransform = aMaskTransform; + maskTransform.PreTranslate(aMask->GetRect().X(), aMask->GetRect().Y()); + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(maskTransform, 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); +} + +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; +} + +RefPtr<DrawTarget> DrawTargetCairo::CreateClippedDrawTarget( + const Rect& aBounds, SurfaceFormat aFormat) { + RefPtr<DrawTarget> result; + // Doing this save()/restore() dance is wasteful + cairo_save(mContext); + + if (!aBounds.IsEmpty()) { + cairo_new_path(mContext); + cairo_rectangle(mContext, aBounds.X(), aBounds.Y(), aBounds.Width(), + aBounds.Height()); + cairo_clip(mContext); + } + cairo_identity_matrix(mContext); + IntRect clipBounds = IntRect::RoundOut(GetUserSpaceClip()); + if (!clipBounds.IsEmpty()) { + RefPtr<DrawTarget> dt = CreateSimilarDrawTarget( + IntSize(clipBounds.width, clipBounds.height), aFormat); + result = gfx::Factory::CreateOffsetDrawTarget( + dt, IntPoint(clipBounds.x, clipBounds.y)); + result->SetTransform(mTransform); + } else { + // Everything is clipped but we still want some kind of surface + result = CreateSimilarDrawTarget(IntSize(1, 1), aFormat); + } + + cairo_restore(mContext); + return result; +} +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; +} + +#ifndef USE_SKIA +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; + } +} +#endif + +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() const { + 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 |