gecko-dev/gfx/2d/DrawTargetCairo.cpp
Nicholas Nethercote 18fae65f38 Bug 1563139 - Remove StaticPrefs.h. r=glandium
This requires replacing inclusions of it with inclusions of more specific prefs
files.

The exception is that StaticPrefsAll.h, which is equivalent to StaticPrefs.h,
and is used in `Codegen.py` because doing something smarter is tricky and
suitable for a follow-up. As a result, any change to StaticPrefList.yaml will
still trigger recompilation of all the generated DOM bindings files, but that's
still a big improvement over trigger recompilation of every file that uses
static prefs.

Most of the changes in this commit are very boring. The only changes that are
not boring are modules/libpref/*, Codegen.py, and ServoBindings.toml.

Differential Revision: https://phabricator.services.mozilla.com/D39138

--HG--
extra : moz-landing-system : lando
2019-07-26 01:10:23 +00:00

2261 lines
71 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DrawTargetCairo.h"
#include "SourceSurfaceCairo.h"
#include "PathCairo.h"
#include "HelpersCairo.h"
#include "ScaledFontBase.h"
#include "BorrowedContext.h"
#include "FilterNodeSoftware.h"
#include "mozilla/Scoped.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Vector.h"
#include "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;
}
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: {
Color color = static_cast<const ColorPattern&>(aPattern).mColor;
pat = cairo_pattern_create_rgba(color.r, color.g, color.b,
color.a * aAlpha);
break;
}
case PatternType::SURFACE: {
const SurfacePattern& pattern =
static_cast<const SurfacePattern&>(aPattern);
cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(
pattern.mSurface, false, pattern.mSamplingRect);
if (!surf) return nullptr;
pat = cairo_pattern_create_for_surface(surf);
matrix = &pattern.mMatrix;
cairo_pattern_set_filter(
pat, GfxSamplingFilterToCairoFilter(pattern.mSamplingFilter));
cairo_pattern_set_extend(pat,
GfxExtendToCairoExtend(pattern.mExtendMode));
cairo_surface_destroy(surf);
break;
}
case PatternType::LINEAR_GRADIENT: {
const LinearGradientPattern& pattern =
static_cast<const LinearGradientPattern&>(aPattern);
pat = cairo_pattern_create_linear(pattern.mBegin.x, pattern.mBegin.y,
pattern.mEnd.x, pattern.mEnd.y);
MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO);
GradientStopsCairo* cairoStops =
static_cast<GradientStopsCairo*>(pattern.mStops.get());
cairo_pattern_set_extend(
pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode()));
matrix = &pattern.mMatrix;
const std::vector<GradientStop>& stops = cairoStops->GetStops();
for (size_t i = 0; i < stops.size(); ++i) {
CairoPatternAddGradientStop(pat, stops[i]);
}
break;
}
case PatternType::RADIAL_GRADIENT: {
const RadialGradientPattern& pattern =
static_cast<const RadialGradientPattern&>(aPattern);
pat = cairo_pattern_create_radial(pattern.mCenter1.x, pattern.mCenter1.y,
pattern.mRadius1, pattern.mCenter2.x,
pattern.mCenter2.y, pattern.mRadius2);
MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO);
GradientStopsCairo* cairoStops =
static_cast<GradientStopsCairo*>(pattern.mStops.get());
cairo_pattern_set_extend(
pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode()));
matrix = &pattern.mMatrix;
const std::vector<GradientStop>& stops = cairoStops->GetStops();
for (size_t i = 0; i < stops.size(); ++i) {
CairoPatternAddGradientStop(pat, stops[i]);
}
break;
}
default: {
// We should support all pattern types!
MOZ_ASSERT(false);
}
}
// The pattern matrix is a matrix that transforms the pattern into user
// space. Cairo takes a matrix that converts from user space to pattern
// space. Cairo therefore needs the inverse.
if (matrix) {
cairo_matrix_t mat;
GfxMatrixToCairoMatrix(*matrix, mat);
cairo_matrix_invert(&mat);
cairo_pattern_set_matrix(pat, &mat);
}
return pat;
}
static bool NeedIntermediateSurface(const Pattern& aPattern,
const DrawOptions& aOptions) {
// We pre-multiply colours' alpha by the global alpha, so we don't need to
// use an intermediate surface for them.
if (aPattern.GetType() == PatternType::COLOR) return false;
if (aOptions.mAlpha == 1.0) return false;
return true;
}
DrawTargetCairo::DrawTargetCairo()
: mContext(nullptr),
mSurface(nullptr),
mTransformSingular(false),
mLockedBits(nullptr),
mFontOptions(nullptr) {}
DrawTargetCairo::~DrawTargetCairo() {
cairo_destroy(mContext);
if (mSurface) {
cairo_surface_destroy(mSurface);
mSurface = nullptr;
}
if (mFontOptions) {
cairo_font_options_destroy(mFontOptions);
mFontOptions = nullptr;
}
MOZ_ASSERT(!mLockedBits);
}
bool DrawTargetCairo::IsValid() const {
return mSurface && !cairo_surface_status(mSurface) && mContext &&
!cairo_surface_status(cairo_get_group_target(mContext));
}
DrawTargetType DrawTargetCairo::GetType() const {
if (mContext) {
cairo_surface_type_t type = cairo_surface_get_type(mSurface);
if (type == CAIRO_SURFACE_TYPE_TEE) {
type = cairo_surface_get_type(cairo_tee_surface_index(mSurface, 0));
MOZ_ASSERT(type != CAIRO_SURFACE_TYPE_TEE, "C'mon!");
MOZ_ASSERT(
type == cairo_surface_get_type(cairo_tee_surface_index(mSurface, 1)),
"What should we do here?");
}
switch (type) {
case CAIRO_SURFACE_TYPE_PDF:
case CAIRO_SURFACE_TYPE_PS:
case CAIRO_SURFACE_TYPE_SVG:
case CAIRO_SURFACE_TYPE_WIN32_PRINTING:
case CAIRO_SURFACE_TYPE_XML:
return DrawTargetType::VECTOR;
case CAIRO_SURFACE_TYPE_VG:
case CAIRO_SURFACE_TYPE_GL:
case CAIRO_SURFACE_TYPE_GLITZ:
case CAIRO_SURFACE_TYPE_QUARTZ:
case CAIRO_SURFACE_TYPE_DIRECTFB:
return DrawTargetType::HARDWARE_RASTER;
case CAIRO_SURFACE_TYPE_SKIA:
case CAIRO_SURFACE_TYPE_QT:
MOZ_FALLTHROUGH_ASSERT(
"Can't determine actual DrawTargetType for DrawTargetCairo - "
"assuming SOFTWARE_RASTER");
case CAIRO_SURFACE_TYPE_IMAGE:
case CAIRO_SURFACE_TYPE_XLIB:
case CAIRO_SURFACE_TYPE_XCB:
case CAIRO_SURFACE_TYPE_WIN32:
case CAIRO_SURFACE_TYPE_BEOS:
case CAIRO_SURFACE_TYPE_OS2:
case CAIRO_SURFACE_TYPE_QUARTZ_IMAGE:
case CAIRO_SURFACE_TYPE_SCRIPT:
case CAIRO_SURFACE_TYPE_RECORDING:
case CAIRO_SURFACE_TYPE_DRM:
case CAIRO_SURFACE_TYPE_SUBSURFACE:
case CAIRO_SURFACE_TYPE_TEE: // included to silence warning about
// unhandled enum value
return DrawTargetType::SOFTWARE_RASTER;
default:
MOZ_CRASH("GFX: Unsupported cairo surface type");
}
}
MOZ_ASSERT(false, "Could not determine DrawTargetType for DrawTargetCairo");
return DrawTargetType::SOFTWARE_RASTER;
}
IntSize DrawTargetCairo::GetSize() const { return mSize; }
SurfaceFormat GfxFormatForCairoSurface(cairo_surface_t* surface) {
cairo_surface_type_t type = cairo_surface_get_type(surface);
if (type == CAIRO_SURFACE_TYPE_IMAGE) {
return CairoFormatToGfxFormat(cairo_image_surface_get_format(surface));
}
#ifdef CAIRO_HAS_XLIB_SURFACE
// xlib is currently the only Cairo backend that creates 16bpp surfaces
if (type == CAIRO_SURFACE_TYPE_XLIB &&
cairo_xlib_surface_get_depth(surface) == 16) {
return SurfaceFormat::R5G6B5_UINT16;
}
#endif
return CairoContentToGfxFormat(cairo_surface_get_content(surface));
}
already_AddRefed<SourceSurface> DrawTargetCairo::Snapshot() {
if (!IsValid()) {
gfxCriticalNote << "DrawTargetCairo::Snapshot with bad surface "
<< hexa(mSurface) << ", context " << hexa(mContext)
<< ", status "
<< (mSurface ? cairo_surface_status(mSurface) : -1);
return nullptr;
}
if (mSnapshot) {
RefPtr<SourceSurface> snapshot(mSnapshot);
return snapshot.forget();
}
IntSize size = GetSize();
mSnapshot = new SourceSurfaceCairo(mSurface, size,
GfxFormatForCairoSurface(mSurface), this);
RefPtr<SourceSurface> snapshot(mSnapshot);
return snapshot.forget();
}
bool DrawTargetCairo::LockBits(uint8_t** aData, IntSize* aSize,
int32_t* aStride, SurfaceFormat* aFormat,
IntPoint* aOrigin) {
cairo_surface_t* target = cairo_get_group_target(mContext);
cairo_surface_t* surf = target;
#ifdef CAIRO_HAS_WIN32_SURFACE
if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_WIN32) {
cairo_surface_t* imgsurf = cairo_win32_surface_get_image(surf);
if (imgsurf) {
surf = imgsurf;
}
}
#endif
if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_IMAGE &&
cairo_surface_status(surf) == CAIRO_STATUS_SUCCESS) {
PointDouble offset;
cairo_surface_get_device_offset(target, &offset.x, &offset.y);
// verify the device offset can be converted to integers suitable for a
// bounds rect
IntPoint origin(int32_t(-offset.x), int32_t(-offset.y));
if (-PointDouble(origin) != offset || (!aOrigin && origin != IntPoint())) {
return false;
}
WillChange();
Flush();
mLockedBits = cairo_image_surface_get_data(surf);
*aData = mLockedBits;
*aSize = IntSize(cairo_image_surface_get_width(surf),
cairo_image_surface_get_height(surf));
*aStride = cairo_image_surface_get_stride(surf);
*aFormat = CairoFormatToGfxFormat(cairo_image_surface_get_format(surf));
if (aOrigin) {
*aOrigin = origin;
}
return true;
}
return false;
}
void DrawTargetCairo::ReleaseBits(uint8_t* aData) {
MOZ_ASSERT(mLockedBits == aData);
mLockedBits = nullptr;
cairo_surface_t* surf = cairo_get_group_target(mContext);
#ifdef CAIRO_HAS_WIN32_SURFACE
if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_WIN32) {
cairo_surface_t* imgsurf = cairo_win32_surface_get_image(surf);
if (imgsurf) {
cairo_surface_mark_dirty(imgsurf);
}
}
#endif
cairo_surface_mark_dirty(surf);
}
void DrawTargetCairo::Flush() {
cairo_surface_t* surf = cairo_get_group_target(mContext);
cairo_surface_flush(surf);
}
void DrawTargetCairo::PrepareForDrawing(cairo_t* aContext,
const Path* aPath /* = nullptr */) {
WillChange(aPath);
}
cairo_surface_t* DrawTargetCairo::GetDummySurface() {
if (mDummySurface) {
return mDummySurface;
}
mDummySurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
return mDummySurface;
}
static void PaintWithAlpha(cairo_t* aContext, const DrawOptions& aOptions) {
if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE) {
// Cairo treats the source operator like a lerp when alpha is < 1.
// Approximate the desired operator by: out = 0; out += src*alpha;
if (aOptions.mAlpha == 1) {
cairo_set_operator(aContext, CAIRO_OPERATOR_SOURCE);
cairo_paint(aContext);
} else {
cairo_set_operator(aContext, CAIRO_OPERATOR_CLEAR);
cairo_paint(aContext);
cairo_set_operator(aContext, CAIRO_OPERATOR_ADD);
cairo_paint_with_alpha(aContext, aOptions.mAlpha);
}
} else {
cairo_set_operator(aContext, GfxOpToCairoOp(aOptions.mCompositionOp));
cairo_paint_with_alpha(aContext, aOptions.mAlpha);
}
}
void DrawTargetCairo::DrawSurface(SourceSurface* aSurface, const Rect& aDest,
const Rect& aSource,
const DrawSurfaceOptions& aSurfOptions,
const DrawOptions& aOptions) {
if (mTransformSingular || aDest.IsEmpty()) {
return;
}
if (!IsValid() || !aSurface) {
gfxCriticalNote << "DrawSurface with bad surface "
<< cairo_surface_status(cairo_get_group_target(mContext));
return;
}
AutoPrepareForDrawing prep(this, mContext);
AutoClearDeviceOffset clear(aSurface);
float sx = aSource.Width() / aDest.Width();
float sy = aSource.Height() / aDest.Height();
cairo_matrix_t src_mat;
cairo_matrix_init_translate(&src_mat, aSource.X(), aSource.Y());
cairo_matrix_scale(&src_mat, sx, sy);
cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aSurface);
if (!surf) {
gfxWarning()
<< "Failed to create cairo surface for DrawTargetCairo::DrawSurface";
return;
}
cairo_pattern_t* pat = cairo_pattern_create_for_surface(surf);
cairo_surface_destroy(surf);
cairo_pattern_set_matrix(pat, &src_mat);
cairo_pattern_set_filter(
pat, GfxSamplingFilterToCairoFilter(aSurfOptions.mSamplingFilter));
cairo_pattern_set_extend(pat, CAIRO_EXTEND_PAD);
cairo_set_antialias(mContext,
GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
// If the destination rect covers the entire clipped area, then unbounded and
// bounded operations are identical, and we don't need to push a group.
bool needsGroup = !IsOperatorBoundByMask(aOptions.mCompositionOp) &&
!aDest.Contains(GetUserSpaceClip());
cairo_translate(mContext, aDest.X(), aDest.Y());
if (needsGroup) {
cairo_push_group(mContext);
cairo_new_path(mContext);
cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height());
cairo_set_source(mContext, pat);
cairo_fill(mContext);
cairo_pop_group_to_source(mContext);
} else {
cairo_new_path(mContext);
cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height());
cairo_clip(mContext);
cairo_set_source(mContext, pat);
}
PaintWithAlpha(mContext, aOptions);
cairo_pattern_destroy(pat);
}
void DrawTargetCairo::DrawFilter(FilterNode* aNode, const Rect& aSourceRect,
const Point& aDestPoint,
const DrawOptions& aOptions) {
FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode);
filter->Draw(this, aSourceRect, aDestPoint, aOptions);
}
void DrawTargetCairo::DrawSurfaceWithShadow(SourceSurface* aSurface,
const Point& aDest,
const Color& aColor,
const Point& aOffset, Float aSigma,
CompositionOp aOperator) {
if (aSurface->GetType() != SurfaceType::CAIRO) {
return;
}
AutoClearDeviceOffset clear(aSurface);
Float width = Float(aSurface->GetSize().width);
Float height = Float(aSurface->GetSize().height);
SourceSurfaceCairo* source = static_cast<SourceSurfaceCairo*>(aSurface);
cairo_surface_t* sourcesurf = source->GetSurface();
cairo_surface_t* blursurf;
cairo_surface_t* surf;
// We only use the A8 surface for blurred shadows. Unblurred shadows can just
// use the RGBA surface directly.
if (cairo_surface_get_type(sourcesurf) == CAIRO_SURFACE_TYPE_TEE) {
blursurf = cairo_tee_surface_index(sourcesurf, 0);
surf = cairo_tee_surface_index(sourcesurf, 1);
} else {
blursurf = sourcesurf;
surf = sourcesurf;
}
if (aSigma != 0.0f) {
MOZ_ASSERT(cairo_surface_get_type(blursurf) == CAIRO_SURFACE_TYPE_IMAGE);
Rect extents(0, 0, width, height);
AlphaBoxBlur blur(extents, cairo_image_surface_get_stride(blursurf), aSigma,
aSigma);
blur.Blur(cairo_image_surface_get_data(blursurf));
}
WillChange();
ClearSurfaceForUnboundedSource(aOperator);
cairo_save(mContext);
cairo_set_operator(mContext, GfxOpToCairoOp(aOperator));
cairo_identity_matrix(mContext);
cairo_translate(mContext, aDest.x, aDest.y);
bool needsGroup = !IsOperatorBoundByMask(aOperator);
if (needsGroup) {
cairo_push_group(mContext);
}
cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a);
cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y);
if (blursurf != surf || aSurface->GetFormat() != SurfaceFormat::A8) {
// Now that the shadow has been drawn, we can draw the surface on top.
cairo_set_source_surface(mContext, surf, 0, 0);
cairo_new_path(mContext);
cairo_rectangle(mContext, 0, 0, width, height);
cairo_fill(mContext);
}
if (needsGroup) {
cairo_pop_group_to_source(mContext);
cairo_paint(mContext);
}
cairo_restore(mContext);
}
void DrawTargetCairo::DrawPattern(const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions,
DrawPatternType aDrawType,
bool aPathBoundsClip) {
if (!PatternIsCompatible(aPattern)) {
return;
}
AutoClearDeviceOffset clear(aPattern);
cairo_pattern_t* pat =
GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform());
if (!pat) {
return;
}
if (cairo_pattern_status(pat)) {
cairo_pattern_destroy(pat);
gfxWarning() << "Invalid pattern";
return;
}
cairo_set_source(mContext, pat);
cairo_set_antialias(mContext,
GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
if (NeedIntermediateSurface(aPattern, aOptions) ||
(!IsOperatorBoundByMask(aOptions.mCompositionOp) && !aPathBoundsClip)) {
cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA);
// Don't want operators to be applied twice
cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
if (aDrawType == DRAW_STROKE) {
SetCairoStrokeOptions(mContext, aStrokeOptions);
cairo_stroke_preserve(mContext);
} else {
cairo_fill_preserve(mContext);
}
cairo_pop_group_to_source(mContext);
// Now draw the content using the desired operator
PaintWithAlpha(mContext, aOptions);
} else {
cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
if (aDrawType == DRAW_STROKE) {
SetCairoStrokeOptions(mContext, aStrokeOptions);
cairo_stroke_preserve(mContext);
} else {
cairo_fill_preserve(mContext);
}
}
cairo_pattern_destroy(pat);
}
void DrawTargetCairo::FillRect(const Rect& aRect, const Pattern& aPattern,
const DrawOptions& aOptions) {
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext);
bool restoreTransform = false;
Matrix mat;
Rect r = aRect;
/* Clamp coordinates to work around a design bug in cairo */
if (r.Width() > CAIRO_COORD_MAX || r.Height() > CAIRO_COORD_MAX ||
r.X() < -CAIRO_COORD_MAX || r.X() > CAIRO_COORD_MAX ||
r.Y() < -CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX) {
if (!mat.IsRectilinear()) {
gfxWarning() << "DrawTargetCairo::FillRect() misdrawing huge Rect "
"with non-rectilinear transform";
}
mat = GetTransform();
r = mat.TransformBounds(r);
if (!ConditionRect(r)) {
gfxWarning() << "Ignoring DrawTargetCairo::FillRect() call with "
"out-of-bounds Rect";
return;
}
restoreTransform = true;
SetTransform(Matrix());
}
cairo_new_path(mContext);
cairo_rectangle(mContext, r.X(), r.Y(), r.Width(), r.Height());
bool pathBoundsClip = false;
if (r.Contains(GetUserSpaceClip())) {
pathBoundsClip = true;
}
DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL, pathBoundsClip);
if (restoreTransform) {
SetTransform(mat);
}
}
void DrawTargetCairo::CopySurfaceInternal(cairo_surface_t* aSurface,
const IntRect& aSource,
const IntPoint& aDest) {
if (cairo_surface_status(aSurface)) {
gfxWarning() << "Invalid surface" << cairo_surface_status(aSurface);
return;
}
cairo_identity_matrix(mContext);
cairo_set_source_surface(mContext, aSurface, aDest.x - aSource.X(),
aDest.y - aSource.Y());
cairo_set_operator(mContext, CAIRO_OPERATOR_SOURCE);
cairo_set_antialias(mContext, CAIRO_ANTIALIAS_NONE);
cairo_reset_clip(mContext);
cairo_new_path(mContext);
cairo_rectangle(mContext, aDest.x, aDest.y, aSource.Width(),
aSource.Height());
cairo_fill(mContext);
}
void DrawTargetCairo::CopySurface(SourceSurface* aSurface,
const IntRect& aSource,
const IntPoint& aDest) {
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext);
AutoClearDeviceOffset clear(aSurface);
if (!aSurface) {
gfxWarning() << "Unsupported surface type specified";
return;
}
cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aSurface);
if (!surf) {
gfxWarning() << "Unsupported surface type specified";
return;
}
CopySurfaceInternal(surf, aSource, aDest);
cairo_surface_destroy(surf);
}
void DrawTargetCairo::CopyRect(const IntRect& aSource, const IntPoint& aDest) {
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext);
IntRect source = aSource;
cairo_surface_t* surf = mSurface;
if (!SupportsSelfCopy(mSurface) && aSource.ContainsY(aDest.y)) {
cairo_surface_t* similar = cairo_surface_create_similar(
mSurface, GfxFormatToCairoContent(GetFormat()), aSource.Width(),
aSource.Height());
cairo_t* ctx = cairo_create(similar);
cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface(ctx, surf, -aSource.X(), -aSource.Y());
cairo_paint(ctx);
cairo_destroy(ctx);
source.MoveTo(0, 0);
surf = similar;
}
CopySurfaceInternal(surf, source, aDest);
if (surf != mSurface) {
cairo_surface_destroy(surf);
}
}
void DrawTargetCairo::ClearRect(const Rect& aRect) {
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext);
if (!mContext || aRect.Width() < 0 || aRect.Height() < 0 ||
!IsFinite(aRect.X()) || !IsFinite(aRect.Width()) ||
!IsFinite(aRect.Y()) || !IsFinite(aRect.Height())) {
gfxCriticalNote << "ClearRect with invalid argument " << gfx::hexa(mContext)
<< " with " << aRect.Width() << "x" << aRect.Height()
<< " [" << aRect.X() << ", " << aRect.Y() << "]";
}
cairo_set_antialias(mContext, CAIRO_ANTIALIAS_NONE);
cairo_new_path(mContext);
cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR);
cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(),
aRect.Height());
cairo_fill(mContext);
}
void DrawTargetCairo::StrokeRect(
const Rect& aRect, const Pattern& aPattern,
const StrokeOptions& aStrokeOptions /* = StrokeOptions() */,
const DrawOptions& aOptions /* = DrawOptions() */) {
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext);
cairo_new_path(mContext);
cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(),
aRect.Height());
DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
}
void DrawTargetCairo::StrokeLine(
const Point& aStart, const Point& aEnd, const Pattern& aPattern,
const StrokeOptions& aStrokeOptions /* = StrokeOptions() */,
const DrawOptions& aOptions /* = DrawOptions() */) {
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext);
cairo_new_path(mContext);
cairo_move_to(mContext, aStart.x, aStart.y);
cairo_line_to(mContext, aEnd.x, aEnd.y);
DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
}
void DrawTargetCairo::Stroke(
const Path* aPath, const Pattern& aPattern,
const StrokeOptions& aStrokeOptions /* = StrokeOptions() */,
const DrawOptions& aOptions /* = DrawOptions() */) {
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext, aPath);
if (aPath->GetBackendType() != BackendType::CAIRO) return;
PathCairo* path =
const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
path->SetPathOnContext(mContext);
DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
}
void DrawTargetCairo::Fill(const Path* aPath, const Pattern& aPattern,
const DrawOptions& aOptions /* = DrawOptions() */) {
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext, aPath);
if (aPath->GetBackendType() != BackendType::CAIRO) return;
PathCairo* path =
const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
path->SetPathOnContext(mContext);
DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL);
}
bool DrawTargetCairo::IsCurrentGroupOpaque() {
cairo_surface_t* surf = cairo_get_group_target(mContext);
if (!surf) {
return false;
}
return cairo_surface_get_content(surf) == CAIRO_CONTENT_COLOR;
}
void DrawTargetCairo::SetFontOptions() {
// This will attempt to detect if the currently set scaled font on the
// context has enabled subpixel AA. If it is not permitted, then it will
// downgrade to grayscale AA.
// This only currently works effectively for the cairo-ft backend relative
// to system defaults, as only cairo-ft reflect system defaults in the scaled
// font state. However, this will work for cairo-ft on both tree Cairo and
// system Cairo.
// Other backends leave the CAIRO_ANTIALIAS_DEFAULT setting untouched while
// potentially interpreting it as subpixel or even other types of AA that
// can't be safely equivocated with grayscale AA. For this reason we don't
// try to also detect and modify the default AA setting, only explicit
// subpixel AA. These other backends must instead rely on tree Cairo's
// cairo_surface_set_subpixel_antialiasing extension.
// If allowing subpixel AA, then leave Cairo's default AA state.
if (mPermitSubpixelAA) {
return;
}
if (!mFontOptions) {
mFontOptions = cairo_font_options_create();
if (!mFontOptions) {
gfxWarning() << "Failed allocating Cairo font options";
return;
}
}
// If the current font requests subpixel AA, force it to gray since we don't
// allow subpixel AA.
cairo_get_font_options(mContext, mFontOptions);
cairo_antialias_t antialias = cairo_font_options_get_antialias(mFontOptions);
if (antialias == CAIRO_ANTIALIAS_SUBPIXEL) {
cairo_font_options_set_antialias(mFontOptions, CAIRO_ANTIALIAS_GRAY);
cairo_set_font_options(mContext, mFontOptions);
}
}
void DrawTargetCairo::SetPermitSubpixelAA(bool aPermitSubpixelAA) {
DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA);
#ifdef MOZ_TREE_CAIRO
cairo_surface_set_subpixel_antialiasing(
cairo_get_group_target(mContext),
aPermitSubpixelAA ? CAIRO_SUBPIXEL_ANTIALIASING_ENABLED
: CAIRO_SUBPIXEL_ANTIALIASING_DISABLED);
#endif
}
static bool SupportsVariationSettings(cairo_surface_t* surface) {
switch (cairo_surface_get_type(surface)) {
case CAIRO_SURFACE_TYPE_PDF:
case CAIRO_SURFACE_TYPE_PS:
return false;
default:
return true;
}
}
void DrawTargetCairo::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
const Pattern& aPattern,
const DrawOptions& aOptions) {
if (mTransformSingular) {
return;
}
if (!IsValid()) {
gfxDebug() << "FillGlyphs bad surface "
<< cairo_surface_status(cairo_get_group_target(mContext));
return;
}
if (!aFont) {
gfxDevCrash(LogReason::InvalidFont) << "Invalid scaled font";
return;
}
AutoPrepareForDrawing prep(this, mContext);
AutoClearDeviceOffset clear(aPattern);
ScaledFontBase* scaledFont = static_cast<ScaledFontBase*>(aFont);
cairo_set_scaled_font(mContext, scaledFont->GetCairoScaledFont());
cairo_pattern_t* pat =
GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform());
if (!pat) return;
cairo_set_source(mContext, pat);
cairo_pattern_destroy(pat);
cairo_set_antialias(mContext,
GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
// Override any font-specific options as necessary.
SetFontOptions();
// Convert our GlyphBuffer into a vector of Cairo glyphs. This code can
// execute millions of times in short periods, so we want to avoid heap
// allocation whenever possible. So we use an inline vector capacity of 1024
// bytes (the maximum allowed by mozilla::Vector), which gives an inline
// length of 1024 / 24 = 42 elements, which is enough to typically avoid heap
// allocation in ~99% of cases.
Vector<cairo_glyph_t, 1024 / sizeof(cairo_glyph_t)> glyphs;
if (!glyphs.resizeUninitialized(aBuffer.mNumGlyphs)) {
gfxDevCrash(LogReason::GlyphAllocFailedCairo) << "glyphs allocation failed";
return;
}
for (uint32_t i = 0; i < aBuffer.mNumGlyphs; ++i) {
glyphs[i].index = aBuffer.mGlyphs[i].mIndex;
glyphs[i].x = aBuffer.mGlyphs[i].mPosition.x;
glyphs[i].y = aBuffer.mGlyphs[i].mPosition.y;
}
if (!SupportsVariationSettings(mSurface) && aFont->HasVariationSettings() &&
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);
}
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;
}
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;
}
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() 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