Files
palemoon27/gfx/thebes/gfxContext.cpp
T
roytam1 10160cd03d import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1240749 - Fixes for DPI support in Gtk widget interface: remove incorrect Get[Avail]RectDisplayPix overrides, as desktop pixels == device pixels for the Gtk widget backend, and implement nsScreenGtk::GetDefaultCSSScaleFactor, required by nsGlobalWindow since per-monitor DPI patches in bug 890156. r=karlt (9975cac985)
- Bug 1033488 - Part 2. Make PuppetBidiKeyboard correctly on content process. r=masayuki (3a91ce60a8)
- Bug 1257067 - modified XF86XK_ keysyms to GDK_ ones r=masayuki (4b922f9365)
- Bug 1251984 - allow gfx.xrender.enabled pref to work for compositing even if content backend is not Cairo. r=jrmuizel (257a163074)
- Bug 1235941 - Detect DPI change for GTK3. r=karlt (f8ca209d4b)
- Bug 1256728: Add environment variable to disable force present from bug 1232042. r=BenWa (c3b12a085b)
- Bug 1257124: Add null check for IDWriteFactory in gfxDWriteFontFileLoader::CreateCustomFontFile. r=bas (d33af4b9e1)
- Bug 1266209 - log failure reason when PathBuilderD2D::Finish fails. r=bas.schouten (360d7c2ada)
- Bug 1256552 - Disable C4577 to unblock compilation on VS2015; r=jrmuizel (7375fbf42a)
- Bug 1265960 - Remove gfxReusable* r=Bas (ed0b3aca14)
- Bug 1246290 - Refactoring to get rid of SampleContentTransformForFrame from AsyncPanZoomController. r=botond (3be6b4c1f9)
- Bug 1246290 - Add the ability for APZCs to not expose async transforms. r=botond (48942ceddf)
- Bug 1259296 - Scroll snap in the compositor in response to wheel events. r=kats (5fc694c804)
- Bug 1249040 - Allow wheel scrolls to accumulate in the presence of scroll snapping. r=kats (7514e3f6d6)
- Bug 1237343 - Replace uses of mozilla::Vector with nsTArray. r=botond (b721de19cd)
- Bug 1236046 - Don't use a stupidly small time delta to compute a velocity. r=botond (6c8f298ab6)
- Bug 1257959 - Dynamic toolbar transition seems to slow down flings r=kats (47ec4d0d54)
- Bug 1265513 - Do a fuzzy match when checking to see if a scroll will result in overscroll. r=botond (03ee304916)
- Bug 1254252 - Have Axis::ClampOriginToScrollableRect just return the original value if it falls with in the page bounds. r=kats (f88c0b4d7d)
- Bug 1261373 - Record how long it takes for content response notifications to be delivered to APZ. r=botond f=bsmedberg (b4b24d7602)
- Bug 1265515 - Update nsAutoPtr.h includes in gfx/layers/apz to RefPtr.h. r=kats (48bbc2cda8)
- Bug 1259296 - Make sure APZ smooth scroll animations end at their exact destination scroll offset. r=kats (36cace259b)
- Bug 1251837, part 1 - Use pushPrefEnv in setDeltaMultiplierSettings in window_wheel_default_action.html. r=masayuki (d673d55f02)
- Bug 1251837, part 2 - Use pushPrefEnv in doTestActionOverride in window_wheel_default_action.html. r=masayuki (f0375a3820)
- Bug 1251837, part 3 - Use pushPrefEnv in doTestWholeScroll in window_wheel_default_action.html. r=masayuki (0a7af4e103)
- Bug 1251837, part 4 - Use pushPrefEnv in doTestActionOverride in window_wheel_default_action.html. r=masayuki (51bd9cd1e2)
- Bug 1251837, part 5 - Make prepare() in doTestScroll take a callback, and use pushPrefEnv. r=masayuki (856056a947)
- Bug 1251837, part 6 - Make cleanup() in doTestScroll take a callback, and use pushPrefEnv. r=masayuki (34aa62b7f3)
- Bug 1255173 - Fix onZoomReset() in test_wheel_default_action.html to work with e10s. r=masayuki, r=jmaher (0c248c7587)
- Bug 1255178 - Part 1: Remove a leftover use of clearUserPref. r=masayuki Also, remove some trailing whitespace. (c067affdba)
- Bug 1255178 - Part 2: Remove extraneous argument to sendWheelAndWait. r=masayuki (80ce47c89b)
- Bug 1255634 - APZ should scroll by more than a page with large values of mousewheel.default.delta_multiplier_{x,y}. r=masayuki,kats (d72f77daed)
- Bug 1251638 - Don't clamp the displayport to the scrollable rect on the compositor side. r=botond (5dae2ba82e)
- Bug 1251638 - Do a bit of cleanup on the displayport margin computation in APZC; no functional changes. r=botond (575453b538)
- Bug 1266154 - Don't start a scroll snap animation if we're already at the destination. r=kats (2926d33a07)
- Bug 1258851 - Update HitTestingTreeNode::IsScrollbarNode to include the scrollbar track layers. r=rbarker (1e68081a6a)
- Bug 1265806 - Update scrollbar finding code to deal with layer trees on both OS X and Windows properly. r=rbarker (e8df49352e)
- Bug 1255224 - Add pref to force glFinish in DXGL path. (default:false) - r=dvander (8d399f8a49)
- Bug 1224199 - Destroy SharedSurfaces before ~GLContext(). - r=jrmuizel (5ad8d11f8e)
- Bug 1232480. Use 'precision' instead of 'prevision' (c7310988fa)
- Bug 1232668. Allow using kTex2DBlit_FragShaderSource on non-ES targets. (1951af5c1c)
- Bug 1235299 - Fix WebGL assertions for copyTexImage2D. r=jgilbert (226b7c14a8)
- Bug 1249189 - Use GL_RED to replace GL_ALPHA in YCbCr convert. r=jgilbert (e1d8ae2513)
- Bug 1256552 - fix int-to-float conversions in RadialGradientEffectD2D1. r=jrmuizel (36bb7f7f0f)
- Bug 1249279 - Let subdocuments' presContexts inherit the DPI setting of their parent, instead of retrieving it from their widget, to avoid using stale values from a currently-hidden widget on a screen with a different DPI. r=bz (4b8f6e0960)
- Bug 1237479 -- nsScriptSecurityManager needs to use the correct user context id in the origin attributes in a few places. r=sicking (90504a4130)
- Bug 1105556 - Call Create(originAttributes) when loadinfo->loadingPrincipal is null, instead of CreatePrincipalWithInheritedAttributes(). r=sicking (fdb341ca53)
- Bug 1253788 - Don't reload inline chrome:// style sheets in nsXBLPrototypeResources. r=bzbarsky (862464ea6c)
- Bug 1261123 - don't needlessly construct nsAutoCString temporaries in nsDefaultURIFixup; r=smaug (08f13674a8)
- Bug 1253673, r=bz (03caab3cce)
- Bug 1259511 - Overwrite loadInfo within AboutProtocolHandler and log warning to console. r=sicking (e2fc868326)
- Bug 905460 - Convert http legacy refs to smart pointers r=dragana a=kwierso (d4bdd102fa)
- Bug 1248564 - "Default authentication credentials (NTLM, Kerb) used in private mode". r=mayhemer (37c5674a0d)
- Bug 345580 - Problem decoding quoted-printable question mark in subject r=biesi (00f66a34a7)
- Bug 906986 - Rework fix for Bug 1241690 to avoid reliance on NrIceCtx inside NrIceMediaStream. r=bwc, r=drno (3d6aabeab6)
- Bug 906986 - Disable TestSrflxCandPairingFilter until bug 1226838 is fixed. r=bwc, r=drno (687281bf8b)
- Bug 906986 - Use the streams from the ice_ctx, don't hold a separate set. r=bwc, r=drno (269cd38ed2)
- Bug 906986 - Genericize some of the test setup and improve logging. r=bwc, r=drno (ad735f2459)
- Bug 906986 - Wrap NrIceCtx in NrIceCtxHandler which will allow us to handle ice restart. r=bwc, r=drno (4f2c1e5866)
- Bug 911216 - Part 12: Windows build bustage fix on a CLOSED TREE. r=efaust (f81e40df57)
- Bug 1261329 - Fix OOM handling in shell dissrc function r=efaust (d61eb121de)
- Bug 1263868 - Properly recover from OOM in ShellAutoEntryMonitor. r=jonco (3830ce7aae)
- Bug 1257722 - Prevent stdout/stderr from getting closed, r=jorendorff (48629efc06)
- Bug 1258847 - Set PTHREAD_MUTEX_ERRORCHECK for js::Mutex in debug builds. r=sfink (48cb353190)
- Bug 1252464 - Remove FrameRange cray cray in favor of using GCVectors. (r=jimb) (60ad943599)
- Bug 1257194: Tracelogger - Check for oom in drainTracelogger, r=bbouvier (7d8299958f)
- Bug 1264961 - Fix OOM case in Debugger::replaceFrameGuts. (r=jimb) (203816a8b3)
- Bug 1257588 - Don't ignore self-hosted frames when getting a saved frame's async cause. r=fitzgen (15b467a950)
- Bug 1258535 - Part 0: Add more and stronger asserts that SavedStacks-related JSAPI methods are called correctly; r=jandem (187251c401)
- Bug 1258535 - Check for the existence of a global before checking if its standard classes are resolved; r=jandem (18f7bbcc69)
- Bug 1264975 - Fix assertion in DebugScope wrapping non-syntactic scopes. (r=jimb) (753e315ddb)
- Bug 1259403 - Tracelogger: Always make sure there are 3 free slots for events, r=bbouvier (67a13b7a14)
- Bug 1231926 - add assertions on BYTEOFFSET_SLOT. r=waldo (3508ea1943)
- Bug 1260725 - Handle and report OOM in census breakdown parsing; r=jorendorff a=kwierso (de669940d1)
- Bug 1263074 - Change the JavaScript syntax error message for the old octal literal notation to suggest the new notation. r=Waldo (4e2457b27a)
- Bug 1257096, don't try to report about unusual unhandled rejected Promises, r=bz,waldo (682e4720d9)
- Bug 1262402: Use the new int64 testing features; r=luke (d213bb4f2c)
- Bug 1263882: Set a default value in DescribeScriptedCaller if the filename is null; r=luke (f66ec211f5)
2024-05-09 21:38:16 +08:00

1301 lines
33 KiB
C++

/* -*- 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 "gfxASurface.h"
#include "gfxPattern.h"
#include "gfxPlatform.h"
#include "gfxTeeSurface.h"
#include "gfxPrefs.h"
#include "GeckoProfiler.h"
#include "gfx2DGlue.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/gfx/DrawTargetTiled.h"
#include <algorithm>
#if XP_WIN
#include "gfxWindowsPlatform.h"
#endif
using namespace mozilla;
using namespace mozilla::gfx;
UserDataKey gfxContext::sDontUseAsSourceKey;
PatternFromState::operator mozilla::gfx::Pattern&()
{
gfxContext::AzureState &state = mContext->CurrentState();
if (state.pattern) {
return *state.pattern->GetPattern(mContext->mDT, state.patternTransformChanged ? &state.patternTransform : nullptr);
}
if (state.sourceSurface) {
Matrix transform = state.surfTransform;
if (state.patternTransformChanged) {
Matrix mat = mContext->GetDTTransform();
if (!mat.Invert()) {
mPattern = new (mColorPattern.addr())
ColorPattern(Color()); // transparent black to paint nothing
return *mPattern;
}
transform = transform * state.patternTransform * mat;
}
mPattern = new (mSurfacePattern.addr())
SurfacePattern(state.sourceSurface, ExtendMode::CLAMP, transform);
return *mPattern;
}
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";
}
MOZ_COUNT_CTOR(gfxContext);
mStateStack.SetLength(1);
CurrentState().drawTarget = mDT;
CurrentState().deviceOffset = aDeviceOffset;
mDT->SetTransform(GetDTTransform());
}
/* static */ already_AddRefed<gfxContext>
gfxContext::ForDrawTarget(DrawTarget* aTarget,
const mozilla::gfx::Point& aDeviceOffset)
{
if (!aTarget || !aTarget->IsValid()) {
gfxCriticalNote << "Invalid target in gfxContext::ForDrawTarget " << hexa(aTarget);
return nullptr;
}
RefPtr<gfxContext> result = new gfxContext(aTarget, aDeviceOffset);
return result.forget();
}
/* static */ already_AddRefed<gfxContext>
gfxContext::ForDrawTargetWithTransform(DrawTarget* aTarget)
{
if (!aTarget || !aTarget->IsValid()) {
gfxCriticalNote << "Invalid target in gfxContext::ForDrawTargetWithTransform " << hexa(aTarget);
return nullptr;
}
Matrix transform = aTarget->GetTransform();
RefPtr<gfxContext> result = new gfxContext(aTarget);
result->SetMatrix(ThebesMatrix(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++) {
mDT->PopClip();
}
}
mDT->Flush();
MOZ_COUNT_DTOR(gfxContext);
}
already_AddRefed<gfxASurface>
gfxContext::CurrentSurface()
{
if (mDT->GetBackendType() == BackendType::CAIRO) {
cairo_t* ctx = static_cast<cairo_t*>
(mDT->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
if (ctx) {
cairo_surface_t* s = cairo_get_group_target(ctx);
if (s) {
return gfxASurface::Wrap(s);
}
}
}
// An Azure context doesn't have a surface backing it.
return nullptr;
}
void
gfxContext::Save()
{
CurrentState().transform = mTransform;
mStateStack.AppendElement(AzureState(CurrentState()));
CurrentState().pushedClips.Clear();
}
void
gfxContext::Restore()
{
for (unsigned int c = 0; c < CurrentState().pushedClips.Length(); c++) {
mDT->PopClip();
}
mStateStack.RemoveElementAt(mStateStack.Length() - 1);
mDT = CurrentState().drawTarget;
ChangeTransform(CurrentState().transform, false);
}
// drawing
void
gfxContext::NewPath()
{
mPath = nullptr;
mPathBuilder = nullptr;
mPathIsRect = false;
mTransformChanged = false;
}
void
gfxContext::ClosePath()
{
EnsurePathBuilder();
mPathBuilder->Close();
}
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;
}
gfxPoint
gfxContext::CurrentPoint()
{
EnsurePathBuilder();
return ThebesPoint(mPathBuilder->CurrentPoint());
}
void
gfxContext::Fill()
{
Fill(PatternFromState(this));
}
void
gfxContext::Fill(const Pattern& aPattern)
{
PROFILER_LABEL("gfxContext", "Fill",
js::ProfileEntry::Category::GRAPHICS);
FillAzure(aPattern, 1.0f);
}
void
gfxContext::MoveTo(const gfxPoint& pt)
{
EnsurePathBuilder();
mPathBuilder->MoveTo(ToPoint(pt));
}
void
gfxContext::LineTo(const gfxPoint& pt)
{
EnsurePathBuilder();
mPathBuilder->LineTo(ToPoint(pt));
}
void
gfxContext::Line(const gfxPoint& start, const gfxPoint& end)
{
EnsurePathBuilder();
mPathBuilder->MoveTo(ToPoint(start));
mPathBuilder->LineTo(ToPoint(end));
}
// 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, true)) {
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();
}
// transform stuff
void
gfxContext::Multiply(const gfxMatrix& matrix)
{
ChangeTransform(ToMatrix(matrix) * mTransform);
}
void
gfxContext::SetMatrix(const gfxMatrix& matrix)
{
ChangeTransform(ToMatrix(matrix));
}
gfxMatrix
gfxContext::CurrentMatrix() const
{
return ThebesMatrix(mTransform);
}
gfxPoint
gfxContext::DeviceToUser(const gfxPoint& point) const
{
Matrix matrix = mTransform;
matrix.Invert();
return ThebesPoint(matrix * ToPoint(point));
}
gfxSize
gfxContext::DeviceToUser(const gfxSize& size) const
{
Matrix matrix = mTransform;
matrix.Invert();
return ThebesSize(matrix * ToSize(size));
}
gfxRect
gfxContext::DeviceToUser(const gfxRect& rect) const
{
Matrix matrix = mTransform;
matrix.Invert();
return ThebesRect(matrix.TransformBounds(ToRect(rect)));
}
gfxPoint
gfxContext::UserToDevice(const gfxPoint& point) const
{
return ThebesPoint(mTransform * ToPoint(point));
}
gfxSize
gfxContext::UserToDevice(const gfxSize& size) const
{
const Matrix &matrix = mTransform;
gfxSize 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, 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
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)) {
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;
}
return false;
}
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().aaMode = mode;
}
AntialiasMode
gfxContext::CurrentAntialiasMode() const
{
return CurrentState().aaMode;
}
void
gfxContext::SetDash(gfxFloat *dashes, int ndash, gfxFloat offset)
{
AzureState &state = CurrentState();
state.dashPattern.SetLength(ndash);
for (int i = 0; i < ndash; i++) {
state.dashPattern[i] = Float(dashes[i]);
}
state.strokeOptions.mDashLength = ndash;
state.strokeOptions.mDashOffset = Float(offset);
state.strokeOptions.mDashPattern = ndash ? state.dashPattern.Elements()
: nullptr;
}
bool
gfxContext::CurrentDash(FallibleTArray<gfxFloat>& dashes, gfxFloat* offset) const
{
const AzureState &state = CurrentState();
int count = state.strokeOptions.mDashLength;
if (count <= 0 || !dashes.SetLength(count, fallible)) {
return false;
}
for (int i = 0; i < count; i++) {
dashes[i] = state.dashPattern[i];
}
*offset = state.strokeOptions.mDashOffset;
return true;
}
gfxFloat
gfxContext::CurrentDashOffset() const
{
return CurrentState().strokeOptions.mDashOffset;
}
void
gfxContext::SetLineWidth(gfxFloat width)
{
CurrentState().strokeOptions.mLineWidth = Float(width);
}
gfxFloat
gfxContext::CurrentLineWidth() const
{
return CurrentState().strokeOptions.mLineWidth;
}
void
gfxContext::SetOp(CompositionOp aOp)
{
CurrentState().op = aOp;
}
CompositionOp
gfxContext::CurrentOp() const
{
return CurrentState().op;
}
void
gfxContext::SetLineCap(CapStyle cap)
{
CurrentState().strokeOptions.mLineCap = cap;
}
CapStyle
gfxContext::CurrentLineCap() const
{
return CurrentState().strokeOptions.mLineCap;
}
void
gfxContext::SetLineJoin(JoinStyle join)
{
CurrentState().strokeOptions.mLineJoin = join;
}
JoinStyle
gfxContext::CurrentLineJoin() const
{
return CurrentState().strokeOptions.mLineJoin;
}
void
gfxContext::SetMiterLimit(gfxFloat limit)
{
CurrentState().strokeOptions.mMiterLimit = Float(limit);
}
gfxFloat
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.RemoveElementAt(CurrentState().pushedClips.Length() - 1);
mDT->PopClip();
}
gfxRect
gfxContext::GetClipExtents()
{
Rect rect = GetAzureDeviceSpaceClipBounds();
if (rect.width == 0 || rect.height == 0) {
return gfxRect(0, 0, 0, 0);
}
Matrix mat = mTransform;
mat.Invert();
rect = mat.TransformBounds(rect);
return ThebesRect(rect);
}
bool
gfxContext::HasComplexClip() const
{
for (int i = mStateStack.Length() - 1; i >= 0; i--) {
for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
const AzureState::PushedClip &clip = mStateStack[i].pushedClips[c];
if (clip.path || !clip.transform.IsRectilinear()) {
return true;
}
}
}
return false;
}
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 Color& aColor)
{
CurrentState().pattern = nullptr;
CurrentState().sourceSurfCairo = nullptr;
CurrentState().sourceSurface = nullptr;
CurrentState().color = ToDeviceColor(aColor);
}
void
gfxContext::SetDeviceColor(const Color& aColor)
{
CurrentState().pattern = nullptr;
CurrentState().sourceSurfCairo = nullptr;
CurrentState().sourceSurface = nullptr;
CurrentState().color = aColor;
}
bool
gfxContext::GetDeviceColor(Color& aColorOut)
{
if (CurrentState().sourceSurface) {
return false;
}
if (CurrentState().pattern) {
return CurrentState().pattern->GetSolidColor(aColorOut);
}
aColorOut = CurrentState().color;
return true;
}
void
gfxContext::SetSource(gfxASurface *surface, const gfxPoint& offset)
{
CurrentState().surfTransform = Matrix(1.0f, 0, 0, 1.0f, Float(offset.x), Float(offset.y));
CurrentState().pattern = nullptr;
CurrentState().patternTransformChanged = false;
// Keep the underlying cairo surface around while we keep the
// sourceSurface.
CurrentState().sourceSurfCairo = surface;
CurrentState().sourceSurface =
gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface);
CurrentState().color = Color(0, 0, 0, 0);
}
void
gfxContext::SetPattern(gfxPattern *pattern)
{
CurrentState().sourceSurfCairo = nullptr;
CurrentState().sourceSurface = nullptr;
CurrentState().patternTransformChanged = false;
CurrentState().pattern = pattern;
}
already_AddRefed<gfxPattern>
gfxContext::GetPattern()
{
RefPtr<gfxPattern> pat;
AzureState &state = CurrentState();
if (state.pattern) {
pat = state.pattern;
} else if (state.sourceSurface) {
NS_ASSERTION(false, "Ugh, this isn't good.");
} else {
pat = new gfxPattern(state.color);
}
return pat.forget();
}
void
gfxContext::SetFontSmoothingBackgroundColor(const Color& aColor)
{
CurrentState().fontSmoothingBackgroundColor = aColor;
}
Color
gfxContext::GetFontSmoothingBackgroundColor()
{
return CurrentState().fontSmoothingBackgroundColor;
}
// 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(gfxASurface *surface, const gfxPoint& offset)
{
PROFILER_LABEL("gfxContext", "Mask",
js::ProfileEntry::Category::GRAPHICS);
// Lifetime needs to be limited here as we may simply wrap surface's data.
RefPtr<SourceSurface> sourceSurf =
gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface);
if (!sourceSurf) {
return;
}
gfxPoint pt = surface->GetDeviceOffset();
Mask(sourceSurf, 1.0f, Point(offset.x - pt.x, offset.y - pt.y));
}
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(gfxFloat alpha)
{
PROFILER_LABEL("gfxContext", "Paint",
js::ProfileEntry::Category::GRAPHICS);
AzureState &state = CurrentState();
if (state.sourceSurface && !state.sourceSurfCairo &&
!state.patternTransformChanged)
{
// This is the case where a PopGroupToSource has been done and this
// paint is executed without changing the transform or the source.
Matrix oldMat = mDT->GetTransform();
IntSize surfSize = state.sourceSurface->GetSize();
mDT->SetTransform(Matrix::Translation(-state.deviceOffset.x,
-state.deviceOffset.y));
mDT->DrawSurface(state.sourceSurface,
Rect(state.sourceSurfaceDeviceOffset, Size(surfSize.width, surfSize.height)),
Rect(Point(), Size(surfSize.width, surfSize.height)),
DrawSurfaceOptions(), DrawOptions(alpha, GetOp()));
mDT->SetTransform(oldMat);
return;
}
Matrix mat = mDT->GetTransform();
mat.Invert();
Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize())));
mDT->FillRect(paintRect, PatternFromState(this),
DrawOptions(Float(alpha), GetOp()));
}
void
gfxContext::PushGroupForBlendBack(gfxContentType content, Float aOpacity, SourceSurface* aMask, const Matrix& aMaskTransform)
{
if (gfxPrefs::UseNativePushLayer()) {
Save();
mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, aMaskTransform);
} else {
DrawTarget* oldDT = mDT;
PushNewDT(content);
if (oldDT != mDT) {
PushClipsToDT(mDT);
}
mDT->SetTransform(GetDTTransform());
CurrentState().mBlendOpacity = aOpacity;
CurrentState().mBlendMask = aMask;
#ifdef DEBUG
CurrentState().mWasPushedForBlendBack = true;
#endif
CurrentState().mBlendMaskTransform = aMaskTransform;
}
}
static gfxRect
GetRoundOutDeviceClipExtents(gfxContext* aCtx)
{
gfxContextMatrixAutoSaveRestore save(aCtx);
aCtx->SetMatrix(gfxMatrix());
gfxRect r = aCtx->GetClipExtents();
r.RoundOut();
return r;
}
void
gfxContext::PushGroupAndCopyBackground(gfxContentType content, Float aOpacity, SourceSurface* aMask, const Matrix& aMaskTransform)
{
IntRect clipExtents;
if (mDT->GetFormat() != SurfaceFormat::B8G8R8X8) {
gfxRect clipRect = GetRoundOutDeviceClipExtents(this);
clipExtents = IntRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
}
bool pushOpaqueWithCopiedBG = (mDT->GetFormat() == SurfaceFormat::B8G8R8X8 ||
mDT->GetOpaqueRect().Contains(clipExtents)) &&
!mDT->GetUserData(&sDontUseAsSourceKey);
if (gfxPrefs::UseNativePushLayer()) {
Save();
if (pushOpaqueWithCopiedBG) {
mDT->PushLayer(true, aOpacity, aMask, aMaskTransform, IntRect(), true);
} else {
mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, aMaskTransform, IntRect(), false);
}
} else {
RefPtr<SourceSurface> source;
// This snapshot can be nullptr if the DrawTarget is a cairo target that is currently
// in an error state.
if (pushOpaqueWithCopiedBG && (source = mDT->Snapshot())) {
DrawTarget *oldDT = mDT;
Point oldDeviceOffset = CurrentState().deviceOffset;
PushNewDT(gfxContentType::COLOR);
if (oldDT == mDT) {
// Creating new DT failed.
return;
}
CurrentState().mBlendOpacity = aOpacity;
CurrentState().mBlendMask = aMask;
#ifdef DEBUG
CurrentState().mWasPushedForBlendBack = true;
#endif
CurrentState().mBlendMaskTransform = aMaskTransform;
Point offset = CurrentState().deviceOffset - oldDeviceOffset;
Rect surfRect(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height));
Rect sourceRect = surfRect + offset;
mDT->SetTransform(Matrix());
// XXX: It's really sad that we have to do this (for performance).
// Once DrawTarget gets a PushLayer API we can implement this within
// DrawTargetTiled.
if (source->GetType() == SurfaceType::TILED) {
SnapshotTiled *sourceTiled = static_cast<SnapshotTiled*>(source.get());
for (uint32_t i = 0; i < sourceTiled->mSnapshots.size(); i++) {
Rect tileSourceRect = sourceRect.Intersect(Rect(sourceTiled->mOrigins[i].x,
sourceTiled->mOrigins[i].y,
sourceTiled->mSnapshots[i]->GetSize().width,
sourceTiled->mSnapshots[i]->GetSize().height));
if (tileSourceRect.IsEmpty()) {
continue;
}
Rect tileDestRect = tileSourceRect - offset;
tileSourceRect -= sourceTiled->mOrigins[i];
mDT->DrawSurface(sourceTiled->mSnapshots[i], tileDestRect, tileSourceRect);
}
} else {
mDT->DrawSurface(source, surfRect, sourceRect);
}
mDT->SetOpaqueRect(oldDT->GetOpaqueRect());
PushClipsToDT(mDT);
mDT->SetTransform(GetDTTransform());
return;
}
DrawTarget* oldDT = mDT;
PushNewDT(content);
if (oldDT != mDT) {
PushClipsToDT(mDT);
}
mDT->SetTransform(GetDTTransform());
CurrentState().mBlendOpacity = aOpacity;
CurrentState().mBlendMask = aMask;
#ifdef DEBUG
CurrentState().mWasPushedForBlendBack = true;
#endif
CurrentState().mBlendMaskTransform = aMaskTransform;
}
}
void
gfxContext::PopGroupAndBlend()
{
if (gfxPrefs::UseNativePushLayer()) {
mDT->PopLayer();
Restore();
} else {
MOZ_ASSERT(CurrentState().mWasPushedForBlendBack);
Float opacity = CurrentState().mBlendOpacity;
RefPtr<SourceSurface> mask = CurrentState().mBlendMask;
Matrix maskTransform = CurrentState().mBlendMaskTransform;
RefPtr<SourceSurface> src = mDT->Snapshot();
Point deviceOffset = CurrentState().deviceOffset;
Restore();
CurrentState().sourceSurfCairo = nullptr;
CurrentState().sourceSurface = src;
CurrentState().sourceSurfaceDeviceOffset = deviceOffset;
CurrentState().pattern = nullptr;
CurrentState().patternTransformChanged = false;
Matrix mat = mTransform;
mat.Invert();
mat.PreTranslate(deviceOffset.x, deviceOffset.y); // device offset translation
CurrentState().surfTransform = mat;
CompositionOp oldOp = GetOp();
SetOp(CompositionOp::OP_OVER);
if (mask) {
if (!maskTransform.HasNonTranslation()) {
Mask(mask, opacity, Point(maskTransform._31, maskTransform._32));
} else {
Mask(mask, opacity, maskTransform);
}
} else {
Paint(opacity);
}
SetOp(oldOp);
}
}
#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;
}
void
gfxContext::FillAzure(const Pattern& aPattern, Float aOpacity)
{
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(aOpacity));
} else {
mDT->FillRect(mRect, aPattern, DrawOptions(aOpacity, op, state.aaMode));
}
} else {
EnsurePath();
mDT->Fill(mPath, aPattern, DrawOptions(aOpacity, op, state.aaMode));
}
}
void
gfxContext::PushClipsToDT(DrawTarget *aDT)
{
// Don't need to save the old transform, we'll be setting a new one soon!
// Push all clips from the bottom of the stack to the clip before ours.
for (unsigned int i = 0; i < mStateStack.Length() - 1; i++) {
for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
aDT->SetTransform(mStateStack[i].pushedClips[c].transform * GetDeviceTransform());
if (mStateStack[i].pushedClips[c].path) {
aDT->PushClip(mStateStack[i].pushedClips[c].path);
} else {
aDT->PushClipRect(mStateStack[i].pushedClips[c].rect);
}
}
}
}
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.sourceSurface) {
if (state.sourceSurface->GetFormat() == SurfaceFormat::B8G8R8X8) {
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.sourceSurface)
&& !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 * mRect.TopLeft());
mPathBuilder->LineTo(toNewUS * mRect.TopRight());
mPathBuilder->LineTo(toNewUS * mRect.BottomRight());
mPathBuilder->LineTo(toNewUS * 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()
{
Rect rect(CurrentState().deviceOffset.x, CurrentState().deviceOffset.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++) {
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;
}
Matrix
gfxContext::GetDeviceTransform() const
{
return Matrix::Translation(-CurrentState().deviceOffset.x,
-CurrentState().deviceOffset.y);
}
Matrix
gfxContext::GetDTTransform() const
{
Matrix mat = mTransform;
mat._31 -= CurrentState().deviceOffset.x;
mat._32 -= CurrentState().deviceOffset.y;
return mat;
}
void
gfxContext::PushNewDT(gfxContentType content)
{
Rect clipBounds = GetAzureDeviceSpaceClipBounds();
clipBounds.RoundOut();
clipBounds.width = std::max(1.0f, clipBounds.width);
clipBounds.height = std::max(1.0f, clipBounds.height);
SurfaceFormat format = gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content);
RefPtr<DrawTarget> newDT =
mDT->CreateSimilarDrawTarget(IntSize(int32_t(clipBounds.width), int32_t(clipBounds.height)),
format);
if (!newDT) {
NS_WARNING("Failed to create DrawTarget of sufficient size.");
newDT = mDT->CreateSimilarDrawTarget(IntSize(64, 64), format);
if (!newDT) {
if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()
#ifdef XP_WIN
&& !(mDT->GetBackendType() == BackendType::DIRECT2D1_1 && !gfxWindowsPlatform::GetPlatform()->GetD3D11ContentDevice())
#endif
) {
// If even this fails.. we're most likely just out of memory!
NS_ABORT_OOM(BytesPerPixel(format) * 64 * 64);
}
newDT = CurrentState().drawTarget;
}
}
Save();
CurrentState().drawTarget = newDT;
CurrentState().deviceOffset = clipBounds.TopLeft();
mDT = newDT;
}