michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifdef _MSC_VER michael@0: #define _USE_MATH_DEFINES michael@0: #endif michael@0: #include michael@0: michael@0: #include "mozilla/Alignment.h" michael@0: michael@0: #include "cairo.h" michael@0: michael@0: #include "gfxContext.h" michael@0: michael@0: #include "gfxColor.h" michael@0: #include "gfxMatrix.h" michael@0: #include "gfxASurface.h" michael@0: #include "gfxPattern.h" michael@0: #include "gfxPlatform.h" michael@0: #include "gfxTeeSurface.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "gfx2DGlue.h" michael@0: #include "mozilla/gfx/PathHelpers.h" michael@0: #include michael@0: michael@0: #if CAIRO_HAS_DWRITE_FONT michael@0: #include "gfxWindowsPlatform.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::gfx; michael@0: michael@0: UserDataKey gfxContext::sDontUseAsSourceKey; michael@0: michael@0: /* This class lives on the stack and allows gfxContext users to easily, and michael@0: * performantly get a gfx::Pattern to use for drawing in their current context. michael@0: */ michael@0: class GeneralPattern michael@0: { michael@0: public: michael@0: GeneralPattern(gfxContext *aContext) : mContext(aContext), mPattern(nullptr) {} michael@0: ~GeneralPattern() { if (mPattern) { mPattern->~Pattern(); } } michael@0: michael@0: operator mozilla::gfx::Pattern&() michael@0: { michael@0: gfxContext::AzureState &state = mContext->CurrentState(); michael@0: michael@0: if (state.pattern) { michael@0: return *state.pattern->GetPattern(mContext->mDT, state.patternTransformChanged ? &state.patternTransform : nullptr); michael@0: } else if (state.sourceSurface) { michael@0: Matrix transform = state.surfTransform; michael@0: michael@0: if (state.patternTransformChanged) { michael@0: Matrix mat = mContext->GetDTTransform(); michael@0: mat.Invert(); michael@0: michael@0: transform = transform * state.patternTransform * mat; michael@0: } michael@0: michael@0: mPattern = new (mSurfacePattern.addr()) michael@0: SurfacePattern(state.sourceSurface, ExtendMode::CLAMP, transform); michael@0: return *mPattern; michael@0: } else { michael@0: mPattern = new (mColorPattern.addr()) michael@0: ColorPattern(state.color); michael@0: return *mPattern; michael@0: } michael@0: } michael@0: michael@0: private: michael@0: union { michael@0: mozilla::AlignedStorage2 mColorPattern; michael@0: mozilla::AlignedStorage2 mSurfacePattern; michael@0: }; michael@0: michael@0: gfxContext *mContext; michael@0: Pattern *mPattern; michael@0: }; michael@0: michael@0: gfxContext::gfxContext(gfxASurface *surface) michael@0: : mRefCairo(nullptr) michael@0: , mSurface(surface) michael@0: { michael@0: MOZ_COUNT_CTOR(gfxContext); michael@0: michael@0: mCairo = cairo_create(surface->CairoSurface()); michael@0: mFlags = surface->GetDefaultContextFlags(); michael@0: if (mSurface->GetRotateForLandscape()) { michael@0: // Rotate page 90 degrees to draw landscape page on portrait paper michael@0: gfxIntSize size = mSurface->GetSize(); michael@0: Translate(gfxPoint(0, size.width)); michael@0: gfxMatrix matrix(0, -1, michael@0: 1, 0, michael@0: 0, 0); michael@0: Multiply(matrix); michael@0: } michael@0: } michael@0: michael@0: gfxContext::gfxContext(DrawTarget *aTarget, const Point& aDeviceOffset) michael@0: : mPathIsRect(false) michael@0: , mTransformChanged(false) michael@0: , mCairo(nullptr) michael@0: , mRefCairo(nullptr) michael@0: , mSurface(nullptr) michael@0: , mFlags(0) michael@0: , mDT(aTarget) michael@0: , mOriginalDT(aTarget) michael@0: { michael@0: MOZ_COUNT_CTOR(gfxContext); michael@0: michael@0: mStateStack.SetLength(1); michael@0: CurrentState().drawTarget = mDT; michael@0: CurrentState().deviceOffset = aDeviceOffset; michael@0: mDT->SetTransform(Matrix()); michael@0: } michael@0: michael@0: /* static */ already_AddRefed michael@0: gfxContext::ContextForDrawTarget(DrawTarget* aTarget) michael@0: { michael@0: Matrix transform = aTarget->GetTransform(); michael@0: nsRefPtr result = new gfxContext(aTarget); michael@0: result->SetMatrix(ThebesMatrix(transform)); michael@0: return result.forget(); michael@0: } michael@0: michael@0: gfxContext::~gfxContext() michael@0: { michael@0: if (mCairo) { michael@0: cairo_destroy(mCairo); michael@0: } michael@0: if (mRefCairo) { michael@0: cairo_destroy(mRefCairo); michael@0: } michael@0: if (mDT) { michael@0: for (int i = mStateStack.Length() - 1; i >= 0; i--) { michael@0: for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { michael@0: mDT->PopClip(); michael@0: } michael@0: michael@0: if (mStateStack[i].clipWasReset) { michael@0: break; michael@0: } michael@0: } michael@0: mDT->Flush(); michael@0: } michael@0: MOZ_COUNT_DTOR(gfxContext); michael@0: } michael@0: michael@0: gfxASurface * michael@0: gfxContext::OriginalSurface() michael@0: { michael@0: if (mCairo || mSurface) { michael@0: return mSurface; michael@0: } michael@0: michael@0: if (mOriginalDT && mOriginalDT->GetType() == BackendType::CAIRO) { michael@0: cairo_surface_t *s = michael@0: (cairo_surface_t*)mOriginalDT->GetNativeSurface(NativeSurfaceType::CAIRO_SURFACE); michael@0: if (s) { michael@0: mSurface = gfxASurface::Wrap(s); michael@0: return mSurface; michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: already_AddRefed michael@0: gfxContext::CurrentSurface(gfxFloat *dx, gfxFloat *dy) michael@0: { michael@0: if (mCairo) { michael@0: cairo_surface_t *s = cairo_get_group_target(mCairo); michael@0: if (s == mSurface->CairoSurface()) { michael@0: if (dx && dy) michael@0: cairo_surface_get_device_offset(s, dx, dy); michael@0: nsRefPtr ret = mSurface; michael@0: return ret.forget(); michael@0: } michael@0: michael@0: if (dx && dy) michael@0: cairo_surface_get_device_offset(s, dx, dy); michael@0: return gfxASurface::Wrap(s); michael@0: } else { michael@0: if (mDT->GetType() == BackendType::CAIRO) { michael@0: cairo_surface_t *s = michael@0: (cairo_surface_t*)mDT->GetNativeSurface(NativeSurfaceType::CAIRO_SURFACE); michael@0: if (s) { michael@0: if (dx && dy) { michael@0: *dx = -CurrentState().deviceOffset.x; michael@0: *dy = -CurrentState().deviceOffset.y; michael@0: } michael@0: return gfxASurface::Wrap(s); michael@0: } michael@0: } michael@0: michael@0: if (dx && dy) { michael@0: *dx = *dy = 0; michael@0: } michael@0: // An Azure context doesn't have a surface backing it. michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: cairo_t * michael@0: gfxContext::GetCairo() michael@0: { michael@0: if (mCairo) { michael@0: return mCairo; michael@0: } michael@0: michael@0: if (mDT->GetType() == BackendType::CAIRO) { michael@0: cairo_t *ctx = michael@0: (cairo_t*)mDT->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT); michael@0: if (ctx) { michael@0: return ctx; michael@0: } michael@0: } michael@0: michael@0: if (mRefCairo) { michael@0: // Set transform! michael@0: return mRefCairo; michael@0: } michael@0: michael@0: mRefCairo = cairo_create(gfxPlatform::GetPlatform()->ScreenReferenceSurface()->CairoSurface()); michael@0: michael@0: return mRefCairo; michael@0: } michael@0: michael@0: void michael@0: gfxContext::Save() michael@0: { michael@0: if (mCairo) { michael@0: cairo_save(mCairo); michael@0: } else { michael@0: CurrentState().transform = mTransform; michael@0: mStateStack.AppendElement(AzureState(CurrentState())); michael@0: CurrentState().clipWasReset = false; michael@0: CurrentState().pushedClips.Clear(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::Restore() michael@0: { michael@0: if (mCairo) { michael@0: cairo_restore(mCairo); michael@0: } else { michael@0: for (unsigned int c = 0; c < CurrentState().pushedClips.Length(); c++) { michael@0: mDT->PopClip(); michael@0: } michael@0: michael@0: if (CurrentState().clipWasReset && michael@0: CurrentState().drawTarget == mStateStack[mStateStack.Length() - 2].drawTarget) { michael@0: PushClipsToDT(mDT); michael@0: } michael@0: michael@0: mStateStack.RemoveElementAt(mStateStack.Length() - 1); michael@0: michael@0: mDT = CurrentState().drawTarget; michael@0: michael@0: ChangeTransform(CurrentState().transform, false); michael@0: } michael@0: } michael@0: michael@0: // drawing michael@0: void michael@0: gfxContext::NewPath() michael@0: { michael@0: if (mCairo) { michael@0: cairo_new_path(mCairo); michael@0: } else { michael@0: mPath = nullptr; michael@0: mPathBuilder = nullptr; michael@0: mPathIsRect = false; michael@0: mTransformChanged = false; michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::ClosePath() michael@0: { michael@0: if (mCairo) { michael@0: cairo_close_path(mCairo); michael@0: } else { michael@0: EnsurePathBuilder(); michael@0: mPathBuilder->Close(); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed gfxContext::CopyPath() michael@0: { michael@0: nsRefPtr path; michael@0: if (mCairo) { michael@0: path = new gfxPath(cairo_copy_path(mCairo)); michael@0: } else { michael@0: EnsurePath(); michael@0: path = new gfxPath(mPath); michael@0: } michael@0: return path.forget(); michael@0: } michael@0: michael@0: void gfxContext::SetPath(gfxPath* path) michael@0: { michael@0: if (mCairo) { michael@0: cairo_new_path(mCairo); michael@0: if (path->mPath->status == CAIRO_STATUS_SUCCESS && path->mPath->num_data != 0) michael@0: cairo_append_path(mCairo, path->mPath); michael@0: } else { michael@0: MOZ_ASSERT(path->mMoz2DPath, "Can't mix cairo and azure paths!"); michael@0: MOZ_ASSERT(path->mMoz2DPath->GetBackendType() == mDT->GetType()); michael@0: mPath = path->mMoz2DPath; michael@0: mPathBuilder = nullptr; michael@0: mPathIsRect = false; michael@0: mTransformChanged = false; michael@0: } michael@0: } michael@0: michael@0: gfxPoint michael@0: gfxContext::CurrentPoint() michael@0: { michael@0: if (mCairo) { michael@0: double x, y; michael@0: cairo_get_current_point(mCairo, &x, &y); michael@0: return gfxPoint(x, y); michael@0: } else { michael@0: EnsurePathBuilder(); michael@0: return ThebesPoint(mPathBuilder->CurrentPoint()); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::Stroke() michael@0: { michael@0: if (mCairo) { michael@0: cairo_stroke_preserve(mCairo); michael@0: } else { michael@0: AzureState &state = CurrentState(); michael@0: if (mPathIsRect) { michael@0: MOZ_ASSERT(!mTransformChanged); michael@0: michael@0: mDT->StrokeRect(mRect, GeneralPattern(this), michael@0: state.strokeOptions, michael@0: DrawOptions(1.0f, GetOp(), state.aaMode)); michael@0: } else { michael@0: EnsurePath(); michael@0: michael@0: mDT->Stroke(mPath, GeneralPattern(this), state.strokeOptions, michael@0: DrawOptions(1.0f, GetOp(), state.aaMode)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::Fill() michael@0: { michael@0: PROFILER_LABEL("gfxContext", "Fill"); michael@0: if (mCairo) { michael@0: cairo_fill_preserve(mCairo); michael@0: } else { michael@0: FillAzure(1.0f); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::FillWithOpacity(gfxFloat aOpacity) michael@0: { michael@0: if (mCairo) { michael@0: // This method exists in the hope that one day cairo gets a direct michael@0: // API for this, and then we would change this method to use that michael@0: // API instead. michael@0: if (aOpacity != 1.0) { michael@0: gfxContextAutoSaveRestore saveRestore(this); michael@0: Clip(); michael@0: Paint(aOpacity); michael@0: } else { michael@0: Fill(); michael@0: } michael@0: } else { michael@0: FillAzure(Float(aOpacity)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::MoveTo(const gfxPoint& pt) michael@0: { michael@0: if (mCairo) { michael@0: cairo_move_to(mCairo, pt.x, pt.y); michael@0: } else { michael@0: EnsurePathBuilder(); michael@0: mPathBuilder->MoveTo(ToPoint(pt)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::NewSubPath() michael@0: { michael@0: if (mCairo) { michael@0: cairo_new_sub_path(mCairo); michael@0: } else { michael@0: // XXX - This has no users, we should kill it, it should be equivelant to a michael@0: // MoveTo to the path's current point. michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::LineTo(const gfxPoint& pt) michael@0: { michael@0: if (mCairo) { michael@0: cairo_line_to(mCairo, pt.x, pt.y); michael@0: } else { michael@0: EnsurePathBuilder(); michael@0: mPathBuilder->LineTo(ToPoint(pt)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::CurveTo(const gfxPoint& pt1, const gfxPoint& pt2, const gfxPoint& pt3) michael@0: { michael@0: if (mCairo) { michael@0: cairo_curve_to(mCairo, pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y); michael@0: } else { michael@0: EnsurePathBuilder(); michael@0: mPathBuilder->BezierTo(ToPoint(pt1), ToPoint(pt2), ToPoint(pt3)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::QuadraticCurveTo(const gfxPoint& pt1, const gfxPoint& pt2) michael@0: { michael@0: if (mCairo) { michael@0: double cx, cy; michael@0: cairo_get_current_point(mCairo, &cx, &cy); michael@0: cairo_curve_to(mCairo, michael@0: (cx + pt1.x * 2.0) / 3.0, michael@0: (cy + pt1.y * 2.0) / 3.0, michael@0: (pt1.x * 2.0 + pt2.x) / 3.0, michael@0: (pt1.y * 2.0 + pt2.y) / 3.0, michael@0: pt2.x, michael@0: pt2.y); michael@0: } else { michael@0: EnsurePathBuilder(); michael@0: mPathBuilder->QuadraticBezierTo(ToPoint(pt1), ToPoint(pt2)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::Arc(const gfxPoint& center, gfxFloat radius, michael@0: gfxFloat angle1, gfxFloat angle2) michael@0: { michael@0: if (mCairo) { michael@0: cairo_arc(mCairo, center.x, center.y, radius, angle1, angle2); michael@0: } else { michael@0: EnsurePathBuilder(); michael@0: mPathBuilder->Arc(ToPoint(center), Float(radius), Float(angle1), Float(angle2)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::NegativeArc(const gfxPoint& center, gfxFloat radius, michael@0: gfxFloat angle1, gfxFloat angle2) michael@0: { michael@0: if (mCairo) { michael@0: cairo_arc_negative(mCairo, center.x, center.y, radius, angle1, angle2); michael@0: } else { michael@0: EnsurePathBuilder(); michael@0: mPathBuilder->Arc(ToPoint(center), Float(radius), Float(angle2), Float(angle1)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::Line(const gfxPoint& start, const gfxPoint& end) michael@0: { michael@0: if (mCairo) { michael@0: MoveTo(start); michael@0: LineTo(end); michael@0: } else { michael@0: EnsurePathBuilder(); michael@0: mPathBuilder->MoveTo(ToPoint(start)); michael@0: mPathBuilder->LineTo(ToPoint(end)); michael@0: } michael@0: } michael@0: michael@0: // XXX snapToPixels is only valid when snapping for filled michael@0: // rectangles and for even-width stroked rectangles. michael@0: // For odd-width stroked rectangles, we need to offset x/y by michael@0: // 0.5... michael@0: void michael@0: gfxContext::Rectangle(const gfxRect& rect, bool snapToPixels) michael@0: { michael@0: if (mCairo) { michael@0: if (snapToPixels) { michael@0: gfxRect snappedRect(rect); michael@0: michael@0: if (UserToDevicePixelSnapped(snappedRect, true)) michael@0: { michael@0: cairo_matrix_t mat; michael@0: cairo_get_matrix(mCairo, &mat); michael@0: cairo_identity_matrix(mCairo); michael@0: Rectangle(snappedRect); michael@0: cairo_set_matrix(mCairo, &mat); michael@0: michael@0: return; michael@0: } michael@0: } michael@0: michael@0: cairo_rectangle(mCairo, rect.X(), rect.Y(), rect.Width(), rect.Height()); michael@0: } else { michael@0: Rect rec = ToRect(rect); michael@0: michael@0: if (snapToPixels) { michael@0: gfxRect newRect(rect); michael@0: if (UserToDevicePixelSnapped(newRect, true)) { michael@0: gfxMatrix mat = ThebesMatrix(mTransform); michael@0: mat.Invert(); michael@0: michael@0: // We need the user space rect. michael@0: rec = ToRect(mat.TransformBounds(newRect)); michael@0: } michael@0: } michael@0: michael@0: if (!mPathBuilder && !mPathIsRect) { michael@0: mPathIsRect = true; michael@0: mRect = rec; michael@0: return; michael@0: } michael@0: michael@0: EnsurePathBuilder(); michael@0: michael@0: mPathBuilder->MoveTo(rec.TopLeft()); michael@0: mPathBuilder->LineTo(rec.TopRight()); michael@0: mPathBuilder->LineTo(rec.BottomRight()); michael@0: mPathBuilder->LineTo(rec.BottomLeft()); michael@0: mPathBuilder->Close(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::Ellipse(const gfxPoint& center, const gfxSize& dimensions) michael@0: { michael@0: gfxSize halfDim = dimensions / 2.0; michael@0: gfxRect r(center - gfxPoint(halfDim.width, halfDim.height), dimensions); michael@0: gfxCornerSizes c(halfDim, halfDim, halfDim, halfDim); michael@0: michael@0: RoundedRectangle (r, c); michael@0: } michael@0: michael@0: void michael@0: gfxContext::Polygon(const gfxPoint *points, uint32_t numPoints) michael@0: { michael@0: if (mCairo) { michael@0: if (numPoints == 0) michael@0: return; michael@0: michael@0: cairo_move_to(mCairo, points[0].x, points[0].y); michael@0: for (uint32_t i = 1; i < numPoints; ++i) { michael@0: cairo_line_to(mCairo, points[i].x, points[i].y); michael@0: } michael@0: } else { michael@0: if (numPoints == 0) { michael@0: return; michael@0: } michael@0: michael@0: EnsurePathBuilder(); michael@0: michael@0: mPathBuilder->MoveTo(ToPoint(points[0])); michael@0: for (uint32_t i = 1; i < numPoints; i++) { michael@0: mPathBuilder->LineTo(ToPoint(points[i])); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::DrawSurface(gfxASurface *surface, const gfxSize& size) michael@0: { michael@0: if (mCairo) { michael@0: cairo_save(mCairo); michael@0: cairo_set_source_surface(mCairo, surface->CairoSurface(), 0, 0); michael@0: cairo_new_path(mCairo); michael@0: michael@0: // pixel-snap this michael@0: Rectangle(gfxRect(gfxPoint(0.0, 0.0), size), true); michael@0: michael@0: cairo_fill(mCairo); michael@0: cairo_restore(mCairo); michael@0: } else { michael@0: // Lifetime needs to be limited here since we may wrap surface's data. michael@0: RefPtr surf = michael@0: gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface); michael@0: michael@0: if (!surf) { michael@0: return; michael@0: } michael@0: michael@0: Rect rect(0, 0, Float(size.width), Float(size.height)); michael@0: rect.Intersect(Rect(0, 0, Float(surf->GetSize().width), Float(surf->GetSize().height))); michael@0: michael@0: // XXX - Should fix pixel snapping. michael@0: mDT->DrawSurface(surf, rect, rect); michael@0: } michael@0: } michael@0: michael@0: // transform stuff michael@0: void michael@0: gfxContext::Translate(const gfxPoint& pt) michael@0: { michael@0: if (mCairo) { michael@0: cairo_translate(mCairo, pt.x, pt.y); michael@0: } else { michael@0: Matrix newMatrix = mTransform; michael@0: michael@0: ChangeTransform(newMatrix.Translate(Float(pt.x), Float(pt.y))); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::Scale(gfxFloat x, gfxFloat y) michael@0: { michael@0: if (mCairo) { michael@0: cairo_scale(mCairo, x, y); michael@0: } else { michael@0: Matrix newMatrix = mTransform; michael@0: michael@0: ChangeTransform(newMatrix.Scale(Float(x), Float(y))); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::Rotate(gfxFloat angle) michael@0: { michael@0: if (mCairo) { michael@0: cairo_rotate(mCairo, angle); michael@0: } else { michael@0: Matrix rotation = Matrix::Rotation(Float(angle)); michael@0: ChangeTransform(rotation * mTransform); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::Multiply(const gfxMatrix& matrix) michael@0: { michael@0: if (mCairo) { michael@0: const cairo_matrix_t& mat = reinterpret_cast(matrix); michael@0: cairo_transform(mCairo, &mat); michael@0: } else { michael@0: ChangeTransform(ToMatrix(matrix) * mTransform); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::MultiplyAndNudgeToIntegers(const gfxMatrix& matrix) michael@0: { michael@0: if (mCairo) { michael@0: const cairo_matrix_t& mat = reinterpret_cast(matrix); michael@0: cairo_transform(mCairo, &mat); michael@0: // XXX nudging to integers not currently supported for Thebes michael@0: } else { michael@0: Matrix transform = ToMatrix(matrix) * mTransform; michael@0: transform.NudgeToIntegers(); michael@0: ChangeTransform(transform); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::SetMatrix(const gfxMatrix& matrix) michael@0: { michael@0: if (mCairo) { michael@0: const cairo_matrix_t& mat = reinterpret_cast(matrix); michael@0: cairo_set_matrix(mCairo, &mat); michael@0: } else { michael@0: ChangeTransform(ToMatrix(matrix)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::IdentityMatrix() michael@0: { michael@0: if (mCairo) { michael@0: cairo_identity_matrix(mCairo); michael@0: } else { michael@0: ChangeTransform(Matrix()); michael@0: } michael@0: } michael@0: michael@0: gfxMatrix michael@0: gfxContext::CurrentMatrix() const michael@0: { michael@0: if (mCairo) { michael@0: cairo_matrix_t mat; michael@0: cairo_get_matrix(mCairo, &mat); michael@0: return gfxMatrix(*reinterpret_cast(&mat)); michael@0: } else { michael@0: return ThebesMatrix(mTransform); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::NudgeCurrentMatrixToIntegers() michael@0: { michael@0: if (mCairo) { michael@0: cairo_matrix_t mat; michael@0: cairo_get_matrix(mCairo, &mat); michael@0: gfxMatrix(*reinterpret_cast(&mat)).NudgeToIntegers(); michael@0: cairo_set_matrix(mCairo, &mat); michael@0: } else { michael@0: gfxMatrix matrix = ThebesMatrix(mTransform); michael@0: matrix.NudgeToIntegers(); michael@0: ChangeTransform(ToMatrix(matrix)); michael@0: } michael@0: } michael@0: michael@0: gfxPoint michael@0: gfxContext::DeviceToUser(const gfxPoint& point) const michael@0: { michael@0: if (mCairo) { michael@0: gfxPoint ret = point; michael@0: cairo_device_to_user(mCairo, &ret.x, &ret.y); michael@0: return ret; michael@0: } else { michael@0: Matrix matrix = mTransform; michael@0: michael@0: matrix.Invert(); michael@0: michael@0: return ThebesPoint(matrix * ToPoint(point)); michael@0: } michael@0: } michael@0: michael@0: gfxSize michael@0: gfxContext::DeviceToUser(const gfxSize& size) const michael@0: { michael@0: if (mCairo) { michael@0: gfxSize ret = size; michael@0: cairo_device_to_user_distance(mCairo, &ret.width, &ret.height); michael@0: return ret; michael@0: } else { michael@0: Matrix matrix = mTransform; michael@0: michael@0: matrix.Invert(); michael@0: michael@0: return ThebesSize(matrix * ToSize(size)); michael@0: } michael@0: } michael@0: michael@0: gfxRect michael@0: gfxContext::DeviceToUser(const gfxRect& rect) const michael@0: { michael@0: if (mCairo) { michael@0: gfxRect ret = rect; michael@0: cairo_device_to_user(mCairo, &ret.x, &ret.y); michael@0: cairo_device_to_user_distance(mCairo, &ret.width, &ret.height); michael@0: return ret; michael@0: } else { michael@0: Matrix matrix = mTransform; michael@0: michael@0: matrix.Invert(); michael@0: michael@0: return ThebesRect(matrix.TransformBounds(ToRect(rect))); michael@0: } michael@0: } michael@0: michael@0: gfxPoint michael@0: gfxContext::UserToDevice(const gfxPoint& point) const michael@0: { michael@0: if (mCairo) { michael@0: gfxPoint ret = point; michael@0: cairo_user_to_device(mCairo, &ret.x, &ret.y); michael@0: return ret; michael@0: } else { michael@0: return ThebesPoint(mTransform * ToPoint(point)); michael@0: } michael@0: } michael@0: michael@0: gfxSize michael@0: gfxContext::UserToDevice(const gfxSize& size) const michael@0: { michael@0: if (mCairo) { michael@0: gfxSize ret = size; michael@0: cairo_user_to_device_distance(mCairo, &ret.width, &ret.height); michael@0: return ret; michael@0: } else { michael@0: const Matrix &matrix = mTransform; michael@0: michael@0: gfxSize newSize; michael@0: newSize.width = size.width * matrix._11 + size.height * matrix._12; michael@0: newSize.height = size.width * matrix._21 + size.height * matrix._22; michael@0: return newSize; michael@0: } michael@0: } michael@0: michael@0: gfxRect michael@0: gfxContext::UserToDevice(const gfxRect& rect) const michael@0: { michael@0: if (mCairo) { michael@0: double xmin = rect.X(), ymin = rect.Y(), xmax = rect.XMost(), ymax = rect.YMost(); michael@0: michael@0: double x[3], y[3]; michael@0: x[0] = xmin; y[0] = ymax; michael@0: x[1] = xmax; y[1] = ymax; michael@0: x[2] = xmax; y[2] = ymin; michael@0: michael@0: cairo_user_to_device(mCairo, &xmin, &ymin); michael@0: xmax = xmin; michael@0: ymax = ymin; michael@0: for (int i = 0; i < 3; i++) { michael@0: cairo_user_to_device(mCairo, &x[i], &y[i]); michael@0: xmin = std::min(xmin, x[i]); michael@0: xmax = std::max(xmax, x[i]); michael@0: ymin = std::min(ymin, y[i]); michael@0: ymax = std::max(ymax, y[i]); michael@0: } michael@0: michael@0: return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin); michael@0: } else { michael@0: const Matrix &matrix = mTransform; michael@0: return ThebesRect(matrix.TransformBounds(ToRect(rect))); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxContext::UserToDevicePixelSnapped(gfxRect& rect, bool ignoreScale) const michael@0: { michael@0: if (GetFlags() & FLAG_DISABLE_SNAPPING) michael@0: return false; michael@0: michael@0: // if we're not at 1.0 scale, don't snap, unless we're michael@0: // ignoring the scale. If we're not -just- a scale, michael@0: // never snap. michael@0: const gfxFloat epsilon = 0.0000001; michael@0: #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon) michael@0: if (mCairo) { michael@0: cairo_matrix_t mat; michael@0: cairo_get_matrix(mCairo, &mat); michael@0: if (!ignoreScale && michael@0: (!WITHIN_E(mat.xx,1.0) || !WITHIN_E(mat.yy,1.0) || michael@0: !WITHIN_E(mat.xy,0.0) || !WITHIN_E(mat.yx,0.0))) michael@0: return false; michael@0: } else { michael@0: Matrix mat = mTransform; michael@0: if (!ignoreScale && michael@0: (!WITHIN_E(mat._11,1.0) || !WITHIN_E(mat._22,1.0) || michael@0: !WITHIN_E(mat._12,0.0) || !WITHIN_E(mat._21,0.0))) michael@0: return false; michael@0: } michael@0: #undef WITHIN_E michael@0: michael@0: gfxPoint p1 = UserToDevice(rect.TopLeft()); michael@0: gfxPoint p2 = UserToDevice(rect.TopRight()); michael@0: gfxPoint p3 = UserToDevice(rect.BottomRight()); michael@0: michael@0: // Check that the rectangle is axis-aligned. For an axis-aligned rectangle, michael@0: // two opposite corners define the entire rectangle. So check if michael@0: // the axis-aligned rectangle with opposite corners p1 and p3 michael@0: // define an axis-aligned rectangle whose other corners are p2 and p4. michael@0: // We actually only need to check one of p2 and p4, since an affine michael@0: // transform maps parallelograms to parallelograms. michael@0: if (p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y)) { michael@0: p1.Round(); michael@0: p3.Round(); michael@0: michael@0: rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y))); michael@0: rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(), michael@0: std::max(p1.y, p3.y) - rect.Y())); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: gfxContext::UserToDevicePixelSnapped(gfxPoint& pt, bool ignoreScale) const michael@0: { michael@0: if (GetFlags() & FLAG_DISABLE_SNAPPING) michael@0: return false; michael@0: michael@0: // if we're not at 1.0 scale, don't snap, unless we're michael@0: // ignoring the scale. If we're not -just- a scale, michael@0: // never snap. michael@0: const gfxFloat epsilon = 0.0000001; michael@0: #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon) michael@0: if (mCairo) { michael@0: cairo_matrix_t mat; michael@0: cairo_get_matrix(mCairo, &mat); michael@0: if (!ignoreScale && michael@0: (!WITHIN_E(mat.xx,1.0) || !WITHIN_E(mat.yy,1.0) || michael@0: !WITHIN_E(mat.xy,0.0) || !WITHIN_E(mat.yx,0.0))) michael@0: return false; michael@0: } else { michael@0: Matrix mat = mTransform; michael@0: if (!ignoreScale && michael@0: (!WITHIN_E(mat._11,1.0) || !WITHIN_E(mat._22,1.0) || michael@0: !WITHIN_E(mat._12,0.0) || !WITHIN_E(mat._21,0.0))) michael@0: return false; michael@0: } michael@0: #undef WITHIN_E michael@0: michael@0: pt = UserToDevice(pt); michael@0: pt.Round(); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: gfxContext::PixelSnappedRectangleAndSetPattern(const gfxRect& rect, michael@0: gfxPattern *pattern) michael@0: { michael@0: gfxRect r(rect); michael@0: michael@0: // Bob attempts to pixel-snap the rectangle, and returns true if michael@0: // the snapping succeeds. If it does, we need to set up an michael@0: // identity matrix, because the rectangle given back is in device michael@0: // coordinates. michael@0: // michael@0: // We then have to call a translate to dr.pos afterwards, to make michael@0: // sure the image lines up in the right place with our pixel michael@0: // snapped rectangle. michael@0: // michael@0: // If snapping wasn't successful, we just translate to where the michael@0: // pattern would normally start (in app coordinates) and do the michael@0: // same thing. michael@0: Rectangle(r, true); michael@0: SetPattern(pattern); michael@0: } michael@0: michael@0: void michael@0: gfxContext::SetAntialiasMode(AntialiasMode mode) michael@0: { michael@0: if (mCairo) { michael@0: if (mode == MODE_ALIASED) { michael@0: cairo_set_antialias(mCairo, CAIRO_ANTIALIAS_NONE); michael@0: } else if (mode == MODE_COVERAGE) { michael@0: cairo_set_antialias(mCairo, CAIRO_ANTIALIAS_DEFAULT); michael@0: } michael@0: } else { michael@0: if (mode == MODE_ALIASED) { michael@0: CurrentState().aaMode = gfx::AntialiasMode::NONE; michael@0: } else if (mode == MODE_COVERAGE) { michael@0: CurrentState().aaMode = gfx::AntialiasMode::SUBPIXEL; michael@0: } michael@0: } michael@0: } michael@0: michael@0: gfxContext::AntialiasMode michael@0: gfxContext::CurrentAntialiasMode() const michael@0: { michael@0: if (mCairo) { michael@0: cairo_antialias_t aa = cairo_get_antialias(mCairo); michael@0: if (aa == CAIRO_ANTIALIAS_NONE) michael@0: return MODE_ALIASED; michael@0: return MODE_COVERAGE; michael@0: } else { michael@0: if (CurrentState().aaMode == gfx::AntialiasMode::NONE) { michael@0: return MODE_ALIASED; michael@0: } michael@0: return MODE_COVERAGE; michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::SetDash(gfxLineType ltype) michael@0: { michael@0: static double dash[] = {5.0, 5.0}; michael@0: static double dot[] = {1.0, 1.0}; michael@0: michael@0: switch (ltype) { michael@0: case gfxLineDashed: michael@0: SetDash(dash, 2, 0.0); michael@0: break; michael@0: case gfxLineDotted: michael@0: SetDash(dot, 2, 0.0); michael@0: break; michael@0: case gfxLineSolid: michael@0: default: michael@0: SetDash(nullptr, 0, 0.0); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::SetDash(gfxFloat *dashes, int ndash, gfxFloat offset) michael@0: { michael@0: if (mCairo) { michael@0: cairo_set_dash(mCairo, dashes, ndash, offset); michael@0: } else { michael@0: AzureState &state = CurrentState(); michael@0: michael@0: state.dashPattern.SetLength(ndash); michael@0: for (int i = 0; i < ndash; i++) { michael@0: state.dashPattern[i] = Float(dashes[i]); michael@0: } michael@0: state.strokeOptions.mDashLength = ndash; michael@0: state.strokeOptions.mDashOffset = Float(offset); michael@0: state.strokeOptions.mDashPattern = ndash ? state.dashPattern.Elements() michael@0: : nullptr; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxContext::CurrentDash(FallibleTArray& dashes, gfxFloat* offset) const michael@0: { michael@0: if (mCairo) { michael@0: int count = cairo_get_dash_count(mCairo); michael@0: if (count <= 0 || !dashes.SetLength(count)) { michael@0: return false; michael@0: } michael@0: cairo_get_dash(mCairo, dashes.Elements(), offset); michael@0: return true; michael@0: } else { michael@0: const AzureState &state = CurrentState(); michael@0: int count = state.strokeOptions.mDashLength; michael@0: michael@0: if (count <= 0 || !dashes.SetLength(count)) { michael@0: return false; michael@0: } michael@0: michael@0: for (int i = 0; i < count; i++) { michael@0: dashes[i] = state.dashPattern[i]; michael@0: } michael@0: michael@0: *offset = state.strokeOptions.mDashOffset; michael@0: michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: gfxFloat michael@0: gfxContext::CurrentDashOffset() const michael@0: { michael@0: if (mCairo) { michael@0: if (cairo_get_dash_count(mCairo) <= 0) { michael@0: return 0.0; michael@0: } michael@0: gfxFloat offset; michael@0: cairo_get_dash(mCairo, nullptr, &offset); michael@0: return offset; michael@0: } else { michael@0: return CurrentState().strokeOptions.mDashOffset; michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::SetLineWidth(gfxFloat width) michael@0: { michael@0: if (mCairo) { michael@0: cairo_set_line_width(mCairo, width); michael@0: } else { michael@0: CurrentState().strokeOptions.mLineWidth = Float(width); michael@0: } michael@0: } michael@0: michael@0: gfxFloat michael@0: gfxContext::CurrentLineWidth() const michael@0: { michael@0: if (mCairo) { michael@0: return cairo_get_line_width(mCairo); michael@0: } else { michael@0: return CurrentState().strokeOptions.mLineWidth; michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::SetOperator(GraphicsOperator op) michael@0: { michael@0: if (mCairo) { michael@0: if (mFlags & FLAG_SIMPLIFY_OPERATORS) { michael@0: if (op != OPERATOR_SOURCE && michael@0: op != OPERATOR_CLEAR && michael@0: op != OPERATOR_OVER) michael@0: op = OPERATOR_OVER; michael@0: } michael@0: michael@0: cairo_set_operator(mCairo, (cairo_operator_t)op); michael@0: } else { michael@0: if (op == OPERATOR_CLEAR) { michael@0: CurrentState().opIsClear = true; michael@0: return; michael@0: } michael@0: CurrentState().opIsClear = false; michael@0: CurrentState().op = CompositionOpForOp(op); michael@0: } michael@0: } michael@0: michael@0: gfxContext::GraphicsOperator michael@0: gfxContext::CurrentOperator() const michael@0: { michael@0: if (mCairo) { michael@0: return (GraphicsOperator)cairo_get_operator(mCairo); michael@0: } else { michael@0: return ThebesOp(CurrentState().op); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::SetLineCap(GraphicsLineCap cap) michael@0: { michael@0: if (mCairo) { michael@0: cairo_set_line_cap(mCairo, (cairo_line_cap_t)cap); michael@0: } else { michael@0: CurrentState().strokeOptions.mLineCap = ToCapStyle(cap); michael@0: } michael@0: } michael@0: michael@0: gfxContext::GraphicsLineCap michael@0: gfxContext::CurrentLineCap() const michael@0: { michael@0: if (mCairo) { michael@0: return (GraphicsLineCap)cairo_get_line_cap(mCairo); michael@0: } else { michael@0: return ThebesLineCap(CurrentState().strokeOptions.mLineCap); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::SetLineJoin(GraphicsLineJoin join) michael@0: { michael@0: if (mCairo) { michael@0: cairo_set_line_join(mCairo, (cairo_line_join_t)join); michael@0: } else { michael@0: CurrentState().strokeOptions.mLineJoin = ToJoinStyle(join); michael@0: } michael@0: } michael@0: michael@0: gfxContext::GraphicsLineJoin michael@0: gfxContext::CurrentLineJoin() const michael@0: { michael@0: if (mCairo) { michael@0: return (GraphicsLineJoin)cairo_get_line_join(mCairo); michael@0: } else { michael@0: return ThebesLineJoin(CurrentState().strokeOptions.mLineJoin); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::SetMiterLimit(gfxFloat limit) michael@0: { michael@0: if (mCairo) { michael@0: cairo_set_miter_limit(mCairo, limit); michael@0: } else { michael@0: CurrentState().strokeOptions.mMiterLimit = Float(limit); michael@0: } michael@0: } michael@0: michael@0: gfxFloat michael@0: gfxContext::CurrentMiterLimit() const michael@0: { michael@0: if (mCairo) { michael@0: return cairo_get_miter_limit(mCairo); michael@0: } else { michael@0: return CurrentState().strokeOptions.mMiterLimit; michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::SetFillRule(FillRule rule) michael@0: { michael@0: if (mCairo) { michael@0: cairo_set_fill_rule(mCairo, (cairo_fill_rule_t)rule); michael@0: } else { michael@0: CurrentState().fillRule = rule == FILL_RULE_WINDING ? gfx::FillRule::FILL_WINDING : gfx::FillRule::FILL_EVEN_ODD; michael@0: } michael@0: } michael@0: michael@0: gfxContext::FillRule michael@0: gfxContext::CurrentFillRule() const michael@0: { michael@0: if (mCairo) { michael@0: return (FillRule)cairo_get_fill_rule(mCairo); michael@0: } else { michael@0: return FILL_RULE_WINDING; michael@0: } michael@0: } michael@0: michael@0: // clipping michael@0: void michael@0: gfxContext::Clip(const gfxRect& rect) michael@0: { michael@0: if (mCairo) { michael@0: cairo_new_path(mCairo); michael@0: cairo_rectangle(mCairo, rect.X(), rect.Y(), rect.Width(), rect.Height()); michael@0: cairo_clip(mCairo); michael@0: } else { michael@0: AzureState::PushedClip clip = { nullptr, ToRect(rect), mTransform }; michael@0: CurrentState().pushedClips.AppendElement(clip); michael@0: mDT->PushClipRect(ToRect(rect)); michael@0: NewPath(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::Clip() michael@0: { michael@0: if (mCairo) { michael@0: cairo_clip_preserve(mCairo); michael@0: } else { michael@0: if (mPathIsRect) { michael@0: MOZ_ASSERT(!mTransformChanged); michael@0: michael@0: AzureState::PushedClip clip = { nullptr, mRect, mTransform }; michael@0: CurrentState().pushedClips.AppendElement(clip); michael@0: mDT->PushClipRect(mRect); michael@0: } else { michael@0: EnsurePath(); michael@0: mDT->PushClip(mPath); michael@0: AzureState::PushedClip clip = { mPath, Rect(), mTransform }; michael@0: CurrentState().pushedClips.AppendElement(clip); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::ResetClip() michael@0: { michael@0: if (mCairo) { michael@0: cairo_reset_clip(mCairo); michael@0: } else { michael@0: for (int i = mStateStack.Length() - 1; i >= 0; i--) { michael@0: for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { michael@0: mDT->PopClip(); michael@0: } michael@0: michael@0: if (mStateStack[i].clipWasReset) { michael@0: break; michael@0: } michael@0: } michael@0: CurrentState().pushedClips.Clear(); michael@0: CurrentState().clipWasReset = true; michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::UpdateSurfaceClip() michael@0: { michael@0: if (mCairo) { michael@0: NewPath(); michael@0: // we paint an empty rectangle to ensure the clip is propagated to michael@0: // the destination surface michael@0: SetDeviceColor(gfxRGBA(0,0,0,0)); michael@0: Rectangle(gfxRect(0,1,1,0)); michael@0: Fill(); michael@0: } michael@0: } michael@0: michael@0: gfxRect michael@0: gfxContext::GetClipExtents() michael@0: { michael@0: if (mCairo) { michael@0: double xmin, ymin, xmax, ymax; michael@0: cairo_clip_extents(mCairo, &xmin, &ymin, &xmax, &ymax); michael@0: return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin); michael@0: } else { michael@0: Rect rect = GetAzureDeviceSpaceClipBounds(); michael@0: michael@0: if (rect.width == 0 || rect.height == 0) { michael@0: return gfxRect(0, 0, 0, 0); michael@0: } michael@0: michael@0: Matrix mat = mTransform; michael@0: mat.Invert(); michael@0: rect = mat.TransformBounds(rect); michael@0: michael@0: return ThebesRect(rect); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxContext::ClipContainsRect(const gfxRect& aRect) michael@0: { michael@0: if (mCairo) { michael@0: cairo_rectangle_list_t *clip = michael@0: cairo_copy_clip_rectangle_list(mCairo); michael@0: michael@0: bool result = false; michael@0: michael@0: if (clip->status == CAIRO_STATUS_SUCCESS) { michael@0: for (int i = 0; i < clip->num_rectangles; i++) { michael@0: gfxRect rect(clip->rectangles[i].x, clip->rectangles[i].y, michael@0: clip->rectangles[i].width, clip->rectangles[i].height); michael@0: if (rect.Contains(aRect)) { michael@0: result = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: cairo_rectangle_list_destroy(clip); michael@0: return result; michael@0: } else { michael@0: unsigned int lastReset = 0; michael@0: for (int i = mStateStack.Length() - 2; i > 0; i--) { michael@0: if (mStateStack[i].clipWasReset) { michael@0: lastReset = i; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Since we always return false when the clip list contains a michael@0: // non-rectangular clip or a non-rectilinear transform, our 'total' clip michael@0: // is always a rectangle if we hit the end of this function. michael@0: Rect clipBounds(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height)); michael@0: michael@0: for (unsigned int i = lastReset; i < mStateStack.Length(); i++) { michael@0: for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { michael@0: AzureState::PushedClip &clip = mStateStack[i].pushedClips[c]; michael@0: if (clip.path || !clip.transform.IsRectilinear()) { michael@0: // Cairo behavior is we return false if the clip contains a non- michael@0: // rectangle. michael@0: return false; michael@0: } else { michael@0: Rect clipRect = mTransform.TransformBounds(clip.rect); michael@0: michael@0: clipBounds.IntersectRect(clipBounds, clipRect); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return clipBounds.Contains(ToRect(aRect)); michael@0: } michael@0: } michael@0: michael@0: // rendering sources michael@0: michael@0: void michael@0: gfxContext::SetColor(const gfxRGBA& c) michael@0: { michael@0: if (mCairo) { michael@0: if (gfxPlatform::GetCMSMode() == eCMSMode_All) { michael@0: michael@0: gfxRGBA cms; michael@0: qcms_transform *transform = gfxPlatform::GetCMSRGBTransform(); michael@0: if (transform) michael@0: gfxPlatform::TransformPixel(c, cms, transform); michael@0: michael@0: // Use the original alpha to avoid unnecessary float->byte->float michael@0: // conversion errors michael@0: cairo_set_source_rgba(mCairo, cms.r, cms.g, cms.b, c.a); michael@0: } michael@0: else michael@0: cairo_set_source_rgba(mCairo, c.r, c.g, c.b, c.a); michael@0: } else { michael@0: CurrentState().pattern = nullptr; michael@0: CurrentState().sourceSurfCairo = nullptr; michael@0: CurrentState().sourceSurface = nullptr; michael@0: michael@0: if (gfxPlatform::GetCMSMode() == eCMSMode_All) { michael@0: michael@0: gfxRGBA cms; michael@0: qcms_transform *transform = gfxPlatform::GetCMSRGBTransform(); michael@0: if (transform) michael@0: gfxPlatform::TransformPixel(c, cms, transform); michael@0: michael@0: // Use the original alpha to avoid unnecessary float->byte->float michael@0: // conversion errors michael@0: CurrentState().color = ToColor(cms); michael@0: } michael@0: else michael@0: CurrentState().color = ToColor(c); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::SetDeviceColor(const gfxRGBA& c) michael@0: { michael@0: if (mCairo) { michael@0: cairo_set_source_rgba(mCairo, c.r, c.g, c.b, c.a); michael@0: } else { michael@0: CurrentState().pattern = nullptr; michael@0: CurrentState().sourceSurfCairo = nullptr; michael@0: CurrentState().sourceSurface = nullptr; michael@0: CurrentState().color = ToColor(c); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxContext::GetDeviceColor(gfxRGBA& c) michael@0: { michael@0: if (mCairo) { michael@0: return cairo_pattern_get_rgba(cairo_get_source(mCairo), michael@0: &c.r, michael@0: &c.g, michael@0: &c.b, michael@0: &c.a) == CAIRO_STATUS_SUCCESS; michael@0: } else { michael@0: if (CurrentState().sourceSurface) { michael@0: return false; michael@0: } michael@0: if (CurrentState().pattern) { michael@0: gfxRGBA color; michael@0: return CurrentState().pattern->GetSolidColor(c); michael@0: } michael@0: michael@0: c = ThebesRGBA(CurrentState().color); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::SetSource(gfxASurface *surface, const gfxPoint& offset) michael@0: { michael@0: if (mCairo) { michael@0: NS_ASSERTION(surface->GetAllowUseAsSource(), "Surface not allowed to be used as source!"); michael@0: cairo_set_source_surface(mCairo, surface->CairoSurface(), offset.x, offset.y); michael@0: } else { michael@0: CurrentState().surfTransform = Matrix(1.0f, 0, 0, 1.0f, Float(offset.x), Float(offset.y)); michael@0: CurrentState().pattern = nullptr; michael@0: CurrentState().patternTransformChanged = false; michael@0: // Keep the underlying cairo surface around while we keep the michael@0: // sourceSurface. michael@0: CurrentState().sourceSurfCairo = surface; michael@0: CurrentState().sourceSurface = michael@0: gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface); michael@0: CurrentState().color = Color(0, 0, 0, 0); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::SetPattern(gfxPattern *pattern) michael@0: { michael@0: if (mCairo) { michael@0: MOZ_ASSERT(!pattern->IsAzure()); michael@0: cairo_set_source(mCairo, pattern->CairoPattern()); michael@0: } else { michael@0: CurrentState().sourceSurfCairo = nullptr; michael@0: CurrentState().sourceSurface = nullptr; michael@0: CurrentState().patternTransformChanged = false; michael@0: CurrentState().pattern = pattern; michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: gfxContext::GetPattern() michael@0: { michael@0: if (mCairo) { michael@0: cairo_pattern_t *pat = cairo_get_source(mCairo); michael@0: NS_ASSERTION(pat, "I was told this couldn't be null"); michael@0: michael@0: nsRefPtr wrapper; michael@0: if (pat) michael@0: wrapper = new gfxPattern(pat); michael@0: else michael@0: wrapper = new gfxPattern(gfxRGBA(0,0,0,0)); michael@0: michael@0: return wrapper.forget(); michael@0: } else { michael@0: nsRefPtr pat; michael@0: michael@0: AzureState &state = CurrentState(); michael@0: if (state.pattern) { michael@0: pat = state.pattern; michael@0: } else if (state.sourceSurface) { michael@0: NS_ASSERTION(false, "Ugh, this isn't good."); michael@0: } else { michael@0: pat = new gfxPattern(ThebesRGBA(state.color)); michael@0: } michael@0: return pat.forget(); michael@0: } michael@0: } michael@0: michael@0: michael@0: // masking michael@0: void michael@0: gfxContext::Mask(gfxPattern *pattern) michael@0: { michael@0: if (mCairo) { michael@0: MOZ_ASSERT(!pattern->IsAzure()); michael@0: cairo_mask(mCairo, pattern->CairoPattern()); michael@0: } else { michael@0: if (pattern->Extend() == gfxPattern::EXTEND_NONE) { michael@0: // In this situation the mask will be fully transparent (i.e. nothing michael@0: // will be drawn) outside of the bounds of the surface. We can support michael@0: // that by clipping out drawing to that area. michael@0: Point offset; michael@0: if (pattern->IsAzure()) { michael@0: // This is an Azure pattern. i.e. this was the result of a PopGroup and michael@0: // then the extend mode was changed to EXTEND_NONE. michael@0: // XXX - We may need some additional magic here in theory to support michael@0: // device offsets in these patterns, but no problems have been observed michael@0: // yet because of this. And it would complicate things a little further. michael@0: offset = Point(0.f, 0.f); michael@0: } else if (pattern->GetType() == gfxPattern::PATTERN_SURFACE) { michael@0: nsRefPtr asurf = pattern->GetSurface(); michael@0: gfxPoint deviceOffset = asurf->GetDeviceOffset(); michael@0: offset = Point(-deviceOffset.x, -deviceOffset.y); michael@0: michael@0: // this lets GetAzureSurface work michael@0: pattern->GetPattern(mDT); michael@0: } michael@0: michael@0: if (pattern->IsAzure() || pattern->GetType() == gfxPattern::PATTERN_SURFACE) { michael@0: RefPtr mask = pattern->GetAzureSurface(); michael@0: Matrix mat = ToMatrix(pattern->GetInverseMatrix()); michael@0: Matrix old = mTransform; michael@0: // add in the inverse of the pattern transform so that when we michael@0: // MaskSurface we are transformed to the place matching the pattern transform michael@0: mat = mat * mTransform; michael@0: michael@0: ChangeTransform(mat); michael@0: mDT->MaskSurface(GeneralPattern(this), mask, offset, DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode)); michael@0: ChangeTransform(old); michael@0: return; michael@0: } michael@0: } michael@0: mDT->Mask(GeneralPattern(this), *pattern->GetPattern(mDT), DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::Mask(gfxASurface *surface, const gfxPoint& offset) michael@0: { michael@0: PROFILER_LABEL("gfxContext", "Mask"); michael@0: if (mCairo) { michael@0: cairo_mask_surface(mCairo, surface->CairoSurface(), offset.x, offset.y); michael@0: } else { michael@0: // Lifetime needs to be limited here as we may simply wrap surface's data. michael@0: RefPtr sourceSurf = michael@0: gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface); michael@0: michael@0: if (!sourceSurf) { michael@0: return; michael@0: } michael@0: michael@0: gfxPoint pt = surface->GetDeviceOffset(); michael@0: michael@0: Mask(sourceSurf, Point(offset.x - pt.x, offset.y - pt.y)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::Mask(SourceSurface *surface, const Point& offset) michael@0: { michael@0: MOZ_ASSERT(mDT); michael@0: michael@0: michael@0: // We clip here to bind to the mask surface bounds, see above. michael@0: mDT->MaskSurface(GeneralPattern(this), michael@0: surface, michael@0: offset, michael@0: DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode)); michael@0: } michael@0: michael@0: void michael@0: gfxContext::Paint(gfxFloat alpha) michael@0: { michael@0: PROFILER_LABEL("gfxContext", "Paint"); michael@0: if (mCairo) { michael@0: cairo_paint_with_alpha(mCairo, alpha); michael@0: } else { michael@0: AzureState &state = CurrentState(); michael@0: michael@0: if (state.sourceSurface && !state.sourceSurfCairo && michael@0: !state.patternTransformChanged && !state.opIsClear) michael@0: { michael@0: // This is the case where a PopGroupToSource has been done and this michael@0: // paint is executed without changing the transform or the source. michael@0: Matrix oldMat = mDT->GetTransform(); michael@0: michael@0: IntSize surfSize = state.sourceSurface->GetSize(); michael@0: michael@0: Matrix mat; michael@0: mat.Translate(-state.deviceOffset.x, -state.deviceOffset.y); michael@0: mDT->SetTransform(mat); michael@0: michael@0: mDT->DrawSurface(state.sourceSurface, michael@0: Rect(state.sourceSurfaceDeviceOffset, Size(surfSize.width, surfSize.height)), michael@0: Rect(Point(), Size(surfSize.width, surfSize.height)), michael@0: DrawSurfaceOptions(), DrawOptions(alpha, GetOp())); michael@0: mDT->SetTransform(oldMat); michael@0: return; michael@0: } michael@0: michael@0: Matrix mat = mDT->GetTransform(); michael@0: mat.Invert(); michael@0: Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize()))); michael@0: michael@0: if (state.opIsClear) { michael@0: mDT->ClearRect(paintRect); michael@0: } else { michael@0: mDT->FillRect(paintRect, GeneralPattern(this), michael@0: DrawOptions(Float(alpha), GetOp())); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // groups michael@0: michael@0: void michael@0: gfxContext::PushGroup(gfxContentType content) michael@0: { michael@0: if (mCairo) { michael@0: cairo_push_group_with_content(mCairo, (cairo_content_t)(int) content); michael@0: } else { michael@0: PushNewDT(content); michael@0: michael@0: PushClipsToDT(mDT); michael@0: mDT->SetTransform(GetDTTransform()); michael@0: } michael@0: } michael@0: michael@0: static gfxRect michael@0: GetRoundOutDeviceClipExtents(gfxContext* aCtx) michael@0: { michael@0: gfxContextMatrixAutoSaveRestore save(aCtx); michael@0: aCtx->IdentityMatrix(); michael@0: gfxRect r = aCtx->GetClipExtents(); michael@0: r.RoundOut(); michael@0: return r; michael@0: } michael@0: michael@0: /** michael@0: * Copy the contents of aSrc to aDest, translated by aTranslation. michael@0: */ michael@0: static void michael@0: CopySurface(gfxASurface* aSrc, gfxASurface* aDest, const gfxPoint& aTranslation) michael@0: { michael@0: cairo_t *cr = cairo_create(aDest->CairoSurface()); michael@0: cairo_set_source_surface(cr, aSrc->CairoSurface(), aTranslation.x, aTranslation.y); michael@0: cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); michael@0: cairo_paint(cr); michael@0: cairo_destroy(cr); michael@0: } michael@0: michael@0: void michael@0: gfxContext::PushGroupAndCopyBackground(gfxContentType content) michael@0: { michael@0: if (mCairo) { michael@0: if (content == gfxContentType::COLOR_ALPHA && michael@0: !(GetFlags() & FLAG_DISABLE_COPY_BACKGROUND)) { michael@0: nsRefPtr s = CurrentSurface(); michael@0: if ((s->GetAllowUseAsSource() || s->GetType() == gfxSurfaceType::Tee) && michael@0: (s->GetContentType() == gfxContentType::COLOR || michael@0: s->GetOpaqueRect().Contains(GetRoundOutDeviceClipExtents(this)))) { michael@0: cairo_push_group_with_content(mCairo, CAIRO_CONTENT_COLOR); michael@0: nsRefPtr d = CurrentSurface(); michael@0: michael@0: if (d->GetType() == gfxSurfaceType::Tee) { michael@0: NS_ASSERTION(s->GetType() == gfxSurfaceType::Tee, "Mismatched types"); michael@0: nsAutoTArray,2> ss; michael@0: nsAutoTArray,2> ds; michael@0: static_cast(s.get())->GetSurfaces(&ss); michael@0: static_cast(d.get())->GetSurfaces(&ds); michael@0: NS_ASSERTION(ss.Length() == ds.Length(), "Mismatched lengths"); michael@0: gfxPoint translation = d->GetDeviceOffset() - s->GetDeviceOffset(); michael@0: for (uint32_t i = 0; i < ss.Length(); ++i) { michael@0: CopySurface(ss[i], ds[i], translation); michael@0: } michael@0: } else { michael@0: CopySurface(s, d, gfxPoint(0, 0)); michael@0: } michael@0: d->SetOpaqueRect(s->GetOpaqueRect()); michael@0: return; michael@0: } michael@0: } michael@0: } else { michael@0: IntRect clipExtents; michael@0: if (mDT->GetFormat() != SurfaceFormat::B8G8R8X8) { michael@0: gfxRect clipRect = GetRoundOutDeviceClipExtents(this); michael@0: clipExtents = IntRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height); michael@0: } michael@0: if ((mDT->GetFormat() == SurfaceFormat::B8G8R8X8 || michael@0: mDT->GetOpaqueRect().Contains(clipExtents)) && michael@0: !mDT->GetUserData(&sDontUseAsSourceKey)) { michael@0: DrawTarget *oldDT = mDT; michael@0: RefPtr source = mDT->Snapshot(); michael@0: Point oldDeviceOffset = CurrentState().deviceOffset; michael@0: michael@0: PushNewDT(gfxContentType::COLOR); michael@0: michael@0: Point offset = CurrentState().deviceOffset - oldDeviceOffset; michael@0: Rect surfRect(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height)); michael@0: Rect sourceRect = surfRect; michael@0: sourceRect.x += offset.x; michael@0: sourceRect.y += offset.y; michael@0: michael@0: mDT->SetTransform(Matrix()); michael@0: mDT->DrawSurface(source, surfRect, sourceRect); michael@0: mDT->SetOpaqueRect(oldDT->GetOpaqueRect()); michael@0: michael@0: PushClipsToDT(mDT); michael@0: mDT->SetTransform(GetDTTransform()); michael@0: return; michael@0: } michael@0: } michael@0: PushGroup(content); michael@0: } michael@0: michael@0: already_AddRefed michael@0: gfxContext::PopGroup() michael@0: { michael@0: if (mCairo) { michael@0: cairo_pattern_t *pat = cairo_pop_group(mCairo); michael@0: nsRefPtr wrapper = new gfxPattern(pat); michael@0: cairo_pattern_destroy(pat); michael@0: return wrapper.forget(); michael@0: } else { michael@0: RefPtr src = mDT->Snapshot(); michael@0: Point deviceOffset = CurrentState().deviceOffset; michael@0: michael@0: Restore(); michael@0: michael@0: Matrix mat = mTransform; michael@0: mat.Invert(); michael@0: michael@0: Matrix deviceOffsetTranslation; michael@0: deviceOffsetTranslation.Translate(deviceOffset.x, deviceOffset.y); michael@0: michael@0: nsRefPtr pat = new gfxPattern(src, deviceOffsetTranslation * mat); michael@0: michael@0: return pat.forget(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::PopGroupToSource() michael@0: { michael@0: if (mCairo) { michael@0: cairo_pop_group_to_source(mCairo); michael@0: } else { michael@0: RefPtr src = mDT->Snapshot(); michael@0: Point deviceOffset = CurrentState().deviceOffset; michael@0: Restore(); michael@0: CurrentState().sourceSurfCairo = nullptr; michael@0: CurrentState().sourceSurface = src; michael@0: CurrentState().sourceSurfaceDeviceOffset = deviceOffset; michael@0: CurrentState().pattern = nullptr; michael@0: CurrentState().patternTransformChanged = false; michael@0: michael@0: Matrix mat = mTransform; michael@0: mat.Invert(); michael@0: michael@0: Matrix deviceOffsetTranslation; michael@0: deviceOffsetTranslation.Translate(deviceOffset.x, deviceOffset.y); michael@0: CurrentState().surfTransform = deviceOffsetTranslation * mat; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxContext::PointInFill(const gfxPoint& pt) michael@0: { michael@0: if (mCairo) { michael@0: return cairo_in_fill(mCairo, pt.x, pt.y); michael@0: } else { michael@0: EnsurePath(); michael@0: return mPath->ContainsPoint(ToPoint(pt), Matrix()); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxContext::PointInStroke(const gfxPoint& pt) michael@0: { michael@0: if (mCairo) { michael@0: return cairo_in_stroke(mCairo, pt.x, pt.y); michael@0: } else { michael@0: EnsurePath(); michael@0: return mPath->StrokeContainsPoint(CurrentState().strokeOptions, michael@0: ToPoint(pt), michael@0: Matrix()); michael@0: } michael@0: } michael@0: michael@0: gfxRect michael@0: gfxContext::GetUserPathExtent() michael@0: { michael@0: if (mCairo) { michael@0: double xmin, ymin, xmax, ymax; michael@0: cairo_path_extents(mCairo, &xmin, &ymin, &xmax, &ymax); michael@0: return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin); michael@0: } else { michael@0: EnsurePath(); michael@0: return ThebesRect(mPath->GetBounds()); michael@0: } michael@0: } michael@0: michael@0: gfxRect michael@0: gfxContext::GetUserFillExtent() michael@0: { michael@0: if (mCairo) { michael@0: double xmin, ymin, xmax, ymax; michael@0: cairo_fill_extents(mCairo, &xmin, &ymin, &xmax, &ymax); michael@0: return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin); michael@0: } else { michael@0: EnsurePath(); michael@0: return ThebesRect(mPath->GetBounds()); michael@0: } michael@0: } michael@0: michael@0: gfxRect michael@0: gfxContext::GetUserStrokeExtent() michael@0: { michael@0: if (mCairo) { michael@0: double xmin, ymin, xmax, ymax; michael@0: cairo_stroke_extents(mCairo, &xmin, &ymin, &xmax, &ymax); michael@0: return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin); michael@0: } else { michael@0: EnsurePath(); michael@0: return ThebesRect(mPath->GetStrokedBounds(CurrentState().strokeOptions, mTransform)); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxContext::HasError() michael@0: { michael@0: if (mCairo) { michael@0: return cairo_status(mCairo) != CAIRO_STATUS_SUCCESS; michael@0: } else { michael@0: // As far as this is concerned, an Azure context is never in error. michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::RoundedRectangle(const gfxRect& rect, michael@0: const gfxCornerSizes& corners, michael@0: bool draw_clockwise) michael@0: { michael@0: // michael@0: // For CW drawing, this looks like: michael@0: // michael@0: // ...******0** 1 C michael@0: // **** michael@0: // *** 2 michael@0: // ** michael@0: // * michael@0: // * michael@0: // 3 michael@0: // * michael@0: // * michael@0: // michael@0: // Where 0, 1, 2, 3 are the control points of the Bezier curve for michael@0: // the corner, and C is the actual corner point. michael@0: // michael@0: // At the start of the loop, the current point is assumed to be michael@0: // the point adjacent to the top left corner on the top michael@0: // horizontal. Note that corner indices start at the top left and michael@0: // continue clockwise, whereas in our loop i = 0 refers to the top michael@0: // right corner. michael@0: // michael@0: // When going CCW, the control points are swapped, and the first michael@0: // corner that's drawn is the top left (along with the top segment). michael@0: // michael@0: // There is considerable latitude in how one chooses the four michael@0: // control points for a Bezier curve approximation to an ellipse. michael@0: // For the overall path to be continuous and show no corner at the michael@0: // endpoints of the arc, points 0 and 3 must be at the ends of the michael@0: // straight segments of the rectangle; points 0, 1, and C must be michael@0: // collinear; and points 3, 2, and C must also be collinear. This michael@0: // leaves only two free parameters: the ratio of the line segments michael@0: // 01 and 0C, and the ratio of the line segments 32 and 3C. See michael@0: // the following papers for extensive discussion of how to choose michael@0: // these ratios: michael@0: // michael@0: // Dokken, Tor, et al. "Good approximation of circles by michael@0: // curvature-continuous Bezier curves." Computer-Aided michael@0: // Geometric Design 7(1990) 33--41. michael@0: // Goldapp, Michael. "Approximation of circular arcs by cubic michael@0: // polynomials." Computer-Aided Geometric Design 8(1991) 227--238. michael@0: // Maisonobe, Luc. "Drawing an elliptical arc using polylines, michael@0: // quadratic, or cubic Bezier curves." michael@0: // http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf michael@0: // michael@0: // We follow the approach in section 2 of Goldapp (least-error, michael@0: // Hermite-type approximation) and make both ratios equal to michael@0: // michael@0: // 2 2 + n - sqrt(2n + 28) michael@0: // alpha = - * --------------------- michael@0: // 3 n - 4 michael@0: // michael@0: // where n = 3( cbrt(sqrt(2)+1) - cbrt(sqrt(2)-1) ). michael@0: // michael@0: // This is the result of Goldapp's equation (10b) when the angle michael@0: // swept out by the arc is pi/2, and the parameter "a-bar" is the michael@0: // expression given immediately below equation (21). michael@0: // michael@0: // Using this value, the maximum radial error for a circle, as a michael@0: // fraction of the radius, is on the order of 0.2 x 10^-3. michael@0: // Neither Dokken nor Goldapp discusses error for a general michael@0: // ellipse; Maisonobe does, but his choice of control points michael@0: // follows different constraints, and Goldapp's expression for michael@0: // 'alpha' gives much smaller radial error, even for very flat michael@0: // ellipses, than Maisonobe's equivalent. michael@0: // michael@0: // For the various corners and for each axis, the sign of this michael@0: // constant changes, or it might be 0 -- it's multiplied by the michael@0: // appropriate multiplier from the list before using. michael@0: michael@0: if (mCairo) { michael@0: const gfxFloat alpha = 0.55191497064665766025; michael@0: michael@0: typedef struct { gfxFloat a, b; } twoFloats; michael@0: michael@0: twoFloats cwCornerMults[4] = { { -1, 0 }, michael@0: { 0, -1 }, michael@0: { +1, 0 }, michael@0: { 0, +1 } }; michael@0: twoFloats ccwCornerMults[4] = { { +1, 0 }, michael@0: { 0, -1 }, michael@0: { -1, 0 }, michael@0: { 0, +1 } }; michael@0: michael@0: twoFloats *cornerMults = draw_clockwise ? cwCornerMults : ccwCornerMults; michael@0: michael@0: gfxPoint pc, p0, p1, p2, p3; michael@0: michael@0: if (draw_clockwise) michael@0: cairo_move_to(mCairo, rect.X() + corners[NS_CORNER_TOP_LEFT].width, rect.Y()); michael@0: else michael@0: cairo_move_to(mCairo, rect.X() + rect.Width() - corners[NS_CORNER_TOP_RIGHT].width, rect.Y()); michael@0: michael@0: NS_FOR_CSS_CORNERS(i) { michael@0: // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw) michael@0: mozilla::css::Corner c = mozilla::css::Corner(draw_clockwise ? ((i+1) % 4) : ((4-i) % 4)); michael@0: michael@0: // i+2 and i+3 respectively. These are used to index into the corner michael@0: // multiplier table, and were deduced by calculating out the long form michael@0: // of each corner and finding a pattern in the signs and values. michael@0: int i2 = (i+2) % 4; michael@0: int i3 = (i+3) % 4; michael@0: michael@0: pc = rect.AtCorner(c); michael@0: michael@0: if (corners[c].width > 0.0 && corners[c].height > 0.0) { michael@0: p0.x = pc.x + cornerMults[i].a * corners[c].width; michael@0: p0.y = pc.y + cornerMults[i].b * corners[c].height; michael@0: michael@0: p3.x = pc.x + cornerMults[i3].a * corners[c].width; michael@0: p3.y = pc.y + cornerMults[i3].b * corners[c].height; michael@0: michael@0: p1.x = p0.x + alpha * cornerMults[i2].a * corners[c].width; michael@0: p1.y = p0.y + alpha * cornerMults[i2].b * corners[c].height; michael@0: michael@0: p2.x = p3.x - alpha * cornerMults[i3].a * corners[c].width; michael@0: p2.y = p3.y - alpha * cornerMults[i3].b * corners[c].height; michael@0: michael@0: cairo_line_to (mCairo, p0.x, p0.y); michael@0: cairo_curve_to (mCairo, michael@0: p1.x, p1.y, michael@0: p2.x, p2.y, michael@0: p3.x, p3.y); michael@0: } else { michael@0: cairo_line_to (mCairo, pc.x, pc.y); michael@0: } michael@0: } michael@0: michael@0: cairo_close_path (mCairo); michael@0: } else { michael@0: EnsurePathBuilder(); michael@0: Size radii[] = { ToSize(corners[NS_CORNER_TOP_LEFT]), michael@0: ToSize(corners[NS_CORNER_TOP_RIGHT]), michael@0: ToSize(corners[NS_CORNER_BOTTOM_RIGHT]), michael@0: ToSize(corners[NS_CORNER_BOTTOM_LEFT]) }; michael@0: AppendRoundedRectToPath(mPathBuilder, ToRect(rect), radii, draw_clockwise); michael@0: } michael@0: } michael@0: michael@0: #ifdef MOZ_DUMP_PAINTING michael@0: void michael@0: gfxContext::WriteAsPNG(const char* aFile) michael@0: { michael@0: nsRefPtr surf = CurrentSurface(); michael@0: if (surf) { michael@0: surf->WriteAsPNG(aFile); michael@0: } else { michael@0: NS_WARNING("No surface found!"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::DumpAsDataURL() michael@0: { michael@0: nsRefPtr surf = CurrentSurface(); michael@0: if (surf) { michael@0: surf->DumpAsDataURL(); michael@0: } else { michael@0: NS_WARNING("No surface found!"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::CopyAsDataURL() michael@0: { michael@0: nsRefPtr surf = CurrentSurface(); michael@0: if (surf) { michael@0: surf->CopyAsDataURL(); michael@0: } else { michael@0: NS_WARNING("No surface found!"); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: gfxContext::EnsurePath() michael@0: { michael@0: if (mPathBuilder) { michael@0: mPath = mPathBuilder->Finish(); michael@0: mPathBuilder = nullptr; michael@0: } michael@0: michael@0: if (mPath) { michael@0: if (mTransformChanged) { michael@0: Matrix mat = mTransform; michael@0: mat.Invert(); michael@0: mat = mPathTransform * mat; michael@0: mPathBuilder = mPath->TransformedCopyToBuilder(mat, CurrentState().fillRule); michael@0: mPath = mPathBuilder->Finish(); michael@0: mPathBuilder = nullptr; michael@0: michael@0: mTransformChanged = false; michael@0: } michael@0: michael@0: if (CurrentState().fillRule == mPath->GetFillRule()) { michael@0: return; michael@0: } michael@0: michael@0: mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule); michael@0: michael@0: mPath = mPathBuilder->Finish(); michael@0: mPathBuilder = nullptr; michael@0: return; michael@0: } michael@0: michael@0: EnsurePathBuilder(); michael@0: mPath = mPathBuilder->Finish(); michael@0: mPathBuilder = nullptr; michael@0: } michael@0: michael@0: void michael@0: gfxContext::EnsurePathBuilder() michael@0: { michael@0: if (mPathBuilder && !mTransformChanged) { michael@0: return; michael@0: } michael@0: michael@0: if (mPath) { michael@0: if (!mTransformChanged) { michael@0: mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule); michael@0: mPath = nullptr; michael@0: } else { michael@0: Matrix invTransform = mTransform; michael@0: invTransform.Invert(); michael@0: Matrix toNewUS = mPathTransform * invTransform; michael@0: mPathBuilder = mPath->TransformedCopyToBuilder(toNewUS, CurrentState().fillRule); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: DebugOnly oldPath = mPathBuilder.get(); michael@0: michael@0: if (!mPathBuilder) { michael@0: mPathBuilder = mDT->CreatePathBuilder(CurrentState().fillRule); michael@0: michael@0: if (mPathIsRect) { michael@0: mPathBuilder->MoveTo(mRect.TopLeft()); michael@0: mPathBuilder->LineTo(mRect.TopRight()); michael@0: mPathBuilder->LineTo(mRect.BottomRight()); michael@0: mPathBuilder->LineTo(mRect.BottomLeft()); michael@0: mPathBuilder->Close(); michael@0: } michael@0: } michael@0: michael@0: if (mTransformChanged) { michael@0: // This could be an else if since this should never happen when michael@0: // mPathBuilder is nullptr and mPath is nullptr. But this way we can michael@0: // assert if all the state is as expected. michael@0: MOZ_ASSERT(oldPath); michael@0: MOZ_ASSERT(!mPathIsRect); michael@0: michael@0: Matrix invTransform = mTransform; michael@0: invTransform.Invert(); michael@0: Matrix toNewUS = mPathTransform * invTransform; michael@0: michael@0: RefPtr path = mPathBuilder->Finish(); michael@0: mPathBuilder = path->TransformedCopyToBuilder(toNewUS, CurrentState().fillRule); michael@0: } michael@0: michael@0: mPathIsRect = false; michael@0: } michael@0: michael@0: void michael@0: gfxContext::FillAzure(Float aOpacity) michael@0: { michael@0: AzureState &state = CurrentState(); michael@0: michael@0: CompositionOp op = GetOp(); michael@0: michael@0: if (mPathIsRect) { michael@0: MOZ_ASSERT(!mTransformChanged); michael@0: michael@0: if (state.opIsClear) { michael@0: mDT->ClearRect(mRect); michael@0: } else if (op == CompositionOp::OP_SOURCE) { michael@0: // Emulate cairo operator source which is bound by mask! michael@0: mDT->ClearRect(mRect); michael@0: mDT->FillRect(mRect, GeneralPattern(this), DrawOptions(aOpacity)); michael@0: } else { michael@0: mDT->FillRect(mRect, GeneralPattern(this), DrawOptions(aOpacity, op, state.aaMode)); michael@0: } michael@0: } else { michael@0: EnsurePath(); michael@0: michael@0: NS_ASSERTION(!state.opIsClear, "We shouldn't be clearing complex paths!"); michael@0: michael@0: mDT->Fill(mPath, GeneralPattern(this), DrawOptions(aOpacity, op, state.aaMode)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxContext::PushClipsToDT(DrawTarget *aDT) michael@0: { michael@0: // Tricky, we have to restore all clips -since the last time- the clip michael@0: // was reset. If we didn't reset the clip, just popping the clips we michael@0: // added was fine. michael@0: unsigned int lastReset = 0; michael@0: for (int i = mStateStack.Length() - 2; i > 0; i--) { michael@0: if (mStateStack[i].clipWasReset) { michael@0: lastReset = i; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Don't need to save the old transform, we'll be setting a new one soon! michael@0: michael@0: // Push all clips from the last state on the stack where the clip was michael@0: // reset to the clip before ours. michael@0: for (unsigned int i = lastReset; i < mStateStack.Length() - 1; i++) { michael@0: for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { michael@0: aDT->SetTransform(mStateStack[i].pushedClips[c].transform * GetDeviceTransform()); michael@0: if (mStateStack[i].pushedClips[c].path) { michael@0: aDT->PushClip(mStateStack[i].pushedClips[c].path); michael@0: } else { michael@0: aDT->PushClipRect(mStateStack[i].pushedClips[c].rect); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: CompositionOp michael@0: gfxContext::GetOp() michael@0: { michael@0: if (CurrentState().op != CompositionOp::OP_SOURCE) { michael@0: return CurrentState().op; michael@0: } michael@0: michael@0: AzureState &state = CurrentState(); michael@0: if (state.pattern) { michael@0: if (state.pattern->IsOpaque()) { michael@0: return CompositionOp::OP_OVER; michael@0: } else { michael@0: return CompositionOp::OP_SOURCE; michael@0: } michael@0: } else if (state.sourceSurface) { michael@0: if (state.sourceSurface->GetFormat() == SurfaceFormat::B8G8R8X8) { michael@0: return CompositionOp::OP_OVER; michael@0: } else { michael@0: return CompositionOp::OP_SOURCE; michael@0: } michael@0: } else { michael@0: if (state.color.a > 0.999) { michael@0: return CompositionOp::OP_OVER; michael@0: } else { michael@0: return CompositionOp::OP_SOURCE; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* SVG font code can change the transform after having set the pattern on the michael@0: * context. When the pattern is set it is in user space, if the transform is michael@0: * changed after doing so the pattern needs to be converted back into userspace. michael@0: * We just store the old pattern transform here so that we only do the work michael@0: * needed here if the pattern is actually used. michael@0: * We need to avoid doing this when this ChangeTransform comes from a restore, michael@0: * since the current pattern and the current transform are both part of the michael@0: * state we know the new CurrentState()'s values are valid. But if we assume michael@0: * a change they might become invalid since patternTransformChanged is part of michael@0: * the state and might be false for the restored AzureState. michael@0: */ michael@0: void michael@0: gfxContext::ChangeTransform(const Matrix &aNewMatrix, bool aUpdatePatternTransform) michael@0: { michael@0: AzureState &state = CurrentState(); michael@0: michael@0: if (aUpdatePatternTransform && (state.pattern || state.sourceSurface) michael@0: && !state.patternTransformChanged) { michael@0: state.patternTransform = GetDTTransform(); michael@0: state.patternTransformChanged = true; michael@0: } michael@0: michael@0: if (mPathIsRect) { michael@0: Matrix invMatrix = aNewMatrix; michael@0: michael@0: invMatrix.Invert(); michael@0: michael@0: Matrix toNewUS = mTransform * invMatrix; michael@0: michael@0: if (toNewUS.IsRectilinear()) { michael@0: mRect = toNewUS.TransformBounds(mRect); michael@0: mRect.NudgeToIntegers(); michael@0: } else { michael@0: mPathBuilder = mDT->CreatePathBuilder(CurrentState().fillRule); michael@0: michael@0: mPathBuilder->MoveTo(toNewUS * mRect.TopLeft()); michael@0: mPathBuilder->LineTo(toNewUS * mRect.TopRight()); michael@0: mPathBuilder->LineTo(toNewUS * mRect.BottomRight()); michael@0: mPathBuilder->LineTo(toNewUS * mRect.BottomLeft()); michael@0: mPathBuilder->Close(); michael@0: michael@0: mPathIsRect = false; michael@0: } michael@0: michael@0: // No need to consider the transform changed now! michael@0: mTransformChanged = false; michael@0: } else if ((mPath || mPathBuilder) && !mTransformChanged) { michael@0: mTransformChanged = true; michael@0: mPathTransform = mTransform; michael@0: } michael@0: michael@0: mTransform = aNewMatrix; michael@0: michael@0: mDT->SetTransform(GetDTTransform()); michael@0: } michael@0: michael@0: Rect michael@0: gfxContext::GetAzureDeviceSpaceClipBounds() michael@0: { michael@0: unsigned int lastReset = 0; michael@0: for (int i = mStateStack.Length() - 1; i > 0; i--) { michael@0: if (mStateStack[i].clipWasReset) { michael@0: lastReset = i; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: Rect rect(CurrentState().deviceOffset.x, CurrentState().deviceOffset.y, michael@0: Float(mDT->GetSize().width), Float(mDT->GetSize().height)); michael@0: for (unsigned int i = lastReset; i < mStateStack.Length(); i++) { michael@0: for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { michael@0: AzureState::PushedClip &clip = mStateStack[i].pushedClips[c]; michael@0: if (clip.path) { michael@0: Rect bounds = clip.path->GetBounds(clip.transform); michael@0: rect.IntersectRect(rect, bounds); michael@0: } else { michael@0: rect.IntersectRect(rect, clip.transform.TransformBounds(clip.rect)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return rect; michael@0: } michael@0: michael@0: Point michael@0: gfxContext::GetDeviceOffset() const michael@0: { michael@0: return CurrentState().deviceOffset; michael@0: } michael@0: michael@0: Matrix michael@0: gfxContext::GetDeviceTransform() const michael@0: { michael@0: Matrix mat; michael@0: mat.Translate(-CurrentState().deviceOffset.x, -CurrentState().deviceOffset.y); michael@0: return mat; michael@0: } michael@0: michael@0: Matrix michael@0: gfxContext::GetDTTransform() const michael@0: { michael@0: Matrix mat = mTransform; michael@0: mat._31 -= CurrentState().deviceOffset.x; michael@0: mat._32 -= CurrentState().deviceOffset.y; michael@0: return mat; michael@0: } michael@0: michael@0: void michael@0: gfxContext::PushNewDT(gfxContentType content) michael@0: { michael@0: Rect clipBounds = GetAzureDeviceSpaceClipBounds(); michael@0: clipBounds.RoundOut(); michael@0: michael@0: clipBounds.width = std::max(1.0f, clipBounds.width); michael@0: clipBounds.height = std::max(1.0f, clipBounds.height); michael@0: michael@0: SurfaceFormat format = gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content); michael@0: michael@0: RefPtr newDT = michael@0: mDT->CreateSimilarDrawTarget(IntSize(int32_t(clipBounds.width), int32_t(clipBounds.height)), michael@0: format); michael@0: michael@0: if (!newDT) { michael@0: NS_WARNING("Failed to create DrawTarget of sufficient size."); michael@0: newDT = mDT->CreateSimilarDrawTarget(IntSize(64, 64), format); michael@0: michael@0: if (!newDT) { michael@0: // If even this fails.. we're most likely just out of memory! michael@0: NS_ABORT_OOM(BytesPerPixel(format) * 64 * 64); michael@0: } michael@0: } michael@0: michael@0: Save(); michael@0: michael@0: CurrentState().drawTarget = newDT; michael@0: CurrentState().deviceOffset = clipBounds.TopLeft(); michael@0: michael@0: mDT = newDT; michael@0: } michael@0: michael@0: /** michael@0: * Work out whether cairo will snap inter-glyph spacing to pixels. michael@0: * michael@0: * Layout does not align text to pixel boundaries, so, with font drawing michael@0: * backends that snap glyph positions to pixels, it is important that michael@0: * inter-glyph spacing within words is always an integer number of pixels. michael@0: * This ensures that the drawing backend snaps all of the word's glyphs in the michael@0: * same direction and so inter-glyph spacing remains the same. michael@0: */ michael@0: void michael@0: gfxContext::GetRoundOffsetsToPixels(bool *aRoundX, bool *aRoundY) michael@0: { michael@0: *aRoundX = false; michael@0: // Could do something fancy here for ScaleFactors of michael@0: // AxisAlignedTransforms, but we leave things simple. michael@0: // Not much point rounding if a matrix will mess things up anyway. michael@0: // Also return false for non-cairo contexts. michael@0: if (CurrentMatrix().HasNonTranslation() || mDT) { michael@0: *aRoundY = false; michael@0: return; michael@0: } michael@0: michael@0: // All raster backends snap glyphs to pixels vertically. michael@0: // Print backends set CAIRO_HINT_METRICS_OFF. michael@0: *aRoundY = true; michael@0: michael@0: cairo_t *cr = GetCairo(); michael@0: cairo_scaled_font_t *scaled_font = cairo_get_scaled_font(cr); michael@0: // Sometimes hint metrics gets set for us, most notably for printing. michael@0: cairo_font_options_t *font_options = cairo_font_options_create(); michael@0: cairo_scaled_font_get_font_options(scaled_font, font_options); michael@0: cairo_hint_metrics_t hint_metrics = michael@0: cairo_font_options_get_hint_metrics(font_options); michael@0: cairo_font_options_destroy(font_options); michael@0: michael@0: switch (hint_metrics) { michael@0: case CAIRO_HINT_METRICS_OFF: michael@0: *aRoundY = false; michael@0: return; michael@0: case CAIRO_HINT_METRICS_DEFAULT: michael@0: // Here we mimic what cairo surface/font backends do. Printing michael@0: // surfaces have already been handled by hint_metrics. The michael@0: // fallback show_glyphs implementation composites pixel-aligned michael@0: // glyph surfaces, so we just pick surface/font combinations that michael@0: // override this. michael@0: switch (cairo_scaled_font_get_type(scaled_font)) { michael@0: #if CAIRO_HAS_DWRITE_FONT // dwrite backend is not in std cairo releases yet michael@0: case CAIRO_FONT_TYPE_DWRITE: michael@0: // show_glyphs is implemented on the font and so is used for michael@0: // all surface types; however, it may pixel-snap depending on michael@0: // the dwrite rendering mode michael@0: if (!cairo_dwrite_scaled_font_get_force_GDI_classic(scaled_font) && michael@0: gfxWindowsPlatform::GetPlatform()->DWriteMeasuringMode() == michael@0: DWRITE_MEASURING_MODE_NATURAL) { michael@0: return; michael@0: } michael@0: #endif michael@0: case CAIRO_FONT_TYPE_QUARTZ: michael@0: // Quartz surfaces implement show_glyphs for Quartz fonts michael@0: if (cairo_surface_get_type(cairo_get_target(cr)) == michael@0: CAIRO_SURFACE_TYPE_QUARTZ) { michael@0: return; michael@0: } michael@0: default: michael@0: break; michael@0: } michael@0: // fall through: michael@0: case CAIRO_HINT_METRICS_ON: michael@0: break; michael@0: } michael@0: *aRoundX = true; michael@0: return; michael@0: }