2017-04-19 07:56:45 +00:00
/* -*- 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 ;
}
2022-05-01 02:46:57 +00:00
// 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.
2017-04-19 07:56:45 +00:00
cairo_surface_t * CreateSubImageForData ( unsigned char * aData ,
const IntRect & aRect ,
int aStride ,
SurfaceFormat aFormat )
{
if ( ! aData ) {
gfxWarning ( ) < < " DrawTargetCairo.CreateSubImageForData null aData " ;
return nullptr ;
}
2022-05-01 02:46:57 +00:00
unsigned char * data = aData + aRect . y * aStride ;
2017-04-19 07:56:45 +00:00
cairo_surface_t * image =
cairo_image_surface_create_for_data ( data ,
GfxFormatToCairoFormat ( aFormat ) ,
aRect . width ,
aRect . height ,
aStride ) ;
2022-05-01 02:46:57 +00:00
// Set the subimage's location in its parent
2017-04-19 07:56:45 +00:00
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