/src/mozilla-central/gfx/thebes/gfxContext.cpp
Line | Count | Source (jump to first uncovered line) |
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/. */ |
6 | | |
7 | | #include <math.h> |
8 | | |
9 | | #include "mozilla/Alignment.h" |
10 | | |
11 | | #include "cairo.h" |
12 | | |
13 | | #include "gfxContext.h" |
14 | | |
15 | | #include "gfxMatrix.h" |
16 | | #include "gfxUtils.h" |
17 | | #include "gfxASurface.h" |
18 | | #include "gfxPattern.h" |
19 | | #include "gfxPlatform.h" |
20 | | #include "gfxPrefs.h" |
21 | | #include "GeckoProfiler.h" |
22 | | #include "gfx2DGlue.h" |
23 | | #include "mozilla/gfx/PathHelpers.h" |
24 | | #include "mozilla/gfx/DrawTargetTiled.h" |
25 | | #include <algorithm> |
26 | | #include "TextDrawTarget.h" |
27 | | |
28 | | #if XP_WIN |
29 | | #include "gfxWindowsPlatform.h" |
30 | | #include "mozilla/gfx/DeviceManagerDx.h" |
31 | | #endif |
32 | | |
33 | | using namespace mozilla; |
34 | | using namespace mozilla::gfx; |
35 | | |
36 | | UserDataKey gfxContext::sDontUseAsSourceKey; |
37 | | |
38 | | #ifdef DEBUG |
39 | | #define CURRENTSTATE_CHANGED() \ |
40 | | CurrentState().mContentChanged = true; |
41 | | #else |
42 | | #define CURRENTSTATE_CHANGED() |
43 | | #endif |
44 | | |
45 | | PatternFromState::operator mozilla::gfx::Pattern&() |
46 | 0 | { |
47 | 0 | gfxContext::AzureState &state = mContext->CurrentState(); |
48 | 0 |
|
49 | 0 | if (state.pattern) { |
50 | 0 | return *state.pattern->GetPattern(mContext->mDT, state.patternTransformChanged ? &state.patternTransform : nullptr); |
51 | 0 | } |
52 | 0 |
|
53 | 0 | mPattern = new (mColorPattern.addr()) |
54 | 0 | ColorPattern(state.color); |
55 | 0 | return *mPattern; |
56 | 0 | } |
57 | | |
58 | | |
59 | | gfxContext::gfxContext(DrawTarget *aTarget, const Point& aDeviceOffset) |
60 | | : mPathIsRect(false) |
61 | | , mTransformChanged(false) |
62 | | , mDT(aTarget) |
63 | 0 | { |
64 | 0 | if (!aTarget) { |
65 | 0 | gfxCriticalError() << "Don't create a gfxContext without a DrawTarget"; |
66 | 0 | } |
67 | 0 |
|
68 | 0 | mStateStack.SetLength(1); |
69 | 0 | CurrentState().drawTarget = mDT; |
70 | 0 | CurrentState().deviceOffset = aDeviceOffset; |
71 | 0 | mDT->SetTransform(GetDTTransform()); |
72 | 0 | } |
73 | | |
74 | | /* static */ already_AddRefed<gfxContext> |
75 | | gfxContext::CreateOrNull(DrawTarget* aTarget, |
76 | | const mozilla::gfx::Point& aDeviceOffset) |
77 | 0 | { |
78 | 0 | if (!aTarget || !aTarget->IsValid()) { |
79 | 0 | gfxCriticalNote << "Invalid target in gfxContext::CreateOrNull " << hexa(aTarget); |
80 | 0 | return nullptr; |
81 | 0 | } |
82 | 0 |
|
83 | 0 | RefPtr<gfxContext> result = new gfxContext(aTarget, aDeviceOffset); |
84 | 0 | return result.forget(); |
85 | 0 | } |
86 | | |
87 | | /* static */ already_AddRefed<gfxContext> |
88 | | gfxContext::CreatePreservingTransformOrNull(DrawTarget* aTarget) |
89 | 0 | { |
90 | 0 | if (!aTarget || !aTarget->IsValid()) { |
91 | 0 | gfxCriticalNote << "Invalid target in gfxContext::CreatePreservingTransformOrNull " << hexa(aTarget); |
92 | 0 | return nullptr; |
93 | 0 | } |
94 | 0 |
|
95 | 0 | Matrix transform = aTarget->GetTransform(); |
96 | 0 | RefPtr<gfxContext> result = new gfxContext(aTarget); |
97 | 0 | result->SetMatrix(transform); |
98 | 0 | return result.forget(); |
99 | 0 | } |
100 | | |
101 | | gfxContext::~gfxContext() |
102 | 0 | { |
103 | 0 | for (int i = mStateStack.Length() - 1; i >= 0; i--) { |
104 | 0 | for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { |
105 | 0 | mStateStack[i].drawTarget->PopClip(); |
106 | 0 | } |
107 | 0 | } |
108 | 0 | } |
109 | | |
110 | | mozilla::layout::TextDrawTarget* |
111 | | gfxContext::GetTextDrawer() |
112 | 0 | { |
113 | 0 | if (mDT->GetBackendType() == BackendType::WEBRENDER_TEXT) { |
114 | 0 | return static_cast<mozilla::layout::TextDrawTarget*>(&*mDT); |
115 | 0 | } |
116 | 0 | return nullptr; |
117 | 0 | } |
118 | | |
119 | | void |
120 | | gfxContext::Save() |
121 | 0 | { |
122 | 0 | CurrentState().transform = mTransform; |
123 | 0 | mStateStack.AppendElement(AzureState(CurrentState())); |
124 | 0 | CurrentState().pushedClips.Clear(); |
125 | | #ifdef DEBUG |
126 | | CurrentState().mContentChanged = false; |
127 | | #endif |
128 | | } |
129 | | |
130 | | void |
131 | | gfxContext::Restore() |
132 | 0 | { |
133 | | #ifdef DEBUG |
134 | | // gfxContext::Restore is used to restore AzureState. We need to restore it |
135 | | // only if it was altered. The following APIs do change the content of |
136 | | // AzureState, a user should save the state before using them and restore it |
137 | | // after finishing painting: |
138 | | // 1. APIs to setup how to paint, such as SetColor()/SetAntialiasMode(). All |
139 | | // gfxContext SetXXXX public functions belong to this category, except |
140 | | // gfxContext::SetPath & gfxContext::SetMatrix. |
141 | | // 2. Clip functions, such as Clip() or PopClip(). You may call PopClip() |
142 | | // directly instead of using gfxContext::Save if the clip region is the |
143 | | // only thing that you altered in the target context. |
144 | | // 3. Function of setup transform matrix, such as Multiply() and |
145 | | // SetMatrix(). Using gfxContextMatrixAutoSaveRestore is more recommended |
146 | | // if transform data is the only thing that you are going to alter. |
147 | | // |
148 | | // You will hit the assertion message below if there is no above functions |
149 | | // been used between a pair of gfxContext::Save and gfxContext::Restore. |
150 | | // Considerate to remove that pair of Save/Restore if hitting that assertion. |
151 | | // |
152 | | // In the other hand, the following APIs do not alter the content of the |
153 | | // current AzureState, therefore, there is no need to save & restore |
154 | | // AzureState: |
155 | | // 1. constant member functions of gfxContext. |
156 | | // 2. Paint calls, such as Line()/Rectangle()/Fill(). Those APIs change the |
157 | | // content of drawing buffer, which is not part of AzureState. |
158 | | // 3. Path building APIs, such as SetPath()/MoveTo()/LineTo()/NewPath(). |
159 | | // Surprisingly, path information is not stored in AzureState either. |
160 | | // Save current AzureState before using these type of APIs does nothing but |
161 | | // make performance worse. |
162 | | NS_ASSERTION(CurrentState().mContentChanged || |
163 | | CurrentState().pushedClips.Length() > 0, |
164 | | "The context of the current AzureState is not altered after " |
165 | | "Save() been called. you may consider to remove this pair of " |
166 | | "gfxContext::Save/Restore."); |
167 | | #endif |
168 | |
|
169 | 0 | for (unsigned int c = 0; c < CurrentState().pushedClips.Length(); c++) { |
170 | 0 | mDT->PopClip(); |
171 | 0 | } |
172 | 0 |
|
173 | 0 | mStateStack.RemoveLastElement(); |
174 | 0 |
|
175 | 0 | mDT = CurrentState().drawTarget; |
176 | 0 |
|
177 | 0 | ChangeTransform(CurrentState().transform, false); |
178 | 0 | } |
179 | | |
180 | | // drawing |
181 | | void |
182 | | gfxContext::NewPath() |
183 | 0 | { |
184 | 0 | mPath = nullptr; |
185 | 0 | mPathBuilder = nullptr; |
186 | 0 | mPathIsRect = false; |
187 | 0 | mTransformChanged = false; |
188 | 0 | } |
189 | | |
190 | | void |
191 | | gfxContext::ClosePath() |
192 | 0 | { |
193 | 0 | EnsurePathBuilder(); |
194 | 0 | mPathBuilder->Close(); |
195 | 0 | } |
196 | | |
197 | | already_AddRefed<Path> gfxContext::GetPath() |
198 | 0 | { |
199 | 0 | EnsurePath(); |
200 | 0 | RefPtr<Path> path(mPath); |
201 | 0 | return path.forget(); |
202 | 0 | } |
203 | | |
204 | | void gfxContext::SetPath(Path* path) |
205 | 0 | { |
206 | 0 | MOZ_ASSERT(path->GetBackendType() == mDT->GetBackendType() || |
207 | 0 | path->GetBackendType() == BackendType::RECORDING || |
208 | 0 | (mDT->GetBackendType() == BackendType::DIRECT2D1_1 && path->GetBackendType() == BackendType::DIRECT2D)); |
209 | 0 | mPath = path; |
210 | 0 | mPathBuilder = nullptr; |
211 | 0 | mPathIsRect = false; |
212 | 0 | mTransformChanged = false; |
213 | 0 | } |
214 | | |
215 | | void |
216 | | gfxContext::Fill() |
217 | 0 | { |
218 | 0 | Fill(PatternFromState(this)); |
219 | 0 | } |
220 | | |
221 | | void |
222 | | gfxContext::Fill(const Pattern& aPattern) |
223 | 0 | { |
224 | 0 | AUTO_PROFILER_LABEL("gfxContext::Fill", GRAPHICS); |
225 | 0 | AzureState &state = CurrentState(); |
226 | 0 |
|
227 | 0 | CompositionOp op = GetOp(); |
228 | 0 |
|
229 | 0 | if (mPathIsRect) { |
230 | 0 | MOZ_ASSERT(!mTransformChanged); |
231 | 0 |
|
232 | 0 | if (op == CompositionOp::OP_SOURCE) { |
233 | 0 | // Emulate cairo operator source which is bound by mask! |
234 | 0 | mDT->ClearRect(mRect); |
235 | 0 | mDT->FillRect(mRect, aPattern, DrawOptions(1.0f)); |
236 | 0 | } else { |
237 | 0 | mDT->FillRect(mRect, aPattern, DrawOptions(1.0f, op, state.aaMode)); |
238 | 0 | } |
239 | 0 | } else { |
240 | 0 | EnsurePath(); |
241 | 0 | mDT->Fill(mPath, aPattern, DrawOptions(1.0f, op, state.aaMode)); |
242 | 0 | } |
243 | 0 | } |
244 | | |
245 | | void |
246 | | gfxContext::MoveTo(const gfxPoint& pt) |
247 | 0 | { |
248 | 0 | EnsurePathBuilder(); |
249 | 0 | mPathBuilder->MoveTo(ToPoint(pt)); |
250 | 0 | } |
251 | | |
252 | | void |
253 | | gfxContext::LineTo(const gfxPoint& pt) |
254 | 0 | { |
255 | 0 | EnsurePathBuilder(); |
256 | 0 | mPathBuilder->LineTo(ToPoint(pt)); |
257 | 0 | } |
258 | | |
259 | | void |
260 | | gfxContext::Line(const gfxPoint& start, const gfxPoint& end) |
261 | 0 | { |
262 | 0 | EnsurePathBuilder(); |
263 | 0 | mPathBuilder->MoveTo(ToPoint(start)); |
264 | 0 | mPathBuilder->LineTo(ToPoint(end)); |
265 | 0 | } |
266 | | |
267 | | // XXX snapToPixels is only valid when snapping for filled |
268 | | // rectangles and for even-width stroked rectangles. |
269 | | // For odd-width stroked rectangles, we need to offset x/y by |
270 | | // 0.5... |
271 | | void |
272 | | gfxContext::Rectangle(const gfxRect& rect, bool snapToPixels) |
273 | 0 | { |
274 | 0 | Rect rec = ToRect(rect); |
275 | 0 |
|
276 | 0 | if (snapToPixels) { |
277 | 0 | gfxRect newRect(rect); |
278 | 0 | if (UserToDevicePixelSnapped(newRect, true)) { |
279 | 0 | gfxMatrix mat = ThebesMatrix(mTransform); |
280 | 0 | if (mat.Invert()) { |
281 | 0 | // We need the user space rect. |
282 | 0 | rec = ToRect(mat.TransformBounds(newRect)); |
283 | 0 | } else { |
284 | 0 | rec = Rect(); |
285 | 0 | } |
286 | 0 | } |
287 | 0 | } |
288 | 0 |
|
289 | 0 | if (!mPathBuilder && !mPathIsRect) { |
290 | 0 | mPathIsRect = true; |
291 | 0 | mRect = rec; |
292 | 0 | return; |
293 | 0 | } |
294 | 0 | |
295 | 0 | EnsurePathBuilder(); |
296 | 0 |
|
297 | 0 | mPathBuilder->MoveTo(rec.TopLeft()); |
298 | 0 | mPathBuilder->LineTo(rec.TopRight()); |
299 | 0 | mPathBuilder->LineTo(rec.BottomRight()); |
300 | 0 | mPathBuilder->LineTo(rec.BottomLeft()); |
301 | 0 | mPathBuilder->Close(); |
302 | 0 | } |
303 | | |
304 | | // transform stuff |
305 | | void |
306 | | gfxContext::Multiply(const gfxMatrix& matrix) |
307 | 0 | { |
308 | 0 | CURRENTSTATE_CHANGED() |
309 | 0 | ChangeTransform(ToMatrix(matrix) * mTransform); |
310 | 0 | } |
311 | | |
312 | | void |
313 | | gfxContext::SetMatrix(const gfx::Matrix& matrix) |
314 | 0 | { |
315 | 0 | CURRENTSTATE_CHANGED() |
316 | 0 | ChangeTransform(matrix); |
317 | 0 | } |
318 | | |
319 | | void |
320 | | gfxContext::SetMatrixDouble(const gfxMatrix& matrix) |
321 | 0 | { |
322 | 0 | SetMatrix(ToMatrix(matrix)); |
323 | 0 | } |
324 | | |
325 | | gfx::Matrix |
326 | | gfxContext::CurrentMatrix() const |
327 | 0 | { |
328 | 0 | return mTransform; |
329 | 0 | } |
330 | | |
331 | | gfxMatrix |
332 | | gfxContext::CurrentMatrixDouble() const |
333 | 0 | { |
334 | 0 | return ThebesMatrix(CurrentMatrix()); |
335 | 0 | } |
336 | | |
337 | | gfxPoint |
338 | | gfxContext::DeviceToUser(const gfxPoint& point) const |
339 | 0 | { |
340 | 0 | return ThebesPoint(mTransform.Inverse().TransformPoint(ToPoint(point))); |
341 | 0 | } |
342 | | |
343 | | Size |
344 | | gfxContext::DeviceToUser(const Size& size) const |
345 | 0 | { |
346 | 0 | return mTransform.Inverse().TransformSize(size); |
347 | 0 | } |
348 | | |
349 | | gfxRect |
350 | | gfxContext::DeviceToUser(const gfxRect& rect) const |
351 | 0 | { |
352 | 0 | return ThebesRect(mTransform.Inverse().TransformBounds(ToRect(rect))); |
353 | 0 | } |
354 | | |
355 | | gfxPoint |
356 | | gfxContext::UserToDevice(const gfxPoint& point) const |
357 | 0 | { |
358 | 0 | return ThebesPoint(mTransform.TransformPoint(ToPoint(point))); |
359 | 0 | } |
360 | | |
361 | | Size |
362 | | gfxContext::UserToDevice(const Size& size) const |
363 | 0 | { |
364 | 0 | const Matrix &matrix = mTransform; |
365 | 0 |
|
366 | 0 | Size newSize; |
367 | 0 | newSize.width = size.width * matrix._11 + size.height * matrix._12; |
368 | 0 | newSize.height = size.width * matrix._21 + size.height * matrix._22; |
369 | 0 | return newSize; |
370 | 0 | } |
371 | | |
372 | | gfxRect |
373 | | gfxContext::UserToDevice(const gfxRect& rect) const |
374 | 0 | { |
375 | 0 | const Matrix &matrix = mTransform; |
376 | 0 | return ThebesRect(matrix.TransformBounds(ToRect(rect))); |
377 | 0 | } |
378 | | |
379 | | bool |
380 | | gfxContext::UserToDevicePixelSnapped(gfxRect& rect, bool ignoreScale) const |
381 | 0 | { |
382 | 0 | if (mDT->GetUserData(&sDisablePixelSnapping)) |
383 | 0 | return false; |
384 | 0 | |
385 | 0 | // if we're not at 1.0 scale, don't snap, unless we're |
386 | 0 | // ignoring the scale. If we're not -just- a scale, |
387 | 0 | // never snap. |
388 | 0 | const gfxFloat epsilon = 0.0000001; |
389 | 0 | #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon) |
390 | 0 | Matrix mat = mTransform; |
391 | 0 | if (!ignoreScale && |
392 | 0 | (!WITHIN_E(mat._11,1.0) || !WITHIN_E(mat._22,1.0) || |
393 | 0 | !WITHIN_E(mat._12,0.0) || !WITHIN_E(mat._21,0.0))) |
394 | 0 | return false; |
395 | 0 | #undef WITHIN_E |
396 | 0 | |
397 | 0 | gfxPoint p1 = UserToDevice(rect.TopLeft()); |
398 | 0 | gfxPoint p2 = UserToDevice(rect.TopRight()); |
399 | 0 | gfxPoint p3 = UserToDevice(rect.BottomRight()); |
400 | 0 |
|
401 | 0 | // Check that the rectangle is axis-aligned. For an axis-aligned rectangle, |
402 | 0 | // two opposite corners define the entire rectangle. So check if |
403 | 0 | // the axis-aligned rectangle with opposite corners p1 and p3 |
404 | 0 | // define an axis-aligned rectangle whose other corners are p2 and p4. |
405 | 0 | // We actually only need to check one of p2 and p4, since an affine |
406 | 0 | // transform maps parallelograms to parallelograms. |
407 | 0 | if (p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y)) { |
408 | 0 | p1.Round(); |
409 | 0 | p3.Round(); |
410 | 0 |
|
411 | 0 | rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y))); |
412 | 0 | rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(), |
413 | 0 | std::max(p1.y, p3.y) - rect.Y())); |
414 | 0 | return true; |
415 | 0 | } |
416 | 0 | |
417 | 0 | return false; |
418 | 0 | } |
419 | | |
420 | | bool |
421 | | gfxContext::UserToDevicePixelSnapped(gfxPoint& pt, bool ignoreScale) const |
422 | 0 | { |
423 | 0 | if (mDT->GetUserData(&sDisablePixelSnapping)) |
424 | 0 | return false; |
425 | 0 | |
426 | 0 | // if we're not at 1.0 scale, don't snap, unless we're |
427 | 0 | // ignoring the scale. If we're not -just- a scale, |
428 | 0 | // never snap. |
429 | 0 | const gfxFloat epsilon = 0.0000001; |
430 | 0 | #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon) |
431 | 0 | Matrix mat = mTransform; |
432 | 0 | if (!ignoreScale && |
433 | 0 | (!WITHIN_E(mat._11,1.0) || !WITHIN_E(mat._22,1.0) || |
434 | 0 | !WITHIN_E(mat._12,0.0) || !WITHIN_E(mat._21,0.0))) |
435 | 0 | return false; |
436 | 0 | #undef WITHIN_E |
437 | 0 | |
438 | 0 | pt = UserToDevice(pt); |
439 | 0 | pt.Round(); |
440 | 0 | return true; |
441 | 0 | } |
442 | | |
443 | | void |
444 | | gfxContext::SetAntialiasMode(AntialiasMode mode) |
445 | 0 | { |
446 | 0 | CURRENTSTATE_CHANGED() |
447 | 0 | CurrentState().aaMode = mode; |
448 | 0 | } |
449 | | |
450 | | AntialiasMode |
451 | | gfxContext::CurrentAntialiasMode() const |
452 | 0 | { |
453 | 0 | return CurrentState().aaMode; |
454 | 0 | } |
455 | | |
456 | | void |
457 | | gfxContext::SetDash(const Float *dashes, int ndash, Float offset) |
458 | 0 | { |
459 | 0 | CURRENTSTATE_CHANGED() |
460 | 0 | AzureState &state = CurrentState(); |
461 | 0 |
|
462 | 0 | state.dashPattern.SetLength(ndash); |
463 | 0 | for (int i = 0; i < ndash; i++) { |
464 | 0 | state.dashPattern[i] = dashes[i]; |
465 | 0 | } |
466 | 0 | state.strokeOptions.mDashLength = ndash; |
467 | 0 | state.strokeOptions.mDashOffset = offset; |
468 | 0 | state.strokeOptions.mDashPattern = ndash ? state.dashPattern.Elements() |
469 | 0 | : nullptr; |
470 | 0 | } |
471 | | |
472 | | bool |
473 | | gfxContext::CurrentDash(FallibleTArray<Float>& dashes, Float* offset) const |
474 | 0 | { |
475 | 0 | const AzureState &state = CurrentState(); |
476 | 0 | int count = state.strokeOptions.mDashLength; |
477 | 0 |
|
478 | 0 | if (count <= 0 || !dashes.SetLength(count, fallible)) { |
479 | 0 | return false; |
480 | 0 | } |
481 | 0 | |
482 | 0 | dashes = state.dashPattern; |
483 | 0 |
|
484 | 0 | *offset = state.strokeOptions.mDashOffset; |
485 | 0 |
|
486 | 0 | return true; |
487 | 0 | } |
488 | | |
489 | | Float |
490 | | gfxContext::CurrentDashOffset() const |
491 | 0 | { |
492 | 0 | return CurrentState().strokeOptions.mDashOffset; |
493 | 0 | } |
494 | | |
495 | | void |
496 | | gfxContext::SetLineWidth(Float width) |
497 | 0 | { |
498 | 0 | CurrentState().strokeOptions.mLineWidth = width; |
499 | 0 | } |
500 | | |
501 | | Float |
502 | | gfxContext::CurrentLineWidth() const |
503 | 0 | { |
504 | 0 | return CurrentState().strokeOptions.mLineWidth; |
505 | 0 | } |
506 | | |
507 | | void |
508 | | gfxContext::SetOp(CompositionOp aOp) |
509 | 0 | { |
510 | 0 | CURRENTSTATE_CHANGED() |
511 | 0 | CurrentState().op = aOp; |
512 | 0 | } |
513 | | |
514 | | CompositionOp |
515 | | gfxContext::CurrentOp() const |
516 | 0 | { |
517 | 0 | return CurrentState().op; |
518 | 0 | } |
519 | | |
520 | | void |
521 | | gfxContext::SetLineCap(CapStyle cap) |
522 | 0 | { |
523 | 0 | CURRENTSTATE_CHANGED() |
524 | 0 | CurrentState().strokeOptions.mLineCap = cap; |
525 | 0 | } |
526 | | |
527 | | CapStyle |
528 | | gfxContext::CurrentLineCap() const |
529 | 0 | { |
530 | 0 | return CurrentState().strokeOptions.mLineCap; |
531 | 0 | } |
532 | | |
533 | | void |
534 | | gfxContext::SetLineJoin(JoinStyle join) |
535 | 0 | { |
536 | 0 | CURRENTSTATE_CHANGED() |
537 | 0 | CurrentState().strokeOptions.mLineJoin = join; |
538 | 0 | } |
539 | | |
540 | | JoinStyle |
541 | | gfxContext::CurrentLineJoin() const |
542 | 0 | { |
543 | 0 | return CurrentState().strokeOptions.mLineJoin; |
544 | 0 | } |
545 | | |
546 | | void |
547 | | gfxContext::SetMiterLimit(Float limit) |
548 | 0 | { |
549 | 0 | CURRENTSTATE_CHANGED() |
550 | 0 | CurrentState().strokeOptions.mMiterLimit = limit; |
551 | 0 | } |
552 | | |
553 | | Float |
554 | | gfxContext::CurrentMiterLimit() const |
555 | 0 | { |
556 | 0 | return CurrentState().strokeOptions.mMiterLimit; |
557 | 0 | } |
558 | | |
559 | | // clipping |
560 | | void |
561 | | gfxContext::Clip(const Rect& rect) |
562 | 0 | { |
563 | 0 | AzureState::PushedClip clip = { nullptr, rect, mTransform }; |
564 | 0 | CurrentState().pushedClips.AppendElement(clip); |
565 | 0 | mDT->PushClipRect(rect); |
566 | 0 | NewPath(); |
567 | 0 | } |
568 | | |
569 | | void |
570 | | gfxContext::Clip(const gfxRect& rect) |
571 | 0 | { |
572 | 0 | Clip(ToRect(rect)); |
573 | 0 | } |
574 | | |
575 | | void |
576 | | gfxContext::Clip(Path* aPath) |
577 | 0 | { |
578 | 0 | mDT->PushClip(aPath); |
579 | 0 | AzureState::PushedClip clip = { aPath, Rect(), mTransform }; |
580 | 0 | CurrentState().pushedClips.AppendElement(clip); |
581 | 0 | } |
582 | | |
583 | | void |
584 | | gfxContext::Clip() |
585 | 0 | { |
586 | 0 | if (mPathIsRect) { |
587 | 0 | MOZ_ASSERT(!mTransformChanged); |
588 | 0 |
|
589 | 0 | AzureState::PushedClip clip = { nullptr, mRect, mTransform }; |
590 | 0 | CurrentState().pushedClips.AppendElement(clip); |
591 | 0 | mDT->PushClipRect(mRect); |
592 | 0 | } else { |
593 | 0 | EnsurePath(); |
594 | 0 | mDT->PushClip(mPath); |
595 | 0 | AzureState::PushedClip clip = { mPath, Rect(), mTransform }; |
596 | 0 | CurrentState().pushedClips.AppendElement(clip); |
597 | 0 | } |
598 | 0 | } |
599 | | |
600 | | void |
601 | | gfxContext::PopClip() |
602 | 0 | { |
603 | 0 | MOZ_ASSERT(CurrentState().pushedClips.Length() > 0); |
604 | 0 |
|
605 | 0 | CurrentState().pushedClips.RemoveLastElement(); |
606 | 0 | mDT->PopClip(); |
607 | 0 | } |
608 | | |
609 | | gfxRect |
610 | | gfxContext::GetClipExtents(ClipExtentsSpace aSpace) const |
611 | 0 | { |
612 | 0 | Rect rect = GetAzureDeviceSpaceClipBounds(); |
613 | 0 |
|
614 | 0 | if (rect.IsZeroArea()) { |
615 | 0 | return gfxRect(0, 0, 0, 0); |
616 | 0 | } |
617 | 0 | |
618 | 0 | if (aSpace == eUserSpace) { |
619 | 0 | Matrix mat = mTransform; |
620 | 0 | mat.Invert(); |
621 | 0 | rect = mat.TransformBounds(rect); |
622 | 0 | } |
623 | 0 |
|
624 | 0 | return ThebesRect(rect); |
625 | 0 | } |
626 | | |
627 | | bool |
628 | | gfxContext::ExportClip(ClipExporter& aExporter) |
629 | 0 | { |
630 | 0 | for (unsigned int i = 0; i < mStateStack.Length(); i++) { |
631 | 0 | for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { |
632 | 0 | AzureState::PushedClip &clip = mStateStack[i].pushedClips[c]; |
633 | 0 | gfx::Matrix transform = clip.transform; |
634 | 0 | transform.PostTranslate(-GetDeviceOffset()); |
635 | 0 |
|
636 | 0 | aExporter.BeginClip(transform); |
637 | 0 | if (clip.path) { |
638 | 0 | clip.path->StreamToSink(&aExporter); |
639 | 0 | } else { |
640 | 0 | aExporter.MoveTo(clip.rect.TopLeft()); |
641 | 0 | aExporter.LineTo(clip.rect.TopRight()); |
642 | 0 | aExporter.LineTo(clip.rect.BottomRight()); |
643 | 0 | aExporter.LineTo(clip.rect.BottomLeft()); |
644 | 0 | aExporter.Close(); |
645 | 0 | } |
646 | 0 | aExporter.EndClip(); |
647 | 0 | } |
648 | 0 | } |
649 | 0 |
|
650 | 0 | return true; |
651 | 0 | } |
652 | | |
653 | | bool |
654 | | gfxContext::ClipContainsRect(const gfxRect& aRect) |
655 | 0 | { |
656 | 0 | // Since we always return false when the clip list contains a |
657 | 0 | // non-rectangular clip or a non-rectilinear transform, our 'total' clip |
658 | 0 | // is always a rectangle if we hit the end of this function. |
659 | 0 | Rect clipBounds(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height)); |
660 | 0 |
|
661 | 0 | for (unsigned int i = 0; i < mStateStack.Length(); i++) { |
662 | 0 | for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { |
663 | 0 | AzureState::PushedClip &clip = mStateStack[i].pushedClips[c]; |
664 | 0 | if (clip.path || !clip.transform.IsRectilinear()) { |
665 | 0 | // Cairo behavior is we return false if the clip contains a non- |
666 | 0 | // rectangle. |
667 | 0 | return false; |
668 | 0 | } else { |
669 | 0 | Rect clipRect = mTransform.TransformBounds(clip.rect); |
670 | 0 |
|
671 | 0 | clipBounds.IntersectRect(clipBounds, clipRect); |
672 | 0 | } |
673 | 0 | } |
674 | 0 | } |
675 | 0 |
|
676 | 0 | return clipBounds.Contains(ToRect(aRect)); |
677 | 0 | } |
678 | | |
679 | | // rendering sources |
680 | | |
681 | | void |
682 | | gfxContext::SetColor(const Color& aColor) |
683 | 0 | { |
684 | 0 | CURRENTSTATE_CHANGED() |
685 | 0 | CurrentState().pattern = nullptr; |
686 | 0 | CurrentState().color = ToDeviceColor(aColor); |
687 | 0 | } |
688 | | |
689 | | void |
690 | | gfxContext::SetDeviceColor(const Color& aColor) |
691 | 0 | { |
692 | 0 | CURRENTSTATE_CHANGED() |
693 | 0 | CurrentState().pattern = nullptr; |
694 | 0 | CurrentState().color = aColor; |
695 | 0 | } |
696 | | |
697 | | bool |
698 | | gfxContext::GetDeviceColor(Color& aColorOut) |
699 | 0 | { |
700 | 0 | if (CurrentState().pattern) { |
701 | 0 | return CurrentState().pattern->GetSolidColor(aColorOut); |
702 | 0 | } |
703 | 0 | |
704 | 0 | aColorOut = CurrentState().color; |
705 | 0 | return true; |
706 | 0 | } |
707 | | |
708 | | void |
709 | | gfxContext::SetPattern(gfxPattern *pattern) |
710 | 0 | { |
711 | 0 | CURRENTSTATE_CHANGED() |
712 | 0 | CurrentState().patternTransformChanged = false; |
713 | 0 | CurrentState().pattern = pattern; |
714 | 0 | } |
715 | | |
716 | | already_AddRefed<gfxPattern> |
717 | | gfxContext::GetPattern() |
718 | 0 | { |
719 | 0 | RefPtr<gfxPattern> pat; |
720 | 0 |
|
721 | 0 | AzureState &state = CurrentState(); |
722 | 0 | if (state.pattern) { |
723 | 0 | pat = state.pattern; |
724 | 0 | } else { |
725 | 0 | pat = new gfxPattern(state.color); |
726 | 0 | } |
727 | 0 | return pat.forget(); |
728 | 0 | } |
729 | | |
730 | | // masking |
731 | | void |
732 | | gfxContext::Mask(SourceSurface* aSurface, Float aAlpha, const Matrix& aTransform) |
733 | 0 | { |
734 | 0 | Matrix old = mTransform; |
735 | 0 | Matrix mat = aTransform * mTransform; |
736 | 0 |
|
737 | 0 | ChangeTransform(mat); |
738 | 0 | mDT->MaskSurface(PatternFromState(this), aSurface, Point(), |
739 | 0 | DrawOptions(aAlpha, CurrentState().op, CurrentState().aaMode)); |
740 | 0 | ChangeTransform(old); |
741 | 0 | } |
742 | | |
743 | | void |
744 | | gfxContext::Mask(SourceSurface *surface, float alpha, const Point& offset) |
745 | 0 | { |
746 | 0 | // We clip here to bind to the mask surface bounds, see above. |
747 | 0 | mDT->MaskSurface(PatternFromState(this), |
748 | 0 | surface, |
749 | 0 | offset, |
750 | 0 | DrawOptions(alpha, CurrentState().op, CurrentState().aaMode)); |
751 | 0 | } |
752 | | |
753 | | void |
754 | | gfxContext::Paint(Float alpha) |
755 | 0 | { |
756 | 0 | AUTO_PROFILER_LABEL("gfxContext::Paint", GRAPHICS); |
757 | 0 |
|
758 | 0 | Matrix mat = mDT->GetTransform(); |
759 | 0 | mat.Invert(); |
760 | 0 | Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize()))); |
761 | 0 |
|
762 | 0 | mDT->FillRect(paintRect, PatternFromState(this), |
763 | 0 | DrawOptions(alpha, GetOp())); |
764 | 0 | } |
765 | | |
766 | | void |
767 | | gfxContext::PushGroupForBlendBack(gfxContentType content, Float aOpacity, SourceSurface* aMask, const Matrix& aMaskTransform) |
768 | 0 | { |
769 | 0 | mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, aMaskTransform); |
770 | 0 | } |
771 | | |
772 | | void |
773 | | gfxContext::PushGroupAndCopyBackground(gfxContentType content, Float aOpacity, SourceSurface* aMask, const Matrix& aMaskTransform) |
774 | 0 | { |
775 | 0 | IntRect clipExtents; |
776 | 0 | if (mDT->GetFormat() != SurfaceFormat::B8G8R8X8) { |
777 | 0 | gfxRect clipRect = GetClipExtents(gfxContext::eDeviceSpace); |
778 | 0 | clipRect.RoundOut(); |
779 | 0 | clipExtents = IntRect::Truncate(clipRect.X(), clipRect.Y(), clipRect.Width(), clipRect.Height()); |
780 | 0 | } |
781 | 0 | bool pushOpaqueWithCopiedBG = (mDT->GetFormat() == SurfaceFormat::B8G8R8X8 || |
782 | 0 | mDT->GetOpaqueRect().Contains(clipExtents)) && |
783 | 0 | !mDT->GetUserData(&sDontUseAsSourceKey); |
784 | 0 |
|
785 | 0 | if (pushOpaqueWithCopiedBG) { |
786 | 0 | mDT->PushLayer(true, aOpacity, aMask, aMaskTransform, IntRect(), true); |
787 | 0 | } else { |
788 | 0 | mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, aMaskTransform, IntRect(), false); |
789 | 0 | } |
790 | 0 | } |
791 | | |
792 | | void |
793 | | gfxContext::PopGroupAndBlend() |
794 | 0 | { |
795 | 0 | mDT->PopLayer(); |
796 | 0 | } |
797 | | |
798 | | #ifdef MOZ_DUMP_PAINTING |
799 | | void |
800 | | gfxContext::WriteAsPNG(const char* aFile) |
801 | | { |
802 | | gfxUtils::WriteAsPNG(mDT, aFile); |
803 | | } |
804 | | |
805 | | void |
806 | | gfxContext::DumpAsDataURI() |
807 | | { |
808 | | gfxUtils::DumpAsDataURI(mDT); |
809 | | } |
810 | | |
811 | | void |
812 | | gfxContext::CopyAsDataURI() |
813 | | { |
814 | | gfxUtils::CopyAsDataURI(mDT); |
815 | | } |
816 | | #endif |
817 | | |
818 | | void |
819 | | gfxContext::EnsurePath() |
820 | 0 | { |
821 | 0 | if (mPathBuilder) { |
822 | 0 | mPath = mPathBuilder->Finish(); |
823 | 0 | mPathBuilder = nullptr; |
824 | 0 | } |
825 | 0 |
|
826 | 0 | if (mPath) { |
827 | 0 | if (mTransformChanged) { |
828 | 0 | Matrix mat = mTransform; |
829 | 0 | mat.Invert(); |
830 | 0 | mat = mPathTransform * mat; |
831 | 0 | mPathBuilder = mPath->TransformedCopyToBuilder(mat); |
832 | 0 | mPath = mPathBuilder->Finish(); |
833 | 0 | mPathBuilder = nullptr; |
834 | 0 |
|
835 | 0 | mTransformChanged = false; |
836 | 0 | } |
837 | 0 | return; |
838 | 0 | } |
839 | 0 |
|
840 | 0 | EnsurePathBuilder(); |
841 | 0 | mPath = mPathBuilder->Finish(); |
842 | 0 | mPathBuilder = nullptr; |
843 | 0 | } |
844 | | |
845 | | void |
846 | | gfxContext::EnsurePathBuilder() |
847 | 0 | { |
848 | 0 | if (mPathBuilder && !mTransformChanged) { |
849 | 0 | return; |
850 | 0 | } |
851 | 0 | |
852 | 0 | if (mPath) { |
853 | 0 | if (!mTransformChanged) { |
854 | 0 | mPathBuilder = mPath->CopyToBuilder(); |
855 | 0 | mPath = nullptr; |
856 | 0 | } else { |
857 | 0 | Matrix invTransform = mTransform; |
858 | 0 | invTransform.Invert(); |
859 | 0 | Matrix toNewUS = mPathTransform * invTransform; |
860 | 0 | mPathBuilder = mPath->TransformedCopyToBuilder(toNewUS); |
861 | 0 | } |
862 | 0 | return; |
863 | 0 | } |
864 | 0 |
|
865 | 0 | DebugOnly<PathBuilder*> oldPath = mPathBuilder.get(); |
866 | 0 |
|
867 | 0 | if (!mPathBuilder) { |
868 | 0 | mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING); |
869 | 0 |
|
870 | 0 | if (mPathIsRect) { |
871 | 0 | mPathBuilder->MoveTo(mRect.TopLeft()); |
872 | 0 | mPathBuilder->LineTo(mRect.TopRight()); |
873 | 0 | mPathBuilder->LineTo(mRect.BottomRight()); |
874 | 0 | mPathBuilder->LineTo(mRect.BottomLeft()); |
875 | 0 | mPathBuilder->Close(); |
876 | 0 | } |
877 | 0 | } |
878 | 0 |
|
879 | 0 | if (mTransformChanged) { |
880 | 0 | // This could be an else if since this should never happen when |
881 | 0 | // mPathBuilder is nullptr and mPath is nullptr. But this way we can |
882 | 0 | // assert if all the state is as expected. |
883 | 0 | MOZ_ASSERT(oldPath); |
884 | 0 | MOZ_ASSERT(!mPathIsRect); |
885 | 0 |
|
886 | 0 | Matrix invTransform = mTransform; |
887 | 0 | invTransform.Invert(); |
888 | 0 | Matrix toNewUS = mPathTransform * invTransform; |
889 | 0 |
|
890 | 0 | RefPtr<Path> path = mPathBuilder->Finish(); |
891 | 0 | if (!path) { |
892 | 0 | gfxCriticalError() << "gfxContext::EnsurePathBuilder failed in PathBuilder::Finish"; |
893 | 0 | } |
894 | 0 | mPathBuilder = path->TransformedCopyToBuilder(toNewUS); |
895 | 0 | } |
896 | 0 |
|
897 | 0 | mPathIsRect = false; |
898 | 0 | } |
899 | | |
900 | | CompositionOp |
901 | | gfxContext::GetOp() |
902 | 0 | { |
903 | 0 | if (CurrentState().op != CompositionOp::OP_SOURCE) { |
904 | 0 | return CurrentState().op; |
905 | 0 | } |
906 | 0 | |
907 | 0 | AzureState &state = CurrentState(); |
908 | 0 | if (state.pattern) { |
909 | 0 | if (state.pattern->IsOpaque()) { |
910 | 0 | return CompositionOp::OP_OVER; |
911 | 0 | } else { |
912 | 0 | return CompositionOp::OP_SOURCE; |
913 | 0 | } |
914 | 0 | } else { |
915 | 0 | if (state.color.a > 0.999) { |
916 | 0 | return CompositionOp::OP_OVER; |
917 | 0 | } else { |
918 | 0 | return CompositionOp::OP_SOURCE; |
919 | 0 | } |
920 | 0 | } |
921 | 0 | } |
922 | | |
923 | | /* SVG font code can change the transform after having set the pattern on the |
924 | | * context. When the pattern is set it is in user space, if the transform is |
925 | | * changed after doing so the pattern needs to be converted back into userspace. |
926 | | * We just store the old pattern transform here so that we only do the work |
927 | | * needed here if the pattern is actually used. |
928 | | * We need to avoid doing this when this ChangeTransform comes from a restore, |
929 | | * since the current pattern and the current transform are both part of the |
930 | | * state we know the new CurrentState()'s values are valid. But if we assume |
931 | | * a change they might become invalid since patternTransformChanged is part of |
932 | | * the state and might be false for the restored AzureState. |
933 | | */ |
934 | | void |
935 | | gfxContext::ChangeTransform(const Matrix &aNewMatrix, bool aUpdatePatternTransform) |
936 | 0 | { |
937 | 0 | AzureState &state = CurrentState(); |
938 | 0 |
|
939 | 0 | if (aUpdatePatternTransform && (state.pattern) |
940 | 0 | && !state.patternTransformChanged) { |
941 | 0 | state.patternTransform = GetDTTransform(); |
942 | 0 | state.patternTransformChanged = true; |
943 | 0 | } |
944 | 0 |
|
945 | 0 | if (mPathIsRect) { |
946 | 0 | Matrix invMatrix = aNewMatrix; |
947 | 0 | |
948 | 0 | invMatrix.Invert(); |
949 | 0 |
|
950 | 0 | Matrix toNewUS = mTransform * invMatrix; |
951 | 0 |
|
952 | 0 | if (toNewUS.IsRectilinear()) { |
953 | 0 | mRect = toNewUS.TransformBounds(mRect); |
954 | 0 | mRect.NudgeToIntegers(); |
955 | 0 | } else { |
956 | 0 | mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING); |
957 | 0 |
|
958 | 0 | mPathBuilder->MoveTo(toNewUS.TransformPoint(mRect.TopLeft())); |
959 | 0 | mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.TopRight())); |
960 | 0 | mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomRight())); |
961 | 0 | mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomLeft())); |
962 | 0 | mPathBuilder->Close(); |
963 | 0 |
|
964 | 0 | mPathIsRect = false; |
965 | 0 | } |
966 | 0 |
|
967 | 0 | // No need to consider the transform changed now! |
968 | 0 | mTransformChanged = false; |
969 | 0 | } else if ((mPath || mPathBuilder) && !mTransformChanged) { |
970 | 0 | mTransformChanged = true; |
971 | 0 | mPathTransform = mTransform; |
972 | 0 | } |
973 | 0 |
|
974 | 0 | mTransform = aNewMatrix; |
975 | 0 |
|
976 | 0 | mDT->SetTransform(GetDTTransform()); |
977 | 0 | } |
978 | | |
979 | | Rect |
980 | | gfxContext::GetAzureDeviceSpaceClipBounds() const |
981 | 0 | { |
982 | 0 | Rect rect(CurrentState().deviceOffset.x, CurrentState().deviceOffset.y, |
983 | 0 | Float(mDT->GetSize().width), Float(mDT->GetSize().height)); |
984 | 0 | for (unsigned int i = 0; i < mStateStack.Length(); i++) { |
985 | 0 | for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { |
986 | 0 | const AzureState::PushedClip &clip = mStateStack[i].pushedClips[c]; |
987 | 0 | if (clip.path) { |
988 | 0 | Rect bounds = clip.path->GetBounds(clip.transform); |
989 | 0 | rect.IntersectRect(rect, bounds); |
990 | 0 | } else { |
991 | 0 | rect.IntersectRect(rect, clip.transform.TransformBounds(clip.rect)); |
992 | 0 | } |
993 | 0 | } |
994 | 0 | } |
995 | 0 |
|
996 | 0 | return rect; |
997 | 0 | } |
998 | | |
999 | | Point |
1000 | | gfxContext::GetDeviceOffset() const |
1001 | 0 | { |
1002 | 0 | return CurrentState().deviceOffset; |
1003 | 0 | } |
1004 | | |
1005 | | Matrix |
1006 | | gfxContext::GetDeviceTransform() const |
1007 | 0 | { |
1008 | 0 | return Matrix::Translation(-CurrentState().deviceOffset.x, |
1009 | 0 | -CurrentState().deviceOffset.y); |
1010 | 0 | } |
1011 | | |
1012 | | Matrix |
1013 | | gfxContext::GetDTTransform() const |
1014 | 0 | { |
1015 | 0 | Matrix mat = mTransform; |
1016 | 0 | mat._31 -= CurrentState().deviceOffset.x; |
1017 | 0 | mat._32 -= CurrentState().deviceOffset.y; |
1018 | 0 | return mat; |
1019 | 0 | } |