diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /gfx/2d/DrawTargetCairo.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/2d/DrawTargetCairo.cpp')
-rw-r--r-- | gfx/2d/DrawTargetCairo.cpp | 2010 |
1 files changed, 2010 insertions, 0 deletions
diff --git a/gfx/2d/DrawTargetCairo.cpp b/gfx/2d/DrawTargetCairo.cpp new file mode 100644 index 0000000000..2121839e4b --- /dev/null +++ b/gfx/2d/DrawTargetCairo.cpp @@ -0,0 +1,2010 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawTargetCairo.h" + +#include "SourceSurfaceCairo.h" +#include "PathCairo.h" +#include "HelpersCairo.h" +#include "BorrowedContext.h" +#include "FilterNodeSoftware.h" +#include "mozilla/Scoped.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_print.h" +#include "nsPrintfCString.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" +#endif + +#ifdef CAIRO_HAS_WIN32_SURFACE +# include "cairo-win32.h" +#endif + +#define PIXMAN_DONT_DEFINE_STDINT +#include "pixman.h" + +#include <algorithm> + +// 2^23 +#define CAIRO_COORD_MAX (Float(0x7fffff)) + +namespace mozilla { + +MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCairoSurface, cairo_surface_t, + cairo_surface_destroy); + +namespace gfx { + +cairo_surface_t* DrawTargetCairo::mDummySurface; + +namespace { + +// An RAII class to prepare to draw a context and optional path. Saves and +// restores the context on construction/destruction. +class AutoPrepareForDrawing { + public: + AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx) : mCtx(ctx) { + dt->PrepareForDrawing(ctx); + cairo_save(mCtx); + MOZ_ASSERT(cairo_status(mCtx) || + dt->GetTransform().FuzzyEquals(GetTransform())); + } + + AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx, const Path* path) + : mCtx(ctx) { + dt->PrepareForDrawing(ctx, path); + cairo_save(mCtx); + MOZ_ASSERT(cairo_status(mCtx) || + dt->GetTransform().FuzzyEquals(GetTransform())); + } + + ~AutoPrepareForDrawing() { + cairo_restore(mCtx); + cairo_status_t status = cairo_status(mCtx); + if (status) { + gfxWarning() << "DrawTargetCairo context in error state: " + << cairo_status_to_string(status) << "(" << status << ")"; + } + } + + private: +#ifdef DEBUG + Matrix GetTransform() { + cairo_matrix_t mat; + cairo_get_matrix(mCtx, &mat); + return Matrix(mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0); + } +#endif + + cairo_t* mCtx; +}; + +/* Clamp r to (0,0) (2^23,2^23) + * these are to be device coordinates. + * + * Returns false if the rectangle is completely out of bounds, + * true otherwise. + * + * This function assumes that it will be called with a rectangle being + * drawn into a surface with an identity transformation matrix; that + * is, anything above or to the left of (0,0) will be offscreen. + * + * First it checks if the rectangle is entirely beyond + * CAIRO_COORD_MAX; if so, it can't ever appear on the screen -- + * false is returned. + * + * Then it shifts any rectangles with x/y < 0 so that x and y are = 0, + * and adjusts the width and height appropriately. For example, a + * rectangle from (0,-5) with dimensions (5,10) will become a + * rectangle from (0,0) with dimensions (5,5). + * + * If after negative x/y adjustment to 0, either the width or height + * is negative, then the rectangle is completely offscreen, and + * nothing is drawn -- false is returned. + * + * Finally, if x+width or y+height are greater than CAIRO_COORD_MAX, + * the width and height are clamped such x+width or y+height are equal + * to CAIRO_COORD_MAX, and true is returned. + */ +static bool ConditionRect(Rect& r) { + // if either x or y is way out of bounds; + // note that we don't handle negative w/h here + if (r.X() > CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX) return false; + + if (r.X() < 0.f) { + r.SetWidth(r.XMost()); + if (r.Width() < 0.f) return false; + r.MoveToX(0.f); + } + + if (r.XMost() > CAIRO_COORD_MAX) { + r.SetRightEdge(CAIRO_COORD_MAX); + } + + if (r.Y() < 0.f) { + r.SetHeight(r.YMost()); + if (r.Height() < 0.f) return false; + + r.MoveToY(0.f); + } + + if (r.YMost() > CAIRO_COORD_MAX) { + r.SetBottomEdge(CAIRO_COORD_MAX); + } + return true; +} + +} // end anonymous namespace + +static bool SupportsSelfCopy(cairo_surface_t* surface) { + switch (cairo_surface_get_type(surface)) { +#ifdef CAIRO_HAS_QUARTZ_SURFACE + case CAIRO_SURFACE_TYPE_QUARTZ: + return true; +#endif +#ifdef CAIRO_HAS_WIN32_SURFACE + case CAIRO_SURFACE_TYPE_WIN32: + case CAIRO_SURFACE_TYPE_WIN32_PRINTING: + return true; +#endif + default: + return false; + } +} + +static bool PatternIsCompatible(const Pattern& aPattern) { + switch (aPattern.GetType()) { + case PatternType::LINEAR_GRADIENT: { + const LinearGradientPattern& pattern = + static_cast<const LinearGradientPattern&>(aPattern); + return pattern.mStops->GetBackendType() == BackendType::CAIRO; + } + case PatternType::RADIAL_GRADIENT: { + const RadialGradientPattern& pattern = + static_cast<const RadialGradientPattern&>(aPattern); + return pattern.mStops->GetBackendType() == BackendType::CAIRO; + } + case PatternType::CONIC_GRADIENT: { + const ConicGradientPattern& pattern = + static_cast<const ConicGradientPattern&>(aPattern); + return pattern.mStops->GetBackendType() == BackendType::CAIRO; + } + default: + return true; + } +} + +static cairo_user_data_key_t surfaceDataKey; + +static void ReleaseData(void* aData) { + DataSourceSurface* data = static_cast<DataSourceSurface*>(aData); + data->Unmap(); + data->Release(); +} + +static cairo_surface_t* CopyToImageSurface(unsigned char* aData, + const IntRect& aRect, + int32_t aStride, + SurfaceFormat aFormat) { + MOZ_ASSERT(aData); + + auto aRectWidth = aRect.Width(); + auto aRectHeight = aRect.Height(); + + cairo_surface_t* surf = cairo_image_surface_create( + GfxFormatToCairoFormat(aFormat), aRectWidth, aRectHeight); + // In certain scenarios, requesting larger than 8k image fails. Bug 803568 + // covers the details of how to run into it, but the full detailed + // investigation hasn't been done to determine the underlying cause. We + // will just handle the failure to allocate the surface to avoid a crash. + if (cairo_surface_status(surf)) { + gfxWarning() << "Invalid surface DTC " << cairo_surface_status(surf); + return nullptr; + } + + unsigned char* surfData = cairo_image_surface_get_data(surf); + int surfStride = cairo_image_surface_get_stride(surf); + int32_t pixelWidth = BytesPerPixel(aFormat); + + unsigned char* source = aData + aRect.Y() * aStride + aRect.X() * pixelWidth; + + MOZ_ASSERT(aStride >= aRectWidth * pixelWidth); + for (int32_t y = 0; y < aRectHeight; ++y) { + memcpy(surfData + y * surfStride, source + y * aStride, + aRectWidth * pixelWidth); + } + cairo_surface_mark_dirty(surf); + return surf; +} + +/** + * If aSurface can be represented as a surface of type + * CAIRO_SURFACE_TYPE_IMAGE then returns that surface. Does + * not add a reference. + */ +static cairo_surface_t* GetAsImageSurface(cairo_surface_t* aSurface) { + if (cairo_surface_get_type(aSurface) == CAIRO_SURFACE_TYPE_IMAGE) { + return aSurface; +#ifdef CAIRO_HAS_WIN32_SURFACE + } else if (cairo_surface_get_type(aSurface) == CAIRO_SURFACE_TYPE_WIN32) { + return cairo_win32_surface_get_image(aSurface); +#endif + } + + return nullptr; +} + +static cairo_surface_t* CreateSubImageForData(unsigned char* aData, + const IntRect& aRect, int aStride, + SurfaceFormat aFormat) { + if (!aData) { + gfxWarning() << "DrawTargetCairo.CreateSubImageForData null aData"; + return nullptr; + } + unsigned char* data = + aData + aRect.Y() * aStride + aRect.X() * BytesPerPixel(aFormat); + + cairo_surface_t* image = cairo_image_surface_create_for_data( + data, GfxFormatToCairoFormat(aFormat), aRect.Width(), aRect.Height(), + aStride); + // Set the subimage's device offset so that in remains in the same place + // relative to the 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. + */ +static cairo_surface_t* ExtractSubImage(cairo_surface_t* aSurface, + const IntRect& aSubImage, + SurfaceFormat aFormat) { + // No need to worry about retaining a reference to the original + // surface since the only caller of this function guarantees + // that aSurface will stay alive as long as the result + + cairo_surface_t* image = GetAsImageSurface(aSurface); + if (image) { + image = + CreateSubImageForData(cairo_image_surface_get_data(image), aSubImage, + cairo_image_surface_get_stride(image), aFormat); + return image; + } + + cairo_surface_t* similar = cairo_surface_create_similar( + aSurface, cairo_surface_get_content(aSurface), aSubImage.Width(), + aSubImage.Height()); + + cairo_t* ctx = cairo_create(similar); + cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface(ctx, aSurface, -aSubImage.X(), -aSubImage.Y()); + cairo_paint(ctx); + cairo_destroy(ctx); + + cairo_surface_set_device_offset(similar, -aSubImage.X(), -aSubImage.Y()); + return similar; +} + +/** + * Returns cairo surface for the given SourceSurface. + * If possible, it will use the cairo_surface associated with aSurface, + * otherwise, it will create a new cairo_surface. + * In either case, the caller must call cairo_surface_destroy on the + * result when it is done with it. + */ +static cairo_surface_t* GetCairoSurfaceForSourceSurface( + SourceSurface* aSurface, bool aExistingOnly = false, + const IntRect& aSubImage = IntRect()) { + if (!aSurface) { + return nullptr; + } + + IntRect subimage = IntRect(IntPoint(), aSurface->GetSize()); + if (!aSubImage.IsEmpty()) { + MOZ_ASSERT(!aExistingOnly); + MOZ_ASSERT(subimage.Contains(aSubImage)); + subimage = aSubImage; + } + + if (aSurface->GetType() == SurfaceType::CAIRO) { + cairo_surface_t* surf = + static_cast<SourceSurfaceCairo*>(aSurface)->GetSurface(); + if (aSubImage.IsEmpty()) { + cairo_surface_reference(surf); + } else { + surf = ExtractSubImage(surf, subimage, aSurface->GetFormat()); + } + return surf; + } + + if (aSurface->GetType() == SurfaceType::CAIRO_IMAGE) { + cairo_surface_t* surf = + static_cast<const DataSourceSurfaceCairo*>(aSurface)->GetSurface(); + if (aSubImage.IsEmpty()) { + cairo_surface_reference(surf); + } else { + surf = ExtractSubImage(surf, subimage, aSurface->GetFormat()); + } + return surf; + } + + if (aExistingOnly) { + return nullptr; + } + + RefPtr<DataSourceSurface> data = aSurface->GetDataSurface(); + if (!data) { + return nullptr; + } + + DataSourceSurface::MappedSurface map; + if (!data->Map(DataSourceSurface::READ, &map)) { + return nullptr; + } + + cairo_surface_t* surf = CreateSubImageForData(map.mData, subimage, + map.mStride, data->GetFormat()); + + // In certain scenarios, requesting larger than 8k image fails. Bug 803568 + // covers the details of how to run into it, but the full detailed + // investigation hasn't been done to determine the underlying cause. We + // will just handle the failure to allocate the surface to avoid a crash. + if (!surf || cairo_surface_status(surf)) { + if (surf && (cairo_surface_status(surf) == CAIRO_STATUS_INVALID_STRIDE)) { + // If we failed because of an invalid stride then copy into + // a new surface with a stride that cairo chooses. No need to + // set user data since we're not dependent on the original + // data. + cairo_surface_t* result = CopyToImageSurface( + map.mData, subimage, map.mStride, data->GetFormat()); + data->Unmap(); + return result; + } + data->Unmap(); + return nullptr; + } + + cairo_surface_set_user_data(surf, &surfaceDataKey, data.forget().take(), + ReleaseData); + return surf; +} + +// An RAII class to temporarily clear any device offset set +// on a surface. Note that this does not take a reference to the +// surface. +class AutoClearDeviceOffset final { + public: + explicit AutoClearDeviceOffset(SourceSurface* aSurface) + : mSurface(nullptr), mX(0), mY(0) { + Init(aSurface); + } + + explicit AutoClearDeviceOffset(const Pattern& aPattern) + : mSurface(nullptr), mX(0.0), mY(0.0) { + if (aPattern.GetType() == PatternType::SURFACE) { + const SurfacePattern& pattern = + static_cast<const SurfacePattern&>(aPattern); + Init(pattern.mSurface); + } + } + + ~AutoClearDeviceOffset() { + if (mSurface) { + cairo_surface_set_device_offset(mSurface, mX, mY); + } + } + + private: + void Init(SourceSurface* aSurface) { + cairo_surface_t* surface = GetCairoSurfaceForSourceSurface(aSurface, true); + if (surface) { + Init(surface); + cairo_surface_destroy(surface); + } + } + + void Init(cairo_surface_t* aSurface) { + mSurface = aSurface; + cairo_surface_get_device_offset(mSurface, &mX, &mY); + cairo_surface_set_device_offset(mSurface, 0, 0); + } + + cairo_surface_t* mSurface; + double mX; + double mY; +}; + +static inline void CairoPatternAddGradientStop(cairo_pattern_t* aPattern, + const GradientStop& aStop, + Float aNudge = 0) { + cairo_pattern_add_color_stop_rgba(aPattern, aStop.offset + aNudge, + aStop.color.r, aStop.color.g, aStop.color.b, + aStop.color.a); +} + +// Never returns nullptr. As such, you must always pass in Cairo-compatible +// patterns, most notably gradients with a GradientStopCairo. +// The pattern returned must have cairo_pattern_destroy() called on it by the +// caller. +// As the cairo_pattern_t returned may depend on the Pattern passed in, the +// lifetime of the cairo_pattern_t returned must not exceed the lifetime of the +// Pattern passed in. +static cairo_pattern_t* GfxPatternToCairoPattern(const Pattern& aPattern, + Float aAlpha, + const Matrix& aTransform) { + cairo_pattern_t* pat; + const Matrix* matrix = nullptr; + + switch (aPattern.GetType()) { + case PatternType::COLOR: { + DeviceColor color = static_cast<const ColorPattern&>(aPattern).mColor; + pat = cairo_pattern_create_rgba(color.r, color.g, color.b, + color.a * aAlpha); + break; + } + + case PatternType::SURFACE: { + const SurfacePattern& pattern = + static_cast<const SurfacePattern&>(aPattern); + cairo_surface_t* surf = GetCairoSurfaceForSourceSurface( + pattern.mSurface, false, pattern.mSamplingRect); + if (!surf) return nullptr; + + pat = cairo_pattern_create_for_surface(surf); + + matrix = &pattern.mMatrix; + + cairo_pattern_set_filter( + pat, GfxSamplingFilterToCairoFilter(pattern.mSamplingFilter)); + cairo_pattern_set_extend(pat, + GfxExtendToCairoExtend(pattern.mExtendMode)); + + cairo_surface_destroy(surf); + break; + } + case PatternType::LINEAR_GRADIENT: { + const LinearGradientPattern& pattern = + static_cast<const LinearGradientPattern&>(aPattern); + + pat = cairo_pattern_create_linear(pattern.mBegin.x, pattern.mBegin.y, + pattern.mEnd.x, pattern.mEnd.y); + + MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO); + GradientStopsCairo* cairoStops = + static_cast<GradientStopsCairo*>(pattern.mStops.get()); + cairo_pattern_set_extend( + pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode())); + + matrix = &pattern.mMatrix; + + const std::vector<GradientStop>& stops = cairoStops->GetStops(); + for (size_t i = 0; i < stops.size(); ++i) { + CairoPatternAddGradientStop(pat, stops[i]); + } + + break; + } + case PatternType::RADIAL_GRADIENT: { + const RadialGradientPattern& pattern = + static_cast<const RadialGradientPattern&>(aPattern); + + pat = cairo_pattern_create_radial(pattern.mCenter1.x, pattern.mCenter1.y, + pattern.mRadius1, pattern.mCenter2.x, + pattern.mCenter2.y, pattern.mRadius2); + + MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO); + GradientStopsCairo* cairoStops = + static_cast<GradientStopsCairo*>(pattern.mStops.get()); + cairo_pattern_set_extend( + pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode())); + + matrix = &pattern.mMatrix; + + const std::vector<GradientStop>& stops = cairoStops->GetStops(); + for (size_t i = 0; i < stops.size(); ++i) { + CairoPatternAddGradientStop(pat, stops[i]); + } + + break; + } + case PatternType::CONIC_GRADIENT: { + // XXX(ntim): Bug 1617039 - Implement conic-gradient for Cairo + pat = cairo_pattern_create_rgba(0.0, 0.0, 0.0, 0.0); + + break; + } + default: { + // We should support all pattern types! + MOZ_ASSERT(false); + } + } + + // The pattern matrix is a matrix that transforms the pattern into user + // space. Cairo takes a matrix that converts from user space to pattern + // space. Cairo therefore needs the inverse. + if (matrix) { + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(*matrix, mat); + cairo_matrix_invert(&mat); + cairo_pattern_set_matrix(pat, &mat); + } + + return pat; +} + +static bool NeedIntermediateSurface(const Pattern& aPattern, + const DrawOptions& aOptions) { + // We pre-multiply colours' alpha by the global alpha, so we don't need to + // use an intermediate surface for them. + if (aPattern.GetType() == PatternType::COLOR) return false; + + if (aOptions.mAlpha == 1.0) return false; + + return true; +} + +DrawTargetCairo::DrawTargetCairo() + : mContext(nullptr), + mSurface(nullptr), + mTransformSingular(false), + mLockedBits(nullptr), + mFontOptions(nullptr) {} + +DrawTargetCairo::~DrawTargetCairo() { + cairo_destroy(mContext); + if (mSurface) { + cairo_surface_destroy(mSurface); + mSurface = nullptr; + } + if (mFontOptions) { + cairo_font_options_destroy(mFontOptions); + mFontOptions = nullptr; + } + MOZ_ASSERT(!mLockedBits); +} + +bool DrawTargetCairo::IsValid() const { + return mSurface && !cairo_surface_status(mSurface) && mContext && + !cairo_surface_status(cairo_get_group_target(mContext)); +} + +DrawTargetType DrawTargetCairo::GetType() const { + if (mContext) { + cairo_surface_type_t type = cairo_surface_get_type(mSurface); + if (type == CAIRO_SURFACE_TYPE_TEE) { + type = cairo_surface_get_type(cairo_tee_surface_index(mSurface, 0)); + MOZ_ASSERT(type != CAIRO_SURFACE_TYPE_TEE, "C'mon!"); + MOZ_ASSERT( + type == cairo_surface_get_type(cairo_tee_surface_index(mSurface, 1)), + "What should we do here?"); + } + switch (type) { + case CAIRO_SURFACE_TYPE_PDF: + case CAIRO_SURFACE_TYPE_PS: + case CAIRO_SURFACE_TYPE_SVG: + case CAIRO_SURFACE_TYPE_WIN32_PRINTING: + case CAIRO_SURFACE_TYPE_XML: + return DrawTargetType::VECTOR; + + case CAIRO_SURFACE_TYPE_VG: + case CAIRO_SURFACE_TYPE_GL: + case CAIRO_SURFACE_TYPE_GLITZ: + case CAIRO_SURFACE_TYPE_QUARTZ: + case CAIRO_SURFACE_TYPE_DIRECTFB: + return DrawTargetType::HARDWARE_RASTER; + + case CAIRO_SURFACE_TYPE_SKIA: + case CAIRO_SURFACE_TYPE_QT: + MOZ_FALLTHROUGH_ASSERT( + "Can't determine actual DrawTargetType for DrawTargetCairo - " + "assuming SOFTWARE_RASTER"); + case CAIRO_SURFACE_TYPE_IMAGE: + case CAIRO_SURFACE_TYPE_XLIB: + case CAIRO_SURFACE_TYPE_XCB: + case CAIRO_SURFACE_TYPE_WIN32: + case CAIRO_SURFACE_TYPE_BEOS: + case CAIRO_SURFACE_TYPE_OS2: + case CAIRO_SURFACE_TYPE_QUARTZ_IMAGE: + case CAIRO_SURFACE_TYPE_SCRIPT: + case CAIRO_SURFACE_TYPE_RECORDING: + case CAIRO_SURFACE_TYPE_DRM: + case CAIRO_SURFACE_TYPE_SUBSURFACE: + case CAIRO_SURFACE_TYPE_TEE: // included to silence warning about + // unhandled enum value + return DrawTargetType::SOFTWARE_RASTER; + default: + MOZ_CRASH("GFX: Unsupported cairo surface type"); + } + } + MOZ_ASSERT(false, "Could not determine DrawTargetType for DrawTargetCairo"); + return DrawTargetType::SOFTWARE_RASTER; +} + +IntSize DrawTargetCairo::GetSize() const { return mSize; } + +SurfaceFormat GfxFormatForCairoSurface(cairo_surface_t* surface) { + cairo_surface_type_t type = cairo_surface_get_type(surface); + if (type == CAIRO_SURFACE_TYPE_IMAGE) { + return CairoFormatToGfxFormat(cairo_image_surface_get_format(surface)); + } +#ifdef CAIRO_HAS_XLIB_SURFACE + // xlib is currently the only Cairo backend that creates 16bpp surfaces + if (type == CAIRO_SURFACE_TYPE_XLIB && + cairo_xlib_surface_get_depth(surface) == 16) { + return SurfaceFormat::R5G6B5_UINT16; + } +#endif + return CairoContentToGfxFormat(cairo_surface_get_content(surface)); +} + +void DrawTargetCairo::Link(const char* aDestination, const Rect& aRect) { + if (!aDestination || !*aDestination) { + // No destination? Just bail out. + return; + } + + // We need to \-escape any single-quotes in the destination string, in order + // to pass it via the attributes arg to cairo_tag_begin. + // + // We also need to escape any backslashes (bug 1748077), as per doc at + // https://www.cairographics.org/manual/cairo-Tags-and-Links.html#cairo-tag-begin + // The cairo-pdf-interchange backend (used on all platforms EXCEPT macOS) + // actually requires that we *doubly* escape the backslashes (this may be a + // cairo bug), while the quartz backend is fine with them singly-escaped. + // + // (Encoding of non-ASCII chars etc gets handled later by the PDF backend.) + nsAutoCString dest(aDestination); + for (size_t i = dest.Length(); i > 0;) { + --i; + if (dest[i] == '\'') { + dest.ReplaceLiteral(i, 1, "\\'"); + } else if (dest[i] == '\\') { +#ifdef XP_MACOSX + dest.ReplaceLiteral(i, 1, "\\\\"); +#else + dest.ReplaceLiteral(i, 1, "\\\\\\\\"); +#endif + } + } + + double x = aRect.x, y = aRect.y, w = aRect.width, h = aRect.height; + cairo_user_to_device(mContext, &x, &y); + cairo_user_to_device_distance(mContext, &w, &h); + + nsPrintfCString attributes("rect=[%f %f %f %f] ", x, y, w, h); + if (dest[0] == '#') { + // The actual destination does not have a leading '#'. + attributes.AppendPrintf("dest='%s'", dest.get() + 1); + } else { + attributes.AppendPrintf("uri='%s'", dest.get()); + } + + // We generate a begin/end pair with no content in between, because we are + // using the rect attribute of the begin tag to specify the link region + // rather than depending on cairo to accumulate the painted area. + cairo_tag_begin(mContext, CAIRO_TAG_LINK, attributes.get()); + cairo_tag_end(mContext, CAIRO_TAG_LINK); +} + +void DrawTargetCairo::Destination(const char* aDestination, + const Point& aPoint) { + if (!aDestination || !*aDestination) { + // No destination? Just bail out. + return; + } + + nsAutoCString dest(aDestination); + for (size_t i = dest.Length(); i > 0;) { + --i; + if (dest[i] == '\'') { + dest.ReplaceLiteral(i, 1, "\\'"); + } + } + + double x = aPoint.x, y = aPoint.y; + cairo_user_to_device(mContext, &x, &y); + + nsPrintfCString attributes("name='%s' x=%f y=%f internal", dest.get(), x, y); + cairo_tag_begin(mContext, CAIRO_TAG_DEST, attributes.get()); + cairo_tag_end(mContext, CAIRO_TAG_DEST); +} + +already_AddRefed<SourceSurface> DrawTargetCairo::Snapshot() { + if (!IsValid()) { + gfxCriticalNote << "DrawTargetCairo::Snapshot with bad surface " + << hexa(mSurface) << ", context " << hexa(mContext) + << ", status " + << (mSurface ? cairo_surface_status(mSurface) : -1); + return nullptr; + } + if (mSnapshot) { + RefPtr<SourceSurface> snapshot(mSnapshot); + return snapshot.forget(); + } + + IntSize size = GetSize(); + + mSnapshot = new SourceSurfaceCairo(mSurface, size, + GfxFormatForCairoSurface(mSurface), this); + RefPtr<SourceSurface> snapshot(mSnapshot); + return snapshot.forget(); +} + +bool DrawTargetCairo::LockBits(uint8_t** aData, IntSize* aSize, + int32_t* aStride, SurfaceFormat* aFormat, + IntPoint* aOrigin) { + cairo_surface_t* target = cairo_get_group_target(mContext); + cairo_surface_t* surf = target; +#ifdef CAIRO_HAS_WIN32_SURFACE + if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_WIN32) { + cairo_surface_t* imgsurf = cairo_win32_surface_get_image(surf); + if (imgsurf) { + surf = imgsurf; + } + } +#endif + if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_IMAGE && + cairo_surface_status(surf) == CAIRO_STATUS_SUCCESS) { + PointDouble offset; + cairo_surface_get_device_offset(target, &offset.x.value, &offset.y.value); + // verify the device offset can be converted to integers suitable for a + // bounds rect + IntPoint origin(int32_t(-offset.x), int32_t(-offset.y)); + if (-PointDouble(origin) != offset || (!aOrigin && origin != IntPoint())) { + return false; + } + + WillChange(); + Flush(); + + mLockedBits = cairo_image_surface_get_data(surf); + *aData = mLockedBits; + *aSize = IntSize(cairo_image_surface_get_width(surf), + cairo_image_surface_get_height(surf)); + *aStride = cairo_image_surface_get_stride(surf); + *aFormat = CairoFormatToGfxFormat(cairo_image_surface_get_format(surf)); + if (aOrigin) { + *aOrigin = origin; + } + return true; + } + + return false; +} + +void DrawTargetCairo::ReleaseBits(uint8_t* aData) { + MOZ_ASSERT(mLockedBits == aData); + mLockedBits = nullptr; + cairo_surface_t* surf = cairo_get_group_target(mContext); +#ifdef CAIRO_HAS_WIN32_SURFACE + if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_WIN32) { + cairo_surface_t* imgsurf = cairo_win32_surface_get_image(surf); + if (imgsurf) { + cairo_surface_mark_dirty(imgsurf); + } + } +#endif + cairo_surface_mark_dirty(surf); +} + +void DrawTargetCairo::Flush() { + cairo_surface_t* surf = cairo_get_group_target(mContext); + cairo_surface_flush(surf); +} + +void DrawTargetCairo::PrepareForDrawing(cairo_t* aContext, + const Path* aPath /* = nullptr */) { + WillChange(aPath); +} + +cairo_surface_t* DrawTargetCairo::GetDummySurface() { + if (mDummySurface) { + return mDummySurface; + } + + mDummySurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + + return mDummySurface; +} + +static void PaintWithAlpha(cairo_t* aContext, const DrawOptions& aOptions) { + if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE) { + // Cairo treats the source operator like a lerp when alpha is < 1. + // Approximate the desired operator by: out = 0; out += src*alpha; + if (aOptions.mAlpha == 1) { + cairo_set_operator(aContext, CAIRO_OPERATOR_SOURCE); + cairo_paint(aContext); + } else { + cairo_set_operator(aContext, CAIRO_OPERATOR_CLEAR); + cairo_paint(aContext); + cairo_set_operator(aContext, CAIRO_OPERATOR_ADD); + cairo_paint_with_alpha(aContext, aOptions.mAlpha); + } + } else { + cairo_set_operator(aContext, GfxOpToCairoOp(aOptions.mCompositionOp)); + cairo_paint_with_alpha(aContext, aOptions.mAlpha); + } +} + +void DrawTargetCairo::DrawSurface(SourceSurface* aSurface, const Rect& aDest, + const Rect& aSource, + const DrawSurfaceOptions& aSurfOptions, + const DrawOptions& aOptions) { + if (mTransformSingular || aDest.IsEmpty()) { + return; + } + + if (!IsValid() || !aSurface) { + gfxCriticalNote << "DrawSurface with bad surface " + << cairo_surface_status(cairo_get_group_target(mContext)); + return; + } + + AutoPrepareForDrawing prep(this, mContext); + AutoClearDeviceOffset clear(aSurface); + + float sx = aSource.Width() / aDest.Width(); + float sy = aSource.Height() / aDest.Height(); + + cairo_matrix_t src_mat; + cairo_matrix_init_translate(&src_mat, aSource.X() - aSurface->GetRect().x, + aSource.Y() - aSurface->GetRect().y); + cairo_matrix_scale(&src_mat, sx, sy); + + cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aSurface); + if (!surf) { + gfxWarning() + << "Failed to create cairo surface for DrawTargetCairo::DrawSurface"; + return; + } + cairo_pattern_t* pat = cairo_pattern_create_for_surface(surf); + cairo_surface_destroy(surf); + + cairo_pattern_set_matrix(pat, &src_mat); + cairo_pattern_set_filter( + pat, GfxSamplingFilterToCairoFilter(aSurfOptions.mSamplingFilter)); + // For PDF output, we avoid using EXTEND_PAD here because floating-point + // error accumulation may lead cairo_pdf_surface to conclude that padding + // is needed due to an apparent one- or two-pixel mismatch between source + // pattern and destination rect sizes when we're rendering a pdf.js page, + // and this forces undesirable fallback to the rasterization codepath + // instead of simply replaying the recording. + // (See bug 1777209.) + cairo_pattern_set_extend( + pat, cairo_surface_get_type(mSurface) == CAIRO_SURFACE_TYPE_PDF + ? CAIRO_EXTEND_NONE + : 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 ShadowOptions& aShadow, + CompositionOp aOperator) { + if (!IsValid() || !aSurface) { + gfxCriticalNote << "DrawSurfaceWithShadow with bad surface " + << cairo_surface_status(cairo_get_group_target(mContext)); + return; + } + + if (aSurface->GetType() != SurfaceType::CAIRO) { + return; + } + + AutoClearDeviceOffset clear(aSurface); + + Float width = Float(aSurface->GetSize().width); + Float height = Float(aSurface->GetSize().height); + + SourceSurfaceCairo* source = static_cast<SourceSurfaceCairo*>(aSurface); + cairo_surface_t* sourcesurf = source->GetSurface(); + cairo_surface_t* blursurf; + cairo_surface_t* surf; + + // We only use the A8 surface for blurred shadows. Unblurred shadows can just + // use the RGBA surface directly. + if (cairo_surface_get_type(sourcesurf) == CAIRO_SURFACE_TYPE_TEE) { + blursurf = cairo_tee_surface_index(sourcesurf, 0); + surf = cairo_tee_surface_index(sourcesurf, 1); + } else { + blursurf = sourcesurf; + surf = sourcesurf; + } + + if (aShadow.mSigma != 0.0f) { + MOZ_ASSERT(cairo_surface_get_type(blursurf) == CAIRO_SURFACE_TYPE_IMAGE); + Rect extents(0, 0, width, height); + AlphaBoxBlur blur(extents, cairo_image_surface_get_stride(blursurf), + aShadow.mSigma, aShadow.mSigma); + blur.Blur(cairo_image_surface_get_data(blursurf)); + } + + WillChange(); + ClearSurfaceForUnboundedSource(aOperator); + + cairo_save(mContext); + cairo_set_operator(mContext, GfxOpToCairoOp(aOperator)); + cairo_identity_matrix(mContext); + cairo_translate(mContext, aDest.x, aDest.y); + + bool needsGroup = !IsOperatorBoundByMask(aOperator); + if (needsGroup) { + cairo_push_group(mContext); + } + + cairo_set_source_rgba(mContext, aShadow.mColor.r, aShadow.mColor.g, + aShadow.mColor.b, aShadow.mColor.a); + cairo_mask_surface(mContext, blursurf, aShadow.mOffset.x, aShadow.mOffset.y); + + if (blursurf != surf || aSurface->GetFormat() != SurfaceFormat::A8) { + // Now that the shadow has been drawn, we can draw the surface on top. + cairo_set_source_surface(mContext, surf, 0, 0); + cairo_new_path(mContext); + cairo_rectangle(mContext, 0, 0, width, height); + cairo_fill(mContext); + } + + if (needsGroup) { + cairo_pop_group_to_source(mContext); + cairo_paint(mContext); + } + + cairo_restore(mContext); +} + +void DrawTargetCairo::DrawPattern(const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions, + DrawPatternType aDrawType, + bool aPathBoundsClip) { + if (!PatternIsCompatible(aPattern)) { + return; + } + + AutoClearDeviceOffset clear(aPattern); + + cairo_pattern_t* pat = + GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform()); + if (!pat) { + return; + } + if (cairo_pattern_status(pat)) { + cairo_pattern_destroy(pat); + gfxWarning() << "Invalid pattern"; + return; + } + + cairo_set_source(mContext, pat); + + cairo_set_antialias(mContext, + GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode)); + + if (NeedIntermediateSurface(aPattern, aOptions) || + (!IsOperatorBoundByMask(aOptions.mCompositionOp) && !aPathBoundsClip)) { + cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA); + + // Don't want operators to be applied twice + cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); + + if (aDrawType == DRAW_STROKE) { + SetCairoStrokeOptions(mContext, aStrokeOptions); + cairo_stroke_preserve(mContext); + } else { + cairo_fill_preserve(mContext); + } + + cairo_pop_group_to_source(mContext); + + // Now draw the content using the desired operator + PaintWithAlpha(mContext, aOptions); + } else { + cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); + + if (aDrawType == DRAW_STROKE) { + SetCairoStrokeOptions(mContext, aStrokeOptions); + cairo_stroke_preserve(mContext); + } else { + cairo_fill_preserve(mContext); + } + } + + cairo_pattern_destroy(pat); +} + +void DrawTargetCairo::FillRect(const Rect& aRect, const Pattern& aPattern, + const DrawOptions& aOptions) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + + bool restoreTransform = false; + Matrix mat; + Rect r = aRect; + + /* Clamp coordinates to work around a design bug in cairo */ + if (r.Width() > CAIRO_COORD_MAX || r.Height() > CAIRO_COORD_MAX || + r.X() < -CAIRO_COORD_MAX || r.X() > CAIRO_COORD_MAX || + r.Y() < -CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX) { + if (!mat.IsRectilinear()) { + gfxWarning() << "DrawTargetCairo::FillRect() misdrawing huge Rect " + "with non-rectilinear transform"; + } + + mat = GetTransform(); + r = mat.TransformBounds(r); + + if (!ConditionRect(r)) { + gfxWarning() << "Ignoring DrawTargetCairo::FillRect() call with " + "out-of-bounds Rect"; + return; + } + + restoreTransform = true; + SetTransform(Matrix()); + } + + cairo_new_path(mContext); + cairo_rectangle(mContext, r.X(), r.Y(), r.Width(), r.Height()); + + bool pathBoundsClip = false; + + if (r.Contains(GetUserSpaceClip())) { + pathBoundsClip = true; + } + + DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL, pathBoundsClip); + + if (restoreTransform) { + SetTransform(mat); + } +} + +void DrawTargetCairo::CopySurfaceInternal(cairo_surface_t* aSurface, + const IntRect& aSource, + const IntPoint& aDest) { + if (cairo_surface_status(aSurface)) { + gfxWarning() << "Invalid surface" << cairo_surface_status(aSurface); + return; + } + + cairo_identity_matrix(mContext); + + cairo_set_source_surface(mContext, aSurface, aDest.x - aSource.X(), + aDest.y - aSource.Y()); + cairo_set_operator(mContext, CAIRO_OPERATOR_SOURCE); + cairo_set_antialias(mContext, CAIRO_ANTIALIAS_NONE); + + cairo_reset_clip(mContext); + cairo_new_path(mContext); + cairo_rectangle(mContext, aDest.x, aDest.y, aSource.Width(), + aSource.Height()); + cairo_fill(mContext); +} + +void DrawTargetCairo::CopySurface(SourceSurface* aSurface, + const IntRect& aSource, + const IntPoint& aDest) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + AutoClearDeviceOffset clear(aSurface); + + if (!aSurface) { + gfxWarning() << "Unsupported surface type specified"; + return; + } + + cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aSurface); + if (!surf) { + gfxWarning() << "Unsupported surface type specified"; + return; + } + + CopySurfaceInternal(surf, aSource, aDest); + cairo_surface_destroy(surf); +} + +void DrawTargetCairo::CopyRect(const IntRect& aSource, const IntPoint& aDest) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + + IntRect source = aSource; + cairo_surface_t* surf = mSurface; + + if (!SupportsSelfCopy(mSurface) && aSource.ContainsY(aDest.y)) { + cairo_surface_t* similar = cairo_surface_create_similar( + mSurface, GfxFormatToCairoContent(GetFormat()), aSource.Width(), + aSource.Height()); + cairo_t* ctx = cairo_create(similar); + cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface(ctx, surf, -aSource.X(), -aSource.Y()); + cairo_paint(ctx); + cairo_destroy(ctx); + + source.MoveTo(0, 0); + surf = similar; + } + + CopySurfaceInternal(surf, source, aDest); + + if (surf != mSurface) { + cairo_surface_destroy(surf); + } +} + +void DrawTargetCairo::ClearRect(const Rect& aRect) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + + if (!mContext || aRect.Width() < 0 || aRect.Height() < 0 || + !std::isfinite(aRect.X()) || !std::isfinite(aRect.Width()) || + !std::isfinite(aRect.Y()) || !std::isfinite(aRect.Height())) { + gfxCriticalNote << "ClearRect with invalid argument " << gfx::hexa(mContext) + << " with " << aRect.Width() << "x" << aRect.Height() + << " [" << aRect.X() << ", " << aRect.Y() << "]"; + } + + cairo_set_antialias(mContext, CAIRO_ANTIALIAS_NONE); + cairo_new_path(mContext); + cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR); + cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), + aRect.Height()); + cairo_fill(mContext); +} + +void DrawTargetCairo::StrokeRect( + const Rect& aRect, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions /* = StrokeOptions() */, + const DrawOptions& aOptions /* = DrawOptions() */) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + + cairo_new_path(mContext); + cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), + aRect.Height()); + + DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE); +} + +void DrawTargetCairo::StrokeLine( + const Point& aStart, const Point& aEnd, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions /* = StrokeOptions() */, + const DrawOptions& aOptions /* = DrawOptions() */) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + + cairo_new_path(mContext); + cairo_move_to(mContext, aStart.x, aStart.y); + cairo_line_to(mContext, aEnd.x, aEnd.y); + + DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE); +} + +void DrawTargetCairo::Stroke( + const Path* aPath, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions /* = StrokeOptions() */, + const DrawOptions& aOptions /* = DrawOptions() */) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext, aPath); + + if (aPath->GetBackendType() != BackendType::CAIRO) return; + + PathCairo* path = + const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath)); + path->SetPathOnContext(mContext); + + DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE); +} + +void DrawTargetCairo::Fill(const Path* aPath, const Pattern& aPattern, + const DrawOptions& aOptions /* = DrawOptions() */) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext, aPath); + + if (aPath->GetBackendType() != BackendType::CAIRO) return; + + PathCairo* path = + const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath)); + path->SetPathOnContext(mContext); + + DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL); +} + +bool DrawTargetCairo::IsCurrentGroupOpaque() { + cairo_surface_t* surf = cairo_get_group_target(mContext); + + if (!surf) { + return false; + } + + return cairo_surface_get_content(surf) == CAIRO_CONTENT_COLOR; +} + +void DrawTargetCairo::SetFontOptions(cairo_antialias_t aAAMode) { + // This will attempt to detect if the currently set scaled font on the + // context has enabled subpixel AA. If it is not permitted, then it will + // downgrade to grayscale AA. + // This only currently works effectively for the cairo-ft backend relative + // to system defaults, as only cairo-ft reflect system defaults in the scaled + // font state. However, this will work for cairo-ft on both tree Cairo and + // system Cairo. + // Other backends leave the CAIRO_ANTIALIAS_DEFAULT setting untouched while + // potentially interpreting it as subpixel or even other types of AA that + // can't be safely equivocated with grayscale AA. For this reason we don't + // try to also detect and modify the default AA setting, only explicit + // subpixel AA. These other backends must instead rely on tree Cairo's + // cairo_surface_set_subpixel_antialiasing extension. + + // If allowing subpixel AA, then leave Cairo's default AA state. + if (mPermitSubpixelAA && aAAMode == CAIRO_ANTIALIAS_DEFAULT) { + return; + } + + if (!mFontOptions) { + mFontOptions = cairo_font_options_create(); + if (!mFontOptions) { + gfxWarning() << "Failed allocating Cairo font options"; + return; + } + } + + cairo_get_font_options(mContext, mFontOptions); + cairo_antialias_t oldAA = cairo_font_options_get_antialias(mFontOptions); + cairo_antialias_t newAA = + aAAMode == CAIRO_ANTIALIAS_DEFAULT ? oldAA : aAAMode; + // Nothing to change if switching to default AA. + if (newAA == CAIRO_ANTIALIAS_DEFAULT) { + return; + } + // If the current font requests subpixel AA, force it to gray since we don't + // allow subpixel AA. + if (!mPermitSubpixelAA && newAA == CAIRO_ANTIALIAS_SUBPIXEL) { + newAA = CAIRO_ANTIALIAS_GRAY; + } + // Only override old AA with lower levels of AA. + if (oldAA == CAIRO_ANTIALIAS_DEFAULT || (int)newAA < (int)oldAA) { + cairo_font_options_set_antialias(mFontOptions, newAA); + cairo_set_font_options(mContext, mFontOptions); + } +} + +void DrawTargetCairo::SetPermitSubpixelAA(bool aPermitSubpixelAA) { + DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA); + cairo_surface_set_subpixel_antialiasing( + cairo_get_group_target(mContext), + aPermitSubpixelAA ? CAIRO_SUBPIXEL_ANTIALIASING_ENABLED + : CAIRO_SUBPIXEL_ANTIALIASING_DISABLED); +} + +static bool SupportsVariationSettings(cairo_surface_t* surface) { + switch (cairo_surface_get_type(surface)) { + case CAIRO_SURFACE_TYPE_PDF: + case CAIRO_SURFACE_TYPE_PS: + return false; + default: + return true; + } +} + +void DrawTargetCairo::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions) { + if (mTransformSingular) { + return; + } + + if (!IsValid()) { + gfxDebug() << "FillGlyphs bad surface " + << cairo_surface_status(cairo_get_group_target(mContext)); + return; + } + + cairo_scaled_font_t* cairoScaledFont = + aFont ? aFont->GetCairoScaledFont() : nullptr; + if (!cairoScaledFont) { + gfxDevCrash(LogReason::InvalidFont) << "Invalid scaled font"; + return; + } + + AutoPrepareForDrawing prep(this, mContext); + AutoClearDeviceOffset clear(aPattern); + + cairo_set_scaled_font(mContext, cairoScaledFont); + + cairo_pattern_t* pat = + GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform()); + if (!pat) return; + + cairo_set_source(mContext, pat); + cairo_pattern_destroy(pat); + + cairo_antialias_t aa = GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode); + cairo_set_antialias(mContext, aa); + + // Override any font-specific options as necessary. + SetFontOptions(aa); + + // Convert our GlyphBuffer into a vector of Cairo glyphs. This code can + // execute millions of times in short periods, so we want to avoid heap + // allocation whenever possible. So we use an inline vector capacity of 1024 + // bytes (the maximum allowed by mozilla::Vector), which gives an inline + // length of 1024 / 24 = 42 elements, which is enough to typically avoid heap + // allocation in ~99% of cases. + Vector<cairo_glyph_t, 1024 / sizeof(cairo_glyph_t)> glyphs; + if (!glyphs.resizeUninitialized(aBuffer.mNumGlyphs)) { + gfxDevCrash(LogReason::GlyphAllocFailedCairo) << "glyphs allocation failed"; + return; + } + for (uint32_t i = 0; i < aBuffer.mNumGlyphs; ++i) { + glyphs[i].index = aBuffer.mGlyphs[i].mIndex; + glyphs[i].x = aBuffer.mGlyphs[i].mPosition.x; + glyphs[i].y = aBuffer.mGlyphs[i].mPosition.y; + } + + if (!SupportsVariationSettings(mSurface) && aFont->HasVariationSettings() && + StaticPrefs::print_font_variations_as_paths()) { + cairo_set_fill_rule(mContext, CAIRO_FILL_RULE_WINDING); + cairo_new_path(mContext); + cairo_glyph_path(mContext, &glyphs[0], aBuffer.mNumGlyphs); + cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); + cairo_fill(mContext); + } else { + cairo_show_glyphs(mContext, &glyphs[0], aBuffer.mNumGlyphs); + } + + if (cairo_surface_status(cairo_get_group_target(mContext))) { + gfxDebug() << "Ending FillGlyphs with a bad surface " + << cairo_surface_status(cairo_get_group_target(mContext)); + } +} + +void DrawTargetCairo::Mask(const Pattern& aSource, const Pattern& aMask, + const DrawOptions& aOptions /* = DrawOptions() */) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + AutoClearDeviceOffset clearSource(aSource); + AutoClearDeviceOffset clearMask(aMask); + + cairo_set_antialias(mContext, + GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode)); + + cairo_pattern_t* source = + GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform()); + if (!source) { + return; + } + + cairo_pattern_t* mask = + GfxPatternToCairoPattern(aMask, aOptions.mAlpha, GetTransform()); + if (!mask) { + cairo_pattern_destroy(source); + return; + } + + if (cairo_pattern_status(source) || cairo_pattern_status(mask)) { + cairo_pattern_destroy(source); + cairo_pattern_destroy(mask); + gfxWarning() << "Invalid pattern"; + return; + } + + cairo_set_source(mContext, source); + cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); + cairo_mask(mContext, mask); + + cairo_pattern_destroy(mask); + cairo_pattern_destroy(source); +} + +void DrawTargetCairo::MaskSurface(const Pattern& aSource, SourceSurface* aMask, + Point aOffset, const DrawOptions& aOptions) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + AutoClearDeviceOffset clearSource(aSource); + AutoClearDeviceOffset clearMask(aMask); + + if (!PatternIsCompatible(aSource)) { + return; + } + + cairo_set_antialias(mContext, + GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode)); + + cairo_pattern_t* pat = + GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform()); + if (!pat) { + return; + } + + if (cairo_pattern_status(pat)) { + cairo_pattern_destroy(pat); + gfxWarning() << "Invalid pattern"; + return; + } + + cairo_set_source(mContext, pat); + + if (NeedIntermediateSurface(aSource, aOptions)) { + cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA); + + // Don't want operators to be applied twice + cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); + + // Now draw the content using the desired operator + cairo_paint_with_alpha(mContext, aOptions.mAlpha); + + cairo_pop_group_to_source(mContext); + } + + cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aMask); + if (!surf) { + cairo_pattern_destroy(pat); + return; + } + cairo_pattern_t* mask = cairo_pattern_create_for_surface(surf); + cairo_matrix_t matrix; + + cairo_matrix_init_translate(&matrix, -aOffset.x - aMask->GetRect().x, + -aOffset.y - aMask->GetRect().y); + cairo_pattern_set_matrix(mask, &matrix); + + cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); + + cairo_mask(mContext, mask); + + cairo_surface_destroy(surf); + cairo_pattern_destroy(mask); + cairo_pattern_destroy(pat); +} + +void DrawTargetCairo::PushClip(const Path* aPath) { + if (aPath->GetBackendType() != BackendType::CAIRO) { + return; + } + + WillChange(aPath); + cairo_save(mContext); + + PathCairo* path = + const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath)); + + if (mTransformSingular) { + cairo_new_path(mContext); + cairo_rectangle(mContext, 0, 0, 0, 0); + } else { + path->SetPathOnContext(mContext); + } + cairo_clip_preserve(mContext); +} + +void DrawTargetCairo::PushClipRect(const Rect& aRect) { + WillChange(); + cairo_save(mContext); + + cairo_new_path(mContext); + if (mTransformSingular) { + cairo_rectangle(mContext, 0, 0, 0, 0); + } else { + cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), + aRect.Height()); + } + cairo_clip_preserve(mContext); +} + +void DrawTargetCairo::PopClip() { + // save/restore does not affect the path, so no need to call WillChange() + + // cairo_restore will restore the transform too and we don't want to do that + // so we'll save it now and restore it after the cairo_restore + cairo_matrix_t mat; + cairo_get_matrix(mContext, &mat); + + cairo_restore(mContext); + + cairo_set_matrix(mContext, &mat); +} + +void DrawTargetCairo::PushLayer(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds, bool aCopyBackground) { + PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds, + aCopyBackground, CompositionOp::OP_OVER); +} + +void DrawTargetCairo::PushLayerWithBlend(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds, + bool aCopyBackground, + CompositionOp aCompositionOp) { + cairo_content_t content = CAIRO_CONTENT_COLOR_ALPHA; + + if (mFormat == SurfaceFormat::A8) { + content = CAIRO_CONTENT_ALPHA; + } else if (aOpaque) { + content = CAIRO_CONTENT_COLOR; + } + + if (aCopyBackground) { + cairo_surface_t* source = cairo_get_group_target(mContext); + cairo_push_group_with_content(mContext, content); + cairo_surface_t* dest = cairo_get_group_target(mContext); + cairo_t* ctx = cairo_create(dest); + cairo_set_source_surface(ctx, source, 0, 0); + cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE); + cairo_paint(ctx); + cairo_destroy(ctx); + } else { + cairo_push_group_with_content(mContext, content); + } + + PushedLayer layer(aOpacity, aCompositionOp, mPermitSubpixelAA); + + if (aMask) { + cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aMask); + if (surf) { + layer.mMaskPattern = cairo_pattern_create_for_surface(surf); + Matrix maskTransform = aMaskTransform; + maskTransform.PreTranslate(aMask->GetRect().X(), aMask->GetRect().Y()); + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(maskTransform, mat); + cairo_matrix_invert(&mat); + cairo_pattern_set_matrix(layer.mMaskPattern, &mat); + cairo_surface_destroy(surf); + } else { + gfxCriticalError() << "Failed to get cairo surface for mask surface!"; + } + } + + mPushedLayers.push_back(layer); + + SetPermitSubpixelAA(aOpaque); +} + +void DrawTargetCairo::PopLayer() { + MOZ_RELEASE_ASSERT(!mPushedLayers.empty()); + + cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); + + cairo_pop_group_to_source(mContext); + + PushedLayer layer = mPushedLayers.back(); + mPushedLayers.pop_back(); + + if (!layer.mMaskPattern) { + cairo_set_operator(mContext, GfxOpToCairoOp(layer.mCompositionOp)); + cairo_paint_with_alpha(mContext, layer.mOpacity); + } else { + if (layer.mOpacity != Float(1.0)) { + cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA); + + // Now draw the content using the desired operator + cairo_paint_with_alpha(mContext, layer.mOpacity); + + cairo_pop_group_to_source(mContext); + } + cairo_set_operator(mContext, GfxOpToCairoOp(layer.mCompositionOp)); + cairo_mask(mContext, layer.mMaskPattern); + } + + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(mTransform, mat); + cairo_set_matrix(mContext, &mat); + + cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); + + cairo_pattern_destroy(layer.mMaskPattern); + SetPermitSubpixelAA(layer.mWasPermittingSubpixelAA); +} + +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(); +} + +already_AddRefed<SourceSurface> DrawTargetCairo::OptimizeSourceSurface( + SourceSurface* aSurface) const { + RefPtr<SourceSurface> surface(aSurface); + return surface.forget(); +} + +already_AddRefed<SourceSurface> +DrawTargetCairo::CreateSourceSurfaceFromNativeSurface( + const NativeSurface& aSurface) const { + return nullptr; +} + +already_AddRefed<DrawTarget> DrawTargetCairo::CreateSimilarDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) const { + if (cairo_surface_status(cairo_get_group_target(mContext))) { + RefPtr<DrawTargetCairo> target = new DrawTargetCairo(); + if (target->Init(aSize, aFormat)) { + return target.forget(); + } + } + + cairo_surface_t* similar; + switch (cairo_surface_get_type(mSurface)) { +#ifdef CAIRO_HAS_WIN32_SURFACE + case CAIRO_SURFACE_TYPE_WIN32: + similar = cairo_win32_surface_create_with_dib( + GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height); + break; +#endif +#ifdef CAIRO_HAS_QUARTZ_SURFACE + case CAIRO_SURFACE_TYPE_QUARTZ: + if (StaticPrefs::gfx_cairo_quartz_cg_layer_enabled()) { + similar = cairo_quartz_surface_create_cg_layer( + mSurface, GfxFormatToCairoContent(aFormat), aSize.width, + aSize.height); + break; + } + [[fallthrough]]; +#endif + default: + similar = cairo_surface_create_similar(mSurface, + GfxFormatToCairoContent(aFormat), + aSize.width, aSize.height); + break; + } + + if (!cairo_surface_status(similar)) { + RefPtr<DrawTargetCairo> target = new DrawTargetCairo(); + if (target->InitAlreadyReferenced(similar, aSize)) { + return target.forget(); + } + } + + gfxCriticalError( + CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize))) + << "Failed to create similar cairo surface! Size: " << aSize + << " Status: " << cairo_surface_status(similar) + << cairo_surface_status(cairo_get_group_target(mContext)) << " format " + << (int)aFormat; + cairo_surface_destroy(similar); + + return nullptr; +} + +RefPtr<DrawTarget> DrawTargetCairo::CreateClippedDrawTarget( + const Rect& aBounds, SurfaceFormat aFormat) { + RefPtr<DrawTarget> result; + // Doing this save()/restore() dance is wasteful + cairo_save(mContext); + + if (!aBounds.IsEmpty()) { + cairo_new_path(mContext); + cairo_rectangle(mContext, aBounds.X(), aBounds.Y(), aBounds.Width(), + aBounds.Height()); + cairo_clip(mContext); + } + cairo_identity_matrix(mContext); + IntRect clipBounds = IntRect::RoundOut(GetUserSpaceClip()); + if (!clipBounds.IsEmpty()) { + RefPtr<DrawTarget> dt = CreateSimilarDrawTarget( + IntSize(clipBounds.width, clipBounds.height), aFormat); + result = gfx::Factory::CreateOffsetDrawTarget( + dt, IntPoint(clipBounds.x, clipBounds.y)); + result->SetTransform(mTransform); + } else { + // Everything is clipped but we still want some kind of surface + result = CreateSimilarDrawTarget(IntSize(1, 1), aFormat); + } + + cairo_restore(mContext); + return result; +} +bool DrawTargetCairo::InitAlreadyReferenced(cairo_surface_t* aSurface, + const IntSize& aSize, + SurfaceFormat* aFormat) { + if (cairo_surface_status(aSurface)) { + gfxCriticalNote << "Attempt to create DrawTarget for invalid surface. " + << aSize + << " Cairo Status: " << cairo_surface_status(aSurface); + cairo_surface_destroy(aSurface); + return false; + } + + mContext = cairo_create(aSurface); + mSurface = aSurface; + mSize = aSize; + mFormat = aFormat ? *aFormat : GfxFormatForCairoSurface(aSurface); + + // Cairo image surface have a bug where they will allocate a mask surface (for + // clipping) the size of the clip extents, and don't take the surface extents + // into account. Add a manual clip to the surface extents to prevent this. + cairo_new_path(mContext); + cairo_rectangle(mContext, 0, 0, mSize.width, mSize.height); + cairo_clip(mContext); + + if (mFormat == SurfaceFormat::A8R8G8B8_UINT32 || + mFormat == SurfaceFormat::R8G8B8A8) { + SetPermitSubpixelAA(false); + } else { + SetPermitSubpixelAA(true); + } + + return true; +} + +already_AddRefed<DrawTarget> DrawTargetCairo::CreateShadowDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat, float aSigma) const { + cairo_surface_t* similar = cairo_surface_create_similar( + cairo_get_target(mContext), GfxFormatToCairoContent(aFormat), aSize.width, + aSize.height); + + if (cairo_surface_status(similar)) { + return nullptr; + } + + // If we don't have a blur then we can use the RGBA mask and keep all the + // operations in graphics memory. + if (aSigma == 0.0f || aFormat == SurfaceFormat::A8) { + RefPtr<DrawTargetCairo> target = new DrawTargetCairo(); + if (target->InitAlreadyReferenced(similar, aSize)) { + return target.forget(); + } else { + return nullptr; + } + } + + cairo_surface_t* blursurf = + cairo_image_surface_create(CAIRO_FORMAT_A8, aSize.width, aSize.height); + + if (cairo_surface_status(blursurf)) { + return nullptr; + } + + cairo_surface_t* tee = cairo_tee_surface_create(blursurf); + cairo_surface_destroy(blursurf); + if (cairo_surface_status(tee)) { + cairo_surface_destroy(similar); + return nullptr; + } + + cairo_tee_surface_add(tee, similar); + cairo_surface_destroy(similar); + + RefPtr<DrawTargetCairo> target = new DrawTargetCairo(); + if (target->InitAlreadyReferenced(tee, aSize)) { + return target.forget(); + } + return nullptr; +} + +bool DrawTargetCairo::Draw3DTransformedSurface(SourceSurface* aSurface, + const Matrix4x4& aMatrix) { + return DrawTarget::Draw3DTransformedSurface(aSurface, aMatrix); +} + +bool DrawTargetCairo::Init(cairo_surface_t* aSurface, const IntSize& aSize, + SurfaceFormat* aFormat) { + cairo_surface_reference(aSurface); + return InitAlreadyReferenced(aSurface, aSize, aFormat); +} + +bool DrawTargetCairo::Init(const IntSize& aSize, SurfaceFormat aFormat) { + cairo_surface_t* surf = cairo_image_surface_create( + GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height); + return InitAlreadyReferenced(surf, aSize); +} + +bool DrawTargetCairo::Init(unsigned char* aData, const IntSize& aSize, + int32_t aStride, SurfaceFormat aFormat) { + cairo_surface_t* surf = cairo_image_surface_create_for_data( + aData, GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height, + aStride); + return InitAlreadyReferenced(surf, aSize); +} + +void* DrawTargetCairo::GetNativeSurface(NativeSurfaceType aType) { + if (aType == NativeSurfaceType::CAIRO_CONTEXT) { + return mContext; + } + + return nullptr; +} + +void DrawTargetCairo::MarkSnapshotIndependent() { + if (mSnapshot) { + if (mSnapshot->refCount() > 1) { + // We only need to worry about snapshots that someone else knows about + mSnapshot->DrawTargetWillChange(); + } + mSnapshot = nullptr; + } +} + +void DrawTargetCairo::WillChange(const Path* aPath /* = nullptr */) { + MarkSnapshotIndependent(); + MOZ_ASSERT(!mLockedBits); +} + +void DrawTargetCairo::SetTransform(const Matrix& aTransform) { + DrawTarget::SetTransform(aTransform); + + mTransformSingular = aTransform.IsSingular(); + if (!mTransformSingular) { + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(mTransform, mat); + cairo_set_matrix(mContext, &mat); + } +} + +Rect DrawTargetCairo::GetUserSpaceClip() const { + double clipX1, clipY1, clipX2, clipY2; + cairo_clip_extents(mContext, &clipX1, &clipY1, &clipX2, &clipY2); + return Rect(clipX1, clipY1, clipX2 - clipX1, + clipY2 - clipY1); // Narrowing of doubles to floats +} + +#ifdef MOZ_X11 +bool BorrowedXlibDrawable::Init(DrawTarget* aDT) { + MOZ_ASSERT(aDT, "Caller should check for nullptr"); + MOZ_ASSERT(!mDT, "Can't initialize twice!"); + mDT = aDT; + mDrawable = X11None; + +# ifdef CAIRO_HAS_XLIB_SURFACE + if (aDT->GetBackendType() != BackendType::CAIRO || aDT->IsTiledDrawTarget()) { + return false; + } + + DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(aDT); + cairo_surface_t* surf = cairo_get_group_target(cairoDT->mContext); + if (cairo_surface_get_type(surf) != CAIRO_SURFACE_TYPE_XLIB) { + return false; + } + cairo_surface_flush(surf); + + cairoDT->WillChange(); + + mDisplay = cairo_xlib_surface_get_display(surf); + mDrawable = cairo_xlib_surface_get_drawable(surf); + mScreen = cairo_xlib_surface_get_screen(surf); + mVisual = cairo_xlib_surface_get_visual(surf); + mSize.width = cairo_xlib_surface_get_width(surf); + mSize.height = cairo_xlib_surface_get_height(surf); + + double x = 0, y = 0; + cairo_surface_get_device_offset(surf, &x, &y); + mOffset = Point(x, y); + + return true; +# else + return false; +# endif +} + +void BorrowedXlibDrawable::Finish() { + DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(mDT); + cairo_surface_t* surf = cairo_get_group_target(cairoDT->mContext); + cairo_surface_mark_dirty(surf); + if (mDrawable) { + mDrawable = X11None; + } +} +#endif + +} // namespace gfx +} // namespace mozilla |