gfx/thebes/gfxContext.cpp

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:7eb60b409a72
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #ifdef _MSC_VER
7 #define _USE_MATH_DEFINES
8 #endif
9 #include <math.h>
10
11 #include "mozilla/Alignment.h"
12
13 #include "cairo.h"
14
15 #include "gfxContext.h"
16
17 #include "gfxColor.h"
18 #include "gfxMatrix.h"
19 #include "gfxASurface.h"
20 #include "gfxPattern.h"
21 #include "gfxPlatform.h"
22 #include "gfxTeeSurface.h"
23 #include "GeckoProfiler.h"
24 #include "gfx2DGlue.h"
25 #include "mozilla/gfx/PathHelpers.h"
26 #include <algorithm>
27
28 #if CAIRO_HAS_DWRITE_FONT
29 #include "gfxWindowsPlatform.h"
30 #endif
31
32 using namespace mozilla;
33 using namespace mozilla::gfx;
34
35 UserDataKey gfxContext::sDontUseAsSourceKey;
36
37 /* This class lives on the stack and allows gfxContext users to easily, and
38 * performantly get a gfx::Pattern to use for drawing in their current context.
39 */
40 class GeneralPattern
41 {
42 public:
43 GeneralPattern(gfxContext *aContext) : mContext(aContext), mPattern(nullptr) {}
44 ~GeneralPattern() { if (mPattern) { mPattern->~Pattern(); } }
45
46 operator mozilla::gfx::Pattern&()
47 {
48 gfxContext::AzureState &state = mContext->CurrentState();
49
50 if (state.pattern) {
51 return *state.pattern->GetPattern(mContext->mDT, state.patternTransformChanged ? &state.patternTransform : nullptr);
52 } else if (state.sourceSurface) {
53 Matrix transform = state.surfTransform;
54
55 if (state.patternTransformChanged) {
56 Matrix mat = mContext->GetDTTransform();
57 mat.Invert();
58
59 transform = transform * state.patternTransform * mat;
60 }
61
62 mPattern = new (mSurfacePattern.addr())
63 SurfacePattern(state.sourceSurface, ExtendMode::CLAMP, transform);
64 return *mPattern;
65 } else {
66 mPattern = new (mColorPattern.addr())
67 ColorPattern(state.color);
68 return *mPattern;
69 }
70 }
71
72 private:
73 union {
74 mozilla::AlignedStorage2<mozilla::gfx::ColorPattern> mColorPattern;
75 mozilla::AlignedStorage2<mozilla::gfx::SurfacePattern> mSurfacePattern;
76 };
77
78 gfxContext *mContext;
79 Pattern *mPattern;
80 };
81
82 gfxContext::gfxContext(gfxASurface *surface)
83 : mRefCairo(nullptr)
84 , mSurface(surface)
85 {
86 MOZ_COUNT_CTOR(gfxContext);
87
88 mCairo = cairo_create(surface->CairoSurface());
89 mFlags = surface->GetDefaultContextFlags();
90 if (mSurface->GetRotateForLandscape()) {
91 // Rotate page 90 degrees to draw landscape page on portrait paper
92 gfxIntSize size = mSurface->GetSize();
93 Translate(gfxPoint(0, size.width));
94 gfxMatrix matrix(0, -1,
95 1, 0,
96 0, 0);
97 Multiply(matrix);
98 }
99 }
100
101 gfxContext::gfxContext(DrawTarget *aTarget, const Point& aDeviceOffset)
102 : mPathIsRect(false)
103 , mTransformChanged(false)
104 , mCairo(nullptr)
105 , mRefCairo(nullptr)
106 , mSurface(nullptr)
107 , mFlags(0)
108 , mDT(aTarget)
109 , mOriginalDT(aTarget)
110 {
111 MOZ_COUNT_CTOR(gfxContext);
112
113 mStateStack.SetLength(1);
114 CurrentState().drawTarget = mDT;
115 CurrentState().deviceOffset = aDeviceOffset;
116 mDT->SetTransform(Matrix());
117 }
118
119 /* static */ already_AddRefed<gfxContext>
120 gfxContext::ContextForDrawTarget(DrawTarget* aTarget)
121 {
122 Matrix transform = aTarget->GetTransform();
123 nsRefPtr<gfxContext> result = new gfxContext(aTarget);
124 result->SetMatrix(ThebesMatrix(transform));
125 return result.forget();
126 }
127
128 gfxContext::~gfxContext()
129 {
130 if (mCairo) {
131 cairo_destroy(mCairo);
132 }
133 if (mRefCairo) {
134 cairo_destroy(mRefCairo);
135 }
136 if (mDT) {
137 for (int i = mStateStack.Length() - 1; i >= 0; i--) {
138 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
139 mDT->PopClip();
140 }
141
142 if (mStateStack[i].clipWasReset) {
143 break;
144 }
145 }
146 mDT->Flush();
147 }
148 MOZ_COUNT_DTOR(gfxContext);
149 }
150
151 gfxASurface *
152 gfxContext::OriginalSurface()
153 {
154 if (mCairo || mSurface) {
155 return mSurface;
156 }
157
158 if (mOriginalDT && mOriginalDT->GetType() == BackendType::CAIRO) {
159 cairo_surface_t *s =
160 (cairo_surface_t*)mOriginalDT->GetNativeSurface(NativeSurfaceType::CAIRO_SURFACE);
161 if (s) {
162 mSurface = gfxASurface::Wrap(s);
163 return mSurface;
164 }
165 }
166 return nullptr;
167 }
168
169 already_AddRefed<gfxASurface>
170 gfxContext::CurrentSurface(gfxFloat *dx, gfxFloat *dy)
171 {
172 if (mCairo) {
173 cairo_surface_t *s = cairo_get_group_target(mCairo);
174 if (s == mSurface->CairoSurface()) {
175 if (dx && dy)
176 cairo_surface_get_device_offset(s, dx, dy);
177 nsRefPtr<gfxASurface> ret = mSurface;
178 return ret.forget();
179 }
180
181 if (dx && dy)
182 cairo_surface_get_device_offset(s, dx, dy);
183 return gfxASurface::Wrap(s);
184 } else {
185 if (mDT->GetType() == BackendType::CAIRO) {
186 cairo_surface_t *s =
187 (cairo_surface_t*)mDT->GetNativeSurface(NativeSurfaceType::CAIRO_SURFACE);
188 if (s) {
189 if (dx && dy) {
190 *dx = -CurrentState().deviceOffset.x;
191 *dy = -CurrentState().deviceOffset.y;
192 }
193 return gfxASurface::Wrap(s);
194 }
195 }
196
197 if (dx && dy) {
198 *dx = *dy = 0;
199 }
200 // An Azure context doesn't have a surface backing it.
201 return nullptr;
202 }
203 }
204
205 cairo_t *
206 gfxContext::GetCairo()
207 {
208 if (mCairo) {
209 return mCairo;
210 }
211
212 if (mDT->GetType() == BackendType::CAIRO) {
213 cairo_t *ctx =
214 (cairo_t*)mDT->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT);
215 if (ctx) {
216 return ctx;
217 }
218 }
219
220 if (mRefCairo) {
221 // Set transform!
222 return mRefCairo;
223 }
224
225 mRefCairo = cairo_create(gfxPlatform::GetPlatform()->ScreenReferenceSurface()->CairoSurface());
226
227 return mRefCairo;
228 }
229
230 void
231 gfxContext::Save()
232 {
233 if (mCairo) {
234 cairo_save(mCairo);
235 } else {
236 CurrentState().transform = mTransform;
237 mStateStack.AppendElement(AzureState(CurrentState()));
238 CurrentState().clipWasReset = false;
239 CurrentState().pushedClips.Clear();
240 }
241 }
242
243 void
244 gfxContext::Restore()
245 {
246 if (mCairo) {
247 cairo_restore(mCairo);
248 } else {
249 for (unsigned int c = 0; c < CurrentState().pushedClips.Length(); c++) {
250 mDT->PopClip();
251 }
252
253 if (CurrentState().clipWasReset &&
254 CurrentState().drawTarget == mStateStack[mStateStack.Length() - 2].drawTarget) {
255 PushClipsToDT(mDT);
256 }
257
258 mStateStack.RemoveElementAt(mStateStack.Length() - 1);
259
260 mDT = CurrentState().drawTarget;
261
262 ChangeTransform(CurrentState().transform, false);
263 }
264 }
265
266 // drawing
267 void
268 gfxContext::NewPath()
269 {
270 if (mCairo) {
271 cairo_new_path(mCairo);
272 } else {
273 mPath = nullptr;
274 mPathBuilder = nullptr;
275 mPathIsRect = false;
276 mTransformChanged = false;
277 }
278 }
279
280 void
281 gfxContext::ClosePath()
282 {
283 if (mCairo) {
284 cairo_close_path(mCairo);
285 } else {
286 EnsurePathBuilder();
287 mPathBuilder->Close();
288 }
289 }
290
291 already_AddRefed<gfxPath> gfxContext::CopyPath()
292 {
293 nsRefPtr<gfxPath> path;
294 if (mCairo) {
295 path = new gfxPath(cairo_copy_path(mCairo));
296 } else {
297 EnsurePath();
298 path = new gfxPath(mPath);
299 }
300 return path.forget();
301 }
302
303 void gfxContext::SetPath(gfxPath* path)
304 {
305 if (mCairo) {
306 cairo_new_path(mCairo);
307 if (path->mPath->status == CAIRO_STATUS_SUCCESS && path->mPath->num_data != 0)
308 cairo_append_path(mCairo, path->mPath);
309 } else {
310 MOZ_ASSERT(path->mMoz2DPath, "Can't mix cairo and azure paths!");
311 MOZ_ASSERT(path->mMoz2DPath->GetBackendType() == mDT->GetType());
312 mPath = path->mMoz2DPath;
313 mPathBuilder = nullptr;
314 mPathIsRect = false;
315 mTransformChanged = false;
316 }
317 }
318
319 gfxPoint
320 gfxContext::CurrentPoint()
321 {
322 if (mCairo) {
323 double x, y;
324 cairo_get_current_point(mCairo, &x, &y);
325 return gfxPoint(x, y);
326 } else {
327 EnsurePathBuilder();
328 return ThebesPoint(mPathBuilder->CurrentPoint());
329 }
330 }
331
332 void
333 gfxContext::Stroke()
334 {
335 if (mCairo) {
336 cairo_stroke_preserve(mCairo);
337 } else {
338 AzureState &state = CurrentState();
339 if (mPathIsRect) {
340 MOZ_ASSERT(!mTransformChanged);
341
342 mDT->StrokeRect(mRect, GeneralPattern(this),
343 state.strokeOptions,
344 DrawOptions(1.0f, GetOp(), state.aaMode));
345 } else {
346 EnsurePath();
347
348 mDT->Stroke(mPath, GeneralPattern(this), state.strokeOptions,
349 DrawOptions(1.0f, GetOp(), state.aaMode));
350 }
351 }
352 }
353
354 void
355 gfxContext::Fill()
356 {
357 PROFILER_LABEL("gfxContext", "Fill");
358 if (mCairo) {
359 cairo_fill_preserve(mCairo);
360 } else {
361 FillAzure(1.0f);
362 }
363 }
364
365 void
366 gfxContext::FillWithOpacity(gfxFloat aOpacity)
367 {
368 if (mCairo) {
369 // This method exists in the hope that one day cairo gets a direct
370 // API for this, and then we would change this method to use that
371 // API instead.
372 if (aOpacity != 1.0) {
373 gfxContextAutoSaveRestore saveRestore(this);
374 Clip();
375 Paint(aOpacity);
376 } else {
377 Fill();
378 }
379 } else {
380 FillAzure(Float(aOpacity));
381 }
382 }
383
384 void
385 gfxContext::MoveTo(const gfxPoint& pt)
386 {
387 if (mCairo) {
388 cairo_move_to(mCairo, pt.x, pt.y);
389 } else {
390 EnsurePathBuilder();
391 mPathBuilder->MoveTo(ToPoint(pt));
392 }
393 }
394
395 void
396 gfxContext::NewSubPath()
397 {
398 if (mCairo) {
399 cairo_new_sub_path(mCairo);
400 } else {
401 // XXX - This has no users, we should kill it, it should be equivelant to a
402 // MoveTo to the path's current point.
403 }
404 }
405
406 void
407 gfxContext::LineTo(const gfxPoint& pt)
408 {
409 if (mCairo) {
410 cairo_line_to(mCairo, pt.x, pt.y);
411 } else {
412 EnsurePathBuilder();
413 mPathBuilder->LineTo(ToPoint(pt));
414 }
415 }
416
417 void
418 gfxContext::CurveTo(const gfxPoint& pt1, const gfxPoint& pt2, const gfxPoint& pt3)
419 {
420 if (mCairo) {
421 cairo_curve_to(mCairo, pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y);
422 } else {
423 EnsurePathBuilder();
424 mPathBuilder->BezierTo(ToPoint(pt1), ToPoint(pt2), ToPoint(pt3));
425 }
426 }
427
428 void
429 gfxContext::QuadraticCurveTo(const gfxPoint& pt1, const gfxPoint& pt2)
430 {
431 if (mCairo) {
432 double cx, cy;
433 cairo_get_current_point(mCairo, &cx, &cy);
434 cairo_curve_to(mCairo,
435 (cx + pt1.x * 2.0) / 3.0,
436 (cy + pt1.y * 2.0) / 3.0,
437 (pt1.x * 2.0 + pt2.x) / 3.0,
438 (pt1.y * 2.0 + pt2.y) / 3.0,
439 pt2.x,
440 pt2.y);
441 } else {
442 EnsurePathBuilder();
443 mPathBuilder->QuadraticBezierTo(ToPoint(pt1), ToPoint(pt2));
444 }
445 }
446
447 void
448 gfxContext::Arc(const gfxPoint& center, gfxFloat radius,
449 gfxFloat angle1, gfxFloat angle2)
450 {
451 if (mCairo) {
452 cairo_arc(mCairo, center.x, center.y, radius, angle1, angle2);
453 } else {
454 EnsurePathBuilder();
455 mPathBuilder->Arc(ToPoint(center), Float(radius), Float(angle1), Float(angle2));
456 }
457 }
458
459 void
460 gfxContext::NegativeArc(const gfxPoint& center, gfxFloat radius,
461 gfxFloat angle1, gfxFloat angle2)
462 {
463 if (mCairo) {
464 cairo_arc_negative(mCairo, center.x, center.y, radius, angle1, angle2);
465 } else {
466 EnsurePathBuilder();
467 mPathBuilder->Arc(ToPoint(center), Float(radius), Float(angle2), Float(angle1));
468 }
469 }
470
471 void
472 gfxContext::Line(const gfxPoint& start, const gfxPoint& end)
473 {
474 if (mCairo) {
475 MoveTo(start);
476 LineTo(end);
477 } else {
478 EnsurePathBuilder();
479 mPathBuilder->MoveTo(ToPoint(start));
480 mPathBuilder->LineTo(ToPoint(end));
481 }
482 }
483
484 // XXX snapToPixels is only valid when snapping for filled
485 // rectangles and for even-width stroked rectangles.
486 // For odd-width stroked rectangles, we need to offset x/y by
487 // 0.5...
488 void
489 gfxContext::Rectangle(const gfxRect& rect, bool snapToPixels)
490 {
491 if (mCairo) {
492 if (snapToPixels) {
493 gfxRect snappedRect(rect);
494
495 if (UserToDevicePixelSnapped(snappedRect, true))
496 {
497 cairo_matrix_t mat;
498 cairo_get_matrix(mCairo, &mat);
499 cairo_identity_matrix(mCairo);
500 Rectangle(snappedRect);
501 cairo_set_matrix(mCairo, &mat);
502
503 return;
504 }
505 }
506
507 cairo_rectangle(mCairo, rect.X(), rect.Y(), rect.Width(), rect.Height());
508 } else {
509 Rect rec = ToRect(rect);
510
511 if (snapToPixels) {
512 gfxRect newRect(rect);
513 if (UserToDevicePixelSnapped(newRect, true)) {
514 gfxMatrix mat = ThebesMatrix(mTransform);
515 mat.Invert();
516
517 // We need the user space rect.
518 rec = ToRect(mat.TransformBounds(newRect));
519 }
520 }
521
522 if (!mPathBuilder && !mPathIsRect) {
523 mPathIsRect = true;
524 mRect = rec;
525 return;
526 }
527
528 EnsurePathBuilder();
529
530 mPathBuilder->MoveTo(rec.TopLeft());
531 mPathBuilder->LineTo(rec.TopRight());
532 mPathBuilder->LineTo(rec.BottomRight());
533 mPathBuilder->LineTo(rec.BottomLeft());
534 mPathBuilder->Close();
535 }
536 }
537
538 void
539 gfxContext::Ellipse(const gfxPoint& center, const gfxSize& dimensions)
540 {
541 gfxSize halfDim = dimensions / 2.0;
542 gfxRect r(center - gfxPoint(halfDim.width, halfDim.height), dimensions);
543 gfxCornerSizes c(halfDim, halfDim, halfDim, halfDim);
544
545 RoundedRectangle (r, c);
546 }
547
548 void
549 gfxContext::Polygon(const gfxPoint *points, uint32_t numPoints)
550 {
551 if (mCairo) {
552 if (numPoints == 0)
553 return;
554
555 cairo_move_to(mCairo, points[0].x, points[0].y);
556 for (uint32_t i = 1; i < numPoints; ++i) {
557 cairo_line_to(mCairo, points[i].x, points[i].y);
558 }
559 } else {
560 if (numPoints == 0) {
561 return;
562 }
563
564 EnsurePathBuilder();
565
566 mPathBuilder->MoveTo(ToPoint(points[0]));
567 for (uint32_t i = 1; i < numPoints; i++) {
568 mPathBuilder->LineTo(ToPoint(points[i]));
569 }
570 }
571 }
572
573 void
574 gfxContext::DrawSurface(gfxASurface *surface, const gfxSize& size)
575 {
576 if (mCairo) {
577 cairo_save(mCairo);
578 cairo_set_source_surface(mCairo, surface->CairoSurface(), 0, 0);
579 cairo_new_path(mCairo);
580
581 // pixel-snap this
582 Rectangle(gfxRect(gfxPoint(0.0, 0.0), size), true);
583
584 cairo_fill(mCairo);
585 cairo_restore(mCairo);
586 } else {
587 // Lifetime needs to be limited here since we may wrap surface's data.
588 RefPtr<SourceSurface> surf =
589 gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface);
590
591 if (!surf) {
592 return;
593 }
594
595 Rect rect(0, 0, Float(size.width), Float(size.height));
596 rect.Intersect(Rect(0, 0, Float(surf->GetSize().width), Float(surf->GetSize().height)));
597
598 // XXX - Should fix pixel snapping.
599 mDT->DrawSurface(surf, rect, rect);
600 }
601 }
602
603 // transform stuff
604 void
605 gfxContext::Translate(const gfxPoint& pt)
606 {
607 if (mCairo) {
608 cairo_translate(mCairo, pt.x, pt.y);
609 } else {
610 Matrix newMatrix = mTransform;
611
612 ChangeTransform(newMatrix.Translate(Float(pt.x), Float(pt.y)));
613 }
614 }
615
616 void
617 gfxContext::Scale(gfxFloat x, gfxFloat y)
618 {
619 if (mCairo) {
620 cairo_scale(mCairo, x, y);
621 } else {
622 Matrix newMatrix = mTransform;
623
624 ChangeTransform(newMatrix.Scale(Float(x), Float(y)));
625 }
626 }
627
628 void
629 gfxContext::Rotate(gfxFloat angle)
630 {
631 if (mCairo) {
632 cairo_rotate(mCairo, angle);
633 } else {
634 Matrix rotation = Matrix::Rotation(Float(angle));
635 ChangeTransform(rotation * mTransform);
636 }
637 }
638
639 void
640 gfxContext::Multiply(const gfxMatrix& matrix)
641 {
642 if (mCairo) {
643 const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix);
644 cairo_transform(mCairo, &mat);
645 } else {
646 ChangeTransform(ToMatrix(matrix) * mTransform);
647 }
648 }
649
650 void
651 gfxContext::MultiplyAndNudgeToIntegers(const gfxMatrix& matrix)
652 {
653 if (mCairo) {
654 const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix);
655 cairo_transform(mCairo, &mat);
656 // XXX nudging to integers not currently supported for Thebes
657 } else {
658 Matrix transform = ToMatrix(matrix) * mTransform;
659 transform.NudgeToIntegers();
660 ChangeTransform(transform);
661 }
662 }
663
664 void
665 gfxContext::SetMatrix(const gfxMatrix& matrix)
666 {
667 if (mCairo) {
668 const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix);
669 cairo_set_matrix(mCairo, &mat);
670 } else {
671 ChangeTransform(ToMatrix(matrix));
672 }
673 }
674
675 void
676 gfxContext::IdentityMatrix()
677 {
678 if (mCairo) {
679 cairo_identity_matrix(mCairo);
680 } else {
681 ChangeTransform(Matrix());
682 }
683 }
684
685 gfxMatrix
686 gfxContext::CurrentMatrix() const
687 {
688 if (mCairo) {
689 cairo_matrix_t mat;
690 cairo_get_matrix(mCairo, &mat);
691 return gfxMatrix(*reinterpret_cast<gfxMatrix*>(&mat));
692 } else {
693 return ThebesMatrix(mTransform);
694 }
695 }
696
697 void
698 gfxContext::NudgeCurrentMatrixToIntegers()
699 {
700 if (mCairo) {
701 cairo_matrix_t mat;
702 cairo_get_matrix(mCairo, &mat);
703 gfxMatrix(*reinterpret_cast<gfxMatrix*>(&mat)).NudgeToIntegers();
704 cairo_set_matrix(mCairo, &mat);
705 } else {
706 gfxMatrix matrix = ThebesMatrix(mTransform);
707 matrix.NudgeToIntegers();
708 ChangeTransform(ToMatrix(matrix));
709 }
710 }
711
712 gfxPoint
713 gfxContext::DeviceToUser(const gfxPoint& point) const
714 {
715 if (mCairo) {
716 gfxPoint ret = point;
717 cairo_device_to_user(mCairo, &ret.x, &ret.y);
718 return ret;
719 } else {
720 Matrix matrix = mTransform;
721
722 matrix.Invert();
723
724 return ThebesPoint(matrix * ToPoint(point));
725 }
726 }
727
728 gfxSize
729 gfxContext::DeviceToUser(const gfxSize& size) const
730 {
731 if (mCairo) {
732 gfxSize ret = size;
733 cairo_device_to_user_distance(mCairo, &ret.width, &ret.height);
734 return ret;
735 } else {
736 Matrix matrix = mTransform;
737
738 matrix.Invert();
739
740 return ThebesSize(matrix * ToSize(size));
741 }
742 }
743
744 gfxRect
745 gfxContext::DeviceToUser(const gfxRect& rect) const
746 {
747 if (mCairo) {
748 gfxRect ret = rect;
749 cairo_device_to_user(mCairo, &ret.x, &ret.y);
750 cairo_device_to_user_distance(mCairo, &ret.width, &ret.height);
751 return ret;
752 } else {
753 Matrix matrix = mTransform;
754
755 matrix.Invert();
756
757 return ThebesRect(matrix.TransformBounds(ToRect(rect)));
758 }
759 }
760
761 gfxPoint
762 gfxContext::UserToDevice(const gfxPoint& point) const
763 {
764 if (mCairo) {
765 gfxPoint ret = point;
766 cairo_user_to_device(mCairo, &ret.x, &ret.y);
767 return ret;
768 } else {
769 return ThebesPoint(mTransform * ToPoint(point));
770 }
771 }
772
773 gfxSize
774 gfxContext::UserToDevice(const gfxSize& size) const
775 {
776 if (mCairo) {
777 gfxSize ret = size;
778 cairo_user_to_device_distance(mCairo, &ret.width, &ret.height);
779 return ret;
780 } else {
781 const Matrix &matrix = mTransform;
782
783 gfxSize newSize;
784 newSize.width = size.width * matrix._11 + size.height * matrix._12;
785 newSize.height = size.width * matrix._21 + size.height * matrix._22;
786 return newSize;
787 }
788 }
789
790 gfxRect
791 gfxContext::UserToDevice(const gfxRect& rect) const
792 {
793 if (mCairo) {
794 double xmin = rect.X(), ymin = rect.Y(), xmax = rect.XMost(), ymax = rect.YMost();
795
796 double x[3], y[3];
797 x[0] = xmin; y[0] = ymax;
798 x[1] = xmax; y[1] = ymax;
799 x[2] = xmax; y[2] = ymin;
800
801 cairo_user_to_device(mCairo, &xmin, &ymin);
802 xmax = xmin;
803 ymax = ymin;
804 for (int i = 0; i < 3; i++) {
805 cairo_user_to_device(mCairo, &x[i], &y[i]);
806 xmin = std::min(xmin, x[i]);
807 xmax = std::max(xmax, x[i]);
808 ymin = std::min(ymin, y[i]);
809 ymax = std::max(ymax, y[i]);
810 }
811
812 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
813 } else {
814 const Matrix &matrix = mTransform;
815 return ThebesRect(matrix.TransformBounds(ToRect(rect)));
816 }
817 }
818
819 bool
820 gfxContext::UserToDevicePixelSnapped(gfxRect& rect, bool ignoreScale) const
821 {
822 if (GetFlags() & FLAG_DISABLE_SNAPPING)
823 return false;
824
825 // if we're not at 1.0 scale, don't snap, unless we're
826 // ignoring the scale. If we're not -just- a scale,
827 // never snap.
828 const gfxFloat epsilon = 0.0000001;
829 #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon)
830 if (mCairo) {
831 cairo_matrix_t mat;
832 cairo_get_matrix(mCairo, &mat);
833 if (!ignoreScale &&
834 (!WITHIN_E(mat.xx,1.0) || !WITHIN_E(mat.yy,1.0) ||
835 !WITHIN_E(mat.xy,0.0) || !WITHIN_E(mat.yx,0.0)))
836 return false;
837 } else {
838 Matrix mat = mTransform;
839 if (!ignoreScale &&
840 (!WITHIN_E(mat._11,1.0) || !WITHIN_E(mat._22,1.0) ||
841 !WITHIN_E(mat._12,0.0) || !WITHIN_E(mat._21,0.0)))
842 return false;
843 }
844 #undef WITHIN_E
845
846 gfxPoint p1 = UserToDevice(rect.TopLeft());
847 gfxPoint p2 = UserToDevice(rect.TopRight());
848 gfxPoint p3 = UserToDevice(rect.BottomRight());
849
850 // Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
851 // two opposite corners define the entire rectangle. So check if
852 // the axis-aligned rectangle with opposite corners p1 and p3
853 // define an axis-aligned rectangle whose other corners are p2 and p4.
854 // We actually only need to check one of p2 and p4, since an affine
855 // transform maps parallelograms to parallelograms.
856 if (p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y)) {
857 p1.Round();
858 p3.Round();
859
860 rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y)));
861 rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(),
862 std::max(p1.y, p3.y) - rect.Y()));
863 return true;
864 }
865
866 return false;
867 }
868
869 bool
870 gfxContext::UserToDevicePixelSnapped(gfxPoint& pt, bool ignoreScale) const
871 {
872 if (GetFlags() & FLAG_DISABLE_SNAPPING)
873 return false;
874
875 // if we're not at 1.0 scale, don't snap, unless we're
876 // ignoring the scale. If we're not -just- a scale,
877 // never snap.
878 const gfxFloat epsilon = 0.0000001;
879 #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon)
880 if (mCairo) {
881 cairo_matrix_t mat;
882 cairo_get_matrix(mCairo, &mat);
883 if (!ignoreScale &&
884 (!WITHIN_E(mat.xx,1.0) || !WITHIN_E(mat.yy,1.0) ||
885 !WITHIN_E(mat.xy,0.0) || !WITHIN_E(mat.yx,0.0)))
886 return false;
887 } else {
888 Matrix mat = mTransform;
889 if (!ignoreScale &&
890 (!WITHIN_E(mat._11,1.0) || !WITHIN_E(mat._22,1.0) ||
891 !WITHIN_E(mat._12,0.0) || !WITHIN_E(mat._21,0.0)))
892 return false;
893 }
894 #undef WITHIN_E
895
896 pt = UserToDevice(pt);
897 pt.Round();
898 return true;
899 }
900
901 void
902 gfxContext::PixelSnappedRectangleAndSetPattern(const gfxRect& rect,
903 gfxPattern *pattern)
904 {
905 gfxRect r(rect);
906
907 // Bob attempts to pixel-snap the rectangle, and returns true if
908 // the snapping succeeds. If it does, we need to set up an
909 // identity matrix, because the rectangle given back is in device
910 // coordinates.
911 //
912 // We then have to call a translate to dr.pos afterwards, to make
913 // sure the image lines up in the right place with our pixel
914 // snapped rectangle.
915 //
916 // If snapping wasn't successful, we just translate to where the
917 // pattern would normally start (in app coordinates) and do the
918 // same thing.
919 Rectangle(r, true);
920 SetPattern(pattern);
921 }
922
923 void
924 gfxContext::SetAntialiasMode(AntialiasMode mode)
925 {
926 if (mCairo) {
927 if (mode == MODE_ALIASED) {
928 cairo_set_antialias(mCairo, CAIRO_ANTIALIAS_NONE);
929 } else if (mode == MODE_COVERAGE) {
930 cairo_set_antialias(mCairo, CAIRO_ANTIALIAS_DEFAULT);
931 }
932 } else {
933 if (mode == MODE_ALIASED) {
934 CurrentState().aaMode = gfx::AntialiasMode::NONE;
935 } else if (mode == MODE_COVERAGE) {
936 CurrentState().aaMode = gfx::AntialiasMode::SUBPIXEL;
937 }
938 }
939 }
940
941 gfxContext::AntialiasMode
942 gfxContext::CurrentAntialiasMode() const
943 {
944 if (mCairo) {
945 cairo_antialias_t aa = cairo_get_antialias(mCairo);
946 if (aa == CAIRO_ANTIALIAS_NONE)
947 return MODE_ALIASED;
948 return MODE_COVERAGE;
949 } else {
950 if (CurrentState().aaMode == gfx::AntialiasMode::NONE) {
951 return MODE_ALIASED;
952 }
953 return MODE_COVERAGE;
954 }
955 }
956
957 void
958 gfxContext::SetDash(gfxLineType ltype)
959 {
960 static double dash[] = {5.0, 5.0};
961 static double dot[] = {1.0, 1.0};
962
963 switch (ltype) {
964 case gfxLineDashed:
965 SetDash(dash, 2, 0.0);
966 break;
967 case gfxLineDotted:
968 SetDash(dot, 2, 0.0);
969 break;
970 case gfxLineSolid:
971 default:
972 SetDash(nullptr, 0, 0.0);
973 break;
974 }
975 }
976
977 void
978 gfxContext::SetDash(gfxFloat *dashes, int ndash, gfxFloat offset)
979 {
980 if (mCairo) {
981 cairo_set_dash(mCairo, dashes, ndash, offset);
982 } else {
983 AzureState &state = CurrentState();
984
985 state.dashPattern.SetLength(ndash);
986 for (int i = 0; i < ndash; i++) {
987 state.dashPattern[i] = Float(dashes[i]);
988 }
989 state.strokeOptions.mDashLength = ndash;
990 state.strokeOptions.mDashOffset = Float(offset);
991 state.strokeOptions.mDashPattern = ndash ? state.dashPattern.Elements()
992 : nullptr;
993 }
994 }
995
996 bool
997 gfxContext::CurrentDash(FallibleTArray<gfxFloat>& dashes, gfxFloat* offset) const
998 {
999 if (mCairo) {
1000 int count = cairo_get_dash_count(mCairo);
1001 if (count <= 0 || !dashes.SetLength(count)) {
1002 return false;
1003 }
1004 cairo_get_dash(mCairo, dashes.Elements(), offset);
1005 return true;
1006 } else {
1007 const AzureState &state = CurrentState();
1008 int count = state.strokeOptions.mDashLength;
1009
1010 if (count <= 0 || !dashes.SetLength(count)) {
1011 return false;
1012 }
1013
1014 for (int i = 0; i < count; i++) {
1015 dashes[i] = state.dashPattern[i];
1016 }
1017
1018 *offset = state.strokeOptions.mDashOffset;
1019
1020 return true;
1021 }
1022 }
1023
1024 gfxFloat
1025 gfxContext::CurrentDashOffset() const
1026 {
1027 if (mCairo) {
1028 if (cairo_get_dash_count(mCairo) <= 0) {
1029 return 0.0;
1030 }
1031 gfxFloat offset;
1032 cairo_get_dash(mCairo, nullptr, &offset);
1033 return offset;
1034 } else {
1035 return CurrentState().strokeOptions.mDashOffset;
1036 }
1037 }
1038
1039 void
1040 gfxContext::SetLineWidth(gfxFloat width)
1041 {
1042 if (mCairo) {
1043 cairo_set_line_width(mCairo, width);
1044 } else {
1045 CurrentState().strokeOptions.mLineWidth = Float(width);
1046 }
1047 }
1048
1049 gfxFloat
1050 gfxContext::CurrentLineWidth() const
1051 {
1052 if (mCairo) {
1053 return cairo_get_line_width(mCairo);
1054 } else {
1055 return CurrentState().strokeOptions.mLineWidth;
1056 }
1057 }
1058
1059 void
1060 gfxContext::SetOperator(GraphicsOperator op)
1061 {
1062 if (mCairo) {
1063 if (mFlags & FLAG_SIMPLIFY_OPERATORS) {
1064 if (op != OPERATOR_SOURCE &&
1065 op != OPERATOR_CLEAR &&
1066 op != OPERATOR_OVER)
1067 op = OPERATOR_OVER;
1068 }
1069
1070 cairo_set_operator(mCairo, (cairo_operator_t)op);
1071 } else {
1072 if (op == OPERATOR_CLEAR) {
1073 CurrentState().opIsClear = true;
1074 return;
1075 }
1076 CurrentState().opIsClear = false;
1077 CurrentState().op = CompositionOpForOp(op);
1078 }
1079 }
1080
1081 gfxContext::GraphicsOperator
1082 gfxContext::CurrentOperator() const
1083 {
1084 if (mCairo) {
1085 return (GraphicsOperator)cairo_get_operator(mCairo);
1086 } else {
1087 return ThebesOp(CurrentState().op);
1088 }
1089 }
1090
1091 void
1092 gfxContext::SetLineCap(GraphicsLineCap cap)
1093 {
1094 if (mCairo) {
1095 cairo_set_line_cap(mCairo, (cairo_line_cap_t)cap);
1096 } else {
1097 CurrentState().strokeOptions.mLineCap = ToCapStyle(cap);
1098 }
1099 }
1100
1101 gfxContext::GraphicsLineCap
1102 gfxContext::CurrentLineCap() const
1103 {
1104 if (mCairo) {
1105 return (GraphicsLineCap)cairo_get_line_cap(mCairo);
1106 } else {
1107 return ThebesLineCap(CurrentState().strokeOptions.mLineCap);
1108 }
1109 }
1110
1111 void
1112 gfxContext::SetLineJoin(GraphicsLineJoin join)
1113 {
1114 if (mCairo) {
1115 cairo_set_line_join(mCairo, (cairo_line_join_t)join);
1116 } else {
1117 CurrentState().strokeOptions.mLineJoin = ToJoinStyle(join);
1118 }
1119 }
1120
1121 gfxContext::GraphicsLineJoin
1122 gfxContext::CurrentLineJoin() const
1123 {
1124 if (mCairo) {
1125 return (GraphicsLineJoin)cairo_get_line_join(mCairo);
1126 } else {
1127 return ThebesLineJoin(CurrentState().strokeOptions.mLineJoin);
1128 }
1129 }
1130
1131 void
1132 gfxContext::SetMiterLimit(gfxFloat limit)
1133 {
1134 if (mCairo) {
1135 cairo_set_miter_limit(mCairo, limit);
1136 } else {
1137 CurrentState().strokeOptions.mMiterLimit = Float(limit);
1138 }
1139 }
1140
1141 gfxFloat
1142 gfxContext::CurrentMiterLimit() const
1143 {
1144 if (mCairo) {
1145 return cairo_get_miter_limit(mCairo);
1146 } else {
1147 return CurrentState().strokeOptions.mMiterLimit;
1148 }
1149 }
1150
1151 void
1152 gfxContext::SetFillRule(FillRule rule)
1153 {
1154 if (mCairo) {
1155 cairo_set_fill_rule(mCairo, (cairo_fill_rule_t)rule);
1156 } else {
1157 CurrentState().fillRule = rule == FILL_RULE_WINDING ? gfx::FillRule::FILL_WINDING : gfx::FillRule::FILL_EVEN_ODD;
1158 }
1159 }
1160
1161 gfxContext::FillRule
1162 gfxContext::CurrentFillRule() const
1163 {
1164 if (mCairo) {
1165 return (FillRule)cairo_get_fill_rule(mCairo);
1166 } else {
1167 return FILL_RULE_WINDING;
1168 }
1169 }
1170
1171 // clipping
1172 void
1173 gfxContext::Clip(const gfxRect& rect)
1174 {
1175 if (mCairo) {
1176 cairo_new_path(mCairo);
1177 cairo_rectangle(mCairo, rect.X(), rect.Y(), rect.Width(), rect.Height());
1178 cairo_clip(mCairo);
1179 } else {
1180 AzureState::PushedClip clip = { nullptr, ToRect(rect), mTransform };
1181 CurrentState().pushedClips.AppendElement(clip);
1182 mDT->PushClipRect(ToRect(rect));
1183 NewPath();
1184 }
1185 }
1186
1187 void
1188 gfxContext::Clip()
1189 {
1190 if (mCairo) {
1191 cairo_clip_preserve(mCairo);
1192 } else {
1193 if (mPathIsRect) {
1194 MOZ_ASSERT(!mTransformChanged);
1195
1196 AzureState::PushedClip clip = { nullptr, mRect, mTransform };
1197 CurrentState().pushedClips.AppendElement(clip);
1198 mDT->PushClipRect(mRect);
1199 } else {
1200 EnsurePath();
1201 mDT->PushClip(mPath);
1202 AzureState::PushedClip clip = { mPath, Rect(), mTransform };
1203 CurrentState().pushedClips.AppendElement(clip);
1204 }
1205 }
1206 }
1207
1208 void
1209 gfxContext::ResetClip()
1210 {
1211 if (mCairo) {
1212 cairo_reset_clip(mCairo);
1213 } else {
1214 for (int i = mStateStack.Length() - 1; i >= 0; i--) {
1215 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
1216 mDT->PopClip();
1217 }
1218
1219 if (mStateStack[i].clipWasReset) {
1220 break;
1221 }
1222 }
1223 CurrentState().pushedClips.Clear();
1224 CurrentState().clipWasReset = true;
1225 }
1226 }
1227
1228 void
1229 gfxContext::UpdateSurfaceClip()
1230 {
1231 if (mCairo) {
1232 NewPath();
1233 // we paint an empty rectangle to ensure the clip is propagated to
1234 // the destination surface
1235 SetDeviceColor(gfxRGBA(0,0,0,0));
1236 Rectangle(gfxRect(0,1,1,0));
1237 Fill();
1238 }
1239 }
1240
1241 gfxRect
1242 gfxContext::GetClipExtents()
1243 {
1244 if (mCairo) {
1245 double xmin, ymin, xmax, ymax;
1246 cairo_clip_extents(mCairo, &xmin, &ymin, &xmax, &ymax);
1247 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
1248 } else {
1249 Rect rect = GetAzureDeviceSpaceClipBounds();
1250
1251 if (rect.width == 0 || rect.height == 0) {
1252 return gfxRect(0, 0, 0, 0);
1253 }
1254
1255 Matrix mat = mTransform;
1256 mat.Invert();
1257 rect = mat.TransformBounds(rect);
1258
1259 return ThebesRect(rect);
1260 }
1261 }
1262
1263 bool
1264 gfxContext::ClipContainsRect(const gfxRect& aRect)
1265 {
1266 if (mCairo) {
1267 cairo_rectangle_list_t *clip =
1268 cairo_copy_clip_rectangle_list(mCairo);
1269
1270 bool result = false;
1271
1272 if (clip->status == CAIRO_STATUS_SUCCESS) {
1273 for (int i = 0; i < clip->num_rectangles; i++) {
1274 gfxRect rect(clip->rectangles[i].x, clip->rectangles[i].y,
1275 clip->rectangles[i].width, clip->rectangles[i].height);
1276 if (rect.Contains(aRect)) {
1277 result = true;
1278 break;
1279 }
1280 }
1281 }
1282
1283 cairo_rectangle_list_destroy(clip);
1284 return result;
1285 } else {
1286 unsigned int lastReset = 0;
1287 for (int i = mStateStack.Length() - 2; i > 0; i--) {
1288 if (mStateStack[i].clipWasReset) {
1289 lastReset = i;
1290 break;
1291 }
1292 }
1293
1294 // Since we always return false when the clip list contains a
1295 // non-rectangular clip or a non-rectilinear transform, our 'total' clip
1296 // is always a rectangle if we hit the end of this function.
1297 Rect clipBounds(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height));
1298
1299 for (unsigned int i = lastReset; i < mStateStack.Length(); i++) {
1300 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
1301 AzureState::PushedClip &clip = mStateStack[i].pushedClips[c];
1302 if (clip.path || !clip.transform.IsRectilinear()) {
1303 // Cairo behavior is we return false if the clip contains a non-
1304 // rectangle.
1305 return false;
1306 } else {
1307 Rect clipRect = mTransform.TransformBounds(clip.rect);
1308
1309 clipBounds.IntersectRect(clipBounds, clipRect);
1310 }
1311 }
1312 }
1313
1314 return clipBounds.Contains(ToRect(aRect));
1315 }
1316 }
1317
1318 // rendering sources
1319
1320 void
1321 gfxContext::SetColor(const gfxRGBA& c)
1322 {
1323 if (mCairo) {
1324 if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
1325
1326 gfxRGBA cms;
1327 qcms_transform *transform = gfxPlatform::GetCMSRGBTransform();
1328 if (transform)
1329 gfxPlatform::TransformPixel(c, cms, transform);
1330
1331 // Use the original alpha to avoid unnecessary float->byte->float
1332 // conversion errors
1333 cairo_set_source_rgba(mCairo, cms.r, cms.g, cms.b, c.a);
1334 }
1335 else
1336 cairo_set_source_rgba(mCairo, c.r, c.g, c.b, c.a);
1337 } else {
1338 CurrentState().pattern = nullptr;
1339 CurrentState().sourceSurfCairo = nullptr;
1340 CurrentState().sourceSurface = nullptr;
1341
1342 if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
1343
1344 gfxRGBA cms;
1345 qcms_transform *transform = gfxPlatform::GetCMSRGBTransform();
1346 if (transform)
1347 gfxPlatform::TransformPixel(c, cms, transform);
1348
1349 // Use the original alpha to avoid unnecessary float->byte->float
1350 // conversion errors
1351 CurrentState().color = ToColor(cms);
1352 }
1353 else
1354 CurrentState().color = ToColor(c);
1355 }
1356 }
1357
1358 void
1359 gfxContext::SetDeviceColor(const gfxRGBA& c)
1360 {
1361 if (mCairo) {
1362 cairo_set_source_rgba(mCairo, c.r, c.g, c.b, c.a);
1363 } else {
1364 CurrentState().pattern = nullptr;
1365 CurrentState().sourceSurfCairo = nullptr;
1366 CurrentState().sourceSurface = nullptr;
1367 CurrentState().color = ToColor(c);
1368 }
1369 }
1370
1371 bool
1372 gfxContext::GetDeviceColor(gfxRGBA& c)
1373 {
1374 if (mCairo) {
1375 return cairo_pattern_get_rgba(cairo_get_source(mCairo),
1376 &c.r,
1377 &c.g,
1378 &c.b,
1379 &c.a) == CAIRO_STATUS_SUCCESS;
1380 } else {
1381 if (CurrentState().sourceSurface) {
1382 return false;
1383 }
1384 if (CurrentState().pattern) {
1385 gfxRGBA color;
1386 return CurrentState().pattern->GetSolidColor(c);
1387 }
1388
1389 c = ThebesRGBA(CurrentState().color);
1390 return true;
1391 }
1392 }
1393
1394 void
1395 gfxContext::SetSource(gfxASurface *surface, const gfxPoint& offset)
1396 {
1397 if (mCairo) {
1398 NS_ASSERTION(surface->GetAllowUseAsSource(), "Surface not allowed to be used as source!");
1399 cairo_set_source_surface(mCairo, surface->CairoSurface(), offset.x, offset.y);
1400 } else {
1401 CurrentState().surfTransform = Matrix(1.0f, 0, 0, 1.0f, Float(offset.x), Float(offset.y));
1402 CurrentState().pattern = nullptr;
1403 CurrentState().patternTransformChanged = false;
1404 // Keep the underlying cairo surface around while we keep the
1405 // sourceSurface.
1406 CurrentState().sourceSurfCairo = surface;
1407 CurrentState().sourceSurface =
1408 gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface);
1409 CurrentState().color = Color(0, 0, 0, 0);
1410 }
1411 }
1412
1413 void
1414 gfxContext::SetPattern(gfxPattern *pattern)
1415 {
1416 if (mCairo) {
1417 MOZ_ASSERT(!pattern->IsAzure());
1418 cairo_set_source(mCairo, pattern->CairoPattern());
1419 } else {
1420 CurrentState().sourceSurfCairo = nullptr;
1421 CurrentState().sourceSurface = nullptr;
1422 CurrentState().patternTransformChanged = false;
1423 CurrentState().pattern = pattern;
1424 }
1425 }
1426
1427 already_AddRefed<gfxPattern>
1428 gfxContext::GetPattern()
1429 {
1430 if (mCairo) {
1431 cairo_pattern_t *pat = cairo_get_source(mCairo);
1432 NS_ASSERTION(pat, "I was told this couldn't be null");
1433
1434 nsRefPtr<gfxPattern> wrapper;
1435 if (pat)
1436 wrapper = new gfxPattern(pat);
1437 else
1438 wrapper = new gfxPattern(gfxRGBA(0,0,0,0));
1439
1440 return wrapper.forget();
1441 } else {
1442 nsRefPtr<gfxPattern> pat;
1443
1444 AzureState &state = CurrentState();
1445 if (state.pattern) {
1446 pat = state.pattern;
1447 } else if (state.sourceSurface) {
1448 NS_ASSERTION(false, "Ugh, this isn't good.");
1449 } else {
1450 pat = new gfxPattern(ThebesRGBA(state.color));
1451 }
1452 return pat.forget();
1453 }
1454 }
1455
1456
1457 // masking
1458 void
1459 gfxContext::Mask(gfxPattern *pattern)
1460 {
1461 if (mCairo) {
1462 MOZ_ASSERT(!pattern->IsAzure());
1463 cairo_mask(mCairo, pattern->CairoPattern());
1464 } else {
1465 if (pattern->Extend() == gfxPattern::EXTEND_NONE) {
1466 // In this situation the mask will be fully transparent (i.e. nothing
1467 // will be drawn) outside of the bounds of the surface. We can support
1468 // that by clipping out drawing to that area.
1469 Point offset;
1470 if (pattern->IsAzure()) {
1471 // This is an Azure pattern. i.e. this was the result of a PopGroup and
1472 // then the extend mode was changed to EXTEND_NONE.
1473 // XXX - We may need some additional magic here in theory to support
1474 // device offsets in these patterns, but no problems have been observed
1475 // yet because of this. And it would complicate things a little further.
1476 offset = Point(0.f, 0.f);
1477 } else if (pattern->GetType() == gfxPattern::PATTERN_SURFACE) {
1478 nsRefPtr<gfxASurface> asurf = pattern->GetSurface();
1479 gfxPoint deviceOffset = asurf->GetDeviceOffset();
1480 offset = Point(-deviceOffset.x, -deviceOffset.y);
1481
1482 // this lets GetAzureSurface work
1483 pattern->GetPattern(mDT);
1484 }
1485
1486 if (pattern->IsAzure() || pattern->GetType() == gfxPattern::PATTERN_SURFACE) {
1487 RefPtr<SourceSurface> mask = pattern->GetAzureSurface();
1488 Matrix mat = ToMatrix(pattern->GetInverseMatrix());
1489 Matrix old = mTransform;
1490 // add in the inverse of the pattern transform so that when we
1491 // MaskSurface we are transformed to the place matching the pattern transform
1492 mat = mat * mTransform;
1493
1494 ChangeTransform(mat);
1495 mDT->MaskSurface(GeneralPattern(this), mask, offset, DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode));
1496 ChangeTransform(old);
1497 return;
1498 }
1499 }
1500 mDT->Mask(GeneralPattern(this), *pattern->GetPattern(mDT), DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode));
1501 }
1502 }
1503
1504 void
1505 gfxContext::Mask(gfxASurface *surface, const gfxPoint& offset)
1506 {
1507 PROFILER_LABEL("gfxContext", "Mask");
1508 if (mCairo) {
1509 cairo_mask_surface(mCairo, surface->CairoSurface(), offset.x, offset.y);
1510 } else {
1511 // Lifetime needs to be limited here as we may simply wrap surface's data.
1512 RefPtr<SourceSurface> sourceSurf =
1513 gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface);
1514
1515 if (!sourceSurf) {
1516 return;
1517 }
1518
1519 gfxPoint pt = surface->GetDeviceOffset();
1520
1521 Mask(sourceSurf, Point(offset.x - pt.x, offset.y - pt.y));
1522 }
1523 }
1524
1525 void
1526 gfxContext::Mask(SourceSurface *surface, const Point& offset)
1527 {
1528 MOZ_ASSERT(mDT);
1529
1530
1531 // We clip here to bind to the mask surface bounds, see above.
1532 mDT->MaskSurface(GeneralPattern(this),
1533 surface,
1534 offset,
1535 DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode));
1536 }
1537
1538 void
1539 gfxContext::Paint(gfxFloat alpha)
1540 {
1541 PROFILER_LABEL("gfxContext", "Paint");
1542 if (mCairo) {
1543 cairo_paint_with_alpha(mCairo, alpha);
1544 } else {
1545 AzureState &state = CurrentState();
1546
1547 if (state.sourceSurface && !state.sourceSurfCairo &&
1548 !state.patternTransformChanged && !state.opIsClear)
1549 {
1550 // This is the case where a PopGroupToSource has been done and this
1551 // paint is executed without changing the transform or the source.
1552 Matrix oldMat = mDT->GetTransform();
1553
1554 IntSize surfSize = state.sourceSurface->GetSize();
1555
1556 Matrix mat;
1557 mat.Translate(-state.deviceOffset.x, -state.deviceOffset.y);
1558 mDT->SetTransform(mat);
1559
1560 mDT->DrawSurface(state.sourceSurface,
1561 Rect(state.sourceSurfaceDeviceOffset, Size(surfSize.width, surfSize.height)),
1562 Rect(Point(), Size(surfSize.width, surfSize.height)),
1563 DrawSurfaceOptions(), DrawOptions(alpha, GetOp()));
1564 mDT->SetTransform(oldMat);
1565 return;
1566 }
1567
1568 Matrix mat = mDT->GetTransform();
1569 mat.Invert();
1570 Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize())));
1571
1572 if (state.opIsClear) {
1573 mDT->ClearRect(paintRect);
1574 } else {
1575 mDT->FillRect(paintRect, GeneralPattern(this),
1576 DrawOptions(Float(alpha), GetOp()));
1577 }
1578 }
1579 }
1580
1581 // groups
1582
1583 void
1584 gfxContext::PushGroup(gfxContentType content)
1585 {
1586 if (mCairo) {
1587 cairo_push_group_with_content(mCairo, (cairo_content_t)(int) content);
1588 } else {
1589 PushNewDT(content);
1590
1591 PushClipsToDT(mDT);
1592 mDT->SetTransform(GetDTTransform());
1593 }
1594 }
1595
1596 static gfxRect
1597 GetRoundOutDeviceClipExtents(gfxContext* aCtx)
1598 {
1599 gfxContextMatrixAutoSaveRestore save(aCtx);
1600 aCtx->IdentityMatrix();
1601 gfxRect r = aCtx->GetClipExtents();
1602 r.RoundOut();
1603 return r;
1604 }
1605
1606 /**
1607 * Copy the contents of aSrc to aDest, translated by aTranslation.
1608 */
1609 static void
1610 CopySurface(gfxASurface* aSrc, gfxASurface* aDest, const gfxPoint& aTranslation)
1611 {
1612 cairo_t *cr = cairo_create(aDest->CairoSurface());
1613 cairo_set_source_surface(cr, aSrc->CairoSurface(), aTranslation.x, aTranslation.y);
1614 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1615 cairo_paint(cr);
1616 cairo_destroy(cr);
1617 }
1618
1619 void
1620 gfxContext::PushGroupAndCopyBackground(gfxContentType content)
1621 {
1622 if (mCairo) {
1623 if (content == gfxContentType::COLOR_ALPHA &&
1624 !(GetFlags() & FLAG_DISABLE_COPY_BACKGROUND)) {
1625 nsRefPtr<gfxASurface> s = CurrentSurface();
1626 if ((s->GetAllowUseAsSource() || s->GetType() == gfxSurfaceType::Tee) &&
1627 (s->GetContentType() == gfxContentType::COLOR ||
1628 s->GetOpaqueRect().Contains(GetRoundOutDeviceClipExtents(this)))) {
1629 cairo_push_group_with_content(mCairo, CAIRO_CONTENT_COLOR);
1630 nsRefPtr<gfxASurface> d = CurrentSurface();
1631
1632 if (d->GetType() == gfxSurfaceType::Tee) {
1633 NS_ASSERTION(s->GetType() == gfxSurfaceType::Tee, "Mismatched types");
1634 nsAutoTArray<nsRefPtr<gfxASurface>,2> ss;
1635 nsAutoTArray<nsRefPtr<gfxASurface>,2> ds;
1636 static_cast<gfxTeeSurface*>(s.get())->GetSurfaces(&ss);
1637 static_cast<gfxTeeSurface*>(d.get())->GetSurfaces(&ds);
1638 NS_ASSERTION(ss.Length() == ds.Length(), "Mismatched lengths");
1639 gfxPoint translation = d->GetDeviceOffset() - s->GetDeviceOffset();
1640 for (uint32_t i = 0; i < ss.Length(); ++i) {
1641 CopySurface(ss[i], ds[i], translation);
1642 }
1643 } else {
1644 CopySurface(s, d, gfxPoint(0, 0));
1645 }
1646 d->SetOpaqueRect(s->GetOpaqueRect());
1647 return;
1648 }
1649 }
1650 } else {
1651 IntRect clipExtents;
1652 if (mDT->GetFormat() != SurfaceFormat::B8G8R8X8) {
1653 gfxRect clipRect = GetRoundOutDeviceClipExtents(this);
1654 clipExtents = IntRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
1655 }
1656 if ((mDT->GetFormat() == SurfaceFormat::B8G8R8X8 ||
1657 mDT->GetOpaqueRect().Contains(clipExtents)) &&
1658 !mDT->GetUserData(&sDontUseAsSourceKey)) {
1659 DrawTarget *oldDT = mDT;
1660 RefPtr<SourceSurface> source = mDT->Snapshot();
1661 Point oldDeviceOffset = CurrentState().deviceOffset;
1662
1663 PushNewDT(gfxContentType::COLOR);
1664
1665 Point offset = CurrentState().deviceOffset - oldDeviceOffset;
1666 Rect surfRect(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height));
1667 Rect sourceRect = surfRect;
1668 sourceRect.x += offset.x;
1669 sourceRect.y += offset.y;
1670
1671 mDT->SetTransform(Matrix());
1672 mDT->DrawSurface(source, surfRect, sourceRect);
1673 mDT->SetOpaqueRect(oldDT->GetOpaqueRect());
1674
1675 PushClipsToDT(mDT);
1676 mDT->SetTransform(GetDTTransform());
1677 return;
1678 }
1679 }
1680 PushGroup(content);
1681 }
1682
1683 already_AddRefed<gfxPattern>
1684 gfxContext::PopGroup()
1685 {
1686 if (mCairo) {
1687 cairo_pattern_t *pat = cairo_pop_group(mCairo);
1688 nsRefPtr<gfxPattern> wrapper = new gfxPattern(pat);
1689 cairo_pattern_destroy(pat);
1690 return wrapper.forget();
1691 } else {
1692 RefPtr<SourceSurface> src = mDT->Snapshot();
1693 Point deviceOffset = CurrentState().deviceOffset;
1694
1695 Restore();
1696
1697 Matrix mat = mTransform;
1698 mat.Invert();
1699
1700 Matrix deviceOffsetTranslation;
1701 deviceOffsetTranslation.Translate(deviceOffset.x, deviceOffset.y);
1702
1703 nsRefPtr<gfxPattern> pat = new gfxPattern(src, deviceOffsetTranslation * mat);
1704
1705 return pat.forget();
1706 }
1707 }
1708
1709 void
1710 gfxContext::PopGroupToSource()
1711 {
1712 if (mCairo) {
1713 cairo_pop_group_to_source(mCairo);
1714 } else {
1715 RefPtr<SourceSurface> src = mDT->Snapshot();
1716 Point deviceOffset = CurrentState().deviceOffset;
1717 Restore();
1718 CurrentState().sourceSurfCairo = nullptr;
1719 CurrentState().sourceSurface = src;
1720 CurrentState().sourceSurfaceDeviceOffset = deviceOffset;
1721 CurrentState().pattern = nullptr;
1722 CurrentState().patternTransformChanged = false;
1723
1724 Matrix mat = mTransform;
1725 mat.Invert();
1726
1727 Matrix deviceOffsetTranslation;
1728 deviceOffsetTranslation.Translate(deviceOffset.x, deviceOffset.y);
1729 CurrentState().surfTransform = deviceOffsetTranslation * mat;
1730 }
1731 }
1732
1733 bool
1734 gfxContext::PointInFill(const gfxPoint& pt)
1735 {
1736 if (mCairo) {
1737 return cairo_in_fill(mCairo, pt.x, pt.y);
1738 } else {
1739 EnsurePath();
1740 return mPath->ContainsPoint(ToPoint(pt), Matrix());
1741 }
1742 }
1743
1744 bool
1745 gfxContext::PointInStroke(const gfxPoint& pt)
1746 {
1747 if (mCairo) {
1748 return cairo_in_stroke(mCairo, pt.x, pt.y);
1749 } else {
1750 EnsurePath();
1751 return mPath->StrokeContainsPoint(CurrentState().strokeOptions,
1752 ToPoint(pt),
1753 Matrix());
1754 }
1755 }
1756
1757 gfxRect
1758 gfxContext::GetUserPathExtent()
1759 {
1760 if (mCairo) {
1761 double xmin, ymin, xmax, ymax;
1762 cairo_path_extents(mCairo, &xmin, &ymin, &xmax, &ymax);
1763 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
1764 } else {
1765 EnsurePath();
1766 return ThebesRect(mPath->GetBounds());
1767 }
1768 }
1769
1770 gfxRect
1771 gfxContext::GetUserFillExtent()
1772 {
1773 if (mCairo) {
1774 double xmin, ymin, xmax, ymax;
1775 cairo_fill_extents(mCairo, &xmin, &ymin, &xmax, &ymax);
1776 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
1777 } else {
1778 EnsurePath();
1779 return ThebesRect(mPath->GetBounds());
1780 }
1781 }
1782
1783 gfxRect
1784 gfxContext::GetUserStrokeExtent()
1785 {
1786 if (mCairo) {
1787 double xmin, ymin, xmax, ymax;
1788 cairo_stroke_extents(mCairo, &xmin, &ymin, &xmax, &ymax);
1789 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
1790 } else {
1791 EnsurePath();
1792 return ThebesRect(mPath->GetStrokedBounds(CurrentState().strokeOptions, mTransform));
1793 }
1794 }
1795
1796 bool
1797 gfxContext::HasError()
1798 {
1799 if (mCairo) {
1800 return cairo_status(mCairo) != CAIRO_STATUS_SUCCESS;
1801 } else {
1802 // As far as this is concerned, an Azure context is never in error.
1803 return false;
1804 }
1805 }
1806
1807 void
1808 gfxContext::RoundedRectangle(const gfxRect& rect,
1809 const gfxCornerSizes& corners,
1810 bool draw_clockwise)
1811 {
1812 //
1813 // For CW drawing, this looks like:
1814 //
1815 // ...******0** 1 C
1816 // ****
1817 // *** 2
1818 // **
1819 // *
1820 // *
1821 // 3
1822 // *
1823 // *
1824 //
1825 // Where 0, 1, 2, 3 are the control points of the Bezier curve for
1826 // the corner, and C is the actual corner point.
1827 //
1828 // At the start of the loop, the current point is assumed to be
1829 // the point adjacent to the top left corner on the top
1830 // horizontal. Note that corner indices start at the top left and
1831 // continue clockwise, whereas in our loop i = 0 refers to the top
1832 // right corner.
1833 //
1834 // When going CCW, the control points are swapped, and the first
1835 // corner that's drawn is the top left (along with the top segment).
1836 //
1837 // There is considerable latitude in how one chooses the four
1838 // control points for a Bezier curve approximation to an ellipse.
1839 // For the overall path to be continuous and show no corner at the
1840 // endpoints of the arc, points 0 and 3 must be at the ends of the
1841 // straight segments of the rectangle; points 0, 1, and C must be
1842 // collinear; and points 3, 2, and C must also be collinear. This
1843 // leaves only two free parameters: the ratio of the line segments
1844 // 01 and 0C, and the ratio of the line segments 32 and 3C. See
1845 // the following papers for extensive discussion of how to choose
1846 // these ratios:
1847 //
1848 // Dokken, Tor, et al. "Good approximation of circles by
1849 // curvature-continuous Bezier curves." Computer-Aided
1850 // Geometric Design 7(1990) 33--41.
1851 // Goldapp, Michael. "Approximation of circular arcs by cubic
1852 // polynomials." Computer-Aided Geometric Design 8(1991) 227--238.
1853 // Maisonobe, Luc. "Drawing an elliptical arc using polylines,
1854 // quadratic, or cubic Bezier curves."
1855 // http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf
1856 //
1857 // We follow the approach in section 2 of Goldapp (least-error,
1858 // Hermite-type approximation) and make both ratios equal to
1859 //
1860 // 2 2 + n - sqrt(2n + 28)
1861 // alpha = - * ---------------------
1862 // 3 n - 4
1863 //
1864 // where n = 3( cbrt(sqrt(2)+1) - cbrt(sqrt(2)-1) ).
1865 //
1866 // This is the result of Goldapp's equation (10b) when the angle
1867 // swept out by the arc is pi/2, and the parameter "a-bar" is the
1868 // expression given immediately below equation (21).
1869 //
1870 // Using this value, the maximum radial error for a circle, as a
1871 // fraction of the radius, is on the order of 0.2 x 10^-3.
1872 // Neither Dokken nor Goldapp discusses error for a general
1873 // ellipse; Maisonobe does, but his choice of control points
1874 // follows different constraints, and Goldapp's expression for
1875 // 'alpha' gives much smaller radial error, even for very flat
1876 // ellipses, than Maisonobe's equivalent.
1877 //
1878 // For the various corners and for each axis, the sign of this
1879 // constant changes, or it might be 0 -- it's multiplied by the
1880 // appropriate multiplier from the list before using.
1881
1882 if (mCairo) {
1883 const gfxFloat alpha = 0.55191497064665766025;
1884
1885 typedef struct { gfxFloat a, b; } twoFloats;
1886
1887 twoFloats cwCornerMults[4] = { { -1, 0 },
1888 { 0, -1 },
1889 { +1, 0 },
1890 { 0, +1 } };
1891 twoFloats ccwCornerMults[4] = { { +1, 0 },
1892 { 0, -1 },
1893 { -1, 0 },
1894 { 0, +1 } };
1895
1896 twoFloats *cornerMults = draw_clockwise ? cwCornerMults : ccwCornerMults;
1897
1898 gfxPoint pc, p0, p1, p2, p3;
1899
1900 if (draw_clockwise)
1901 cairo_move_to(mCairo, rect.X() + corners[NS_CORNER_TOP_LEFT].width, rect.Y());
1902 else
1903 cairo_move_to(mCairo, rect.X() + rect.Width() - corners[NS_CORNER_TOP_RIGHT].width, rect.Y());
1904
1905 NS_FOR_CSS_CORNERS(i) {
1906 // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
1907 mozilla::css::Corner c = mozilla::css::Corner(draw_clockwise ? ((i+1) % 4) : ((4-i) % 4));
1908
1909 // i+2 and i+3 respectively. These are used to index into the corner
1910 // multiplier table, and were deduced by calculating out the long form
1911 // of each corner and finding a pattern in the signs and values.
1912 int i2 = (i+2) % 4;
1913 int i3 = (i+3) % 4;
1914
1915 pc = rect.AtCorner(c);
1916
1917 if (corners[c].width > 0.0 && corners[c].height > 0.0) {
1918 p0.x = pc.x + cornerMults[i].a * corners[c].width;
1919 p0.y = pc.y + cornerMults[i].b * corners[c].height;
1920
1921 p3.x = pc.x + cornerMults[i3].a * corners[c].width;
1922 p3.y = pc.y + cornerMults[i3].b * corners[c].height;
1923
1924 p1.x = p0.x + alpha * cornerMults[i2].a * corners[c].width;
1925 p1.y = p0.y + alpha * cornerMults[i2].b * corners[c].height;
1926
1927 p2.x = p3.x - alpha * cornerMults[i3].a * corners[c].width;
1928 p2.y = p3.y - alpha * cornerMults[i3].b * corners[c].height;
1929
1930 cairo_line_to (mCairo, p0.x, p0.y);
1931 cairo_curve_to (mCairo,
1932 p1.x, p1.y,
1933 p2.x, p2.y,
1934 p3.x, p3.y);
1935 } else {
1936 cairo_line_to (mCairo, pc.x, pc.y);
1937 }
1938 }
1939
1940 cairo_close_path (mCairo);
1941 } else {
1942 EnsurePathBuilder();
1943 Size radii[] = { ToSize(corners[NS_CORNER_TOP_LEFT]),
1944 ToSize(corners[NS_CORNER_TOP_RIGHT]),
1945 ToSize(corners[NS_CORNER_BOTTOM_RIGHT]),
1946 ToSize(corners[NS_CORNER_BOTTOM_LEFT]) };
1947 AppendRoundedRectToPath(mPathBuilder, ToRect(rect), radii, draw_clockwise);
1948 }
1949 }
1950
1951 #ifdef MOZ_DUMP_PAINTING
1952 void
1953 gfxContext::WriteAsPNG(const char* aFile)
1954 {
1955 nsRefPtr<gfxASurface> surf = CurrentSurface();
1956 if (surf) {
1957 surf->WriteAsPNG(aFile);
1958 } else {
1959 NS_WARNING("No surface found!");
1960 }
1961 }
1962
1963 void
1964 gfxContext::DumpAsDataURL()
1965 {
1966 nsRefPtr<gfxASurface> surf = CurrentSurface();
1967 if (surf) {
1968 surf->DumpAsDataURL();
1969 } else {
1970 NS_WARNING("No surface found!");
1971 }
1972 }
1973
1974 void
1975 gfxContext::CopyAsDataURL()
1976 {
1977 nsRefPtr<gfxASurface> surf = CurrentSurface();
1978 if (surf) {
1979 surf->CopyAsDataURL();
1980 } else {
1981 NS_WARNING("No surface found!");
1982 }
1983 }
1984 #endif
1985
1986 void
1987 gfxContext::EnsurePath()
1988 {
1989 if (mPathBuilder) {
1990 mPath = mPathBuilder->Finish();
1991 mPathBuilder = nullptr;
1992 }
1993
1994 if (mPath) {
1995 if (mTransformChanged) {
1996 Matrix mat = mTransform;
1997 mat.Invert();
1998 mat = mPathTransform * mat;
1999 mPathBuilder = mPath->TransformedCopyToBuilder(mat, CurrentState().fillRule);
2000 mPath = mPathBuilder->Finish();
2001 mPathBuilder = nullptr;
2002
2003 mTransformChanged = false;
2004 }
2005
2006 if (CurrentState().fillRule == mPath->GetFillRule()) {
2007 return;
2008 }
2009
2010 mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
2011
2012 mPath = mPathBuilder->Finish();
2013 mPathBuilder = nullptr;
2014 return;
2015 }
2016
2017 EnsurePathBuilder();
2018 mPath = mPathBuilder->Finish();
2019 mPathBuilder = nullptr;
2020 }
2021
2022 void
2023 gfxContext::EnsurePathBuilder()
2024 {
2025 if (mPathBuilder && !mTransformChanged) {
2026 return;
2027 }
2028
2029 if (mPath) {
2030 if (!mTransformChanged) {
2031 mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
2032 mPath = nullptr;
2033 } else {
2034 Matrix invTransform = mTransform;
2035 invTransform.Invert();
2036 Matrix toNewUS = mPathTransform * invTransform;
2037 mPathBuilder = mPath->TransformedCopyToBuilder(toNewUS, CurrentState().fillRule);
2038 }
2039 return;
2040 }
2041
2042 DebugOnly<PathBuilder*> oldPath = mPathBuilder.get();
2043
2044 if (!mPathBuilder) {
2045 mPathBuilder = mDT->CreatePathBuilder(CurrentState().fillRule);
2046
2047 if (mPathIsRect) {
2048 mPathBuilder->MoveTo(mRect.TopLeft());
2049 mPathBuilder->LineTo(mRect.TopRight());
2050 mPathBuilder->LineTo(mRect.BottomRight());
2051 mPathBuilder->LineTo(mRect.BottomLeft());
2052 mPathBuilder->Close();
2053 }
2054 }
2055
2056 if (mTransformChanged) {
2057 // This could be an else if since this should never happen when
2058 // mPathBuilder is nullptr and mPath is nullptr. But this way we can
2059 // assert if all the state is as expected.
2060 MOZ_ASSERT(oldPath);
2061 MOZ_ASSERT(!mPathIsRect);
2062
2063 Matrix invTransform = mTransform;
2064 invTransform.Invert();
2065 Matrix toNewUS = mPathTransform * invTransform;
2066
2067 RefPtr<Path> path = mPathBuilder->Finish();
2068 mPathBuilder = path->TransformedCopyToBuilder(toNewUS, CurrentState().fillRule);
2069 }
2070
2071 mPathIsRect = false;
2072 }
2073
2074 void
2075 gfxContext::FillAzure(Float aOpacity)
2076 {
2077 AzureState &state = CurrentState();
2078
2079 CompositionOp op = GetOp();
2080
2081 if (mPathIsRect) {
2082 MOZ_ASSERT(!mTransformChanged);
2083
2084 if (state.opIsClear) {
2085 mDT->ClearRect(mRect);
2086 } else if (op == CompositionOp::OP_SOURCE) {
2087 // Emulate cairo operator source which is bound by mask!
2088 mDT->ClearRect(mRect);
2089 mDT->FillRect(mRect, GeneralPattern(this), DrawOptions(aOpacity));
2090 } else {
2091 mDT->FillRect(mRect, GeneralPattern(this), DrawOptions(aOpacity, op, state.aaMode));
2092 }
2093 } else {
2094 EnsurePath();
2095
2096 NS_ASSERTION(!state.opIsClear, "We shouldn't be clearing complex paths!");
2097
2098 mDT->Fill(mPath, GeneralPattern(this), DrawOptions(aOpacity, op, state.aaMode));
2099 }
2100 }
2101
2102 void
2103 gfxContext::PushClipsToDT(DrawTarget *aDT)
2104 {
2105 // Tricky, we have to restore all clips -since the last time- the clip
2106 // was reset. If we didn't reset the clip, just popping the clips we
2107 // added was fine.
2108 unsigned int lastReset = 0;
2109 for (int i = mStateStack.Length() - 2; i > 0; i--) {
2110 if (mStateStack[i].clipWasReset) {
2111 lastReset = i;
2112 break;
2113 }
2114 }
2115
2116 // Don't need to save the old transform, we'll be setting a new one soon!
2117
2118 // Push all clips from the last state on the stack where the clip was
2119 // reset to the clip before ours.
2120 for (unsigned int i = lastReset; i < mStateStack.Length() - 1; i++) {
2121 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
2122 aDT->SetTransform(mStateStack[i].pushedClips[c].transform * GetDeviceTransform());
2123 if (mStateStack[i].pushedClips[c].path) {
2124 aDT->PushClip(mStateStack[i].pushedClips[c].path);
2125 } else {
2126 aDT->PushClipRect(mStateStack[i].pushedClips[c].rect);
2127 }
2128 }
2129 }
2130 }
2131
2132 CompositionOp
2133 gfxContext::GetOp()
2134 {
2135 if (CurrentState().op != CompositionOp::OP_SOURCE) {
2136 return CurrentState().op;
2137 }
2138
2139 AzureState &state = CurrentState();
2140 if (state.pattern) {
2141 if (state.pattern->IsOpaque()) {
2142 return CompositionOp::OP_OVER;
2143 } else {
2144 return CompositionOp::OP_SOURCE;
2145 }
2146 } else if (state.sourceSurface) {
2147 if (state.sourceSurface->GetFormat() == SurfaceFormat::B8G8R8X8) {
2148 return CompositionOp::OP_OVER;
2149 } else {
2150 return CompositionOp::OP_SOURCE;
2151 }
2152 } else {
2153 if (state.color.a > 0.999) {
2154 return CompositionOp::OP_OVER;
2155 } else {
2156 return CompositionOp::OP_SOURCE;
2157 }
2158 }
2159 }
2160
2161 /* SVG font code can change the transform after having set the pattern on the
2162 * context. When the pattern is set it is in user space, if the transform is
2163 * changed after doing so the pattern needs to be converted back into userspace.
2164 * We just store the old pattern transform here so that we only do the work
2165 * needed here if the pattern is actually used.
2166 * We need to avoid doing this when this ChangeTransform comes from a restore,
2167 * since the current pattern and the current transform are both part of the
2168 * state we know the new CurrentState()'s values are valid. But if we assume
2169 * a change they might become invalid since patternTransformChanged is part of
2170 * the state and might be false for the restored AzureState.
2171 */
2172 void
2173 gfxContext::ChangeTransform(const Matrix &aNewMatrix, bool aUpdatePatternTransform)
2174 {
2175 AzureState &state = CurrentState();
2176
2177 if (aUpdatePatternTransform && (state.pattern || state.sourceSurface)
2178 && !state.patternTransformChanged) {
2179 state.patternTransform = GetDTTransform();
2180 state.patternTransformChanged = true;
2181 }
2182
2183 if (mPathIsRect) {
2184 Matrix invMatrix = aNewMatrix;
2185
2186 invMatrix.Invert();
2187
2188 Matrix toNewUS = mTransform * invMatrix;
2189
2190 if (toNewUS.IsRectilinear()) {
2191 mRect = toNewUS.TransformBounds(mRect);
2192 mRect.NudgeToIntegers();
2193 } else {
2194 mPathBuilder = mDT->CreatePathBuilder(CurrentState().fillRule);
2195
2196 mPathBuilder->MoveTo(toNewUS * mRect.TopLeft());
2197 mPathBuilder->LineTo(toNewUS * mRect.TopRight());
2198 mPathBuilder->LineTo(toNewUS * mRect.BottomRight());
2199 mPathBuilder->LineTo(toNewUS * mRect.BottomLeft());
2200 mPathBuilder->Close();
2201
2202 mPathIsRect = false;
2203 }
2204
2205 // No need to consider the transform changed now!
2206 mTransformChanged = false;
2207 } else if ((mPath || mPathBuilder) && !mTransformChanged) {
2208 mTransformChanged = true;
2209 mPathTransform = mTransform;
2210 }
2211
2212 mTransform = aNewMatrix;
2213
2214 mDT->SetTransform(GetDTTransform());
2215 }
2216
2217 Rect
2218 gfxContext::GetAzureDeviceSpaceClipBounds()
2219 {
2220 unsigned int lastReset = 0;
2221 for (int i = mStateStack.Length() - 1; i > 0; i--) {
2222 if (mStateStack[i].clipWasReset) {
2223 lastReset = i;
2224 break;
2225 }
2226 }
2227
2228 Rect rect(CurrentState().deviceOffset.x, CurrentState().deviceOffset.y,
2229 Float(mDT->GetSize().width), Float(mDT->GetSize().height));
2230 for (unsigned int i = lastReset; i < mStateStack.Length(); i++) {
2231 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
2232 AzureState::PushedClip &clip = mStateStack[i].pushedClips[c];
2233 if (clip.path) {
2234 Rect bounds = clip.path->GetBounds(clip.transform);
2235 rect.IntersectRect(rect, bounds);
2236 } else {
2237 rect.IntersectRect(rect, clip.transform.TransformBounds(clip.rect));
2238 }
2239 }
2240 }
2241
2242 return rect;
2243 }
2244
2245 Point
2246 gfxContext::GetDeviceOffset() const
2247 {
2248 return CurrentState().deviceOffset;
2249 }
2250
2251 Matrix
2252 gfxContext::GetDeviceTransform() const
2253 {
2254 Matrix mat;
2255 mat.Translate(-CurrentState().deviceOffset.x, -CurrentState().deviceOffset.y);
2256 return mat;
2257 }
2258
2259 Matrix
2260 gfxContext::GetDTTransform() const
2261 {
2262 Matrix mat = mTransform;
2263 mat._31 -= CurrentState().deviceOffset.x;
2264 mat._32 -= CurrentState().deviceOffset.y;
2265 return mat;
2266 }
2267
2268 void
2269 gfxContext::PushNewDT(gfxContentType content)
2270 {
2271 Rect clipBounds = GetAzureDeviceSpaceClipBounds();
2272 clipBounds.RoundOut();
2273
2274 clipBounds.width = std::max(1.0f, clipBounds.width);
2275 clipBounds.height = std::max(1.0f, clipBounds.height);
2276
2277 SurfaceFormat format = gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content);
2278
2279 RefPtr<DrawTarget> newDT =
2280 mDT->CreateSimilarDrawTarget(IntSize(int32_t(clipBounds.width), int32_t(clipBounds.height)),
2281 format);
2282
2283 if (!newDT) {
2284 NS_WARNING("Failed to create DrawTarget of sufficient size.");
2285 newDT = mDT->CreateSimilarDrawTarget(IntSize(64, 64), format);
2286
2287 if (!newDT) {
2288 // If even this fails.. we're most likely just out of memory!
2289 NS_ABORT_OOM(BytesPerPixel(format) * 64 * 64);
2290 }
2291 }
2292
2293 Save();
2294
2295 CurrentState().drawTarget = newDT;
2296 CurrentState().deviceOffset = clipBounds.TopLeft();
2297
2298 mDT = newDT;
2299 }
2300
2301 /**
2302 * Work out whether cairo will snap inter-glyph spacing to pixels.
2303 *
2304 * Layout does not align text to pixel boundaries, so, with font drawing
2305 * backends that snap glyph positions to pixels, it is important that
2306 * inter-glyph spacing within words is always an integer number of pixels.
2307 * This ensures that the drawing backend snaps all of the word's glyphs in the
2308 * same direction and so inter-glyph spacing remains the same.
2309 */
2310 void
2311 gfxContext::GetRoundOffsetsToPixels(bool *aRoundX, bool *aRoundY)
2312 {
2313 *aRoundX = false;
2314 // Could do something fancy here for ScaleFactors of
2315 // AxisAlignedTransforms, but we leave things simple.
2316 // Not much point rounding if a matrix will mess things up anyway.
2317 // Also return false for non-cairo contexts.
2318 if (CurrentMatrix().HasNonTranslation() || mDT) {
2319 *aRoundY = false;
2320 return;
2321 }
2322
2323 // All raster backends snap glyphs to pixels vertically.
2324 // Print backends set CAIRO_HINT_METRICS_OFF.
2325 *aRoundY = true;
2326
2327 cairo_t *cr = GetCairo();
2328 cairo_scaled_font_t *scaled_font = cairo_get_scaled_font(cr);
2329 // Sometimes hint metrics gets set for us, most notably for printing.
2330 cairo_font_options_t *font_options = cairo_font_options_create();
2331 cairo_scaled_font_get_font_options(scaled_font, font_options);
2332 cairo_hint_metrics_t hint_metrics =
2333 cairo_font_options_get_hint_metrics(font_options);
2334 cairo_font_options_destroy(font_options);
2335
2336 switch (hint_metrics) {
2337 case CAIRO_HINT_METRICS_OFF:
2338 *aRoundY = false;
2339 return;
2340 case CAIRO_HINT_METRICS_DEFAULT:
2341 // Here we mimic what cairo surface/font backends do. Printing
2342 // surfaces have already been handled by hint_metrics. The
2343 // fallback show_glyphs implementation composites pixel-aligned
2344 // glyph surfaces, so we just pick surface/font combinations that
2345 // override this.
2346 switch (cairo_scaled_font_get_type(scaled_font)) {
2347 #if CAIRO_HAS_DWRITE_FONT // dwrite backend is not in std cairo releases yet
2348 case CAIRO_FONT_TYPE_DWRITE:
2349 // show_glyphs is implemented on the font and so is used for
2350 // all surface types; however, it may pixel-snap depending on
2351 // the dwrite rendering mode
2352 if (!cairo_dwrite_scaled_font_get_force_GDI_classic(scaled_font) &&
2353 gfxWindowsPlatform::GetPlatform()->DWriteMeasuringMode() ==
2354 DWRITE_MEASURING_MODE_NATURAL) {
2355 return;
2356 }
2357 #endif
2358 case CAIRO_FONT_TYPE_QUARTZ:
2359 // Quartz surfaces implement show_glyphs for Quartz fonts
2360 if (cairo_surface_get_type(cairo_get_target(cr)) ==
2361 CAIRO_SURFACE_TYPE_QUARTZ) {
2362 return;
2363 }
2364 default:
2365 break;
2366 }
2367 // fall through:
2368 case CAIRO_HINT_METRICS_ON:
2369 break;
2370 }
2371 *aRoundX = true;
2372 return;
2373 }

mercurial