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 "PathRecording.h"
8 #include "DrawEventRecorder.h"
9 #include "RecordedEventImpl.h"
14 inline Maybe
<float> PathOps::ArcParams::GetRadius() const {
15 // Do a quick check for a uniform scale and/or translation transform. In the
16 // worst case scenario, failing just causes a fallback to ArcToBezier.
17 if (transform
._11
== transform
._22
&& transform
._12
== 0.0f
&&
18 transform
._21
== 0.0f
&& transform
._11
> 0.0f
) {
19 return Some(transform
._11
);
24 inline void PathOps::ArcParams::ToSink(PathSink
& aPathSink
,
25 bool aAntiClockwise
) const {
26 if (Maybe
<float> radius
= GetRadius()) {
27 aPathSink
.Arc(GetOrigin(), *radius
, startAngle
, endAngle
, aAntiClockwise
);
29 ArcToBezier(&aPathSink
, Point(), Size(1.0f
, 1.0f
), startAngle
, endAngle
,
30 aAntiClockwise
, 0.0f
, transform
);
34 #define NEXT_PARAMS(_type) \
35 const _type params = *reinterpret_cast<const _type*>(nextByte); \
36 nextByte += sizeof(_type);
38 bool PathOps::StreamToSink(PathSink
& aPathSink
) const {
39 if (mPathData
.empty()) {
43 const uint8_t* nextByte
= mPathData
.data();
44 const uint8_t* end
= nextByte
+ mPathData
.size();
45 while (nextByte
< end
) {
46 const OpType opType
= *reinterpret_cast<const OpType
*>(nextByte
);
47 nextByte
+= sizeof(OpType
);
49 case OpType::OP_MOVETO
: {
51 aPathSink
.MoveTo(params
);
54 case OpType::OP_LINETO
: {
56 aPathSink
.LineTo(params
);
59 case OpType::OP_BEZIERTO
: {
60 NEXT_PARAMS(ThreePoints
)
61 aPathSink
.BezierTo(params
.p1
, params
.p2
, params
.p3
);
64 case OpType::OP_QUADRATICBEZIERTO
: {
65 NEXT_PARAMS(TwoPoints
)
66 aPathSink
.QuadraticBezierTo(params
.p1
, params
.p2
);
69 case OpType::OP_ARC_CW
:
70 case OpType::OP_ARC_CCW
: {
71 NEXT_PARAMS(ArcParams
)
72 params
.ToSink(aPathSink
, opType
== OpType::OP_ARC_CCW
);
75 case OpType::OP_CLOSE
:
86 #define CHECKED_NEXT_PARAMS(_type) \
87 if (nextByte + sizeof(_type) > end) { \
92 bool PathOps::CheckedStreamToSink(PathSink
& aPathSink
) const {
93 if (mPathData
.empty()) {
97 const uint8_t* nextByte
= mPathData
.data();
98 const uint8_t* end
= nextByte
+ mPathData
.size();
100 if (nextByte
== end
) {
104 if (nextByte
+ sizeof(OpType
) > end
) {
108 const OpType opType
= *reinterpret_cast<const OpType
*>(nextByte
);
109 nextByte
+= sizeof(OpType
);
111 case OpType::OP_MOVETO
: {
112 CHECKED_NEXT_PARAMS(Point
)
113 aPathSink
.MoveTo(params
);
116 case OpType::OP_LINETO
: {
117 CHECKED_NEXT_PARAMS(Point
)
118 aPathSink
.LineTo(params
);
121 case OpType::OP_BEZIERTO
: {
122 CHECKED_NEXT_PARAMS(ThreePoints
)
123 aPathSink
.BezierTo(params
.p1
, params
.p2
, params
.p3
);
126 case OpType::OP_QUADRATICBEZIERTO
: {
127 CHECKED_NEXT_PARAMS(TwoPoints
)
128 aPathSink
.QuadraticBezierTo(params
.p1
, params
.p2
);
131 case OpType::OP_ARC_CW
:
132 case OpType::OP_ARC_CCW
: {
133 CHECKED_NEXT_PARAMS(ArcParams
)
134 params
.ToSink(aPathSink
, opType
== OpType::OP_ARC_CCW
);
137 case OpType::OP_CLOSE
:
147 #undef CHECKED_NEXT_PARAMS
149 PathOps
PathOps::TransformedCopy(const Matrix
& aTransform
) const {
151 newPathOps
.mPathData
.reserve(mPathData
.size());
152 const uint8_t* nextByte
= mPathData
.data();
153 const uint8_t* end
= nextByte
+ mPathData
.size();
154 while (nextByte
< end
) {
155 const OpType opType
= *reinterpret_cast<const OpType
*>(nextByte
);
156 nextByte
+= sizeof(OpType
);
158 case OpType::OP_MOVETO
: {
160 newPathOps
.MoveTo(aTransform
.TransformPoint(params
));
163 case OpType::OP_LINETO
: {
165 newPathOps
.LineTo(aTransform
.TransformPoint(params
));
168 case OpType::OP_BEZIERTO
: {
169 NEXT_PARAMS(ThreePoints
)
170 newPathOps
.BezierTo(aTransform
.TransformPoint(params
.p1
),
171 aTransform
.TransformPoint(params
.p2
),
172 aTransform
.TransformPoint(params
.p3
));
175 case OpType::OP_QUADRATICBEZIERTO
: {
176 NEXT_PARAMS(TwoPoints
)
177 newPathOps
.QuadraticBezierTo(aTransform
.TransformPoint(params
.p1
),
178 aTransform
.TransformPoint(params
.p2
));
181 case OpType::OP_ARC_CW
:
182 case OpType::OP_ARC_CCW
: {
183 NEXT_PARAMS(ArcParams
)
184 newPathOps
.Arc(params
.transform
* aTransform
, params
.startAngle
,
185 params
.endAngle
, opType
== OpType::OP_ARC_CCW
);
188 case OpType::OP_CLOSE
:
192 MOZ_CRASH("We control mOpTypes, so this should never happen.");
199 #define MODIFY_NEXT_PARAMS(_type) \
200 _type& params = *reinterpret_cast<_type*>(nextByte); \
201 nextByte += sizeof(_type);
203 void PathOps::TransformInPlace(const Matrix
& aTransform
) {
204 uint8_t* nextByte
= mPathData
.data();
205 uint8_t* end
= nextByte
+ mPathData
.size();
206 while (nextByte
< end
) {
207 const OpType opType
= *reinterpret_cast<const OpType
*>(nextByte
);
208 nextByte
+= sizeof(OpType
);
210 case OpType::OP_MOVETO
: {
211 MODIFY_NEXT_PARAMS(Point
)
212 params
= aTransform
.TransformPoint(params
);
215 case OpType::OP_LINETO
: {
216 MODIFY_NEXT_PARAMS(Point
)
217 params
= aTransform
.TransformPoint(params
);
220 case OpType::OP_BEZIERTO
: {
221 MODIFY_NEXT_PARAMS(ThreePoints
)
222 params
.p1
= aTransform
.TransformPoint(params
.p1
);
223 params
.p2
= aTransform
.TransformPoint(params
.p2
);
224 params
.p3
= aTransform
.TransformPoint(params
.p3
);
227 case OpType::OP_QUADRATICBEZIERTO
: {
228 MODIFY_NEXT_PARAMS(TwoPoints
)
229 params
.p1
= aTransform
.TransformPoint(params
.p1
);
230 params
.p2
= aTransform
.TransformPoint(params
.p2
);
233 case OpType::OP_ARC_CW
:
234 case OpType::OP_ARC_CCW
: {
235 MODIFY_NEXT_PARAMS(ArcParams
)
236 params
.transform
*= aTransform
;
239 case OpType::OP_CLOSE
:
242 MOZ_CRASH("We control mOpTypes, so this should never happen.");
247 Maybe
<Circle
> PathOps::AsCircle() const {
248 if (mPathData
.empty()) {
252 const uint8_t* nextByte
= mPathData
.data();
253 const uint8_t* end
= nextByte
+ mPathData
.size();
254 const OpType opType
= *reinterpret_cast<const OpType
*>(nextByte
);
255 nextByte
+= sizeof(OpType
);
256 if (opType
== OpType::OP_ARC_CW
|| opType
== OpType::OP_ARC_CCW
) {
257 NEXT_PARAMS(ArcParams
)
258 if (fabs(fabs(params
.startAngle
- params
.endAngle
) - 2 * M_PI
) < 1e-6) {
259 if (Maybe
<float> radius
= params
.GetRadius()) {
260 // we have a full circle
261 if (nextByte
< end
) {
262 const OpType nextOpType
= *reinterpret_cast<const OpType
*>(nextByte
);
263 nextByte
+= sizeof(OpType
);
264 if (nextOpType
== OpType::OP_CLOSE
) {
265 if (nextByte
== end
) {
266 return Some(Circle
{params
.GetOrigin(), *radius
, true});
270 // the circle wasn't closed
271 return Some(Circle
{params
.GetOrigin(), *radius
, false});
280 Maybe
<Line
> PathOps::AsLine() const {
281 if (mPathData
.empty()) {
287 const uint8_t* nextByte
= mPathData
.data();
288 const uint8_t* end
= nextByte
+ mPathData
.size();
289 OpType opType
= *reinterpret_cast<const OpType
*>(nextByte
);
290 nextByte
+= sizeof(OpType
);
292 if (opType
== OpType::OP_MOVETO
) {
293 MOZ_ASSERT(nextByte
!= end
);
296 retval
.origin
= params
;
301 if (nextByte
>= end
) {
305 opType
= *reinterpret_cast<const OpType
*>(nextByte
);
306 nextByte
+= sizeof(OpType
);
308 if (opType
== OpType::OP_LINETO
) {
309 MOZ_ASSERT(nextByte
!= end
);
313 if (nextByte
== end
) {
314 retval
.destination
= params
;
323 size_t PathOps::NumberOfOps() const {
325 const uint8_t* nextByte
= mPathData
.data();
326 const uint8_t* end
= nextByte
+ mPathData
.size();
327 while (nextByte
< end
) {
329 const OpType opType
= *reinterpret_cast<const OpType
*>(nextByte
);
330 nextByte
+= sizeof(OpType
);
332 case OpType::OP_MOVETO
:
333 nextByte
+= sizeof(Point
);
335 case OpType::OP_LINETO
:
336 nextByte
+= sizeof(Point
);
338 case OpType::OP_BEZIERTO
:
339 nextByte
+= sizeof(ThreePoints
);
341 case OpType::OP_QUADRATICBEZIERTO
:
342 nextByte
+= sizeof(TwoPoints
);
344 case OpType::OP_ARC_CW
:
345 case OpType::OP_ARC_CCW
:
346 nextByte
+= sizeof(ArcParams
);
348 case OpType::OP_CLOSE
:
351 MOZ_CRASH("We control mOpTypes, so this should never happen.");
358 bool PathOps::IsEmpty() const {
359 const uint8_t* nextByte
= mPathData
.data();
360 const uint8_t* end
= nextByte
+ mPathData
.size();
361 while (nextByte
< end
) {
362 const OpType opType
= *reinterpret_cast<const OpType
*>(nextByte
);
363 nextByte
+= sizeof(OpType
);
365 case OpType::OP_MOVETO
:
366 nextByte
+= sizeof(Point
);
368 case OpType::OP_CLOSE
:
377 void PathBuilderRecording::MoveTo(const Point
& aPoint
) {
378 mPathOps
.MoveTo(aPoint
);
379 mBeginPoint
= aPoint
;
380 mCurrentPoint
= aPoint
;
383 void PathBuilderRecording::LineTo(const Point
& aPoint
) {
384 mPathOps
.LineTo(aPoint
);
385 mCurrentPoint
= aPoint
;
388 void PathBuilderRecording::BezierTo(const Point
& aCP1
, const Point
& aCP2
,
390 mPathOps
.BezierTo(aCP1
, aCP2
, aCP3
);
391 mCurrentPoint
= aCP3
;
394 void PathBuilderRecording::QuadraticBezierTo(const Point
& aCP1
,
396 mPathOps
.QuadraticBezierTo(aCP1
, aCP2
);
397 mCurrentPoint
= aCP2
;
400 void PathBuilderRecording::Close() {
402 mCurrentPoint
= mBeginPoint
;
405 void PathBuilderRecording::Arc(const Point
& aOrigin
, float aRadius
,
406 float aStartAngle
, float aEndAngle
,
407 bool aAntiClockwise
) {
408 mPathOps
.Arc(aOrigin
, aRadius
, aStartAngle
, aEndAngle
, aAntiClockwise
);
410 mCurrentPoint
= aOrigin
+ Point(cosf(aEndAngle
), sinf(aEndAngle
)) * aRadius
;
413 already_AddRefed
<Path
> PathBuilderRecording::Finish() {
414 return MakeAndAddRef
<PathRecording
>(mBackendType
, std::move(mPathOps
),
415 mFillRule
, mCurrentPoint
, mBeginPoint
);
418 PathRecording::PathRecording(BackendType aBackend
, PathOps
&& aOps
,
419 FillRule aFillRule
, const Point
& aCurrentPoint
,
420 const Point
& aBeginPoint
)
421 : mBackendType(aBackend
),
422 mPathOps(std::move(aOps
)),
423 mFillRule(aFillRule
),
424 mCurrentPoint(aCurrentPoint
),
425 mBeginPoint(aBeginPoint
) {}
427 PathRecording::~PathRecording() {
428 for (size_t i
= 0; i
< mStoredRecorders
.size(); i
++) {
429 mStoredRecorders
[i
]->RemoveStoredObject(this);
430 mStoredRecorders
[i
]->RecordEvent(RecordedPathDestruction(this));
434 void PathRecording::EnsurePath() const {
438 if (RefPtr
<PathBuilder
> pathBuilder
=
439 Factory::CreatePathBuilder(mBackendType
, mFillRule
)) {
440 if (!mPathOps
.StreamToSink(*pathBuilder
)) {
441 MOZ_ASSERT(false, "Failed to stream PathOps to PathBuilder");
443 mPath
= pathBuilder
->Finish();
444 MOZ_ASSERT(!!mPath
, "Failed finishing Path from PathBuilder");
447 MOZ_ASSERT(false, "Failed to create PathBuilder for PathRecording");
451 already_AddRefed
<PathBuilder
> PathRecording::CopyToBuilder(
452 FillRule aFillRule
) const {
453 RefPtr
<PathBuilderRecording
> recording
=
454 new PathBuilderRecording(mBackendType
, PathOps(mPathOps
), aFillRule
);
455 recording
->SetCurrentPoint(mCurrentPoint
);
456 recording
->SetBeginPoint(mBeginPoint
);
457 return recording
.forget();
460 already_AddRefed
<PathBuilder
> PathRecording::TransformedCopyToBuilder(
461 const Matrix
& aTransform
, FillRule aFillRule
) const {
462 RefPtr
<PathBuilderRecording
> recording
= new PathBuilderRecording(
463 mBackendType
, mPathOps
.TransformedCopy(aTransform
), aFillRule
);
464 recording
->SetCurrentPoint(aTransform
.TransformPoint(mCurrentPoint
));
465 recording
->SetBeginPoint(aTransform
.TransformPoint(mBeginPoint
));
466 return recording
.forget();
469 already_AddRefed
<PathBuilder
> PathRecording::MoveToBuilder(FillRule aFillRule
) {
470 RefPtr
<PathBuilderRecording
> recording
=
471 new PathBuilderRecording(mBackendType
, std::move(mPathOps
), aFillRule
);
472 recording
->SetCurrentPoint(mCurrentPoint
);
473 recording
->SetBeginPoint(mBeginPoint
);
474 return recording
.forget();
477 already_AddRefed
<PathBuilder
> PathRecording::TransformedMoveToBuilder(
478 const Matrix
& aTransform
, FillRule aFillRule
) {
479 mPathOps
.TransformInPlace(aTransform
);
480 RefPtr
<PathBuilderRecording
> recording
=
481 new PathBuilderRecording(mBackendType
, std::move(mPathOps
), aFillRule
);
482 recording
->SetCurrentPoint(aTransform
.TransformPoint(mCurrentPoint
));
483 recording
->SetBeginPoint(aTransform
.TransformPoint(mBeginPoint
));
484 return recording
.forget();
488 } // namespace mozilla