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/Constants.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"
26 #if CAIRO_HAS_DWRITE_FONT
27 #include "gfxWindowsPlatform.h"
30 using namespace mozilla
;
31 using namespace mozilla::gfx
;
33 /* This class lives on the stack and allows gfxContext users to easily, and
34 * performantly get a gfx::Pattern to use for drawing in their current context.
39 GeneralPattern(gfxContext
*aContext
) : mContext(aContext
), mPattern(NULL
) {}
40 ~GeneralPattern() { if (mPattern
) { mPattern
->~Pattern(); } }
42 operator mozilla::gfx::Pattern
&()
44 gfxContext::AzureState
&state
= mContext
->CurrentState();
47 return *state
.pattern
->GetPattern(mContext
->mDT
, state
.patternTransformChanged
? &state
.patternTransform
: nullptr);
48 } else if (state
.sourceSurface
) {
49 Matrix transform
= state
.surfTransform
;
51 if (state
.patternTransformChanged
) {
52 Matrix mat
= mContext
->mTransform
;
55 transform
= transform
* state
.patternTransform
* mat
;
58 mPattern
= new (mSurfacePattern
.addr())
59 SurfacePattern(state
.sourceSurface
, EXTEND_CLAMP
, transform
);
62 mPattern
= new (mColorPattern
.addr())
63 ColorPattern(state
.color
);
70 mozilla::AlignedStorage2
<mozilla::gfx::ColorPattern
> mColorPattern
;
71 mozilla::AlignedStorage2
<mozilla::gfx::SurfacePattern
> mSurfacePattern
;
78 gfxContext::gfxContext(gfxASurface
*surface
)
82 MOZ_COUNT_CTOR(gfxContext
);
84 mCairo
= cairo_create(surface
->CairoSurface());
85 mFlags
= surface
->GetDefaultContextFlags();
86 if (mSurface
->GetRotateForLandscape()) {
87 // Rotate page 90 degrees to draw landscape page on portrait paper
88 gfxIntSize size
= mSurface
->GetSize();
89 Translate(gfxPoint(0, size
.width
));
90 gfxMatrix
matrix(0, -1,
97 gfxContext::gfxContext(DrawTarget
*aTarget
)
99 , mTransformChanged(false)
105 , mOriginalDT(aTarget
)
107 MOZ_COUNT_CTOR(gfxContext
);
109 mStateStack
.SetLength(1);
110 CurrentState().drawTarget
= mDT
;
111 mDT
->SetTransform(Matrix());
114 gfxContext::~gfxContext()
117 cairo_destroy(mCairo
);
120 cairo_destroy(mRefCairo
);
123 for (int i
= mStateStack
.Length() - 1; i
>= 0; i
--) {
124 for (unsigned int c
= 0; c
< mStateStack
[i
].pushedClips
.Length(); c
++) {
128 if (mStateStack
[i
].clipWasReset
) {
134 MOZ_COUNT_DTOR(gfxContext
);
138 gfxContext::OriginalSurface()
143 already_AddRefed
<gfxASurface
>
144 gfxContext::CurrentSurface(gfxFloat
*dx
, gfxFloat
*dy
)
147 cairo_surface_t
*s
= cairo_get_group_target(mCairo
);
148 if (s
== mSurface
->CairoSurface()) {
150 cairo_surface_get_device_offset(s
, dx
, dy
);
151 gfxASurface
*ret
= mSurface
;
157 cairo_surface_get_device_offset(s
, dx
, dy
);
158 return gfxASurface::Wrap(s
);
163 // An Azure context doesn't have a surface backing it.
169 gfxContext::GetCairo()
180 mRefCairo
= cairo_create(gfxPlatform::GetPlatform()->ScreenReferenceSurface()->CairoSurface());
191 CurrentState().transform
= mTransform
;
192 mStateStack
.AppendElement(AzureState(CurrentState()));
193 CurrentState().clipWasReset
= false;
194 CurrentState().pushedClips
.Clear();
199 gfxContext::Restore()
202 cairo_restore(mCairo
);
204 for (unsigned int c
= 0; c
< CurrentState().pushedClips
.Length(); c
++) {
208 if (CurrentState().clipWasReset
&&
209 CurrentState().drawTarget
== mStateStack
[mStateStack
.Length() - 2].drawTarget
) {
213 mStateStack
.RemoveElementAt(mStateStack
.Length() - 1);
215 mDT
= CurrentState().drawTarget
;
217 ChangeTransform(CurrentState().transform
, false);
223 gfxContext::NewPath()
226 cairo_new_path(mCairo
);
231 mTransformChanged
= false;
236 gfxContext::ClosePath()
239 cairo_close_path(mCairo
);
242 mPathBuilder
->Close();
246 already_AddRefed
<gfxPath
> gfxContext::CopyPath() const
249 nsRefPtr
<gfxPath
> path
= new gfxPath(cairo_copy_path(mCairo
));
250 return path
.forget();
252 // XXX - This is not yet supported for Azure.
257 void gfxContext::AppendPath(gfxPath
* path
)
260 if (path
->mPath
->status
== CAIRO_STATUS_SUCCESS
&& path
->mPath
->num_data
!= 0)
261 cairo_append_path(mCairo
, path
->mPath
);
263 // XXX - This is not yet supported for Azure.
269 gfxContext::CurrentPoint()
273 cairo_get_current_point(mCairo
, &x
, &y
);
274 return gfxPoint(x
, y
);
277 return ThebesPoint(mPathBuilder
->CurrentPoint());
285 cairo_stroke_preserve(mCairo
);
287 AzureState
&state
= CurrentState();
289 MOZ_ASSERT(!mTransformChanged
);
291 mDT
->StrokeRect(mRect
, GeneralPattern(this),
293 DrawOptions(1.0f
, GetOp(), state
.aaMode
));
297 mDT
->Stroke(mPath
, GeneralPattern(this), state
.strokeOptions
,
298 DrawOptions(1.0f
, GetOp(), state
.aaMode
));
306 SAMPLE_LABEL("gfxContext", "Fill");
308 cairo_fill_preserve(mCairo
);
315 gfxContext::FillWithOpacity(gfxFloat aOpacity
)
318 // This method exists in the hope that one day cairo gets a direct
319 // API for this, and then we would change this method to use that
321 if (aOpacity
!= 1.0) {
322 gfxContextAutoSaveRestore
saveRestore(this);
329 FillAzure(Float(aOpacity
));
334 gfxContext::MoveTo(const gfxPoint
& pt
)
337 cairo_move_to(mCairo
, pt
.x
, pt
.y
);
340 mPathBuilder
->MoveTo(ToPoint(pt
));
345 gfxContext::NewSubPath()
348 cairo_new_sub_path(mCairo
);
350 // XXX - This has no users, we should kill it, it should be equivelant to a
351 // MoveTo to the path's current point.
356 gfxContext::LineTo(const gfxPoint
& pt
)
359 cairo_line_to(mCairo
, pt
.x
, pt
.y
);
362 mPathBuilder
->LineTo(ToPoint(pt
));
367 gfxContext::CurveTo(const gfxPoint
& pt1
, const gfxPoint
& pt2
, const gfxPoint
& pt3
)
370 cairo_curve_to(mCairo
, pt1
.x
, pt1
.y
, pt2
.x
, pt2
.y
, pt3
.x
, pt3
.y
);
373 mPathBuilder
->BezierTo(ToPoint(pt1
), ToPoint(pt2
), ToPoint(pt3
));
378 gfxContext::QuadraticCurveTo(const gfxPoint
& pt1
, const gfxPoint
& pt2
)
382 cairo_get_current_point(mCairo
, &cx
, &cy
);
383 cairo_curve_to(mCairo
,
384 (cx
+ pt1
.x
* 2.0) / 3.0,
385 (cy
+ pt1
.y
* 2.0) / 3.0,
386 (pt1
.x
* 2.0 + pt2
.x
) / 3.0,
387 (pt1
.y
* 2.0 + pt2
.y
) / 3.0,
392 mPathBuilder
->QuadraticBezierTo(ToPoint(pt1
), ToPoint(pt2
));
397 gfxContext::Arc(const gfxPoint
& center
, gfxFloat radius
,
398 gfxFloat angle1
, gfxFloat angle2
)
401 cairo_arc(mCairo
, center
.x
, center
.y
, radius
, angle1
, angle2
);
404 mPathBuilder
->Arc(ToPoint(center
), Float(radius
), Float(angle1
), Float(angle2
));
409 gfxContext::NegativeArc(const gfxPoint
& center
, gfxFloat radius
,
410 gfxFloat angle1
, gfxFloat angle2
)
413 cairo_arc_negative(mCairo
, center
.x
, center
.y
, radius
, angle1
, angle2
);
416 mPathBuilder
->Arc(ToPoint(center
), Float(radius
), Float(angle2
), Float(angle1
));
421 gfxContext::Line(const gfxPoint
& start
, const gfxPoint
& end
)
428 mPathBuilder
->MoveTo(ToPoint(start
));
429 mPathBuilder
->LineTo(ToPoint(end
));
433 // XXX snapToPixels is only valid when snapping for filled
434 // rectangles and for even-width stroked rectangles.
435 // For odd-width stroked rectangles, we need to offset x/y by
438 gfxContext::Rectangle(const gfxRect
& rect
, bool snapToPixels
)
442 gfxRect
snappedRect(rect
);
444 if (UserToDevicePixelSnapped(snappedRect
, true))
447 cairo_get_matrix(mCairo
, &mat
);
448 cairo_identity_matrix(mCairo
);
449 Rectangle(snappedRect
);
450 cairo_set_matrix(mCairo
, &mat
);
456 cairo_rectangle(mCairo
, rect
.X(), rect
.Y(), rect
.Width(), rect
.Height());
458 Rect rec
= ToRect(rect
);
461 gfxRect
newRect(rect
);
462 if (UserToDevicePixelSnapped(newRect
, true)) {
463 gfxMatrix mat
= ThebesMatrix(mTransform
);
466 // We need the user space rect.
467 rec
= ToRect(mat
.TransformBounds(newRect
));
471 if (!mPathBuilder
&& !mPathIsRect
) {
479 mPathBuilder
->MoveTo(rec
.TopLeft());
480 mPathBuilder
->LineTo(rec
.TopRight());
481 mPathBuilder
->LineTo(rec
.BottomRight());
482 mPathBuilder
->LineTo(rec
.BottomLeft());
483 mPathBuilder
->Close();
488 gfxContext::Ellipse(const gfxPoint
& center
, const gfxSize
& dimensions
)
490 gfxSize halfDim
= dimensions
/ 2.0;
491 gfxRect
r(center
- gfxPoint(halfDim
.width
, halfDim
.height
), dimensions
);
492 gfxCornerSizes
c(halfDim
, halfDim
, halfDim
, halfDim
);
494 RoundedRectangle (r
, c
);
498 gfxContext::Polygon(const gfxPoint
*points
, uint32_t numPoints
)
504 cairo_move_to(mCairo
, points
[0].x
, points
[0].y
);
505 for (uint32_t i
= 1; i
< numPoints
; ++i
) {
506 cairo_line_to(mCairo
, points
[i
].x
, points
[i
].y
);
509 if (numPoints
== 0) {
515 mPathBuilder
->MoveTo(ToPoint(points
[0]));
516 for (uint32_t i
= 1; i
< numPoints
; i
++) {
517 mPathBuilder
->LineTo(ToPoint(points
[i
]));
523 gfxContext::DrawSurface(gfxASurface
*surface
, const gfxSize
& size
)
527 cairo_set_source_surface(mCairo
, surface
->CairoSurface(), 0, 0);
528 cairo_new_path(mCairo
);
531 Rectangle(gfxRect(gfxPoint(0.0, 0.0), size
), true);
534 cairo_restore(mCairo
);
536 // Lifetime needs to be limited here since we may wrap surface's data.
537 RefPtr
<SourceSurface
> surf
=
538 gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT
, surface
);
540 Rect
rect(0, 0, Float(size
.width
), Float(size
.height
));
541 rect
.Intersect(Rect(0, 0, Float(surf
->GetSize().width
), Float(surf
->GetSize().height
)));
543 // XXX - Should fix pixel snapping.
544 mDT
->DrawSurface(surf
, rect
, rect
);
550 gfxContext::Translate(const gfxPoint
& pt
)
553 cairo_translate(mCairo
, pt
.x
, pt
.y
);
555 Matrix newMatrix
= mTransform
;
557 ChangeTransform(newMatrix
.Translate(Float(pt
.x
), Float(pt
.y
)));
562 gfxContext::Scale(gfxFloat x
, gfxFloat y
)
565 cairo_scale(mCairo
, x
, y
);
567 Matrix newMatrix
= mTransform
;
569 ChangeTransform(newMatrix
.Scale(Float(x
), Float(y
)));
574 gfxContext::Rotate(gfxFloat angle
)
577 cairo_rotate(mCairo
, angle
);
579 Matrix rotation
= Matrix::Rotation(Float(angle
));
580 ChangeTransform(rotation
* mTransform
);
585 gfxContext::Multiply(const gfxMatrix
& matrix
)
588 const cairo_matrix_t
& mat
= reinterpret_cast<const cairo_matrix_t
&>(matrix
);
589 cairo_transform(mCairo
, &mat
);
591 ChangeTransform(ToMatrix(matrix
) * mTransform
);
596 gfxContext::MultiplyAndNudgeToIntegers(const gfxMatrix
& matrix
)
599 const cairo_matrix_t
& mat
= reinterpret_cast<const cairo_matrix_t
&>(matrix
);
600 cairo_transform(mCairo
, &mat
);
601 // XXX nudging to integers not currently supported for Thebes
603 Matrix transform
= ToMatrix(matrix
) * mTransform
;
604 transform
.NudgeToIntegers();
605 ChangeTransform(transform
);
610 gfxContext::SetMatrix(const gfxMatrix
& matrix
)
613 const cairo_matrix_t
& mat
= reinterpret_cast<const cairo_matrix_t
&>(matrix
);
614 cairo_set_matrix(mCairo
, &mat
);
617 mat
.Translate(-CurrentState().deviceOffset
.x
, -CurrentState().deviceOffset
.y
);
618 ChangeTransform(ToMatrix(matrix
));
623 gfxContext::IdentityMatrix()
626 cairo_identity_matrix(mCairo
);
628 ChangeTransform(Matrix());
633 gfxContext::CurrentMatrix() const
637 cairo_get_matrix(mCairo
, &mat
);
638 return gfxMatrix(*reinterpret_cast<gfxMatrix
*>(&mat
));
640 return ThebesMatrix(mTransform
);
645 gfxContext::NudgeCurrentMatrixToIntegers()
649 cairo_get_matrix(mCairo
, &mat
);
650 gfxMatrix(*reinterpret_cast<gfxMatrix
*>(&mat
)).NudgeToIntegers();
651 cairo_set_matrix(mCairo
, &mat
);
653 gfxMatrix matrix
= ThebesMatrix(mTransform
);
654 matrix
.NudgeToIntegers();
655 ChangeTransform(ToMatrix(matrix
));
660 gfxContext::DeviceToUser(const gfxPoint
& point
) const
663 gfxPoint ret
= point
;
664 cairo_device_to_user(mCairo
, &ret
.x
, &ret
.y
);
667 Matrix matrix
= mTransform
;
671 return ThebesPoint(matrix
* ToPoint(point
));
676 gfxContext::DeviceToUser(const gfxSize
& size
) const
680 cairo_device_to_user_distance(mCairo
, &ret
.width
, &ret
.height
);
683 Matrix matrix
= mTransform
;
687 return ThebesSize(matrix
* ToSize(size
));
692 gfxContext::DeviceToUser(const gfxRect
& rect
) const
696 cairo_device_to_user(mCairo
, &ret
.x
, &ret
.y
);
697 cairo_device_to_user_distance(mCairo
, &ret
.width
, &ret
.height
);
700 Matrix matrix
= mTransform
;
704 return ThebesRect(matrix
.TransformBounds(ToRect(rect
)));
709 gfxContext::UserToDevice(const gfxPoint
& point
) const
712 gfxPoint ret
= point
;
713 cairo_user_to_device(mCairo
, &ret
.x
, &ret
.y
);
716 return ThebesPoint(mTransform
* ToPoint(point
));
721 gfxContext::UserToDevice(const gfxSize
& size
) const
725 cairo_user_to_device_distance(mCairo
, &ret
.width
, &ret
.height
);
728 const Matrix
&matrix
= mTransform
;
730 gfxSize newSize
= size
;
731 newSize
.width
= newSize
.width
* matrix
._11
+ newSize
.height
* matrix
._12
;
732 newSize
.height
= newSize
.width
* matrix
._21
+ newSize
.height
* matrix
._22
;
738 gfxContext::UserToDevice(const gfxRect
& rect
) const
741 double xmin
= rect
.X(), ymin
= rect
.Y(), xmax
= rect
.XMost(), ymax
= rect
.YMost();
744 x
[0] = xmin
; y
[0] = ymax
;
745 x
[1] = xmax
; y
[1] = ymax
;
746 x
[2] = xmax
; y
[2] = ymin
;
748 cairo_user_to_device(mCairo
, &xmin
, &ymin
);
751 for (int i
= 0; i
< 3; i
++) {
752 cairo_user_to_device(mCairo
, &x
[i
], &y
[i
]);
753 xmin
= std::min(xmin
, x
[i
]);
754 xmax
= std::max(xmax
, x
[i
]);
755 ymin
= std::min(ymin
, y
[i
]);
756 ymax
= std::max(ymax
, y
[i
]);
759 return gfxRect(xmin
, ymin
, xmax
- xmin
, ymax
- ymin
);
761 const Matrix
&matrix
= mTransform
;
762 return ThebesRect(matrix
.TransformBounds(ToRect(rect
)));
767 gfxContext::UserToDevicePixelSnapped(gfxRect
& rect
, bool ignoreScale
) const
769 if (GetFlags() & FLAG_DISABLE_SNAPPING
)
772 // if we're not at 1.0 scale, don't snap, unless we're
773 // ignoring the scale. If we're not -just- a scale,
775 const gfxFloat epsilon
= 0.0000001;
776 #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon)
779 cairo_get_matrix(mCairo
, &mat
);
781 (!WITHIN_E(mat
.xx
,1.0) || !WITHIN_E(mat
.yy
,1.0) ||
782 !WITHIN_E(mat
.xy
,0.0) || !WITHIN_E(mat
.yx
,0.0)))
785 Matrix mat
= mTransform
;
787 (!WITHIN_E(mat
._11
,1.0) || !WITHIN_E(mat
._22
,1.0) ||
788 !WITHIN_E(mat
._12
,0.0) || !WITHIN_E(mat
._21
,0.0)))
793 gfxPoint p1
= UserToDevice(rect
.TopLeft());
794 gfxPoint p2
= UserToDevice(rect
.TopRight());
795 gfxPoint p3
= UserToDevice(rect
.BottomRight());
797 // Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
798 // two opposite corners define the entire rectangle. So check if
799 // the axis-aligned rectangle with opposite corners p1 and p3
800 // define an axis-aligned rectangle whose other corners are p2 and p4.
801 // We actually only need to check one of p2 and p4, since an affine
802 // transform maps parallelograms to parallelograms.
803 if (p2
== gfxPoint(p1
.x
, p3
.y
) || p2
== gfxPoint(p3
.x
, p1
.y
)) {
807 rect
.MoveTo(gfxPoint(std::min(p1
.x
, p3
.x
), std::min(p1
.y
, p3
.y
)));
808 rect
.SizeTo(gfxSize(std::max(p1
.x
, p3
.x
) - rect
.X(),
809 std::max(p1
.y
, p3
.y
) - rect
.Y()));
817 gfxContext::UserToDevicePixelSnapped(gfxPoint
& pt
, bool ignoreScale
) const
819 if (GetFlags() & FLAG_DISABLE_SNAPPING
)
822 // if we're not at 1.0 scale, don't snap, unless we're
823 // ignoring the scale. If we're not -just- a scale,
825 const gfxFloat epsilon
= 0.0000001;
826 #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon)
829 cairo_get_matrix(mCairo
, &mat
);
831 (!WITHIN_E(mat
.xx
,1.0) || !WITHIN_E(mat
.yy
,1.0) ||
832 !WITHIN_E(mat
.xy
,0.0) || !WITHIN_E(mat
.yx
,0.0)))
835 Matrix mat
= mTransform
;
837 (!WITHIN_E(mat
._11
,1.0) || !WITHIN_E(mat
._22
,1.0) ||
838 !WITHIN_E(mat
._12
,0.0) || !WITHIN_E(mat
._21
,0.0)))
843 pt
= UserToDevice(pt
);
849 gfxContext::PixelSnappedRectangleAndSetPattern(const gfxRect
& rect
,
854 // Bob attempts to pixel-snap the rectangle, and returns true if
855 // the snapping succeeds. If it does, we need to set up an
856 // identity matrix, because the rectangle given back is in device
859 // We then have to call a translate to dr.pos afterwards, to make
860 // sure the image lines up in the right place with our pixel
861 // snapped rectangle.
863 // If snapping wasn't successful, we just translate to where the
864 // pattern would normally start (in app coordinates) and do the
871 gfxContext::SetAntialiasMode(AntialiasMode mode
)
874 if (mode
== MODE_ALIASED
) {
875 cairo_set_antialias(mCairo
, CAIRO_ANTIALIAS_NONE
);
876 } else if (mode
== MODE_COVERAGE
) {
877 cairo_set_antialias(mCairo
, CAIRO_ANTIALIAS_DEFAULT
);
880 if (mode
== MODE_ALIASED
) {
881 CurrentState().aaMode
= AA_NONE
;
882 } else if (mode
== MODE_COVERAGE
) {
883 CurrentState().aaMode
= AA_SUBPIXEL
;
888 gfxContext::AntialiasMode
889 gfxContext::CurrentAntialiasMode() const
892 cairo_antialias_t aa
= cairo_get_antialias(mCairo
);
893 if (aa
== CAIRO_ANTIALIAS_NONE
)
895 return MODE_COVERAGE
;
897 if (CurrentState().aaMode
== AA_NONE
) {
900 return MODE_COVERAGE
;
905 gfxContext::SetDash(gfxLineType ltype
)
907 static double dash
[] = {5.0, 5.0};
908 static double dot
[] = {1.0, 1.0};
912 SetDash(dash
, 2, 0.0);
915 SetDash(dot
, 2, 0.0);
919 SetDash(nullptr, 0, 0.0);
925 gfxContext::SetDash(gfxFloat
*dashes
, int ndash
, gfxFloat offset
)
928 cairo_set_dash(mCairo
, dashes
, ndash
, offset
);
930 AzureState
&state
= CurrentState();
932 state
.dashPattern
.SetLength(ndash
);
933 for (int i
= 0; i
< ndash
; i
++) {
934 state
.dashPattern
[i
] = Float(dashes
[i
]);
936 state
.strokeOptions
.mDashLength
= ndash
;
937 state
.strokeOptions
.mDashOffset
= Float(offset
);
938 state
.strokeOptions
.mDashPattern
= ndash
? state
.dashPattern
.Elements() : NULL
;
943 gfxContext::CurrentDash(FallibleTArray
<gfxFloat
>& dashes
, gfxFloat
* offset
) const
946 int count
= cairo_get_dash_count(mCairo
);
947 if (count
<= 0 || !dashes
.SetLength(count
)) {
950 cairo_get_dash(mCairo
, dashes
.Elements(), offset
);
953 const AzureState
&state
= CurrentState();
954 int count
= state
.strokeOptions
.mDashLength
;
956 if (count
<= 0 || !dashes
.SetLength(count
)) {
960 for (int i
= 0; i
< count
; i
++) {
961 dashes
[i
] = state
.dashPattern
[i
];
964 *offset
= state
.strokeOptions
.mDashOffset
;
971 gfxContext::CurrentDashOffset() const
974 if (cairo_get_dash_count(mCairo
) <= 0) {
978 cairo_get_dash(mCairo
, NULL
, &offset
);
981 return CurrentState().strokeOptions
.mDashOffset
;
986 gfxContext::SetLineWidth(gfxFloat width
)
989 cairo_set_line_width(mCairo
, width
);
991 CurrentState().strokeOptions
.mLineWidth
= Float(width
);
996 gfxContext::CurrentLineWidth() const
999 return cairo_get_line_width(mCairo
);
1001 return CurrentState().strokeOptions
.mLineWidth
;
1006 gfxContext::SetOperator(GraphicsOperator op
)
1009 if (mFlags
& FLAG_SIMPLIFY_OPERATORS
) {
1010 if (op
!= OPERATOR_SOURCE
&&
1011 op
!= OPERATOR_CLEAR
&&
1012 op
!= OPERATOR_OVER
)
1016 cairo_set_operator(mCairo
, (cairo_operator_t
)op
);
1018 if (op
== OPERATOR_CLEAR
) {
1019 CurrentState().opIsClear
= true;
1022 CurrentState().opIsClear
= false;
1023 CurrentState().op
= CompositionOpForOp(op
);
1027 gfxContext::GraphicsOperator
1028 gfxContext::CurrentOperator() const
1031 return (GraphicsOperator
)cairo_get_operator(mCairo
);
1033 return ThebesOp(CurrentState().op
);
1038 gfxContext::SetLineCap(GraphicsLineCap cap
)
1041 cairo_set_line_cap(mCairo
, (cairo_line_cap_t
)cap
);
1043 CurrentState().strokeOptions
.mLineCap
= ToCapStyle(cap
);
1047 gfxContext::GraphicsLineCap
1048 gfxContext::CurrentLineCap() const
1051 return (GraphicsLineCap
)cairo_get_line_cap(mCairo
);
1053 return ThebesLineCap(CurrentState().strokeOptions
.mLineCap
);
1058 gfxContext::SetLineJoin(GraphicsLineJoin join
)
1061 cairo_set_line_join(mCairo
, (cairo_line_join_t
)join
);
1063 CurrentState().strokeOptions
.mLineJoin
= ToJoinStyle(join
);
1067 gfxContext::GraphicsLineJoin
1068 gfxContext::CurrentLineJoin() const
1071 return (GraphicsLineJoin
)cairo_get_line_join(mCairo
);
1073 return ThebesLineJoin(CurrentState().strokeOptions
.mLineJoin
);
1078 gfxContext::SetMiterLimit(gfxFloat limit
)
1081 cairo_set_miter_limit(mCairo
, limit
);
1083 CurrentState().strokeOptions
.mMiterLimit
= Float(limit
);
1088 gfxContext::CurrentMiterLimit() const
1091 return cairo_get_miter_limit(mCairo
);
1093 return CurrentState().strokeOptions
.mMiterLimit
;
1098 gfxContext::SetFillRule(FillRule rule
)
1101 cairo_set_fill_rule(mCairo
, (cairo_fill_rule_t
)rule
);
1103 CurrentState().fillRule
= rule
== FILL_RULE_WINDING
? FILL_WINDING
: FILL_EVEN_ODD
;
1107 gfxContext::FillRule
1108 gfxContext::CurrentFillRule() const
1111 return (FillRule
)cairo_get_fill_rule(mCairo
);
1113 return FILL_RULE_WINDING
;
1119 gfxContext::Clip(const gfxRect
& rect
)
1122 cairo_new_path(mCairo
);
1123 cairo_rectangle(mCairo
, rect
.X(), rect
.Y(), rect
.Width(), rect
.Height());
1126 AzureState::PushedClip clip
= { NULL
, ToRect(rect
), mTransform
};
1127 CurrentState().pushedClips
.AppendElement(clip
);
1128 mDT
->PushClipRect(ToRect(rect
));
1137 cairo_clip_preserve(mCairo
);
1140 MOZ_ASSERT(!mTransformChanged
);
1142 AzureState::PushedClip clip
= { NULL
, mRect
, mTransform
};
1143 CurrentState().pushedClips
.AppendElement(clip
);
1144 mDT
->PushClipRect(mRect
);
1147 mDT
->PushClip(mPath
);
1148 AzureState::PushedClip clip
= { mPath
, Rect(), mTransform
};
1149 CurrentState().pushedClips
.AppendElement(clip
);
1155 gfxContext::ResetClip()
1158 cairo_reset_clip(mCairo
);
1160 for (int i
= mStateStack
.Length() - 1; i
>= 0; i
--) {
1161 for (unsigned int c
= 0; c
< mStateStack
[i
].pushedClips
.Length(); c
++) {
1165 if (mStateStack
[i
].clipWasReset
) {
1169 CurrentState().pushedClips
.Clear();
1170 CurrentState().clipWasReset
= true;
1175 gfxContext::UpdateSurfaceClip()
1179 // we paint an empty rectangle to ensure the clip is propagated to
1180 // the destination surface
1181 SetDeviceColor(gfxRGBA(0,0,0,0));
1182 Rectangle(gfxRect(0,1,1,0));
1188 gfxContext::GetClipExtents()
1191 double xmin
, ymin
, xmax
, ymax
;
1192 cairo_clip_extents(mCairo
, &xmin
, &ymin
, &xmax
, &ymax
);
1193 return gfxRect(xmin
, ymin
, xmax
- xmin
, ymax
- ymin
);
1195 Rect rect
= GetAzureDeviceSpaceClipBounds();
1197 if (rect
.width
== 0 || rect
.height
== 0) {
1198 return gfxRect(0, 0, 0, 0);
1201 Matrix mat
= mTransform
;
1203 rect
= mat
.TransformBounds(rect
);
1205 return ThebesRect(rect
);
1210 gfxContext::ClipContainsRect(const gfxRect
& aRect
)
1213 cairo_rectangle_list_t
*clip
=
1214 cairo_copy_clip_rectangle_list(mCairo
);
1216 bool result
= false;
1218 if (clip
->status
== CAIRO_STATUS_SUCCESS
) {
1219 for (int i
= 0; i
< clip
->num_rectangles
; i
++) {
1220 gfxRect
rect(clip
->rectangles
[i
].x
, clip
->rectangles
[i
].y
,
1221 clip
->rectangles
[i
].width
, clip
->rectangles
[i
].height
);
1222 if (rect
.Contains(aRect
)) {
1229 cairo_rectangle_list_destroy(clip
);
1232 unsigned int lastReset
= 0;
1233 for (int i
= mStateStack
.Length() - 2; i
> 0; i
--) {
1234 if (mStateStack
[i
].clipWasReset
) {
1239 // Since we always return false when the clip list contains a
1240 // non-rectangular clip or a non-rectilinear transform, our 'total' clip
1241 // is always a rectangle if we hit the end of this function.
1242 Rect
clipBounds(0, 0, Float(mDT
->GetSize().width
), Float(mDT
->GetSize().height
));
1244 for (unsigned int i
= lastReset
; i
< mStateStack
.Length(); i
++) {
1245 for (unsigned int c
= 0; c
< mStateStack
[i
].pushedClips
.Length(); c
++) {
1246 AzureState::PushedClip
&clip
= mStateStack
[i
].pushedClips
[c
];
1247 if (clip
.path
|| !clip
.transform
.IsRectilinear()) {
1248 // Cairo behavior is we return false if the clip contains a non-
1252 Rect clipRect
= mTransform
.TransformBounds(clip
.rect
);
1254 clipBounds
.IntersectRect(clipBounds
, clipRect
);
1259 return clipBounds
.Contains(ToRect(aRect
));
1263 // rendering sources
1266 gfxContext::SetColor(const gfxRGBA
& c
)
1269 if (gfxPlatform::GetCMSMode() == eCMSMode_All
) {
1272 qcms_transform
*transform
= gfxPlatform::GetCMSRGBTransform();
1274 gfxPlatform::TransformPixel(c
, cms
, transform
);
1276 // Use the original alpha to avoid unnecessary float->byte->float
1277 // conversion errors
1278 cairo_set_source_rgba(mCairo
, cms
.r
, cms
.g
, cms
.b
, c
.a
);
1281 cairo_set_source_rgba(mCairo
, c
.r
, c
.g
, c
.b
, c
.a
);
1283 CurrentState().pattern
= NULL
;
1284 CurrentState().sourceSurfCairo
= NULL
;
1285 CurrentState().sourceSurface
= NULL
;
1287 if (gfxPlatform::GetCMSMode() == eCMSMode_All
) {
1290 qcms_transform
*transform
= gfxPlatform::GetCMSRGBTransform();
1292 gfxPlatform::TransformPixel(c
, cms
, transform
);
1294 // Use the original alpha to avoid unnecessary float->byte->float
1295 // conversion errors
1296 CurrentState().color
= ToColor(cms
);
1299 CurrentState().color
= ToColor(c
);
1304 gfxContext::SetDeviceColor(const gfxRGBA
& c
)
1307 cairo_set_source_rgba(mCairo
, c
.r
, c
.g
, c
.b
, c
.a
);
1309 CurrentState().pattern
= NULL
;
1310 CurrentState().sourceSurfCairo
= NULL
;
1311 CurrentState().sourceSurface
= NULL
;
1312 CurrentState().color
= ToColor(c
);
1317 gfxContext::GetDeviceColor(gfxRGBA
& c
)
1320 return cairo_pattern_get_rgba(cairo_get_source(mCairo
),
1324 &c
.a
) == CAIRO_STATUS_SUCCESS
;
1326 if (CurrentState().sourceSurface
) {
1329 if (CurrentState().pattern
) {
1331 return CurrentState().pattern
->GetSolidColor(c
);
1334 c
= ThebesRGBA(CurrentState().color
);
1340 gfxContext::SetSource(gfxASurface
*surface
, const gfxPoint
& offset
)
1343 NS_ASSERTION(surface
->GetAllowUseAsSource(), "Surface not allowed to be used as source!");
1344 cairo_set_source_surface(mCairo
, surface
->CairoSurface(), offset
.x
, offset
.y
);
1346 CurrentState().surfTransform
= Matrix(1.0f
, 0, 0, 1.0f
, Float(offset
.x
), Float(offset
.y
));
1347 CurrentState().pattern
= NULL
;
1348 CurrentState().patternTransformChanged
= false;
1349 // Keep the underlying cairo surface around while we keep the
1351 CurrentState().sourceSurfCairo
= surface
;
1352 CurrentState().sourceSurface
=
1353 gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT
, surface
);
1358 gfxContext::SetPattern(gfxPattern
*pattern
)
1361 cairo_set_source(mCairo
, pattern
->CairoPattern());
1363 CurrentState().sourceSurfCairo
= NULL
;
1364 CurrentState().sourceSurface
= NULL
;
1365 CurrentState().patternTransformChanged
= false;
1366 CurrentState().pattern
= pattern
;
1370 already_AddRefed
<gfxPattern
>
1371 gfxContext::GetPattern()
1374 cairo_pattern_t
*pat
= cairo_get_source(mCairo
);
1375 NS_ASSERTION(pat
, "I was told this couldn't be null");
1377 gfxPattern
*wrapper
= nullptr;
1379 wrapper
= new gfxPattern(pat
);
1381 wrapper
= new gfxPattern(gfxRGBA(0,0,0,0));
1383 NS_IF_ADDREF(wrapper
);
1386 nsRefPtr
<gfxPattern
> pat
;
1388 AzureState
&state
= CurrentState();
1389 if (state
.pattern
) {
1390 pat
= state
.pattern
;
1391 } else if (state
.sourceSurface
) {
1392 NS_ASSERTION(false, "Ugh, this isn't good.");
1394 pat
= new gfxPattern(ThebesRGBA(state
.color
));
1396 return pat
.forget();
1404 gfxContext::Mask(gfxPattern
*pattern
)
1407 cairo_mask(mCairo
, pattern
->CairoPattern());
1409 mDT
->Mask(GeneralPattern(this), *pattern
->GetPattern(mDT
), DrawOptions(1.0f
, CurrentState().op
, CurrentState().aaMode
));
1414 gfxContext::Mask(gfxASurface
*surface
, const gfxPoint
& offset
)
1416 SAMPLE_LABEL("gfxContext", "Mask");
1418 cairo_mask_surface(mCairo
, surface
->CairoSurface(), offset
.x
, offset
.y
);
1420 // Lifetime needs to be limited here as we may simply wrap surface's data.
1421 RefPtr
<SourceSurface
> sourceSurf
=
1422 gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT
, surface
);
1424 gfxPoint pt
= surface
->GetDeviceOffset();
1425 mDT
->Mask(GeneralPattern(this),
1426 SurfacePattern(sourceSurf
, EXTEND_CLAMP
,
1427 Matrix(1.0f
, 0, 0, 1.0f
, Float(offset
.x
- pt
.x
), Float(offset
.y
- pt
.y
))),
1428 DrawOptions(1.0f
, CurrentState().op
, CurrentState().aaMode
));
1433 gfxContext::Paint(gfxFloat alpha
)
1435 SAMPLE_LABEL("gfxContext", "Paint");
1437 cairo_paint_with_alpha(mCairo
, alpha
);
1439 AzureState
&state
= CurrentState();
1441 Matrix mat
= mDT
->GetTransform();
1443 Rect paintRect
= mat
.TransformBounds(Rect(Point(0, 0), Size(mDT
->GetSize())));
1445 if (state
.opIsClear
) {
1446 mDT
->ClearRect(paintRect
);
1448 mDT
->FillRect(paintRect
, GeneralPattern(this),
1449 DrawOptions(Float(alpha
), GetOp()));
1457 gfxContext::PushGroup(gfxASurface::gfxContentType content
)
1460 cairo_push_group_with_content(mCairo
, (cairo_content_t
) content
);
1465 mDT
->SetTransform(GetDTTransform());
1470 GetRoundOutDeviceClipExtents(gfxContext
* aCtx
)
1472 gfxContextMatrixAutoSaveRestore
save(aCtx
);
1473 aCtx
->IdentityMatrix();
1474 gfxRect r
= aCtx
->GetClipExtents();
1480 * Copy the contents of aSrc to aDest, translated by aTranslation.
1483 CopySurface(gfxASurface
* aSrc
, gfxASurface
* aDest
, const gfxPoint
& aTranslation
)
1485 cairo_t
*cr
= cairo_create(aDest
->CairoSurface());
1486 cairo_set_source_surface(cr
, aSrc
->CairoSurface(), aTranslation
.x
, aTranslation
.y
);
1487 cairo_set_operator(cr
, CAIRO_OPERATOR_SOURCE
);
1493 gfxContext::PushGroupAndCopyBackground(gfxASurface::gfxContentType content
)
1496 if (content
== gfxASurface::CONTENT_COLOR_ALPHA
&&
1497 !(GetFlags() & FLAG_DISABLE_COPY_BACKGROUND
)) {
1498 nsRefPtr
<gfxASurface
> s
= CurrentSurface();
1499 if ((s
->GetAllowUseAsSource() || s
->GetType() == gfxASurface::SurfaceTypeTee
) &&
1500 (s
->GetContentType() == gfxASurface::CONTENT_COLOR
||
1501 s
->GetOpaqueRect().Contains(GetRoundOutDeviceClipExtents(this)))) {
1502 cairo_push_group_with_content(mCairo
, CAIRO_CONTENT_COLOR
);
1503 nsRefPtr
<gfxASurface
> d
= CurrentSurface();
1505 if (d
->GetType() == gfxASurface::SurfaceTypeTee
) {
1506 NS_ASSERTION(s
->GetType() == gfxASurface::SurfaceTypeTee
, "Mismatched types");
1507 nsAutoTArray
<nsRefPtr
<gfxASurface
>,2> ss
;
1508 nsAutoTArray
<nsRefPtr
<gfxASurface
>,2> ds
;
1509 static_cast<gfxTeeSurface
*>(s
.get())->GetSurfaces(&ss
);
1510 static_cast<gfxTeeSurface
*>(d
.get())->GetSurfaces(&ds
);
1511 NS_ASSERTION(ss
.Length() == ds
.Length(), "Mismatched lengths");
1512 gfxPoint translation
= d
->GetDeviceOffset() - s
->GetDeviceOffset();
1513 for (uint32_t i
= 0; i
< ss
.Length(); ++i
) {
1514 CopySurface(ss
[i
], ds
[i
], translation
);
1517 CopySurface(s
, d
, gfxPoint(0, 0));
1519 d
->SetOpaqueRect(s
->GetOpaqueRect());
1524 IntRect clipExtents
;
1525 if (mDT
->GetFormat() != FORMAT_B8G8R8X8
) {
1526 gfxRect clipRect
= GetRoundOutDeviceClipExtents(this);
1527 clipExtents
= IntRect(clipRect
.x
, clipRect
.y
, clipRect
.width
, clipRect
.height
);
1529 if (mDT
->GetFormat() == FORMAT_B8G8R8X8
||
1530 mDT
->GetOpaqueRect().Contains(clipExtents
)) {
1531 DrawTarget
*oldDT
= mDT
;
1532 RefPtr
<SourceSurface
> source
= mDT
->Snapshot();
1533 Point oldDeviceOffset
= CurrentState().deviceOffset
;
1535 PushNewDT(gfxASurface::CONTENT_COLOR
);
1537 Point offset
= CurrentState().deviceOffset
- oldDeviceOffset
;
1538 Rect
surfRect(0, 0, Float(mDT
->GetSize().width
), Float(mDT
->GetSize().height
));
1539 Rect sourceRect
= surfRect
;
1540 sourceRect
.x
+= offset
.x
;
1541 sourceRect
.y
+= offset
.y
;
1543 mDT
->SetTransform(Matrix());
1544 mDT
->DrawSurface(source
, surfRect
, sourceRect
);
1545 mDT
->SetOpaqueRect(oldDT
->GetOpaqueRect());
1548 mDT
->SetTransform(GetDTTransform());
1555 already_AddRefed
<gfxPattern
>
1556 gfxContext::PopGroup()
1559 cairo_pattern_t
*pat
= cairo_pop_group(mCairo
);
1560 gfxPattern
*wrapper
= new gfxPattern(pat
);
1561 cairo_pattern_destroy(pat
);
1562 NS_IF_ADDREF(wrapper
);
1565 RefPtr
<SourceSurface
> src
= mDT
->Snapshot();
1566 Point deviceOffset
= CurrentState().deviceOffset
;
1570 Matrix mat
= mTransform
;
1573 Matrix deviceOffsetTranslation
;
1574 deviceOffsetTranslation
.Translate(deviceOffset
.x
, deviceOffset
.y
);
1576 nsRefPtr
<gfxPattern
> pat
= new gfxPattern(src
, deviceOffsetTranslation
* mat
);
1578 return pat
.forget();
1583 gfxContext::PopGroupToSource()
1586 cairo_pop_group_to_source(mCairo
);
1588 RefPtr
<SourceSurface
> src
= mDT
->Snapshot();
1589 Point deviceOffset
= CurrentState().deviceOffset
;
1591 CurrentState().sourceSurfCairo
= NULL
;
1592 CurrentState().sourceSurface
= src
;
1593 CurrentState().pattern
= NULL
;
1594 CurrentState().patternTransformChanged
= false;
1596 Matrix mat
= mTransform
;
1599 Matrix deviceOffsetTranslation
;
1600 deviceOffsetTranslation
.Translate(deviceOffset
.x
, deviceOffset
.y
);
1601 CurrentState().surfTransform
= deviceOffsetTranslation
* mat
;
1606 gfxContext::PointInFill(const gfxPoint
& pt
)
1609 return cairo_in_fill(mCairo
, pt
.x
, pt
.y
);
1611 return mPath
->ContainsPoint(ToPoint(pt
), mTransform
);
1616 gfxContext::PointInStroke(const gfxPoint
& pt
)
1619 return cairo_in_stroke(mCairo
, pt
.x
, pt
.y
);
1621 return mPath
->StrokeContainsPoint(CurrentState().strokeOptions
,
1628 gfxContext::GetUserPathExtent()
1631 double xmin
, ymin
, xmax
, ymax
;
1632 cairo_path_extents(mCairo
, &xmin
, &ymin
, &xmax
, &ymax
);
1633 return gfxRect(xmin
, ymin
, xmax
- xmin
, ymax
- ymin
);
1635 return ThebesRect(mPath
->GetBounds());
1640 gfxContext::GetUserFillExtent()
1643 double xmin
, ymin
, xmax
, ymax
;
1644 cairo_fill_extents(mCairo
, &xmin
, &ymin
, &xmax
, &ymax
);
1645 return gfxRect(xmin
, ymin
, xmax
- xmin
, ymax
- ymin
);
1647 return ThebesRect(mPath
->GetBounds());
1652 gfxContext::GetUserStrokeExtent()
1655 double xmin
, ymin
, xmax
, ymax
;
1656 cairo_stroke_extents(mCairo
, &xmin
, &ymin
, &xmax
, &ymax
);
1657 return gfxRect(xmin
, ymin
, xmax
- xmin
, ymax
- ymin
);
1659 return ThebesRect(mPath
->GetStrokedBounds(CurrentState().strokeOptions
, mTransform
));
1663 already_AddRefed
<gfxFlattenedPath
>
1664 gfxContext::GetFlattenedPath()
1667 gfxFlattenedPath
*path
=
1668 new gfxFlattenedPath(cairo_copy_path_flat(mCairo
));
1672 // XXX - Used by SVG, needs fixing.
1678 gfxContext::HasError()
1681 return cairo_status(mCairo
) != CAIRO_STATUS_SUCCESS
;
1683 // As far as this is concerned, an Azure context is never in error.
1689 gfxContext::RoundedRectangle(const gfxRect
& rect
,
1690 const gfxCornerSizes
& corners
,
1691 bool draw_clockwise
)
1694 // For CW drawing, this looks like:
1706 // Where 0, 1, 2, 3 are the control points of the Bezier curve for
1707 // the corner, and C is the actual corner point.
1709 // At the start of the loop, the current point is assumed to be
1710 // the point adjacent to the top left corner on the top
1711 // horizontal. Note that corner indices start at the top left and
1712 // continue clockwise, whereas in our loop i = 0 refers to the top
1715 // When going CCW, the control points are swapped, and the first
1716 // corner that's drawn is the top left (along with the top segment).
1718 // There is considerable latitude in how one chooses the four
1719 // control points for a Bezier curve approximation to an ellipse.
1720 // For the overall path to be continuous and show no corner at the
1721 // endpoints of the arc, points 0 and 3 must be at the ends of the
1722 // straight segments of the rectangle; points 0, 1, and C must be
1723 // collinear; and points 3, 2, and C must also be collinear. This
1724 // leaves only two free parameters: the ratio of the line segments
1725 // 01 and 0C, and the ratio of the line segments 32 and 3C. See
1726 // the following papers for extensive discussion of how to choose
1729 // Dokken, Tor, et al. "Good approximation of circles by
1730 // curvature-continuous Bezier curves." Computer-Aided
1731 // Geometric Design 7(1990) 33--41.
1732 // Goldapp, Michael. "Approximation of circular arcs by cubic
1733 // polynomials." Computer-Aided Geometric Design 8(1991) 227--238.
1734 // Maisonobe, Luc. "Drawing an elliptical arc using polylines,
1735 // quadratic, or cubic Bezier curves."
1736 // http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf
1738 // We follow the approach in section 2 of Goldapp (least-error,
1739 // Hermite-type approximation) and make both ratios equal to
1741 // 2 2 + n - sqrt(2n + 28)
1742 // alpha = - * ---------------------
1745 // where n = 3( cbrt(sqrt(2)+1) - cbrt(sqrt(2)-1) ).
1747 // This is the result of Goldapp's equation (10b) when the angle
1748 // swept out by the arc is pi/2, and the parameter "a-bar" is the
1749 // expression given immediately below equation (21).
1751 // Using this value, the maximum radial error for a circle, as a
1752 // fraction of the radius, is on the order of 0.2 x 10^-3.
1753 // Neither Dokken nor Goldapp discusses error for a general
1754 // ellipse; Maisonobe does, but his choice of control points
1755 // follows different constraints, and Goldapp's expression for
1756 // 'alpha' gives much smaller radial error, even for very flat
1757 // ellipses, than Maisonobe's equivalent.
1759 // For the various corners and for each axis, the sign of this
1760 // constant changes, or it might be 0 -- it's multiplied by the
1761 // appropriate multiplier from the list before using.
1764 const gfxFloat alpha
= 0.55191497064665766025;
1766 typedef struct { gfxFloat a
, b
; } twoFloats
;
1768 twoFloats cwCornerMults
[4] = { { -1, 0 },
1772 twoFloats ccwCornerMults
[4] = { { +1, 0 },
1777 twoFloats
*cornerMults
= draw_clockwise
? cwCornerMults
: ccwCornerMults
;
1779 gfxPoint pc
, p0
, p1
, p2
, p3
;
1782 cairo_move_to(mCairo
, rect
.X() + corners
[NS_CORNER_TOP_LEFT
].width
, rect
.Y());
1784 cairo_move_to(mCairo
, rect
.X() + rect
.Width() - corners
[NS_CORNER_TOP_RIGHT
].width
, rect
.Y());
1786 NS_FOR_CSS_CORNERS(i
) {
1787 // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
1788 mozilla::css::Corner c
= mozilla::css::Corner(draw_clockwise
? ((i
+1) % 4) : ((4-i
) % 4));
1790 // i+2 and i+3 respectively. These are used to index into the corner
1791 // multiplier table, and were deduced by calculating out the long form
1792 // of each corner and finding a pattern in the signs and values.
1796 pc
= rect
.AtCorner(c
);
1798 if (corners
[c
].width
> 0.0 && corners
[c
].height
> 0.0) {
1799 p0
.x
= pc
.x
+ cornerMults
[i
].a
* corners
[c
].width
;
1800 p0
.y
= pc
.y
+ cornerMults
[i
].b
* corners
[c
].height
;
1802 p3
.x
= pc
.x
+ cornerMults
[i3
].a
* corners
[c
].width
;
1803 p3
.y
= pc
.y
+ cornerMults
[i3
].b
* corners
[c
].height
;
1805 p1
.x
= p0
.x
+ alpha
* cornerMults
[i2
].a
* corners
[c
].width
;
1806 p1
.y
= p0
.y
+ alpha
* cornerMults
[i2
].b
* corners
[c
].height
;
1808 p2
.x
= p3
.x
- alpha
* cornerMults
[i3
].a
* corners
[c
].width
;
1809 p2
.y
= p3
.y
- alpha
* cornerMults
[i3
].b
* corners
[c
].height
;
1811 cairo_line_to (mCairo
, p0
.x
, p0
.y
);
1812 cairo_curve_to (mCairo
,
1817 cairo_line_to (mCairo
, pc
.x
, pc
.y
);
1821 cairo_close_path (mCairo
);
1823 EnsurePathBuilder();
1825 const gfxFloat alpha
= 0.55191497064665766025;
1827 typedef struct { gfxFloat a
, b
; } twoFloats
;
1829 twoFloats cwCornerMults
[4] = { { -1, 0 },
1833 twoFloats ccwCornerMults
[4] = { { +1, 0 },
1838 twoFloats
*cornerMults
= draw_clockwise
? cwCornerMults
: ccwCornerMults
;
1840 gfxPoint pc
, p0
, p1
, p2
, p3
;
1843 mPathBuilder
->MoveTo(Point(Float(rect
.X() + corners
[NS_CORNER_TOP_LEFT
].width
), Float(rect
.Y())));
1845 mPathBuilder
->MoveTo(Point(Float(rect
.X() + rect
.Width() - corners
[NS_CORNER_TOP_RIGHT
].width
), Float(rect
.Y())));
1847 NS_FOR_CSS_CORNERS(i
) {
1848 // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
1849 mozilla::css::Corner c
= mozilla::css::Corner(draw_clockwise
? ((i
+1) % 4) : ((4-i
) % 4));
1851 // i+2 and i+3 respectively. These are used to index into the corner
1852 // multiplier table, and were deduced by calculating out the long form
1853 // of each corner and finding a pattern in the signs and values.
1857 pc
= rect
.AtCorner(c
);
1859 if (corners
[c
].width
> 0.0 && corners
[c
].height
> 0.0) {
1860 p0
.x
= pc
.x
+ cornerMults
[i
].a
* corners
[c
].width
;
1861 p0
.y
= pc
.y
+ cornerMults
[i
].b
* corners
[c
].height
;
1863 p3
.x
= pc
.x
+ cornerMults
[i3
].a
* corners
[c
].width
;
1864 p3
.y
= pc
.y
+ cornerMults
[i3
].b
* corners
[c
].height
;
1866 p1
.x
= p0
.x
+ alpha
* cornerMults
[i2
].a
* corners
[c
].width
;
1867 p1
.y
= p0
.y
+ alpha
* cornerMults
[i2
].b
* corners
[c
].height
;
1869 p2
.x
= p3
.x
- alpha
* cornerMults
[i3
].a
* corners
[c
].width
;
1870 p2
.y
= p3
.y
- alpha
* cornerMults
[i3
].b
* corners
[c
].height
;
1872 mPathBuilder
->LineTo(ToPoint(p0
));
1873 mPathBuilder
->BezierTo(ToPoint(p1
), ToPoint(p2
), ToPoint(p3
));
1875 mPathBuilder
->LineTo(ToPoint(pc
));
1879 mPathBuilder
->Close();
1883 #ifdef MOZ_DUMP_PAINTING
1885 gfxContext::WriteAsPNG(const char* aFile
)
1887 nsRefPtr
<gfxASurface
> surf
= CurrentSurface();
1889 surf
->WriteAsPNG(aFile
);
1891 NS_WARNING("No surface found!");
1896 gfxContext::DumpAsDataURL()
1898 nsRefPtr
<gfxASurface
> surf
= CurrentSurface();
1900 surf
->DumpAsDataURL();
1902 NS_WARNING("No surface found!");
1907 gfxContext::CopyAsDataURL()
1909 nsRefPtr
<gfxASurface
> surf
= CurrentSurface();
1911 surf
->CopyAsDataURL();
1913 NS_WARNING("No surface found!");
1919 gfxContext::EnsurePath()
1922 mPath
= mPathBuilder
->Finish();
1923 mPathBuilder
= NULL
;
1927 if (mTransformChanged
) {
1928 Matrix mat
= mTransform
;
1930 mat
= mPathTransform
* mat
;
1931 mPathBuilder
= mPath
->TransformedCopyToBuilder(mat
, CurrentState().fillRule
);
1932 mPath
= mPathBuilder
->Finish();
1933 mPathBuilder
= NULL
;
1935 mTransformChanged
= false;
1938 if (CurrentState().fillRule
== mPath
->GetFillRule()) {
1942 mPathBuilder
= mPath
->CopyToBuilder(CurrentState().fillRule
);
1944 mPath
= mPathBuilder
->Finish();
1945 mPathBuilder
= NULL
;
1949 EnsurePathBuilder();
1950 mPath
= mPathBuilder
->Finish();
1951 mPathBuilder
= NULL
;
1955 gfxContext::EnsurePathBuilder()
1957 if (mPathBuilder
&& !mTransformChanged
) {
1962 if (!mTransformChanged
) {
1963 mPathBuilder
= mPath
->CopyToBuilder(CurrentState().fillRule
);
1966 Matrix invTransform
= mTransform
;
1967 invTransform
.Invert();
1968 Matrix toNewUS
= mPathTransform
* invTransform
;
1969 mPathBuilder
= mPath
->TransformedCopyToBuilder(toNewUS
, CurrentState().fillRule
);
1974 DebugOnly
<PathBuilder
*> oldPath
= mPathBuilder
.get();
1976 if (!mPathBuilder
) {
1977 mPathBuilder
= mDT
->CreatePathBuilder(CurrentState().fillRule
);
1980 mPathBuilder
->MoveTo(mRect
.TopLeft());
1981 mPathBuilder
->LineTo(mRect
.TopRight());
1982 mPathBuilder
->LineTo(mRect
.BottomRight());
1983 mPathBuilder
->LineTo(mRect
.BottomLeft());
1984 mPathBuilder
->Close();
1988 if (mTransformChanged
) {
1989 // This could be an else if since this should never happen when
1990 // mPathBuilder is NULL and mPath is NULL. But this way we can assert
1991 // if all the state is as expected.
1992 MOZ_ASSERT(oldPath
);
1993 MOZ_ASSERT(!mPathIsRect
);
1995 Matrix invTransform
= mTransform
;
1996 invTransform
.Invert();
1997 Matrix toNewUS
= mPathTransform
* invTransform
;
1999 RefPtr
<Path
> path
= mPathBuilder
->Finish();
2000 mPathBuilder
= path
->TransformedCopyToBuilder(toNewUS
, CurrentState().fillRule
);
2003 mPathIsRect
= false;
2007 gfxContext::FillAzure(Float aOpacity
)
2009 AzureState
&state
= CurrentState();
2011 CompositionOp op
= GetOp();
2014 MOZ_ASSERT(!mTransformChanged
);
2016 if (state
.opIsClear
) {
2017 mDT
->ClearRect(mRect
);
2018 } else if (op
== OP_SOURCE
) {
2019 // Emulate cairo operator source which is bound by mask!
2020 mDT
->ClearRect(mRect
);
2021 mDT
->FillRect(mRect
, GeneralPattern(this), DrawOptions(aOpacity
));
2023 mDT
->FillRect(mRect
, GeneralPattern(this), DrawOptions(aOpacity
, op
, state
.aaMode
));
2028 NS_ASSERTION(!state
.opIsClear
, "We shouldn't be clearing complex paths!");
2030 mDT
->Fill(mPath
, GeneralPattern(this), DrawOptions(aOpacity
, op
, state
.aaMode
));
2035 gfxContext::PushClipsToDT(DrawTarget
*aDT
)
2037 // Tricky, we have to restore all clips -since the last time- the clip
2038 // was reset. If we didn't reset the clip, just popping the clips we
2040 unsigned int lastReset
= 0;
2041 for (int i
= mStateStack
.Length() - 2; i
> 0; i
--) {
2042 if (mStateStack
[i
].clipWasReset
) {
2047 // Don't need to save the old transform, we'll be setting a new one soon!
2049 // Push all clips from the last state on the stack where the clip was
2050 // reset to the clip before ours.
2051 for (unsigned int i
= lastReset
; i
< mStateStack
.Length() - 1; i
++) {
2052 for (unsigned int c
= 0; c
< mStateStack
[i
].pushedClips
.Length(); c
++) {
2053 aDT
->SetTransform(mStateStack
[i
].pushedClips
[c
].transform
* GetDeviceTransform());
2054 if (mStateStack
[i
].pushedClips
[c
].path
) {
2055 aDT
->PushClip(mStateStack
[i
].pushedClips
[c
].path
);
2057 aDT
->PushClipRect(mStateStack
[i
].pushedClips
[c
].rect
);
2066 if (CurrentState().op
!= OP_SOURCE
) {
2067 return CurrentState().op
;
2070 AzureState
&state
= CurrentState();
2071 if (state
.pattern
) {
2072 if (state
.pattern
->IsOpaque()) {
2077 } else if (state
.sourceSurface
) {
2078 if (state
.sourceSurface
->GetFormat() == FORMAT_B8G8R8X8
) {
2084 if (state
.color
.a
> 0.999) {
2092 /* SVG font code can change the transform after having set the pattern on the
2093 * context. When the pattern is set it is in user space, if the transform is
2094 * changed after doing so the pattern needs to be converted back into userspace.
2095 * We just store the old pattern transform here so that we only do the work
2096 * needed here if the pattern is actually used.
2097 * We need to avoid doing this when this ChangeTransform comes from a restore,
2098 * since the current pattern and the current transform are both part of the
2099 * state we know the new CurrentState()'s values are valid. But if we assume
2100 * a change they might become invalid since patternTransformChanged is part of
2101 * the state and might be false for the restored AzureState.
2104 gfxContext::ChangeTransform(const Matrix
&aNewMatrix
, bool aUpdatePatternTransform
)
2106 AzureState
&state
= CurrentState();
2108 if (aUpdatePatternTransform
&& (state
.pattern
|| state
.sourceSurface
)
2109 && !state
.patternTransformChanged
) {
2110 state
.patternTransform
= mTransform
;
2111 state
.patternTransformChanged
= true;
2115 Matrix invMatrix
= aNewMatrix
;
2119 Matrix toNewUS
= mTransform
* invMatrix
;
2121 if (toNewUS
.IsRectilinear()) {
2122 mRect
= toNewUS
.TransformBounds(mRect
);
2123 mRect
.NudgeToIntegers();
2125 mPathBuilder
= mDT
->CreatePathBuilder(CurrentState().fillRule
);
2127 mPathBuilder
->MoveTo(toNewUS
* mRect
.TopLeft());
2128 mPathBuilder
->LineTo(toNewUS
* mRect
.TopRight());
2129 mPathBuilder
->LineTo(toNewUS
* mRect
.BottomRight());
2130 mPathBuilder
->LineTo(toNewUS
* mRect
.BottomLeft());
2131 mPathBuilder
->Close();
2133 mPathIsRect
= false;
2136 // No need to consider the transform changed now!
2137 mTransformChanged
= false;
2138 } else if ((mPath
|| mPathBuilder
) && !mTransformChanged
) {
2139 mTransformChanged
= true;
2140 mPathTransform
= mTransform
;
2143 mTransform
= aNewMatrix
;
2145 mDT
->SetTransform(GetDTTransform());
2149 gfxContext::GetAzureDeviceSpaceClipBounds()
2151 unsigned int lastReset
= 0;
2152 for (int i
= mStateStack
.Length() - 1; i
> 0; i
--) {
2153 if (mStateStack
[i
].clipWasReset
) {
2158 Rect
rect(CurrentState().deviceOffset
.x
, CurrentState().deviceOffset
.y
,
2159 Float(mDT
->GetSize().width
), Float(mDT
->GetSize().height
));
2160 for (unsigned int i
= lastReset
; i
< mStateStack
.Length(); i
++) {
2161 for (unsigned int c
= 0; c
< mStateStack
[i
].pushedClips
.Length(); c
++) {
2162 AzureState::PushedClip
&clip
= mStateStack
[i
].pushedClips
[c
];
2164 Rect bounds
= clip
.path
->GetBounds(clip
.transform
);
2165 rect
.IntersectRect(rect
, bounds
);
2167 rect
.IntersectRect(rect
, clip
.transform
.TransformBounds(clip
.rect
));
2176 gfxContext::GetDeviceTransform() const
2179 mat
.Translate(-CurrentState().deviceOffset
.x
, -CurrentState().deviceOffset
.y
);
2184 gfxContext::GetDTTransform() const
2186 Matrix mat
= mTransform
;
2187 mat
._31
-= CurrentState().deviceOffset
.x
;
2188 mat
._32
-= CurrentState().deviceOffset
.y
;
2193 gfxContext::PushNewDT(gfxASurface::gfxContentType content
)
2195 Rect clipBounds
= GetAzureDeviceSpaceClipBounds();
2196 clipBounds
.RoundOut();
2198 clipBounds
.width
= std::max(1.0f
, clipBounds
.width
);
2199 clipBounds
.height
= std::max(1.0f
, clipBounds
.height
);
2201 RefPtr
<DrawTarget
> newDT
=
2202 mDT
->CreateSimilarDrawTarget(IntSize(int32_t(clipBounds
.width
), int32_t(clipBounds
.height
)),
2203 gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content
));
2207 CurrentState().drawTarget
= newDT
;
2208 CurrentState().deviceOffset
= clipBounds
.TopLeft();
2214 * Work out whether cairo will snap inter-glyph spacing to pixels.
2216 * Layout does not align text to pixel boundaries, so, with font drawing
2217 * backends that snap glyph positions to pixels, it is important that
2218 * inter-glyph spacing within words is always an integer number of pixels.
2219 * This ensures that the drawing backend snaps all of the word's glyphs in the
2220 * same direction and so inter-glyph spacing remains the same.
2223 gfxContext::GetRoundOffsetsToPixels(bool *aRoundX
, bool *aRoundY
)
2226 // Could do something fancy here for ScaleFactors of
2227 // AxisAlignedTransforms, but we leave things simple.
2228 // Not much point rounding if a matrix will mess things up anyway.
2229 // Also return false for non-cairo contexts.
2230 if (CurrentMatrix().HasNonTranslation() || mDT
) {
2235 // All raster backends snap glyphs to pixels vertically.
2236 // Print backends set CAIRO_HINT_METRICS_OFF.
2239 cairo_t
*cr
= GetCairo();
2240 cairo_scaled_font_t
*scaled_font
= cairo_get_scaled_font(cr
);
2241 // Sometimes hint metrics gets set for us, most notably for printing.
2242 cairo_font_options_t
*font_options
= cairo_font_options_create();
2243 cairo_scaled_font_get_font_options(scaled_font
, font_options
);
2244 cairo_hint_metrics_t hint_metrics
=
2245 cairo_font_options_get_hint_metrics(font_options
);
2246 cairo_font_options_destroy(font_options
);
2248 switch (hint_metrics
) {
2249 case CAIRO_HINT_METRICS_OFF
:
2252 case CAIRO_HINT_METRICS_DEFAULT
:
2253 // Here we mimic what cairo surface/font backends do. Printing
2254 // surfaces have already been handled by hint_metrics. The
2255 // fallback show_glyphs implementation composites pixel-aligned
2256 // glyph surfaces, so we just pick surface/font combinations that
2258 switch (cairo_scaled_font_get_type(scaled_font
)) {
2259 #if CAIRO_HAS_DWRITE_FONT // dwrite backend is not in std cairo releases yet
2260 case CAIRO_FONT_TYPE_DWRITE
:
2261 // show_glyphs is implemented on the font and so is used for
2262 // all surface types; however, it may pixel-snap depending on
2263 // the dwrite rendering mode
2264 if (!cairo_dwrite_scaled_font_get_force_GDI_classic(scaled_font
) &&
2265 gfxWindowsPlatform::GetPlatform()->DWriteMeasuringMode() ==
2266 DWRITE_MEASURING_MODE_NATURAL
) {
2270 case CAIRO_FONT_TYPE_QUARTZ
:
2271 // Quartz surfaces implement show_glyphs for Quartz fonts
2272 if (cairo_surface_get_type(cairo_get_target(cr
)) ==
2273 CAIRO_SURFACE_TYPE_QUARTZ
) {
2280 case CAIRO_HINT_METRICS_ON
: