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/. */
7 #define _USE_MATH_DEFINES
11 #include "mozilla/Alignment.h"
15 #include "gfxContext.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"
28 #if CAIRO_HAS_DWRITE_FONT
29 #include "gfxWindowsPlatform.h"
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.
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();
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
;
59 transform
= transform
* state
.patternTransform
* mat
;
62 mPattern
= new (mSurfacePattern
.addr())
63 SurfacePattern(state
.sourceSurface
, EXTEND_CLAMP
, transform
);
66 mPattern
= new (mColorPattern
.addr())
67 ColorPattern(state
.color
);
74 mozilla::AlignedStorage2
<mozilla::gfx::ColorPattern
> mColorPattern
;
75 mozilla::AlignedStorage2
<mozilla::gfx::SurfacePattern
> mSurfacePattern
;
82 gfxContext::gfxContext(gfxASurface
*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,
101 gfxContext::gfxContext(DrawTarget
*aTarget
)
103 , mTransformChanged(false)
109 , mOriginalDT(aTarget
)
111 MOZ_COUNT_CTOR(gfxContext
);
113 mStateStack
.SetLength(1);
114 CurrentState().drawTarget
= mDT
;
115 mDT
->SetTransform(Matrix());
118 gfxContext::~gfxContext()
121 cairo_destroy(mCairo
);
124 cairo_destroy(mRefCairo
);
127 for (int i
= mStateStack
.Length() - 1; i
>= 0; i
--) {
128 for (unsigned int c
= 0; c
< mStateStack
[i
].pushedClips
.Length(); c
++) {
132 if (mStateStack
[i
].clipWasReset
) {
138 MOZ_COUNT_DTOR(gfxContext
);
142 gfxContext::OriginalSurface()
144 if (mCairo
|| mSurface
) {
148 if (mOriginalDT
&& mOriginalDT
->GetType() == BACKEND_CAIRO
) {
150 (cairo_surface_t
*)mOriginalDT
->GetNativeSurface(NATIVE_SURFACE_CAIRO_SURFACE
);
152 mSurface
= gfxASurface::Wrap(s
);
159 already_AddRefed
<gfxASurface
>
160 gfxContext::CurrentSurface(gfxFloat
*dx
, gfxFloat
*dy
)
163 cairo_surface_t
*s
= cairo_get_group_target(mCairo
);
164 if (s
== mSurface
->CairoSurface()) {
166 cairo_surface_get_device_offset(s
, dx
, dy
);
167 nsRefPtr
<gfxASurface
> ret
= mSurface
;
172 cairo_surface_get_device_offset(s
, dx
, dy
);
173 return gfxASurface::Wrap(s
);
175 if (mDT
->GetType() == BACKEND_CAIRO
) {
177 (cairo_surface_t
*)mDT
->GetNativeSurface(NATIVE_SURFACE_CAIRO_SURFACE
);
180 cairo_surface_get_device_offset(s
, dx
, dy
);
181 return gfxASurface::Wrap(s
);
188 // An Azure context doesn't have a surface backing it.
194 gfxContext::GetCairo()
200 if (mDT
->GetType() == BACKEND_CAIRO
) {
202 (cairo_t
*)mDT
->GetNativeSurface(NATIVE_SURFACE_CAIRO_CONTEXT
);
213 mRefCairo
= cairo_create(gfxPlatform::GetPlatform()->ScreenReferenceSurface()->CairoSurface());
224 CurrentState().transform
= mTransform
;
225 mStateStack
.AppendElement(AzureState(CurrentState()));
226 CurrentState().clipWasReset
= false;
227 CurrentState().pushedClips
.Clear();
232 gfxContext::Restore()
235 cairo_restore(mCairo
);
237 for (unsigned int c
= 0; c
< CurrentState().pushedClips
.Length(); c
++) {
241 if (CurrentState().clipWasReset
&&
242 CurrentState().drawTarget
== mStateStack
[mStateStack
.Length() - 2].drawTarget
) {
246 mStateStack
.RemoveElementAt(mStateStack
.Length() - 1);
248 mDT
= CurrentState().drawTarget
;
250 ChangeTransform(CurrentState().transform
, false);
256 gfxContext::NewPath()
259 cairo_new_path(mCairo
);
262 mPathBuilder
= nullptr;
264 mTransformChanged
= false;
269 gfxContext::ClosePath()
272 cairo_close_path(mCairo
);
275 mPathBuilder
->Close();
279 already_AddRefed
<gfxPath
> gfxContext::CopyPath()
281 nsRefPtr
<gfxPath
> path
;
283 path
= new gfxPath(cairo_copy_path(mCairo
));
286 path
= new gfxPath(mPath
);
288 return path
.forget();
291 void gfxContext::SetPath(gfxPath
* path
)
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
);
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;
303 mTransformChanged
= false;
308 gfxContext::CurrentPoint()
312 cairo_get_current_point(mCairo
, &x
, &y
);
313 return gfxPoint(x
, y
);
316 return ThebesPoint(mPathBuilder
->CurrentPoint());
324 cairo_stroke_preserve(mCairo
);
326 AzureState
&state
= CurrentState();
328 MOZ_ASSERT(!mTransformChanged
);
330 mDT
->StrokeRect(mRect
, GeneralPattern(this),
332 DrawOptions(1.0f
, GetOp(), state
.aaMode
));
336 mDT
->Stroke(mPath
, GeneralPattern(this), state
.strokeOptions
,
337 DrawOptions(1.0f
, GetOp(), state
.aaMode
));
345 PROFILER_LABEL("gfxContext", "Fill");
347 cairo_fill_preserve(mCairo
);
354 gfxContext::FillWithOpacity(gfxFloat aOpacity
)
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
360 if (aOpacity
!= 1.0) {
361 gfxContextAutoSaveRestore
saveRestore(this);
368 FillAzure(Float(aOpacity
));
373 gfxContext::MoveTo(const gfxPoint
& pt
)
376 cairo_move_to(mCairo
, pt
.x
, pt
.y
);
379 mPathBuilder
->MoveTo(ToPoint(pt
));
384 gfxContext::NewSubPath()
387 cairo_new_sub_path(mCairo
);
389 // XXX - This has no users, we should kill it, it should be equivelant to a
390 // MoveTo to the path's current point.
395 gfxContext::LineTo(const gfxPoint
& pt
)
398 cairo_line_to(mCairo
, pt
.x
, pt
.y
);
401 mPathBuilder
->LineTo(ToPoint(pt
));
406 gfxContext::CurveTo(const gfxPoint
& pt1
, const gfxPoint
& pt2
, const gfxPoint
& pt3
)
409 cairo_curve_to(mCairo
, pt1
.x
, pt1
.y
, pt2
.x
, pt2
.y
, pt3
.x
, pt3
.y
);
412 mPathBuilder
->BezierTo(ToPoint(pt1
), ToPoint(pt2
), ToPoint(pt3
));
417 gfxContext::QuadraticCurveTo(const gfxPoint
& pt1
, const gfxPoint
& pt2
)
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,
431 mPathBuilder
->QuadraticBezierTo(ToPoint(pt1
), ToPoint(pt2
));
436 gfxContext::Arc(const gfxPoint
& center
, gfxFloat radius
,
437 gfxFloat angle1
, gfxFloat angle2
)
440 cairo_arc(mCairo
, center
.x
, center
.y
, radius
, angle1
, angle2
);
443 mPathBuilder
->Arc(ToPoint(center
), Float(radius
), Float(angle1
), Float(angle2
));
448 gfxContext::NegativeArc(const gfxPoint
& center
, gfxFloat radius
,
449 gfxFloat angle1
, gfxFloat angle2
)
452 cairo_arc_negative(mCairo
, center
.x
, center
.y
, radius
, angle1
, angle2
);
455 mPathBuilder
->Arc(ToPoint(center
), Float(radius
), Float(angle2
), Float(angle1
));
460 gfxContext::Line(const gfxPoint
& start
, const gfxPoint
& end
)
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
477 gfxContext::Rectangle(const gfxRect
& rect
, bool snapToPixels
)
481 gfxRect
snappedRect(rect
);
483 if (UserToDevicePixelSnapped(snappedRect
, true))
486 cairo_get_matrix(mCairo
, &mat
);
487 cairo_identity_matrix(mCairo
);
488 Rectangle(snappedRect
);
489 cairo_set_matrix(mCairo
, &mat
);
495 cairo_rectangle(mCairo
, rect
.X(), rect
.Y(), rect
.Width(), rect
.Height());
497 Rect rec
= ToRect(rect
);
500 gfxRect
newRect(rect
);
501 if (UserToDevicePixelSnapped(newRect
, true)) {
502 gfxMatrix mat
= ThebesMatrix(mTransform
);
505 // We need the user space rect.
506 rec
= ToRect(mat
.TransformBounds(newRect
));
510 if (!mPathBuilder
&& !mPathIsRect
) {
518 mPathBuilder
->MoveTo(rec
.TopLeft());
519 mPathBuilder
->LineTo(rec
.TopRight());
520 mPathBuilder
->LineTo(rec
.BottomRight());
521 mPathBuilder
->LineTo(rec
.BottomLeft());
522 mPathBuilder
->Close();
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
);
537 gfxContext::Polygon(const gfxPoint
*points
, uint32_t numPoints
)
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
);
548 if (numPoints
== 0) {
554 mPathBuilder
->MoveTo(ToPoint(points
[0]));
555 for (uint32_t i
= 1; i
< numPoints
; i
++) {
556 mPathBuilder
->LineTo(ToPoint(points
[i
]));
562 gfxContext::DrawSurface(gfxASurface
*surface
, const gfxSize
& size
)
566 cairo_set_source_surface(mCairo
, surface
->CairoSurface(), 0, 0);
567 cairo_new_path(mCairo
);
570 Rectangle(gfxRect(gfxPoint(0.0, 0.0), size
), true);
573 cairo_restore(mCairo
);
575 // Lifetime needs to be limited here since we may wrap surface's data.
576 RefPtr
<SourceSurface
> surf
=
577 gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT
, surface
);
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
);
593 gfxContext::Translate(const gfxPoint
& pt
)
596 cairo_translate(mCairo
, pt
.x
, pt
.y
);
598 Matrix newMatrix
= mTransform
;
600 ChangeTransform(newMatrix
.Translate(Float(pt
.x
), Float(pt
.y
)));
605 gfxContext::Scale(gfxFloat x
, gfxFloat y
)
608 cairo_scale(mCairo
, x
, y
);
610 Matrix newMatrix
= mTransform
;
612 ChangeTransform(newMatrix
.Scale(Float(x
), Float(y
)));
617 gfxContext::Rotate(gfxFloat angle
)
620 cairo_rotate(mCairo
, angle
);
622 Matrix rotation
= Matrix::Rotation(Float(angle
));
623 ChangeTransform(rotation
* mTransform
);
628 gfxContext::Multiply(const gfxMatrix
& matrix
)
631 const cairo_matrix_t
& mat
= reinterpret_cast<const cairo_matrix_t
&>(matrix
);
632 cairo_transform(mCairo
, &mat
);
634 ChangeTransform(ToMatrix(matrix
) * mTransform
);
639 gfxContext::MultiplyAndNudgeToIntegers(const gfxMatrix
& matrix
)
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
646 Matrix transform
= ToMatrix(matrix
) * mTransform
;
647 transform
.NudgeToIntegers();
648 ChangeTransform(transform
);
653 gfxContext::SetMatrix(const gfxMatrix
& matrix
)
656 const cairo_matrix_t
& mat
= reinterpret_cast<const cairo_matrix_t
&>(matrix
);
657 cairo_set_matrix(mCairo
, &mat
);
659 ChangeTransform(ToMatrix(matrix
));
664 gfxContext::IdentityMatrix()
667 cairo_identity_matrix(mCairo
);
669 ChangeTransform(Matrix());
674 gfxContext::CurrentMatrix() const
678 cairo_get_matrix(mCairo
, &mat
);
679 return gfxMatrix(*reinterpret_cast<gfxMatrix
*>(&mat
));
681 return ThebesMatrix(mTransform
);
686 gfxContext::NudgeCurrentMatrixToIntegers()
690 cairo_get_matrix(mCairo
, &mat
);
691 gfxMatrix(*reinterpret_cast<gfxMatrix
*>(&mat
)).NudgeToIntegers();
692 cairo_set_matrix(mCairo
, &mat
);
694 gfxMatrix matrix
= ThebesMatrix(mTransform
);
695 matrix
.NudgeToIntegers();
696 ChangeTransform(ToMatrix(matrix
));
701 gfxContext::DeviceToUser(const gfxPoint
& point
) const
704 gfxPoint ret
= point
;
705 cairo_device_to_user(mCairo
, &ret
.x
, &ret
.y
);
708 Matrix matrix
= mTransform
;
712 return ThebesPoint(matrix
* ToPoint(point
));
717 gfxContext::DeviceToUser(const gfxSize
& size
) const
721 cairo_device_to_user_distance(mCairo
, &ret
.width
, &ret
.height
);
724 Matrix matrix
= mTransform
;
728 return ThebesSize(matrix
* ToSize(size
));
733 gfxContext::DeviceToUser(const gfxRect
& rect
) const
737 cairo_device_to_user(mCairo
, &ret
.x
, &ret
.y
);
738 cairo_device_to_user_distance(mCairo
, &ret
.width
, &ret
.height
);
741 Matrix matrix
= mTransform
;
745 return ThebesRect(matrix
.TransformBounds(ToRect(rect
)));
750 gfxContext::UserToDevice(const gfxPoint
& point
) const
753 gfxPoint ret
= point
;
754 cairo_user_to_device(mCairo
, &ret
.x
, &ret
.y
);
757 return ThebesPoint(mTransform
* ToPoint(point
));
762 gfxContext::UserToDevice(const gfxSize
& size
) const
766 cairo_user_to_device_distance(mCairo
, &ret
.width
, &ret
.height
);
769 const Matrix
&matrix
= mTransform
;
772 newSize
.width
= size
.width
* matrix
._11
+ size
.height
* matrix
._12
;
773 newSize
.height
= size
.width
* matrix
._21
+ size
.height
* matrix
._22
;
779 gfxContext::UserToDevice(const gfxRect
& rect
) const
782 double xmin
= rect
.X(), ymin
= rect
.Y(), xmax
= rect
.XMost(), ymax
= rect
.YMost();
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
);
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
);
802 const Matrix
&matrix
= mTransform
;
803 return ThebesRect(matrix
.TransformBounds(ToRect(rect
)));
808 gfxContext::UserToDevicePixelSnapped(gfxRect
& rect
, bool ignoreScale
) const
810 if (GetFlags() & FLAG_DISABLE_SNAPPING
)
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,
816 const gfxFloat epsilon
= 0.0000001;
817 #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon)
820 cairo_get_matrix(mCairo
, &mat
);
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)))
826 Matrix mat
= mTransform
;
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)))
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
)) {
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()));
858 gfxContext::UserToDevicePixelSnapped(gfxPoint
& pt
, bool ignoreScale
) const
860 if (GetFlags() & FLAG_DISABLE_SNAPPING
)
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,
866 const gfxFloat epsilon
= 0.0000001;
867 #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon)
870 cairo_get_matrix(mCairo
, &mat
);
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)))
876 Matrix mat
= mTransform
;
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)))
884 pt
= UserToDevice(pt
);
890 gfxContext::PixelSnappedRectangleAndSetPattern(const gfxRect
& 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
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
912 gfxContext::SetAntialiasMode(AntialiasMode mode
)
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
);
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
933 cairo_antialias_t aa
= cairo_get_antialias(mCairo
);
934 if (aa
== CAIRO_ANTIALIAS_NONE
)
936 return MODE_COVERAGE
;
938 if (CurrentState().aaMode
== AA_NONE
) {
941 return MODE_COVERAGE
;
946 gfxContext::SetDash(gfxLineType ltype
)
948 static double dash
[] = {5.0, 5.0};
949 static double dot
[] = {1.0, 1.0};
953 SetDash(dash
, 2, 0.0);
956 SetDash(dot
, 2, 0.0);
960 SetDash(nullptr, 0, 0.0);
966 gfxContext::SetDash(gfxFloat
*dashes
, int ndash
, gfxFloat offset
)
969 cairo_set_dash(mCairo
, dashes
, ndash
, offset
);
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()
985 gfxContext::CurrentDash(FallibleTArray
<gfxFloat
>& dashes
, gfxFloat
* offset
) const
988 int count
= cairo_get_dash_count(mCairo
);
989 if (count
<= 0 || !dashes
.SetLength(count
)) {
992 cairo_get_dash(mCairo
, dashes
.Elements(), offset
);
995 const AzureState
&state
= CurrentState();
996 int count
= state
.strokeOptions
.mDashLength
;
998 if (count
<= 0 || !dashes
.SetLength(count
)) {
1002 for (int i
= 0; i
< count
; i
++) {
1003 dashes
[i
] = state
.dashPattern
[i
];
1006 *offset
= state
.strokeOptions
.mDashOffset
;
1013 gfxContext::CurrentDashOffset() const
1016 if (cairo_get_dash_count(mCairo
) <= 0) {
1020 cairo_get_dash(mCairo
, nullptr, &offset
);
1023 return CurrentState().strokeOptions
.mDashOffset
;
1028 gfxContext::SetLineWidth(gfxFloat width
)
1031 cairo_set_line_width(mCairo
, width
);
1033 CurrentState().strokeOptions
.mLineWidth
= Float(width
);
1038 gfxContext::CurrentLineWidth() const
1041 return cairo_get_line_width(mCairo
);
1043 return CurrentState().strokeOptions
.mLineWidth
;
1048 gfxContext::SetOperator(GraphicsOperator op
)
1051 if (mFlags
& FLAG_SIMPLIFY_OPERATORS
) {
1052 if (op
!= OPERATOR_SOURCE
&&
1053 op
!= OPERATOR_CLEAR
&&
1054 op
!= OPERATOR_OVER
)
1058 cairo_set_operator(mCairo
, (cairo_operator_t
)op
);
1060 if (op
== OPERATOR_CLEAR
) {
1061 CurrentState().opIsClear
= true;
1064 CurrentState().opIsClear
= false;
1065 CurrentState().op
= CompositionOpForOp(op
);
1069 gfxContext::GraphicsOperator
1070 gfxContext::CurrentOperator() const
1073 return (GraphicsOperator
)cairo_get_operator(mCairo
);
1075 return ThebesOp(CurrentState().op
);
1080 gfxContext::SetLineCap(GraphicsLineCap cap
)
1083 cairo_set_line_cap(mCairo
, (cairo_line_cap_t
)cap
);
1085 CurrentState().strokeOptions
.mLineCap
= ToCapStyle(cap
);
1089 gfxContext::GraphicsLineCap
1090 gfxContext::CurrentLineCap() const
1093 return (GraphicsLineCap
)cairo_get_line_cap(mCairo
);
1095 return ThebesLineCap(CurrentState().strokeOptions
.mLineCap
);
1100 gfxContext::SetLineJoin(GraphicsLineJoin join
)
1103 cairo_set_line_join(mCairo
, (cairo_line_join_t
)join
);
1105 CurrentState().strokeOptions
.mLineJoin
= ToJoinStyle(join
);
1109 gfxContext::GraphicsLineJoin
1110 gfxContext::CurrentLineJoin() const
1113 return (GraphicsLineJoin
)cairo_get_line_join(mCairo
);
1115 return ThebesLineJoin(CurrentState().strokeOptions
.mLineJoin
);
1120 gfxContext::SetMiterLimit(gfxFloat limit
)
1123 cairo_set_miter_limit(mCairo
, limit
);
1125 CurrentState().strokeOptions
.mMiterLimit
= Float(limit
);
1130 gfxContext::CurrentMiterLimit() const
1133 return cairo_get_miter_limit(mCairo
);
1135 return CurrentState().strokeOptions
.mMiterLimit
;
1140 gfxContext::SetFillRule(FillRule rule
)
1143 cairo_set_fill_rule(mCairo
, (cairo_fill_rule_t
)rule
);
1145 CurrentState().fillRule
= rule
== FILL_RULE_WINDING
? FILL_WINDING
: FILL_EVEN_ODD
;
1149 gfxContext::FillRule
1150 gfxContext::CurrentFillRule() const
1153 return (FillRule
)cairo_get_fill_rule(mCairo
);
1155 return FILL_RULE_WINDING
;
1161 gfxContext::Clip(const gfxRect
& rect
)
1164 cairo_new_path(mCairo
);
1165 cairo_rectangle(mCairo
, rect
.X(), rect
.Y(), rect
.Width(), rect
.Height());
1168 AzureState::PushedClip clip
= { nullptr, ToRect(rect
), mTransform
};
1169 CurrentState().pushedClips
.AppendElement(clip
);
1170 mDT
->PushClipRect(ToRect(rect
));
1179 cairo_clip_preserve(mCairo
);
1182 MOZ_ASSERT(!mTransformChanged
);
1184 AzureState::PushedClip clip
= { nullptr, mRect
, mTransform
};
1185 CurrentState().pushedClips
.AppendElement(clip
);
1186 mDT
->PushClipRect(mRect
);
1189 mDT
->PushClip(mPath
);
1190 AzureState::PushedClip clip
= { mPath
, Rect(), mTransform
};
1191 CurrentState().pushedClips
.AppendElement(clip
);
1197 gfxContext::ResetClip()
1200 cairo_reset_clip(mCairo
);
1202 for (int i
= mStateStack
.Length() - 1; i
>= 0; i
--) {
1203 for (unsigned int c
= 0; c
< mStateStack
[i
].pushedClips
.Length(); c
++) {
1207 if (mStateStack
[i
].clipWasReset
) {
1211 CurrentState().pushedClips
.Clear();
1212 CurrentState().clipWasReset
= true;
1217 gfxContext::UpdateSurfaceClip()
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));
1230 gfxContext::GetClipExtents()
1233 double xmin
, ymin
, xmax
, ymax
;
1234 cairo_clip_extents(mCairo
, &xmin
, &ymin
, &xmax
, &ymax
);
1235 return gfxRect(xmin
, ymin
, xmax
- xmin
, ymax
- ymin
);
1237 Rect rect
= GetAzureDeviceSpaceClipBounds();
1239 if (rect
.width
== 0 || rect
.height
== 0) {
1240 return gfxRect(0, 0, 0, 0);
1243 Matrix mat
= mTransform
;
1245 rect
= mat
.TransformBounds(rect
);
1247 return ThebesRect(rect
);
1252 gfxContext::ClipContainsRect(const gfxRect
& aRect
)
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
)) {
1271 cairo_rectangle_list_destroy(clip
);
1274 unsigned int lastReset
= 0;
1275 for (int i
= mStateStack
.Length() - 2; i
> 0; i
--) {
1276 if (mStateStack
[i
].clipWasReset
) {
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-
1295 Rect clipRect
= mTransform
.TransformBounds(clip
.rect
);
1297 clipBounds
.IntersectRect(clipBounds
, clipRect
);
1302 return clipBounds
.Contains(ToRect(aRect
));
1306 // rendering sources
1309 gfxContext::SetColor(const gfxRGBA
& c
)
1312 if (gfxPlatform::GetCMSMode() == eCMSMode_All
) {
1315 qcms_transform
*transform
= gfxPlatform::GetCMSRGBTransform();
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
);
1324 cairo_set_source_rgba(mCairo
, c
.r
, c
.g
, c
.b
, c
.a
);
1326 CurrentState().pattern
= nullptr;
1327 CurrentState().sourceSurfCairo
= nullptr;
1328 CurrentState().sourceSurface
= nullptr;
1330 if (gfxPlatform::GetCMSMode() == eCMSMode_All
) {
1333 qcms_transform
*transform
= gfxPlatform::GetCMSRGBTransform();
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
);
1342 CurrentState().color
= ToColor(c
);
1347 gfxContext::SetDeviceColor(const gfxRGBA
& c
)
1350 cairo_set_source_rgba(mCairo
, c
.r
, c
.g
, c
.b
, c
.a
);
1352 CurrentState().pattern
= nullptr;
1353 CurrentState().sourceSurfCairo
= nullptr;
1354 CurrentState().sourceSurface
= nullptr;
1355 CurrentState().color
= ToColor(c
);
1360 gfxContext::GetDeviceColor(gfxRGBA
& c
)
1363 return cairo_pattern_get_rgba(cairo_get_source(mCairo
),
1367 &c
.a
) == CAIRO_STATUS_SUCCESS
;
1369 if (CurrentState().sourceSurface
) {
1372 if (CurrentState().pattern
) {
1374 return CurrentState().pattern
->GetSolidColor(c
);
1377 c
= ThebesRGBA(CurrentState().color
);
1383 gfxContext::SetSource(gfxASurface
*surface
, const gfxPoint
& offset
)
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
);
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
1394 CurrentState().sourceSurfCairo
= surface
;
1395 CurrentState().sourceSurface
=
1396 gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT
, surface
);
1397 CurrentState().color
= Color(0, 0, 0, 0);
1402 gfxContext::SetPattern(gfxPattern
*pattern
)
1405 cairo_set_source(mCairo
, pattern
->CairoPattern());
1407 CurrentState().sourceSurfCairo
= nullptr;
1408 CurrentState().sourceSurface
= nullptr;
1409 CurrentState().patternTransformChanged
= false;
1410 CurrentState().pattern
= pattern
;
1414 already_AddRefed
<gfxPattern
>
1415 gfxContext::GetPattern()
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
;
1423 wrapper
= new gfxPattern(pat
);
1425 wrapper
= new gfxPattern(gfxRGBA(0,0,0,0));
1427 return wrapper
.forget();
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.");
1437 pat
= new gfxPattern(ThebesRGBA(state
.color
));
1439 return pat
.forget();
1446 gfxContext::Mask(gfxPattern
*pattern
)
1449 cairo_mask(mCairo
, pattern
->CairoPattern());
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.
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
);
1486 mDT
->Mask(GeneralPattern(this), *pattern
->GetPattern(mDT
), DrawOptions(1.0f
, CurrentState().op
, CurrentState().aaMode
));
1491 gfxContext::Mask(gfxASurface
*surface
, const gfxPoint
& offset
)
1493 PROFILER_LABEL("gfxContext", "Mask");
1495 cairo_mask_surface(mCairo
, surface
->CairoSurface(), offset
.x
, offset
.y
);
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
);
1505 gfxPoint pt
= surface
->GetDeviceOffset();
1507 // We clip here to bind to the mask surface bounds, see above.
1508 mDT
->MaskSurface(GeneralPattern(this),
1510 Point(offset
.x
- pt
.x
, offset
.y
- pt
.y
),
1511 DrawOptions(1.0f
, CurrentState().op
, CurrentState().aaMode
));
1516 gfxContext::Paint(gfxFloat alpha
)
1518 PROFILER_LABEL("gfxContext", "Paint");
1520 cairo_paint_with_alpha(mCairo
, alpha
);
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();
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
);
1545 Matrix mat
= mDT
->GetTransform();
1547 Rect paintRect
= mat
.TransformBounds(Rect(Point(0, 0), Size(mDT
->GetSize())));
1549 if (state
.opIsClear
) {
1550 mDT
->ClearRect(paintRect
);
1552 mDT
->FillRect(paintRect
, GeneralPattern(this),
1553 DrawOptions(Float(alpha
), GetOp()));
1561 gfxContext::PushGroup(gfxContentType content
)
1564 cairo_push_group_with_content(mCairo
, (cairo_content_t
) content
);
1569 mDT
->SetTransform(GetDTTransform());
1574 GetRoundOutDeviceClipExtents(gfxContext
* aCtx
)
1576 gfxContextMatrixAutoSaveRestore
save(aCtx
);
1577 aCtx
->IdentityMatrix();
1578 gfxRect r
= aCtx
->GetClipExtents();
1584 * Copy the contents of aSrc to aDest, translated by aTranslation.
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
);
1597 gfxContext::PushGroupAndCopyBackground(gfxContentType content
)
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
);
1621 CopySurface(s
, d
, gfxPoint(0, 0));
1623 d
->SetOpaqueRect(s
->GetOpaqueRect());
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());
1653 mDT
->SetTransform(GetDTTransform());
1660 already_AddRefed
<gfxPattern
>
1661 gfxContext::PopGroup()
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();
1669 RefPtr
<SourceSurface
> src
= mDT
->Snapshot();
1670 Point deviceOffset
= CurrentState().deviceOffset
;
1674 Matrix mat
= mTransform
;
1677 Matrix deviceOffsetTranslation
;
1678 deviceOffsetTranslation
.Translate(deviceOffset
.x
, deviceOffset
.y
);
1680 nsRefPtr
<gfxPattern
> pat
= new gfxPattern(src
, deviceOffsetTranslation
* mat
);
1682 return pat
.forget();
1687 gfxContext::PopGroupToSource()
1690 cairo_pop_group_to_source(mCairo
);
1692 RefPtr
<SourceSurface
> src
= mDT
->Snapshot();
1693 Point deviceOffset
= CurrentState().deviceOffset
;
1695 CurrentState().sourceSurfCairo
= nullptr;
1696 CurrentState().sourceSurface
= src
;
1697 CurrentState().sourceSurfaceDeviceOffset
= deviceOffset
;
1698 CurrentState().pattern
= nullptr;
1699 CurrentState().patternTransformChanged
= false;
1701 Matrix mat
= mTransform
;
1704 Matrix deviceOffsetTranslation
;
1705 deviceOffsetTranslation
.Translate(deviceOffset
.x
, deviceOffset
.y
);
1706 CurrentState().surfTransform
= deviceOffsetTranslation
* mat
;
1711 gfxContext::PointInFill(const gfxPoint
& pt
)
1714 return cairo_in_fill(mCairo
, pt
.x
, pt
.y
);
1717 return mPath
->ContainsPoint(ToPoint(pt
), Matrix());
1722 gfxContext::PointInStroke(const gfxPoint
& pt
)
1725 return cairo_in_stroke(mCairo
, pt
.x
, pt
.y
);
1728 return mPath
->StrokeContainsPoint(CurrentState().strokeOptions
,
1735 gfxContext::GetUserPathExtent()
1738 double xmin
, ymin
, xmax
, ymax
;
1739 cairo_path_extents(mCairo
, &xmin
, &ymin
, &xmax
, &ymax
);
1740 return gfxRect(xmin
, ymin
, xmax
- xmin
, ymax
- ymin
);
1743 return ThebesRect(mPath
->GetBounds());
1748 gfxContext::GetUserFillExtent()
1751 double xmin
, ymin
, xmax
, ymax
;
1752 cairo_fill_extents(mCairo
, &xmin
, &ymin
, &xmax
, &ymax
);
1753 return gfxRect(xmin
, ymin
, xmax
- xmin
, ymax
- ymin
);
1756 return ThebesRect(mPath
->GetBounds());
1761 gfxContext::GetUserStrokeExtent()
1764 double xmin
, ymin
, xmax
, ymax
;
1765 cairo_stroke_extents(mCairo
, &xmin
, &ymin
, &xmax
, &ymax
);
1766 return gfxRect(xmin
, ymin
, xmax
- xmin
, ymax
- ymin
);
1769 return ThebesRect(mPath
->GetStrokedBounds(CurrentState().strokeOptions
, mTransform
));
1774 gfxContext::HasError()
1777 return cairo_status(mCairo
) != CAIRO_STATUS_SUCCESS
;
1779 // As far as this is concerned, an Azure context is never in error.
1785 gfxContext::RoundedRectangle(const gfxRect
& rect
,
1786 const gfxCornerSizes
& corners
,
1787 bool draw_clockwise
)
1790 // For CW drawing, this looks like:
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
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
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 = - * ---------------------
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.
1860 const gfxFloat alpha
= 0.55191497064665766025;
1862 typedef struct { gfxFloat a
, b
; } twoFloats
;
1864 twoFloats cwCornerMults
[4] = { { -1, 0 },
1868 twoFloats ccwCornerMults
[4] = { { +1, 0 },
1873 twoFloats
*cornerMults
= draw_clockwise
? cwCornerMults
: ccwCornerMults
;
1875 gfxPoint pc
, p0
, p1
, p2
, p3
;
1878 cairo_move_to(mCairo
, rect
.X() + corners
[NS_CORNER_TOP_LEFT
].width
, rect
.Y());
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.
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
,
1913 cairo_line_to (mCairo
, pc
.x
, pc
.y
);
1917 cairo_close_path (mCairo
);
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
1930 gfxContext::WriteAsPNG(const char* aFile
)
1932 nsRefPtr
<gfxASurface
> surf
= CurrentSurface();
1934 surf
->WriteAsPNG(aFile
);
1936 NS_WARNING("No surface found!");
1941 gfxContext::DumpAsDataURL()
1943 nsRefPtr
<gfxASurface
> surf
= CurrentSurface();
1945 surf
->DumpAsDataURL();
1947 NS_WARNING("No surface found!");
1952 gfxContext::CopyAsDataURL()
1954 nsRefPtr
<gfxASurface
> surf
= CurrentSurface();
1956 surf
->CopyAsDataURL();
1958 NS_WARNING("No surface found!");
1964 gfxContext::EnsurePath()
1967 mPath
= mPathBuilder
->Finish();
1968 mPathBuilder
= nullptr;
1972 if (mTransformChanged
) {
1973 Matrix mat
= mTransform
;
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()) {
1987 mPathBuilder
= mPath
->CopyToBuilder(CurrentState().fillRule
);
1989 mPath
= mPathBuilder
->Finish();
1990 mPathBuilder
= nullptr;
1994 EnsurePathBuilder();
1995 mPath
= mPathBuilder
->Finish();
1996 mPathBuilder
= nullptr;
2000 gfxContext::EnsurePathBuilder()
2002 if (mPathBuilder
&& !mTransformChanged
) {
2007 if (!mTransformChanged
) {
2008 mPathBuilder
= mPath
->CopyToBuilder(CurrentState().fillRule
);
2011 Matrix invTransform
= mTransform
;
2012 invTransform
.Invert();
2013 Matrix toNewUS
= mPathTransform
* invTransform
;
2014 mPathBuilder
= mPath
->TransformedCopyToBuilder(toNewUS
, CurrentState().fillRule
);
2019 DebugOnly
<PathBuilder
*> oldPath
= mPathBuilder
.get();
2021 if (!mPathBuilder
) {
2022 mPathBuilder
= mDT
->CreatePathBuilder(CurrentState().fillRule
);
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;
2052 gfxContext::FillAzure(Float aOpacity
)
2054 AzureState
&state
= CurrentState();
2056 CompositionOp op
= GetOp();
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
));
2068 mDT
->FillRect(mRect
, GeneralPattern(this), DrawOptions(aOpacity
, op
, state
.aaMode
));
2073 NS_ASSERTION(!state
.opIsClear
, "We shouldn't be clearing complex paths!");
2075 mDT
->Fill(mPath
, GeneralPattern(this), DrawOptions(aOpacity
, op
, state
.aaMode
));
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
2085 unsigned int lastReset
= 0;
2086 for (int i
= mStateStack
.Length() - 2; i
> 0; i
--) {
2087 if (mStateStack
[i
].clipWasReset
) {
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
);
2103 aDT
->PushClipRect(mStateStack
[i
].pushedClips
[c
].rect
);
2112 if (CurrentState().op
!= OP_SOURCE
) {
2113 return CurrentState().op
;
2116 AzureState
&state
= CurrentState();
2117 if (state
.pattern
) {
2118 if (state
.pattern
->IsOpaque()) {
2123 } else if (state
.sourceSurface
) {
2124 if (state
.sourceSurface
->GetFormat() == FORMAT_B8G8R8X8
) {
2130 if (state
.color
.a
> 0.999) {
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.
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;
2161 Matrix invMatrix
= aNewMatrix
;
2165 Matrix toNewUS
= mTransform
* invMatrix
;
2167 if (toNewUS
.IsRectilinear()) {
2168 mRect
= toNewUS
.TransformBounds(mRect
);
2169 mRect
.NudgeToIntegers();
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());
2195 gfxContext::GetAzureDeviceSpaceClipBounds()
2197 unsigned int lastReset
= 0;
2198 for (int i
= mStateStack
.Length() - 1; i
> 0; i
--) {
2199 if (mStateStack
[i
].clipWasReset
) {
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
];
2211 Rect bounds
= clip
.path
->GetBounds(clip
.transform
);
2212 rect
.IntersectRect(rect
, bounds
);
2214 rect
.IntersectRect(rect
, clip
.transform
.TransformBounds(clip
.rect
));
2223 gfxContext::GetDeviceTransform() const
2226 mat
.Translate(-CurrentState().deviceOffset
.x
, -CurrentState().deviceOffset
.y
);
2231 gfxContext::GetDTTransform() const
2233 Matrix mat
= mTransform
;
2234 mat
._31
-= CurrentState().deviceOffset
.x
;
2235 mat
._32
-= CurrentState().deviceOffset
.y
;
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
));
2254 CurrentState().drawTarget
= newDT
;
2255 CurrentState().deviceOffset
= clipBounds
.TopLeft();
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.
2270 gfxContext::GetRoundOffsetsToPixels(bool *aRoundX
, bool *aRoundY
)
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
) {
2282 // All raster backends snap glyphs to pixels vertically.
2283 // Print backends set CAIRO_HINT_METRICS_OFF.
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
:
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
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
) {
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
) {
2327 case CAIRO_HINT_METRICS_ON
: