diff options
Diffstat (limited to 'gfx/thebes/gfxContext.cpp')
-rw-r--r-- | gfx/thebes/gfxContext.cpp | 866 |
1 files changed, 866 insertions, 0 deletions
diff --git a/gfx/thebes/gfxContext.cpp b/gfx/thebes/gfxContext.cpp new file mode 100644 index 0000000000..deb782742c --- /dev/null +++ b/gfx/thebes/gfxContext.cpp @@ -0,0 +1,866 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <math.h> + +#include "mozilla/Alignment.h" + +#include "cairo.h" + +#include "gfxContext.h" + +#include "gfxMatrix.h" +#include "gfxUtils.h" +#include "gfxPattern.h" +#include "gfxPlatform.h" + +#include "gfx2DGlue.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/ProfilerLabels.h" +#include <algorithm> +#include "TextDrawTarget.h" + +#if XP_WIN +# include "gfxWindowsPlatform.h" +# include "mozilla/gfx/DeviceManagerDx.h" +#endif + +using namespace mozilla; +using namespace mozilla::gfx; + +#ifdef DEBUG +# define CURRENTSTATE_CHANGED() CurrentState().mContentChanged = true; +#else +# define CURRENTSTATE_CHANGED() +#endif + +PatternFromState::operator mozilla::gfx::Pattern&() { + gfxContext::AzureState& state = mContext->CurrentState(); + + if (state.pattern) { + return *state.pattern->GetPattern( + mContext->mDT, + state.patternTransformChanged ? &state.patternTransform : nullptr); + } + + mPattern = new (mColorPattern.addr()) ColorPattern(state.color); + return *mPattern; +} + +gfxContext::gfxContext(DrawTarget* aTarget, const Point& aDeviceOffset) + : mPathIsRect(false), mTransformChanged(false), mDT(aTarget) { + if (!aTarget) { + gfxCriticalError() << "Don't create a gfxContext without a DrawTarget"; + } + + mStateStack.SetLength(1); + CurrentState().drawTarget = mDT; + CurrentState().deviceOffset = aDeviceOffset; + mDT->SetTransform(GetDTTransform()); +} + +/* static */ +already_AddRefed<gfxContext> gfxContext::CreateOrNull( + DrawTarget* aTarget, const mozilla::gfx::Point& aDeviceOffset) { + if (!aTarget || !aTarget->IsValid()) { + gfxCriticalNote << "Invalid target in gfxContext::CreateOrNull " + << hexa(aTarget); + return nullptr; + } + + RefPtr<gfxContext> result = new gfxContext(aTarget, aDeviceOffset); + return result.forget(); +} + +/* static */ +already_AddRefed<gfxContext> gfxContext::CreatePreservingTransformOrNull( + DrawTarget* aTarget) { + if (!aTarget || !aTarget->IsValid()) { + gfxCriticalNote + << "Invalid target in gfxContext::CreatePreservingTransformOrNull " + << hexa(aTarget); + return nullptr; + } + + Matrix transform = aTarget->GetTransform(); + RefPtr<gfxContext> result = new gfxContext(aTarget); + result->SetMatrix(transform); + return result.forget(); +} + +gfxContext::~gfxContext() { + for (int i = mStateStack.Length() - 1; i >= 0; i--) { + for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { + mStateStack[i].drawTarget->PopClip(); + } + } +} + +mozilla::layout::TextDrawTarget* gfxContext::GetTextDrawer() { + if (mDT->GetBackendType() == BackendType::WEBRENDER_TEXT) { + return static_cast<mozilla::layout::TextDrawTarget*>(&*mDT); + } + return nullptr; +} + +void gfxContext::Save() { + CurrentState().transform = mTransform; + mStateStack.AppendElement(AzureState(CurrentState())); + CurrentState().pushedClips.Clear(); +#ifdef DEBUG + CurrentState().mContentChanged = false; +#endif +} + +void gfxContext::Restore() { +#ifdef DEBUG + // gfxContext::Restore is used to restore AzureState. We need to restore it + // only if it was altered. The following APIs do change the content of + // AzureState, a user should save the state before using them and restore it + // after finishing painting: + // 1. APIs to setup how to paint, such as SetColor()/SetAntialiasMode(). All + // gfxContext SetXXXX public functions belong to this category, except + // gfxContext::SetPath & gfxContext::SetMatrix. + // 2. Clip functions, such as Clip() or PopClip(). You may call PopClip() + // directly instead of using gfxContext::Save if the clip region is the + // only thing that you altered in the target context. + // 3. Function of setup transform matrix, such as Multiply() and + // SetMatrix(). Using gfxContextMatrixAutoSaveRestore is more recommended + // if transform data is the only thing that you are going to alter. + // + // You will hit the assertion message below if there is no above functions + // been used between a pair of gfxContext::Save and gfxContext::Restore. + // Considerate to remove that pair of Save/Restore if hitting that assertion. + // + // In the other hand, the following APIs do not alter the content of the + // current AzureState, therefore, there is no need to save & restore + // AzureState: + // 1. constant member functions of gfxContext. + // 2. Paint calls, such as Line()/Rectangle()/Fill(). Those APIs change the + // content of drawing buffer, which is not part of AzureState. + // 3. Path building APIs, such as SetPath()/MoveTo()/LineTo()/NewPath(). + // Surprisingly, path information is not stored in AzureState either. + // Save current AzureState before using these type of APIs does nothing but + // make performance worse. + NS_ASSERTION( + CurrentState().mContentChanged || CurrentState().pushedClips.Length() > 0, + "The context of the current AzureState is not altered after " + "Save() been called. you may consider to remove this pair of " + "gfxContext::Save/Restore."); +#endif + + for (unsigned int c = 0; c < CurrentState().pushedClips.Length(); c++) { + mDT->PopClip(); + } + + mStateStack.RemoveLastElement(); + + mDT = CurrentState().drawTarget; + + ChangeTransform(CurrentState().transform, false); +} + +// drawing +void gfxContext::NewPath() { + mPath = nullptr; + mPathBuilder = nullptr; + mPathIsRect = false; + mTransformChanged = false; +} + +already_AddRefed<Path> gfxContext::GetPath() { + EnsurePath(); + RefPtr<Path> path(mPath); + return path.forget(); +} + +void gfxContext::SetPath(Path* path) { + MOZ_ASSERT(path->GetBackendType() == mDT->GetBackendType() || + path->GetBackendType() == BackendType::RECORDING || + (mDT->GetBackendType() == BackendType::DIRECT2D1_1 && + path->GetBackendType() == BackendType::DIRECT2D)); + mPath = path; + mPathBuilder = nullptr; + mPathIsRect = false; + mTransformChanged = false; +} + +void gfxContext::Fill() { Fill(PatternFromState(this)); } + +void gfxContext::Fill(const Pattern& aPattern) { + AUTO_PROFILER_LABEL("gfxContext::Fill", GRAPHICS); + AzureState& state = CurrentState(); + + CompositionOp op = GetOp(); + + if (mPathIsRect) { + MOZ_ASSERT(!mTransformChanged); + + if (op == CompositionOp::OP_SOURCE) { + // Emulate cairo operator source which is bound by mask! + mDT->ClearRect(mRect); + mDT->FillRect(mRect, aPattern, DrawOptions(1.0f)); + } else { + mDT->FillRect(mRect, aPattern, DrawOptions(1.0f, op, state.aaMode)); + } + } else { + EnsurePath(); + mDT->Fill(mPath, aPattern, DrawOptions(1.0f, op, state.aaMode)); + } +} + +// XXX snapToPixels is only valid when snapping for filled +// rectangles and for even-width stroked rectangles. +// For odd-width stroked rectangles, we need to offset x/y by +// 0.5... +void gfxContext::Rectangle(const gfxRect& rect, bool snapToPixels) { + Rect rec = ToRect(rect); + + if (snapToPixels) { + gfxRect newRect(rect); + if (UserToDevicePixelSnapped(newRect, SnapOption::IgnoreScale)) { + gfxMatrix mat = ThebesMatrix(mTransform); + if (mat.Invert()) { + // We need the user space rect. + rec = ToRect(mat.TransformBounds(newRect)); + } else { + rec = Rect(); + } + } + } + + if (!mPathBuilder && !mPathIsRect) { + mPathIsRect = true; + mRect = rec; + return; + } + + EnsurePathBuilder(); + + mPathBuilder->MoveTo(rec.TopLeft()); + mPathBuilder->LineTo(rec.TopRight()); + mPathBuilder->LineTo(rec.BottomRight()); + mPathBuilder->LineTo(rec.BottomLeft()); + mPathBuilder->Close(); +} + +void gfxContext::SnappedClip(const gfxRect& rect) { + Rect rec = ToRect(rect); + + gfxRect newRect(rect); + if (UserToDevicePixelSnapped(newRect, SnapOption::IgnoreScale)) { + gfxMatrix mat = ThebesMatrix(mTransform); + if (mat.Invert()) { + // We need the user space rect. + rec = ToRect(mat.TransformBounds(newRect)); + } else { + rec = Rect(); + } + } + + Clip(rec); +} + +// transform stuff +void gfxContext::Multiply(const gfxMatrix& matrix) { + Multiply(ToMatrix(matrix)); +} + +// transform stuff +void gfxContext::Multiply(const Matrix& matrix) { + CURRENTSTATE_CHANGED() + ChangeTransform(matrix * mTransform); +} + +void gfxContext::SetMatrix(const gfx::Matrix& matrix) { + CURRENTSTATE_CHANGED() + ChangeTransform(matrix); +} + +void gfxContext::SetMatrixDouble(const gfxMatrix& matrix) { + SetMatrix(ToMatrix(matrix)); +} + +gfx::Matrix gfxContext::CurrentMatrix() const { return mTransform; } + +gfxMatrix gfxContext::CurrentMatrixDouble() const { + return ThebesMatrix(CurrentMatrix()); +} + +gfxPoint gfxContext::DeviceToUser(const gfxPoint& point) const { + return ThebesPoint(mTransform.Inverse().TransformPoint(ToPoint(point))); +} + +Size gfxContext::DeviceToUser(const Size& size) const { + return mTransform.Inverse().TransformSize(size); +} + +gfxRect gfxContext::DeviceToUser(const gfxRect& rect) const { + return ThebesRect(mTransform.Inverse().TransformBounds(ToRect(rect))); +} + +gfxPoint gfxContext::UserToDevice(const gfxPoint& point) const { + return ThebesPoint(mTransform.TransformPoint(ToPoint(point))); +} + +Size gfxContext::UserToDevice(const Size& size) const { + const Matrix& matrix = mTransform; + + Size newSize; + newSize.width = size.width * matrix._11 + size.height * matrix._12; + newSize.height = size.width * matrix._21 + size.height * matrix._22; + return newSize; +} + +gfxRect gfxContext::UserToDevice(const gfxRect& rect) const { + const Matrix& matrix = mTransform; + return ThebesRect(matrix.TransformBounds(ToRect(rect))); +} + +bool gfxContext::UserToDevicePixelSnapped(gfxRect& rect, + SnapOptions aOptions) const { + if (mDT->GetUserData(&sDisablePixelSnapping)) { + return false; + } + + // if we're not at 1.0 scale, don't snap, unless we're + // ignoring the scale. If we're not -just- a scale, + // never snap. + const gfxFloat epsilon = 0.0000001; +#define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon) + Matrix mat = mTransform; + if (!aOptions.contains(SnapOption::IgnoreScale) && + (!WITHIN_E(mat._11, 1.0) || !WITHIN_E(mat._22, 1.0) || + !WITHIN_E(mat._12, 0.0) || !WITHIN_E(mat._21, 0.0))) { + return false; + } +#undef WITHIN_E + + gfxPoint p1 = UserToDevice(rect.TopLeft()); + gfxPoint p2 = UserToDevice(rect.TopRight()); + gfxPoint p3 = UserToDevice(rect.BottomRight()); + + // Check that the rectangle is axis-aligned. For an axis-aligned rectangle, + // two opposite corners define the entire rectangle. So check if + // the axis-aligned rectangle with opposite corners p1 and p3 + // define an axis-aligned rectangle whose other corners are p2 and p4. + // We actually only need to check one of p2 and p4, since an affine + // transform maps parallelograms to parallelograms. + if (!(p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y))) { + return false; + } + + if (aOptions.contains(SnapOption::PrioritizeSize)) { + // Snap the dimensions of the rect, to minimize distortion; only after that + // will we snap its position. In particular, this guarantees that a square + // remains square after snapping, which may not be the case if each edge is + // independently snapped to device pixels. + + // Use the same rounding approach as gfx::BasePoint::Round. + rect.SizeTo(std::floor(rect.width + 0.5), std::floor(rect.height + 0.5)); + + // Find the top-left corner based on the original center and the snapped + // size, then snap this new corner to the grid. + gfxPoint center = (p1 + p3) / 2; + gfxPoint topLeft = center - gfxPoint(rect.width / 2.0, rect.height / 2.0); + topLeft.Round(); + rect.MoveTo(topLeft); + } else { + p1.Round(); + p3.Round(); + rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y))); + rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(), + std::max(p1.y, p3.y) - rect.Y())); + } + + return true; +} + +bool gfxContext::UserToDevicePixelSnapped(gfxPoint& pt, + bool ignoreScale) const { + if (mDT->GetUserData(&sDisablePixelSnapping)) { + return false; + } + + // if we're not at 1.0 scale, don't snap, unless we're + // ignoring the scale. If we're not -just- a scale, + // never snap. + const gfxFloat epsilon = 0.0000001; +#define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon) + Matrix mat = mTransform; + if (!ignoreScale && (!WITHIN_E(mat._11, 1.0) || !WITHIN_E(mat._22, 1.0) || + !WITHIN_E(mat._12, 0.0) || !WITHIN_E(mat._21, 0.0))) { + return false; + } +#undef WITHIN_E + + pt = UserToDevice(pt); + pt.Round(); + return true; +} + +void gfxContext::SetAntialiasMode(AntialiasMode mode) { + CURRENTSTATE_CHANGED() + CurrentState().aaMode = mode; +} + +AntialiasMode gfxContext::CurrentAntialiasMode() const { + return CurrentState().aaMode; +} + +void gfxContext::SetDash(const Float* dashes, int ndash, Float offset, + Float devPxScale) { + CURRENTSTATE_CHANGED() + AzureState& state = CurrentState(); + + state.dashPattern.SetLength(ndash); + for (int i = 0; i < ndash; i++) { + state.dashPattern[i] = dashes[i] * devPxScale; + } + state.strokeOptions.mDashLength = ndash; + state.strokeOptions.mDashOffset = offset * devPxScale; + state.strokeOptions.mDashPattern = + ndash ? state.dashPattern.Elements() : nullptr; +} + +bool gfxContext::CurrentDash(FallibleTArray<Float>& dashes, + Float* offset) const { + const AzureState& state = CurrentState(); + int count = state.strokeOptions.mDashLength; + + if (count <= 0 || !dashes.Assign(state.dashPattern, fallible)) { + return false; + } + + *offset = state.strokeOptions.mDashOffset; + + return true; +} + +void gfxContext::SetLineWidth(Float width) { + CurrentState().strokeOptions.mLineWidth = width; +} + +Float gfxContext::CurrentLineWidth() const { + return CurrentState().strokeOptions.mLineWidth; +} + +void gfxContext::SetOp(CompositionOp aOp) { + CURRENTSTATE_CHANGED() + CurrentState().op = aOp; +} + +CompositionOp gfxContext::CurrentOp() const { return CurrentState().op; } + +void gfxContext::SetLineCap(CapStyle cap) { + CURRENTSTATE_CHANGED() + CurrentState().strokeOptions.mLineCap = cap; +} + +CapStyle gfxContext::CurrentLineCap() const { + return CurrentState().strokeOptions.mLineCap; +} + +void gfxContext::SetLineJoin(JoinStyle join) { + CURRENTSTATE_CHANGED() + CurrentState().strokeOptions.mLineJoin = join; +} + +JoinStyle gfxContext::CurrentLineJoin() const { + return CurrentState().strokeOptions.mLineJoin; +} + +void gfxContext::SetMiterLimit(Float limit) { + CURRENTSTATE_CHANGED() + CurrentState().strokeOptions.mMiterLimit = limit; +} + +Float gfxContext::CurrentMiterLimit() const { + return CurrentState().strokeOptions.mMiterLimit; +} + +// clipping +void gfxContext::Clip(const Rect& rect) { + AzureState::PushedClip clip = {nullptr, rect, mTransform}; + CurrentState().pushedClips.AppendElement(clip); + mDT->PushClipRect(rect); + NewPath(); +} + +void gfxContext::Clip(const gfxRect& rect) { Clip(ToRect(rect)); } + +void gfxContext::Clip(Path* aPath) { + mDT->PushClip(aPath); + AzureState::PushedClip clip = {aPath, Rect(), mTransform}; + CurrentState().pushedClips.AppendElement(clip); +} + +void gfxContext::Clip() { + if (mPathIsRect) { + MOZ_ASSERT(!mTransformChanged); + + AzureState::PushedClip clip = {nullptr, mRect, mTransform}; + CurrentState().pushedClips.AppendElement(clip); + mDT->PushClipRect(mRect); + } else { + EnsurePath(); + mDT->PushClip(mPath); + AzureState::PushedClip clip = {mPath, Rect(), mTransform}; + CurrentState().pushedClips.AppendElement(clip); + } +} + +void gfxContext::PopClip() { + MOZ_ASSERT(CurrentState().pushedClips.Length() > 0); + + CurrentState().pushedClips.RemoveLastElement(); + mDT->PopClip(); +} + +gfxRect gfxContext::GetClipExtents(ClipExtentsSpace aSpace) const { + Rect rect = GetAzureDeviceSpaceClipBounds(); + + if (rect.IsZeroArea()) { + return gfxRect(0, 0, 0, 0); + } + + if (aSpace == eUserSpace) { + Matrix mat = mTransform; + mat.Invert(); + rect = mat.TransformBounds(rect); + } + + return ThebesRect(rect); +} + +bool gfxContext::ExportClip(ClipExporter& aExporter) { + for (unsigned int i = 0; i < mStateStack.Length(); i++) { + for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { + AzureState::PushedClip& clip = mStateStack[i].pushedClips[c]; + gfx::Matrix transform = clip.transform; + transform.PostTranslate(-GetDeviceOffset()); + + aExporter.BeginClip(transform); + if (clip.path) { + clip.path->StreamToSink(&aExporter); + } else { + aExporter.MoveTo(clip.rect.TopLeft()); + aExporter.LineTo(clip.rect.TopRight()); + aExporter.LineTo(clip.rect.BottomRight()); + aExporter.LineTo(clip.rect.BottomLeft()); + aExporter.Close(); + } + aExporter.EndClip(); + } + } + + return true; +} + +bool gfxContext::ClipContainsRect(const gfxRect& aRect) { + // Since we always return false when the clip list contains a + // non-rectangular clip or a non-rectilinear transform, our 'total' clip + // is always a rectangle if we hit the end of this function. + Rect clipBounds(0, 0, Float(mDT->GetSize().width), + Float(mDT->GetSize().height)); + + for (unsigned int i = 0; i < mStateStack.Length(); i++) { + for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { + AzureState::PushedClip& clip = mStateStack[i].pushedClips[c]; + if (clip.path || !clip.transform.IsRectilinear()) { + // Cairo behavior is we return false if the clip contains a non- + // rectangle. + return false; + } else { + Rect clipRect = mTransform.TransformBounds(clip.rect); + + clipBounds.IntersectRect(clipBounds, clipRect); + } + } + } + + return clipBounds.Contains(ToRect(aRect)); +} + +// rendering sources + +void gfxContext::SetColor(const sRGBColor& aColor) { + CURRENTSTATE_CHANGED() + CurrentState().pattern = nullptr; + CurrentState().color = ToDeviceColor(aColor); +} + +void gfxContext::SetDeviceColor(const DeviceColor& aColor) { + CURRENTSTATE_CHANGED() + CurrentState().pattern = nullptr; + CurrentState().color = aColor; +} + +bool gfxContext::GetDeviceColor(DeviceColor& aColorOut) { + if (CurrentState().pattern) { + return CurrentState().pattern->GetSolidColor(aColorOut); + } + + aColorOut = CurrentState().color; + return true; +} + +void gfxContext::SetPattern(gfxPattern* pattern) { + CURRENTSTATE_CHANGED() + CurrentState().patternTransformChanged = false; + CurrentState().pattern = pattern; +} + +already_AddRefed<gfxPattern> gfxContext::GetPattern() { + RefPtr<gfxPattern> pat; + + AzureState& state = CurrentState(); + if (state.pattern) { + pat = state.pattern; + } else { + pat = new gfxPattern(state.color); + } + return pat.forget(); +} + +// masking +void gfxContext::Mask(SourceSurface* aSurface, Float aAlpha, + const Matrix& aTransform) { + Matrix old = mTransform; + Matrix mat = aTransform * mTransform; + + ChangeTransform(mat); + mDT->MaskSurface( + PatternFromState(this), aSurface, Point(), + DrawOptions(aAlpha, CurrentState().op, CurrentState().aaMode)); + ChangeTransform(old); +} + +void gfxContext::Mask(SourceSurface* surface, float alpha, + const Point& offset) { + // We clip here to bind to the mask surface bounds, see above. + mDT->MaskSurface( + PatternFromState(this), surface, offset, + DrawOptions(alpha, CurrentState().op, CurrentState().aaMode)); +} + +void gfxContext::Paint(Float alpha) { + AUTO_PROFILER_LABEL("gfxContext::Paint", GRAPHICS); + + Matrix mat = mDT->GetTransform(); + mat.Invert(); + Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize()))); + + mDT->FillRect(paintRect, PatternFromState(this), DrawOptions(alpha, GetOp())); +} + +void gfxContext::PushGroupForBlendBack(gfxContentType content, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform) { + mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, + aMaskTransform); +} + +void gfxContext::PopGroupAndBlend() { mDT->PopLayer(); } + +#ifdef MOZ_DUMP_PAINTING +void gfxContext::WriteAsPNG(const char* aFile) { + gfxUtils::WriteAsPNG(mDT, aFile); +} + +void gfxContext::DumpAsDataURI() { gfxUtils::DumpAsDataURI(mDT); } + +void gfxContext::CopyAsDataURI() { gfxUtils::CopyAsDataURI(mDT); } +#endif + +void gfxContext::EnsurePath() { + if (mPathBuilder) { + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; + } + + if (mPath) { + if (mTransformChanged) { + Matrix mat = mTransform; + mat.Invert(); + mat = mPathTransform * mat; + mPathBuilder = mPath->TransformedCopyToBuilder(mat); + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; + + mTransformChanged = false; + } + return; + } + + EnsurePathBuilder(); + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; +} + +void gfxContext::EnsurePathBuilder() { + if (mPathBuilder && !mTransformChanged) { + return; + } + + if (mPath) { + if (!mTransformChanged) { + mPathBuilder = mPath->CopyToBuilder(); + mPath = nullptr; + } else { + Matrix invTransform = mTransform; + invTransform.Invert(); + Matrix toNewUS = mPathTransform * invTransform; + mPathBuilder = mPath->TransformedCopyToBuilder(toNewUS); + } + return; + } + + DebugOnly<PathBuilder*> oldPath = mPathBuilder.get(); + + if (!mPathBuilder) { + mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING); + + if (mPathIsRect) { + mPathBuilder->MoveTo(mRect.TopLeft()); + mPathBuilder->LineTo(mRect.TopRight()); + mPathBuilder->LineTo(mRect.BottomRight()); + mPathBuilder->LineTo(mRect.BottomLeft()); + mPathBuilder->Close(); + } + } + + if (mTransformChanged) { + // This could be an else if since this should never happen when + // mPathBuilder is nullptr and mPath is nullptr. But this way we can + // assert if all the state is as expected. + MOZ_ASSERT(oldPath); + MOZ_ASSERT(!mPathIsRect); + + Matrix invTransform = mTransform; + invTransform.Invert(); + Matrix toNewUS = mPathTransform * invTransform; + + RefPtr<Path> path = mPathBuilder->Finish(); + if (!path) { + gfxCriticalError() + << "gfxContext::EnsurePathBuilder failed in PathBuilder::Finish"; + } + mPathBuilder = path->TransformedCopyToBuilder(toNewUS); + } + + mPathIsRect = false; +} + +CompositionOp gfxContext::GetOp() { + if (CurrentState().op != CompositionOp::OP_SOURCE) { + return CurrentState().op; + } + + AzureState& state = CurrentState(); + if (state.pattern) { + if (state.pattern->IsOpaque()) { + return CompositionOp::OP_OVER; + } else { + return CompositionOp::OP_SOURCE; + } + } else { + if (state.color.a > 0.999) { + return CompositionOp::OP_OVER; + } else { + return CompositionOp::OP_SOURCE; + } + } +} + +/* SVG font code can change the transform after having set the pattern on the + * context. When the pattern is set it is in user space, if the transform is + * changed after doing so the pattern needs to be converted back into userspace. + * We just store the old pattern transform here so that we only do the work + * needed here if the pattern is actually used. + * We need to avoid doing this when this ChangeTransform comes from a restore, + * since the current pattern and the current transform are both part of the + * state we know the new CurrentState()'s values are valid. But if we assume + * a change they might become invalid since patternTransformChanged is part of + * the state and might be false for the restored AzureState. + */ +void gfxContext::ChangeTransform(const Matrix& aNewMatrix, + bool aUpdatePatternTransform) { + AzureState& state = CurrentState(); + + if (aUpdatePatternTransform && (state.pattern) && + !state.patternTransformChanged) { + state.patternTransform = GetDTTransform(); + state.patternTransformChanged = true; + } + + if (mPathIsRect) { + Matrix invMatrix = aNewMatrix; + + invMatrix.Invert(); + + Matrix toNewUS = mTransform * invMatrix; + + if (toNewUS.IsRectilinear()) { + mRect = toNewUS.TransformBounds(mRect); + mRect.NudgeToIntegers(); + } else { + mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING); + + mPathBuilder->MoveTo(toNewUS.TransformPoint(mRect.TopLeft())); + mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.TopRight())); + mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomRight())); + mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomLeft())); + mPathBuilder->Close(); + + mPathIsRect = false; + } + + // No need to consider the transform changed now! + mTransformChanged = false; + } else if ((mPath || mPathBuilder) && !mTransformChanged) { + mTransformChanged = true; + mPathTransform = mTransform; + } + + mTransform = aNewMatrix; + + mDT->SetTransform(GetDTTransform()); +} + +Rect gfxContext::GetAzureDeviceSpaceClipBounds() const { + Rect rect(CurrentState().deviceOffset.x + Float(mDT->GetRect().x), + CurrentState().deviceOffset.y + Float(mDT->GetRect().y), + Float(mDT->GetSize().width), Float(mDT->GetSize().height)); + for (unsigned int i = 0; i < mStateStack.Length(); i++) { + for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { + const AzureState::PushedClip& clip = mStateStack[i].pushedClips[c]; + if (clip.path) { + Rect bounds = clip.path->GetBounds(clip.transform); + rect.IntersectRect(rect, bounds); + } else { + rect.IntersectRect(rect, clip.transform.TransformBounds(clip.rect)); + } + } + } + + return rect; +} + +Point gfxContext::GetDeviceOffset() const { + return CurrentState().deviceOffset; +} + +void gfxContext::SetDeviceOffset(const Point& aOffset) { + CurrentState().deviceOffset = aOffset; +} + +Matrix gfxContext::GetDTTransform() const { + Matrix mat = mTransform; + mat._31 -= CurrentState().deviceOffset.x; + mat._32 -= CurrentState().deviceOffset.y; + return mat; +} |