/* -*- 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/. */
// 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()));
}
/* 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.
*/ staticbool 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) returnfalse;
if (r.X() < 0.f) {
r.SetWidth(r.XMost()); if (r.Width() < 0.f) returnfalse;
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) returnfalse;
r.MoveToY(0.f);
}
if (r.YMost() > CAIRO_COORD_MAX) {
r.SetBottomEdge(CAIRO_COORD_MAX);
} returntrue;
}
} // end anonymous namespace
staticbool SupportsSelfCopy(cairo_surface_t* surface) { switch (cairo_surface_get_type(surface)) { #ifdef CAIRO_HAS_QUARTZ_SURFACE case CAIRO_SURFACE_TYPE_QUARTZ: returntrue; #endif #ifdef CAIRO_HAS_WIN32_SURFACE case CAIRO_SURFACE_TYPE_WIN32: case CAIRO_SURFACE_TYPE_WIN32_PRINTING: returntrue; #endif default: returnfalse;
}
}
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;
}
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
} elseif (cairo_surface_get_type(aSurface) == CAIRO_SURFACE_TYPE_WIN32) { return cairo_win32_surface_get_image(aSurface); #endif
}
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
/** * 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;
}
// 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;
}
// 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);
}
// 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));
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;
}
staticbool 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) returnfalse;
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;
}
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));
}
// We need to \-escape any single-quotes in the destination and URI strings, // in order to pass them 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 // // (Encoding of non-ASCII chars etc gets handled later by the PDF backend.) staticvoid EscapeForCairo(nsACString& aStr) { for (size_t i = aStr.Length(); i > 0;) {
--i; if (aStr[i] == '\'') {
aStr.ReplaceLiteral(i, 1, "\\'");
} elseif (aStr[i] == '\\') {
aStr.ReplaceLiteral(i, 1, "\\\\");
}
}
}
void DrawTargetCairo::Link(constchar* aDest, constchar* aURI, const Rect& aRect) { if ((!aURI || !*aURI) && (!aDest || !*aDest)) { // No destination? Just bail out. return;
}
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);
// 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(constchar* aDestination, const Point& aPoint) { if (!aDestination || !*aDestination) { // No destination? Just bail out. return;
}
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);
// 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());
// 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));
}
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);
}
// Now draw the content using the desired operator
PaintWithAlpha(mContext, aOptions);
} else {
cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
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);
}
}
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 (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::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);
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);
}
// 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 (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;
}
}
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;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.