1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "PathHelpers.h"
12 UserDataKey sDisablePixelSnapping
;
14 void AppendRectToPath(PathBuilder
* aPathBuilder
, const Rect
& aRect
,
15 bool aDrawClockwise
) {
17 aPathBuilder
->MoveTo(aRect
.TopLeft());
18 aPathBuilder
->LineTo(aRect
.TopRight());
19 aPathBuilder
->LineTo(aRect
.BottomRight());
20 aPathBuilder
->LineTo(aRect
.BottomLeft());
22 aPathBuilder
->MoveTo(aRect
.TopRight());
23 aPathBuilder
->LineTo(aRect
.TopLeft());
24 aPathBuilder
->LineTo(aRect
.BottomLeft());
25 aPathBuilder
->LineTo(aRect
.BottomRight());
27 aPathBuilder
->Close();
30 void AppendRoundedRectToPath(PathBuilder
* aPathBuilder
, const Rect
& aRect
,
31 const RectCornerRadii
& aRadii
, bool aDrawClockwise
,
32 const Maybe
<Matrix
>& aTransform
) {
33 // For CW drawing, this looks like:
45 // Where 0, 1, 2, 3 are the control points of the Bezier curve for
46 // the corner, and C is the actual corner point.
48 // At the start of the loop, the current point is assumed to be
49 // the point adjacent to the top left corner on the top
50 // horizontal. Note that corner indices start at the top left and
51 // continue clockwise, whereas in our loop i = 0 refers to the top
54 // When going CCW, the control points are swapped, and the first
55 // corner that's drawn is the top left (along with the top segment).
57 // There is considerable latitude in how one chooses the four
58 // control points for a Bezier curve approximation to an ellipse.
59 // For the overall path to be continuous and show no corner at the
60 // endpoints of the arc, points 0 and 3 must be at the ends of the
61 // straight segments of the rectangle; points 0, 1, and C must be
62 // collinear; and points 3, 2, and C must also be collinear. This
63 // leaves only two free parameters: the ratio of the line segments
64 // 01 and 0C, and the ratio of the line segments 32 and 3C. See
65 // the following papers for extensive discussion of how to choose
68 // Dokken, Tor, et al. "Good approximation of circles by
69 // curvature-continuous Bezier curves." Computer-Aided
70 // Geometric Design 7(1990) 33--41.
71 // Goldapp, Michael. "Approximation of circular arcs by cubic
72 // polynomials." Computer-Aided Geometric Design 8(1991) 227--238.
73 // Maisonobe, Luc. "Drawing an elliptical arc using polylines,
74 // quadratic, or cubic Bezier curves."
75 // http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf
77 // We follow the approach in section 2 of Goldapp (least-error,
78 // Hermite-type approximation) and make both ratios equal to
80 // 2 2 + n - sqrt(2n + 28)
81 // alpha = - * ---------------------
84 // where n = 3( cbrt(sqrt(2)+1) - cbrt(sqrt(2)-1) ).
86 // This is the result of Goldapp's equation (10b) when the angle
87 // swept out by the arc is pi/2, and the parameter "a-bar" is the
88 // expression given immediately below equation (21).
90 // Using this value, the maximum radial error for a circle, as a
91 // fraction of the radius, is on the order of 0.2 x 10^-3.
92 // Neither Dokken nor Goldapp discusses error for a general
93 // ellipse; Maisonobe does, but his choice of control points
94 // follows different constraints, and Goldapp's expression for
95 // 'alpha' gives much smaller radial error, even for very flat
96 // ellipses, than Maisonobe's equivalent.
98 // For the various corners and for each axis, the sign of this
99 // constant changes, or it might be 0 -- it's multiplied by the
100 // appropriate multiplier from the list before using.
102 const Float alpha
= Float(0.55191497064665766025);
108 twoFloats cwCornerMults
[4] = {{-1, 0}, // cc == clockwise
112 twoFloats ccwCornerMults
[4] = {{+1, 0}, // ccw == counter-clockwise
117 twoFloats
* cornerMults
= aDrawClockwise
? cwCornerMults
: ccwCornerMults
;
119 Point cornerCoords
[] = {aRect
.TopLeft(), aRect
.TopRight(),
120 aRect
.BottomRight(), aRect
.BottomLeft()};
122 Point pc
, p0
, p1
, p2
, p3
;
124 if (aDrawClockwise
) {
125 Point
pt(aRect
.X() + aRadii
[eCornerTopLeft
].width
, aRect
.Y());
127 pt
= aTransform
->TransformPoint(pt
);
129 aPathBuilder
->MoveTo(pt
);
131 Point
pt(aRect
.X() + aRect
.Width() - aRadii
[eCornerTopRight
].width
,
134 pt
= aTransform
->TransformPoint(pt
);
136 aPathBuilder
->MoveTo(pt
);
139 for (int i
= 0; i
< 4; ++i
) {
140 // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
141 int c
= aDrawClockwise
? ((i
+ 1) % 4) : ((4 - i
) % 4);
143 // i+2 and i+3 respectively. These are used to index into the corner
144 // multiplier table, and were deduced by calculating out the long form
145 // of each corner and finding a pattern in the signs and values.
146 int i2
= (i
+ 2) % 4;
147 int i3
= (i
+ 3) % 4;
149 pc
= cornerCoords
[c
];
151 if (aRadii
[c
].width
> 0.0 && aRadii
[c
].height
> 0.0) {
152 p0
.x
= pc
.x
+ cornerMults
[i
].a
* aRadii
[c
].width
;
153 p0
.y
= pc
.y
+ cornerMults
[i
].b
* aRadii
[c
].height
;
155 p3
.x
= pc
.x
+ cornerMults
[i3
].a
* aRadii
[c
].width
;
156 p3
.y
= pc
.y
+ cornerMults
[i3
].b
* aRadii
[c
].height
;
158 p1
.x
= p0
.x
+ alpha
* cornerMults
[i2
].a
* aRadii
[c
].width
;
159 p1
.y
= p0
.y
+ alpha
* cornerMults
[i2
].b
* aRadii
[c
].height
;
161 p2
.x
= p3
.x
- alpha
* cornerMults
[i3
].a
* aRadii
[c
].width
;
162 p2
.y
= p3
.y
- alpha
* cornerMults
[i3
].b
* aRadii
[c
].height
;
164 if (aTransform
.isNothing()) {
165 aPathBuilder
->LineTo(p0
);
166 aPathBuilder
->BezierTo(p1
, p2
, p3
);
168 const Matrix
& transform
= *aTransform
;
169 aPathBuilder
->LineTo(transform
.TransformPoint(p0
));
170 aPathBuilder
->BezierTo(transform
.TransformPoint(p1
),
171 transform
.TransformPoint(p2
),
172 transform
.TransformPoint(p3
));
175 if (aTransform
.isNothing()) {
176 aPathBuilder
->LineTo(pc
);
178 aPathBuilder
->LineTo(aTransform
->TransformPoint(pc
));
183 aPathBuilder
->Close();
186 void AppendEllipseToPath(PathBuilder
* aPathBuilder
, const Point
& aCenter
,
187 const Size
& aDimensions
) {
188 Size halfDim
= aDimensions
/ 2.f
;
189 Rect
rect(aCenter
- Point(halfDim
.width
, halfDim
.height
), aDimensions
);
190 RectCornerRadii
radii(halfDim
.width
, halfDim
.height
);
192 AppendRoundedRectToPath(aPathBuilder
, rect
, radii
);
195 bool SnapLineToDevicePixelsForStroking(Point
& aP1
, Point
& aP2
,
196 const DrawTarget
& aDrawTarget
,
198 Matrix mat
= aDrawTarget
.GetTransform();
199 if (mat
.HasNonTranslation()) {
202 if (aP1
.x
!= aP2
.x
&& aP1
.y
!= aP2
.y
) {
203 return false; // not a horizontal or vertical line
205 Point p1
= aP1
+ mat
.GetTranslation(); // into device space
206 Point p2
= aP2
+ mat
.GetTranslation();
209 p1
-= mat
.GetTranslation(); // back into user space
210 p2
-= mat
.GetTranslation();
215 bool lineWidthIsOdd
= (int(aLineWidth
) % 2) == 1;
216 if (lineWidthIsOdd
) {
217 if (aP1
.x
== aP2
.x
) {
218 // snap vertical line, adding 0.5 to align it to be mid-pixel:
219 aP1
+= Point(0.5, 0);
220 aP2
+= Point(0.5, 0);
222 // snap horizontal line, adding 0.5 to align it to be mid-pixel:
223 aP1
+= Point(0, 0.5);
224 aP2
+= Point(0, 0.5);
230 void StrokeSnappedEdgesOfRect(const Rect
& aRect
, DrawTarget
& aDrawTarget
,
231 const ColorPattern
& aColor
,
232 const StrokeOptions
& aStrokeOptions
) {
233 if (aRect
.IsEmpty()) {
237 Point p1
= aRect
.TopLeft();
238 Point p2
= aRect
.BottomLeft();
239 SnapLineToDevicePixelsForStroking(p1
, p2
, aDrawTarget
,
240 aStrokeOptions
.mLineWidth
);
241 aDrawTarget
.StrokeLine(p1
, p2
, aColor
, aStrokeOptions
);
243 p1
= aRect
.BottomLeft();
244 p2
= aRect
.BottomRight();
245 SnapLineToDevicePixelsForStroking(p1
, p2
, aDrawTarget
,
246 aStrokeOptions
.mLineWidth
);
247 aDrawTarget
.StrokeLine(p1
, p2
, aColor
, aStrokeOptions
);
249 p1
= aRect
.TopLeft();
250 p2
= aRect
.TopRight();
251 SnapLineToDevicePixelsForStroking(p1
, p2
, aDrawTarget
,
252 aStrokeOptions
.mLineWidth
);
253 aDrawTarget
.StrokeLine(p1
, p2
, aColor
, aStrokeOptions
);
255 p1
= aRect
.TopRight();
256 p2
= aRect
.BottomRight();
257 SnapLineToDevicePixelsForStroking(p1
, p2
, aDrawTarget
,
258 aStrokeOptions
.mLineWidth
);
259 aDrawTarget
.StrokeLine(p1
, p2
, aColor
, aStrokeOptions
);
262 // The logic for this comes from _cairo_stroke_style_max_distance_from_path
263 Margin
MaxStrokeExtents(const StrokeOptions
& aStrokeOptions
,
264 const Matrix
& aTransform
) {
265 double styleExpansionFactor
= 0.5f
;
267 if (aStrokeOptions
.mLineCap
== CapStyle::SQUARE
) {
268 styleExpansionFactor
= M_SQRT1_2
;
271 if (aStrokeOptions
.mLineJoin
== JoinStyle::MITER
&&
272 styleExpansionFactor
< M_SQRT2
* aStrokeOptions
.mMiterLimit
) {
273 styleExpansionFactor
= M_SQRT2
* aStrokeOptions
.mMiterLimit
;
276 styleExpansionFactor
*= aStrokeOptions
.mLineWidth
;
278 double dx
= styleExpansionFactor
* hypot(aTransform
._11
, aTransform
._21
);
279 double dy
= styleExpansionFactor
* hypot(aTransform
._22
, aTransform
._12
);
281 // Even if the stroke only partially covers a pixel, it must still render to
282 // full pixels. Round up to compensate for this.
286 return Margin(dy
, dx
, dy
, dx
);
290 } // namespace mozilla