tenfourfox/gfx/2d/DrawTargetCairo.cpp

1928 lines
56 KiB
C++

/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DrawTargetCairo.h"
#include "SourceSurfaceCairo.h"
#include "PathCairo.h"
#include "HelpersCairo.h"
#include "ScaledFontBase.h"
#include "BorrowedContext.h"
#include "FilterNodeSoftware.h"
#include "mozilla/Scoped.h"
#include "mozilla/Vector.h"
#include "cairo.h"
#include "cairo-tee.h"
#include <string.h>
#include "Blur.h"
#include "Logging.h"
#include "Tools.h"
#ifdef CAIRO_HAS_QUARTZ_SURFACE
#include "cairo-quartz.h"
#ifdef MOZ_WIDGET_COCOA
#include <ApplicationServices/ApplicationServices.h>
#endif
#endif
#ifdef CAIRO_HAS_XLIB_SURFACE
#include "cairo-xlib.h"
#include "cairo-xlib-xrender.h"
#endif
#ifdef CAIRO_HAS_WIN32_SURFACE
#include "cairo-win32.h"
#endif
#include <algorithm>
// 2^23
#define CAIRO_COORD_MAX (Float(0x7fffff))
namespace mozilla {
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCairoSurface, cairo_surface_t, cairo_surface_destroy);
namespace gfx {
cairo_surface_t *DrawTargetCairo::mDummySurface;
namespace {
// An RAII class to prepare to draw a context and optional path. Saves and
// restores the context on construction/destruction.
class AutoPrepareForDrawing
{
public:
AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx)
: mCtx(ctx)
{
dt->PrepareForDrawing(ctx);
cairo_save(mCtx);
MOZ_ASSERT(cairo_status(mCtx) || dt->GetTransform() == GetTransform());
}
AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx, const Path* path)
: mCtx(ctx)
{
dt->PrepareForDrawing(ctx, path);
cairo_save(mCtx);
MOZ_ASSERT(cairo_status(mCtx) || dt->GetTransform() == GetTransform());
}
~AutoPrepareForDrawing()
{
cairo_restore(mCtx);
cairo_status_t status = cairo_status(mCtx);
if (status) {
gfxWarning() << "DrawTargetCairo context in error state: " << cairo_status_to_string(status) << "(" << status << ")";
}
}
private:
#ifdef DEBUG
Matrix GetTransform()
{
cairo_matrix_t mat;
cairo_get_matrix(mCtx, &mat);
return Matrix(mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0);
}
#endif
cairo_t* mCtx;
};
/* Clamp r to (0,0) (2^23,2^23)
* these are to be device coordinates.
*
* Returns false if the rectangle is completely out of bounds,
* true otherwise.
*
* This function assumes that it will be called with a rectangle being
* drawn into a surface with an identity transformation matrix; that
* is, anything above or to the left of (0,0) will be offscreen.
*
* First it checks if the rectangle is entirely beyond
* CAIRO_COORD_MAX; if so, it can't ever appear on the screen --
* false is returned.
*
* Then it shifts any rectangles with x/y < 0 so that x and y are = 0,
* and adjusts the width and height appropriately. For example, a
* rectangle from (0,-5) with dimensions (5,10) will become a
* rectangle from (0,0) with dimensions (5,5).
*
* If after negative x/y adjustment to 0, either the width or height
* is negative, then the rectangle is completely offscreen, and
* nothing is drawn -- false is returned.
*
* Finally, if x+width or y+height are greater than CAIRO_COORD_MAX,
* the width and height are clamped such x+width or y+height are equal
* to CAIRO_COORD_MAX, and true is returned.
*/
static bool
ConditionRect(Rect& r) {
// if either x or y is way out of bounds;
// note that we don't handle negative w/h here
if (r.X() > CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX)
return false;
if (r.X() < 0.f) {
r.width += r.X();
if (r.width < 0.f)
return false;
r.x = 0.f;
}
if (r.XMost() > CAIRO_COORD_MAX) {
r.width = CAIRO_COORD_MAX - r.X();
}
if (r.Y() < 0.f) {
r.height += r.Y();
if (r.Height() < 0.f)
return false;
r.y = 0.f;
}
if (r.YMost() > CAIRO_COORD_MAX) {
r.height = CAIRO_COORD_MAX - r.Y();
}
return true;
}
} // end anonymous namespace
static bool
SupportsSelfCopy(cairo_surface_t* surface)
{
switch (cairo_surface_get_type(surface))
{
#ifdef CAIRO_HAS_QUARTZ_SURFACE
case CAIRO_SURFACE_TYPE_QUARTZ:
return true;
#endif
#ifdef CAIRO_HAS_WIN32_SURFACE
case CAIRO_SURFACE_TYPE_WIN32:
case CAIRO_SURFACE_TYPE_WIN32_PRINTING:
return true;
#endif
default:
return false;
}
}
static bool
PatternIsCompatible(const Pattern& aPattern)
{
switch (aPattern.GetType())
{
case PatternType::LINEAR_GRADIENT:
{
const LinearGradientPattern& pattern = static_cast<const LinearGradientPattern&>(aPattern);
return pattern.mStops->GetBackendType() == BackendType::CAIRO;
}
case PatternType::RADIAL_GRADIENT:
{
const RadialGradientPattern& pattern = static_cast<const RadialGradientPattern&>(aPattern);
return pattern.mStops->GetBackendType() == BackendType::CAIRO;
}
default:
return true;
}
}
static cairo_user_data_key_t surfaceDataKey;
void
ReleaseData(void* aData)
{
DataSourceSurface *data = static_cast<DataSourceSurface*>(aData);
//data->Unmap(); // See backout of bug 1035168 below.
data->Release();
}
cairo_surface_t*
CopyToImageSurface(unsigned char *aData,
const IntRect &aRect,
int32_t aStride,
SurfaceFormat aFormat)
{
MOZ_ASSERT(aData);
cairo_surface_t* surf = cairo_image_surface_create(GfxFormatToCairoFormat(aFormat),
aRect.width,
aRect.height);
// In certain scenarios, requesting larger than 8k image fails. Bug 803568
// covers the details of how to run into it, but the full detailed
// investigation hasn't been done to determine the underlying cause. We
// will just handle the failure to allocate the surface to avoid a crash.
if (cairo_surface_status(surf)) {
gfxWarning() << "Invalid surface DTC " << cairo_surface_status(surf);
return nullptr;
}
unsigned char* surfData = cairo_image_surface_get_data(surf);
int surfStride = cairo_image_surface_get_stride(surf);
int32_t pixelWidth = BytesPerPixel(aFormat);
unsigned char* source = aData +
aRect.y * aStride +
aRect.x * pixelWidth;
MOZ_ASSERT(aStride >= aRect.width * pixelWidth);
for (int32_t y = 0; y < aRect.height; ++y) {
memcpy(surfData + y * surfStride,
source + y * aStride,
aRect.width * pixelWidth);
}
cairo_surface_mark_dirty(surf);
return surf;
}
/**
* If aSurface can be represented as a surface of type
* CAIRO_SURFACE_TYPE_IMAGE then returns that surface. Does
* not add a reference.
*/
cairo_surface_t* GetAsImageSurface(cairo_surface_t* aSurface)
{
if (cairo_surface_get_type(aSurface) == CAIRO_SURFACE_TYPE_IMAGE) {
return aSurface;
#ifdef CAIRO_HAS_WIN32_SURFACE
} else if (cairo_surface_get_type(aSurface) == CAIRO_SURFACE_TYPE_WIN32) {
return cairo_win32_surface_get_image(aSurface);
#endif
}
return nullptr;
}
// We're creating a subimage from the parent image's data (in aData) without
// altering that data or its stride. This constrains the values in aRect, and
// how they're used. Callers must see to it that the parent fully contains the
// subimage. Here we ensure that no clipping is done in the X dimension at the
// beginning of any line. (To do otherwise would require creating a copy of
// aData from parts of every line in aData (from aRect.Y() to aRect.Height()),
// and setting the copy to a different stride.) A non-zero aRect.X() is used
// only to specify the subimage's location in its parent (via
// cairo_surface_set_device_offset()). This change resolves bug 1719215.
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;
cairo_surface_t *image =
cairo_image_surface_create_for_data(data,
GfxFormatToCairoFormat(aFormat),
aRect.width,
aRect.height,
aStride);
// Set the subimage's location in its parent
cairo_surface_set_device_offset(image, -aRect.x, -aRect.y);
return image;
}
/**
* Returns a referenced cairo_surface_t representing the
* sub-image specified by aSubImage.
*/
cairo_surface_t* ExtractSubImage(cairo_surface_t* aSurface,
const IntRect& aSubImage,
SurfaceFormat aFormat)
{
// No need to worry about retaining a reference to the original
// surface since the only caller of this function guarantees
// that aSurface will stay alive as long as the result
cairo_surface_t* image = GetAsImageSurface(aSurface);
if (image) {
image = CreateSubImageForData(cairo_image_surface_get_data(image),
aSubImage,
cairo_image_surface_get_stride(image),
aFormat);
return image;
}
cairo_surface_t* similar =
cairo_surface_create_similar(aSurface,
cairo_surface_get_content(aSurface),
aSubImage.width, aSubImage.height);
cairo_t* ctx = cairo_create(similar);
cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface(ctx, aSurface, -aSubImage.x, -aSubImage.y);
cairo_paint(ctx);
cairo_destroy(ctx);
cairo_surface_set_device_offset(similar, -aSubImage.x, -aSubImage.y);
return similar;
}
/**
* Returns cairo surface for the given SourceSurface.
* If possible, it will use the cairo_surface associated with aSurface,
* otherwise, it will create a new cairo_surface.
* In either case, the caller must call cairo_surface_destroy on the
* result when it is done with it.
*/
cairo_surface_t*
GetCairoSurfaceForSourceSurface(SourceSurface *aSurface,
bool aExistingOnly = false,
const IntRect& aSubImage = IntRect())
{
if (!aSurface) {
return nullptr;
}
IntRect subimage = IntRect(IntPoint(), aSurface->GetSize());
if (!aSubImage.IsEmpty()) {
MOZ_ASSERT(!aExistingOnly);
MOZ_ASSERT(subimage.Contains(aSubImage));
subimage = aSubImage;
}
if (aSurface->GetType() == SurfaceType::CAIRO) {
cairo_surface_t* surf = static_cast<SourceSurfaceCairo*>(aSurface)->GetSurface();
if (aSubImage.IsEmpty()) {
cairo_surface_reference(surf);
} else {
surf = ExtractSubImage(surf, subimage, aSurface->GetFormat());
}
return surf;
}
if (aSurface->GetType() == SurfaceType::CAIRO_IMAGE) {
cairo_surface_t* surf =
static_cast<const DataSourceSurfaceCairo*>(aSurface)->GetSurface();
if (aSubImage.IsEmpty()) {
cairo_surface_reference(surf);
} else {
surf = ExtractSubImage(surf, subimage, aSurface->GetFormat());
}
return surf;
}
if (aExistingOnly) {
return nullptr;
}
RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
if (!data) {
return nullptr;
}
// Backout bug 1035168. For some reason this doesn't map properly for us, and
// we don't actually need the hack. ("Assertion failure: mIsMapped" when
// printing complex pages; text-only pages don't seem to trigger this.)
#if(0)
DataSourceSurface::MappedSurface map;
if (!data->Map(DataSourceSurface::READ, &map)) {
return nullptr;
}
#endif
cairo_surface_t* surf =
CreateSubImageForData(data->GetData(), subimage, // map.mData, subimage,
data->Stride(), data->GetFormat()); // 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(0)
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();
#endif
return nullptr;
}
// End backout
cairo_surface_set_user_data(surf,
&surfaceDataKey,
data.forget().take(),
ReleaseData);
return surf;
}
// An RAII class to temporarily clear any device offset set
// on a surface. Note that this does not take a reference to the
// surface.
class AutoClearDeviceOffset
{
public:
explicit AutoClearDeviceOffset(SourceSurface* aSurface)
: mSurface(nullptr)
, mX(0)
, mY(0)
{
Init(aSurface);
}
explicit AutoClearDeviceOffset(const Pattern& aPattern)
: mSurface(nullptr)
{
if (aPattern.GetType() == PatternType::SURFACE) {
const SurfacePattern& pattern = static_cast<const SurfacePattern&>(aPattern);
Init(pattern.mSurface);
}
}
~AutoClearDeviceOffset()
{
if (mSurface) {
cairo_surface_set_device_offset(mSurface, mX, mY);
}
}
private:
void Init(SourceSurface* aSurface)
{
cairo_surface_t* surface = GetCairoSurfaceForSourceSurface(aSurface, true);
if (surface) {
Init(surface);
cairo_surface_destroy(surface);
}
}
void Init(cairo_surface_t *aSurface)
{
mSurface = aSurface;
cairo_surface_get_device_offset(mSurface, &mX, &mY);
cairo_surface_set_device_offset(mSurface, 0, 0);
}
cairo_surface_t* mSurface;
double mX;
double mY;
};
static inline void
CairoPatternAddGradientStop(cairo_pattern_t* aPattern,
const GradientStop &aStop,
Float aNudge = 0)
{
cairo_pattern_add_color_stop_rgba(aPattern, aStop.offset + aNudge,
aStop.color.r, aStop.color.g, aStop.color.b,
aStop.color.a);
}
// Never returns nullptr. As such, you must always pass in Cairo-compatible
// patterns, most notably gradients with a GradientStopCairo.
// The pattern returned must have cairo_pattern_destroy() called on it by the
// caller.
// As the cairo_pattern_t returned may depend on the Pattern passed in, the
// lifetime of the cairo_pattern_t returned must not exceed the lifetime of the
// Pattern passed in.
static cairo_pattern_t*
GfxPatternToCairoPattern(const Pattern& aPattern,
Float aAlpha,
const Matrix& aTransform)
{
cairo_pattern_t* pat;
const Matrix* matrix = nullptr;
switch (aPattern.GetType())
{
case PatternType::COLOR:
{
Color color = static_cast<const ColorPattern&>(aPattern).mColor;
pat = cairo_pattern_create_rgba(color.r, color.g, color.b, color.a * aAlpha);
break;
}
case PatternType::SURFACE:
{
const SurfacePattern& pattern = static_cast<const SurfacePattern&>(aPattern);
cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(pattern.mSurface,
false,
pattern.mSamplingRect);
if (!surf)
return nullptr;
pat = cairo_pattern_create_for_surface(surf);
matrix = &pattern.mMatrix;
cairo_pattern_set_filter(pat, GfxFilterToCairoFilter(pattern.mFilter));
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)
{
}
DrawTargetCairo::~DrawTargetCairo()
{
cairo_destroy(mContext);
if (mSurface) {
cairo_surface_destroy(mSurface);
mSurface = nullptr;
}
MOZ_ASSERT(!mLockedBits);
}
bool
DrawTargetCairo::IsValid() const
{
return mSurface && !cairo_surface_status(mSurface);
}
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_ASSERT(false, "Can't determine actual DrawTargetType for DrawTargetCairo - assuming SOFTWARE_RASTER");
// fallthrough
case CAIRO_SURFACE_TYPE_IMAGE:
case CAIRO_SURFACE_TYPE_XLIB:
case CAIRO_SURFACE_TYPE_XCB:
case CAIRO_SURFACE_TYPE_WIN32:
case CAIRO_SURFACE_TYPE_BEOS:
case CAIRO_SURFACE_TYPE_OS2:
case CAIRO_SURFACE_TYPE_QUARTZ_IMAGE:
case CAIRO_SURFACE_TYPE_SCRIPT:
case CAIRO_SURFACE_TYPE_RECORDING:
case CAIRO_SURFACE_TYPE_DRM:
case CAIRO_SURFACE_TYPE_SUBSURFACE:
case CAIRO_SURFACE_TYPE_TEE: // included to silence warning about unhandled enum value
return DrawTargetType::SOFTWARE_RASTER;
default:
MOZ_CRASH("GFX: Unsupported cairo surface type");
}
}
MOZ_ASSERT(false, "Could not determine DrawTargetType for DrawTargetCairo");
return DrawTargetType::SOFTWARE_RASTER;
}
IntSize
DrawTargetCairo::GetSize()
{
return mSize;
}
SurfaceFormat
GfxFormatForCairoSurface(cairo_surface_t* surface)
{
cairo_surface_type_t type = cairo_surface_get_type(surface);
if (type == CAIRO_SURFACE_TYPE_IMAGE) {
return CairoFormatToGfxFormat(cairo_image_surface_get_format(surface));
}
#ifdef CAIRO_HAS_XLIB_SURFACE
// xlib is currently the only Cairo backend that creates 16bpp surfaces
if (type == CAIRO_SURFACE_TYPE_XLIB &&
cairo_xlib_surface_get_depth(surface) == 16) {
return SurfaceFormat::R5G6B5_UINT16;
}
#endif
return CairoContentToGfxFormat(cairo_surface_get_content(surface));
}
already_AddRefed<SourceSurface>
DrawTargetCairo::Snapshot()
{
if (!IsValid()) {
gfxCriticalNote << "DrawTargetCairo::Snapshot with bad surface " << cairo_surface_status(mSurface);
return nullptr;
}
if (mSnapshot) {
RefPtr<SourceSurface> snapshot(mSnapshot);
return snapshot.forget();
}
IntSize size = GetSize();
mSnapshot = new SourceSurfaceCairo(mSurface,
size,
GfxFormatForCairoSurface(mSurface),
this);
RefPtr<SourceSurface> snapshot(mSnapshot);
return snapshot.forget();
}
bool
DrawTargetCairo::LockBits(uint8_t** aData, IntSize* aSize,
int32_t* aStride, SurfaceFormat* aFormat)
{
if (cairo_surface_get_type(mSurface) == CAIRO_SURFACE_TYPE_IMAGE) {
WillChange();
Flush();
mLockedBits = cairo_image_surface_get_data(mSurface);
*aData = mLockedBits;
*aSize = GetSize();
*aStride = cairo_image_surface_get_stride(mSurface);
*aFormat = GetFormat();
return true;
}
return false;
}
void
DrawTargetCairo::ReleaseBits(uint8_t* aData)
{
MOZ_ASSERT(mLockedBits == aData);
mLockedBits = nullptr;
cairo_surface_mark_dirty(mSurface);
}
void
DrawTargetCairo::Flush()
{
cairo_surface_t* surf = cairo_get_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) {
return;
}
if (!IsValid() || !aSurface) {
gfxCriticalNote << "DrawSurface with bad surface " << cairo_surface_status(mSurface);
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);
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, GfxFilterToCairoFilter(aSurfOptions.mFilter));
cairo_pattern_set_extend(pat, CAIRO_EXTEND_PAD);
cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
// If the destination rect covers the entire clipped area, then unbounded and bounded
// operations are identical, and we don't need to push a group.
bool needsGroup = !IsOperatorBoundByMask(aOptions.mCompositionOp) &&
!aDest.Contains(GetUserSpaceClip());
cairo_translate(mContext, aDest.X(), aDest.Y());
if (needsGroup) {
cairo_push_group(mContext);
cairo_new_path(mContext);
cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height());
cairo_set_source(mContext, pat);
cairo_fill(mContext);
cairo_pop_group_to_source(mContext);
} else {
cairo_new_path(mContext);
cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height());
cairo_clip(mContext);
cairo_set_source(mContext, pat);
}
PaintWithAlpha(mContext, aOptions);
cairo_pattern_destroy(pat);
}
void
DrawTargetCairo::DrawFilter(FilterNode *aNode,
const Rect &aSourceRect,
const Point &aDestPoint,
const DrawOptions &aOptions)
{
FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode);
filter->Draw(this, aSourceRect, aDestPoint, aOptions);
}
void
DrawTargetCairo::DrawSurfaceWithShadow(SourceSurface *aSurface,
const Point &aDest,
const Color &aColor,
const Point &aOffset,
Float aSigma,
CompositionOp aOperator)
{
if (aSurface->GetType() != SurfaceType::CAIRO) {
return;
}
AutoClearDeviceOffset clear(aSurface);
Float width = Float(aSurface->GetSize().width);
Float height = Float(aSurface->GetSize().height);
SourceSurfaceCairo* source = static_cast<SourceSurfaceCairo*>(aSurface);
cairo_surface_t* sourcesurf = source->GetSurface();
cairo_surface_t* blursurf;
cairo_surface_t* surf;
// We only use the A8 surface for blurred shadows. Unblurred shadows can just
// use the RGBA surface directly.
if (cairo_surface_get_type(sourcesurf) == CAIRO_SURFACE_TYPE_TEE) {
blursurf = cairo_tee_surface_index(sourcesurf, 0);
surf = cairo_tee_surface_index(sourcesurf, 1);
MOZ_ASSERT(cairo_surface_get_type(blursurf) == CAIRO_SURFACE_TYPE_IMAGE);
Rect extents(0, 0, width, height);
AlphaBoxBlur blur(extents,
cairo_image_surface_get_stride(blursurf),
aSigma, aSigma);
blur.Blur(cairo_image_surface_get_data(blursurf));
} else {
blursurf = sourcesurf;
surf = sourcesurf;
}
WillChange();
ClearSurfaceForUnboundedSource(aOperator);
cairo_save(mContext);
cairo_set_operator(mContext, GfxOpToCairoOp(aOperator));
cairo_identity_matrix(mContext);
cairo_translate(mContext, aDest.x, aDest.y);
if (IsOperatorBoundByMask(aOperator)){
cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a);
cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y);
// Now that the shadow has been drawn, we can draw the surface on top.
cairo_set_source_surface(mContext, surf, 0, 0);
cairo_new_path(mContext);
cairo_rectangle(mContext, 0, 0, width, height);
cairo_fill(mContext);
} else {
cairo_push_group(mContext);
cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a);
cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y);
// Now that the shadow has been drawn, we can draw the surface on top.
cairo_set_source_surface(mContext, surf, 0, 0);
cairo_new_path(mContext);
cairo_rectangle(mContext, 0, 0, width, height);
cairo_fill(mContext);
cairo_pop_group_to_source(mContext);
cairo_paint(mContext);
}
cairo_restore(mContext);
}
void
DrawTargetCairo::DrawPattern(const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions,
DrawPatternType aDrawType,
bool aPathBoundsClip)
{
if (!PatternIsCompatible(aPattern)) {
return;
}
AutoClearDeviceOffset clear(aPattern);
cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform());
if (!pat) {
return;
}
if (cairo_pattern_status(pat)) {
cairo_pattern_destroy(pat);
gfxWarning() << "Invalid pattern";
return;
}
cairo_set_source(mContext, pat);
cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
if (NeedIntermediateSurface(aPattern, aOptions) ||
(!IsOperatorBoundByMask(aOptions.mCompositionOp) && !aPathBoundsClip)) {
cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA);
// Don't want operators to be applied twice
cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
if (aDrawType == DRAW_STROKE) {
SetCairoStrokeOptions(mContext, aStrokeOptions);
cairo_stroke_preserve(mContext);
} else {
cairo_fill_preserve(mContext);
}
cairo_pop_group_to_source(mContext);
// Now draw the content using the desired operator
PaintWithAlpha(mContext, aOptions);
} else {
cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
if (aDrawType == DRAW_STROKE) {
SetCairoStrokeOptions(mContext, aStrokeOptions);
cairo_stroke_preserve(mContext);
} else {
cairo_fill_preserve(mContext);
}
}
cairo_pattern_destroy(pat);
}
void
DrawTargetCairo::FillRect(const Rect &aRect,
const Pattern &aPattern,
const DrawOptions &aOptions)
{
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext);
bool restoreTransform = false;
Matrix mat;
Rect r = aRect;
/* Clamp coordinates to work around a design bug in cairo */
if (r.width > CAIRO_COORD_MAX ||
r.height > CAIRO_COORD_MAX ||
r.x < -CAIRO_COORD_MAX ||
r.x > CAIRO_COORD_MAX ||
r.y < -CAIRO_COORD_MAX ||
r.y > CAIRO_COORD_MAX)
{
if (!mat.IsRectilinear()) {
gfxWarning() << "DrawTargetCairo::FillRect() misdrawing huge Rect "
"with non-rectilinear transform";
}
mat = GetTransform();
r = mat.TransformBounds(r);
if (!ConditionRect(r)) {
gfxWarning() << "Ignoring DrawTargetCairo::FillRect() call with "
"out-of-bounds Rect";
return;
}
restoreTransform = true;
SetTransform(Matrix());
}
cairo_new_path(mContext);
cairo_rectangle(mContext, r.x, r.y, r.Width(), r.Height());
bool pathBoundsClip = false;
if (r.Contains(GetUserSpaceClip())) {
pathBoundsClip = true;
}
DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL, pathBoundsClip);
if (restoreTransform) {
SetTransform(mat);
}
}
void
DrawTargetCairo::CopySurfaceInternal(cairo_surface_t* aSurface,
const IntRect &aSource,
const IntPoint &aDest)
{
if (cairo_surface_status(aSurface)) {
gfxWarning() << "Invalid surface" << cairo_surface_status(aSurface);
return;
}
cairo_identity_matrix(mContext);
cairo_set_source_surface(mContext, aSurface, aDest.x - aSource.x, aDest.y - aSource.y);
cairo_set_operator(mContext, CAIRO_OPERATOR_SOURCE);
cairo_set_antialias(mContext, CAIRO_ANTIALIAS_NONE);
cairo_reset_clip(mContext);
cairo_new_path(mContext);
cairo_rectangle(mContext, aDest.x, aDest.y, aSource.width, aSource.height);
cairo_fill(mContext);
}
void
DrawTargetCairo::CopySurface(SourceSurface *aSurface,
const IntRect &aSource,
const IntPoint &aDest)
{
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext);
AutoClearDeviceOffset clear(aSurface);
if (!aSurface) {
gfxWarning() << "Unsupported surface type specified";
return;
}
cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aSurface);
if (!surf) {
gfxWarning() << "Unsupported surface type specified";
return;
}
CopySurfaceInternal(surf, aSource, aDest);
cairo_surface_destroy(surf);
}
void
DrawTargetCairo::CopyRect(const IntRect &aSource,
const IntPoint &aDest)
{
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext);
IntRect source = aSource;
cairo_surface_t* surf = mSurface;
if (!SupportsSelfCopy(mSurface) &&
aDest.y >= aSource.y &&
aDest.y < aSource.YMost()) {
cairo_surface_t* similar = cairo_surface_create_similar(mSurface,
GfxFormatToCairoContent(GetFormat()),
aSource.width, aSource.height);
cairo_t* ctx = cairo_create(similar);
cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface(ctx, surf, -aSource.x, -aSource.y);
cairo_paint(ctx);
cairo_destroy(ctx);
source.x = 0;
source.y = 0;
surf = similar;
}
CopySurfaceInternal(surf, source, aDest);
if (surf != mSurface) {
cairo_surface_destroy(surf);
}
}
void
DrawTargetCairo::ClearRect(const Rect& aRect)
{
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext);
if (!mContext || aRect.Width() <= 0 || aRect.Height() <= 0 ||
!IsFinite(aRect.X()) || !IsFinite(aRect.Width()) ||
!IsFinite(aRect.Y()) || !IsFinite(aRect.Height())) {
gfxCriticalNote << "ClearRect with invalid argument " << gfx::hexa(mContext) << " with " << aRect.Width() << "x" << aRect.Height() << " [" << aRect.X() << ", " << aRect.Y() << "]";
}
cairo_set_antialias(mContext, CAIRO_ANTIALIAS_NONE);
cairo_new_path(mContext);
cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR);
cairo_rectangle(mContext, aRect.X(), aRect.Y(),
aRect.Width(), aRect.Height());
cairo_fill(mContext);
}
void
DrawTargetCairo::StrokeRect(const Rect &aRect,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
const DrawOptions &aOptions /* = DrawOptions() */)
{
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext);
cairo_new_path(mContext);
cairo_rectangle(mContext, aRect.x, aRect.y, aRect.Width(), aRect.Height());
DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
}
void
DrawTargetCairo::StrokeLine(const Point &aStart,
const Point &aEnd,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
const DrawOptions &aOptions /* = DrawOptions() */)
{
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext);
cairo_new_path(mContext);
cairo_move_to(mContext, aStart.x, aStart.y);
cairo_line_to(mContext, aEnd.x, aEnd.y);
DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
}
void
DrawTargetCairo::Stroke(const Path *aPath,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
const DrawOptions &aOptions /* = DrawOptions() */)
{
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext, aPath);
if (aPath->GetBackendType() != BackendType::CAIRO)
return;
PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
path->SetPathOnContext(mContext);
DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
}
void
DrawTargetCairo::Fill(const Path *aPath,
const Pattern &aPattern,
const DrawOptions &aOptions /* = DrawOptions() */)
{
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext, aPath);
if (aPath->GetBackendType() != BackendType::CAIRO)
return;
PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
path->SetPathOnContext(mContext);
DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL);
}
void
DrawTargetCairo::SetPermitSubpixelAA(bool aPermitSubpixelAA)
{
DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA);
#ifdef MOZ_TREE_CAIRO
cairo_surface_set_subpixel_antialiasing(mSurface,
aPermitSubpixelAA ? CAIRO_SUBPIXEL_ANTIALIASING_ENABLED : CAIRO_SUBPIXEL_ANTIALIASING_DISABLED);
#endif
}
void
DrawTargetCairo::FillGlyphs(ScaledFont *aFont,
const GlyphBuffer &aBuffer,
const Pattern &aPattern,
const DrawOptions &aOptions,
const GlyphRenderingOptions*)
{
if (mTransformSingular) {
return;
}
if (!IsValid()) {
gfxDebug() << "FillGlyphs bad surface " << cairo_surface_status(mSurface);
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));
// Convert our GlyphBuffer into a vector of Cairo glyphs. This code can
// execute millions of times in short periods, so we want to avoid heap
// allocation whenever possible. So we use an inline vector capacity of 1024
// bytes (the maximum allowed by mozilla::Vector), which gives an inline
// length of 1024 / 24 = 42 elements, which is enough to typically avoid heap
// allocation in ~99% of cases.
Vector<cairo_glyph_t, 1024 / sizeof(cairo_glyph_t)> glyphs;
if (!glyphs.resizeUninitialized(aBuffer.mNumGlyphs)) {
gfxDevCrash(LogReason::GlyphAllocFailedCairo) << "glyphs allocation failed";
return;
}
for (uint32_t i = 0; i < aBuffer.mNumGlyphs; ++i) {
glyphs[i].index = aBuffer.mGlyphs[i].mIndex;
glyphs[i].x = aBuffer.mGlyphs[i].mPosition.x;
glyphs[i].y = aBuffer.mGlyphs[i].mPosition.y;
}
cairo_show_glyphs(mContext, &glyphs[0], aBuffer.mNumGlyphs);
if (mSurface && cairo_surface_status(mSurface)) {
gfxDebug() << "Ending FillGlyphs with a bad surface " << cairo_surface_status(mSurface);
}
}
void
DrawTargetCairo::Mask(const Pattern &aSource,
const Pattern &aMask,
const DrawOptions &aOptions /* = DrawOptions() */)
{
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext);
AutoClearDeviceOffset clearSource(aSource);
AutoClearDeviceOffset clearMask(aMask);
cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
cairo_pattern_t* source = GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform());
if (!source) {
return;
}
cairo_pattern_t* mask = GfxPatternToCairoPattern(aMask, aOptions.mAlpha, GetTransform());
if (!mask) {
cairo_pattern_destroy(source);
return;
}
if (cairo_pattern_status(source) || cairo_pattern_status(mask)) {
cairo_pattern_destroy(source);
cairo_pattern_destroy(mask);
gfxWarning() << "Invalid pattern";
return;
}
cairo_set_source(mContext, source);
cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
cairo_mask(mContext, mask);
cairo_pattern_destroy(mask);
cairo_pattern_destroy(source);
}
void
DrawTargetCairo::MaskSurface(const Pattern &aSource,
SourceSurface *aMask,
Point aOffset,
const DrawOptions &aOptions)
{
if (mTransformSingular) {
return;
}
AutoPrepareForDrawing prep(this, mContext);
AutoClearDeviceOffset clearSource(aSource);
AutoClearDeviceOffset clearMask(aMask);
if (!PatternIsCompatible(aSource)) {
return;
}
cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
cairo_pattern_t* pat = GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform());
if (!pat) {
return;
}
if (cairo_pattern_status(pat)) {
cairo_pattern_destroy(pat);
gfxWarning() << "Invalid pattern";
return;
}
cairo_set_source(mContext, pat);
if (NeedIntermediateSurface(aSource, aOptions)) {
cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA);
// Don't want operators to be applied twice
cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
// Now draw the content using the desired operator
cairo_paint_with_alpha(mContext, aOptions.mAlpha);
cairo_pop_group_to_source(mContext);
}
cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aMask);
if (!surf) {
cairo_pattern_destroy(pat);
return;
}
cairo_pattern_t* mask = cairo_pattern_create_for_surface(surf);
cairo_matrix_t matrix;
cairo_matrix_init_translate (&matrix, -aOffset.x, -aOffset.y);
cairo_pattern_set_matrix (mask, &matrix);
cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
cairo_mask(mContext, mask);
cairo_surface_destroy(surf);
cairo_pattern_destroy(mask);
cairo_pattern_destroy(pat);
}
void
DrawTargetCairo::PushClip(const Path *aPath)
{
if (aPath->GetBackendType() != BackendType::CAIRO) {
return;
}
WillChange(aPath);
cairo_save(mContext);
PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
if (mTransformSingular) {
cairo_new_path(mContext);
cairo_rectangle(mContext, 0, 0, 0, 0);
} else {
path->SetPathOnContext(mContext);
}
cairo_clip_preserve(mContext);
}
void
DrawTargetCairo::PushClipRect(const Rect& aRect)
{
WillChange();
cairo_save(mContext);
cairo_new_path(mContext);
if (mTransformSingular) {
cairo_rectangle(mContext, 0, 0, 0, 0);
} else {
cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
}
cairo_clip_preserve(mContext);
}
void
DrawTargetCairo::PopClip()
{
// save/restore does not affect the path, so no need to call WillChange()
// cairo_restore will restore the transform too and we don't want to do that
// so we'll save it now and restore it after the cairo_restore
cairo_matrix_t mat;
cairo_get_matrix(mContext, &mat);
cairo_restore(mContext);
cairo_set_matrix(mContext, &mat);
}
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();
}
ScopedDeletePtr<DestroyPixmapClosure> closure(
new 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.forget(), 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
{
if (aSurface.mType == NativeSurfaceType::CAIRO_SURFACE) {
if (aSurface.mSize.width <= 0 ||
aSurface.mSize.height <= 0) {
gfxWarning() << "Can't create a SourceSurface without a valid size";
return nullptr;
}
cairo_surface_t* surf = static_cast<cairo_surface_t*>(aSurface.mSurface);
return MakeAndAddRef<SourceSurfaceCairo>(surf, aSurface.mSize, aSurface.mFormat);
}
return nullptr;
}
already_AddRefed<DrawTarget>
DrawTargetCairo::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
{
if (cairo_surface_status(mSurface)) {
RefPtr<DrawTargetCairo> target = new DrawTargetCairo();
if (target->Init(aSize, aFormat)) {
return target.forget();
}
}
cairo_surface_t* similar = cairo_surface_create_similar(mSurface,
GfxFormatToCairoContent(aFormat),
aSize.width, aSize.height);
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(mSurface) << " format " << (int)aFormat;
return nullptr;
}
bool
DrawTargetCairo::InitAlreadyReferenced(cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat* aFormat)
{
if (cairo_surface_status(aSurface)) {
gfxCriticalNote
<< "Attempt to create DrawTarget for invalid surface. "
<< aSize << " Cairo Status: " << cairo_surface_status(aSurface);
cairo_surface_destroy(aSurface);
return false;
}
mContext = cairo_create(aSurface);
mSurface = aSurface;
mSize = aSize;
mFormat = aFormat ? *aFormat : GfxFormatForCairoSurface(aSurface);
// Cairo image surface have a bug where they will allocate a mask surface (for clipping)
// the size of the clip extents, and don't take the surface extents into account.
// Add a manual clip to the surface extents to prevent this.
cairo_new_path(mContext);
cairo_rectangle(mContext, 0, 0, mSize.width, mSize.height);
cairo_clip(mContext);
if (mFormat == SurfaceFormat::A8R8G8B8_UINT32 ||
mFormat == SurfaceFormat::R8G8B8A8) {
SetPermitSubpixelAA(false);
} else {
SetPermitSubpixelAA(true);
}
return true;
}
already_AddRefed<DrawTarget>
DrawTargetCairo::CreateShadowDrawTarget(const IntSize &aSize, SurfaceFormat aFormat,
float aSigma) const
{
cairo_surface_t* similar = cairo_surface_create_similar(cairo_get_target(mContext),
GfxFormatToCairoContent(aFormat),
aSize.width, aSize.height);
if (cairo_surface_status(similar)) {
return nullptr;
}
// If we don't have a blur then we can use the RGBA mask and keep all the
// operations in graphics memory.
if (aSigma == 0.0F) {
RefPtr<DrawTargetCairo> target = new DrawTargetCairo();
if (target->InitAlreadyReferenced(similar, aSize)) {
return target.forget();
} else {
return nullptr;
}
}
cairo_surface_t* blursurf = cairo_image_surface_create(CAIRO_FORMAT_A8,
aSize.width,
aSize.height);
if (cairo_surface_status(blursurf)) {
return nullptr;
}
cairo_surface_t* tee = cairo_tee_surface_create(blursurf);
cairo_surface_destroy(blursurf);
if (cairo_surface_status(tee)) {
cairo_surface_destroy(similar);
return nullptr;
}
cairo_tee_surface_add(tee, similar);
cairo_surface_destroy(similar);
RefPtr<DrawTargetCairo> target = new DrawTargetCairo();
if (target->InitAlreadyReferenced(tee, aSize)) {
return target.forget();
}
return nullptr;
}
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_SURFACE) {
return cairo_get_target(mContext);
}
if (aType == NativeSurfaceType::CAIRO_CONTEXT) {
return mContext;
}
return nullptr;
}
void
DrawTargetCairo::MarkSnapshotIndependent()
{
if (mSnapshot) {
if (mSnapshot->refCount() > 1) {
// We only need to worry about snapshots that someone else knows about
mSnapshot->DrawTargetWillChange();
}
mSnapshot = nullptr;
}
}
void
DrawTargetCairo::WillChange(const Path* aPath /* = nullptr */)
{
MarkSnapshotIndependent();
MOZ_ASSERT(!mLockedBits);
}
void
DrawTargetCairo::SetTransform(const Matrix& aTransform)
{
DrawTarget::SetTransform(aTransform);
mTransformSingular = aTransform.IsSingular();
if (!mTransformSingular) {
cairo_matrix_t mat;
GfxMatrixToCairoMatrix(mTransform, mat);
cairo_set_matrix(mContext, &mat);
}
}
Rect
DrawTargetCairo::GetUserSpaceClip()
{
double clipX1, clipY1, clipX2, clipY2;
cairo_clip_extents(mContext, &clipX1, &clipY1, &clipX2, &clipY2);
return Rect(clipX1, clipY1, clipX2 - clipX1, clipY2 - clipY1); // Narrowing of doubles to floats
}
cairo_t*
BorrowedCairoContext::BorrowCairoContextFromDrawTarget(DrawTarget* aDT)
{
if (aDT->GetBackendType() != BackendType::CAIRO ||
aDT->IsDualDrawTarget() ||
aDT->IsTiledDrawTarget()) {
return nullptr;
}
DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(aDT);
cairoDT->WillChange();
// save the state to make it easier for callers to avoid mucking with things
cairo_save(cairoDT->mContext);
// Neuter the DrawTarget while the context is being borrowed
cairo_t* cairo = cairoDT->mContext;
cairoDT->mContext = nullptr;
return cairo;
}
void
BorrowedCairoContext::ReturnCairoContextToDrawTarget(DrawTarget* aDT,
cairo_t* aCairo)
{
if (aDT->GetBackendType() != BackendType::CAIRO ||
aDT->IsDualDrawTarget() ||
aDT->IsTiledDrawTarget()) {
return;
}
DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(aDT);
cairo_restore(aCairo);
cairoDT->mContext = aCairo;
}
#ifdef MOZ_X11
bool
BorrowedXlibDrawable::Init(DrawTarget* aDT)
{
MOZ_ASSERT(aDT, "Caller should check for nullptr");
MOZ_ASSERT(!mDT, "Can't initialize twice!");
mDT = aDT;
mDrawable = None;
#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 = cairoDT->mSurface;
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);
return true;
#else
return false;
#endif
}
void
BorrowedXlibDrawable::Finish()
{
DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(mDT);
cairo_surface_t* surf = cairoDT->mSurface;
cairo_surface_mark_dirty(surf);
if (mDrawable) {
mDrawable = None;
}
}
#endif
} // namespace gfx
} // namespace mozilla