1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include <math.h>
8 
9 #include "mozilla/Alignment.h"
10 
11 #include "cairo.h"
12 
13 #include "gfxContext.h"
14 
15 #include "gfxMatrix.h"
16 #include "gfxUtils.h"
17 #include "gfxASurface.h"
18 #include "gfxPattern.h"
19 #include "gfxPlatform.h"
20 #include "gfxPrefs.h"
21 #include "GeckoProfiler.h"
22 #include "gfx2DGlue.h"
23 #include "mozilla/gfx/PathHelpers.h"
24 #include "mozilla/gfx/DrawTargetTiled.h"
25 #include <algorithm>
26 #include "TextDrawTarget.h"
27 
28 #if XP_WIN
29 #include "gfxWindowsPlatform.h"
30 #include "mozilla/gfx/DeviceManagerDx.h"
31 #endif
32 
33 using namespace mozilla;
34 using namespace mozilla::gfx;
35 
36 UserDataKey gfxContext::sDontUseAsSourceKey;
37 
38 #ifdef DEBUG
39 #define CURRENTSTATE_CHANGED() CurrentState().mContentChanged = true;
40 #else
41 #define CURRENTSTATE_CHANGED()
42 #endif
43 
operator mozilla::gfx::Pattern&()44 PatternFromState::operator mozilla::gfx::Pattern&() {
45   gfxContext::AzureState& state = mContext->CurrentState();
46 
47   if (state.pattern) {
48     return *state.pattern->GetPattern(
49         mContext->mDT,
50         state.patternTransformChanged ? &state.patternTransform : nullptr);
51   }
52 
53   mPattern = new (mColorPattern.addr()) ColorPattern(state.color);
54   return *mPattern;
55 }
56 
gfxContext(DrawTarget * aTarget,const Point & aDeviceOffset)57 gfxContext::gfxContext(DrawTarget* aTarget, const Point& aDeviceOffset)
58     : mPathIsRect(false), mTransformChanged(false), mDT(aTarget) {
59   if (!aTarget) {
60     gfxCriticalError() << "Don't create a gfxContext without a DrawTarget";
61   }
62 
63   mStateStack.SetLength(1);
64   CurrentState().drawTarget = mDT;
65   CurrentState().deviceOffset = aDeviceOffset;
66   mDT->SetTransform(GetDTTransform());
67 }
68 
CreateOrNull(DrawTarget * aTarget,const mozilla::gfx::Point & aDeviceOffset)69 /* static */ already_AddRefed<gfxContext> gfxContext::CreateOrNull(
70     DrawTarget* aTarget, const mozilla::gfx::Point& aDeviceOffset) {
71   if (!aTarget || !aTarget->IsValid()) {
72     gfxCriticalNote << "Invalid target in gfxContext::CreateOrNull "
73                     << hexa(aTarget);
74     return nullptr;
75   }
76 
77   RefPtr<gfxContext> result = new gfxContext(aTarget, aDeviceOffset);
78   return result.forget();
79 }
80 
81 /* static */ already_AddRefed<gfxContext>
CreatePreservingTransformOrNull(DrawTarget * aTarget)82 gfxContext::CreatePreservingTransformOrNull(DrawTarget* aTarget) {
83   if (!aTarget || !aTarget->IsValid()) {
84     gfxCriticalNote
85         << "Invalid target in gfxContext::CreatePreservingTransformOrNull "
86         << hexa(aTarget);
87     return nullptr;
88   }
89 
90   Matrix transform = aTarget->GetTransform();
91   RefPtr<gfxContext> result = new gfxContext(aTarget);
92   result->SetMatrix(transform);
93   return result.forget();
94 }
95 
~gfxContext()96 gfxContext::~gfxContext() {
97   for (int i = mStateStack.Length() - 1; i >= 0; i--) {
98     for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
99       mStateStack[i].drawTarget->PopClip();
100     }
101   }
102 }
103 
GetTextDrawer()104 mozilla::layout::TextDrawTarget* gfxContext::GetTextDrawer() {
105   if (mDT->GetBackendType() == BackendType::WEBRENDER_TEXT) {
106     return static_cast<mozilla::layout::TextDrawTarget*>(&*mDT);
107   }
108   return nullptr;
109 }
110 
Save()111 void gfxContext::Save() {
112   CurrentState().transform = mTransform;
113   mStateStack.AppendElement(AzureState(CurrentState()));
114   CurrentState().pushedClips.Clear();
115 #ifdef DEBUG
116   CurrentState().mContentChanged = false;
117 #endif
118 }
119 
Restore()120 void gfxContext::Restore() {
121 #ifdef DEBUG
122   // gfxContext::Restore is used to restore AzureState. We need to restore it
123   // only if it was altered. The following APIs do change the content of
124   // AzureState, a user should save the state before using them and restore it
125   // after finishing painting:
126   // 1. APIs to setup how to paint, such as SetColor()/SetAntialiasMode(). All
127   //    gfxContext SetXXXX public functions belong to this category, except
128   //    gfxContext::SetPath & gfxContext::SetMatrix.
129   // 2. Clip functions, such as Clip() or PopClip(). You may call PopClip()
130   //    directly instead of using gfxContext::Save if the clip region is the
131   //    only thing that you altered in the target context.
132   // 3. Function of setup transform matrix, such as Multiply() and
133   //    SetMatrix(). Using gfxContextMatrixAutoSaveRestore is more recommended
134   //    if transform data is the only thing that you are going to alter.
135   //
136   // You will hit the assertion message below if there is no above functions
137   // been used between a pair of gfxContext::Save and gfxContext::Restore.
138   // Considerate to remove that pair of Save/Restore if hitting that assertion.
139   //
140   // In the other hand, the following APIs do not alter the content of the
141   // current AzureState, therefore, there is no need to save & restore
142   // AzureState:
143   // 1. constant member functions of gfxContext.
144   // 2. Paint calls, such as Line()/Rectangle()/Fill(). Those APIs change the
145   //    content of drawing buffer, which is not part of AzureState.
146   // 3. Path building APIs, such as SetPath()/MoveTo()/LineTo()/NewPath().
147   //    Surprisingly, path information is not stored in AzureState either.
148   // Save current AzureState before using these type of APIs does nothing but
149   // make performance worse.
150   NS_ASSERTION(
151       CurrentState().mContentChanged || CurrentState().pushedClips.Length() > 0,
152       "The context of the current AzureState is not altered after "
153       "Save() been called. you may consider to remove this pair of "
154       "gfxContext::Save/Restore.");
155 #endif
156 
157   for (unsigned int c = 0; c < CurrentState().pushedClips.Length(); c++) {
158     mDT->PopClip();
159   }
160 
161   mStateStack.RemoveElementAt(mStateStack.Length() - 1);
162 
163   mDT = CurrentState().drawTarget;
164 
165   ChangeTransform(CurrentState().transform, false);
166 }
167 
168 // drawing
NewPath()169 void gfxContext::NewPath() {
170   mPath = nullptr;
171   mPathBuilder = nullptr;
172   mPathIsRect = false;
173   mTransformChanged = false;
174 }
175 
ClosePath()176 void gfxContext::ClosePath() {
177   EnsurePathBuilder();
178   mPathBuilder->Close();
179 }
180 
GetPath()181 already_AddRefed<Path> gfxContext::GetPath() {
182   EnsurePath();
183   RefPtr<Path> path(mPath);
184   return path.forget();
185 }
186 
SetPath(Path * path)187 void gfxContext::SetPath(Path* path) {
188   MOZ_ASSERT(path->GetBackendType() == mDT->GetBackendType() ||
189              path->GetBackendType() == BackendType::RECORDING ||
190              (mDT->GetBackendType() == BackendType::DIRECT2D1_1 &&
191               path->GetBackendType() == BackendType::DIRECT2D));
192   mPath = path;
193   mPathBuilder = nullptr;
194   mPathIsRect = false;
195   mTransformChanged = false;
196 }
197 
CurrentPoint()198 gfxPoint gfxContext::CurrentPoint() {
199   EnsurePathBuilder();
200   return ThebesPoint(mPathBuilder->CurrentPoint());
201 }
202 
Fill()203 void gfxContext::Fill() { Fill(PatternFromState(this)); }
204 
Fill(const Pattern & aPattern)205 void gfxContext::Fill(const Pattern& aPattern) {
206   AUTO_PROFILER_LABEL("gfxContext::Fill", GRAPHICS);
207   AzureState& state = CurrentState();
208 
209   CompositionOp op = GetOp();
210 
211   if (mPathIsRect) {
212     MOZ_ASSERT(!mTransformChanged);
213 
214     if (op == CompositionOp::OP_SOURCE) {
215       // Emulate cairo operator source which is bound by mask!
216       mDT->ClearRect(mRect);
217       mDT->FillRect(mRect, aPattern, DrawOptions(1.0f));
218     } else {
219       mDT->FillRect(mRect, aPattern, DrawOptions(1.0f, op, state.aaMode));
220     }
221   } else {
222     EnsurePath();
223     mDT->Fill(mPath, aPattern, DrawOptions(1.0f, op, state.aaMode));
224   }
225 }
226 
MoveTo(const gfxPoint & pt)227 void gfxContext::MoveTo(const gfxPoint& pt) {
228   EnsurePathBuilder();
229   mPathBuilder->MoveTo(ToPoint(pt));
230 }
231 
LineTo(const gfxPoint & pt)232 void gfxContext::LineTo(const gfxPoint& pt) {
233   EnsurePathBuilder();
234   mPathBuilder->LineTo(ToPoint(pt));
235 }
236 
Line(const gfxPoint & start,const gfxPoint & end)237 void gfxContext::Line(const gfxPoint& start, const gfxPoint& end) {
238   EnsurePathBuilder();
239   mPathBuilder->MoveTo(ToPoint(start));
240   mPathBuilder->LineTo(ToPoint(end));
241 }
242 
243 // XXX snapToPixels is only valid when snapping for filled
244 // rectangles and for even-width stroked rectangles.
245 // For odd-width stroked rectangles, we need to offset x/y by
246 // 0.5...
Rectangle(const gfxRect & rect,bool snapToPixels)247 void gfxContext::Rectangle(const gfxRect& rect, bool snapToPixels) {
248   Rect rec = ToRect(rect);
249 
250   if (snapToPixels) {
251     gfxRect newRect(rect);
252     if (UserToDevicePixelSnapped(newRect, true)) {
253       gfxMatrix mat = ThebesMatrix(mTransform);
254       if (mat.Invert()) {
255         // We need the user space rect.
256         rec = ToRect(mat.TransformBounds(newRect));
257       } else {
258         rec = Rect();
259       }
260     }
261   }
262 
263   if (!mPathBuilder && !mPathIsRect) {
264     mPathIsRect = true;
265     mRect = rec;
266     return;
267   }
268 
269   EnsurePathBuilder();
270 
271   mPathBuilder->MoveTo(rec.TopLeft());
272   mPathBuilder->LineTo(rec.TopRight());
273   mPathBuilder->LineTo(rec.BottomRight());
274   mPathBuilder->LineTo(rec.BottomLeft());
275   mPathBuilder->Close();
276 }
277 
278 // transform stuff
Multiply(const gfxMatrix & matrix)279 void gfxContext::Multiply(const gfxMatrix& matrix) {
280   CURRENTSTATE_CHANGED()
281   ChangeTransform(ToMatrix(matrix) * mTransform);
282 }
283 
SetMatrix(const gfx::Matrix & matrix)284 void gfxContext::SetMatrix(const gfx::Matrix& matrix) {
285   CURRENTSTATE_CHANGED()
286   ChangeTransform(matrix);
287 }
288 
SetMatrixDouble(const gfxMatrix & matrix)289 void gfxContext::SetMatrixDouble(const gfxMatrix& matrix) {
290   SetMatrix(ToMatrix(matrix));
291 }
292 
CurrentMatrix() const293 gfx::Matrix gfxContext::CurrentMatrix() const { return mTransform; }
294 
CurrentMatrixDouble() const295 gfxMatrix gfxContext::CurrentMatrixDouble() const {
296   return ThebesMatrix(CurrentMatrix());
297 }
298 
DeviceToUser(const gfxPoint & point) const299 gfxPoint gfxContext::DeviceToUser(const gfxPoint& point) const {
300   return ThebesPoint(mTransform.Inverse().TransformPoint(ToPoint(point)));
301 }
302 
DeviceToUser(const Size & size) const303 Size gfxContext::DeviceToUser(const Size& size) const {
304   return mTransform.Inverse().TransformSize(size);
305 }
306 
DeviceToUser(const gfxRect & rect) const307 gfxRect gfxContext::DeviceToUser(const gfxRect& rect) const {
308   return ThebesRect(mTransform.Inverse().TransformBounds(ToRect(rect)));
309 }
310 
UserToDevice(const gfxPoint & point) const311 gfxPoint gfxContext::UserToDevice(const gfxPoint& point) const {
312   return ThebesPoint(mTransform.TransformPoint(ToPoint(point)));
313 }
314 
UserToDevice(const Size & size) const315 Size gfxContext::UserToDevice(const Size& size) const {
316   const Matrix& matrix = mTransform;
317 
318   Size newSize;
319   newSize.width = size.width * matrix._11 + size.height * matrix._12;
320   newSize.height = size.width * matrix._21 + size.height * matrix._22;
321   return newSize;
322 }
323 
UserToDevice(const gfxRect & rect) const324 gfxRect gfxContext::UserToDevice(const gfxRect& rect) const {
325   const Matrix& matrix = mTransform;
326   return ThebesRect(matrix.TransformBounds(ToRect(rect)));
327 }
328 
UserToDevicePixelSnapped(gfxRect & rect,bool ignoreScale) const329 bool gfxContext::UserToDevicePixelSnapped(gfxRect& rect,
330                                           bool ignoreScale) const {
331   if (mDT->GetUserData(&sDisablePixelSnapping)) return false;
332 
333   // if we're not at 1.0 scale, don't snap, unless we're
334   // ignoring the scale.  If we're not -just- a scale,
335   // never snap.
336   const gfxFloat epsilon = 0.0000001;
337 #define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon)
338   Matrix mat = mTransform;
339   if (!ignoreScale && (!WITHIN_E(mat._11, 1.0) || !WITHIN_E(mat._22, 1.0) ||
340                        !WITHIN_E(mat._12, 0.0) || !WITHIN_E(mat._21, 0.0)))
341     return false;
342 #undef WITHIN_E
343 
344   gfxPoint p1 = UserToDevice(rect.TopLeft());
345   gfxPoint p2 = UserToDevice(rect.TopRight());
346   gfxPoint p3 = UserToDevice(rect.BottomRight());
347 
348   // Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
349   // two opposite corners define the entire rectangle. So check if
350   // the axis-aligned rectangle with opposite corners p1 and p3
351   // define an axis-aligned rectangle whose other corners are p2 and p4.
352   // We actually only need to check one of p2 and p4, since an affine
353   // transform maps parallelograms to parallelograms.
354   if (p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y)) {
355     p1.Round();
356     p3.Round();
357 
358     rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y)));
359     rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(),
360                         std::max(p1.y, p3.y) - rect.Y()));
361     return true;
362   }
363 
364   return false;
365 }
366 
UserToDevicePixelSnapped(gfxPoint & pt,bool ignoreScale) const367 bool gfxContext::UserToDevicePixelSnapped(gfxPoint& pt,
368                                           bool ignoreScale) const {
369   if (mDT->GetUserData(&sDisablePixelSnapping)) return false;
370 
371   // if we're not at 1.0 scale, don't snap, unless we're
372   // ignoring the scale.  If we're not -just- a scale,
373   // never snap.
374   const gfxFloat epsilon = 0.0000001;
375 #define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon)
376   Matrix mat = mTransform;
377   if (!ignoreScale && (!WITHIN_E(mat._11, 1.0) || !WITHIN_E(mat._22, 1.0) ||
378                        !WITHIN_E(mat._12, 0.0) || !WITHIN_E(mat._21, 0.0)))
379     return false;
380 #undef WITHIN_E
381 
382   pt = UserToDevice(pt);
383   pt.Round();
384   return true;
385 }
386 
SetAntialiasMode(AntialiasMode mode)387 void gfxContext::SetAntialiasMode(AntialiasMode mode) {
388   CURRENTSTATE_CHANGED()
389   CurrentState().aaMode = mode;
390 }
391 
CurrentAntialiasMode() const392 AntialiasMode gfxContext::CurrentAntialiasMode() const {
393   return CurrentState().aaMode;
394 }
395 
SetDash(const Float * dashes,int ndash,Float offset)396 void gfxContext::SetDash(const Float* dashes, int ndash, Float offset) {
397   CURRENTSTATE_CHANGED()
398   AzureState& state = CurrentState();
399 
400   state.dashPattern.SetLength(ndash);
401   for (int i = 0; i < ndash; i++) {
402     state.dashPattern[i] = dashes[i];
403   }
404   state.strokeOptions.mDashLength = ndash;
405   state.strokeOptions.mDashOffset = offset;
406   state.strokeOptions.mDashPattern =
407       ndash ? state.dashPattern.Elements() : nullptr;
408 }
409 
CurrentDash(FallibleTArray<Float> & dashes,Float * offset) const410 bool gfxContext::CurrentDash(FallibleTArray<Float>& dashes,
411                              Float* offset) const {
412   const AzureState& state = CurrentState();
413   int count = state.strokeOptions.mDashLength;
414 
415   if (count <= 0 || !dashes.SetLength(count, fallible)) {
416     return false;
417   }
418 
419   dashes = state.dashPattern;
420 
421   *offset = state.strokeOptions.mDashOffset;
422 
423   return true;
424 }
425 
CurrentDashOffset() const426 Float gfxContext::CurrentDashOffset() const {
427   return CurrentState().strokeOptions.mDashOffset;
428 }
429 
SetLineWidth(Float width)430 void gfxContext::SetLineWidth(Float width) {
431   CurrentState().strokeOptions.mLineWidth = width;
432 }
433 
CurrentLineWidth() const434 Float gfxContext::CurrentLineWidth() const {
435   return CurrentState().strokeOptions.mLineWidth;
436 }
437 
SetOp(CompositionOp aOp)438 void gfxContext::SetOp(CompositionOp aOp) {
439   CURRENTSTATE_CHANGED()
440   CurrentState().op = aOp;
441 }
442 
CurrentOp() const443 CompositionOp gfxContext::CurrentOp() const { return CurrentState().op; }
444 
SetLineCap(CapStyle cap)445 void gfxContext::SetLineCap(CapStyle cap) {
446   CURRENTSTATE_CHANGED()
447   CurrentState().strokeOptions.mLineCap = cap;
448 }
449 
CurrentLineCap() const450 CapStyle gfxContext::CurrentLineCap() const {
451   return CurrentState().strokeOptions.mLineCap;
452 }
453 
SetLineJoin(JoinStyle join)454 void gfxContext::SetLineJoin(JoinStyle join) {
455   CURRENTSTATE_CHANGED()
456   CurrentState().strokeOptions.mLineJoin = join;
457 }
458 
CurrentLineJoin() const459 JoinStyle gfxContext::CurrentLineJoin() const {
460   return CurrentState().strokeOptions.mLineJoin;
461 }
462 
SetMiterLimit(Float limit)463 void gfxContext::SetMiterLimit(Float limit) {
464   CURRENTSTATE_CHANGED()
465   CurrentState().strokeOptions.mMiterLimit = limit;
466 }
467 
CurrentMiterLimit() const468 Float gfxContext::CurrentMiterLimit() const {
469   return CurrentState().strokeOptions.mMiterLimit;
470 }
471 
472 // clipping
Clip(const Rect & rect)473 void gfxContext::Clip(const Rect& rect) {
474   AzureState::PushedClip clip = {nullptr, rect, mTransform};
475   CurrentState().pushedClips.AppendElement(clip);
476   mDT->PushClipRect(rect);
477   NewPath();
478 }
479 
Clip(const gfxRect & rect)480 void gfxContext::Clip(const gfxRect& rect) { Clip(ToRect(rect)); }
481 
Clip(Path * aPath)482 void gfxContext::Clip(Path* aPath) {
483   mDT->PushClip(aPath);
484   AzureState::PushedClip clip = {aPath, Rect(), mTransform};
485   CurrentState().pushedClips.AppendElement(clip);
486 }
487 
Clip()488 void gfxContext::Clip() {
489   if (mPathIsRect) {
490     MOZ_ASSERT(!mTransformChanged);
491 
492     AzureState::PushedClip clip = {nullptr, mRect, mTransform};
493     CurrentState().pushedClips.AppendElement(clip);
494     mDT->PushClipRect(mRect);
495   } else {
496     EnsurePath();
497     mDT->PushClip(mPath);
498     AzureState::PushedClip clip = {mPath, Rect(), mTransform};
499     CurrentState().pushedClips.AppendElement(clip);
500   }
501 }
502 
PopClip()503 void gfxContext::PopClip() {
504   MOZ_ASSERT(CurrentState().pushedClips.Length() > 0);
505 
506   CurrentState().pushedClips.RemoveElementAt(
507       CurrentState().pushedClips.Length() - 1);
508   mDT->PopClip();
509 }
510 
GetClipExtents(ClipExtentsSpace aSpace) const511 gfxRect gfxContext::GetClipExtents(ClipExtentsSpace aSpace) const {
512   Rect rect = GetAzureDeviceSpaceClipBounds();
513 
514   if (rect.IsZeroArea()) {
515     return gfxRect(0, 0, 0, 0);
516   }
517 
518   if (aSpace == eUserSpace) {
519     Matrix mat = mTransform;
520     mat.Invert();
521     rect = mat.TransformBounds(rect);
522   }
523 
524   return ThebesRect(rect);
525 }
526 
ExportClip(ClipExporter & aExporter)527 bool gfxContext::ExportClip(ClipExporter& aExporter) {
528   for (unsigned int i = 0; i < mStateStack.Length(); i++) {
529     for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
530       AzureState::PushedClip& clip = mStateStack[i].pushedClips[c];
531       gfx::Matrix transform = clip.transform;
532       transform.PostTranslate(-GetDeviceOffset());
533 
534       aExporter.BeginClip(transform);
535       if (clip.path) {
536         clip.path->StreamToSink(&aExporter);
537       } else {
538         aExporter.MoveTo(clip.rect.TopLeft());
539         aExporter.LineTo(clip.rect.TopRight());
540         aExporter.LineTo(clip.rect.BottomRight());
541         aExporter.LineTo(clip.rect.BottomLeft());
542         aExporter.Close();
543       }
544       aExporter.EndClip();
545     }
546   }
547 
548   return true;
549 }
550 
ClipContainsRect(const gfxRect & aRect)551 bool gfxContext::ClipContainsRect(const gfxRect& aRect) {
552   // Since we always return false when the clip list contains a
553   // non-rectangular clip or a non-rectilinear transform, our 'total' clip
554   // is always a rectangle if we hit the end of this function.
555   Rect clipBounds(0, 0, Float(mDT->GetSize().width),
556                   Float(mDT->GetSize().height));
557 
558   for (unsigned int i = 0; i < mStateStack.Length(); i++) {
559     for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
560       AzureState::PushedClip& clip = mStateStack[i].pushedClips[c];
561       if (clip.path || !clip.transform.IsRectilinear()) {
562         // Cairo behavior is we return false if the clip contains a non-
563         // rectangle.
564         return false;
565       } else {
566         Rect clipRect = mTransform.TransformBounds(clip.rect);
567 
568         clipBounds.IntersectRect(clipBounds, clipRect);
569       }
570     }
571   }
572 
573   return clipBounds.Contains(ToRect(aRect));
574 }
575 
576 // rendering sources
577 
SetColor(const Color & aColor)578 void gfxContext::SetColor(const Color& aColor) {
579   CURRENTSTATE_CHANGED()
580   CurrentState().pattern = nullptr;
581   CurrentState().color = ToDeviceColor(aColor);
582 }
583 
SetDeviceColor(const Color & aColor)584 void gfxContext::SetDeviceColor(const Color& aColor) {
585   CURRENTSTATE_CHANGED()
586   CurrentState().pattern = nullptr;
587   CurrentState().color = aColor;
588 }
589 
GetDeviceColor(Color & aColorOut)590 bool gfxContext::GetDeviceColor(Color& aColorOut) {
591   if (CurrentState().pattern) {
592     return CurrentState().pattern->GetSolidColor(aColorOut);
593   }
594 
595   aColorOut = CurrentState().color;
596   return true;
597 }
598 
SetPattern(gfxPattern * pattern)599 void gfxContext::SetPattern(gfxPattern* pattern) {
600   CURRENTSTATE_CHANGED()
601   CurrentState().patternTransformChanged = false;
602   CurrentState().pattern = pattern;
603 }
604 
GetPattern()605 already_AddRefed<gfxPattern> gfxContext::GetPattern() {
606   RefPtr<gfxPattern> pat;
607 
608   AzureState& state = CurrentState();
609   if (state.pattern) {
610     pat = state.pattern;
611   } else {
612     pat = new gfxPattern(state.color);
613   }
614   return pat.forget();
615 }
616 
617 // masking
Mask(SourceSurface * aSurface,Float aAlpha,const Matrix & aTransform)618 void gfxContext::Mask(SourceSurface* aSurface, Float aAlpha,
619                       const Matrix& aTransform) {
620   Matrix old = mTransform;
621   Matrix mat = aTransform * mTransform;
622 
623   ChangeTransform(mat);
624   mDT->MaskSurface(
625       PatternFromState(this), aSurface, Point(),
626       DrawOptions(aAlpha, CurrentState().op, CurrentState().aaMode));
627   ChangeTransform(old);
628 }
629 
Mask(SourceSurface * surface,float alpha,const Point & offset)630 void gfxContext::Mask(SourceSurface* surface, float alpha,
631                       const Point& offset) {
632   // We clip here to bind to the mask surface bounds, see above.
633   mDT->MaskSurface(
634       PatternFromState(this), surface, offset,
635       DrawOptions(alpha, CurrentState().op, CurrentState().aaMode));
636 }
637 
Paint(Float alpha)638 void gfxContext::Paint(Float alpha) {
639   AUTO_PROFILER_LABEL("gfxContext::Paint", GRAPHICS);
640 
641   Matrix mat = mDT->GetTransform();
642   mat.Invert();
643   Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize())));
644 
645   mDT->FillRect(paintRect, PatternFromState(this), DrawOptions(alpha, GetOp()));
646 }
647 
PushGroupForBlendBack(gfxContentType content,Float aOpacity,SourceSurface * aMask,const Matrix & aMaskTransform)648 void gfxContext::PushGroupForBlendBack(gfxContentType content, Float aOpacity,
649                                        SourceSurface* aMask,
650                                        const Matrix& aMaskTransform) {
651   mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask,
652                  aMaskTransform);
653 }
654 
PushGroupAndCopyBackground(gfxContentType content,Float aOpacity,SourceSurface * aMask,const Matrix & aMaskTransform)655 void gfxContext::PushGroupAndCopyBackground(gfxContentType content,
656                                             Float aOpacity,
657                                             SourceSurface* aMask,
658                                             const Matrix& aMaskTransform) {
659   IntRect clipExtents;
660   if (mDT->GetFormat() != SurfaceFormat::B8G8R8X8) {
661     gfxRect clipRect = GetClipExtents(gfxContext::eDeviceSpace);
662     clipRect.RoundOut();
663     clipExtents = IntRect::Truncate(clipRect.X(), clipRect.Y(),
664                                     clipRect.Width(), clipRect.Height());
665   }
666   bool pushOpaqueWithCopiedBG = (mDT->GetFormat() == SurfaceFormat::B8G8R8X8 ||
667                                  mDT->GetOpaqueRect().Contains(clipExtents)) &&
668                                 !mDT->GetUserData(&sDontUseAsSourceKey);
669 
670   if (pushOpaqueWithCopiedBG) {
671     mDT->PushLayer(true, aOpacity, aMask, aMaskTransform, IntRect(), true);
672   } else {
673     mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask,
674                    aMaskTransform, IntRect(), false);
675   }
676 }
677 
PopGroupAndBlend()678 void gfxContext::PopGroupAndBlend() { mDT->PopLayer(); }
679 
680 #ifdef MOZ_DUMP_PAINTING
WriteAsPNG(const char * aFile)681 void gfxContext::WriteAsPNG(const char* aFile) {
682   gfxUtils::WriteAsPNG(mDT, aFile);
683 }
684 
DumpAsDataURI()685 void gfxContext::DumpAsDataURI() { gfxUtils::DumpAsDataURI(mDT); }
686 
CopyAsDataURI()687 void gfxContext::CopyAsDataURI() { gfxUtils::CopyAsDataURI(mDT); }
688 #endif
689 
EnsurePath()690 void gfxContext::EnsurePath() {
691   if (mPathBuilder) {
692     mPath = mPathBuilder->Finish();
693     mPathBuilder = nullptr;
694   }
695 
696   if (mPath) {
697     if (mTransformChanged) {
698       Matrix mat = mTransform;
699       mat.Invert();
700       mat = mPathTransform * mat;
701       mPathBuilder = mPath->TransformedCopyToBuilder(mat);
702       mPath = mPathBuilder->Finish();
703       mPathBuilder = nullptr;
704 
705       mTransformChanged = false;
706     }
707     return;
708   }
709 
710   EnsurePathBuilder();
711   mPath = mPathBuilder->Finish();
712   mPathBuilder = nullptr;
713 }
714 
EnsurePathBuilder()715 void gfxContext::EnsurePathBuilder() {
716   if (mPathBuilder && !mTransformChanged) {
717     return;
718   }
719 
720   if (mPath) {
721     if (!mTransformChanged) {
722       mPathBuilder = mPath->CopyToBuilder();
723       mPath = nullptr;
724     } else {
725       Matrix invTransform = mTransform;
726       invTransform.Invert();
727       Matrix toNewUS = mPathTransform * invTransform;
728       mPathBuilder = mPath->TransformedCopyToBuilder(toNewUS);
729     }
730     return;
731   }
732 
733   DebugOnly<PathBuilder*> oldPath = mPathBuilder.get();
734 
735   if (!mPathBuilder) {
736     mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING);
737 
738     if (mPathIsRect) {
739       mPathBuilder->MoveTo(mRect.TopLeft());
740       mPathBuilder->LineTo(mRect.TopRight());
741       mPathBuilder->LineTo(mRect.BottomRight());
742       mPathBuilder->LineTo(mRect.BottomLeft());
743       mPathBuilder->Close();
744     }
745   }
746 
747   if (mTransformChanged) {
748     // This could be an else if since this should never happen when
749     // mPathBuilder is nullptr and mPath is nullptr. But this way we can
750     // assert if all the state is as expected.
751     MOZ_ASSERT(oldPath);
752     MOZ_ASSERT(!mPathIsRect);
753 
754     Matrix invTransform = mTransform;
755     invTransform.Invert();
756     Matrix toNewUS = mPathTransform * invTransform;
757 
758     RefPtr<Path> path = mPathBuilder->Finish();
759     if (!path) {
760       gfxCriticalError()
761           << "gfxContext::EnsurePathBuilder failed in PathBuilder::Finish";
762     }
763     mPathBuilder = path->TransformedCopyToBuilder(toNewUS);
764   }
765 
766   mPathIsRect = false;
767 }
768 
GetOp()769 CompositionOp gfxContext::GetOp() {
770   if (CurrentState().op != CompositionOp::OP_SOURCE) {
771     return CurrentState().op;
772   }
773 
774   AzureState& state = CurrentState();
775   if (state.pattern) {
776     if (state.pattern->IsOpaque()) {
777       return CompositionOp::OP_OVER;
778     } else {
779       return CompositionOp::OP_SOURCE;
780     }
781   } else {
782     if (state.color.a > 0.999) {
783       return CompositionOp::OP_OVER;
784     } else {
785       return CompositionOp::OP_SOURCE;
786     }
787   }
788 }
789 
790 /* SVG font code can change the transform after having set the pattern on the
791  * context. When the pattern is set it is in user space, if the transform is
792  * changed after doing so the pattern needs to be converted back into userspace.
793  * We just store the old pattern transform here so that we only do the work
794  * needed here if the pattern is actually used.
795  * We need to avoid doing this when this ChangeTransform comes from a restore,
796  * since the current pattern and the current transform are both part of the
797  * state we know the new CurrentState()'s values are valid. But if we assume
798  * a change they might become invalid since patternTransformChanged is part of
799  * the state and might be false for the restored AzureState.
800  */
ChangeTransform(const Matrix & aNewMatrix,bool aUpdatePatternTransform)801 void gfxContext::ChangeTransform(const Matrix& aNewMatrix,
802                                  bool aUpdatePatternTransform) {
803   AzureState& state = CurrentState();
804 
805   if (aUpdatePatternTransform && (state.pattern) &&
806       !state.patternTransformChanged) {
807     state.patternTransform = GetDTTransform();
808     state.patternTransformChanged = true;
809   }
810 
811   if (mPathIsRect) {
812     Matrix invMatrix = aNewMatrix;
813 
814     invMatrix.Invert();
815 
816     Matrix toNewUS = mTransform * invMatrix;
817 
818     if (toNewUS.IsRectilinear()) {
819       mRect = toNewUS.TransformBounds(mRect);
820       mRect.NudgeToIntegers();
821     } else {
822       mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING);
823 
824       mPathBuilder->MoveTo(toNewUS.TransformPoint(mRect.TopLeft()));
825       mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.TopRight()));
826       mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomRight()));
827       mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomLeft()));
828       mPathBuilder->Close();
829 
830       mPathIsRect = false;
831     }
832 
833     // No need to consider the transform changed now!
834     mTransformChanged = false;
835   } else if ((mPath || mPathBuilder) && !mTransformChanged) {
836     mTransformChanged = true;
837     mPathTransform = mTransform;
838   }
839 
840   mTransform = aNewMatrix;
841 
842   mDT->SetTransform(GetDTTransform());
843 }
844 
GetAzureDeviceSpaceClipBounds() const845 Rect gfxContext::GetAzureDeviceSpaceClipBounds() const {
846   Rect rect(CurrentState().deviceOffset.x, CurrentState().deviceOffset.y,
847             Float(mDT->GetSize().width), Float(mDT->GetSize().height));
848   for (unsigned int i = 0; i < mStateStack.Length(); i++) {
849     for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
850       const AzureState::PushedClip& clip = mStateStack[i].pushedClips[c];
851       if (clip.path) {
852         Rect bounds = clip.path->GetBounds(clip.transform);
853         rect.IntersectRect(rect, bounds);
854       } else {
855         rect.IntersectRect(rect, clip.transform.TransformBounds(clip.rect));
856       }
857     }
858   }
859 
860   return rect;
861 }
862 
GetDeviceOffset() const863 Point gfxContext::GetDeviceOffset() const {
864   return CurrentState().deviceOffset;
865 }
866 
GetDeviceTransform() const867 Matrix gfxContext::GetDeviceTransform() const {
868   return Matrix::Translation(-CurrentState().deviceOffset.x,
869                              -CurrentState().deviceOffset.y);
870 }
871 
GetDTTransform() const872 Matrix gfxContext::GetDTTransform() const {
873   Matrix mat = mTransform;
874   mat._31 -= CurrentState().deviceOffset.x;
875   mat._32 -= CurrentState().deviceOffset.y;
876   return mat;
877 }
878