Merge m-c to inbound.
[gecko.git] / gfx / thebes / gfxContext.cpp
blob8cb8a43be066f6c3602764d473730129cbd0cc0d
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/. */
6 #ifdef _MSC_VER
7 #define _USE_MATH_DEFINES
8 #endif
9 #include <math.h>
11 #include "mozilla/Alignment.h"
13 #include "cairo.h"
15 #include "gfxContext.h"
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>
28 #if CAIRO_HAS_DWRITE_FONT
29 #include "gfxWindowsPlatform.h"
30 #endif
32 using namespace mozilla;
33 using namespace mozilla::gfx;
35 UserDataKey gfxContext::sDontUseAsSourceKey;
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.
40 class GeneralPattern
42 public:
43 GeneralPattern(gfxContext *aContext) : mContext(aContext), mPattern(nullptr) {}
44 ~GeneralPattern() { if (mPattern) { mPattern->~Pattern(); } }
46 operator mozilla::gfx::Pattern&()
48 gfxContext::AzureState &state = mContext->CurrentState();
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;
55 if (state.patternTransformChanged) {
56 Matrix mat = mContext->mTransform;
57 mat.Invert();
59 transform = transform * state.patternTransform * mat;
62 mPattern = new (mSurfacePattern.addr())
63 SurfacePattern(state.sourceSurface, EXTEND_CLAMP, transform);
64 return *mPattern;
65 } else {
66 mPattern = new (mColorPattern.addr())
67 ColorPattern(state.color);
68 return *mPattern;
72 private:
73 union {
74 mozilla::AlignedStorage2<mozilla::gfx::ColorPattern> mColorPattern;
75 mozilla::AlignedStorage2<mozilla::gfx::SurfacePattern> mSurfacePattern;
78 gfxContext *mContext;
79 Pattern *mPattern;
82 gfxContext::gfxContext(gfxASurface *surface)
83 : mRefCairo(nullptr)
84 , mSurface(surface)
86 MOZ_COUNT_CTOR(gfxContext);
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);
101 gfxContext::gfxContext(DrawTarget *aTarget)
102 : mPathIsRect(false)
103 , mTransformChanged(false)
104 , mCairo(nullptr)
105 , mRefCairo(nullptr)
106 , mSurface(nullptr)
107 , mFlags(0)
108 , mDT(aTarget)
109 , mOriginalDT(aTarget)
111 MOZ_COUNT_CTOR(gfxContext);
113 mStateStack.SetLength(1);
114 CurrentState().drawTarget = mDT;
115 mDT->SetTransform(Matrix());
118 gfxContext::~gfxContext()
120 if (mCairo) {
121 cairo_destroy(mCairo);
123 if (mRefCairo) {
124 cairo_destroy(mRefCairo);
126 if (mDT) {
127 for (int i = mStateStack.Length() - 1; i >= 0; i--) {
128 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
129 mDT->PopClip();
132 if (mStateStack[i].clipWasReset) {
133 break;
136 mDT->Flush();
138 MOZ_COUNT_DTOR(gfxContext);
141 gfxASurface *
142 gfxContext::OriginalSurface()
144 if (mCairo || mSurface) {
145 return mSurface;
148 if (mOriginalDT && mOriginalDT->GetType() == BACKEND_CAIRO) {
149 cairo_surface_t *s =
150 (cairo_surface_t*)mOriginalDT->GetNativeSurface(NATIVE_SURFACE_CAIRO_SURFACE);
151 if (s) {
152 mSurface = gfxASurface::Wrap(s);
153 return mSurface;
156 return nullptr;
159 already_AddRefed<gfxASurface>
160 gfxContext::CurrentSurface(gfxFloat *dx, gfxFloat *dy)
162 if (mCairo) {
163 cairo_surface_t *s = cairo_get_group_target(mCairo);
164 if (s == mSurface->CairoSurface()) {
165 if (dx && dy)
166 cairo_surface_get_device_offset(s, dx, dy);
167 nsRefPtr<gfxASurface> ret = mSurface;
168 return ret.forget();
171 if (dx && dy)
172 cairo_surface_get_device_offset(s, dx, dy);
173 return gfxASurface::Wrap(s);
174 } else {
175 if (mDT->GetType() == BACKEND_CAIRO) {
176 cairo_surface_t *s =
177 (cairo_surface_t*)mDT->GetNativeSurface(NATIVE_SURFACE_CAIRO_SURFACE);
178 if (s) {
179 if (dx && dy)
180 cairo_surface_get_device_offset(s, dx, dy);
181 return gfxASurface::Wrap(s);
185 if (dx && dy) {
186 *dx = *dy = 0;
188 // An Azure context doesn't have a surface backing it.
189 return nullptr;
193 cairo_t *
194 gfxContext::GetCairo()
196 if (mCairo) {
197 return mCairo;
200 if (mDT->GetType() == BACKEND_CAIRO) {
201 cairo_t *ctx =
202 (cairo_t*)mDT->GetNativeSurface(NATIVE_SURFACE_CAIRO_CONTEXT);
203 if (ctx) {
204 return ctx;
208 if (mRefCairo) {
209 // Set transform!
210 return mRefCairo;
213 mRefCairo = cairo_create(gfxPlatform::GetPlatform()->ScreenReferenceSurface()->CairoSurface());
215 return mRefCairo;
218 void
219 gfxContext::Save()
221 if (mCairo) {
222 cairo_save(mCairo);
223 } else {
224 CurrentState().transform = mTransform;
225 mStateStack.AppendElement(AzureState(CurrentState()));
226 CurrentState().clipWasReset = false;
227 CurrentState().pushedClips.Clear();
231 void
232 gfxContext::Restore()
234 if (mCairo) {
235 cairo_restore(mCairo);
236 } else {
237 for (unsigned int c = 0; c < CurrentState().pushedClips.Length(); c++) {
238 mDT->PopClip();
241 if (CurrentState().clipWasReset &&
242 CurrentState().drawTarget == mStateStack[mStateStack.Length() - 2].drawTarget) {
243 PushClipsToDT(mDT);
246 mStateStack.RemoveElementAt(mStateStack.Length() - 1);
248 mDT = CurrentState().drawTarget;
250 ChangeTransform(CurrentState().transform, false);
254 // drawing
255 void
256 gfxContext::NewPath()
258 if (mCairo) {
259 cairo_new_path(mCairo);
260 } else {
261 mPath = nullptr;
262 mPathBuilder = nullptr;
263 mPathIsRect = false;
264 mTransformChanged = false;
268 void
269 gfxContext::ClosePath()
271 if (mCairo) {
272 cairo_close_path(mCairo);
273 } else {
274 EnsurePathBuilder();
275 mPathBuilder->Close();
279 already_AddRefed<gfxPath> gfxContext::CopyPath()
281 nsRefPtr<gfxPath> path;
282 if (mCairo) {
283 path = new gfxPath(cairo_copy_path(mCairo));
284 } else {
285 EnsurePath();
286 path = new gfxPath(mPath);
288 return path.forget();
291 void gfxContext::SetPath(gfxPath* path)
293 if (mCairo) {
294 cairo_new_path(mCairo);
295 if (path->mPath->status == CAIRO_STATUS_SUCCESS && path->mPath->num_data != 0)
296 cairo_append_path(mCairo, path->mPath);
297 } else {
298 MOZ_ASSERT(path->mMoz2DPath, "Can't mix cairo and azure paths!");
299 MOZ_ASSERT(path->mMoz2DPath->GetBackendType() == mDT->GetType());
300 mPath = path->mMoz2DPath;
301 mPathBuilder = nullptr;
302 mPathIsRect = false;
303 mTransformChanged = false;
307 gfxPoint
308 gfxContext::CurrentPoint()
310 if (mCairo) {
311 double x, y;
312 cairo_get_current_point(mCairo, &x, &y);
313 return gfxPoint(x, y);
314 } else {
315 EnsurePathBuilder();
316 return ThebesPoint(mPathBuilder->CurrentPoint());
320 void
321 gfxContext::Stroke()
323 if (mCairo) {
324 cairo_stroke_preserve(mCairo);
325 } else {
326 AzureState &state = CurrentState();
327 if (mPathIsRect) {
328 MOZ_ASSERT(!mTransformChanged);
330 mDT->StrokeRect(mRect, GeneralPattern(this),
331 state.strokeOptions,
332 DrawOptions(1.0f, GetOp(), state.aaMode));
333 } else {
334 EnsurePath();
336 mDT->Stroke(mPath, GeneralPattern(this), state.strokeOptions,
337 DrawOptions(1.0f, GetOp(), state.aaMode));
342 void
343 gfxContext::Fill()
345 PROFILER_LABEL("gfxContext", "Fill");
346 if (mCairo) {
347 cairo_fill_preserve(mCairo);
348 } else {
349 FillAzure(1.0f);
353 void
354 gfxContext::FillWithOpacity(gfxFloat aOpacity)
356 if (mCairo) {
357 // This method exists in the hope that one day cairo gets a direct
358 // API for this, and then we would change this method to use that
359 // API instead.
360 if (aOpacity != 1.0) {
361 gfxContextAutoSaveRestore saveRestore(this);
362 Clip();
363 Paint(aOpacity);
364 } else {
365 Fill();
367 } else {
368 FillAzure(Float(aOpacity));
372 void
373 gfxContext::MoveTo(const gfxPoint& pt)
375 if (mCairo) {
376 cairo_move_to(mCairo, pt.x, pt.y);
377 } else {
378 EnsurePathBuilder();
379 mPathBuilder->MoveTo(ToPoint(pt));
383 void
384 gfxContext::NewSubPath()
386 if (mCairo) {
387 cairo_new_sub_path(mCairo);
388 } else {
389 // XXX - This has no users, we should kill it, it should be equivelant to a
390 // MoveTo to the path's current point.
394 void
395 gfxContext::LineTo(const gfxPoint& pt)
397 if (mCairo) {
398 cairo_line_to(mCairo, pt.x, pt.y);
399 } else {
400 EnsurePathBuilder();
401 mPathBuilder->LineTo(ToPoint(pt));
405 void
406 gfxContext::CurveTo(const gfxPoint& pt1, const gfxPoint& pt2, const gfxPoint& pt3)
408 if (mCairo) {
409 cairo_curve_to(mCairo, pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y);
410 } else {
411 EnsurePathBuilder();
412 mPathBuilder->BezierTo(ToPoint(pt1), ToPoint(pt2), ToPoint(pt3));
416 void
417 gfxContext::QuadraticCurveTo(const gfxPoint& pt1, const gfxPoint& pt2)
419 if (mCairo) {
420 double cx, cy;
421 cairo_get_current_point(mCairo, &cx, &cy);
422 cairo_curve_to(mCairo,
423 (cx + pt1.x * 2.0) / 3.0,
424 (cy + pt1.y * 2.0) / 3.0,
425 (pt1.x * 2.0 + pt2.x) / 3.0,
426 (pt1.y * 2.0 + pt2.y) / 3.0,
427 pt2.x,
428 pt2.y);
429 } else {
430 EnsurePathBuilder();
431 mPathBuilder->QuadraticBezierTo(ToPoint(pt1), ToPoint(pt2));
435 void
436 gfxContext::Arc(const gfxPoint& center, gfxFloat radius,
437 gfxFloat angle1, gfxFloat angle2)
439 if (mCairo) {
440 cairo_arc(mCairo, center.x, center.y, radius, angle1, angle2);
441 } else {
442 EnsurePathBuilder();
443 mPathBuilder->Arc(ToPoint(center), Float(radius), Float(angle1), Float(angle2));
447 void
448 gfxContext::NegativeArc(const gfxPoint& center, gfxFloat radius,
449 gfxFloat angle1, gfxFloat angle2)
451 if (mCairo) {
452 cairo_arc_negative(mCairo, center.x, center.y, radius, angle1, angle2);
453 } else {
454 EnsurePathBuilder();
455 mPathBuilder->Arc(ToPoint(center), Float(radius), Float(angle2), Float(angle1));
459 void
460 gfxContext::Line(const gfxPoint& start, const gfxPoint& end)
462 if (mCairo) {
463 MoveTo(start);
464 LineTo(end);
465 } else {
466 EnsurePathBuilder();
467 mPathBuilder->MoveTo(ToPoint(start));
468 mPathBuilder->LineTo(ToPoint(end));
472 // XXX snapToPixels is only valid when snapping for filled
473 // rectangles and for even-width stroked rectangles.
474 // For odd-width stroked rectangles, we need to offset x/y by
475 // 0.5...
476 void
477 gfxContext::Rectangle(const gfxRect& rect, bool snapToPixels)
479 if (mCairo) {
480 if (snapToPixels) {
481 gfxRect snappedRect(rect);
483 if (UserToDevicePixelSnapped(snappedRect, true))
485 cairo_matrix_t mat;
486 cairo_get_matrix(mCairo, &mat);
487 cairo_identity_matrix(mCairo);
488 Rectangle(snappedRect);
489 cairo_set_matrix(mCairo, &mat);
491 return;
495 cairo_rectangle(mCairo, rect.X(), rect.Y(), rect.Width(), rect.Height());
496 } else {
497 Rect rec = ToRect(rect);
499 if (snapToPixels) {
500 gfxRect newRect(rect);
501 if (UserToDevicePixelSnapped(newRect, true)) {
502 gfxMatrix mat = ThebesMatrix(mTransform);
503 mat.Invert();
505 // We need the user space rect.
506 rec = ToRect(mat.TransformBounds(newRect));
510 if (!mPathBuilder && !mPathIsRect) {
511 mPathIsRect = true;
512 mRect = rec;
513 return;
516 EnsurePathBuilder();
518 mPathBuilder->MoveTo(rec.TopLeft());
519 mPathBuilder->LineTo(rec.TopRight());
520 mPathBuilder->LineTo(rec.BottomRight());
521 mPathBuilder->LineTo(rec.BottomLeft());
522 mPathBuilder->Close();
526 void
527 gfxContext::Ellipse(const gfxPoint& center, const gfxSize& dimensions)
529 gfxSize halfDim = dimensions / 2.0;
530 gfxRect r(center - gfxPoint(halfDim.width, halfDim.height), dimensions);
531 gfxCornerSizes c(halfDim, halfDim, halfDim, halfDim);
533 RoundedRectangle (r, c);
536 void
537 gfxContext::Polygon(const gfxPoint *points, uint32_t numPoints)
539 if (mCairo) {
540 if (numPoints == 0)
541 return;
543 cairo_move_to(mCairo, points[0].x, points[0].y);
544 for (uint32_t i = 1; i < numPoints; ++i) {
545 cairo_line_to(mCairo, points[i].x, points[i].y);
547 } else {
548 if (numPoints == 0) {
549 return;
552 EnsurePathBuilder();
554 mPathBuilder->MoveTo(ToPoint(points[0]));
555 for (uint32_t i = 1; i < numPoints; i++) {
556 mPathBuilder->LineTo(ToPoint(points[i]));
561 void
562 gfxContext::DrawSurface(gfxASurface *surface, const gfxSize& size)
564 if (mCairo) {
565 cairo_save(mCairo);
566 cairo_set_source_surface(mCairo, surface->CairoSurface(), 0, 0);
567 cairo_new_path(mCairo);
569 // pixel-snap this
570 Rectangle(gfxRect(gfxPoint(0.0, 0.0), size), true);
572 cairo_fill(mCairo);
573 cairo_restore(mCairo);
574 } else {
575 // Lifetime needs to be limited here since we may wrap surface's data.
576 RefPtr<SourceSurface> surf =
577 gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface);
579 if (!surf) {
580 return;
583 Rect rect(0, 0, Float(size.width), Float(size.height));
584 rect.Intersect(Rect(0, 0, Float(surf->GetSize().width), Float(surf->GetSize().height)));
586 // XXX - Should fix pixel snapping.
587 mDT->DrawSurface(surf, rect, rect);
591 // transform stuff
592 void
593 gfxContext::Translate(const gfxPoint& pt)
595 if (mCairo) {
596 cairo_translate(mCairo, pt.x, pt.y);
597 } else {
598 Matrix newMatrix = mTransform;
600 ChangeTransform(newMatrix.Translate(Float(pt.x), Float(pt.y)));
604 void
605 gfxContext::Scale(gfxFloat x, gfxFloat y)
607 if (mCairo) {
608 cairo_scale(mCairo, x, y);
609 } else {
610 Matrix newMatrix = mTransform;
612 ChangeTransform(newMatrix.Scale(Float(x), Float(y)));
616 void
617 gfxContext::Rotate(gfxFloat angle)
619 if (mCairo) {
620 cairo_rotate(mCairo, angle);
621 } else {
622 Matrix rotation = Matrix::Rotation(Float(angle));
623 ChangeTransform(rotation * mTransform);
627 void
628 gfxContext::Multiply(const gfxMatrix& matrix)
630 if (mCairo) {
631 const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix);
632 cairo_transform(mCairo, &mat);
633 } else {
634 ChangeTransform(ToMatrix(matrix) * mTransform);
638 void
639 gfxContext::MultiplyAndNudgeToIntegers(const gfxMatrix& matrix)
641 if (mCairo) {
642 const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix);
643 cairo_transform(mCairo, &mat);
644 // XXX nudging to integers not currently supported for Thebes
645 } else {
646 Matrix transform = ToMatrix(matrix) * mTransform;
647 transform.NudgeToIntegers();
648 ChangeTransform(transform);
652 void
653 gfxContext::SetMatrix(const gfxMatrix& matrix)
655 if (mCairo) {
656 const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix);
657 cairo_set_matrix(mCairo, &mat);
658 } else {
659 ChangeTransform(ToMatrix(matrix));
663 void
664 gfxContext::IdentityMatrix()
666 if (mCairo) {
667 cairo_identity_matrix(mCairo);
668 } else {
669 ChangeTransform(Matrix());
673 gfxMatrix
674 gfxContext::CurrentMatrix() const
676 if (mCairo) {
677 cairo_matrix_t mat;
678 cairo_get_matrix(mCairo, &mat);
679 return gfxMatrix(*reinterpret_cast<gfxMatrix*>(&mat));
680 } else {
681 return ThebesMatrix(mTransform);
685 void
686 gfxContext::NudgeCurrentMatrixToIntegers()
688 if (mCairo) {
689 cairo_matrix_t mat;
690 cairo_get_matrix(mCairo, &mat);
691 gfxMatrix(*reinterpret_cast<gfxMatrix*>(&mat)).NudgeToIntegers();
692 cairo_set_matrix(mCairo, &mat);
693 } else {
694 gfxMatrix matrix = ThebesMatrix(mTransform);
695 matrix.NudgeToIntegers();
696 ChangeTransform(ToMatrix(matrix));
700 gfxPoint
701 gfxContext::DeviceToUser(const gfxPoint& point) const
703 if (mCairo) {
704 gfxPoint ret = point;
705 cairo_device_to_user(mCairo, &ret.x, &ret.y);
706 return ret;
707 } else {
708 Matrix matrix = mTransform;
710 matrix.Invert();
712 return ThebesPoint(matrix * ToPoint(point));
716 gfxSize
717 gfxContext::DeviceToUser(const gfxSize& size) const
719 if (mCairo) {
720 gfxSize ret = size;
721 cairo_device_to_user_distance(mCairo, &ret.width, &ret.height);
722 return ret;
723 } else {
724 Matrix matrix = mTransform;
726 matrix.Invert();
728 return ThebesSize(matrix * ToSize(size));
732 gfxRect
733 gfxContext::DeviceToUser(const gfxRect& rect) const
735 if (mCairo) {
736 gfxRect ret = rect;
737 cairo_device_to_user(mCairo, &ret.x, &ret.y);
738 cairo_device_to_user_distance(mCairo, &ret.width, &ret.height);
739 return ret;
740 } else {
741 Matrix matrix = mTransform;
743 matrix.Invert();
745 return ThebesRect(matrix.TransformBounds(ToRect(rect)));
749 gfxPoint
750 gfxContext::UserToDevice(const gfxPoint& point) const
752 if (mCairo) {
753 gfxPoint ret = point;
754 cairo_user_to_device(mCairo, &ret.x, &ret.y);
755 return ret;
756 } else {
757 return ThebesPoint(mTransform * ToPoint(point));
761 gfxSize
762 gfxContext::UserToDevice(const gfxSize& size) const
764 if (mCairo) {
765 gfxSize ret = size;
766 cairo_user_to_device_distance(mCairo, &ret.width, &ret.height);
767 return ret;
768 } else {
769 const Matrix &matrix = mTransform;
771 gfxSize newSize;
772 newSize.width = size.width * matrix._11 + size.height * matrix._12;
773 newSize.height = size.width * matrix._21 + size.height * matrix._22;
774 return newSize;
778 gfxRect
779 gfxContext::UserToDevice(const gfxRect& rect) const
781 if (mCairo) {
782 double xmin = rect.X(), ymin = rect.Y(), xmax = rect.XMost(), ymax = rect.YMost();
784 double x[3], y[3];
785 x[0] = xmin; y[0] = ymax;
786 x[1] = xmax; y[1] = ymax;
787 x[2] = xmax; y[2] = ymin;
789 cairo_user_to_device(mCairo, &xmin, &ymin);
790 xmax = xmin;
791 ymax = ymin;
792 for (int i = 0; i < 3; i++) {
793 cairo_user_to_device(mCairo, &x[i], &y[i]);
794 xmin = std::min(xmin, x[i]);
795 xmax = std::max(xmax, x[i]);
796 ymin = std::min(ymin, y[i]);
797 ymax = std::max(ymax, y[i]);
800 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
801 } else {
802 const Matrix &matrix = mTransform;
803 return ThebesRect(matrix.TransformBounds(ToRect(rect)));
807 bool
808 gfxContext::UserToDevicePixelSnapped(gfxRect& rect, bool ignoreScale) const
810 if (GetFlags() & FLAG_DISABLE_SNAPPING)
811 return false;
813 // if we're not at 1.0 scale, don't snap, unless we're
814 // ignoring the scale. If we're not -just- a scale,
815 // never snap.
816 const gfxFloat epsilon = 0.0000001;
817 #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon)
818 if (mCairo) {
819 cairo_matrix_t mat;
820 cairo_get_matrix(mCairo, &mat);
821 if (!ignoreScale &&
822 (!WITHIN_E(mat.xx,1.0) || !WITHIN_E(mat.yy,1.0) ||
823 !WITHIN_E(mat.xy,0.0) || !WITHIN_E(mat.yx,0.0)))
824 return false;
825 } else {
826 Matrix mat = mTransform;
827 if (!ignoreScale &&
828 (!WITHIN_E(mat._11,1.0) || !WITHIN_E(mat._22,1.0) ||
829 !WITHIN_E(mat._12,0.0) || !WITHIN_E(mat._21,0.0)))
830 return false;
832 #undef WITHIN_E
834 gfxPoint p1 = UserToDevice(rect.TopLeft());
835 gfxPoint p2 = UserToDevice(rect.TopRight());
836 gfxPoint p3 = UserToDevice(rect.BottomRight());
838 // Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
839 // two opposite corners define the entire rectangle. So check if
840 // the axis-aligned rectangle with opposite corners p1 and p3
841 // define an axis-aligned rectangle whose other corners are p2 and p4.
842 // We actually only need to check one of p2 and p4, since an affine
843 // transform maps parallelograms to parallelograms.
844 if (p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y)) {
845 p1.Round();
846 p3.Round();
848 rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y)));
849 rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(),
850 std::max(p1.y, p3.y) - rect.Y()));
851 return true;
854 return false;
857 bool
858 gfxContext::UserToDevicePixelSnapped(gfxPoint& pt, bool ignoreScale) const
860 if (GetFlags() & FLAG_DISABLE_SNAPPING)
861 return false;
863 // if we're not at 1.0 scale, don't snap, unless we're
864 // ignoring the scale. If we're not -just- a scale,
865 // never snap.
866 const gfxFloat epsilon = 0.0000001;
867 #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon)
868 if (mCairo) {
869 cairo_matrix_t mat;
870 cairo_get_matrix(mCairo, &mat);
871 if (!ignoreScale &&
872 (!WITHIN_E(mat.xx,1.0) || !WITHIN_E(mat.yy,1.0) ||
873 !WITHIN_E(mat.xy,0.0) || !WITHIN_E(mat.yx,0.0)))
874 return false;
875 } else {
876 Matrix mat = mTransform;
877 if (!ignoreScale &&
878 (!WITHIN_E(mat._11,1.0) || !WITHIN_E(mat._22,1.0) ||
879 !WITHIN_E(mat._12,0.0) || !WITHIN_E(mat._21,0.0)))
880 return false;
882 #undef WITHIN_E
884 pt = UserToDevice(pt);
885 pt.Round();
886 return true;
889 void
890 gfxContext::PixelSnappedRectangleAndSetPattern(const gfxRect& rect,
891 gfxPattern *pattern)
893 gfxRect r(rect);
895 // Bob attempts to pixel-snap the rectangle, and returns true if
896 // the snapping succeeds. If it does, we need to set up an
897 // identity matrix, because the rectangle given back is in device
898 // coordinates.
900 // We then have to call a translate to dr.pos afterwards, to make
901 // sure the image lines up in the right place with our pixel
902 // snapped rectangle.
904 // If snapping wasn't successful, we just translate to where the
905 // pattern would normally start (in app coordinates) and do the
906 // same thing.
907 Rectangle(r, true);
908 SetPattern(pattern);
911 void
912 gfxContext::SetAntialiasMode(AntialiasMode mode)
914 if (mCairo) {
915 if (mode == MODE_ALIASED) {
916 cairo_set_antialias(mCairo, CAIRO_ANTIALIAS_NONE);
917 } else if (mode == MODE_COVERAGE) {
918 cairo_set_antialias(mCairo, CAIRO_ANTIALIAS_DEFAULT);
920 } else {
921 if (mode == MODE_ALIASED) {
922 CurrentState().aaMode = AA_NONE;
923 } else if (mode == MODE_COVERAGE) {
924 CurrentState().aaMode = AA_SUBPIXEL;
929 gfxContext::AntialiasMode
930 gfxContext::CurrentAntialiasMode() const
932 if (mCairo) {
933 cairo_antialias_t aa = cairo_get_antialias(mCairo);
934 if (aa == CAIRO_ANTIALIAS_NONE)
935 return MODE_ALIASED;
936 return MODE_COVERAGE;
937 } else {
938 if (CurrentState().aaMode == AA_NONE) {
939 return MODE_ALIASED;
941 return MODE_COVERAGE;
945 void
946 gfxContext::SetDash(gfxLineType ltype)
948 static double dash[] = {5.0, 5.0};
949 static double dot[] = {1.0, 1.0};
951 switch (ltype) {
952 case gfxLineDashed:
953 SetDash(dash, 2, 0.0);
954 break;
955 case gfxLineDotted:
956 SetDash(dot, 2, 0.0);
957 break;
958 case gfxLineSolid:
959 default:
960 SetDash(nullptr, 0, 0.0);
961 break;
965 void
966 gfxContext::SetDash(gfxFloat *dashes, int ndash, gfxFloat offset)
968 if (mCairo) {
969 cairo_set_dash(mCairo, dashes, ndash, offset);
970 } else {
971 AzureState &state = CurrentState();
973 state.dashPattern.SetLength(ndash);
974 for (int i = 0; i < ndash; i++) {
975 state.dashPattern[i] = Float(dashes[i]);
977 state.strokeOptions.mDashLength = ndash;
978 state.strokeOptions.mDashOffset = Float(offset);
979 state.strokeOptions.mDashPattern = ndash ? state.dashPattern.Elements()
980 : nullptr;
984 bool
985 gfxContext::CurrentDash(FallibleTArray<gfxFloat>& dashes, gfxFloat* offset) const
987 if (mCairo) {
988 int count = cairo_get_dash_count(mCairo);
989 if (count <= 0 || !dashes.SetLength(count)) {
990 return false;
992 cairo_get_dash(mCairo, dashes.Elements(), offset);
993 return true;
994 } else {
995 const AzureState &state = CurrentState();
996 int count = state.strokeOptions.mDashLength;
998 if (count <= 0 || !dashes.SetLength(count)) {
999 return false;
1002 for (int i = 0; i < count; i++) {
1003 dashes[i] = state.dashPattern[i];
1006 *offset = state.strokeOptions.mDashOffset;
1008 return true;
1012 gfxFloat
1013 gfxContext::CurrentDashOffset() const
1015 if (mCairo) {
1016 if (cairo_get_dash_count(mCairo) <= 0) {
1017 return 0.0;
1019 gfxFloat offset;
1020 cairo_get_dash(mCairo, nullptr, &offset);
1021 return offset;
1022 } else {
1023 return CurrentState().strokeOptions.mDashOffset;
1027 void
1028 gfxContext::SetLineWidth(gfxFloat width)
1030 if (mCairo) {
1031 cairo_set_line_width(mCairo, width);
1032 } else {
1033 CurrentState().strokeOptions.mLineWidth = Float(width);
1037 gfxFloat
1038 gfxContext::CurrentLineWidth() const
1040 if (mCairo) {
1041 return cairo_get_line_width(mCairo);
1042 } else {
1043 return CurrentState().strokeOptions.mLineWidth;
1047 void
1048 gfxContext::SetOperator(GraphicsOperator op)
1050 if (mCairo) {
1051 if (mFlags & FLAG_SIMPLIFY_OPERATORS) {
1052 if (op != OPERATOR_SOURCE &&
1053 op != OPERATOR_CLEAR &&
1054 op != OPERATOR_OVER)
1055 op = OPERATOR_OVER;
1058 cairo_set_operator(mCairo, (cairo_operator_t)op);
1059 } else {
1060 if (op == OPERATOR_CLEAR) {
1061 CurrentState().opIsClear = true;
1062 return;
1064 CurrentState().opIsClear = false;
1065 CurrentState().op = CompositionOpForOp(op);
1069 gfxContext::GraphicsOperator
1070 gfxContext::CurrentOperator() const
1072 if (mCairo) {
1073 return (GraphicsOperator)cairo_get_operator(mCairo);
1074 } else {
1075 return ThebesOp(CurrentState().op);
1079 void
1080 gfxContext::SetLineCap(GraphicsLineCap cap)
1082 if (mCairo) {
1083 cairo_set_line_cap(mCairo, (cairo_line_cap_t)cap);
1084 } else {
1085 CurrentState().strokeOptions.mLineCap = ToCapStyle(cap);
1089 gfxContext::GraphicsLineCap
1090 gfxContext::CurrentLineCap() const
1092 if (mCairo) {
1093 return (GraphicsLineCap)cairo_get_line_cap(mCairo);
1094 } else {
1095 return ThebesLineCap(CurrentState().strokeOptions.mLineCap);
1099 void
1100 gfxContext::SetLineJoin(GraphicsLineJoin join)
1102 if (mCairo) {
1103 cairo_set_line_join(mCairo, (cairo_line_join_t)join);
1104 } else {
1105 CurrentState().strokeOptions.mLineJoin = ToJoinStyle(join);
1109 gfxContext::GraphicsLineJoin
1110 gfxContext::CurrentLineJoin() const
1112 if (mCairo) {
1113 return (GraphicsLineJoin)cairo_get_line_join(mCairo);
1114 } else {
1115 return ThebesLineJoin(CurrentState().strokeOptions.mLineJoin);
1119 void
1120 gfxContext::SetMiterLimit(gfxFloat limit)
1122 if (mCairo) {
1123 cairo_set_miter_limit(mCairo, limit);
1124 } else {
1125 CurrentState().strokeOptions.mMiterLimit = Float(limit);
1129 gfxFloat
1130 gfxContext::CurrentMiterLimit() const
1132 if (mCairo) {
1133 return cairo_get_miter_limit(mCairo);
1134 } else {
1135 return CurrentState().strokeOptions.mMiterLimit;
1139 void
1140 gfxContext::SetFillRule(FillRule rule)
1142 if (mCairo) {
1143 cairo_set_fill_rule(mCairo, (cairo_fill_rule_t)rule);
1144 } else {
1145 CurrentState().fillRule = rule == FILL_RULE_WINDING ? FILL_WINDING : FILL_EVEN_ODD;
1149 gfxContext::FillRule
1150 gfxContext::CurrentFillRule() const
1152 if (mCairo) {
1153 return (FillRule)cairo_get_fill_rule(mCairo);
1154 } else {
1155 return FILL_RULE_WINDING;
1159 // clipping
1160 void
1161 gfxContext::Clip(const gfxRect& rect)
1163 if (mCairo) {
1164 cairo_new_path(mCairo);
1165 cairo_rectangle(mCairo, rect.X(), rect.Y(), rect.Width(), rect.Height());
1166 cairo_clip(mCairo);
1167 } else {
1168 AzureState::PushedClip clip = { nullptr, ToRect(rect), mTransform };
1169 CurrentState().pushedClips.AppendElement(clip);
1170 mDT->PushClipRect(ToRect(rect));
1171 NewPath();
1175 void
1176 gfxContext::Clip()
1178 if (mCairo) {
1179 cairo_clip_preserve(mCairo);
1180 } else {
1181 if (mPathIsRect) {
1182 MOZ_ASSERT(!mTransformChanged);
1184 AzureState::PushedClip clip = { nullptr, mRect, mTransform };
1185 CurrentState().pushedClips.AppendElement(clip);
1186 mDT->PushClipRect(mRect);
1187 } else {
1188 EnsurePath();
1189 mDT->PushClip(mPath);
1190 AzureState::PushedClip clip = { mPath, Rect(), mTransform };
1191 CurrentState().pushedClips.AppendElement(clip);
1196 void
1197 gfxContext::ResetClip()
1199 if (mCairo) {
1200 cairo_reset_clip(mCairo);
1201 } else {
1202 for (int i = mStateStack.Length() - 1; i >= 0; i--) {
1203 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
1204 mDT->PopClip();
1207 if (mStateStack[i].clipWasReset) {
1208 break;
1211 CurrentState().pushedClips.Clear();
1212 CurrentState().clipWasReset = true;
1216 void
1217 gfxContext::UpdateSurfaceClip()
1219 if (mCairo) {
1220 NewPath();
1221 // we paint an empty rectangle to ensure the clip is propagated to
1222 // the destination surface
1223 SetDeviceColor(gfxRGBA(0,0,0,0));
1224 Rectangle(gfxRect(0,1,1,0));
1225 Fill();
1229 gfxRect
1230 gfxContext::GetClipExtents()
1232 if (mCairo) {
1233 double xmin, ymin, xmax, ymax;
1234 cairo_clip_extents(mCairo, &xmin, &ymin, &xmax, &ymax);
1235 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
1236 } else {
1237 Rect rect = GetAzureDeviceSpaceClipBounds();
1239 if (rect.width == 0 || rect.height == 0) {
1240 return gfxRect(0, 0, 0, 0);
1243 Matrix mat = mTransform;
1244 mat.Invert();
1245 rect = mat.TransformBounds(rect);
1247 return ThebesRect(rect);
1251 bool
1252 gfxContext::ClipContainsRect(const gfxRect& aRect)
1254 if (mCairo) {
1255 cairo_rectangle_list_t *clip =
1256 cairo_copy_clip_rectangle_list(mCairo);
1258 bool result = false;
1260 if (clip->status == CAIRO_STATUS_SUCCESS) {
1261 for (int i = 0; i < clip->num_rectangles; i++) {
1262 gfxRect rect(clip->rectangles[i].x, clip->rectangles[i].y,
1263 clip->rectangles[i].width, clip->rectangles[i].height);
1264 if (rect.Contains(aRect)) {
1265 result = true;
1266 break;
1271 cairo_rectangle_list_destroy(clip);
1272 return result;
1273 } else {
1274 unsigned int lastReset = 0;
1275 for (int i = mStateStack.Length() - 2; i > 0; i--) {
1276 if (mStateStack[i].clipWasReset) {
1277 lastReset = i;
1278 break;
1282 // Since we always return false when the clip list contains a
1283 // non-rectangular clip or a non-rectilinear transform, our 'total' clip
1284 // is always a rectangle if we hit the end of this function.
1285 Rect clipBounds(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height));
1287 for (unsigned int i = lastReset; i < mStateStack.Length(); i++) {
1288 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
1289 AzureState::PushedClip &clip = mStateStack[i].pushedClips[c];
1290 if (clip.path || !clip.transform.IsRectilinear()) {
1291 // Cairo behavior is we return false if the clip contains a non-
1292 // rectangle.
1293 return false;
1294 } else {
1295 Rect clipRect = mTransform.TransformBounds(clip.rect);
1297 clipBounds.IntersectRect(clipBounds, clipRect);
1302 return clipBounds.Contains(ToRect(aRect));
1306 // rendering sources
1308 void
1309 gfxContext::SetColor(const gfxRGBA& c)
1311 if (mCairo) {
1312 if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
1314 gfxRGBA cms;
1315 qcms_transform *transform = gfxPlatform::GetCMSRGBTransform();
1316 if (transform)
1317 gfxPlatform::TransformPixel(c, cms, transform);
1319 // Use the original alpha to avoid unnecessary float->byte->float
1320 // conversion errors
1321 cairo_set_source_rgba(mCairo, cms.r, cms.g, cms.b, c.a);
1323 else
1324 cairo_set_source_rgba(mCairo, c.r, c.g, c.b, c.a);
1325 } else {
1326 CurrentState().pattern = nullptr;
1327 CurrentState().sourceSurfCairo = nullptr;
1328 CurrentState().sourceSurface = nullptr;
1330 if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
1332 gfxRGBA cms;
1333 qcms_transform *transform = gfxPlatform::GetCMSRGBTransform();
1334 if (transform)
1335 gfxPlatform::TransformPixel(c, cms, transform);
1337 // Use the original alpha to avoid unnecessary float->byte->float
1338 // conversion errors
1339 CurrentState().color = ToColor(cms);
1341 else
1342 CurrentState().color = ToColor(c);
1346 void
1347 gfxContext::SetDeviceColor(const gfxRGBA& c)
1349 if (mCairo) {
1350 cairo_set_source_rgba(mCairo, c.r, c.g, c.b, c.a);
1351 } else {
1352 CurrentState().pattern = nullptr;
1353 CurrentState().sourceSurfCairo = nullptr;
1354 CurrentState().sourceSurface = nullptr;
1355 CurrentState().color = ToColor(c);
1359 bool
1360 gfxContext::GetDeviceColor(gfxRGBA& c)
1362 if (mCairo) {
1363 return cairo_pattern_get_rgba(cairo_get_source(mCairo),
1364 &c.r,
1365 &c.g,
1366 &c.b,
1367 &c.a) == CAIRO_STATUS_SUCCESS;
1368 } else {
1369 if (CurrentState().sourceSurface) {
1370 return false;
1372 if (CurrentState().pattern) {
1373 gfxRGBA color;
1374 return CurrentState().pattern->GetSolidColor(c);
1377 c = ThebesRGBA(CurrentState().color);
1378 return true;
1382 void
1383 gfxContext::SetSource(gfxASurface *surface, const gfxPoint& offset)
1385 if (mCairo) {
1386 NS_ASSERTION(surface->GetAllowUseAsSource(), "Surface not allowed to be used as source!");
1387 cairo_set_source_surface(mCairo, surface->CairoSurface(), offset.x, offset.y);
1388 } else {
1389 CurrentState().surfTransform = Matrix(1.0f, 0, 0, 1.0f, Float(offset.x), Float(offset.y));
1390 CurrentState().pattern = nullptr;
1391 CurrentState().patternTransformChanged = false;
1392 // Keep the underlying cairo surface around while we keep the
1393 // sourceSurface.
1394 CurrentState().sourceSurfCairo = surface;
1395 CurrentState().sourceSurface =
1396 gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface);
1397 CurrentState().color = Color(0, 0, 0, 0);
1401 void
1402 gfxContext::SetPattern(gfxPattern *pattern)
1404 if (mCairo) {
1405 cairo_set_source(mCairo, pattern->CairoPattern());
1406 } else {
1407 CurrentState().sourceSurfCairo = nullptr;
1408 CurrentState().sourceSurface = nullptr;
1409 CurrentState().patternTransformChanged = false;
1410 CurrentState().pattern = pattern;
1414 already_AddRefed<gfxPattern>
1415 gfxContext::GetPattern()
1417 if (mCairo) {
1418 cairo_pattern_t *pat = cairo_get_source(mCairo);
1419 NS_ASSERTION(pat, "I was told this couldn't be null");
1421 nsRefPtr<gfxPattern> wrapper;
1422 if (pat)
1423 wrapper = new gfxPattern(pat);
1424 else
1425 wrapper = new gfxPattern(gfxRGBA(0,0,0,0));
1427 return wrapper.forget();
1428 } else {
1429 nsRefPtr<gfxPattern> pat;
1431 AzureState &state = CurrentState();
1432 if (state.pattern) {
1433 pat = state.pattern;
1434 } else if (state.sourceSurface) {
1435 NS_ASSERTION(false, "Ugh, this isn't good.");
1436 } else {
1437 pat = new gfxPattern(ThebesRGBA(state.color));
1439 return pat.forget();
1444 // masking
1445 void
1446 gfxContext::Mask(gfxPattern *pattern)
1448 if (mCairo) {
1449 cairo_mask(mCairo, pattern->CairoPattern());
1450 } else {
1451 if (pattern->Extend() == gfxPattern::EXTEND_NONE) {
1452 // In this situation the mask will be fully transparent (i.e. nothing
1453 // will be drawn) outside of the bounds of the surface. We can support
1454 // that by clipping out drawing to that area.
1455 Point offset;
1456 if (pattern->IsAzure()) {
1457 // This is an Azure pattern. i.e. this was the result of a PopGroup and
1458 // then the extend mode was changed to EXTEND_NONE.
1459 // XXX - We may need some additional magic here in theory to support
1460 // device offsets in these patterns, but no problems have been observed
1461 // yet because of this. And it would complicate things a little further.
1462 offset = Point(0.f, 0.f);
1463 } else if (pattern->GetType() == gfxPattern::PATTERN_SURFACE) {
1464 nsRefPtr<gfxASurface> asurf = pattern->GetSurface();
1465 gfxPoint deviceOffset = asurf->GetDeviceOffset();
1466 offset = Point(-deviceOffset.x, -deviceOffset.y);
1468 // this lets GetAzureSurface work
1469 pattern->GetPattern(mDT);
1472 if (pattern->IsAzure() || pattern->GetType() == gfxPattern::PATTERN_SURFACE) {
1473 RefPtr<SourceSurface> mask = pattern->GetAzureSurface();
1474 Matrix mat = ToMatrix(pattern->GetInverseMatrix());
1475 Matrix old = mTransform;
1476 // add in the inverse of the pattern transform so that when we
1477 // MaskSurface we are transformed to the place matching the pattern transform
1478 mat = mat * mTransform;
1480 ChangeTransform(mat);
1481 mDT->MaskSurface(GeneralPattern(this), mask, offset, DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode));
1482 ChangeTransform(old);
1483 return;
1486 mDT->Mask(GeneralPattern(this), *pattern->GetPattern(mDT), DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode));
1490 void
1491 gfxContext::Mask(gfxASurface *surface, const gfxPoint& offset)
1493 PROFILER_LABEL("gfxContext", "Mask");
1494 if (mCairo) {
1495 cairo_mask_surface(mCairo, surface->CairoSurface(), offset.x, offset.y);
1496 } else {
1497 // Lifetime needs to be limited here as we may simply wrap surface's data.
1498 RefPtr<SourceSurface> sourceSurf =
1499 gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface);
1501 if (!sourceSurf) {
1502 return;
1505 gfxPoint pt = surface->GetDeviceOffset();
1507 // We clip here to bind to the mask surface bounds, see above.
1508 mDT->MaskSurface(GeneralPattern(this),
1509 sourceSurf,
1510 Point(offset.x - pt.x, offset.y - pt.y),
1511 DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode));
1515 void
1516 gfxContext::Paint(gfxFloat alpha)
1518 PROFILER_LABEL("gfxContext", "Paint");
1519 if (mCairo) {
1520 cairo_paint_with_alpha(mCairo, alpha);
1521 } else {
1522 AzureState &state = CurrentState();
1524 if (state.sourceSurface && !state.sourceSurfCairo &&
1525 !state.patternTransformChanged && !state.opIsClear)
1527 // This is the case where a PopGroupToSource has been done and this
1528 // paint is executed without changing the transform or the source.
1529 Matrix oldMat = mDT->GetTransform();
1531 IntSize surfSize = state.sourceSurface->GetSize();
1533 Matrix mat;
1534 mat.Translate(-state.deviceOffset.x, -state.deviceOffset.y);
1535 mDT->SetTransform(mat);
1537 mDT->DrawSurface(state.sourceSurface,
1538 Rect(state.sourceSurfaceDeviceOffset, Size(surfSize.width, surfSize.height)),
1539 Rect(Point(), Size(surfSize.width, surfSize.height)),
1540 DrawSurfaceOptions(), DrawOptions(alpha, GetOp()));
1541 mDT->SetTransform(oldMat);
1542 return;
1545 Matrix mat = mDT->GetTransform();
1546 mat.Invert();
1547 Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize())));
1549 if (state.opIsClear) {
1550 mDT->ClearRect(paintRect);
1551 } else {
1552 mDT->FillRect(paintRect, GeneralPattern(this),
1553 DrawOptions(Float(alpha), GetOp()));
1558 // groups
1560 void
1561 gfxContext::PushGroup(gfxContentType content)
1563 if (mCairo) {
1564 cairo_push_group_with_content(mCairo, (cairo_content_t) content);
1565 } else {
1566 PushNewDT(content);
1568 PushClipsToDT(mDT);
1569 mDT->SetTransform(GetDTTransform());
1573 static gfxRect
1574 GetRoundOutDeviceClipExtents(gfxContext* aCtx)
1576 gfxContextMatrixAutoSaveRestore save(aCtx);
1577 aCtx->IdentityMatrix();
1578 gfxRect r = aCtx->GetClipExtents();
1579 r.RoundOut();
1580 return r;
1584 * Copy the contents of aSrc to aDest, translated by aTranslation.
1586 static void
1587 CopySurface(gfxASurface* aSrc, gfxASurface* aDest, const gfxPoint& aTranslation)
1589 cairo_t *cr = cairo_create(aDest->CairoSurface());
1590 cairo_set_source_surface(cr, aSrc->CairoSurface(), aTranslation.x, aTranslation.y);
1591 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1592 cairo_paint(cr);
1593 cairo_destroy(cr);
1596 void
1597 gfxContext::PushGroupAndCopyBackground(gfxContentType content)
1599 if (mCairo) {
1600 if (content == GFX_CONTENT_COLOR_ALPHA &&
1601 !(GetFlags() & FLAG_DISABLE_COPY_BACKGROUND)) {
1602 nsRefPtr<gfxASurface> s = CurrentSurface();
1603 if ((s->GetAllowUseAsSource() || s->GetType() == gfxSurfaceTypeTee) &&
1604 (s->GetContentType() == GFX_CONTENT_COLOR ||
1605 s->GetOpaqueRect().Contains(GetRoundOutDeviceClipExtents(this)))) {
1606 cairo_push_group_with_content(mCairo, CAIRO_CONTENT_COLOR);
1607 nsRefPtr<gfxASurface> d = CurrentSurface();
1609 if (d->GetType() == gfxSurfaceTypeTee) {
1610 NS_ASSERTION(s->GetType() == gfxSurfaceTypeTee, "Mismatched types");
1611 nsAutoTArray<nsRefPtr<gfxASurface>,2> ss;
1612 nsAutoTArray<nsRefPtr<gfxASurface>,2> ds;
1613 static_cast<gfxTeeSurface*>(s.get())->GetSurfaces(&ss);
1614 static_cast<gfxTeeSurface*>(d.get())->GetSurfaces(&ds);
1615 NS_ASSERTION(ss.Length() == ds.Length(), "Mismatched lengths");
1616 gfxPoint translation = d->GetDeviceOffset() - s->GetDeviceOffset();
1617 for (uint32_t i = 0; i < ss.Length(); ++i) {
1618 CopySurface(ss[i], ds[i], translation);
1620 } else {
1621 CopySurface(s, d, gfxPoint(0, 0));
1623 d->SetOpaqueRect(s->GetOpaqueRect());
1624 return;
1627 } else {
1628 IntRect clipExtents;
1629 if (mDT->GetFormat() != FORMAT_B8G8R8X8) {
1630 gfxRect clipRect = GetRoundOutDeviceClipExtents(this);
1631 clipExtents = IntRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
1633 if ((mDT->GetFormat() == FORMAT_B8G8R8X8 ||
1634 mDT->GetOpaqueRect().Contains(clipExtents)) &&
1635 !mDT->GetUserData(&sDontUseAsSourceKey)) {
1636 DrawTarget *oldDT = mDT;
1637 RefPtr<SourceSurface> source = mDT->Snapshot();
1638 Point oldDeviceOffset = CurrentState().deviceOffset;
1640 PushNewDT(GFX_CONTENT_COLOR);
1642 Point offset = CurrentState().deviceOffset - oldDeviceOffset;
1643 Rect surfRect(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height));
1644 Rect sourceRect = surfRect;
1645 sourceRect.x += offset.x;
1646 sourceRect.y += offset.y;
1648 mDT->SetTransform(Matrix());
1649 mDT->DrawSurface(source, surfRect, sourceRect);
1650 mDT->SetOpaqueRect(oldDT->GetOpaqueRect());
1652 PushClipsToDT(mDT);
1653 mDT->SetTransform(GetDTTransform());
1654 return;
1657 PushGroup(content);
1660 already_AddRefed<gfxPattern>
1661 gfxContext::PopGroup()
1663 if (mCairo) {
1664 cairo_pattern_t *pat = cairo_pop_group(mCairo);
1665 nsRefPtr<gfxPattern> wrapper = new gfxPattern(pat);
1666 cairo_pattern_destroy(pat);
1667 return wrapper.forget();
1668 } else {
1669 RefPtr<SourceSurface> src = mDT->Snapshot();
1670 Point deviceOffset = CurrentState().deviceOffset;
1672 Restore();
1674 Matrix mat = mTransform;
1675 mat.Invert();
1677 Matrix deviceOffsetTranslation;
1678 deviceOffsetTranslation.Translate(deviceOffset.x, deviceOffset.y);
1680 nsRefPtr<gfxPattern> pat = new gfxPattern(src, deviceOffsetTranslation * mat);
1682 return pat.forget();
1686 void
1687 gfxContext::PopGroupToSource()
1689 if (mCairo) {
1690 cairo_pop_group_to_source(mCairo);
1691 } else {
1692 RefPtr<SourceSurface> src = mDT->Snapshot();
1693 Point deviceOffset = CurrentState().deviceOffset;
1694 Restore();
1695 CurrentState().sourceSurfCairo = nullptr;
1696 CurrentState().sourceSurface = src;
1697 CurrentState().sourceSurfaceDeviceOffset = deviceOffset;
1698 CurrentState().pattern = nullptr;
1699 CurrentState().patternTransformChanged = false;
1701 Matrix mat = mTransform;
1702 mat.Invert();
1704 Matrix deviceOffsetTranslation;
1705 deviceOffsetTranslation.Translate(deviceOffset.x, deviceOffset.y);
1706 CurrentState().surfTransform = deviceOffsetTranslation * mat;
1710 bool
1711 gfxContext::PointInFill(const gfxPoint& pt)
1713 if (mCairo) {
1714 return cairo_in_fill(mCairo, pt.x, pt.y);
1715 } else {
1716 EnsurePath();
1717 return mPath->ContainsPoint(ToPoint(pt), Matrix());
1721 bool
1722 gfxContext::PointInStroke(const gfxPoint& pt)
1724 if (mCairo) {
1725 return cairo_in_stroke(mCairo, pt.x, pt.y);
1726 } else {
1727 EnsurePath();
1728 return mPath->StrokeContainsPoint(CurrentState().strokeOptions,
1729 ToPoint(pt),
1730 Matrix());
1734 gfxRect
1735 gfxContext::GetUserPathExtent()
1737 if (mCairo) {
1738 double xmin, ymin, xmax, ymax;
1739 cairo_path_extents(mCairo, &xmin, &ymin, &xmax, &ymax);
1740 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
1741 } else {
1742 EnsurePath();
1743 return ThebesRect(mPath->GetBounds());
1747 gfxRect
1748 gfxContext::GetUserFillExtent()
1750 if (mCairo) {
1751 double xmin, ymin, xmax, ymax;
1752 cairo_fill_extents(mCairo, &xmin, &ymin, &xmax, &ymax);
1753 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
1754 } else {
1755 EnsurePath();
1756 return ThebesRect(mPath->GetBounds());
1760 gfxRect
1761 gfxContext::GetUserStrokeExtent()
1763 if (mCairo) {
1764 double xmin, ymin, xmax, ymax;
1765 cairo_stroke_extents(mCairo, &xmin, &ymin, &xmax, &ymax);
1766 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin);
1767 } else {
1768 EnsurePath();
1769 return ThebesRect(mPath->GetStrokedBounds(CurrentState().strokeOptions, mTransform));
1773 bool
1774 gfxContext::HasError()
1776 if (mCairo) {
1777 return cairo_status(mCairo) != CAIRO_STATUS_SUCCESS;
1778 } else {
1779 // As far as this is concerned, an Azure context is never in error.
1780 return false;
1784 void
1785 gfxContext::RoundedRectangle(const gfxRect& rect,
1786 const gfxCornerSizes& corners,
1787 bool draw_clockwise)
1790 // For CW drawing, this looks like:
1792 // ...******0** 1 C
1793 // ****
1794 // *** 2
1795 // **
1796 // *
1797 // *
1798 // 3
1799 // *
1800 // *
1802 // Where 0, 1, 2, 3 are the control points of the Bezier curve for
1803 // the corner, and C is the actual corner point.
1805 // At the start of the loop, the current point is assumed to be
1806 // the point adjacent to the top left corner on the top
1807 // horizontal. Note that corner indices start at the top left and
1808 // continue clockwise, whereas in our loop i = 0 refers to the top
1809 // right corner.
1811 // When going CCW, the control points are swapped, and the first
1812 // corner that's drawn is the top left (along with the top segment).
1814 // There is considerable latitude in how one chooses the four
1815 // control points for a Bezier curve approximation to an ellipse.
1816 // For the overall path to be continuous and show no corner at the
1817 // endpoints of the arc, points 0 and 3 must be at the ends of the
1818 // straight segments of the rectangle; points 0, 1, and C must be
1819 // collinear; and points 3, 2, and C must also be collinear. This
1820 // leaves only two free parameters: the ratio of the line segments
1821 // 01 and 0C, and the ratio of the line segments 32 and 3C. See
1822 // the following papers for extensive discussion of how to choose
1823 // these ratios:
1825 // Dokken, Tor, et al. "Good approximation of circles by
1826 // curvature-continuous Bezier curves." Computer-Aided
1827 // Geometric Design 7(1990) 33--41.
1828 // Goldapp, Michael. "Approximation of circular arcs by cubic
1829 // polynomials." Computer-Aided Geometric Design 8(1991) 227--238.
1830 // Maisonobe, Luc. "Drawing an elliptical arc using polylines,
1831 // quadratic, or cubic Bezier curves."
1832 // http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf
1834 // We follow the approach in section 2 of Goldapp (least-error,
1835 // Hermite-type approximation) and make both ratios equal to
1837 // 2 2 + n - sqrt(2n + 28)
1838 // alpha = - * ---------------------
1839 // 3 n - 4
1841 // where n = 3( cbrt(sqrt(2)+1) - cbrt(sqrt(2)-1) ).
1843 // This is the result of Goldapp's equation (10b) when the angle
1844 // swept out by the arc is pi/2, and the parameter "a-bar" is the
1845 // expression given immediately below equation (21).
1847 // Using this value, the maximum radial error for a circle, as a
1848 // fraction of the radius, is on the order of 0.2 x 10^-3.
1849 // Neither Dokken nor Goldapp discusses error for a general
1850 // ellipse; Maisonobe does, but his choice of control points
1851 // follows different constraints, and Goldapp's expression for
1852 // 'alpha' gives much smaller radial error, even for very flat
1853 // ellipses, than Maisonobe's equivalent.
1855 // For the various corners and for each axis, the sign of this
1856 // constant changes, or it might be 0 -- it's multiplied by the
1857 // appropriate multiplier from the list before using.
1859 if (mCairo) {
1860 const gfxFloat alpha = 0.55191497064665766025;
1862 typedef struct { gfxFloat a, b; } twoFloats;
1864 twoFloats cwCornerMults[4] = { { -1, 0 },
1865 { 0, -1 },
1866 { +1, 0 },
1867 { 0, +1 } };
1868 twoFloats ccwCornerMults[4] = { { +1, 0 },
1869 { 0, -1 },
1870 { -1, 0 },
1871 { 0, +1 } };
1873 twoFloats *cornerMults = draw_clockwise ? cwCornerMults : ccwCornerMults;
1875 gfxPoint pc, p0, p1, p2, p3;
1877 if (draw_clockwise)
1878 cairo_move_to(mCairo, rect.X() + corners[NS_CORNER_TOP_LEFT].width, rect.Y());
1879 else
1880 cairo_move_to(mCairo, rect.X() + rect.Width() - corners[NS_CORNER_TOP_RIGHT].width, rect.Y());
1882 NS_FOR_CSS_CORNERS(i) {
1883 // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
1884 mozilla::css::Corner c = mozilla::css::Corner(draw_clockwise ? ((i+1) % 4) : ((4-i) % 4));
1886 // i+2 and i+3 respectively. These are used to index into the corner
1887 // multiplier table, and were deduced by calculating out the long form
1888 // of each corner and finding a pattern in the signs and values.
1889 int i2 = (i+2) % 4;
1890 int i3 = (i+3) % 4;
1892 pc = rect.AtCorner(c);
1894 if (corners[c].width > 0.0 && corners[c].height > 0.0) {
1895 p0.x = pc.x + cornerMults[i].a * corners[c].width;
1896 p0.y = pc.y + cornerMults[i].b * corners[c].height;
1898 p3.x = pc.x + cornerMults[i3].a * corners[c].width;
1899 p3.y = pc.y + cornerMults[i3].b * corners[c].height;
1901 p1.x = p0.x + alpha * cornerMults[i2].a * corners[c].width;
1902 p1.y = p0.y + alpha * cornerMults[i2].b * corners[c].height;
1904 p2.x = p3.x - alpha * cornerMults[i3].a * corners[c].width;
1905 p2.y = p3.y - alpha * cornerMults[i3].b * corners[c].height;
1907 cairo_line_to (mCairo, p0.x, p0.y);
1908 cairo_curve_to (mCairo,
1909 p1.x, p1.y,
1910 p2.x, p2.y,
1911 p3.x, p3.y);
1912 } else {
1913 cairo_line_to (mCairo, pc.x, pc.y);
1917 cairo_close_path (mCairo);
1918 } else {
1919 EnsurePathBuilder();
1920 Size radii[] = { ToSize(corners[NS_CORNER_TOP_LEFT]),
1921 ToSize(corners[NS_CORNER_TOP_RIGHT]),
1922 ToSize(corners[NS_CORNER_BOTTOM_RIGHT]),
1923 ToSize(corners[NS_CORNER_BOTTOM_LEFT]) };
1924 AppendRoundedRectToPath(mPathBuilder, ToRect(rect), radii, draw_clockwise);
1928 #ifdef MOZ_DUMP_PAINTING
1929 void
1930 gfxContext::WriteAsPNG(const char* aFile)
1932 nsRefPtr<gfxASurface> surf = CurrentSurface();
1933 if (surf) {
1934 surf->WriteAsPNG(aFile);
1935 } else {
1936 NS_WARNING("No surface found!");
1940 void
1941 gfxContext::DumpAsDataURL()
1943 nsRefPtr<gfxASurface> surf = CurrentSurface();
1944 if (surf) {
1945 surf->DumpAsDataURL();
1946 } else {
1947 NS_WARNING("No surface found!");
1951 void
1952 gfxContext::CopyAsDataURL()
1954 nsRefPtr<gfxASurface> surf = CurrentSurface();
1955 if (surf) {
1956 surf->CopyAsDataURL();
1957 } else {
1958 NS_WARNING("No surface found!");
1961 #endif
1963 void
1964 gfxContext::EnsurePath()
1966 if (mPathBuilder) {
1967 mPath = mPathBuilder->Finish();
1968 mPathBuilder = nullptr;
1971 if (mPath) {
1972 if (mTransformChanged) {
1973 Matrix mat = mTransform;
1974 mat.Invert();
1975 mat = mPathTransform * mat;
1976 mPathBuilder = mPath->TransformedCopyToBuilder(mat, CurrentState().fillRule);
1977 mPath = mPathBuilder->Finish();
1978 mPathBuilder = nullptr;
1980 mTransformChanged = false;
1983 if (CurrentState().fillRule == mPath->GetFillRule()) {
1984 return;
1987 mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
1989 mPath = mPathBuilder->Finish();
1990 mPathBuilder = nullptr;
1991 return;
1994 EnsurePathBuilder();
1995 mPath = mPathBuilder->Finish();
1996 mPathBuilder = nullptr;
1999 void
2000 gfxContext::EnsurePathBuilder()
2002 if (mPathBuilder && !mTransformChanged) {
2003 return;
2006 if (mPath) {
2007 if (!mTransformChanged) {
2008 mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule);
2009 mPath = nullptr;
2010 } else {
2011 Matrix invTransform = mTransform;
2012 invTransform.Invert();
2013 Matrix toNewUS = mPathTransform * invTransform;
2014 mPathBuilder = mPath->TransformedCopyToBuilder(toNewUS, CurrentState().fillRule);
2016 return;
2019 DebugOnly<PathBuilder*> oldPath = mPathBuilder.get();
2021 if (!mPathBuilder) {
2022 mPathBuilder = mDT->CreatePathBuilder(CurrentState().fillRule);
2024 if (mPathIsRect) {
2025 mPathBuilder->MoveTo(mRect.TopLeft());
2026 mPathBuilder->LineTo(mRect.TopRight());
2027 mPathBuilder->LineTo(mRect.BottomRight());
2028 mPathBuilder->LineTo(mRect.BottomLeft());
2029 mPathBuilder->Close();
2033 if (mTransformChanged) {
2034 // This could be an else if since this should never happen when
2035 // mPathBuilder is nullptr and mPath is nullptr. But this way we can
2036 // assert if all the state is as expected.
2037 MOZ_ASSERT(oldPath);
2038 MOZ_ASSERT(!mPathIsRect);
2040 Matrix invTransform = mTransform;
2041 invTransform.Invert();
2042 Matrix toNewUS = mPathTransform * invTransform;
2044 RefPtr<Path> path = mPathBuilder->Finish();
2045 mPathBuilder = path->TransformedCopyToBuilder(toNewUS, CurrentState().fillRule);
2048 mPathIsRect = false;
2051 void
2052 gfxContext::FillAzure(Float aOpacity)
2054 AzureState &state = CurrentState();
2056 CompositionOp op = GetOp();
2058 if (mPathIsRect) {
2059 MOZ_ASSERT(!mTransformChanged);
2061 if (state.opIsClear) {
2062 mDT->ClearRect(mRect);
2063 } else if (op == OP_SOURCE) {
2064 // Emulate cairo operator source which is bound by mask!
2065 mDT->ClearRect(mRect);
2066 mDT->FillRect(mRect, GeneralPattern(this), DrawOptions(aOpacity));
2067 } else {
2068 mDT->FillRect(mRect, GeneralPattern(this), DrawOptions(aOpacity, op, state.aaMode));
2070 } else {
2071 EnsurePath();
2073 NS_ASSERTION(!state.opIsClear, "We shouldn't be clearing complex paths!");
2075 mDT->Fill(mPath, GeneralPattern(this), DrawOptions(aOpacity, op, state.aaMode));
2079 void
2080 gfxContext::PushClipsToDT(DrawTarget *aDT)
2082 // Tricky, we have to restore all clips -since the last time- the clip
2083 // was reset. If we didn't reset the clip, just popping the clips we
2084 // added was fine.
2085 unsigned int lastReset = 0;
2086 for (int i = mStateStack.Length() - 2; i > 0; i--) {
2087 if (mStateStack[i].clipWasReset) {
2088 lastReset = i;
2089 break;
2093 // Don't need to save the old transform, we'll be setting a new one soon!
2095 // Push all clips from the last state on the stack where the clip was
2096 // reset to the clip before ours.
2097 for (unsigned int i = lastReset; i < mStateStack.Length() - 1; i++) {
2098 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
2099 aDT->SetTransform(mStateStack[i].pushedClips[c].transform * GetDeviceTransform());
2100 if (mStateStack[i].pushedClips[c].path) {
2101 aDT->PushClip(mStateStack[i].pushedClips[c].path);
2102 } else {
2103 aDT->PushClipRect(mStateStack[i].pushedClips[c].rect);
2109 CompositionOp
2110 gfxContext::GetOp()
2112 if (CurrentState().op != OP_SOURCE) {
2113 return CurrentState().op;
2116 AzureState &state = CurrentState();
2117 if (state.pattern) {
2118 if (state.pattern->IsOpaque()) {
2119 return OP_OVER;
2120 } else {
2121 return OP_SOURCE;
2123 } else if (state.sourceSurface) {
2124 if (state.sourceSurface->GetFormat() == FORMAT_B8G8R8X8) {
2125 return OP_OVER;
2126 } else {
2127 return OP_SOURCE;
2129 } else {
2130 if (state.color.a > 0.999) {
2131 return OP_OVER;
2132 } else {
2133 return OP_SOURCE;
2138 /* SVG font code can change the transform after having set the pattern on the
2139 * context. When the pattern is set it is in user space, if the transform is
2140 * changed after doing so the pattern needs to be converted back into userspace.
2141 * We just store the old pattern transform here so that we only do the work
2142 * needed here if the pattern is actually used.
2143 * We need to avoid doing this when this ChangeTransform comes from a restore,
2144 * since the current pattern and the current transform are both part of the
2145 * state we know the new CurrentState()'s values are valid. But if we assume
2146 * a change they might become invalid since patternTransformChanged is part of
2147 * the state and might be false for the restored AzureState.
2149 void
2150 gfxContext::ChangeTransform(const Matrix &aNewMatrix, bool aUpdatePatternTransform)
2152 AzureState &state = CurrentState();
2154 if (aUpdatePatternTransform && (state.pattern || state.sourceSurface)
2155 && !state.patternTransformChanged) {
2156 state.patternTransform = GetDTTransform();
2157 state.patternTransformChanged = true;
2160 if (mPathIsRect) {
2161 Matrix invMatrix = aNewMatrix;
2163 invMatrix.Invert();
2165 Matrix toNewUS = mTransform * invMatrix;
2167 if (toNewUS.IsRectilinear()) {
2168 mRect = toNewUS.TransformBounds(mRect);
2169 mRect.NudgeToIntegers();
2170 } else {
2171 mPathBuilder = mDT->CreatePathBuilder(CurrentState().fillRule);
2173 mPathBuilder->MoveTo(toNewUS * mRect.TopLeft());
2174 mPathBuilder->LineTo(toNewUS * mRect.TopRight());
2175 mPathBuilder->LineTo(toNewUS * mRect.BottomRight());
2176 mPathBuilder->LineTo(toNewUS * mRect.BottomLeft());
2177 mPathBuilder->Close();
2179 mPathIsRect = false;
2182 // No need to consider the transform changed now!
2183 mTransformChanged = false;
2184 } else if ((mPath || mPathBuilder) && !mTransformChanged) {
2185 mTransformChanged = true;
2186 mPathTransform = mTransform;
2189 mTransform = aNewMatrix;
2191 mDT->SetTransform(GetDTTransform());
2194 Rect
2195 gfxContext::GetAzureDeviceSpaceClipBounds()
2197 unsigned int lastReset = 0;
2198 for (int i = mStateStack.Length() - 1; i > 0; i--) {
2199 if (mStateStack[i].clipWasReset) {
2200 lastReset = i;
2201 break;
2205 Rect rect(CurrentState().deviceOffset.x, CurrentState().deviceOffset.y,
2206 Float(mDT->GetSize().width), Float(mDT->GetSize().height));
2207 for (unsigned int i = lastReset; i < mStateStack.Length(); i++) {
2208 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
2209 AzureState::PushedClip &clip = mStateStack[i].pushedClips[c];
2210 if (clip.path) {
2211 Rect bounds = clip.path->GetBounds(clip.transform);
2212 rect.IntersectRect(rect, bounds);
2213 } else {
2214 rect.IntersectRect(rect, clip.transform.TransformBounds(clip.rect));
2219 return rect;
2222 Matrix
2223 gfxContext::GetDeviceTransform() const
2225 Matrix mat;
2226 mat.Translate(-CurrentState().deviceOffset.x, -CurrentState().deviceOffset.y);
2227 return mat;
2230 Matrix
2231 gfxContext::GetDTTransform() const
2233 Matrix mat = mTransform;
2234 mat._31 -= CurrentState().deviceOffset.x;
2235 mat._32 -= CurrentState().deviceOffset.y;
2236 return mat;
2239 void
2240 gfxContext::PushNewDT(gfxContentType content)
2242 Rect clipBounds = GetAzureDeviceSpaceClipBounds();
2243 clipBounds.RoundOut();
2245 clipBounds.width = std::max(1.0f, clipBounds.width);
2246 clipBounds.height = std::max(1.0f, clipBounds.height);
2248 RefPtr<DrawTarget> newDT =
2249 mDT->CreateSimilarDrawTarget(IntSize(int32_t(clipBounds.width), int32_t(clipBounds.height)),
2250 gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content));
2252 Save();
2254 CurrentState().drawTarget = newDT;
2255 CurrentState().deviceOffset = clipBounds.TopLeft();
2257 mDT = newDT;
2261 * Work out whether cairo will snap inter-glyph spacing to pixels.
2263 * Layout does not align text to pixel boundaries, so, with font drawing
2264 * backends that snap glyph positions to pixels, it is important that
2265 * inter-glyph spacing within words is always an integer number of pixels.
2266 * This ensures that the drawing backend snaps all of the word's glyphs in the
2267 * same direction and so inter-glyph spacing remains the same.
2269 void
2270 gfxContext::GetRoundOffsetsToPixels(bool *aRoundX, bool *aRoundY)
2272 *aRoundX = false;
2273 // Could do something fancy here for ScaleFactors of
2274 // AxisAlignedTransforms, but we leave things simple.
2275 // Not much point rounding if a matrix will mess things up anyway.
2276 // Also return false for non-cairo contexts.
2277 if (CurrentMatrix().HasNonTranslation() || mDT) {
2278 *aRoundY = false;
2279 return;
2282 // All raster backends snap glyphs to pixels vertically.
2283 // Print backends set CAIRO_HINT_METRICS_OFF.
2284 *aRoundY = true;
2286 cairo_t *cr = GetCairo();
2287 cairo_scaled_font_t *scaled_font = cairo_get_scaled_font(cr);
2288 // Sometimes hint metrics gets set for us, most notably for printing.
2289 cairo_font_options_t *font_options = cairo_font_options_create();
2290 cairo_scaled_font_get_font_options(scaled_font, font_options);
2291 cairo_hint_metrics_t hint_metrics =
2292 cairo_font_options_get_hint_metrics(font_options);
2293 cairo_font_options_destroy(font_options);
2295 switch (hint_metrics) {
2296 case CAIRO_HINT_METRICS_OFF:
2297 *aRoundY = false;
2298 return;
2299 case CAIRO_HINT_METRICS_DEFAULT:
2300 // Here we mimic what cairo surface/font backends do. Printing
2301 // surfaces have already been handled by hint_metrics. The
2302 // fallback show_glyphs implementation composites pixel-aligned
2303 // glyph surfaces, so we just pick surface/font combinations that
2304 // override this.
2305 switch (cairo_scaled_font_get_type(scaled_font)) {
2306 #if CAIRO_HAS_DWRITE_FONT // dwrite backend is not in std cairo releases yet
2307 case CAIRO_FONT_TYPE_DWRITE:
2308 // show_glyphs is implemented on the font and so is used for
2309 // all surface types; however, it may pixel-snap depending on
2310 // the dwrite rendering mode
2311 if (!cairo_dwrite_scaled_font_get_force_GDI_classic(scaled_font) &&
2312 gfxWindowsPlatform::GetPlatform()->DWriteMeasuringMode() ==
2313 DWRITE_MEASURING_MODE_NATURAL) {
2314 return;
2316 #endif
2317 case CAIRO_FONT_TYPE_QUARTZ:
2318 // Quartz surfaces implement show_glyphs for Quartz fonts
2319 if (cairo_surface_get_type(cairo_get_target(cr)) ==
2320 CAIRO_SURFACE_TYPE_QUARTZ) {
2321 return;
2323 default:
2324 break;
2326 // fall through:
2327 case CAIRO_HINT_METRICS_ON:
2328 break;
2330 *aRoundX = true;
2331 return;