|
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/. */ |
|
5 |
|
6 #ifdef _MSC_VER |
|
7 #define _USE_MATH_DEFINES |
|
8 #endif |
|
9 #include <math.h> |
|
10 |
|
11 #include "mozilla/Alignment.h" |
|
12 |
|
13 #include "cairo.h" |
|
14 |
|
15 #include "gfxContext.h" |
|
16 |
|
17 #include "gfxColor.h" |
|
18 #include "gfxMatrix.h" |
|
19 #include "gfxASurface.h" |
|
20 #include "gfxPattern.h" |
|
21 #include "gfxPlatform.h" |
|
22 #include "gfxTeeSurface.h" |
|
23 #include "GeckoProfiler.h" |
|
24 #include "gfx2DGlue.h" |
|
25 #include "mozilla/gfx/PathHelpers.h" |
|
26 #include <algorithm> |
|
27 |
|
28 #if CAIRO_HAS_DWRITE_FONT |
|
29 #include "gfxWindowsPlatform.h" |
|
30 #endif |
|
31 |
|
32 using namespace mozilla; |
|
33 using namespace mozilla::gfx; |
|
34 |
|
35 UserDataKey gfxContext::sDontUseAsSourceKey; |
|
36 |
|
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. |
|
39 */ |
|
40 class GeneralPattern |
|
41 { |
|
42 public: |
|
43 GeneralPattern(gfxContext *aContext) : mContext(aContext), mPattern(nullptr) {} |
|
44 ~GeneralPattern() { if (mPattern) { mPattern->~Pattern(); } } |
|
45 |
|
46 operator mozilla::gfx::Pattern&() |
|
47 { |
|
48 gfxContext::AzureState &state = mContext->CurrentState(); |
|
49 |
|
50 if (state.pattern) { |
|
51 return *state.pattern->GetPattern(mContext->mDT, state.patternTransformChanged ? &state.patternTransform : nullptr); |
|
52 } else if (state.sourceSurface) { |
|
53 Matrix transform = state.surfTransform; |
|
54 |
|
55 if (state.patternTransformChanged) { |
|
56 Matrix mat = mContext->GetDTTransform(); |
|
57 mat.Invert(); |
|
58 |
|
59 transform = transform * state.patternTransform * mat; |
|
60 } |
|
61 |
|
62 mPattern = new (mSurfacePattern.addr()) |
|
63 SurfacePattern(state.sourceSurface, ExtendMode::CLAMP, transform); |
|
64 return *mPattern; |
|
65 } else { |
|
66 mPattern = new (mColorPattern.addr()) |
|
67 ColorPattern(state.color); |
|
68 return *mPattern; |
|
69 } |
|
70 } |
|
71 |
|
72 private: |
|
73 union { |
|
74 mozilla::AlignedStorage2<mozilla::gfx::ColorPattern> mColorPattern; |
|
75 mozilla::AlignedStorage2<mozilla::gfx::SurfacePattern> mSurfacePattern; |
|
76 }; |
|
77 |
|
78 gfxContext *mContext; |
|
79 Pattern *mPattern; |
|
80 }; |
|
81 |
|
82 gfxContext::gfxContext(gfxASurface *surface) |
|
83 : mRefCairo(nullptr) |
|
84 , mSurface(surface) |
|
85 { |
|
86 MOZ_COUNT_CTOR(gfxContext); |
|
87 |
|
88 mCairo = cairo_create(surface->CairoSurface()); |
|
89 mFlags = surface->GetDefaultContextFlags(); |
|
90 if (mSurface->GetRotateForLandscape()) { |
|
91 // Rotate page 90 degrees to draw landscape page on portrait paper |
|
92 gfxIntSize size = mSurface->GetSize(); |
|
93 Translate(gfxPoint(0, size.width)); |
|
94 gfxMatrix matrix(0, -1, |
|
95 1, 0, |
|
96 0, 0); |
|
97 Multiply(matrix); |
|
98 } |
|
99 } |
|
100 |
|
101 gfxContext::gfxContext(DrawTarget *aTarget, const Point& aDeviceOffset) |
|
102 : mPathIsRect(false) |
|
103 , mTransformChanged(false) |
|
104 , mCairo(nullptr) |
|
105 , mRefCairo(nullptr) |
|
106 , mSurface(nullptr) |
|
107 , mFlags(0) |
|
108 , mDT(aTarget) |
|
109 , mOriginalDT(aTarget) |
|
110 { |
|
111 MOZ_COUNT_CTOR(gfxContext); |
|
112 |
|
113 mStateStack.SetLength(1); |
|
114 CurrentState().drawTarget = mDT; |
|
115 CurrentState().deviceOffset = aDeviceOffset; |
|
116 mDT->SetTransform(Matrix()); |
|
117 } |
|
118 |
|
119 /* static */ already_AddRefed<gfxContext> |
|
120 gfxContext::ContextForDrawTarget(DrawTarget* aTarget) |
|
121 { |
|
122 Matrix transform = aTarget->GetTransform(); |
|
123 nsRefPtr<gfxContext> result = new gfxContext(aTarget); |
|
124 result->SetMatrix(ThebesMatrix(transform)); |
|
125 return result.forget(); |
|
126 } |
|
127 |
|
128 gfxContext::~gfxContext() |
|
129 { |
|
130 if (mCairo) { |
|
131 cairo_destroy(mCairo); |
|
132 } |
|
133 if (mRefCairo) { |
|
134 cairo_destroy(mRefCairo); |
|
135 } |
|
136 if (mDT) { |
|
137 for (int i = mStateStack.Length() - 1; i >= 0; i--) { |
|
138 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { |
|
139 mDT->PopClip(); |
|
140 } |
|
141 |
|
142 if (mStateStack[i].clipWasReset) { |
|
143 break; |
|
144 } |
|
145 } |
|
146 mDT->Flush(); |
|
147 } |
|
148 MOZ_COUNT_DTOR(gfxContext); |
|
149 } |
|
150 |
|
151 gfxASurface * |
|
152 gfxContext::OriginalSurface() |
|
153 { |
|
154 if (mCairo || mSurface) { |
|
155 return mSurface; |
|
156 } |
|
157 |
|
158 if (mOriginalDT && mOriginalDT->GetType() == BackendType::CAIRO) { |
|
159 cairo_surface_t *s = |
|
160 (cairo_surface_t*)mOriginalDT->GetNativeSurface(NativeSurfaceType::CAIRO_SURFACE); |
|
161 if (s) { |
|
162 mSurface = gfxASurface::Wrap(s); |
|
163 return mSurface; |
|
164 } |
|
165 } |
|
166 return nullptr; |
|
167 } |
|
168 |
|
169 already_AddRefed<gfxASurface> |
|
170 gfxContext::CurrentSurface(gfxFloat *dx, gfxFloat *dy) |
|
171 { |
|
172 if (mCairo) { |
|
173 cairo_surface_t *s = cairo_get_group_target(mCairo); |
|
174 if (s == mSurface->CairoSurface()) { |
|
175 if (dx && dy) |
|
176 cairo_surface_get_device_offset(s, dx, dy); |
|
177 nsRefPtr<gfxASurface> ret = mSurface; |
|
178 return ret.forget(); |
|
179 } |
|
180 |
|
181 if (dx && dy) |
|
182 cairo_surface_get_device_offset(s, dx, dy); |
|
183 return gfxASurface::Wrap(s); |
|
184 } else { |
|
185 if (mDT->GetType() == BackendType::CAIRO) { |
|
186 cairo_surface_t *s = |
|
187 (cairo_surface_t*)mDT->GetNativeSurface(NativeSurfaceType::CAIRO_SURFACE); |
|
188 if (s) { |
|
189 if (dx && dy) { |
|
190 *dx = -CurrentState().deviceOffset.x; |
|
191 *dy = -CurrentState().deviceOffset.y; |
|
192 } |
|
193 return gfxASurface::Wrap(s); |
|
194 } |
|
195 } |
|
196 |
|
197 if (dx && dy) { |
|
198 *dx = *dy = 0; |
|
199 } |
|
200 // An Azure context doesn't have a surface backing it. |
|
201 return nullptr; |
|
202 } |
|
203 } |
|
204 |
|
205 cairo_t * |
|
206 gfxContext::GetCairo() |
|
207 { |
|
208 if (mCairo) { |
|
209 return mCairo; |
|
210 } |
|
211 |
|
212 if (mDT->GetType() == BackendType::CAIRO) { |
|
213 cairo_t *ctx = |
|
214 (cairo_t*)mDT->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT); |
|
215 if (ctx) { |
|
216 return ctx; |
|
217 } |
|
218 } |
|
219 |
|
220 if (mRefCairo) { |
|
221 // Set transform! |
|
222 return mRefCairo; |
|
223 } |
|
224 |
|
225 mRefCairo = cairo_create(gfxPlatform::GetPlatform()->ScreenReferenceSurface()->CairoSurface()); |
|
226 |
|
227 return mRefCairo; |
|
228 } |
|
229 |
|
230 void |
|
231 gfxContext::Save() |
|
232 { |
|
233 if (mCairo) { |
|
234 cairo_save(mCairo); |
|
235 } else { |
|
236 CurrentState().transform = mTransform; |
|
237 mStateStack.AppendElement(AzureState(CurrentState())); |
|
238 CurrentState().clipWasReset = false; |
|
239 CurrentState().pushedClips.Clear(); |
|
240 } |
|
241 } |
|
242 |
|
243 void |
|
244 gfxContext::Restore() |
|
245 { |
|
246 if (mCairo) { |
|
247 cairo_restore(mCairo); |
|
248 } else { |
|
249 for (unsigned int c = 0; c < CurrentState().pushedClips.Length(); c++) { |
|
250 mDT->PopClip(); |
|
251 } |
|
252 |
|
253 if (CurrentState().clipWasReset && |
|
254 CurrentState().drawTarget == mStateStack[mStateStack.Length() - 2].drawTarget) { |
|
255 PushClipsToDT(mDT); |
|
256 } |
|
257 |
|
258 mStateStack.RemoveElementAt(mStateStack.Length() - 1); |
|
259 |
|
260 mDT = CurrentState().drawTarget; |
|
261 |
|
262 ChangeTransform(CurrentState().transform, false); |
|
263 } |
|
264 } |
|
265 |
|
266 // drawing |
|
267 void |
|
268 gfxContext::NewPath() |
|
269 { |
|
270 if (mCairo) { |
|
271 cairo_new_path(mCairo); |
|
272 } else { |
|
273 mPath = nullptr; |
|
274 mPathBuilder = nullptr; |
|
275 mPathIsRect = false; |
|
276 mTransformChanged = false; |
|
277 } |
|
278 } |
|
279 |
|
280 void |
|
281 gfxContext::ClosePath() |
|
282 { |
|
283 if (mCairo) { |
|
284 cairo_close_path(mCairo); |
|
285 } else { |
|
286 EnsurePathBuilder(); |
|
287 mPathBuilder->Close(); |
|
288 } |
|
289 } |
|
290 |
|
291 already_AddRefed<gfxPath> gfxContext::CopyPath() |
|
292 { |
|
293 nsRefPtr<gfxPath> path; |
|
294 if (mCairo) { |
|
295 path = new gfxPath(cairo_copy_path(mCairo)); |
|
296 } else { |
|
297 EnsurePath(); |
|
298 path = new gfxPath(mPath); |
|
299 } |
|
300 return path.forget(); |
|
301 } |
|
302 |
|
303 void gfxContext::SetPath(gfxPath* path) |
|
304 { |
|
305 if (mCairo) { |
|
306 cairo_new_path(mCairo); |
|
307 if (path->mPath->status == CAIRO_STATUS_SUCCESS && path->mPath->num_data != 0) |
|
308 cairo_append_path(mCairo, path->mPath); |
|
309 } else { |
|
310 MOZ_ASSERT(path->mMoz2DPath, "Can't mix cairo and azure paths!"); |
|
311 MOZ_ASSERT(path->mMoz2DPath->GetBackendType() == mDT->GetType()); |
|
312 mPath = path->mMoz2DPath; |
|
313 mPathBuilder = nullptr; |
|
314 mPathIsRect = false; |
|
315 mTransformChanged = false; |
|
316 } |
|
317 } |
|
318 |
|
319 gfxPoint |
|
320 gfxContext::CurrentPoint() |
|
321 { |
|
322 if (mCairo) { |
|
323 double x, y; |
|
324 cairo_get_current_point(mCairo, &x, &y); |
|
325 return gfxPoint(x, y); |
|
326 } else { |
|
327 EnsurePathBuilder(); |
|
328 return ThebesPoint(mPathBuilder->CurrentPoint()); |
|
329 } |
|
330 } |
|
331 |
|
332 void |
|
333 gfxContext::Stroke() |
|
334 { |
|
335 if (mCairo) { |
|
336 cairo_stroke_preserve(mCairo); |
|
337 } else { |
|
338 AzureState &state = CurrentState(); |
|
339 if (mPathIsRect) { |
|
340 MOZ_ASSERT(!mTransformChanged); |
|
341 |
|
342 mDT->StrokeRect(mRect, GeneralPattern(this), |
|
343 state.strokeOptions, |
|
344 DrawOptions(1.0f, GetOp(), state.aaMode)); |
|
345 } else { |
|
346 EnsurePath(); |
|
347 |
|
348 mDT->Stroke(mPath, GeneralPattern(this), state.strokeOptions, |
|
349 DrawOptions(1.0f, GetOp(), state.aaMode)); |
|
350 } |
|
351 } |
|
352 } |
|
353 |
|
354 void |
|
355 gfxContext::Fill() |
|
356 { |
|
357 PROFILER_LABEL("gfxContext", "Fill"); |
|
358 if (mCairo) { |
|
359 cairo_fill_preserve(mCairo); |
|
360 } else { |
|
361 FillAzure(1.0f); |
|
362 } |
|
363 } |
|
364 |
|
365 void |
|
366 gfxContext::FillWithOpacity(gfxFloat aOpacity) |
|
367 { |
|
368 if (mCairo) { |
|
369 // This method exists in the hope that one day cairo gets a direct |
|
370 // API for this, and then we would change this method to use that |
|
371 // API instead. |
|
372 if (aOpacity != 1.0) { |
|
373 gfxContextAutoSaveRestore saveRestore(this); |
|
374 Clip(); |
|
375 Paint(aOpacity); |
|
376 } else { |
|
377 Fill(); |
|
378 } |
|
379 } else { |
|
380 FillAzure(Float(aOpacity)); |
|
381 } |
|
382 } |
|
383 |
|
384 void |
|
385 gfxContext::MoveTo(const gfxPoint& pt) |
|
386 { |
|
387 if (mCairo) { |
|
388 cairo_move_to(mCairo, pt.x, pt.y); |
|
389 } else { |
|
390 EnsurePathBuilder(); |
|
391 mPathBuilder->MoveTo(ToPoint(pt)); |
|
392 } |
|
393 } |
|
394 |
|
395 void |
|
396 gfxContext::NewSubPath() |
|
397 { |
|
398 if (mCairo) { |
|
399 cairo_new_sub_path(mCairo); |
|
400 } else { |
|
401 // XXX - This has no users, we should kill it, it should be equivelant to a |
|
402 // MoveTo to the path's current point. |
|
403 } |
|
404 } |
|
405 |
|
406 void |
|
407 gfxContext::LineTo(const gfxPoint& pt) |
|
408 { |
|
409 if (mCairo) { |
|
410 cairo_line_to(mCairo, pt.x, pt.y); |
|
411 } else { |
|
412 EnsurePathBuilder(); |
|
413 mPathBuilder->LineTo(ToPoint(pt)); |
|
414 } |
|
415 } |
|
416 |
|
417 void |
|
418 gfxContext::CurveTo(const gfxPoint& pt1, const gfxPoint& pt2, const gfxPoint& pt3) |
|
419 { |
|
420 if (mCairo) { |
|
421 cairo_curve_to(mCairo, pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y); |
|
422 } else { |
|
423 EnsurePathBuilder(); |
|
424 mPathBuilder->BezierTo(ToPoint(pt1), ToPoint(pt2), ToPoint(pt3)); |
|
425 } |
|
426 } |
|
427 |
|
428 void |
|
429 gfxContext::QuadraticCurveTo(const gfxPoint& pt1, const gfxPoint& pt2) |
|
430 { |
|
431 if (mCairo) { |
|
432 double cx, cy; |
|
433 cairo_get_current_point(mCairo, &cx, &cy); |
|
434 cairo_curve_to(mCairo, |
|
435 (cx + pt1.x * 2.0) / 3.0, |
|
436 (cy + pt1.y * 2.0) / 3.0, |
|
437 (pt1.x * 2.0 + pt2.x) / 3.0, |
|
438 (pt1.y * 2.0 + pt2.y) / 3.0, |
|
439 pt2.x, |
|
440 pt2.y); |
|
441 } else { |
|
442 EnsurePathBuilder(); |
|
443 mPathBuilder->QuadraticBezierTo(ToPoint(pt1), ToPoint(pt2)); |
|
444 } |
|
445 } |
|
446 |
|
447 void |
|
448 gfxContext::Arc(const gfxPoint& center, gfxFloat radius, |
|
449 gfxFloat angle1, gfxFloat angle2) |
|
450 { |
|
451 if (mCairo) { |
|
452 cairo_arc(mCairo, center.x, center.y, radius, angle1, angle2); |
|
453 } else { |
|
454 EnsurePathBuilder(); |
|
455 mPathBuilder->Arc(ToPoint(center), Float(radius), Float(angle1), Float(angle2)); |
|
456 } |
|
457 } |
|
458 |
|
459 void |
|
460 gfxContext::NegativeArc(const gfxPoint& center, gfxFloat radius, |
|
461 gfxFloat angle1, gfxFloat angle2) |
|
462 { |
|
463 if (mCairo) { |
|
464 cairo_arc_negative(mCairo, center.x, center.y, radius, angle1, angle2); |
|
465 } else { |
|
466 EnsurePathBuilder(); |
|
467 mPathBuilder->Arc(ToPoint(center), Float(radius), Float(angle2), Float(angle1)); |
|
468 } |
|
469 } |
|
470 |
|
471 void |
|
472 gfxContext::Line(const gfxPoint& start, const gfxPoint& end) |
|
473 { |
|
474 if (mCairo) { |
|
475 MoveTo(start); |
|
476 LineTo(end); |
|
477 } else { |
|
478 EnsurePathBuilder(); |
|
479 mPathBuilder->MoveTo(ToPoint(start)); |
|
480 mPathBuilder->LineTo(ToPoint(end)); |
|
481 } |
|
482 } |
|
483 |
|
484 // XXX snapToPixels is only valid when snapping for filled |
|
485 // rectangles and for even-width stroked rectangles. |
|
486 // For odd-width stroked rectangles, we need to offset x/y by |
|
487 // 0.5... |
|
488 void |
|
489 gfxContext::Rectangle(const gfxRect& rect, bool snapToPixels) |
|
490 { |
|
491 if (mCairo) { |
|
492 if (snapToPixels) { |
|
493 gfxRect snappedRect(rect); |
|
494 |
|
495 if (UserToDevicePixelSnapped(snappedRect, true)) |
|
496 { |
|
497 cairo_matrix_t mat; |
|
498 cairo_get_matrix(mCairo, &mat); |
|
499 cairo_identity_matrix(mCairo); |
|
500 Rectangle(snappedRect); |
|
501 cairo_set_matrix(mCairo, &mat); |
|
502 |
|
503 return; |
|
504 } |
|
505 } |
|
506 |
|
507 cairo_rectangle(mCairo, rect.X(), rect.Y(), rect.Width(), rect.Height()); |
|
508 } else { |
|
509 Rect rec = ToRect(rect); |
|
510 |
|
511 if (snapToPixels) { |
|
512 gfxRect newRect(rect); |
|
513 if (UserToDevicePixelSnapped(newRect, true)) { |
|
514 gfxMatrix mat = ThebesMatrix(mTransform); |
|
515 mat.Invert(); |
|
516 |
|
517 // We need the user space rect. |
|
518 rec = ToRect(mat.TransformBounds(newRect)); |
|
519 } |
|
520 } |
|
521 |
|
522 if (!mPathBuilder && !mPathIsRect) { |
|
523 mPathIsRect = true; |
|
524 mRect = rec; |
|
525 return; |
|
526 } |
|
527 |
|
528 EnsurePathBuilder(); |
|
529 |
|
530 mPathBuilder->MoveTo(rec.TopLeft()); |
|
531 mPathBuilder->LineTo(rec.TopRight()); |
|
532 mPathBuilder->LineTo(rec.BottomRight()); |
|
533 mPathBuilder->LineTo(rec.BottomLeft()); |
|
534 mPathBuilder->Close(); |
|
535 } |
|
536 } |
|
537 |
|
538 void |
|
539 gfxContext::Ellipse(const gfxPoint& center, const gfxSize& dimensions) |
|
540 { |
|
541 gfxSize halfDim = dimensions / 2.0; |
|
542 gfxRect r(center - gfxPoint(halfDim.width, halfDim.height), dimensions); |
|
543 gfxCornerSizes c(halfDim, halfDim, halfDim, halfDim); |
|
544 |
|
545 RoundedRectangle (r, c); |
|
546 } |
|
547 |
|
548 void |
|
549 gfxContext::Polygon(const gfxPoint *points, uint32_t numPoints) |
|
550 { |
|
551 if (mCairo) { |
|
552 if (numPoints == 0) |
|
553 return; |
|
554 |
|
555 cairo_move_to(mCairo, points[0].x, points[0].y); |
|
556 for (uint32_t i = 1; i < numPoints; ++i) { |
|
557 cairo_line_to(mCairo, points[i].x, points[i].y); |
|
558 } |
|
559 } else { |
|
560 if (numPoints == 0) { |
|
561 return; |
|
562 } |
|
563 |
|
564 EnsurePathBuilder(); |
|
565 |
|
566 mPathBuilder->MoveTo(ToPoint(points[0])); |
|
567 for (uint32_t i = 1; i < numPoints; i++) { |
|
568 mPathBuilder->LineTo(ToPoint(points[i])); |
|
569 } |
|
570 } |
|
571 } |
|
572 |
|
573 void |
|
574 gfxContext::DrawSurface(gfxASurface *surface, const gfxSize& size) |
|
575 { |
|
576 if (mCairo) { |
|
577 cairo_save(mCairo); |
|
578 cairo_set_source_surface(mCairo, surface->CairoSurface(), 0, 0); |
|
579 cairo_new_path(mCairo); |
|
580 |
|
581 // pixel-snap this |
|
582 Rectangle(gfxRect(gfxPoint(0.0, 0.0), size), true); |
|
583 |
|
584 cairo_fill(mCairo); |
|
585 cairo_restore(mCairo); |
|
586 } else { |
|
587 // Lifetime needs to be limited here since we may wrap surface's data. |
|
588 RefPtr<SourceSurface> surf = |
|
589 gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface); |
|
590 |
|
591 if (!surf) { |
|
592 return; |
|
593 } |
|
594 |
|
595 Rect rect(0, 0, Float(size.width), Float(size.height)); |
|
596 rect.Intersect(Rect(0, 0, Float(surf->GetSize().width), Float(surf->GetSize().height))); |
|
597 |
|
598 // XXX - Should fix pixel snapping. |
|
599 mDT->DrawSurface(surf, rect, rect); |
|
600 } |
|
601 } |
|
602 |
|
603 // transform stuff |
|
604 void |
|
605 gfxContext::Translate(const gfxPoint& pt) |
|
606 { |
|
607 if (mCairo) { |
|
608 cairo_translate(mCairo, pt.x, pt.y); |
|
609 } else { |
|
610 Matrix newMatrix = mTransform; |
|
611 |
|
612 ChangeTransform(newMatrix.Translate(Float(pt.x), Float(pt.y))); |
|
613 } |
|
614 } |
|
615 |
|
616 void |
|
617 gfxContext::Scale(gfxFloat x, gfxFloat y) |
|
618 { |
|
619 if (mCairo) { |
|
620 cairo_scale(mCairo, x, y); |
|
621 } else { |
|
622 Matrix newMatrix = mTransform; |
|
623 |
|
624 ChangeTransform(newMatrix.Scale(Float(x), Float(y))); |
|
625 } |
|
626 } |
|
627 |
|
628 void |
|
629 gfxContext::Rotate(gfxFloat angle) |
|
630 { |
|
631 if (mCairo) { |
|
632 cairo_rotate(mCairo, angle); |
|
633 } else { |
|
634 Matrix rotation = Matrix::Rotation(Float(angle)); |
|
635 ChangeTransform(rotation * mTransform); |
|
636 } |
|
637 } |
|
638 |
|
639 void |
|
640 gfxContext::Multiply(const gfxMatrix& matrix) |
|
641 { |
|
642 if (mCairo) { |
|
643 const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix); |
|
644 cairo_transform(mCairo, &mat); |
|
645 } else { |
|
646 ChangeTransform(ToMatrix(matrix) * mTransform); |
|
647 } |
|
648 } |
|
649 |
|
650 void |
|
651 gfxContext::MultiplyAndNudgeToIntegers(const gfxMatrix& matrix) |
|
652 { |
|
653 if (mCairo) { |
|
654 const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix); |
|
655 cairo_transform(mCairo, &mat); |
|
656 // XXX nudging to integers not currently supported for Thebes |
|
657 } else { |
|
658 Matrix transform = ToMatrix(matrix) * mTransform; |
|
659 transform.NudgeToIntegers(); |
|
660 ChangeTransform(transform); |
|
661 } |
|
662 } |
|
663 |
|
664 void |
|
665 gfxContext::SetMatrix(const gfxMatrix& matrix) |
|
666 { |
|
667 if (mCairo) { |
|
668 const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix); |
|
669 cairo_set_matrix(mCairo, &mat); |
|
670 } else { |
|
671 ChangeTransform(ToMatrix(matrix)); |
|
672 } |
|
673 } |
|
674 |
|
675 void |
|
676 gfxContext::IdentityMatrix() |
|
677 { |
|
678 if (mCairo) { |
|
679 cairo_identity_matrix(mCairo); |
|
680 } else { |
|
681 ChangeTransform(Matrix()); |
|
682 } |
|
683 } |
|
684 |
|
685 gfxMatrix |
|
686 gfxContext::CurrentMatrix() const |
|
687 { |
|
688 if (mCairo) { |
|
689 cairo_matrix_t mat; |
|
690 cairo_get_matrix(mCairo, &mat); |
|
691 return gfxMatrix(*reinterpret_cast<gfxMatrix*>(&mat)); |
|
692 } else { |
|
693 return ThebesMatrix(mTransform); |
|
694 } |
|
695 } |
|
696 |
|
697 void |
|
698 gfxContext::NudgeCurrentMatrixToIntegers() |
|
699 { |
|
700 if (mCairo) { |
|
701 cairo_matrix_t mat; |
|
702 cairo_get_matrix(mCairo, &mat); |
|
703 gfxMatrix(*reinterpret_cast<gfxMatrix*>(&mat)).NudgeToIntegers(); |
|
704 cairo_set_matrix(mCairo, &mat); |
|
705 } else { |
|
706 gfxMatrix matrix = ThebesMatrix(mTransform); |
|
707 matrix.NudgeToIntegers(); |
|
708 ChangeTransform(ToMatrix(matrix)); |
|
709 } |
|
710 } |
|
711 |
|
712 gfxPoint |
|
713 gfxContext::DeviceToUser(const gfxPoint& point) const |
|
714 { |
|
715 if (mCairo) { |
|
716 gfxPoint ret = point; |
|
717 cairo_device_to_user(mCairo, &ret.x, &ret.y); |
|
718 return ret; |
|
719 } else { |
|
720 Matrix matrix = mTransform; |
|
721 |
|
722 matrix.Invert(); |
|
723 |
|
724 return ThebesPoint(matrix * ToPoint(point)); |
|
725 } |
|
726 } |
|
727 |
|
728 gfxSize |
|
729 gfxContext::DeviceToUser(const gfxSize& size) const |
|
730 { |
|
731 if (mCairo) { |
|
732 gfxSize ret = size; |
|
733 cairo_device_to_user_distance(mCairo, &ret.width, &ret.height); |
|
734 return ret; |
|
735 } else { |
|
736 Matrix matrix = mTransform; |
|
737 |
|
738 matrix.Invert(); |
|
739 |
|
740 return ThebesSize(matrix * ToSize(size)); |
|
741 } |
|
742 } |
|
743 |
|
744 gfxRect |
|
745 gfxContext::DeviceToUser(const gfxRect& rect) const |
|
746 { |
|
747 if (mCairo) { |
|
748 gfxRect ret = rect; |
|
749 cairo_device_to_user(mCairo, &ret.x, &ret.y); |
|
750 cairo_device_to_user_distance(mCairo, &ret.width, &ret.height); |
|
751 return ret; |
|
752 } else { |
|
753 Matrix matrix = mTransform; |
|
754 |
|
755 matrix.Invert(); |
|
756 |
|
757 return ThebesRect(matrix.TransformBounds(ToRect(rect))); |
|
758 } |
|
759 } |
|
760 |
|
761 gfxPoint |
|
762 gfxContext::UserToDevice(const gfxPoint& point) const |
|
763 { |
|
764 if (mCairo) { |
|
765 gfxPoint ret = point; |
|
766 cairo_user_to_device(mCairo, &ret.x, &ret.y); |
|
767 return ret; |
|
768 } else { |
|
769 return ThebesPoint(mTransform * ToPoint(point)); |
|
770 } |
|
771 } |
|
772 |
|
773 gfxSize |
|
774 gfxContext::UserToDevice(const gfxSize& size) const |
|
775 { |
|
776 if (mCairo) { |
|
777 gfxSize ret = size; |
|
778 cairo_user_to_device_distance(mCairo, &ret.width, &ret.height); |
|
779 return ret; |
|
780 } else { |
|
781 const Matrix &matrix = mTransform; |
|
782 |
|
783 gfxSize newSize; |
|
784 newSize.width = size.width * matrix._11 + size.height * matrix._12; |
|
785 newSize.height = size.width * matrix._21 + size.height * matrix._22; |
|
786 return newSize; |
|
787 } |
|
788 } |
|
789 |
|
790 gfxRect |
|
791 gfxContext::UserToDevice(const gfxRect& rect) const |
|
792 { |
|
793 if (mCairo) { |
|
794 double xmin = rect.X(), ymin = rect.Y(), xmax = rect.XMost(), ymax = rect.YMost(); |
|
795 |
|
796 double x[3], y[3]; |
|
797 x[0] = xmin; y[0] = ymax; |
|
798 x[1] = xmax; y[1] = ymax; |
|
799 x[2] = xmax; y[2] = ymin; |
|
800 |
|
801 cairo_user_to_device(mCairo, &xmin, &ymin); |
|
802 xmax = xmin; |
|
803 ymax = ymin; |
|
804 for (int i = 0; i < 3; i++) { |
|
805 cairo_user_to_device(mCairo, &x[i], &y[i]); |
|
806 xmin = std::min(xmin, x[i]); |
|
807 xmax = std::max(xmax, x[i]); |
|
808 ymin = std::min(ymin, y[i]); |
|
809 ymax = std::max(ymax, y[i]); |
|
810 } |
|
811 |
|
812 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin); |
|
813 } else { |
|
814 const Matrix &matrix = mTransform; |
|
815 return ThebesRect(matrix.TransformBounds(ToRect(rect))); |
|
816 } |
|
817 } |
|
818 |
|
819 bool |
|
820 gfxContext::UserToDevicePixelSnapped(gfxRect& rect, bool ignoreScale) const |
|
821 { |
|
822 if (GetFlags() & FLAG_DISABLE_SNAPPING) |
|
823 return false; |
|
824 |
|
825 // if we're not at 1.0 scale, don't snap, unless we're |
|
826 // ignoring the scale. If we're not -just- a scale, |
|
827 // never snap. |
|
828 const gfxFloat epsilon = 0.0000001; |
|
829 #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon) |
|
830 if (mCairo) { |
|
831 cairo_matrix_t mat; |
|
832 cairo_get_matrix(mCairo, &mat); |
|
833 if (!ignoreScale && |
|
834 (!WITHIN_E(mat.xx,1.0) || !WITHIN_E(mat.yy,1.0) || |
|
835 !WITHIN_E(mat.xy,0.0) || !WITHIN_E(mat.yx,0.0))) |
|
836 return false; |
|
837 } else { |
|
838 Matrix mat = mTransform; |
|
839 if (!ignoreScale && |
|
840 (!WITHIN_E(mat._11,1.0) || !WITHIN_E(mat._22,1.0) || |
|
841 !WITHIN_E(mat._12,0.0) || !WITHIN_E(mat._21,0.0))) |
|
842 return false; |
|
843 } |
|
844 #undef WITHIN_E |
|
845 |
|
846 gfxPoint p1 = UserToDevice(rect.TopLeft()); |
|
847 gfxPoint p2 = UserToDevice(rect.TopRight()); |
|
848 gfxPoint p3 = UserToDevice(rect.BottomRight()); |
|
849 |
|
850 // Check that the rectangle is axis-aligned. For an axis-aligned rectangle, |
|
851 // two opposite corners define the entire rectangle. So check if |
|
852 // the axis-aligned rectangle with opposite corners p1 and p3 |
|
853 // define an axis-aligned rectangle whose other corners are p2 and p4. |
|
854 // We actually only need to check one of p2 and p4, since an affine |
|
855 // transform maps parallelograms to parallelograms. |
|
856 if (p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y)) { |
|
857 p1.Round(); |
|
858 p3.Round(); |
|
859 |
|
860 rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y))); |
|
861 rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(), |
|
862 std::max(p1.y, p3.y) - rect.Y())); |
|
863 return true; |
|
864 } |
|
865 |
|
866 return false; |
|
867 } |
|
868 |
|
869 bool |
|
870 gfxContext::UserToDevicePixelSnapped(gfxPoint& pt, bool ignoreScale) const |
|
871 { |
|
872 if (GetFlags() & FLAG_DISABLE_SNAPPING) |
|
873 return false; |
|
874 |
|
875 // if we're not at 1.0 scale, don't snap, unless we're |
|
876 // ignoring the scale. If we're not -just- a scale, |
|
877 // never snap. |
|
878 const gfxFloat epsilon = 0.0000001; |
|
879 #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon) |
|
880 if (mCairo) { |
|
881 cairo_matrix_t mat; |
|
882 cairo_get_matrix(mCairo, &mat); |
|
883 if (!ignoreScale && |
|
884 (!WITHIN_E(mat.xx,1.0) || !WITHIN_E(mat.yy,1.0) || |
|
885 !WITHIN_E(mat.xy,0.0) || !WITHIN_E(mat.yx,0.0))) |
|
886 return false; |
|
887 } else { |
|
888 Matrix mat = mTransform; |
|
889 if (!ignoreScale && |
|
890 (!WITHIN_E(mat._11,1.0) || !WITHIN_E(mat._22,1.0) || |
|
891 !WITHIN_E(mat._12,0.0) || !WITHIN_E(mat._21,0.0))) |
|
892 return false; |
|
893 } |
|
894 #undef WITHIN_E |
|
895 |
|
896 pt = UserToDevice(pt); |
|
897 pt.Round(); |
|
898 return true; |
|
899 } |
|
900 |
|
901 void |
|
902 gfxContext::PixelSnappedRectangleAndSetPattern(const gfxRect& rect, |
|
903 gfxPattern *pattern) |
|
904 { |
|
905 gfxRect r(rect); |
|
906 |
|
907 // Bob attempts to pixel-snap the rectangle, and returns true if |
|
908 // the snapping succeeds. If it does, we need to set up an |
|
909 // identity matrix, because the rectangle given back is in device |
|
910 // coordinates. |
|
911 // |
|
912 // We then have to call a translate to dr.pos afterwards, to make |
|
913 // sure the image lines up in the right place with our pixel |
|
914 // snapped rectangle. |
|
915 // |
|
916 // If snapping wasn't successful, we just translate to where the |
|
917 // pattern would normally start (in app coordinates) and do the |
|
918 // same thing. |
|
919 Rectangle(r, true); |
|
920 SetPattern(pattern); |
|
921 } |
|
922 |
|
923 void |
|
924 gfxContext::SetAntialiasMode(AntialiasMode mode) |
|
925 { |
|
926 if (mCairo) { |
|
927 if (mode == MODE_ALIASED) { |
|
928 cairo_set_antialias(mCairo, CAIRO_ANTIALIAS_NONE); |
|
929 } else if (mode == MODE_COVERAGE) { |
|
930 cairo_set_antialias(mCairo, CAIRO_ANTIALIAS_DEFAULT); |
|
931 } |
|
932 } else { |
|
933 if (mode == MODE_ALIASED) { |
|
934 CurrentState().aaMode = gfx::AntialiasMode::NONE; |
|
935 } else if (mode == MODE_COVERAGE) { |
|
936 CurrentState().aaMode = gfx::AntialiasMode::SUBPIXEL; |
|
937 } |
|
938 } |
|
939 } |
|
940 |
|
941 gfxContext::AntialiasMode |
|
942 gfxContext::CurrentAntialiasMode() const |
|
943 { |
|
944 if (mCairo) { |
|
945 cairo_antialias_t aa = cairo_get_antialias(mCairo); |
|
946 if (aa == CAIRO_ANTIALIAS_NONE) |
|
947 return MODE_ALIASED; |
|
948 return MODE_COVERAGE; |
|
949 } else { |
|
950 if (CurrentState().aaMode == gfx::AntialiasMode::NONE) { |
|
951 return MODE_ALIASED; |
|
952 } |
|
953 return MODE_COVERAGE; |
|
954 } |
|
955 } |
|
956 |
|
957 void |
|
958 gfxContext::SetDash(gfxLineType ltype) |
|
959 { |
|
960 static double dash[] = {5.0, 5.0}; |
|
961 static double dot[] = {1.0, 1.0}; |
|
962 |
|
963 switch (ltype) { |
|
964 case gfxLineDashed: |
|
965 SetDash(dash, 2, 0.0); |
|
966 break; |
|
967 case gfxLineDotted: |
|
968 SetDash(dot, 2, 0.0); |
|
969 break; |
|
970 case gfxLineSolid: |
|
971 default: |
|
972 SetDash(nullptr, 0, 0.0); |
|
973 break; |
|
974 } |
|
975 } |
|
976 |
|
977 void |
|
978 gfxContext::SetDash(gfxFloat *dashes, int ndash, gfxFloat offset) |
|
979 { |
|
980 if (mCairo) { |
|
981 cairo_set_dash(mCairo, dashes, ndash, offset); |
|
982 } else { |
|
983 AzureState &state = CurrentState(); |
|
984 |
|
985 state.dashPattern.SetLength(ndash); |
|
986 for (int i = 0; i < ndash; i++) { |
|
987 state.dashPattern[i] = Float(dashes[i]); |
|
988 } |
|
989 state.strokeOptions.mDashLength = ndash; |
|
990 state.strokeOptions.mDashOffset = Float(offset); |
|
991 state.strokeOptions.mDashPattern = ndash ? state.dashPattern.Elements() |
|
992 : nullptr; |
|
993 } |
|
994 } |
|
995 |
|
996 bool |
|
997 gfxContext::CurrentDash(FallibleTArray<gfxFloat>& dashes, gfxFloat* offset) const |
|
998 { |
|
999 if (mCairo) { |
|
1000 int count = cairo_get_dash_count(mCairo); |
|
1001 if (count <= 0 || !dashes.SetLength(count)) { |
|
1002 return false; |
|
1003 } |
|
1004 cairo_get_dash(mCairo, dashes.Elements(), offset); |
|
1005 return true; |
|
1006 } else { |
|
1007 const AzureState &state = CurrentState(); |
|
1008 int count = state.strokeOptions.mDashLength; |
|
1009 |
|
1010 if (count <= 0 || !dashes.SetLength(count)) { |
|
1011 return false; |
|
1012 } |
|
1013 |
|
1014 for (int i = 0; i < count; i++) { |
|
1015 dashes[i] = state.dashPattern[i]; |
|
1016 } |
|
1017 |
|
1018 *offset = state.strokeOptions.mDashOffset; |
|
1019 |
|
1020 return true; |
|
1021 } |
|
1022 } |
|
1023 |
|
1024 gfxFloat |
|
1025 gfxContext::CurrentDashOffset() const |
|
1026 { |
|
1027 if (mCairo) { |
|
1028 if (cairo_get_dash_count(mCairo) <= 0) { |
|
1029 return 0.0; |
|
1030 } |
|
1031 gfxFloat offset; |
|
1032 cairo_get_dash(mCairo, nullptr, &offset); |
|
1033 return offset; |
|
1034 } else { |
|
1035 return CurrentState().strokeOptions.mDashOffset; |
|
1036 } |
|
1037 } |
|
1038 |
|
1039 void |
|
1040 gfxContext::SetLineWidth(gfxFloat width) |
|
1041 { |
|
1042 if (mCairo) { |
|
1043 cairo_set_line_width(mCairo, width); |
|
1044 } else { |
|
1045 CurrentState().strokeOptions.mLineWidth = Float(width); |
|
1046 } |
|
1047 } |
|
1048 |
|
1049 gfxFloat |
|
1050 gfxContext::CurrentLineWidth() const |
|
1051 { |
|
1052 if (mCairo) { |
|
1053 return cairo_get_line_width(mCairo); |
|
1054 } else { |
|
1055 return CurrentState().strokeOptions.mLineWidth; |
|
1056 } |
|
1057 } |
|
1058 |
|
1059 void |
|
1060 gfxContext::SetOperator(GraphicsOperator op) |
|
1061 { |
|
1062 if (mCairo) { |
|
1063 if (mFlags & FLAG_SIMPLIFY_OPERATORS) { |
|
1064 if (op != OPERATOR_SOURCE && |
|
1065 op != OPERATOR_CLEAR && |
|
1066 op != OPERATOR_OVER) |
|
1067 op = OPERATOR_OVER; |
|
1068 } |
|
1069 |
|
1070 cairo_set_operator(mCairo, (cairo_operator_t)op); |
|
1071 } else { |
|
1072 if (op == OPERATOR_CLEAR) { |
|
1073 CurrentState().opIsClear = true; |
|
1074 return; |
|
1075 } |
|
1076 CurrentState().opIsClear = false; |
|
1077 CurrentState().op = CompositionOpForOp(op); |
|
1078 } |
|
1079 } |
|
1080 |
|
1081 gfxContext::GraphicsOperator |
|
1082 gfxContext::CurrentOperator() const |
|
1083 { |
|
1084 if (mCairo) { |
|
1085 return (GraphicsOperator)cairo_get_operator(mCairo); |
|
1086 } else { |
|
1087 return ThebesOp(CurrentState().op); |
|
1088 } |
|
1089 } |
|
1090 |
|
1091 void |
|
1092 gfxContext::SetLineCap(GraphicsLineCap cap) |
|
1093 { |
|
1094 if (mCairo) { |
|
1095 cairo_set_line_cap(mCairo, (cairo_line_cap_t)cap); |
|
1096 } else { |
|
1097 CurrentState().strokeOptions.mLineCap = ToCapStyle(cap); |
|
1098 } |
|
1099 } |
|
1100 |
|
1101 gfxContext::GraphicsLineCap |
|
1102 gfxContext::CurrentLineCap() const |
|
1103 { |
|
1104 if (mCairo) { |
|
1105 return (GraphicsLineCap)cairo_get_line_cap(mCairo); |
|
1106 } else { |
|
1107 return ThebesLineCap(CurrentState().strokeOptions.mLineCap); |
|
1108 } |
|
1109 } |
|
1110 |
|
1111 void |
|
1112 gfxContext::SetLineJoin(GraphicsLineJoin join) |
|
1113 { |
|
1114 if (mCairo) { |
|
1115 cairo_set_line_join(mCairo, (cairo_line_join_t)join); |
|
1116 } else { |
|
1117 CurrentState().strokeOptions.mLineJoin = ToJoinStyle(join); |
|
1118 } |
|
1119 } |
|
1120 |
|
1121 gfxContext::GraphicsLineJoin |
|
1122 gfxContext::CurrentLineJoin() const |
|
1123 { |
|
1124 if (mCairo) { |
|
1125 return (GraphicsLineJoin)cairo_get_line_join(mCairo); |
|
1126 } else { |
|
1127 return ThebesLineJoin(CurrentState().strokeOptions.mLineJoin); |
|
1128 } |
|
1129 } |
|
1130 |
|
1131 void |
|
1132 gfxContext::SetMiterLimit(gfxFloat limit) |
|
1133 { |
|
1134 if (mCairo) { |
|
1135 cairo_set_miter_limit(mCairo, limit); |
|
1136 } else { |
|
1137 CurrentState().strokeOptions.mMiterLimit = Float(limit); |
|
1138 } |
|
1139 } |
|
1140 |
|
1141 gfxFloat |
|
1142 gfxContext::CurrentMiterLimit() const |
|
1143 { |
|
1144 if (mCairo) { |
|
1145 return cairo_get_miter_limit(mCairo); |
|
1146 } else { |
|
1147 return CurrentState().strokeOptions.mMiterLimit; |
|
1148 } |
|
1149 } |
|
1150 |
|
1151 void |
|
1152 gfxContext::SetFillRule(FillRule rule) |
|
1153 { |
|
1154 if (mCairo) { |
|
1155 cairo_set_fill_rule(mCairo, (cairo_fill_rule_t)rule); |
|
1156 } else { |
|
1157 CurrentState().fillRule = rule == FILL_RULE_WINDING ? gfx::FillRule::FILL_WINDING : gfx::FillRule::FILL_EVEN_ODD; |
|
1158 } |
|
1159 } |
|
1160 |
|
1161 gfxContext::FillRule |
|
1162 gfxContext::CurrentFillRule() const |
|
1163 { |
|
1164 if (mCairo) { |
|
1165 return (FillRule)cairo_get_fill_rule(mCairo); |
|
1166 } else { |
|
1167 return FILL_RULE_WINDING; |
|
1168 } |
|
1169 } |
|
1170 |
|
1171 // clipping |
|
1172 void |
|
1173 gfxContext::Clip(const gfxRect& rect) |
|
1174 { |
|
1175 if (mCairo) { |
|
1176 cairo_new_path(mCairo); |
|
1177 cairo_rectangle(mCairo, rect.X(), rect.Y(), rect.Width(), rect.Height()); |
|
1178 cairo_clip(mCairo); |
|
1179 } else { |
|
1180 AzureState::PushedClip clip = { nullptr, ToRect(rect), mTransform }; |
|
1181 CurrentState().pushedClips.AppendElement(clip); |
|
1182 mDT->PushClipRect(ToRect(rect)); |
|
1183 NewPath(); |
|
1184 } |
|
1185 } |
|
1186 |
|
1187 void |
|
1188 gfxContext::Clip() |
|
1189 { |
|
1190 if (mCairo) { |
|
1191 cairo_clip_preserve(mCairo); |
|
1192 } else { |
|
1193 if (mPathIsRect) { |
|
1194 MOZ_ASSERT(!mTransformChanged); |
|
1195 |
|
1196 AzureState::PushedClip clip = { nullptr, mRect, mTransform }; |
|
1197 CurrentState().pushedClips.AppendElement(clip); |
|
1198 mDT->PushClipRect(mRect); |
|
1199 } else { |
|
1200 EnsurePath(); |
|
1201 mDT->PushClip(mPath); |
|
1202 AzureState::PushedClip clip = { mPath, Rect(), mTransform }; |
|
1203 CurrentState().pushedClips.AppendElement(clip); |
|
1204 } |
|
1205 } |
|
1206 } |
|
1207 |
|
1208 void |
|
1209 gfxContext::ResetClip() |
|
1210 { |
|
1211 if (mCairo) { |
|
1212 cairo_reset_clip(mCairo); |
|
1213 } else { |
|
1214 for (int i = mStateStack.Length() - 1; i >= 0; i--) { |
|
1215 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { |
|
1216 mDT->PopClip(); |
|
1217 } |
|
1218 |
|
1219 if (mStateStack[i].clipWasReset) { |
|
1220 break; |
|
1221 } |
|
1222 } |
|
1223 CurrentState().pushedClips.Clear(); |
|
1224 CurrentState().clipWasReset = true; |
|
1225 } |
|
1226 } |
|
1227 |
|
1228 void |
|
1229 gfxContext::UpdateSurfaceClip() |
|
1230 { |
|
1231 if (mCairo) { |
|
1232 NewPath(); |
|
1233 // we paint an empty rectangle to ensure the clip is propagated to |
|
1234 // the destination surface |
|
1235 SetDeviceColor(gfxRGBA(0,0,0,0)); |
|
1236 Rectangle(gfxRect(0,1,1,0)); |
|
1237 Fill(); |
|
1238 } |
|
1239 } |
|
1240 |
|
1241 gfxRect |
|
1242 gfxContext::GetClipExtents() |
|
1243 { |
|
1244 if (mCairo) { |
|
1245 double xmin, ymin, xmax, ymax; |
|
1246 cairo_clip_extents(mCairo, &xmin, &ymin, &xmax, &ymax); |
|
1247 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin); |
|
1248 } else { |
|
1249 Rect rect = GetAzureDeviceSpaceClipBounds(); |
|
1250 |
|
1251 if (rect.width == 0 || rect.height == 0) { |
|
1252 return gfxRect(0, 0, 0, 0); |
|
1253 } |
|
1254 |
|
1255 Matrix mat = mTransform; |
|
1256 mat.Invert(); |
|
1257 rect = mat.TransformBounds(rect); |
|
1258 |
|
1259 return ThebesRect(rect); |
|
1260 } |
|
1261 } |
|
1262 |
|
1263 bool |
|
1264 gfxContext::ClipContainsRect(const gfxRect& aRect) |
|
1265 { |
|
1266 if (mCairo) { |
|
1267 cairo_rectangle_list_t *clip = |
|
1268 cairo_copy_clip_rectangle_list(mCairo); |
|
1269 |
|
1270 bool result = false; |
|
1271 |
|
1272 if (clip->status == CAIRO_STATUS_SUCCESS) { |
|
1273 for (int i = 0; i < clip->num_rectangles; i++) { |
|
1274 gfxRect rect(clip->rectangles[i].x, clip->rectangles[i].y, |
|
1275 clip->rectangles[i].width, clip->rectangles[i].height); |
|
1276 if (rect.Contains(aRect)) { |
|
1277 result = true; |
|
1278 break; |
|
1279 } |
|
1280 } |
|
1281 } |
|
1282 |
|
1283 cairo_rectangle_list_destroy(clip); |
|
1284 return result; |
|
1285 } else { |
|
1286 unsigned int lastReset = 0; |
|
1287 for (int i = mStateStack.Length() - 2; i > 0; i--) { |
|
1288 if (mStateStack[i].clipWasReset) { |
|
1289 lastReset = i; |
|
1290 break; |
|
1291 } |
|
1292 } |
|
1293 |
|
1294 // Since we always return false when the clip list contains a |
|
1295 // non-rectangular clip or a non-rectilinear transform, our 'total' clip |
|
1296 // is always a rectangle if we hit the end of this function. |
|
1297 Rect clipBounds(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height)); |
|
1298 |
|
1299 for (unsigned int i = lastReset; i < mStateStack.Length(); i++) { |
|
1300 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { |
|
1301 AzureState::PushedClip &clip = mStateStack[i].pushedClips[c]; |
|
1302 if (clip.path || !clip.transform.IsRectilinear()) { |
|
1303 // Cairo behavior is we return false if the clip contains a non- |
|
1304 // rectangle. |
|
1305 return false; |
|
1306 } else { |
|
1307 Rect clipRect = mTransform.TransformBounds(clip.rect); |
|
1308 |
|
1309 clipBounds.IntersectRect(clipBounds, clipRect); |
|
1310 } |
|
1311 } |
|
1312 } |
|
1313 |
|
1314 return clipBounds.Contains(ToRect(aRect)); |
|
1315 } |
|
1316 } |
|
1317 |
|
1318 // rendering sources |
|
1319 |
|
1320 void |
|
1321 gfxContext::SetColor(const gfxRGBA& c) |
|
1322 { |
|
1323 if (mCairo) { |
|
1324 if (gfxPlatform::GetCMSMode() == eCMSMode_All) { |
|
1325 |
|
1326 gfxRGBA cms; |
|
1327 qcms_transform *transform = gfxPlatform::GetCMSRGBTransform(); |
|
1328 if (transform) |
|
1329 gfxPlatform::TransformPixel(c, cms, transform); |
|
1330 |
|
1331 // Use the original alpha to avoid unnecessary float->byte->float |
|
1332 // conversion errors |
|
1333 cairo_set_source_rgba(mCairo, cms.r, cms.g, cms.b, c.a); |
|
1334 } |
|
1335 else |
|
1336 cairo_set_source_rgba(mCairo, c.r, c.g, c.b, c.a); |
|
1337 } else { |
|
1338 CurrentState().pattern = nullptr; |
|
1339 CurrentState().sourceSurfCairo = nullptr; |
|
1340 CurrentState().sourceSurface = nullptr; |
|
1341 |
|
1342 if (gfxPlatform::GetCMSMode() == eCMSMode_All) { |
|
1343 |
|
1344 gfxRGBA cms; |
|
1345 qcms_transform *transform = gfxPlatform::GetCMSRGBTransform(); |
|
1346 if (transform) |
|
1347 gfxPlatform::TransformPixel(c, cms, transform); |
|
1348 |
|
1349 // Use the original alpha to avoid unnecessary float->byte->float |
|
1350 // conversion errors |
|
1351 CurrentState().color = ToColor(cms); |
|
1352 } |
|
1353 else |
|
1354 CurrentState().color = ToColor(c); |
|
1355 } |
|
1356 } |
|
1357 |
|
1358 void |
|
1359 gfxContext::SetDeviceColor(const gfxRGBA& c) |
|
1360 { |
|
1361 if (mCairo) { |
|
1362 cairo_set_source_rgba(mCairo, c.r, c.g, c.b, c.a); |
|
1363 } else { |
|
1364 CurrentState().pattern = nullptr; |
|
1365 CurrentState().sourceSurfCairo = nullptr; |
|
1366 CurrentState().sourceSurface = nullptr; |
|
1367 CurrentState().color = ToColor(c); |
|
1368 } |
|
1369 } |
|
1370 |
|
1371 bool |
|
1372 gfxContext::GetDeviceColor(gfxRGBA& c) |
|
1373 { |
|
1374 if (mCairo) { |
|
1375 return cairo_pattern_get_rgba(cairo_get_source(mCairo), |
|
1376 &c.r, |
|
1377 &c.g, |
|
1378 &c.b, |
|
1379 &c.a) == CAIRO_STATUS_SUCCESS; |
|
1380 } else { |
|
1381 if (CurrentState().sourceSurface) { |
|
1382 return false; |
|
1383 } |
|
1384 if (CurrentState().pattern) { |
|
1385 gfxRGBA color; |
|
1386 return CurrentState().pattern->GetSolidColor(c); |
|
1387 } |
|
1388 |
|
1389 c = ThebesRGBA(CurrentState().color); |
|
1390 return true; |
|
1391 } |
|
1392 } |
|
1393 |
|
1394 void |
|
1395 gfxContext::SetSource(gfxASurface *surface, const gfxPoint& offset) |
|
1396 { |
|
1397 if (mCairo) { |
|
1398 NS_ASSERTION(surface->GetAllowUseAsSource(), "Surface not allowed to be used as source!"); |
|
1399 cairo_set_source_surface(mCairo, surface->CairoSurface(), offset.x, offset.y); |
|
1400 } else { |
|
1401 CurrentState().surfTransform = Matrix(1.0f, 0, 0, 1.0f, Float(offset.x), Float(offset.y)); |
|
1402 CurrentState().pattern = nullptr; |
|
1403 CurrentState().patternTransformChanged = false; |
|
1404 // Keep the underlying cairo surface around while we keep the |
|
1405 // sourceSurface. |
|
1406 CurrentState().sourceSurfCairo = surface; |
|
1407 CurrentState().sourceSurface = |
|
1408 gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface); |
|
1409 CurrentState().color = Color(0, 0, 0, 0); |
|
1410 } |
|
1411 } |
|
1412 |
|
1413 void |
|
1414 gfxContext::SetPattern(gfxPattern *pattern) |
|
1415 { |
|
1416 if (mCairo) { |
|
1417 MOZ_ASSERT(!pattern->IsAzure()); |
|
1418 cairo_set_source(mCairo, pattern->CairoPattern()); |
|
1419 } else { |
|
1420 CurrentState().sourceSurfCairo = nullptr; |
|
1421 CurrentState().sourceSurface = nullptr; |
|
1422 CurrentState().patternTransformChanged = false; |
|
1423 CurrentState().pattern = pattern; |
|
1424 } |
|
1425 } |
|
1426 |
|
1427 already_AddRefed<gfxPattern> |
|
1428 gfxContext::GetPattern() |
|
1429 { |
|
1430 if (mCairo) { |
|
1431 cairo_pattern_t *pat = cairo_get_source(mCairo); |
|
1432 NS_ASSERTION(pat, "I was told this couldn't be null"); |
|
1433 |
|
1434 nsRefPtr<gfxPattern> wrapper; |
|
1435 if (pat) |
|
1436 wrapper = new gfxPattern(pat); |
|
1437 else |
|
1438 wrapper = new gfxPattern(gfxRGBA(0,0,0,0)); |
|
1439 |
|
1440 return wrapper.forget(); |
|
1441 } else { |
|
1442 nsRefPtr<gfxPattern> pat; |
|
1443 |
|
1444 AzureState &state = CurrentState(); |
|
1445 if (state.pattern) { |
|
1446 pat = state.pattern; |
|
1447 } else if (state.sourceSurface) { |
|
1448 NS_ASSERTION(false, "Ugh, this isn't good."); |
|
1449 } else { |
|
1450 pat = new gfxPattern(ThebesRGBA(state.color)); |
|
1451 } |
|
1452 return pat.forget(); |
|
1453 } |
|
1454 } |
|
1455 |
|
1456 |
|
1457 // masking |
|
1458 void |
|
1459 gfxContext::Mask(gfxPattern *pattern) |
|
1460 { |
|
1461 if (mCairo) { |
|
1462 MOZ_ASSERT(!pattern->IsAzure()); |
|
1463 cairo_mask(mCairo, pattern->CairoPattern()); |
|
1464 } else { |
|
1465 if (pattern->Extend() == gfxPattern::EXTEND_NONE) { |
|
1466 // In this situation the mask will be fully transparent (i.e. nothing |
|
1467 // will be drawn) outside of the bounds of the surface. We can support |
|
1468 // that by clipping out drawing to that area. |
|
1469 Point offset; |
|
1470 if (pattern->IsAzure()) { |
|
1471 // This is an Azure pattern. i.e. this was the result of a PopGroup and |
|
1472 // then the extend mode was changed to EXTEND_NONE. |
|
1473 // XXX - We may need some additional magic here in theory to support |
|
1474 // device offsets in these patterns, but no problems have been observed |
|
1475 // yet because of this. And it would complicate things a little further. |
|
1476 offset = Point(0.f, 0.f); |
|
1477 } else if (pattern->GetType() == gfxPattern::PATTERN_SURFACE) { |
|
1478 nsRefPtr<gfxASurface> asurf = pattern->GetSurface(); |
|
1479 gfxPoint deviceOffset = asurf->GetDeviceOffset(); |
|
1480 offset = Point(-deviceOffset.x, -deviceOffset.y); |
|
1481 |
|
1482 // this lets GetAzureSurface work |
|
1483 pattern->GetPattern(mDT); |
|
1484 } |
|
1485 |
|
1486 if (pattern->IsAzure() || pattern->GetType() == gfxPattern::PATTERN_SURFACE) { |
|
1487 RefPtr<SourceSurface> mask = pattern->GetAzureSurface(); |
|
1488 Matrix mat = ToMatrix(pattern->GetInverseMatrix()); |
|
1489 Matrix old = mTransform; |
|
1490 // add in the inverse of the pattern transform so that when we |
|
1491 // MaskSurface we are transformed to the place matching the pattern transform |
|
1492 mat = mat * mTransform; |
|
1493 |
|
1494 ChangeTransform(mat); |
|
1495 mDT->MaskSurface(GeneralPattern(this), mask, offset, DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode)); |
|
1496 ChangeTransform(old); |
|
1497 return; |
|
1498 } |
|
1499 } |
|
1500 mDT->Mask(GeneralPattern(this), *pattern->GetPattern(mDT), DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode)); |
|
1501 } |
|
1502 } |
|
1503 |
|
1504 void |
|
1505 gfxContext::Mask(gfxASurface *surface, const gfxPoint& offset) |
|
1506 { |
|
1507 PROFILER_LABEL("gfxContext", "Mask"); |
|
1508 if (mCairo) { |
|
1509 cairo_mask_surface(mCairo, surface->CairoSurface(), offset.x, offset.y); |
|
1510 } else { |
|
1511 // Lifetime needs to be limited here as we may simply wrap surface's data. |
|
1512 RefPtr<SourceSurface> sourceSurf = |
|
1513 gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface); |
|
1514 |
|
1515 if (!sourceSurf) { |
|
1516 return; |
|
1517 } |
|
1518 |
|
1519 gfxPoint pt = surface->GetDeviceOffset(); |
|
1520 |
|
1521 Mask(sourceSurf, Point(offset.x - pt.x, offset.y - pt.y)); |
|
1522 } |
|
1523 } |
|
1524 |
|
1525 void |
|
1526 gfxContext::Mask(SourceSurface *surface, const Point& offset) |
|
1527 { |
|
1528 MOZ_ASSERT(mDT); |
|
1529 |
|
1530 |
|
1531 // We clip here to bind to the mask surface bounds, see above. |
|
1532 mDT->MaskSurface(GeneralPattern(this), |
|
1533 surface, |
|
1534 offset, |
|
1535 DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode)); |
|
1536 } |
|
1537 |
|
1538 void |
|
1539 gfxContext::Paint(gfxFloat alpha) |
|
1540 { |
|
1541 PROFILER_LABEL("gfxContext", "Paint"); |
|
1542 if (mCairo) { |
|
1543 cairo_paint_with_alpha(mCairo, alpha); |
|
1544 } else { |
|
1545 AzureState &state = CurrentState(); |
|
1546 |
|
1547 if (state.sourceSurface && !state.sourceSurfCairo && |
|
1548 !state.patternTransformChanged && !state.opIsClear) |
|
1549 { |
|
1550 // This is the case where a PopGroupToSource has been done and this |
|
1551 // paint is executed without changing the transform or the source. |
|
1552 Matrix oldMat = mDT->GetTransform(); |
|
1553 |
|
1554 IntSize surfSize = state.sourceSurface->GetSize(); |
|
1555 |
|
1556 Matrix mat; |
|
1557 mat.Translate(-state.deviceOffset.x, -state.deviceOffset.y); |
|
1558 mDT->SetTransform(mat); |
|
1559 |
|
1560 mDT->DrawSurface(state.sourceSurface, |
|
1561 Rect(state.sourceSurfaceDeviceOffset, Size(surfSize.width, surfSize.height)), |
|
1562 Rect(Point(), Size(surfSize.width, surfSize.height)), |
|
1563 DrawSurfaceOptions(), DrawOptions(alpha, GetOp())); |
|
1564 mDT->SetTransform(oldMat); |
|
1565 return; |
|
1566 } |
|
1567 |
|
1568 Matrix mat = mDT->GetTransform(); |
|
1569 mat.Invert(); |
|
1570 Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize()))); |
|
1571 |
|
1572 if (state.opIsClear) { |
|
1573 mDT->ClearRect(paintRect); |
|
1574 } else { |
|
1575 mDT->FillRect(paintRect, GeneralPattern(this), |
|
1576 DrawOptions(Float(alpha), GetOp())); |
|
1577 } |
|
1578 } |
|
1579 } |
|
1580 |
|
1581 // groups |
|
1582 |
|
1583 void |
|
1584 gfxContext::PushGroup(gfxContentType content) |
|
1585 { |
|
1586 if (mCairo) { |
|
1587 cairo_push_group_with_content(mCairo, (cairo_content_t)(int) content); |
|
1588 } else { |
|
1589 PushNewDT(content); |
|
1590 |
|
1591 PushClipsToDT(mDT); |
|
1592 mDT->SetTransform(GetDTTransform()); |
|
1593 } |
|
1594 } |
|
1595 |
|
1596 static gfxRect |
|
1597 GetRoundOutDeviceClipExtents(gfxContext* aCtx) |
|
1598 { |
|
1599 gfxContextMatrixAutoSaveRestore save(aCtx); |
|
1600 aCtx->IdentityMatrix(); |
|
1601 gfxRect r = aCtx->GetClipExtents(); |
|
1602 r.RoundOut(); |
|
1603 return r; |
|
1604 } |
|
1605 |
|
1606 /** |
|
1607 * Copy the contents of aSrc to aDest, translated by aTranslation. |
|
1608 */ |
|
1609 static void |
|
1610 CopySurface(gfxASurface* aSrc, gfxASurface* aDest, const gfxPoint& aTranslation) |
|
1611 { |
|
1612 cairo_t *cr = cairo_create(aDest->CairoSurface()); |
|
1613 cairo_set_source_surface(cr, aSrc->CairoSurface(), aTranslation.x, aTranslation.y); |
|
1614 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); |
|
1615 cairo_paint(cr); |
|
1616 cairo_destroy(cr); |
|
1617 } |
|
1618 |
|
1619 void |
|
1620 gfxContext::PushGroupAndCopyBackground(gfxContentType content) |
|
1621 { |
|
1622 if (mCairo) { |
|
1623 if (content == gfxContentType::COLOR_ALPHA && |
|
1624 !(GetFlags() & FLAG_DISABLE_COPY_BACKGROUND)) { |
|
1625 nsRefPtr<gfxASurface> s = CurrentSurface(); |
|
1626 if ((s->GetAllowUseAsSource() || s->GetType() == gfxSurfaceType::Tee) && |
|
1627 (s->GetContentType() == gfxContentType::COLOR || |
|
1628 s->GetOpaqueRect().Contains(GetRoundOutDeviceClipExtents(this)))) { |
|
1629 cairo_push_group_with_content(mCairo, CAIRO_CONTENT_COLOR); |
|
1630 nsRefPtr<gfxASurface> d = CurrentSurface(); |
|
1631 |
|
1632 if (d->GetType() == gfxSurfaceType::Tee) { |
|
1633 NS_ASSERTION(s->GetType() == gfxSurfaceType::Tee, "Mismatched types"); |
|
1634 nsAutoTArray<nsRefPtr<gfxASurface>,2> ss; |
|
1635 nsAutoTArray<nsRefPtr<gfxASurface>,2> ds; |
|
1636 static_cast<gfxTeeSurface*>(s.get())->GetSurfaces(&ss); |
|
1637 static_cast<gfxTeeSurface*>(d.get())->GetSurfaces(&ds); |
|
1638 NS_ASSERTION(ss.Length() == ds.Length(), "Mismatched lengths"); |
|
1639 gfxPoint translation = d->GetDeviceOffset() - s->GetDeviceOffset(); |
|
1640 for (uint32_t i = 0; i < ss.Length(); ++i) { |
|
1641 CopySurface(ss[i], ds[i], translation); |
|
1642 } |
|
1643 } else { |
|
1644 CopySurface(s, d, gfxPoint(0, 0)); |
|
1645 } |
|
1646 d->SetOpaqueRect(s->GetOpaqueRect()); |
|
1647 return; |
|
1648 } |
|
1649 } |
|
1650 } else { |
|
1651 IntRect clipExtents; |
|
1652 if (mDT->GetFormat() != SurfaceFormat::B8G8R8X8) { |
|
1653 gfxRect clipRect = GetRoundOutDeviceClipExtents(this); |
|
1654 clipExtents = IntRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height); |
|
1655 } |
|
1656 if ((mDT->GetFormat() == SurfaceFormat::B8G8R8X8 || |
|
1657 mDT->GetOpaqueRect().Contains(clipExtents)) && |
|
1658 !mDT->GetUserData(&sDontUseAsSourceKey)) { |
|
1659 DrawTarget *oldDT = mDT; |
|
1660 RefPtr<SourceSurface> source = mDT->Snapshot(); |
|
1661 Point oldDeviceOffset = CurrentState().deviceOffset; |
|
1662 |
|
1663 PushNewDT(gfxContentType::COLOR); |
|
1664 |
|
1665 Point offset = CurrentState().deviceOffset - oldDeviceOffset; |
|
1666 Rect surfRect(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height)); |
|
1667 Rect sourceRect = surfRect; |
|
1668 sourceRect.x += offset.x; |
|
1669 sourceRect.y += offset.y; |
|
1670 |
|
1671 mDT->SetTransform(Matrix()); |
|
1672 mDT->DrawSurface(source, surfRect, sourceRect); |
|
1673 mDT->SetOpaqueRect(oldDT->GetOpaqueRect()); |
|
1674 |
|
1675 PushClipsToDT(mDT); |
|
1676 mDT->SetTransform(GetDTTransform()); |
|
1677 return; |
|
1678 } |
|
1679 } |
|
1680 PushGroup(content); |
|
1681 } |
|
1682 |
|
1683 already_AddRefed<gfxPattern> |
|
1684 gfxContext::PopGroup() |
|
1685 { |
|
1686 if (mCairo) { |
|
1687 cairo_pattern_t *pat = cairo_pop_group(mCairo); |
|
1688 nsRefPtr<gfxPattern> wrapper = new gfxPattern(pat); |
|
1689 cairo_pattern_destroy(pat); |
|
1690 return wrapper.forget(); |
|
1691 } else { |
|
1692 RefPtr<SourceSurface> src = mDT->Snapshot(); |
|
1693 Point deviceOffset = CurrentState().deviceOffset; |
|
1694 |
|
1695 Restore(); |
|
1696 |
|
1697 Matrix mat = mTransform; |
|
1698 mat.Invert(); |
|
1699 |
|
1700 Matrix deviceOffsetTranslation; |
|
1701 deviceOffsetTranslation.Translate(deviceOffset.x, deviceOffset.y); |
|
1702 |
|
1703 nsRefPtr<gfxPattern> pat = new gfxPattern(src, deviceOffsetTranslation * mat); |
|
1704 |
|
1705 return pat.forget(); |
|
1706 } |
|
1707 } |
|
1708 |
|
1709 void |
|
1710 gfxContext::PopGroupToSource() |
|
1711 { |
|
1712 if (mCairo) { |
|
1713 cairo_pop_group_to_source(mCairo); |
|
1714 } else { |
|
1715 RefPtr<SourceSurface> src = mDT->Snapshot(); |
|
1716 Point deviceOffset = CurrentState().deviceOffset; |
|
1717 Restore(); |
|
1718 CurrentState().sourceSurfCairo = nullptr; |
|
1719 CurrentState().sourceSurface = src; |
|
1720 CurrentState().sourceSurfaceDeviceOffset = deviceOffset; |
|
1721 CurrentState().pattern = nullptr; |
|
1722 CurrentState().patternTransformChanged = false; |
|
1723 |
|
1724 Matrix mat = mTransform; |
|
1725 mat.Invert(); |
|
1726 |
|
1727 Matrix deviceOffsetTranslation; |
|
1728 deviceOffsetTranslation.Translate(deviceOffset.x, deviceOffset.y); |
|
1729 CurrentState().surfTransform = deviceOffsetTranslation * mat; |
|
1730 } |
|
1731 } |
|
1732 |
|
1733 bool |
|
1734 gfxContext::PointInFill(const gfxPoint& pt) |
|
1735 { |
|
1736 if (mCairo) { |
|
1737 return cairo_in_fill(mCairo, pt.x, pt.y); |
|
1738 } else { |
|
1739 EnsurePath(); |
|
1740 return mPath->ContainsPoint(ToPoint(pt), Matrix()); |
|
1741 } |
|
1742 } |
|
1743 |
|
1744 bool |
|
1745 gfxContext::PointInStroke(const gfxPoint& pt) |
|
1746 { |
|
1747 if (mCairo) { |
|
1748 return cairo_in_stroke(mCairo, pt.x, pt.y); |
|
1749 } else { |
|
1750 EnsurePath(); |
|
1751 return mPath->StrokeContainsPoint(CurrentState().strokeOptions, |
|
1752 ToPoint(pt), |
|
1753 Matrix()); |
|
1754 } |
|
1755 } |
|
1756 |
|
1757 gfxRect |
|
1758 gfxContext::GetUserPathExtent() |
|
1759 { |
|
1760 if (mCairo) { |
|
1761 double xmin, ymin, xmax, ymax; |
|
1762 cairo_path_extents(mCairo, &xmin, &ymin, &xmax, &ymax); |
|
1763 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin); |
|
1764 } else { |
|
1765 EnsurePath(); |
|
1766 return ThebesRect(mPath->GetBounds()); |
|
1767 } |
|
1768 } |
|
1769 |
|
1770 gfxRect |
|
1771 gfxContext::GetUserFillExtent() |
|
1772 { |
|
1773 if (mCairo) { |
|
1774 double xmin, ymin, xmax, ymax; |
|
1775 cairo_fill_extents(mCairo, &xmin, &ymin, &xmax, &ymax); |
|
1776 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin); |
|
1777 } else { |
|
1778 EnsurePath(); |
|
1779 return ThebesRect(mPath->GetBounds()); |
|
1780 } |
|
1781 } |
|
1782 |
|
1783 gfxRect |
|
1784 gfxContext::GetUserStrokeExtent() |
|
1785 { |
|
1786 if (mCairo) { |
|
1787 double xmin, ymin, xmax, ymax; |
|
1788 cairo_stroke_extents(mCairo, &xmin, &ymin, &xmax, &ymax); |
|
1789 return gfxRect(xmin, ymin, xmax - xmin, ymax - ymin); |
|
1790 } else { |
|
1791 EnsurePath(); |
|
1792 return ThebesRect(mPath->GetStrokedBounds(CurrentState().strokeOptions, mTransform)); |
|
1793 } |
|
1794 } |
|
1795 |
|
1796 bool |
|
1797 gfxContext::HasError() |
|
1798 { |
|
1799 if (mCairo) { |
|
1800 return cairo_status(mCairo) != CAIRO_STATUS_SUCCESS; |
|
1801 } else { |
|
1802 // As far as this is concerned, an Azure context is never in error. |
|
1803 return false; |
|
1804 } |
|
1805 } |
|
1806 |
|
1807 void |
|
1808 gfxContext::RoundedRectangle(const gfxRect& rect, |
|
1809 const gfxCornerSizes& corners, |
|
1810 bool draw_clockwise) |
|
1811 { |
|
1812 // |
|
1813 // For CW drawing, this looks like: |
|
1814 // |
|
1815 // ...******0** 1 C |
|
1816 // **** |
|
1817 // *** 2 |
|
1818 // ** |
|
1819 // * |
|
1820 // * |
|
1821 // 3 |
|
1822 // * |
|
1823 // * |
|
1824 // |
|
1825 // Where 0, 1, 2, 3 are the control points of the Bezier curve for |
|
1826 // the corner, and C is the actual corner point. |
|
1827 // |
|
1828 // At the start of the loop, the current point is assumed to be |
|
1829 // the point adjacent to the top left corner on the top |
|
1830 // horizontal. Note that corner indices start at the top left and |
|
1831 // continue clockwise, whereas in our loop i = 0 refers to the top |
|
1832 // right corner. |
|
1833 // |
|
1834 // When going CCW, the control points are swapped, and the first |
|
1835 // corner that's drawn is the top left (along with the top segment). |
|
1836 // |
|
1837 // There is considerable latitude in how one chooses the four |
|
1838 // control points for a Bezier curve approximation to an ellipse. |
|
1839 // For the overall path to be continuous and show no corner at the |
|
1840 // endpoints of the arc, points 0 and 3 must be at the ends of the |
|
1841 // straight segments of the rectangle; points 0, 1, and C must be |
|
1842 // collinear; and points 3, 2, and C must also be collinear. This |
|
1843 // leaves only two free parameters: the ratio of the line segments |
|
1844 // 01 and 0C, and the ratio of the line segments 32 and 3C. See |
|
1845 // the following papers for extensive discussion of how to choose |
|
1846 // these ratios: |
|
1847 // |
|
1848 // Dokken, Tor, et al. "Good approximation of circles by |
|
1849 // curvature-continuous Bezier curves." Computer-Aided |
|
1850 // Geometric Design 7(1990) 33--41. |
|
1851 // Goldapp, Michael. "Approximation of circular arcs by cubic |
|
1852 // polynomials." Computer-Aided Geometric Design 8(1991) 227--238. |
|
1853 // Maisonobe, Luc. "Drawing an elliptical arc using polylines, |
|
1854 // quadratic, or cubic Bezier curves." |
|
1855 // http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf |
|
1856 // |
|
1857 // We follow the approach in section 2 of Goldapp (least-error, |
|
1858 // Hermite-type approximation) and make both ratios equal to |
|
1859 // |
|
1860 // 2 2 + n - sqrt(2n + 28) |
|
1861 // alpha = - * --------------------- |
|
1862 // 3 n - 4 |
|
1863 // |
|
1864 // where n = 3( cbrt(sqrt(2)+1) - cbrt(sqrt(2)-1) ). |
|
1865 // |
|
1866 // This is the result of Goldapp's equation (10b) when the angle |
|
1867 // swept out by the arc is pi/2, and the parameter "a-bar" is the |
|
1868 // expression given immediately below equation (21). |
|
1869 // |
|
1870 // Using this value, the maximum radial error for a circle, as a |
|
1871 // fraction of the radius, is on the order of 0.2 x 10^-3. |
|
1872 // Neither Dokken nor Goldapp discusses error for a general |
|
1873 // ellipse; Maisonobe does, but his choice of control points |
|
1874 // follows different constraints, and Goldapp's expression for |
|
1875 // 'alpha' gives much smaller radial error, even for very flat |
|
1876 // ellipses, than Maisonobe's equivalent. |
|
1877 // |
|
1878 // For the various corners and for each axis, the sign of this |
|
1879 // constant changes, or it might be 0 -- it's multiplied by the |
|
1880 // appropriate multiplier from the list before using. |
|
1881 |
|
1882 if (mCairo) { |
|
1883 const gfxFloat alpha = 0.55191497064665766025; |
|
1884 |
|
1885 typedef struct { gfxFloat a, b; } twoFloats; |
|
1886 |
|
1887 twoFloats cwCornerMults[4] = { { -1, 0 }, |
|
1888 { 0, -1 }, |
|
1889 { +1, 0 }, |
|
1890 { 0, +1 } }; |
|
1891 twoFloats ccwCornerMults[4] = { { +1, 0 }, |
|
1892 { 0, -1 }, |
|
1893 { -1, 0 }, |
|
1894 { 0, +1 } }; |
|
1895 |
|
1896 twoFloats *cornerMults = draw_clockwise ? cwCornerMults : ccwCornerMults; |
|
1897 |
|
1898 gfxPoint pc, p0, p1, p2, p3; |
|
1899 |
|
1900 if (draw_clockwise) |
|
1901 cairo_move_to(mCairo, rect.X() + corners[NS_CORNER_TOP_LEFT].width, rect.Y()); |
|
1902 else |
|
1903 cairo_move_to(mCairo, rect.X() + rect.Width() - corners[NS_CORNER_TOP_RIGHT].width, rect.Y()); |
|
1904 |
|
1905 NS_FOR_CSS_CORNERS(i) { |
|
1906 // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw) |
|
1907 mozilla::css::Corner c = mozilla::css::Corner(draw_clockwise ? ((i+1) % 4) : ((4-i) % 4)); |
|
1908 |
|
1909 // i+2 and i+3 respectively. These are used to index into the corner |
|
1910 // multiplier table, and were deduced by calculating out the long form |
|
1911 // of each corner and finding a pattern in the signs and values. |
|
1912 int i2 = (i+2) % 4; |
|
1913 int i3 = (i+3) % 4; |
|
1914 |
|
1915 pc = rect.AtCorner(c); |
|
1916 |
|
1917 if (corners[c].width > 0.0 && corners[c].height > 0.0) { |
|
1918 p0.x = pc.x + cornerMults[i].a * corners[c].width; |
|
1919 p0.y = pc.y + cornerMults[i].b * corners[c].height; |
|
1920 |
|
1921 p3.x = pc.x + cornerMults[i3].a * corners[c].width; |
|
1922 p3.y = pc.y + cornerMults[i3].b * corners[c].height; |
|
1923 |
|
1924 p1.x = p0.x + alpha * cornerMults[i2].a * corners[c].width; |
|
1925 p1.y = p0.y + alpha * cornerMults[i2].b * corners[c].height; |
|
1926 |
|
1927 p2.x = p3.x - alpha * cornerMults[i3].a * corners[c].width; |
|
1928 p2.y = p3.y - alpha * cornerMults[i3].b * corners[c].height; |
|
1929 |
|
1930 cairo_line_to (mCairo, p0.x, p0.y); |
|
1931 cairo_curve_to (mCairo, |
|
1932 p1.x, p1.y, |
|
1933 p2.x, p2.y, |
|
1934 p3.x, p3.y); |
|
1935 } else { |
|
1936 cairo_line_to (mCairo, pc.x, pc.y); |
|
1937 } |
|
1938 } |
|
1939 |
|
1940 cairo_close_path (mCairo); |
|
1941 } else { |
|
1942 EnsurePathBuilder(); |
|
1943 Size radii[] = { ToSize(corners[NS_CORNER_TOP_LEFT]), |
|
1944 ToSize(corners[NS_CORNER_TOP_RIGHT]), |
|
1945 ToSize(corners[NS_CORNER_BOTTOM_RIGHT]), |
|
1946 ToSize(corners[NS_CORNER_BOTTOM_LEFT]) }; |
|
1947 AppendRoundedRectToPath(mPathBuilder, ToRect(rect), radii, draw_clockwise); |
|
1948 } |
|
1949 } |
|
1950 |
|
1951 #ifdef MOZ_DUMP_PAINTING |
|
1952 void |
|
1953 gfxContext::WriteAsPNG(const char* aFile) |
|
1954 { |
|
1955 nsRefPtr<gfxASurface> surf = CurrentSurface(); |
|
1956 if (surf) { |
|
1957 surf->WriteAsPNG(aFile); |
|
1958 } else { |
|
1959 NS_WARNING("No surface found!"); |
|
1960 } |
|
1961 } |
|
1962 |
|
1963 void |
|
1964 gfxContext::DumpAsDataURL() |
|
1965 { |
|
1966 nsRefPtr<gfxASurface> surf = CurrentSurface(); |
|
1967 if (surf) { |
|
1968 surf->DumpAsDataURL(); |
|
1969 } else { |
|
1970 NS_WARNING("No surface found!"); |
|
1971 } |
|
1972 } |
|
1973 |
|
1974 void |
|
1975 gfxContext::CopyAsDataURL() |
|
1976 { |
|
1977 nsRefPtr<gfxASurface> surf = CurrentSurface(); |
|
1978 if (surf) { |
|
1979 surf->CopyAsDataURL(); |
|
1980 } else { |
|
1981 NS_WARNING("No surface found!"); |
|
1982 } |
|
1983 } |
|
1984 #endif |
|
1985 |
|
1986 void |
|
1987 gfxContext::EnsurePath() |
|
1988 { |
|
1989 if (mPathBuilder) { |
|
1990 mPath = mPathBuilder->Finish(); |
|
1991 mPathBuilder = nullptr; |
|
1992 } |
|
1993 |
|
1994 if (mPath) { |
|
1995 if (mTransformChanged) { |
|
1996 Matrix mat = mTransform; |
|
1997 mat.Invert(); |
|
1998 mat = mPathTransform * mat; |
|
1999 mPathBuilder = mPath->TransformedCopyToBuilder(mat, CurrentState().fillRule); |
|
2000 mPath = mPathBuilder->Finish(); |
|
2001 mPathBuilder = nullptr; |
|
2002 |
|
2003 mTransformChanged = false; |
|
2004 } |
|
2005 |
|
2006 if (CurrentState().fillRule == mPath->GetFillRule()) { |
|
2007 return; |
|
2008 } |
|
2009 |
|
2010 mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule); |
|
2011 |
|
2012 mPath = mPathBuilder->Finish(); |
|
2013 mPathBuilder = nullptr; |
|
2014 return; |
|
2015 } |
|
2016 |
|
2017 EnsurePathBuilder(); |
|
2018 mPath = mPathBuilder->Finish(); |
|
2019 mPathBuilder = nullptr; |
|
2020 } |
|
2021 |
|
2022 void |
|
2023 gfxContext::EnsurePathBuilder() |
|
2024 { |
|
2025 if (mPathBuilder && !mTransformChanged) { |
|
2026 return; |
|
2027 } |
|
2028 |
|
2029 if (mPath) { |
|
2030 if (!mTransformChanged) { |
|
2031 mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule); |
|
2032 mPath = nullptr; |
|
2033 } else { |
|
2034 Matrix invTransform = mTransform; |
|
2035 invTransform.Invert(); |
|
2036 Matrix toNewUS = mPathTransform * invTransform; |
|
2037 mPathBuilder = mPath->TransformedCopyToBuilder(toNewUS, CurrentState().fillRule); |
|
2038 } |
|
2039 return; |
|
2040 } |
|
2041 |
|
2042 DebugOnly<PathBuilder*> oldPath = mPathBuilder.get(); |
|
2043 |
|
2044 if (!mPathBuilder) { |
|
2045 mPathBuilder = mDT->CreatePathBuilder(CurrentState().fillRule); |
|
2046 |
|
2047 if (mPathIsRect) { |
|
2048 mPathBuilder->MoveTo(mRect.TopLeft()); |
|
2049 mPathBuilder->LineTo(mRect.TopRight()); |
|
2050 mPathBuilder->LineTo(mRect.BottomRight()); |
|
2051 mPathBuilder->LineTo(mRect.BottomLeft()); |
|
2052 mPathBuilder->Close(); |
|
2053 } |
|
2054 } |
|
2055 |
|
2056 if (mTransformChanged) { |
|
2057 // This could be an else if since this should never happen when |
|
2058 // mPathBuilder is nullptr and mPath is nullptr. But this way we can |
|
2059 // assert if all the state is as expected. |
|
2060 MOZ_ASSERT(oldPath); |
|
2061 MOZ_ASSERT(!mPathIsRect); |
|
2062 |
|
2063 Matrix invTransform = mTransform; |
|
2064 invTransform.Invert(); |
|
2065 Matrix toNewUS = mPathTransform * invTransform; |
|
2066 |
|
2067 RefPtr<Path> path = mPathBuilder->Finish(); |
|
2068 mPathBuilder = path->TransformedCopyToBuilder(toNewUS, CurrentState().fillRule); |
|
2069 } |
|
2070 |
|
2071 mPathIsRect = false; |
|
2072 } |
|
2073 |
|
2074 void |
|
2075 gfxContext::FillAzure(Float aOpacity) |
|
2076 { |
|
2077 AzureState &state = CurrentState(); |
|
2078 |
|
2079 CompositionOp op = GetOp(); |
|
2080 |
|
2081 if (mPathIsRect) { |
|
2082 MOZ_ASSERT(!mTransformChanged); |
|
2083 |
|
2084 if (state.opIsClear) { |
|
2085 mDT->ClearRect(mRect); |
|
2086 } else if (op == CompositionOp::OP_SOURCE) { |
|
2087 // Emulate cairo operator source which is bound by mask! |
|
2088 mDT->ClearRect(mRect); |
|
2089 mDT->FillRect(mRect, GeneralPattern(this), DrawOptions(aOpacity)); |
|
2090 } else { |
|
2091 mDT->FillRect(mRect, GeneralPattern(this), DrawOptions(aOpacity, op, state.aaMode)); |
|
2092 } |
|
2093 } else { |
|
2094 EnsurePath(); |
|
2095 |
|
2096 NS_ASSERTION(!state.opIsClear, "We shouldn't be clearing complex paths!"); |
|
2097 |
|
2098 mDT->Fill(mPath, GeneralPattern(this), DrawOptions(aOpacity, op, state.aaMode)); |
|
2099 } |
|
2100 } |
|
2101 |
|
2102 void |
|
2103 gfxContext::PushClipsToDT(DrawTarget *aDT) |
|
2104 { |
|
2105 // Tricky, we have to restore all clips -since the last time- the clip |
|
2106 // was reset. If we didn't reset the clip, just popping the clips we |
|
2107 // added was fine. |
|
2108 unsigned int lastReset = 0; |
|
2109 for (int i = mStateStack.Length() - 2; i > 0; i--) { |
|
2110 if (mStateStack[i].clipWasReset) { |
|
2111 lastReset = i; |
|
2112 break; |
|
2113 } |
|
2114 } |
|
2115 |
|
2116 // Don't need to save the old transform, we'll be setting a new one soon! |
|
2117 |
|
2118 // Push all clips from the last state on the stack where the clip was |
|
2119 // reset to the clip before ours. |
|
2120 for (unsigned int i = lastReset; i < mStateStack.Length() - 1; i++) { |
|
2121 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { |
|
2122 aDT->SetTransform(mStateStack[i].pushedClips[c].transform * GetDeviceTransform()); |
|
2123 if (mStateStack[i].pushedClips[c].path) { |
|
2124 aDT->PushClip(mStateStack[i].pushedClips[c].path); |
|
2125 } else { |
|
2126 aDT->PushClipRect(mStateStack[i].pushedClips[c].rect); |
|
2127 } |
|
2128 } |
|
2129 } |
|
2130 } |
|
2131 |
|
2132 CompositionOp |
|
2133 gfxContext::GetOp() |
|
2134 { |
|
2135 if (CurrentState().op != CompositionOp::OP_SOURCE) { |
|
2136 return CurrentState().op; |
|
2137 } |
|
2138 |
|
2139 AzureState &state = CurrentState(); |
|
2140 if (state.pattern) { |
|
2141 if (state.pattern->IsOpaque()) { |
|
2142 return CompositionOp::OP_OVER; |
|
2143 } else { |
|
2144 return CompositionOp::OP_SOURCE; |
|
2145 } |
|
2146 } else if (state.sourceSurface) { |
|
2147 if (state.sourceSurface->GetFormat() == SurfaceFormat::B8G8R8X8) { |
|
2148 return CompositionOp::OP_OVER; |
|
2149 } else { |
|
2150 return CompositionOp::OP_SOURCE; |
|
2151 } |
|
2152 } else { |
|
2153 if (state.color.a > 0.999) { |
|
2154 return CompositionOp::OP_OVER; |
|
2155 } else { |
|
2156 return CompositionOp::OP_SOURCE; |
|
2157 } |
|
2158 } |
|
2159 } |
|
2160 |
|
2161 /* SVG font code can change the transform after having set the pattern on the |
|
2162 * context. When the pattern is set it is in user space, if the transform is |
|
2163 * changed after doing so the pattern needs to be converted back into userspace. |
|
2164 * We just store the old pattern transform here so that we only do the work |
|
2165 * needed here if the pattern is actually used. |
|
2166 * We need to avoid doing this when this ChangeTransform comes from a restore, |
|
2167 * since the current pattern and the current transform are both part of the |
|
2168 * state we know the new CurrentState()'s values are valid. But if we assume |
|
2169 * a change they might become invalid since patternTransformChanged is part of |
|
2170 * the state and might be false for the restored AzureState. |
|
2171 */ |
|
2172 void |
|
2173 gfxContext::ChangeTransform(const Matrix &aNewMatrix, bool aUpdatePatternTransform) |
|
2174 { |
|
2175 AzureState &state = CurrentState(); |
|
2176 |
|
2177 if (aUpdatePatternTransform && (state.pattern || state.sourceSurface) |
|
2178 && !state.patternTransformChanged) { |
|
2179 state.patternTransform = GetDTTransform(); |
|
2180 state.patternTransformChanged = true; |
|
2181 } |
|
2182 |
|
2183 if (mPathIsRect) { |
|
2184 Matrix invMatrix = aNewMatrix; |
|
2185 |
|
2186 invMatrix.Invert(); |
|
2187 |
|
2188 Matrix toNewUS = mTransform * invMatrix; |
|
2189 |
|
2190 if (toNewUS.IsRectilinear()) { |
|
2191 mRect = toNewUS.TransformBounds(mRect); |
|
2192 mRect.NudgeToIntegers(); |
|
2193 } else { |
|
2194 mPathBuilder = mDT->CreatePathBuilder(CurrentState().fillRule); |
|
2195 |
|
2196 mPathBuilder->MoveTo(toNewUS * mRect.TopLeft()); |
|
2197 mPathBuilder->LineTo(toNewUS * mRect.TopRight()); |
|
2198 mPathBuilder->LineTo(toNewUS * mRect.BottomRight()); |
|
2199 mPathBuilder->LineTo(toNewUS * mRect.BottomLeft()); |
|
2200 mPathBuilder->Close(); |
|
2201 |
|
2202 mPathIsRect = false; |
|
2203 } |
|
2204 |
|
2205 // No need to consider the transform changed now! |
|
2206 mTransformChanged = false; |
|
2207 } else if ((mPath || mPathBuilder) && !mTransformChanged) { |
|
2208 mTransformChanged = true; |
|
2209 mPathTransform = mTransform; |
|
2210 } |
|
2211 |
|
2212 mTransform = aNewMatrix; |
|
2213 |
|
2214 mDT->SetTransform(GetDTTransform()); |
|
2215 } |
|
2216 |
|
2217 Rect |
|
2218 gfxContext::GetAzureDeviceSpaceClipBounds() |
|
2219 { |
|
2220 unsigned int lastReset = 0; |
|
2221 for (int i = mStateStack.Length() - 1; i > 0; i--) { |
|
2222 if (mStateStack[i].clipWasReset) { |
|
2223 lastReset = i; |
|
2224 break; |
|
2225 } |
|
2226 } |
|
2227 |
|
2228 Rect rect(CurrentState().deviceOffset.x, CurrentState().deviceOffset.y, |
|
2229 Float(mDT->GetSize().width), Float(mDT->GetSize().height)); |
|
2230 for (unsigned int i = lastReset; i < mStateStack.Length(); i++) { |
|
2231 for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { |
|
2232 AzureState::PushedClip &clip = mStateStack[i].pushedClips[c]; |
|
2233 if (clip.path) { |
|
2234 Rect bounds = clip.path->GetBounds(clip.transform); |
|
2235 rect.IntersectRect(rect, bounds); |
|
2236 } else { |
|
2237 rect.IntersectRect(rect, clip.transform.TransformBounds(clip.rect)); |
|
2238 } |
|
2239 } |
|
2240 } |
|
2241 |
|
2242 return rect; |
|
2243 } |
|
2244 |
|
2245 Point |
|
2246 gfxContext::GetDeviceOffset() const |
|
2247 { |
|
2248 return CurrentState().deviceOffset; |
|
2249 } |
|
2250 |
|
2251 Matrix |
|
2252 gfxContext::GetDeviceTransform() const |
|
2253 { |
|
2254 Matrix mat; |
|
2255 mat.Translate(-CurrentState().deviceOffset.x, -CurrentState().deviceOffset.y); |
|
2256 return mat; |
|
2257 } |
|
2258 |
|
2259 Matrix |
|
2260 gfxContext::GetDTTransform() const |
|
2261 { |
|
2262 Matrix mat = mTransform; |
|
2263 mat._31 -= CurrentState().deviceOffset.x; |
|
2264 mat._32 -= CurrentState().deviceOffset.y; |
|
2265 return mat; |
|
2266 } |
|
2267 |
|
2268 void |
|
2269 gfxContext::PushNewDT(gfxContentType content) |
|
2270 { |
|
2271 Rect clipBounds = GetAzureDeviceSpaceClipBounds(); |
|
2272 clipBounds.RoundOut(); |
|
2273 |
|
2274 clipBounds.width = std::max(1.0f, clipBounds.width); |
|
2275 clipBounds.height = std::max(1.0f, clipBounds.height); |
|
2276 |
|
2277 SurfaceFormat format = gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content); |
|
2278 |
|
2279 RefPtr<DrawTarget> newDT = |
|
2280 mDT->CreateSimilarDrawTarget(IntSize(int32_t(clipBounds.width), int32_t(clipBounds.height)), |
|
2281 format); |
|
2282 |
|
2283 if (!newDT) { |
|
2284 NS_WARNING("Failed to create DrawTarget of sufficient size."); |
|
2285 newDT = mDT->CreateSimilarDrawTarget(IntSize(64, 64), format); |
|
2286 |
|
2287 if (!newDT) { |
|
2288 // If even this fails.. we're most likely just out of memory! |
|
2289 NS_ABORT_OOM(BytesPerPixel(format) * 64 * 64); |
|
2290 } |
|
2291 } |
|
2292 |
|
2293 Save(); |
|
2294 |
|
2295 CurrentState().drawTarget = newDT; |
|
2296 CurrentState().deviceOffset = clipBounds.TopLeft(); |
|
2297 |
|
2298 mDT = newDT; |
|
2299 } |
|
2300 |
|
2301 /** |
|
2302 * Work out whether cairo will snap inter-glyph spacing to pixels. |
|
2303 * |
|
2304 * Layout does not align text to pixel boundaries, so, with font drawing |
|
2305 * backends that snap glyph positions to pixels, it is important that |
|
2306 * inter-glyph spacing within words is always an integer number of pixels. |
|
2307 * This ensures that the drawing backend snaps all of the word's glyphs in the |
|
2308 * same direction and so inter-glyph spacing remains the same. |
|
2309 */ |
|
2310 void |
|
2311 gfxContext::GetRoundOffsetsToPixels(bool *aRoundX, bool *aRoundY) |
|
2312 { |
|
2313 *aRoundX = false; |
|
2314 // Could do something fancy here for ScaleFactors of |
|
2315 // AxisAlignedTransforms, but we leave things simple. |
|
2316 // Not much point rounding if a matrix will mess things up anyway. |
|
2317 // Also return false for non-cairo contexts. |
|
2318 if (CurrentMatrix().HasNonTranslation() || mDT) { |
|
2319 *aRoundY = false; |
|
2320 return; |
|
2321 } |
|
2322 |
|
2323 // All raster backends snap glyphs to pixels vertically. |
|
2324 // Print backends set CAIRO_HINT_METRICS_OFF. |
|
2325 *aRoundY = true; |
|
2326 |
|
2327 cairo_t *cr = GetCairo(); |
|
2328 cairo_scaled_font_t *scaled_font = cairo_get_scaled_font(cr); |
|
2329 // Sometimes hint metrics gets set for us, most notably for printing. |
|
2330 cairo_font_options_t *font_options = cairo_font_options_create(); |
|
2331 cairo_scaled_font_get_font_options(scaled_font, font_options); |
|
2332 cairo_hint_metrics_t hint_metrics = |
|
2333 cairo_font_options_get_hint_metrics(font_options); |
|
2334 cairo_font_options_destroy(font_options); |
|
2335 |
|
2336 switch (hint_metrics) { |
|
2337 case CAIRO_HINT_METRICS_OFF: |
|
2338 *aRoundY = false; |
|
2339 return; |
|
2340 case CAIRO_HINT_METRICS_DEFAULT: |
|
2341 // Here we mimic what cairo surface/font backends do. Printing |
|
2342 // surfaces have already been handled by hint_metrics. The |
|
2343 // fallback show_glyphs implementation composites pixel-aligned |
|
2344 // glyph surfaces, so we just pick surface/font combinations that |
|
2345 // override this. |
|
2346 switch (cairo_scaled_font_get_type(scaled_font)) { |
|
2347 #if CAIRO_HAS_DWRITE_FONT // dwrite backend is not in std cairo releases yet |
|
2348 case CAIRO_FONT_TYPE_DWRITE: |
|
2349 // show_glyphs is implemented on the font and so is used for |
|
2350 // all surface types; however, it may pixel-snap depending on |
|
2351 // the dwrite rendering mode |
|
2352 if (!cairo_dwrite_scaled_font_get_force_GDI_classic(scaled_font) && |
|
2353 gfxWindowsPlatform::GetPlatform()->DWriteMeasuringMode() == |
|
2354 DWRITE_MEASURING_MODE_NATURAL) { |
|
2355 return; |
|
2356 } |
|
2357 #endif |
|
2358 case CAIRO_FONT_TYPE_QUARTZ: |
|
2359 // Quartz surfaces implement show_glyphs for Quartz fonts |
|
2360 if (cairo_surface_get_type(cairo_get_target(cr)) == |
|
2361 CAIRO_SURFACE_TYPE_QUARTZ) { |
|
2362 return; |
|
2363 } |
|
2364 default: |
|
2365 break; |
|
2366 } |
|
2367 // fall through: |
|
2368 case CAIRO_HINT_METRICS_ON: |
|
2369 break; |
|
2370 } |
|
2371 *aRoundX = true; |
|
2372 return; |
|
2373 } |