Bug 1874684 - Part 28: Return DateDuration from DifferenceISODateTime. r=mgaudet
[gecko.git] / gfx / thebes / gfxContext.cpp
blob6f097ce49f058e2711939df1ab50bf96aa9ff6b0
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/. */
7 #include <math.h>
9 #include "mozilla/Alignment.h"
11 #include "cairo.h"
13 #include "gfxContext.h"
15 #include "gfxMatrix.h"
16 #include "gfxUtils.h"
17 #include "gfxPattern.h"
18 #include "gfxPlatform.h"
20 #include "gfx2DGlue.h"
21 #include "mozilla/gfx/PathHelpers.h"
22 #include "mozilla/ProfilerLabels.h"
23 #include <algorithm>
24 #include "TextDrawTarget.h"
26 #if XP_WIN
27 # include "gfxWindowsPlatform.h"
28 # include "mozilla/gfx/DeviceManagerDx.h"
29 #endif
31 using namespace mozilla;
32 using namespace mozilla::gfx;
34 #ifdef DEBUG
35 # define CURRENTSTATE_CHANGED() mAzureState.mContentChanged = true;
36 #else
37 # define CURRENTSTATE_CHANGED()
38 #endif
40 PatternFromState::operator Pattern&() {
41 const gfxContext::AzureState& state = mContext->mAzureState;
43 if (state.pattern) {
44 return *state.pattern->GetPattern(
45 mContext->mDT,
46 state.patternTransformChanged ? &state.patternTransform : nullptr);
49 mPattern = new (mColorPattern.addr()) ColorPattern(state.color);
50 return *mPattern;
53 /* static */
54 UniquePtr<gfxContext> gfxContext::CreateOrNull(DrawTarget* aTarget) {
55 if (!aTarget || !aTarget->IsValid()) {
56 gfxCriticalNote << "Invalid target in gfxContext::CreateOrNull "
57 << hexa(aTarget);
58 return nullptr;
61 return MakeUnique<gfxContext>(aTarget);
64 gfxContext::~gfxContext() {
65 while (!mSavedStates.IsEmpty()) {
66 Restore();
68 for (unsigned int c = 0; c < mAzureState.pushedClips.Length(); c++) {
69 mDT->PopClip();
73 mozilla::layout::TextDrawTarget* gfxContext::GetTextDrawer() const {
74 if (mDT->GetBackendType() == BackendType::WEBRENDER_TEXT) {
75 return static_cast<mozilla::layout::TextDrawTarget*>(&*mDT);
77 return nullptr;
80 void gfxContext::Save() {
81 mSavedStates.AppendElement(mAzureState);
82 mAzureState.pushedClips.Clear();
83 #ifdef DEBUG
84 mAzureState.mContentChanged = false;
85 #endif
88 void gfxContext::Restore() {
89 #ifdef DEBUG
90 // gfxContext::Restore is used to restore AzureState. We need to restore it
91 // only if it was altered. The following APIs do change the content of
92 // AzureState, a user should save the state before using them and restore it
93 // after finishing painting:
94 // 1. APIs to setup how to paint, such as SetColor()/SetAntialiasMode(). All
95 // gfxContext SetXXXX public functions belong to this category, except
96 // gfxContext::SetPath & gfxContext::SetMatrix.
97 // 2. Clip functions, such as Clip() or PopClip(). You may call PopClip()
98 // directly instead of using gfxContext::Save if the clip region is the
99 // only thing that you altered in the target context.
100 // 3. Function of setup transform matrix, such as Multiply() and
101 // SetMatrix(). Using gfxContextMatrixAutoSaveRestore is more recommended
102 // if transform data is the only thing that you are going to alter.
104 // You will hit the assertion message below if there is no above functions
105 // been used between a pair of gfxContext::Save and gfxContext::Restore.
106 // Considerate to remove that pair of Save/Restore if hitting that assertion.
108 // In the other hand, the following APIs do not alter the content of the
109 // current AzureState, therefore, there is no need to save & restore
110 // AzureState:
111 // 1. constant member functions of gfxContext.
112 // 2. Paint calls, such as Line()/Rectangle()/Fill(). Those APIs change the
113 // content of drawing buffer, which is not part of AzureState.
114 // 3. Path building APIs, such as SetPath()/MoveTo()/LineTo()/NewPath().
115 // Surprisingly, path information is not stored in AzureState either.
116 // Save current AzureState before using these type of APIs does nothing but
117 // make performance worse.
118 NS_ASSERTION(
119 mAzureState.mContentChanged || mAzureState.pushedClips.Length() > 0,
120 "The context of the current AzureState is not altered after "
121 "Save() been called. you may consider to remove this pair of "
122 "gfxContext::Save/Restore.");
123 #endif
125 for (unsigned int c = 0; c < mAzureState.pushedClips.Length(); c++) {
126 mDT->PopClip();
129 mAzureState = mSavedStates.PopLastElement();
131 ChangeTransform(mAzureState.transform, false);
134 // drawing
136 void gfxContext::Fill(const Pattern& aPattern) {
137 AUTO_PROFILER_LABEL("gfxContext::Fill", GRAPHICS);
139 CompositionOp op = GetOp();
141 if (mPathIsRect) {
142 MOZ_ASSERT(!mTransformChanged);
144 if (op == CompositionOp::OP_SOURCE) {
145 // Emulate cairo operator source which is bound by mask!
146 mDT->ClearRect(mRect);
147 mDT->FillRect(mRect, aPattern, DrawOptions(1.0f));
148 } else {
149 mDT->FillRect(mRect, aPattern, DrawOptions(1.0f, op, mAzureState.aaMode));
151 } else {
152 EnsurePath();
153 mDT->Fill(mPath, aPattern, DrawOptions(1.0f, op, mAzureState.aaMode));
157 // XXX snapToPixels is only valid when snapping for filled
158 // rectangles and for even-width stroked rectangles.
159 // For odd-width stroked rectangles, we need to offset x/y by
160 // 0.5...
161 void gfxContext::Rectangle(const gfxRect& rect, bool snapToPixels) {
162 Rect rec = ToRect(rect);
164 if (snapToPixels) {
165 gfxRect newRect(rect);
166 if (UserToDevicePixelSnapped(newRect, SnapOption::IgnoreScale)) {
167 gfxMatrix mat = CurrentMatrixDouble();
168 if (mat.Invert()) {
169 // We need the user space rect.
170 rec = ToRect(mat.TransformBounds(newRect));
171 } else {
172 rec = Rect();
177 if (!mPathBuilder && !mPathIsRect) {
178 mPathIsRect = true;
179 mRect = rec;
180 return;
183 EnsurePathBuilder();
185 mPathBuilder->MoveTo(rec.TopLeft());
186 mPathBuilder->LineTo(rec.TopRight());
187 mPathBuilder->LineTo(rec.BottomRight());
188 mPathBuilder->LineTo(rec.BottomLeft());
189 mPathBuilder->Close();
192 void gfxContext::SnappedClip(const gfxRect& rect) {
193 Rect rec = ToRect(rect);
195 gfxRect newRect(rect);
196 if (UserToDevicePixelSnapped(newRect, SnapOption::IgnoreScale)) {
197 gfxMatrix mat = CurrentMatrixDouble();
198 if (mat.Invert()) {
199 // We need the user space rect.
200 rec = ToRect(mat.TransformBounds(newRect));
201 } else {
202 rec = Rect();
206 Clip(rec);
209 bool gfxContext::UserToDevicePixelSnapped(gfxRect& rect,
210 SnapOptions aOptions) const {
211 if (mDT->GetUserData(&sDisablePixelSnapping)) {
212 return false;
215 // if we're not at 1.0 scale, don't snap, unless we're
216 // ignoring the scale. If we're not -just- a scale,
217 // never snap.
218 const gfxFloat epsilon = 0.0000001;
219 #define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon)
220 Matrix mat = mAzureState.transform;
221 if (!aOptions.contains(SnapOption::IgnoreScale) &&
222 (!WITHIN_E(mat._11, 1.0) || !WITHIN_E(mat._22, 1.0) ||
223 !WITHIN_E(mat._12, 0.0) || !WITHIN_E(mat._21, 0.0))) {
224 return false;
226 #undef WITHIN_E
228 gfxPoint p1 = UserToDevice(rect.TopLeft());
229 gfxPoint p2 = UserToDevice(rect.TopRight());
230 gfxPoint p3 = UserToDevice(rect.BottomRight());
232 // Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
233 // two opposite corners define the entire rectangle. So check if
234 // the axis-aligned rectangle with opposite corners p1 and p3
235 // define an axis-aligned rectangle whose other corners are p2 and p4.
236 // We actually only need to check one of p2 and p4, since an affine
237 // transform maps parallelograms to parallelograms.
238 if (!(p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y))) {
239 return false;
242 if (aOptions.contains(SnapOption::PrioritizeSize)) {
243 // Snap the dimensions of the rect, to minimize distortion; only after that
244 // will we snap its position. In particular, this guarantees that a square
245 // remains square after snapping, which may not be the case if each edge is
246 // independently snapped to device pixels.
248 // Use the same rounding approach as gfx::BasePoint::Round.
249 rect.SizeTo(std::floor(rect.width + 0.5), std::floor(rect.height + 0.5));
251 // Find the top-left corner based on the original center and the snapped
252 // size, then snap this new corner to the grid.
253 gfxPoint center = (p1 + p3) / 2;
254 gfxPoint topLeft = center - gfxPoint(rect.width / 2.0, rect.height / 2.0);
255 topLeft.Round();
256 rect.MoveTo(topLeft);
257 } else {
258 p1.Round();
259 p3.Round();
260 rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y)));
261 rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(),
262 std::max(p1.y, p3.y) - rect.Y()));
265 return true;
268 bool gfxContext::UserToDevicePixelSnapped(gfxPoint& pt,
269 bool ignoreScale) const {
270 if (mDT->GetUserData(&sDisablePixelSnapping)) {
271 return false;
274 // if we're not at 1.0 scale, don't snap, unless we're
275 // ignoring the scale. If we're not -just- a scale,
276 // never snap.
277 const gfxFloat epsilon = 0.0000001;
278 #define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon)
279 Matrix mat = mAzureState.transform;
280 if (!ignoreScale && (!WITHIN_E(mat._11, 1.0) || !WITHIN_E(mat._22, 1.0) ||
281 !WITHIN_E(mat._12, 0.0) || !WITHIN_E(mat._21, 0.0))) {
282 return false;
284 #undef WITHIN_E
286 pt = UserToDevice(pt);
287 pt.Round();
288 return true;
291 void gfxContext::SetDash(const Float* dashes, int ndash, Float offset,
292 Float devPxScale) {
293 CURRENTSTATE_CHANGED()
295 mAzureState.dashPattern.SetLength(ndash);
296 for (int i = 0; i < ndash; i++) {
297 mAzureState.dashPattern[i] = dashes[i] * devPxScale;
299 mAzureState.strokeOptions.mDashLength = ndash;
300 mAzureState.strokeOptions.mDashOffset = offset * devPxScale;
301 mAzureState.strokeOptions.mDashPattern =
302 ndash ? mAzureState.dashPattern.Elements() : nullptr;
305 bool gfxContext::CurrentDash(FallibleTArray<Float>& dashes,
306 Float* offset) const {
307 if (mAzureState.strokeOptions.mDashLength == 0 ||
308 !dashes.Assign(mAzureState.dashPattern, fallible)) {
309 return false;
312 *offset = mAzureState.strokeOptions.mDashOffset;
314 return true;
317 // clipping
318 void gfxContext::Clip(const Rect& rect) {
319 AzureState::PushedClip clip = {nullptr, rect, mAzureState.transform};
320 mAzureState.pushedClips.AppendElement(clip);
321 mDT->PushClipRect(rect);
322 NewPath();
325 void gfxContext::Clip(Path* aPath) {
326 mDT->PushClip(aPath);
327 AzureState::PushedClip clip = {aPath, Rect(), mAzureState.transform};
328 mAzureState.pushedClips.AppendElement(clip);
331 void gfxContext::Clip() {
332 if (mPathIsRect) {
333 MOZ_ASSERT(!mTransformChanged);
335 AzureState::PushedClip clip = {nullptr, mRect, mAzureState.transform};
336 mAzureState.pushedClips.AppendElement(clip);
337 mDT->PushClipRect(mRect);
338 } else {
339 EnsurePath();
340 mDT->PushClip(mPath);
341 AzureState::PushedClip clip = {mPath, Rect(), mAzureState.transform};
342 mAzureState.pushedClips.AppendElement(clip);
346 gfxRect gfxContext::GetClipExtents(ClipExtentsSpace aSpace) const {
347 Rect rect = GetAzureDeviceSpaceClipBounds();
349 if (rect.IsZeroArea()) {
350 return gfxRect(0, 0, 0, 0);
353 if (aSpace == eUserSpace) {
354 Matrix mat = mAzureState.transform;
355 mat.Invert();
356 rect = mat.TransformBounds(rect);
359 return ThebesRect(rect);
362 bool gfxContext::ExportClip(ClipExporter& aExporter) const {
363 ForAllClips([&](const AzureState::PushedClip& aClip) -> void {
364 gfx::Matrix transform = aClip.transform;
365 transform.PostTranslate(-GetDeviceOffset());
367 aExporter.BeginClip(transform);
368 if (aClip.path) {
369 aClip.path->StreamToSink(&aExporter);
370 } else {
371 aExporter.MoveTo(aClip.rect.TopLeft());
372 aExporter.LineTo(aClip.rect.TopRight());
373 aExporter.LineTo(aClip.rect.BottomRight());
374 aExporter.LineTo(aClip.rect.BottomLeft());
375 aExporter.Close();
377 aExporter.EndClip();
380 return true;
383 // rendering sources
385 bool gfxContext::GetDeviceColor(DeviceColor& aColorOut) const {
386 if (mAzureState.pattern) {
387 return mAzureState.pattern->GetSolidColor(aColorOut);
390 aColorOut = mAzureState.color;
391 return true;
394 already_AddRefed<gfxPattern> gfxContext::GetPattern() const {
395 RefPtr<gfxPattern> pat;
397 if (mAzureState.pattern) {
398 pat = mAzureState.pattern;
399 } else {
400 pat = new gfxPattern(mAzureState.color);
402 return pat.forget();
405 void gfxContext::Paint(Float alpha) const {
406 AUTO_PROFILER_LABEL("gfxContext::Paint", GRAPHICS);
408 Matrix mat = mDT->GetTransform();
409 mat.Invert();
410 Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize())));
412 mDT->FillRect(paintRect, PatternFromState(this), DrawOptions(alpha, GetOp()));
415 #ifdef MOZ_DUMP_PAINTING
416 void gfxContext::WriteAsPNG(const char* aFile) {
417 gfxUtils::WriteAsPNG(mDT, aFile);
420 void gfxContext::DumpAsDataURI() { gfxUtils::DumpAsDataURI(mDT); }
422 void gfxContext::CopyAsDataURI() { gfxUtils::CopyAsDataURI(mDT); }
423 #endif
425 void gfxContext::EnsurePath() {
426 if (mPathBuilder) {
427 mPath = mPathBuilder->Finish();
428 mPathBuilder = nullptr;
431 if (mPath) {
432 if (mTransformChanged) {
433 Matrix mat = mAzureState.transform;
434 mat.Invert();
435 mat = mPathTransform * mat;
436 mPathBuilder = mPath->TransformedCopyToBuilder(mat);
437 mPath = mPathBuilder->Finish();
438 mPathBuilder = nullptr;
440 mTransformChanged = false;
442 return;
445 EnsurePathBuilder();
446 mPath = mPathBuilder->Finish();
447 mPathBuilder = nullptr;
450 void gfxContext::EnsurePathBuilder() {
451 if (mPathBuilder && !mTransformChanged) {
452 return;
455 if (mPath) {
456 if (!mTransformChanged) {
457 mPathBuilder = mPath->CopyToBuilder();
458 mPath = nullptr;
459 } else {
460 Matrix invTransform = mAzureState.transform;
461 invTransform.Invert();
462 Matrix toNewUS = mPathTransform * invTransform;
463 mPathBuilder = mPath->TransformedCopyToBuilder(toNewUS);
465 return;
468 DebugOnly<PathBuilder*> oldPath = mPathBuilder.get();
470 if (!mPathBuilder) {
471 mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING);
473 if (mPathIsRect) {
474 mPathBuilder->MoveTo(mRect.TopLeft());
475 mPathBuilder->LineTo(mRect.TopRight());
476 mPathBuilder->LineTo(mRect.BottomRight());
477 mPathBuilder->LineTo(mRect.BottomLeft());
478 mPathBuilder->Close();
482 if (mTransformChanged) {
483 // This could be an else if since this should never happen when
484 // mPathBuilder is nullptr and mPath is nullptr. But this way we can
485 // assert if all the state is as expected.
486 MOZ_ASSERT(oldPath);
487 MOZ_ASSERT(!mPathIsRect);
489 Matrix invTransform = mAzureState.transform;
490 invTransform.Invert();
491 Matrix toNewUS = mPathTransform * invTransform;
493 RefPtr<Path> path = mPathBuilder->Finish();
494 if (!path) {
495 gfxCriticalError()
496 << "gfxContext::EnsurePathBuilder failed in PathBuilder::Finish";
498 mPathBuilder = path->TransformedCopyToBuilder(toNewUS);
501 mPathIsRect = false;
504 CompositionOp gfxContext::GetOp() const {
505 if (mAzureState.op != CompositionOp::OP_SOURCE) {
506 return mAzureState.op;
509 if (mAzureState.pattern) {
510 if (mAzureState.pattern->IsOpaque()) {
511 return CompositionOp::OP_OVER;
512 } else {
513 return CompositionOp::OP_SOURCE;
515 } else {
516 if (mAzureState.color.a > 0.999) {
517 return CompositionOp::OP_OVER;
518 } else {
519 return CompositionOp::OP_SOURCE;
524 /* SVG font code can change the transform after having set the pattern on the
525 * context. When the pattern is set it is in user space, if the transform is
526 * changed after doing so the pattern needs to be converted back into userspace.
527 * We just store the old pattern transform here so that we only do the work
528 * needed here if the pattern is actually used.
529 * We need to avoid doing this when this ChangeTransform comes from a restore,
530 * since the current pattern and the current transform are both part of the
531 * state we know the new mAzureState's values are valid. But if we assume
532 * a change they might become invalid since patternTransformChanged is part of
533 * the state and might be false for the restored AzureState.
535 void gfxContext::ChangeTransform(const Matrix& aNewMatrix,
536 bool aUpdatePatternTransform) {
537 if (aUpdatePatternTransform && (mAzureState.pattern) &&
538 !mAzureState.patternTransformChanged) {
539 mAzureState.patternTransform = GetDTTransform();
540 mAzureState.patternTransformChanged = true;
543 if (mPathIsRect) {
544 Matrix invMatrix = aNewMatrix;
546 invMatrix.Invert();
548 Matrix toNewUS = mAzureState.transform * invMatrix;
550 if (toNewUS.IsRectilinear()) {
551 mRect = toNewUS.TransformBounds(mRect);
552 mRect.NudgeToIntegers();
553 } else {
554 mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING);
556 mPathBuilder->MoveTo(toNewUS.TransformPoint(mRect.TopLeft()));
557 mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.TopRight()));
558 mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomRight()));
559 mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomLeft()));
560 mPathBuilder->Close();
562 mPathIsRect = false;
565 // No need to consider the transform changed now!
566 mTransformChanged = false;
567 } else if ((mPath || mPathBuilder) && !mTransformChanged) {
568 mTransformChanged = true;
569 mPathTransform = mAzureState.transform;
572 mAzureState.transform = aNewMatrix;
574 mDT->SetTransform(GetDTTransform());
577 Rect gfxContext::GetAzureDeviceSpaceClipBounds() const {
578 Rect rect(mAzureState.deviceOffset.x + Float(mDT->GetRect().x),
579 mAzureState.deviceOffset.y + Float(mDT->GetRect().y),
580 Float(mDT->GetSize().width), Float(mDT->GetSize().height));
581 ForAllClips([&](const AzureState::PushedClip& aClip) -> void {
582 if (aClip.path) {
583 rect.IntersectRect(rect, aClip.path->GetBounds(aClip.transform));
584 } else {
585 rect.IntersectRect(rect, aClip.transform.TransformBounds(aClip.rect));
589 return rect;
592 template <typename F>
593 void gfxContext::ForAllClips(F&& aLambda) const {
594 for (const auto& state : mSavedStates) {
595 for (const auto& clip : state.pushedClips) {
596 aLambda(clip);
599 for (const auto& clip : mAzureState.pushedClips) {
600 aLambda(clip);