OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc.
All rights reserved. | |
3 * Copyright (C) 2008, 2010 Nokia Corporation and/or its subsidiary(-ies) | |
4 * Copyright (C) 2007 Alp Toker <alp@atoker.com> | |
5 * Copyright (C) 2008 Eric Seidel <eric@webkit.org> | |
6 * Copyright (C) 2008 Dirk Schulze <krit@webkit.org> | |
7 * Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved. | |
8 * Copyright (C) 2012, 2013 Intel Corporation. All rights reserved. | |
9 * Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved. | |
10 * | |
11 * Redistribution and use in source and binary forms, with or without | |
12 * modification, are permitted provided that the following conditions | |
13 * are met: | |
14 * 1. Redistributions of source code must retain the above copyright | |
15 * notice, this list of conditions and the following disclaimer. | |
16 * 2. Redistributions in binary form must reproduce the above copyright | |
17 * notice, this list of conditions and the following disclaimer in the | |
18 * documentation and/or other materials provided with the distribution. | |
19 * | |
20 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY | |
21 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR | |
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
31 */ | |
32 | |
33 #include "sky/engine/config.h" | |
34 #include "sky/engine/core/html/canvas/CanvasRenderingContext2D.h" | |
35 | |
36 #include "gen/sky/core/CSSPropertyNames.h" | |
37 #include "sky/engine/bindings/exception_messages.h" | |
38 #include "sky/engine/bindings/exception_state.h" | |
39 #include "sky/engine/bindings/exception_state_placeholder.h" | |
40 #include "sky/engine/core/css/CSSFontSelector.h" | |
41 #include "sky/engine/core/css/StylePropertySet.h" | |
42 #include "sky/engine/core/css/parser/BisonCSSParser.h" | |
43 #include "sky/engine/core/css/resolver/StyleResolver.h" | |
44 #include "sky/engine/core/dom/ExceptionCode.h" | |
45 #include "sky/engine/core/dom/StyleEngine.h" | |
46 #include "sky/engine/core/events/Event.h" | |
47 #include "sky/engine/core/fetch/ImageResource.h" | |
48 #include "sky/engine/core/frame/ImageBitmap.h" | |
49 #include "sky/engine/core/html/HTMLCanvasElement.h" | |
50 #include "sky/engine/core/html/HTMLImageElement.h" | |
51 #include "sky/engine/core/html/ImageData.h" | |
52 #include "sky/engine/core/html/TextMetrics.h" | |
53 #include "sky/engine/core/html/canvas/CanvasGradient.h" | |
54 #include "sky/engine/core/html/canvas/CanvasPattern.h" | |
55 #include "sky/engine/core/html/canvas/CanvasStyle.h" | |
56 #include "sky/engine/core/html/canvas/Path2D.h" | |
57 #include "sky/engine/core/rendering/RenderImage.h" | |
58 #include "sky/engine/core/rendering/RenderLayer.h" | |
59 #include "sky/engine/core/rendering/RenderTheme.h" | |
60 #include "sky/engine/platform/fonts/FontCache.h" | |
61 #include "sky/engine/platform/geometry/FloatQuad.h" | |
62 #include "sky/engine/platform/graphics/DrawLooperBuilder.h" | |
63 #include "sky/engine/platform/graphics/GraphicsContextStateSaver.h" | |
64 #include "sky/engine/platform/text/TextRun.h" | |
65 #include "sky/engine/wtf/CheckedArithmetic.h" | |
66 #include "sky/engine/wtf/MathExtras.h" | |
67 #include "sky/engine/wtf/OwnPtr.h" | |
68 #include "sky/engine/wtf/Uint8ClampedArray.h" | |
69 #include "sky/engine/wtf/text/StringBuilder.h" | |
70 | |
71 namespace blink { | |
72 | |
73 static const int defaultFontSize = 10; | |
74 static const char defaultFontFamily[] = "sans-serif"; | |
75 static const char defaultFont[] = "10px sans-serif"; | |
76 static const char inherit[] = "inherit"; | |
77 static const char rtl[] = "rtl"; | |
78 static const char ltr[] = "ltr"; | |
79 static const double TryRestoreContextInterval = 0.5; | |
80 static const unsigned MaxTryRestoreContextAttempts = 4; | |
81 | |
82 static bool contextLostRestoredEventsEnabled() | |
83 { | |
84 return RuntimeEnabledFeatures::experimentalCanvasFeaturesEnabled(); | |
85 } | |
86 | |
87 CanvasRenderingContext2D::CanvasRenderingContext2D(HTMLCanvasElement* canvas, co
nst Canvas2DContextAttributes* attrs) | |
88 : CanvasRenderingContext(canvas) | |
89 , m_isContextLost(false) | |
90 , m_contextRestorable(true) | |
91 , m_storageMode(!attrs ? PersistentStorage : attrs->parsedStorage()) | |
92 , m_tryRestoreContextAttemptCount(0) | |
93 , m_dispatchContextLostEventTimer(this, &CanvasRenderingContext2D::dispatchC
ontextLostEvent) | |
94 , m_dispatchContextRestoredEventTimer(this, &CanvasRenderingContext2D::dispa
tchContextRestoredEvent) | |
95 , m_tryRestoreContextEventTimer(this, &CanvasRenderingContext2D::tryRestoreC
ontextEvent) | |
96 { | |
97 m_stateStack.append(adoptPtr(new State())); | |
98 } | |
99 | |
100 void CanvasRenderingContext2D::unwindStateStack() | |
101 { | |
102 if (size_t stackSize = m_stateStack.size()) { | |
103 if (GraphicsContext* context = canvas()->existingDrawingContext()) { | |
104 while (--stackSize) | |
105 context->restore(); | |
106 } | |
107 } | |
108 } | |
109 | |
110 CanvasRenderingContext2D::~CanvasRenderingContext2D() | |
111 { | |
112 } | |
113 | |
114 void CanvasRenderingContext2D::validateStateStack() | |
115 { | |
116 #if ENABLE(ASSERT) | |
117 GraphicsContext* context = canvas()->existingDrawingContext(); | |
118 if (context && !context->contextDisabled()) | |
119 ASSERT(context->saveCount() == m_stateStack.size()); | |
120 #endif | |
121 } | |
122 | |
123 bool CanvasRenderingContext2D::isAccelerated() const | |
124 { | |
125 if (!canvas()->hasImageBuffer()) | |
126 return false; | |
127 GraphicsContext* context = drawingContext(); | |
128 return context && context->isAccelerated(); | |
129 } | |
130 | |
131 bool CanvasRenderingContext2D::isContextLost() const | |
132 { | |
133 return m_isContextLost; | |
134 } | |
135 | |
136 void CanvasRenderingContext2D::loseContext() | |
137 { | |
138 if (m_isContextLost) | |
139 return; | |
140 m_isContextLost = true; | |
141 m_dispatchContextLostEventTimer.startOneShot(0, FROM_HERE); | |
142 } | |
143 | |
144 void CanvasRenderingContext2D::restoreContext() | |
145 { | |
146 if (!m_contextRestorable) | |
147 return; | |
148 // This code path is for restoring from an eviction | |
149 // Restoring from surface failure is handled internally | |
150 ASSERT(m_isContextLost && !canvas()->hasImageBuffer()); | |
151 | |
152 if (canvas()->buffer()) { | |
153 if (contextLostRestoredEventsEnabled()) { | |
154 m_dispatchContextRestoredEventTimer.startOneShot(0, FROM_HERE); | |
155 } else { | |
156 // legacy synchronous context restoration. | |
157 reset(); | |
158 m_isContextLost = false; | |
159 } | |
160 } | |
161 } | |
162 | |
163 void CanvasRenderingContext2D::dispatchContextLostEvent(Timer<CanvasRenderingCon
text2D>*) | |
164 { | |
165 if (contextLostRestoredEventsEnabled()) { | |
166 RefPtr<Event> event = Event::createCancelable(EventTypeNames::contextlos
t); | |
167 canvas()->dispatchEvent(event); | |
168 if (event->defaultPrevented()) { | |
169 m_contextRestorable = false; | |
170 } | |
171 } | |
172 | |
173 // If an image buffer is present, it means the context was not lost due to | |
174 // an eviction, but rather due to a surface failure (gpu context lost?) | |
175 if (m_contextRestorable && canvas()->hasImageBuffer()) { | |
176 m_tryRestoreContextAttemptCount = 0; | |
177 m_tryRestoreContextEventTimer.startRepeating(TryRestoreContextInterval,
FROM_HERE); | |
178 } | |
179 } | |
180 | |
181 void CanvasRenderingContext2D::tryRestoreContextEvent(Timer<CanvasRenderingConte
xt2D>* timer) | |
182 { | |
183 if (!m_isContextLost) { | |
184 // Canvas was already restored (possibly thanks to a resize), so stop tr
ying. | |
185 m_tryRestoreContextEventTimer.stop(); | |
186 return; | |
187 } | |
188 if (canvas()->hasImageBuffer() && canvas()->buffer()->restoreSurface()) { | |
189 m_tryRestoreContextEventTimer.stop(); | |
190 dispatchContextRestoredEvent(0); | |
191 } | |
192 | |
193 if (++m_tryRestoreContextAttemptCount > MaxTryRestoreContextAttempts) | |
194 canvas()->discardImageBuffer(); | |
195 | |
196 if (!canvas()->hasImageBuffer()) { | |
197 // final attempt: allocate a brand new image buffer instead of restoring | |
198 timer->stop(); | |
199 if (canvas()->buffer()) | |
200 dispatchContextRestoredEvent(0); | |
201 } | |
202 } | |
203 | |
204 void CanvasRenderingContext2D::dispatchContextRestoredEvent(Timer<CanvasRenderin
gContext2D>*) | |
205 { | |
206 if (!m_isContextLost) | |
207 return; | |
208 reset(); | |
209 m_isContextLost = false; | |
210 if (contextLostRestoredEventsEnabled()) { | |
211 RefPtr<Event> event(Event::create(EventTypeNames::contextrestored)); | |
212 canvas()->dispatchEvent(event); | |
213 } | |
214 } | |
215 | |
216 void CanvasRenderingContext2D::reset() | |
217 { | |
218 validateStateStack(); | |
219 unwindStateStack(); | |
220 m_stateStack.resize(1); | |
221 m_stateStack.first() = adoptPtr(new State()); | |
222 m_path.clear(); | |
223 validateStateStack(); | |
224 } | |
225 | |
226 // Important: Several of these properties are also stored in GraphicsContext's | |
227 // StrokeData. The default values that StrokeData uses may not the same values | |
228 // that the canvas 2d spec specifies. Make sure to sync the initial state of the | |
229 // GraphicsContext in HTMLCanvasElement::createImageBuffer()! | |
230 CanvasRenderingContext2D::State::State() | |
231 : m_unrealizedSaveCount(0) | |
232 , m_strokeStyle(CanvasStyle::createFromRGBA(Color::black)) | |
233 , m_fillStyle(CanvasStyle::createFromRGBA(Color::black)) | |
234 , m_lineWidth(1) | |
235 , m_lineCap(ButtCap) | |
236 , m_lineJoin(MiterJoin) | |
237 , m_miterLimit(10) | |
238 , m_shadowBlur(0) | |
239 , m_shadowColor(Color::transparent) | |
240 , m_invertibleCTM(true) | |
241 , m_lineDashOffset(0) | |
242 , m_imageSmoothingEnabled(true) | |
243 , m_textAlign(StartTextAlign) | |
244 , m_textBaseline(AlphabeticTextBaseline) | |
245 , m_direction(DirectionInherit) | |
246 , m_unparsedFont(defaultFont) | |
247 , m_realizedFont(false) | |
248 , m_hasClip(false) | |
249 { | |
250 } | |
251 | |
252 CanvasRenderingContext2D::State::State(const State& other) | |
253 : CSSFontSelectorClient() | |
254 , m_unrealizedSaveCount(other.m_unrealizedSaveCount) | |
255 , m_unparsedStrokeColor(other.m_unparsedStrokeColor) | |
256 , m_unparsedFillColor(other.m_unparsedFillColor) | |
257 , m_strokeStyle(other.m_strokeStyle) | |
258 , m_fillStyle(other.m_fillStyle) | |
259 , m_lineWidth(other.m_lineWidth) | |
260 , m_lineCap(other.m_lineCap) | |
261 , m_lineJoin(other.m_lineJoin) | |
262 , m_miterLimit(other.m_miterLimit) | |
263 , m_shadowOffset(other.m_shadowOffset) | |
264 , m_shadowBlur(other.m_shadowBlur) | |
265 , m_shadowColor(other.m_shadowColor) | |
266 , m_transform(other.m_transform) | |
267 , m_invertibleCTM(other.m_invertibleCTM) | |
268 , m_lineDashOffset(other.m_lineDashOffset) | |
269 , m_imageSmoothingEnabled(other.m_imageSmoothingEnabled) | |
270 , m_textAlign(other.m_textAlign) | |
271 , m_textBaseline(other.m_textBaseline) | |
272 , m_direction(other.m_direction) | |
273 , m_unparsedFont(other.m_unparsedFont) | |
274 , m_font(other.m_font) | |
275 , m_realizedFont(other.m_realizedFont) | |
276 , m_hasClip(other.m_hasClip) | |
277 { | |
278 if (m_realizedFont) | |
279 static_cast<CSSFontSelector*>(m_font.fontSelector())->registerForInvalid
ationCallbacks(this); | |
280 } | |
281 | |
282 CanvasRenderingContext2D::State& CanvasRenderingContext2D::State::operator=(cons
t State& other) | |
283 { | |
284 if (this == &other) | |
285 return *this; | |
286 | |
287 #if !ENABLE(OILPAN) | |
288 if (m_realizedFont) | |
289 static_cast<CSSFontSelector*>(m_font.fontSelector())->unregisterForInval
idationCallbacks(this); | |
290 #endif | |
291 | |
292 m_unrealizedSaveCount = other.m_unrealizedSaveCount; | |
293 m_unparsedStrokeColor = other.m_unparsedStrokeColor; | |
294 m_unparsedFillColor = other.m_unparsedFillColor; | |
295 m_strokeStyle = other.m_strokeStyle; | |
296 m_fillStyle = other.m_fillStyle; | |
297 m_lineWidth = other.m_lineWidth; | |
298 m_lineCap = other.m_lineCap; | |
299 m_lineJoin = other.m_lineJoin; | |
300 m_miterLimit = other.m_miterLimit; | |
301 m_shadowOffset = other.m_shadowOffset; | |
302 m_shadowBlur = other.m_shadowBlur; | |
303 m_shadowColor = other.m_shadowColor; | |
304 m_transform = other.m_transform; | |
305 m_invertibleCTM = other.m_invertibleCTM; | |
306 m_imageSmoothingEnabled = other.m_imageSmoothingEnabled; | |
307 m_textAlign = other.m_textAlign; | |
308 m_textBaseline = other.m_textBaseline; | |
309 m_direction = other.m_direction; | |
310 m_unparsedFont = other.m_unparsedFont; | |
311 m_font = other.m_font; | |
312 m_realizedFont = other.m_realizedFont; | |
313 m_hasClip = other.m_hasClip; | |
314 | |
315 if (m_realizedFont) | |
316 static_cast<CSSFontSelector*>(m_font.fontSelector())->registerForInvalid
ationCallbacks(this); | |
317 | |
318 return *this; | |
319 } | |
320 | |
321 CanvasRenderingContext2D::State::~State() | |
322 { | |
323 #if !ENABLE(OILPAN) | |
324 if (m_realizedFont) | |
325 static_cast<CSSFontSelector*>(m_font.fontSelector())->unregisterForInval
idationCallbacks(this); | |
326 #endif | |
327 } | |
328 | |
329 void CanvasRenderingContext2D::State::fontsNeedUpdate(CSSFontSelector* fontSelec
tor) | |
330 { | |
331 ASSERT_ARG(fontSelector, fontSelector == m_font.fontSelector()); | |
332 ASSERT(m_realizedFont); | |
333 | |
334 m_font.update(fontSelector); | |
335 } | |
336 | |
337 void CanvasRenderingContext2D::realizeSaves(GraphicsContext* context) | |
338 { | |
339 validateStateStack(); | |
340 if (state().m_unrealizedSaveCount) { | |
341 ASSERT(m_stateStack.size() >= 1); | |
342 // Reduce the current state's unrealized count by one now, | |
343 // to reflect the fact we are saving one state. | |
344 m_stateStack.last()->m_unrealizedSaveCount--; | |
345 m_stateStack.append(adoptPtr(new State(state()))); | |
346 // Set the new state's unrealized count to 0, because it has no outstand
ing saves. | |
347 // We need to do this explicitly because the copy constructor and operat
or= used | |
348 // by the Vector operations copy the unrealized count from the previous
state (in | |
349 // turn necessary to support correct resizing and unwinding of the stack
). | |
350 m_stateStack.last()->m_unrealizedSaveCount = 0; | |
351 if (!context) | |
352 context = drawingContext(); | |
353 if (context) | |
354 context->save(); | |
355 validateStateStack(); | |
356 } | |
357 } | |
358 | |
359 void CanvasRenderingContext2D::restore() | |
360 { | |
361 validateStateStack(); | |
362 if (state().m_unrealizedSaveCount) { | |
363 // We never realized the save, so just record that it was unnecessary. | |
364 --m_stateStack.last()->m_unrealizedSaveCount; | |
365 return; | |
366 } | |
367 ASSERT(m_stateStack.size() >= 1); | |
368 if (m_stateStack.size() <= 1) | |
369 return; | |
370 m_path.transform(state().m_transform); | |
371 m_stateStack.removeLast(); | |
372 m_path.transform(state().m_transform.inverse()); | |
373 GraphicsContext* c = drawingContext(); | |
374 if (c) | |
375 c->restore(); | |
376 validateStateStack(); | |
377 } | |
378 | |
379 CanvasStyle* CanvasRenderingContext2D::strokeStyle() const | |
380 { | |
381 return state().m_strokeStyle.get(); | |
382 } | |
383 | |
384 void CanvasRenderingContext2D::setStrokeStyle(PassRefPtr<CanvasStyle> prpStyle) | |
385 { | |
386 RefPtr<CanvasStyle> style = prpStyle; | |
387 | |
388 if (!style) | |
389 return; | |
390 | |
391 if (state().m_strokeStyle && state().m_strokeStyle->isEquivalentColor(*style
)) | |
392 return; | |
393 | |
394 if (style->isCurrentColor()) { | |
395 if (style->hasOverrideAlpha()) | |
396 style = CanvasStyle::createFromRGBA(colorWithOverrideAlpha(currentCo
lor(canvas()), style->overrideAlpha())); | |
397 else | |
398 style = CanvasStyle::createFromRGBA(currentColor(canvas())); | |
399 } | |
400 | |
401 GraphicsContext* c = drawingContext(); | |
402 realizeSaves(c); | |
403 modifiableState().m_strokeStyle = style.release(); | |
404 if (!c) | |
405 return; | |
406 state().m_strokeStyle->applyStrokeColor(c); | |
407 modifiableState().m_unparsedStrokeColor = String(); | |
408 } | |
409 | |
410 CanvasStyle* CanvasRenderingContext2D::fillStyle() const | |
411 { | |
412 return state().m_fillStyle.get(); | |
413 } | |
414 | |
415 void CanvasRenderingContext2D::setFillStyle(PassRefPtr<CanvasStyle> prpStyle) | |
416 { | |
417 RefPtr<CanvasStyle> style = prpStyle; | |
418 | |
419 if (!style) | |
420 return; | |
421 | |
422 if (state().m_fillStyle && state().m_fillStyle->isEquivalentColor(*style)) | |
423 return; | |
424 | |
425 if (style->isCurrentColor()) { | |
426 if (style->hasOverrideAlpha()) | |
427 style = CanvasStyle::createFromRGBA(colorWithOverrideAlpha(currentCo
lor(canvas()), style->overrideAlpha())); | |
428 else | |
429 style = CanvasStyle::createFromRGBA(currentColor(canvas())); | |
430 } | |
431 | |
432 GraphicsContext* c = drawingContext(); | |
433 realizeSaves(c); | |
434 modifiableState().m_fillStyle = style.release(); | |
435 if (!c) | |
436 return; | |
437 state().m_fillStyle->applyFillColor(c); | |
438 modifiableState().m_unparsedFillColor = String(); | |
439 } | |
440 | |
441 float CanvasRenderingContext2D::lineWidth() const | |
442 { | |
443 return state().m_lineWidth; | |
444 } | |
445 | |
446 void CanvasRenderingContext2D::setLineWidth(float width) | |
447 { | |
448 if (!(std::isfinite(width) && width > 0)) | |
449 return; | |
450 if (state().m_lineWidth == width) | |
451 return; | |
452 GraphicsContext* c = drawingContext(); | |
453 realizeSaves(c); | |
454 modifiableState().m_lineWidth = width; | |
455 if (!c) | |
456 return; | |
457 c->setStrokeThickness(width); | |
458 } | |
459 | |
460 String CanvasRenderingContext2D::lineCap() const | |
461 { | |
462 return lineCapName(state().m_lineCap); | |
463 } | |
464 | |
465 void CanvasRenderingContext2D::setLineCap(const String& s) | |
466 { | |
467 LineCap cap; | |
468 if (!parseLineCap(s, cap)) | |
469 return; | |
470 if (state().m_lineCap == cap) | |
471 return; | |
472 GraphicsContext* c = drawingContext(); | |
473 realizeSaves(c); | |
474 modifiableState().m_lineCap = cap; | |
475 if (!c) | |
476 return; | |
477 c->setLineCap(cap); | |
478 } | |
479 | |
480 String CanvasRenderingContext2D::lineJoin() const | |
481 { | |
482 return lineJoinName(state().m_lineJoin); | |
483 } | |
484 | |
485 void CanvasRenderingContext2D::setLineJoin(const String& s) | |
486 { | |
487 LineJoin join; | |
488 if (!parseLineJoin(s, join)) | |
489 return; | |
490 if (state().m_lineJoin == join) | |
491 return; | |
492 GraphicsContext* c = drawingContext(); | |
493 realizeSaves(c); | |
494 modifiableState().m_lineJoin = join; | |
495 if (!c) | |
496 return; | |
497 c->setLineJoin(join); | |
498 } | |
499 | |
500 float CanvasRenderingContext2D::miterLimit() const | |
501 { | |
502 return state().m_miterLimit; | |
503 } | |
504 | |
505 void CanvasRenderingContext2D::setMiterLimit(float limit) | |
506 { | |
507 if (!(std::isfinite(limit) && limit > 0)) | |
508 return; | |
509 if (state().m_miterLimit == limit) | |
510 return; | |
511 GraphicsContext* c = drawingContext(); | |
512 realizeSaves(c); | |
513 modifiableState().m_miterLimit = limit; | |
514 if (!c) | |
515 return; | |
516 c->setMiterLimit(limit); | |
517 } | |
518 | |
519 float CanvasRenderingContext2D::shadowOffsetX() const | |
520 { | |
521 return state().m_shadowOffset.width(); | |
522 } | |
523 | |
524 void CanvasRenderingContext2D::setShadowOffsetX(float x) | |
525 { | |
526 if (!std::isfinite(x)) | |
527 return; | |
528 if (state().m_shadowOffset.width() == x) | |
529 return; | |
530 realizeSaves(0); | |
531 modifiableState().m_shadowOffset.setWidth(x); | |
532 applyShadow(); | |
533 } | |
534 | |
535 float CanvasRenderingContext2D::shadowOffsetY() const | |
536 { | |
537 return state().m_shadowOffset.height(); | |
538 } | |
539 | |
540 void CanvasRenderingContext2D::setShadowOffsetY(float y) | |
541 { | |
542 if (!std::isfinite(y)) | |
543 return; | |
544 if (state().m_shadowOffset.height() == y) | |
545 return; | |
546 realizeSaves(0); | |
547 modifiableState().m_shadowOffset.setHeight(y); | |
548 applyShadow(); | |
549 } | |
550 | |
551 float CanvasRenderingContext2D::shadowBlur() const | |
552 { | |
553 return state().m_shadowBlur; | |
554 } | |
555 | |
556 void CanvasRenderingContext2D::setShadowBlur(float blur) | |
557 { | |
558 if (!(std::isfinite(blur) && blur >= 0)) | |
559 return; | |
560 if (state().m_shadowBlur == blur) | |
561 return; | |
562 realizeSaves(0); | |
563 modifiableState().m_shadowBlur = blur; | |
564 applyShadow(); | |
565 } | |
566 | |
567 String CanvasRenderingContext2D::shadowColor() const | |
568 { | |
569 return Color(state().m_shadowColor).serialized(); | |
570 } | |
571 | |
572 void CanvasRenderingContext2D::setShadowColor(const String& color) | |
573 { | |
574 RGBA32 rgba; | |
575 if (!parseColorOrCurrentColor(rgba, color, canvas())) | |
576 return; | |
577 if (state().m_shadowColor == rgba) | |
578 return; | |
579 realizeSaves(0); | |
580 modifiableState().m_shadowColor = rgba; | |
581 applyShadow(); | |
582 } | |
583 | |
584 const Vector<float>& CanvasRenderingContext2D::getLineDash() const | |
585 { | |
586 return state().m_lineDash; | |
587 } | |
588 | |
589 static bool lineDashSequenceIsValid(const Vector<float>& dash) | |
590 { | |
591 for (size_t i = 0; i < dash.size(); i++) { | |
592 if (!std::isfinite(dash[i]) || dash[i] < 0) | |
593 return false; | |
594 } | |
595 return true; | |
596 } | |
597 | |
598 void CanvasRenderingContext2D::setLineDash(const Vector<float>& dash) | |
599 { | |
600 if (!lineDashSequenceIsValid(dash)) | |
601 return; | |
602 | |
603 realizeSaves(0); | |
604 modifiableState().m_lineDash = dash; | |
605 // Spec requires the concatenation of two copies the dash list when the | |
606 // number of elements is odd | |
607 if (dash.size() % 2) | |
608 modifiableState().m_lineDash.appendVector(dash); | |
609 | |
610 applyLineDash(); | |
611 } | |
612 | |
613 float CanvasRenderingContext2D::lineDashOffset() const | |
614 { | |
615 return state().m_lineDashOffset; | |
616 } | |
617 | |
618 void CanvasRenderingContext2D::setLineDashOffset(float offset) | |
619 { | |
620 if (!std::isfinite(offset) || state().m_lineDashOffset == offset) | |
621 return; | |
622 | |
623 realizeSaves(0); | |
624 modifiableState().m_lineDashOffset = offset; | |
625 applyLineDash(); | |
626 } | |
627 | |
628 void CanvasRenderingContext2D::applyLineDash() const | |
629 { | |
630 GraphicsContext* c = drawingContext(); | |
631 if (!c) | |
632 return; | |
633 DashArray convertedLineDash(state().m_lineDash.size()); | |
634 for (size_t i = 0; i < state().m_lineDash.size(); ++i) | |
635 convertedLineDash[i] = static_cast<DashArrayElement>(state().m_lineDash[
i]); | |
636 c->setLineDash(convertedLineDash, state().m_lineDashOffset); | |
637 } | |
638 | |
639 void CanvasRenderingContext2D::scale(float sx, float sy) | |
640 { | |
641 GraphicsContext* c = drawingContext(); | |
642 if (!c) | |
643 return; | |
644 if (!state().m_invertibleCTM) | |
645 return; | |
646 | |
647 if (!std::isfinite(sx) | !std::isfinite(sy)) | |
648 return; | |
649 | |
650 AffineTransform newTransform = state().m_transform; | |
651 newTransform.scaleNonUniform(sx, sy); | |
652 if (state().m_transform == newTransform) | |
653 return; | |
654 | |
655 realizeSaves(c); | |
656 | |
657 if (!newTransform.isInvertible()) { | |
658 modifiableState().m_invertibleCTM = false; | |
659 return; | |
660 } | |
661 | |
662 modifiableState().m_transform = newTransform; | |
663 c->scale(sx, sy); | |
664 m_path.transform(AffineTransform().scaleNonUniform(1.0 / sx, 1.0 / sy)); | |
665 } | |
666 | |
667 void CanvasRenderingContext2D::rotate(float angleInRadians) | |
668 { | |
669 GraphicsContext* c = drawingContext(); | |
670 if (!c) | |
671 return; | |
672 if (!state().m_invertibleCTM) | |
673 return; | |
674 | |
675 if (!std::isfinite(angleInRadians)) | |
676 return; | |
677 | |
678 AffineTransform newTransform = state().m_transform; | |
679 newTransform.rotateRadians(angleInRadians); | |
680 if (state().m_transform == newTransform) | |
681 return; | |
682 | |
683 realizeSaves(c); | |
684 | |
685 if (!newTransform.isInvertible()) { | |
686 modifiableState().m_invertibleCTM = false; | |
687 return; | |
688 } | |
689 | |
690 modifiableState().m_transform = newTransform; | |
691 c->rotate(angleInRadians); | |
692 m_path.transform(AffineTransform().rotateRadians(-angleInRadians)); | |
693 } | |
694 | |
695 void CanvasRenderingContext2D::translate(float tx, float ty) | |
696 { | |
697 GraphicsContext* c = drawingContext(); | |
698 if (!c) | |
699 return; | |
700 if (!state().m_invertibleCTM) | |
701 return; | |
702 | |
703 if (!std::isfinite(tx) | !std::isfinite(ty)) | |
704 return; | |
705 | |
706 AffineTransform newTransform = state().m_transform; | |
707 newTransform.translate(tx, ty); | |
708 if (state().m_transform == newTransform) | |
709 return; | |
710 | |
711 realizeSaves(c); | |
712 | |
713 if (!newTransform.isInvertible()) { | |
714 modifiableState().m_invertibleCTM = false; | |
715 return; | |
716 } | |
717 | |
718 modifiableState().m_transform = newTransform; | |
719 c->translate(tx, ty); | |
720 m_path.transform(AffineTransform().translate(-tx, -ty)); | |
721 } | |
722 | |
723 void CanvasRenderingContext2D::transform(float m11, float m12, float m21, float
m22, float dx, float dy) | |
724 { | |
725 GraphicsContext* c = drawingContext(); | |
726 if (!c) | |
727 return; | |
728 if (!state().m_invertibleCTM) | |
729 return; | |
730 | |
731 if (!std::isfinite(m11) | !std::isfinite(m21) | !std::isfinite(dx) | !std::i
sfinite(m12) | !std::isfinite(m22) | !std::isfinite(dy)) | |
732 return; | |
733 | |
734 AffineTransform transform(m11, m12, m21, m22, dx, dy); | |
735 AffineTransform newTransform = state().m_transform * transform; | |
736 if (state().m_transform == newTransform) | |
737 return; | |
738 | |
739 realizeSaves(c); | |
740 | |
741 modifiableState().m_transform = newTransform; | |
742 if (!newTransform.isInvertible()) { | |
743 modifiableState().m_invertibleCTM = false; | |
744 return; | |
745 } | |
746 | |
747 c->concatCTM(transform); | |
748 m_path.transform(transform.inverse()); | |
749 } | |
750 | |
751 void CanvasRenderingContext2D::resetTransform() | |
752 { | |
753 GraphicsContext* c = drawingContext(); | |
754 if (!c) | |
755 return; | |
756 | |
757 AffineTransform ctm = state().m_transform; | |
758 bool invertibleCTM = state().m_invertibleCTM; | |
759 // It is possible that CTM is identity while CTM is not invertible. | |
760 // When CTM becomes non-invertible, realizeSaves() can make CTM identity. | |
761 if (ctm.isIdentity() && invertibleCTM) | |
762 return; | |
763 | |
764 realizeSaves(c); | |
765 // resetTransform() resolves the non-invertible CTM state. | |
766 modifiableState().m_transform.makeIdentity(); | |
767 modifiableState().m_invertibleCTM = true; | |
768 c->setCTM(canvas()->baseTransform()); | |
769 | |
770 if (invertibleCTM) | |
771 m_path.transform(ctm); | |
772 // When else, do nothing because all transform methods didn't update m_path
when CTM became non-invertible. | |
773 // It means that resetTransform() restores m_path just before CTM became non
-invertible. | |
774 } | |
775 | |
776 void CanvasRenderingContext2D::setTransform(float m11, float m12, float m21, flo
at m22, float dx, float dy) | |
777 { | |
778 GraphicsContext* c = drawingContext(); | |
779 if (!c) | |
780 return; | |
781 | |
782 if (!std::isfinite(m11) | !std::isfinite(m21) | !std::isfinite(dx) | !std::i
sfinite(m12) | !std::isfinite(m22) | !std::isfinite(dy)) | |
783 return; | |
784 | |
785 resetTransform(); | |
786 transform(m11, m12, m21, m22, dx, dy); | |
787 } | |
788 | |
789 String CanvasRenderingContext2D::strokeColor() | |
790 { | |
791 return strokeStyle()->color(); | |
792 } | |
793 | |
794 void CanvasRenderingContext2D::setStrokeColor(const String& color) | |
795 { | |
796 if (color == state().m_unparsedStrokeColor) | |
797 return; | |
798 realizeSaves(0); | |
799 setStrokeStyle(CanvasStyle::createFromString(color)); | |
800 modifiableState().m_unparsedStrokeColor = color; | |
801 } | |
802 | |
803 void CanvasRenderingContext2D::setStrokeColor(float grayLevel) | |
804 { | |
805 if (state().m_strokeStyle && state().m_strokeStyle->isEquivalentRGBA(grayLev
el, grayLevel, grayLevel, 1.0f)) | |
806 return; | |
807 setStrokeStyle(CanvasStyle::createFromGrayLevelWithAlpha(grayLevel, 1.0f)); | |
808 } | |
809 | |
810 void CanvasRenderingContext2D::setStrokeColor(const String& color, float alpha) | |
811 { | |
812 setStrokeStyle(CanvasStyle::createFromStringWithOverrideAlpha(color, alpha))
; | |
813 } | |
814 | |
815 void CanvasRenderingContext2D::setStrokeColor(float grayLevel, float alpha) | |
816 { | |
817 if (state().m_strokeStyle && state().m_strokeStyle->isEquivalentRGBA(grayLev
el, grayLevel, grayLevel, alpha)) | |
818 return; | |
819 setStrokeStyle(CanvasStyle::createFromGrayLevelWithAlpha(grayLevel, alpha)); | |
820 } | |
821 | |
822 void CanvasRenderingContext2D::setStrokeColor(float r, float g, float b, float a
) | |
823 { | |
824 if (state().m_strokeStyle && state().m_strokeStyle->isEquivalentRGBA(r, g, b
, a)) | |
825 return; | |
826 setStrokeStyle(CanvasStyle::createFromRGBAChannels(r, g, b, a)); | |
827 } | |
828 | |
829 void CanvasRenderingContext2D::setStrokeColor(float c, float m, float y, float k
, float a) | |
830 { | |
831 if (state().m_strokeStyle && state().m_strokeStyle->isEquivalentCMYKA(c, m,
y, k, a)) | |
832 return; | |
833 setStrokeStyle(CanvasStyle::createFromCMYKAChannels(c, m, y, k, a)); | |
834 } | |
835 | |
836 String CanvasRenderingContext2D::fillColor() | |
837 { | |
838 return fillStyle()->color(); | |
839 } | |
840 | |
841 void CanvasRenderingContext2D::setFillColor(const String& color) | |
842 { | |
843 if (color == state().m_unparsedFillColor) | |
844 return; | |
845 realizeSaves(0); | |
846 setFillStyle(CanvasStyle::createFromString(color)); | |
847 modifiableState().m_unparsedFillColor = color; | |
848 } | |
849 | |
850 void CanvasRenderingContext2D::setFillColor(float grayLevel) | |
851 { | |
852 if (state().m_fillStyle && state().m_fillStyle->isEquivalentRGBA(grayLevel,
grayLevel, grayLevel, 1.0f)) | |
853 return; | |
854 setFillStyle(CanvasStyle::createFromGrayLevelWithAlpha(grayLevel, 1.0f)); | |
855 } | |
856 | |
857 void CanvasRenderingContext2D::setFillColor(const String& color, float alpha) | |
858 { | |
859 setFillStyle(CanvasStyle::createFromStringWithOverrideAlpha(color, alpha)); | |
860 } | |
861 | |
862 void CanvasRenderingContext2D::setFillColor(float grayLevel, float alpha) | |
863 { | |
864 if (state().m_fillStyle && state().m_fillStyle->isEquivalentRGBA(grayLevel,
grayLevel, grayLevel, alpha)) | |
865 return; | |
866 setFillStyle(CanvasStyle::createFromGrayLevelWithAlpha(grayLevel, alpha)); | |
867 } | |
868 | |
869 void CanvasRenderingContext2D::setFillColor(float r, float g, float b, float a) | |
870 { | |
871 if (state().m_fillStyle && state().m_fillStyle->isEquivalentRGBA(r, g, b, a)
) | |
872 return; | |
873 setFillStyle(CanvasStyle::createFromRGBAChannels(r, g, b, a)); | |
874 } | |
875 | |
876 void CanvasRenderingContext2D::setFillColor(float c, float m, float y, float k,
float a) | |
877 { | |
878 if (state().m_fillStyle && state().m_fillStyle->isEquivalentCMYKA(c, m, y, k
, a)) | |
879 return; | |
880 setFillStyle(CanvasStyle::createFromCMYKAChannels(c, m, y, k, a)); | |
881 } | |
882 | |
883 void CanvasRenderingContext2D::beginPath() | |
884 { | |
885 m_path.clear(); | |
886 } | |
887 | |
888 static bool validateRectForCanvas(float& x, float& y, float& width, float& heigh
t) | |
889 { | |
890 if (!std::isfinite(x) | !std::isfinite(y) | !std::isfinite(width) | !std::is
finite(height)) | |
891 return false; | |
892 | |
893 if (!width && !height) | |
894 return false; | |
895 | |
896 if (width < 0) { | |
897 width = -width; | |
898 x -= width; | |
899 } | |
900 | |
901 if (height < 0) { | |
902 height = -height; | |
903 y -= height; | |
904 } | |
905 | |
906 return true; | |
907 } | |
908 | |
909 static WindRule parseWinding(const String& windingRuleString) | |
910 { | |
911 if (windingRuleString == "nonzero") | |
912 return RULE_NONZERO; | |
913 if (windingRuleString == "evenodd") | |
914 return RULE_EVENODD; | |
915 | |
916 ASSERT_NOT_REACHED(); | |
917 return RULE_EVENODD; | |
918 } | |
919 | |
920 void CanvasRenderingContext2D::fillInternal(const Path& path, const String& wind
ingRuleString) | |
921 { | |
922 if (path.isEmpty()) { | |
923 return; | |
924 } | |
925 GraphicsContext* c = drawingContext(); | |
926 if (!c) { | |
927 return; | |
928 } | |
929 if (!state().m_invertibleCTM) { | |
930 return; | |
931 } | |
932 FloatRect clipBounds; | |
933 if (!c->getTransformedClipBounds(&clipBounds)) { | |
934 return; | |
935 } | |
936 | |
937 // If gradient size is zero, then paint nothing. | |
938 Gradient* gradient = c->fillGradient(); | |
939 if (gradient && gradient->isZeroSize()) { | |
940 return; | |
941 } | |
942 | |
943 WindRule windRule = c->fillRule(); | |
944 c->setFillRule(parseWinding(windingRuleString)); | |
945 | |
946 FloatRect dirtyRect; | |
947 if (computeDirtyRect(path.boundingRect(), clipBounds, &dirtyRect)) { | |
948 c->fillPath(path); | |
949 didDraw(dirtyRect); | |
950 } | |
951 | |
952 c->setFillRule(windRule); | |
953 } | |
954 | |
955 void CanvasRenderingContext2D::fill(const String& windingRuleString) | |
956 { | |
957 fillInternal(m_path, windingRuleString); | |
958 } | |
959 | |
960 void CanvasRenderingContext2D::fill(Path2D* domPath, const String& windingRuleSt
ring) | |
961 { | |
962 fillInternal(domPath->path(), windingRuleString); | |
963 } | |
964 | |
965 void CanvasRenderingContext2D::strokeInternal(const Path& path) | |
966 { | |
967 if (path.isEmpty()) { | |
968 return; | |
969 } | |
970 GraphicsContext* c = drawingContext(); | |
971 if (!c) { | |
972 return; | |
973 } | |
974 if (!state().m_invertibleCTM) { | |
975 return; | |
976 } | |
977 FloatRect clipBounds; | |
978 if (!c->getTransformedClipBounds(&clipBounds)) | |
979 return; | |
980 | |
981 // If gradient size is zero, then paint nothing. | |
982 Gradient* gradient = c->strokeGradient(); | |
983 if (gradient && gradient->isZeroSize()) { | |
984 return; | |
985 } | |
986 | |
987 FloatRect bounds = path.boundingRect(); | |
988 inflateStrokeRect(bounds); | |
989 FloatRect dirtyRect; | |
990 if (computeDirtyRect(bounds, clipBounds, &dirtyRect)) { | |
991 c->strokePath(path); | |
992 didDraw(dirtyRect); | |
993 } | |
994 } | |
995 | |
996 void CanvasRenderingContext2D::stroke() | |
997 { | |
998 strokeInternal(m_path); | |
999 } | |
1000 | |
1001 void CanvasRenderingContext2D::stroke(Path2D* domPath) | |
1002 { | |
1003 strokeInternal(domPath->path()); | |
1004 } | |
1005 | |
1006 void CanvasRenderingContext2D::clipInternal(const Path& path, const String& wind
ingRuleString) | |
1007 { | |
1008 GraphicsContext* c = drawingContext(); | |
1009 if (!c) { | |
1010 return; | |
1011 } | |
1012 if (!state().m_invertibleCTM) { | |
1013 return; | |
1014 } | |
1015 | |
1016 realizeSaves(c); | |
1017 c->canvasClip(path, parseWinding(windingRuleString)); | |
1018 modifiableState().m_hasClip = true; | |
1019 } | |
1020 | |
1021 void CanvasRenderingContext2D::clip(const String& windingRuleString) | |
1022 { | |
1023 clipInternal(m_path, windingRuleString); | |
1024 } | |
1025 | |
1026 void CanvasRenderingContext2D::clip(Path2D* domPath, const String& windingRuleSt
ring) | |
1027 { | |
1028 clipInternal(domPath->path(), windingRuleString); | |
1029 } | |
1030 | |
1031 bool CanvasRenderingContext2D::isPointInPath(const float x, const float y, const
String& windingRuleString) | |
1032 { | |
1033 return isPointInPathInternal(m_path, x, y, windingRuleString); | |
1034 } | |
1035 | |
1036 bool CanvasRenderingContext2D::isPointInPath(Path2D* domPath, const float x, con
st float y, const String& windingRuleString) | |
1037 { | |
1038 return isPointInPathInternal(domPath->path(), x, y, windingRuleString); | |
1039 } | |
1040 | |
1041 bool CanvasRenderingContext2D::isPointInPathInternal(const Path& path, const flo
at x, const float y, const String& windingRuleString) | |
1042 { | |
1043 GraphicsContext* c = drawingContext(); | |
1044 if (!c) | |
1045 return false; | |
1046 if (!state().m_invertibleCTM) | |
1047 return false; | |
1048 | |
1049 FloatPoint point(x, y); | |
1050 AffineTransform ctm = state().m_transform; | |
1051 FloatPoint transformedPoint = ctm.inverse().mapPoint(point); | |
1052 if (!std::isfinite(transformedPoint.x()) || !std::isfinite(transformedPoint.
y())) | |
1053 return false; | |
1054 | |
1055 return path.contains(transformedPoint, parseWinding(windingRuleString)); | |
1056 } | |
1057 | |
1058 bool CanvasRenderingContext2D::isPointInStroke(const float x, const float y) | |
1059 { | |
1060 return isPointInStrokeInternal(m_path, x, y); | |
1061 } | |
1062 | |
1063 bool CanvasRenderingContext2D::isPointInStroke(Path2D* domPath, const float x, c
onst float y) | |
1064 { | |
1065 return isPointInStrokeInternal(domPath->path(), x, y); | |
1066 } | |
1067 | |
1068 bool CanvasRenderingContext2D::isPointInStrokeInternal(const Path& path, const f
loat x, const float y) | |
1069 { | |
1070 GraphicsContext* c = drawingContext(); | |
1071 if (!c) | |
1072 return false; | |
1073 if (!state().m_invertibleCTM) | |
1074 return false; | |
1075 | |
1076 FloatPoint point(x, y); | |
1077 AffineTransform ctm = state().m_transform; | |
1078 FloatPoint transformedPoint = ctm.inverse().mapPoint(point); | |
1079 if (!std::isfinite(transformedPoint.x()) || !std::isfinite(transformedPoint.
y())) | |
1080 return false; | |
1081 | |
1082 StrokeData strokeData; | |
1083 strokeData.setThickness(lineWidth()); | |
1084 strokeData.setLineCap(getLineCap()); | |
1085 strokeData.setLineJoin(getLineJoin()); | |
1086 strokeData.setMiterLimit(miterLimit()); | |
1087 strokeData.setLineDash(getLineDash(), lineDashOffset()); | |
1088 return path.strokeContains(transformedPoint, strokeData); | |
1089 } | |
1090 | |
1091 void CanvasRenderingContext2D::clearRect(float x, float y, float width, float he
ight) | |
1092 { | |
1093 if (!validateRectForCanvas(x, y, width, height)) | |
1094 return; | |
1095 GraphicsContext* context = drawingContext(); | |
1096 if (!context) | |
1097 return; | |
1098 if (!state().m_invertibleCTM) | |
1099 return; | |
1100 FloatRect rect(x, y, width, height); | |
1101 | |
1102 FloatRect dirtyRect; | |
1103 if (!computeDirtyRect(rect, &dirtyRect)) | |
1104 return; | |
1105 | |
1106 bool saved = false; | |
1107 if (shouldDrawShadows()) { | |
1108 context->save(); | |
1109 saved = true; | |
1110 context->clearShadow(); | |
1111 } | |
1112 context->clearRect(rect); | |
1113 if (m_hitRegionManager) | |
1114 m_hitRegionManager->removeHitRegionsInRect(rect, state().m_transform); | |
1115 if (saved) | |
1116 context->restore(); | |
1117 | |
1118 validateStateStack(); | |
1119 didDraw(dirtyRect); | |
1120 } | |
1121 | |
1122 void CanvasRenderingContext2D::fillRect(float x, float y, float width, float hei
ght) | |
1123 { | |
1124 if (!validateRectForCanvas(x, y, width, height)) | |
1125 return; | |
1126 | |
1127 GraphicsContext* c = drawingContext(); | |
1128 if (!c) | |
1129 return; | |
1130 if (!state().m_invertibleCTM) | |
1131 return; | |
1132 FloatRect clipBounds; | |
1133 if (!c->getTransformedClipBounds(&clipBounds)) | |
1134 return; | |
1135 | |
1136 // from the HTML5 Canvas spec: | |
1137 // If x0 = x1 and y0 = y1, then the linear gradient must paint nothing | |
1138 // If x0 = x1 and y0 = y1 and r0 = r1, then the radial gradient must paint n
othing | |
1139 Gradient* gradient = c->fillGradient(); | |
1140 if (gradient && gradient->isZeroSize()) | |
1141 return; | |
1142 | |
1143 FloatRect rect(x, y, width, height); | |
1144 if (rectContainsTransformedRect(rect, clipBounds)) { | |
1145 c->fillRect(rect); | |
1146 didDraw(clipBounds); | |
1147 } else { | |
1148 FloatRect dirtyRect; | |
1149 if (computeDirtyRect(rect, clipBounds, &dirtyRect)) { | |
1150 c->fillRect(rect); | |
1151 didDraw(dirtyRect); | |
1152 } | |
1153 } | |
1154 } | |
1155 | |
1156 void CanvasRenderingContext2D::strokeRect(float x, float y, float width, float h
eight) | |
1157 { | |
1158 if (!validateRectForCanvas(x, y, width, height)) | |
1159 return; | |
1160 | |
1161 if (!(state().m_lineWidth >= 0)) | |
1162 return; | |
1163 | |
1164 GraphicsContext* c = drawingContext(); | |
1165 if (!c) | |
1166 return; | |
1167 if (!state().m_invertibleCTM) | |
1168 return; | |
1169 FloatRect clipBounds; | |
1170 if (!c->getTransformedClipBounds(&clipBounds)) | |
1171 return; | |
1172 | |
1173 // If gradient size is zero, then paint nothing. | |
1174 Gradient* gradient = c->strokeGradient(); | |
1175 if (gradient && gradient->isZeroSize()) | |
1176 return; | |
1177 | |
1178 FloatRect rect(x, y, width, height); | |
1179 | |
1180 FloatRect boundingRect = rect; | |
1181 boundingRect.inflate(state().m_lineWidth / 2); | |
1182 FloatRect dirtyRect; | |
1183 if (computeDirtyRect(boundingRect, clipBounds, &dirtyRect)) { | |
1184 c->strokeRect(rect); | |
1185 didDraw(dirtyRect); | |
1186 } | |
1187 } | |
1188 | |
1189 void CanvasRenderingContext2D::setShadow(float width, float height, float blur) | |
1190 { | |
1191 setShadow(FloatSize(width, height), blur, Color::transparent); | |
1192 } | |
1193 | |
1194 void CanvasRenderingContext2D::setShadow(float width, float height, float blur,
const String& color) | |
1195 { | |
1196 RGBA32 rgba; | |
1197 if (!parseColorOrCurrentColor(rgba, color, canvas())) | |
1198 return; | |
1199 setShadow(FloatSize(width, height), blur, rgba); | |
1200 } | |
1201 | |
1202 void CanvasRenderingContext2D::setShadow(float width, float height, float blur,
float grayLevel) | |
1203 { | |
1204 setShadow(FloatSize(width, height), blur, makeRGBA32FromFloats(grayLevel, gr
ayLevel, grayLevel, 1)); | |
1205 } | |
1206 | |
1207 void CanvasRenderingContext2D::setShadow(float width, float height, float blur,
const String& color, float alpha) | |
1208 { | |
1209 RGBA32 rgba; | |
1210 if (!parseColorOrCurrentColor(rgba, color, canvas())) | |
1211 return; | |
1212 setShadow(FloatSize(width, height), blur, colorWithOverrideAlpha(rgba, alpha
)); | |
1213 } | |
1214 | |
1215 void CanvasRenderingContext2D::setShadow(float width, float height, float blur,
float grayLevel, float alpha) | |
1216 { | |
1217 setShadow(FloatSize(width, height), blur, makeRGBA32FromFloats(grayLevel, gr
ayLevel, grayLevel, alpha)); | |
1218 } | |
1219 | |
1220 void CanvasRenderingContext2D::setShadow(float width, float height, float blur,
float r, float g, float b, float a) | |
1221 { | |
1222 setShadow(FloatSize(width, height), blur, makeRGBA32FromFloats(r, g, b, a)); | |
1223 } | |
1224 | |
1225 void CanvasRenderingContext2D::setShadow(float width, float height, float blur,
float c, float m, float y, float k, float a) | |
1226 { | |
1227 setShadow(FloatSize(width, height), blur, makeRGBAFromCMYKA(c, m, y, k, a)); | |
1228 } | |
1229 | |
1230 void CanvasRenderingContext2D::clearShadow() | |
1231 { | |
1232 setShadow(FloatSize(), 0, Color::transparent); | |
1233 } | |
1234 | |
1235 void CanvasRenderingContext2D::setShadow(const FloatSize& offset, float blur, RG
BA32 color) | |
1236 { | |
1237 if (state().m_shadowOffset == offset && state().m_shadowBlur == blur && stat
e().m_shadowColor == color) | |
1238 return; | |
1239 bool wasDrawingShadows = shouldDrawShadows(); | |
1240 realizeSaves(0); | |
1241 modifiableState().m_shadowOffset = offset; | |
1242 modifiableState().m_shadowBlur = blur; | |
1243 modifiableState().m_shadowColor = color; | |
1244 if (!wasDrawingShadows && !shouldDrawShadows()) | |
1245 return; | |
1246 applyShadow(); | |
1247 } | |
1248 | |
1249 void CanvasRenderingContext2D::applyShadow() | |
1250 { | |
1251 GraphicsContext* c = drawingContext(); | |
1252 if (!c) | |
1253 return; | |
1254 | |
1255 if (shouldDrawShadows()) { | |
1256 c->setShadow(state().m_shadowOffset, state().m_shadowBlur, state().m_sha
dowColor, | |
1257 DrawLooperBuilder::ShadowIgnoresTransforms); | |
1258 } else { | |
1259 c->clearShadow(); | |
1260 } | |
1261 } | |
1262 | |
1263 bool CanvasRenderingContext2D::shouldDrawShadows() const | |
1264 { | |
1265 return alphaChannel(state().m_shadowColor) && (state().m_shadowBlur || !stat
e().m_shadowOffset.isZero()); | |
1266 } | |
1267 | |
1268 static inline FloatRect normalizeRect(const FloatRect& rect) | |
1269 { | |
1270 return FloatRect(std::min(rect.x(), rect.maxX()), | |
1271 std::min(rect.y(), rect.maxY()), | |
1272 std::max(rect.width(), -rect.width()), | |
1273 std::max(rect.height(), -rect.height())); | |
1274 } | |
1275 | |
1276 static inline void clipRectsToImageRect(const FloatRect& imageRect, FloatRect* s
rcRect, FloatRect* dstRect) | |
1277 { | |
1278 if (imageRect.contains(*srcRect)) | |
1279 return; | |
1280 | |
1281 // Compute the src to dst transform | |
1282 FloatSize scale(dstRect->size().width() / srcRect->size().width(), dstRect->
size().height() / srcRect->size().height()); | |
1283 FloatPoint scaledSrcLocation = srcRect->location(); | |
1284 scaledSrcLocation.scale(scale.width(), scale.height()); | |
1285 FloatSize offset = dstRect->location() - scaledSrcLocation; | |
1286 | |
1287 srcRect->intersect(imageRect); | |
1288 | |
1289 // To clip the destination rectangle in the same proportion, transform the c
lipped src rect | |
1290 *dstRect = *srcRect; | |
1291 dstRect->scale(scale.width(), scale.height()); | |
1292 dstRect->move(offset); | |
1293 } | |
1294 | |
1295 void CanvasRenderingContext2D::drawImage(CanvasImageSource* imageSource, float x
, float y, ExceptionState& exceptionState) | |
1296 { | |
1297 FloatSize destRectSize = imageSource->defaultDestinationSize(); | |
1298 drawImage(imageSource, x, y, destRectSize.width(), destRectSize.height(), ex
ceptionState); | |
1299 } | |
1300 | |
1301 void CanvasRenderingContext2D::drawImage(CanvasImageSource* imageSource, | |
1302 float x, float y, float width, float height, ExceptionState& exceptionState) | |
1303 { | |
1304 FloatSize sourceRectSize = imageSource->sourceSize(); | |
1305 drawImage(imageSource, 0, 0, sourceRectSize.width(), sourceRectSize.height()
, x, y, width, height, exceptionState); | |
1306 } | |
1307 | |
1308 void CanvasRenderingContext2D::drawImage(CanvasImageSource* imageSource, | |
1309 float sx, float sy, float sw, float sh, | |
1310 float dx, float dy, float dw, float dh, ExceptionState& exceptionState) | |
1311 { | |
1312 GraphicsContext* c = drawingContext(); // Do not exit yet if !c because we m
ay need to throw exceptions first | |
1313 CompositeOperator op = c ? c->compositeOperation() : CompositeSourceOver; | |
1314 blink::WebBlendMode blendMode = c ? c->blendModeOperation() : blink::WebBlen
dModeNormal; | |
1315 drawImageInternal(imageSource, sx, sy, sw, sh, dx, dy, dw, dh, exceptionStat
e, op, blendMode, c); | |
1316 } | |
1317 | |
1318 void CanvasRenderingContext2D::drawImageInternal(CanvasImageSource* imageSource, | |
1319 float sx, float sy, float sw, float sh, | |
1320 float dx, float dy, float dw, float dh, ExceptionState& exceptionState, | |
1321 CompositeOperator op, blink::WebBlendMode blendMode, GraphicsContext* c) | |
1322 { | |
1323 RefPtr<Image> image; | |
1324 SourceImageStatus sourceImageStatus = InvalidSourceImageStatus; | |
1325 if (!imageSource->isVideoElement()) { | |
1326 SourceImageMode mode = canvas() == imageSource ? CopySourceImageIfVolati
le : DontCopySourceImage; // Thunking for == | |
1327 image = imageSource->getSourceImageForCanvas(mode, &sourceImageStatus); | |
1328 if (sourceImageStatus == UndecodableSourceImageStatus) | |
1329 exceptionState.ThrowDOMException(InvalidStateError, "The HTMLImageEl
ement provided is in the 'broken' state."); | |
1330 if (!image || !image->width() || !image->height()) | |
1331 return; | |
1332 } | |
1333 | |
1334 if (!c) | |
1335 c = drawingContext(); | |
1336 if (!c) | |
1337 return; | |
1338 | |
1339 if (!state().m_invertibleCTM) | |
1340 return; | |
1341 | |
1342 if (!std::isfinite(dx) || !std::isfinite(dy) || !std::isfinite(dw) || !std::
isfinite(dh) | |
1343 || !std::isfinite(sx) || !std::isfinite(sy) || !std::isfinite(sw) || !st
d::isfinite(sh) | |
1344 || !dw || !dh || !sw || !sh) | |
1345 return; | |
1346 | |
1347 FloatRect clipBounds; | |
1348 if (!c->getTransformedClipBounds(&clipBounds)) | |
1349 return; | |
1350 | |
1351 FloatRect srcRect = normalizeRect(FloatRect(sx, sy, sw, sh)); | |
1352 FloatRect dstRect = normalizeRect(FloatRect(dx, dy, dw, dh)); | |
1353 | |
1354 clipRectsToImageRect(FloatRect(FloatPoint(), imageSource->sourceSize()), &sr
cRect, &dstRect); | |
1355 | |
1356 imageSource->adjustDrawRects(&srcRect, &dstRect); | |
1357 | |
1358 if (srcRect.isEmpty()) | |
1359 return; | |
1360 | |
1361 FloatRect dirtyRect = clipBounds; | |
1362 if (rectContainsTransformedRect(dstRect, clipBounds)) { | |
1363 c->drawImage(image.get(), dstRect, srcRect, op, blendMode); | |
1364 } else { | |
1365 FloatRect dirtyRect; | |
1366 computeDirtyRect(dstRect, clipBounds, &dirtyRect); | |
1367 c->drawImage(image.get(), dstRect, srcRect, op, blendMode); | |
1368 } | |
1369 | |
1370 if (sourceImageStatus == ExternalSourceImageStatus && isAccelerated() && can
vas()->buffer()) | |
1371 canvas()->buffer()->flush(); | |
1372 | |
1373 didDraw(dirtyRect); | |
1374 } | |
1375 | |
1376 void CanvasRenderingContext2D::drawImageFromRect(HTMLImageElement* image, | |
1377 float sx, float sy, float sw, float sh, | |
1378 float dx, float dy, float dw, float dh, | |
1379 const String& compositeOperation) | |
1380 { | |
1381 if (!image) | |
1382 return; | |
1383 CompositeOperator op; | |
1384 blink::WebBlendMode blendOp = blink::WebBlendModeNormal; | |
1385 if (!parseCompositeAndBlendOperator(compositeOperation, op, blendOp) || blen
dOp != blink::WebBlendModeNormal) | |
1386 op = CompositeSourceOver; | |
1387 | |
1388 drawImageInternal(image, sx, sy, sw, sh, dx, dy, dw, dh, IGNORE_EXCEPTION, o
p, blendOp); | |
1389 } | |
1390 | |
1391 void CanvasRenderingContext2D::clearCanvas() | |
1392 { | |
1393 FloatRect canvasRect(0, 0, canvas()->width(), canvas()->height()); | |
1394 GraphicsContext* c = drawingContext(); | |
1395 if (!c) | |
1396 return; | |
1397 | |
1398 c->save(); | |
1399 c->setCTM(canvas()->baseTransform()); | |
1400 c->clearRect(canvasRect); | |
1401 c->restore(); | |
1402 } | |
1403 | |
1404 bool CanvasRenderingContext2D::rectContainsTransformedRect(const FloatRect& rect
, const FloatRect& transformedRect) const | |
1405 { | |
1406 FloatQuad quad(rect); | |
1407 FloatQuad transformedQuad(transformedRect); | |
1408 return state().m_transform.mapQuad(quad).containsQuad(transformedQuad); | |
1409 } | |
1410 | |
1411 PassRefPtr<CanvasGradient> CanvasRenderingContext2D::createLinearGradient(float
x0, float y0, float x1, float y1) | |
1412 { | |
1413 RefPtr<CanvasGradient> gradient = CanvasGradient::create(FloatPoint(x0, y0),
FloatPoint(x1, y1)); | |
1414 return gradient.release(); | |
1415 } | |
1416 | |
1417 PassRefPtr<CanvasGradient> CanvasRenderingContext2D::createRadialGradient(float
x0, float y0, float r0, float x1, float y1, float r1, ExceptionState& exceptionS
tate) | |
1418 { | |
1419 if (r0 < 0 || r1 < 0) { | |
1420 exceptionState.ThrowDOMException(IndexSizeError, String::format("The %s
provided is less than 0.", r0 < 0 ? "r0" : "r1")); | |
1421 return nullptr; | |
1422 } | |
1423 | |
1424 RefPtr<CanvasGradient> gradient = CanvasGradient::create(FloatPoint(x0, y0),
r0, FloatPoint(x1, y1), r1); | |
1425 return gradient.release(); | |
1426 } | |
1427 | |
1428 PassRefPtr<CanvasPattern> CanvasRenderingContext2D::createPattern(CanvasImageSou
rce* imageSource, | |
1429 const String& repetitionType, ExceptionState& exceptionState) | |
1430 { | |
1431 Pattern::RepeatMode repeatMode = CanvasPattern::parseRepetitionType(repetiti
onType, exceptionState); | |
1432 if (exceptionState.had_exception()) | |
1433 return nullptr; | |
1434 | |
1435 SourceImageStatus status; | |
1436 RefPtr<Image> imageForRendering = imageSource->getSourceImageForCanvas(CopyS
ourceImageIfVolatile, &status); | |
1437 | |
1438 switch (status) { | |
1439 case NormalSourceImageStatus: | |
1440 break; | |
1441 case ZeroSizeCanvasSourceImageStatus: | |
1442 exceptionState.ThrowDOMException(InvalidStateError, String::format("The
canvas %s is 0.", imageSource->sourceSize().width() ? "height" : "width")); | |
1443 return nullptr; | |
1444 case UndecodableSourceImageStatus: | |
1445 exceptionState.ThrowDOMException(InvalidStateError, "Source image is in
the 'broken' state."); | |
1446 return nullptr; | |
1447 case InvalidSourceImageStatus: | |
1448 imageForRendering = Image::nullImage(); | |
1449 break; | |
1450 case IncompleteSourceImageStatus: | |
1451 return nullptr; | |
1452 default: | |
1453 case ExternalSourceImageStatus: // should not happen when mode is CopySource
ImageIfVolatile | |
1454 ASSERT_NOT_REACHED(); | |
1455 return nullptr; | |
1456 } | |
1457 ASSERT(imageForRendering); | |
1458 | |
1459 return CanvasPattern::create(imageForRendering.release(), repeatMode); | |
1460 } | |
1461 | |
1462 bool CanvasRenderingContext2D::computeDirtyRect(const FloatRect& localRect, Floa
tRect* dirtyRect) | |
1463 { | |
1464 FloatRect clipBounds; | |
1465 if (!drawingContext()->getTransformedClipBounds(&clipBounds)) | |
1466 return false; | |
1467 return computeDirtyRect(localRect, clipBounds, dirtyRect); | |
1468 } | |
1469 | |
1470 bool CanvasRenderingContext2D::computeDirtyRect(const FloatRect& localRect, cons
t FloatRect& transformedClipBounds, FloatRect* dirtyRect) | |
1471 { | |
1472 FloatRect canvasRect = state().m_transform.mapRect(localRect); | |
1473 | |
1474 if (alphaChannel(state().m_shadowColor)) { | |
1475 FloatRect shadowRect(canvasRect); | |
1476 shadowRect.move(state().m_shadowOffset); | |
1477 shadowRect.inflate(state().m_shadowBlur); | |
1478 canvasRect.unite(shadowRect); | |
1479 } | |
1480 | |
1481 canvasRect.intersect(transformedClipBounds); | |
1482 if (canvasRect.isEmpty()) | |
1483 return false; | |
1484 | |
1485 if (dirtyRect) | |
1486 *dirtyRect = canvasRect; | |
1487 | |
1488 return true; | |
1489 } | |
1490 | |
1491 void CanvasRenderingContext2D::didDraw(const FloatRect& dirtyRect) | |
1492 { | |
1493 if (dirtyRect.isEmpty()) | |
1494 return; | |
1495 | |
1496 canvas()->didDraw(dirtyRect); | |
1497 } | |
1498 | |
1499 GraphicsContext* CanvasRenderingContext2D::drawingContext() const | |
1500 { | |
1501 if (isContextLost()) | |
1502 return 0; | |
1503 return canvas()->drawingContext(); | |
1504 } | |
1505 | |
1506 static PassRefPtr<ImageData> createEmptyImageData(const IntSize& size) | |
1507 { | |
1508 if (RefPtr<ImageData> data = ImageData::create(size)) { | |
1509 data->data()->zeroFill(); | |
1510 return data.release(); | |
1511 } | |
1512 | |
1513 return nullptr; | |
1514 } | |
1515 | |
1516 PassRefPtr<ImageData> CanvasRenderingContext2D::createImageData(PassRefPtr<Image
Data> imageData) const | |
1517 { | |
1518 return createEmptyImageData(imageData->size()); | |
1519 } | |
1520 | |
1521 PassRefPtr<ImageData> CanvasRenderingContext2D::createImageData(float sw, float
sh, ExceptionState& exceptionState) const | |
1522 { | |
1523 if (!sw || !sh) { | |
1524 exceptionState.ThrowDOMException(IndexSizeError, String::format("The sou
rce %s is 0.", sw ? "height" : "width")); | |
1525 return nullptr; | |
1526 } | |
1527 | |
1528 FloatSize logicalSize(fabs(sw), fabs(sh)); | |
1529 if (!logicalSize.isExpressibleAsIntSize()) | |
1530 return nullptr; | |
1531 | |
1532 IntSize size = expandedIntSize(logicalSize); | |
1533 if (size.width() < 1) | |
1534 size.setWidth(1); | |
1535 if (size.height() < 1) | |
1536 size.setHeight(1); | |
1537 | |
1538 return createEmptyImageData(size); | |
1539 } | |
1540 | |
1541 PassRefPtr<ImageData> CanvasRenderingContext2D::getImageData(float sx, float sy,
float sw, float sh, ExceptionState& exceptionState) const | |
1542 { | |
1543 if (!sw || !sh) | |
1544 exceptionState.ThrowDOMException(IndexSizeError, String::format("The sou
rce %s is 0.", sw ? "height" : "width")); | |
1545 | |
1546 if (exceptionState.had_exception()) | |
1547 return nullptr; | |
1548 | |
1549 if (sw < 0) { | |
1550 sx += sw; | |
1551 sw = -sw; | |
1552 } | |
1553 if (sh < 0) { | |
1554 sy += sh; | |
1555 sh = -sh; | |
1556 } | |
1557 | |
1558 FloatRect logicalRect(sx, sy, sw, sh); | |
1559 if (logicalRect.width() < 1) | |
1560 logicalRect.setWidth(1); | |
1561 if (logicalRect.height() < 1) | |
1562 logicalRect.setHeight(1); | |
1563 if (!logicalRect.isExpressibleAsIntRect()) | |
1564 return nullptr; | |
1565 | |
1566 IntRect imageDataRect = enclosingIntRect(logicalRect); | |
1567 ImageBuffer* buffer = canvas()->buffer(); | |
1568 if (!buffer || isContextLost()) | |
1569 return createEmptyImageData(imageDataRect.size()); | |
1570 | |
1571 RefPtr<Uint8ClampedArray> byteArray = buffer->getImageData(Unmultiplied, ima
geDataRect); | |
1572 if (!byteArray) | |
1573 return nullptr; | |
1574 | |
1575 return ImageData::create(imageDataRect.size(), byteArray.release()); | |
1576 } | |
1577 | |
1578 void CanvasRenderingContext2D::putImageData(ImageData* data, float dx, float dy) | |
1579 { | |
1580 putImageData(data, dx, dy, 0, 0, data->width(), data->height()); | |
1581 } | |
1582 | |
1583 void CanvasRenderingContext2D::putImageData(ImageData* data, float dx, float dy,
float dirtyX, float dirtyY, float dirtyWidth, float dirtyHeight) | |
1584 { | |
1585 ImageBuffer* buffer = canvas()->buffer(); | |
1586 if (!buffer) | |
1587 return; | |
1588 | |
1589 if (dirtyWidth < 0) { | |
1590 dirtyX += dirtyWidth; | |
1591 dirtyWidth = -dirtyWidth; | |
1592 } | |
1593 | |
1594 if (dirtyHeight < 0) { | |
1595 dirtyY += dirtyHeight; | |
1596 dirtyHeight = -dirtyHeight; | |
1597 } | |
1598 | |
1599 FloatRect clipRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight); | |
1600 clipRect.intersect(IntRect(0, 0, data->width(), data->height())); | |
1601 IntSize destOffset(static_cast<int>(dx), static_cast<int>(dy)); | |
1602 IntRect destRect = enclosingIntRect(clipRect); | |
1603 destRect.move(destOffset); | |
1604 destRect.intersect(IntRect(IntPoint(), buffer->size())); | |
1605 if (destRect.isEmpty()) | |
1606 return; | |
1607 IntRect sourceRect(destRect); | |
1608 sourceRect.move(-destOffset); | |
1609 | |
1610 buffer->putByteArray(Unmultiplied, data->data(), IntSize(data->width(), data
->height()), sourceRect, IntPoint(destOffset)); | |
1611 | |
1612 didDraw(destRect); | |
1613 } | |
1614 | |
1615 String CanvasRenderingContext2D::font() const | |
1616 { | |
1617 if (!state().m_realizedFont) | |
1618 return defaultFont; | |
1619 | |
1620 StringBuilder serializedFont; | |
1621 const FontDescription& fontDescription = state().m_font.fontDescription(); | |
1622 | |
1623 if (fontDescription.style() == FontStyleItalic) | |
1624 serializedFont.appendLiteral("italic "); | |
1625 if (fontDescription.weight() == FontWeightBold) | |
1626 serializedFont.appendLiteral("bold "); | |
1627 if (fontDescription.variant() == FontVariantSmallCaps) | |
1628 serializedFont.appendLiteral("small-caps "); | |
1629 | |
1630 serializedFont.appendNumber(fontDescription.computedPixelSize()); | |
1631 serializedFont.appendLiteral("px"); | |
1632 | |
1633 const FontFamily& firstFontFamily = fontDescription.family(); | |
1634 for (const FontFamily* fontFamily = &firstFontFamily; fontFamily; fontFamily
= fontFamily->next()) { | |
1635 if (fontFamily != &firstFontFamily) | |
1636 serializedFont.append(','); | |
1637 | |
1638 // FIXME: We should append family directly to serializedFont rather than
building a temporary string. | |
1639 String family = fontFamily->family(); | |
1640 if (family.startsWith("-webkit-")) | |
1641 family = family.substring(8); | |
1642 if (family.contains(' ')) | |
1643 family = "\"" + family + "\""; | |
1644 | |
1645 serializedFont.append(' '); | |
1646 serializedFont.append(family); | |
1647 } | |
1648 | |
1649 return serializedFont.toString(); | |
1650 } | |
1651 | |
1652 void CanvasRenderingContext2D::setFont(const String& newFont) | |
1653 { | |
1654 // The style resolution required for rendering text is not available in fram
e-less documents. | |
1655 if (!canvas()->document().frame()) | |
1656 return; | |
1657 | |
1658 MutableStylePropertyMap::iterator i = m_fetchedFonts.find(newFont); | |
1659 RefPtr<MutableStylePropertySet> parsedStyle = i != m_fetchedFonts.end() ? i-
>value : nullptr; | |
1660 | |
1661 if (!parsedStyle) { | |
1662 parsedStyle = MutableStylePropertySet::create(); | |
1663 BisonCSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, newFont,
HTMLStandardMode, 0); | |
1664 m_fetchedFonts.add(newFont, parsedStyle); | |
1665 } | |
1666 if (parsedStyle->isEmpty()) | |
1667 return; | |
1668 | |
1669 String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont); | |
1670 | |
1671 // According to http://lists.w3.org/Archives/Public/public-html/2009Jul/0947
.html, | |
1672 // the "inherit" and "initial" values must be ignored. | |
1673 if (fontValue == "inherit" || fontValue == "initial") | |
1674 return; | |
1675 | |
1676 // The parse succeeded. | |
1677 String newFontSafeCopy(newFont); // Create a string copy since newFont can b
e deleted inside realizeSaves. | |
1678 realizeSaves(0); | |
1679 modifiableState().m_unparsedFont = newFontSafeCopy; | |
1680 | |
1681 // Map the <canvas> font into the text style. If the font uses keywords like
larger/smaller, these will work | |
1682 // relative to the canvas. | |
1683 RefPtr<RenderStyle> newStyle = RenderStyle::create(); | |
1684 if (RenderStyle* computedStyle = canvas()->computedStyle()) { | |
1685 FontDescription elementFontDescription(computedStyle->fontDescription())
; | |
1686 // Reset the computed size to avoid inheriting the zoom factor from the
<canvas> element. | |
1687 elementFontDescription.setComputedSize(elementFontDescription.specifiedS
ize()); | |
1688 newStyle->setFontDescription(elementFontDescription); | |
1689 } else { | |
1690 FontFamily fontFamily; | |
1691 fontFamily.setFamily(defaultFontFamily); | |
1692 | |
1693 FontDescription defaultFontDescription; | |
1694 defaultFontDescription.setFamily(fontFamily); | |
1695 defaultFontDescription.setSpecifiedSize(defaultFontSize); | |
1696 defaultFontDescription.setComputedSize(defaultFontSize); | |
1697 | |
1698 newStyle->setFontDescription(defaultFontDescription); | |
1699 } | |
1700 | |
1701 newStyle->font().update(newStyle->font().fontSelector()); | |
1702 | |
1703 // Now map the font property longhands into the style. | |
1704 CSSPropertyValue properties[] = { | |
1705 CSSPropertyValue(CSSPropertyFontFamily, *parsedStyle), | |
1706 CSSPropertyValue(CSSPropertyFontStyle, *parsedStyle), | |
1707 CSSPropertyValue(CSSPropertyFontVariant, *parsedStyle), | |
1708 CSSPropertyValue(CSSPropertyFontWeight, *parsedStyle), | |
1709 CSSPropertyValue(CSSPropertyFontSize, *parsedStyle), | |
1710 CSSPropertyValue(CSSPropertyLineHeight, *parsedStyle), | |
1711 }; | |
1712 | |
1713 StyleResolver& styleResolver = canvas()->document().styleResolver(); | |
1714 styleResolver.applyPropertiesToStyle(properties, WTF_ARRAY_LENGTH(properties
), newStyle.get()); | |
1715 | |
1716 #if !ENABLE(OILPAN) | |
1717 if (state().m_realizedFont) | |
1718 static_cast<CSSFontSelector*>(state().m_font.fontSelector())->unregister
ForInvalidationCallbacks(&modifiableState()); | |
1719 #endif | |
1720 modifiableState().m_font = newStyle->font(); | |
1721 modifiableState().m_font.update(canvas()->document().styleEngine()->fontSele
ctor()); | |
1722 modifiableState().m_realizedFont = true; | |
1723 canvas()->document().styleEngine()->fontSelector()->registerForInvalidationC
allbacks(&modifiableState()); | |
1724 } | |
1725 | |
1726 String CanvasRenderingContext2D::textAlign() const | |
1727 { | |
1728 return textAlignName(state().m_textAlign); | |
1729 } | |
1730 | |
1731 void CanvasRenderingContext2D::setTextAlign(const String& s) | |
1732 { | |
1733 TextAlign align; | |
1734 if (!parseTextAlign(s, align)) | |
1735 return; | |
1736 if (state().m_textAlign == align) | |
1737 return; | |
1738 realizeSaves(0); | |
1739 modifiableState().m_textAlign = align; | |
1740 } | |
1741 | |
1742 String CanvasRenderingContext2D::textBaseline() const | |
1743 { | |
1744 return textBaselineName(state().m_textBaseline); | |
1745 } | |
1746 | |
1747 void CanvasRenderingContext2D::setTextBaseline(const String& s) | |
1748 { | |
1749 TextBaseline baseline; | |
1750 if (!parseTextBaseline(s, baseline)) | |
1751 return; | |
1752 if (state().m_textBaseline == baseline) | |
1753 return; | |
1754 realizeSaves(0); | |
1755 modifiableState().m_textBaseline = baseline; | |
1756 } | |
1757 | |
1758 inline TextDirection CanvasRenderingContext2D::toTextDirection(Direction directi
on, RenderStyle** computedStyle) const | |
1759 { | |
1760 RenderStyle* style = (computedStyle || direction == DirectionInherit) ? canv
as()->computedStyle() : nullptr; | |
1761 if (computedStyle) | |
1762 *computedStyle = style; | |
1763 switch (direction) { | |
1764 case DirectionInherit: | |
1765 return style ? style->direction() : LTR; | |
1766 case DirectionRTL: | |
1767 return RTL; | |
1768 case DirectionLTR: | |
1769 return LTR; | |
1770 } | |
1771 ASSERT_NOT_REACHED(); | |
1772 return LTR; | |
1773 } | |
1774 | |
1775 String CanvasRenderingContext2D::direction() const | |
1776 { | |
1777 if (state().m_direction == DirectionInherit) | |
1778 canvas()->document().updateRenderTreeIfNeeded(); | |
1779 return toTextDirection(state().m_direction) == RTL ? rtl : ltr; | |
1780 } | |
1781 | |
1782 void CanvasRenderingContext2D::setDirection(const String& directionString) | |
1783 { | |
1784 Direction direction; | |
1785 if (directionString == inherit) | |
1786 direction = DirectionInherit; | |
1787 else if (directionString == rtl) | |
1788 direction = DirectionRTL; | |
1789 else if (directionString == ltr) | |
1790 direction = DirectionLTR; | |
1791 else | |
1792 return; | |
1793 | |
1794 if (state().m_direction == direction) | |
1795 return; | |
1796 | |
1797 realizeSaves(0); | |
1798 modifiableState().m_direction = direction; | |
1799 } | |
1800 | |
1801 void CanvasRenderingContext2D::fillText(const String& text, float x, float y) | |
1802 { | |
1803 drawTextInternal(text, x, y, true); | |
1804 } | |
1805 | |
1806 void CanvasRenderingContext2D::fillText(const String& text, float x, float y, fl
oat maxWidth) | |
1807 { | |
1808 drawTextInternal(text, x, y, true, maxWidth, true); | |
1809 } | |
1810 | |
1811 void CanvasRenderingContext2D::strokeText(const String& text, float x, float y) | |
1812 { | |
1813 drawTextInternal(text, x, y, false); | |
1814 } | |
1815 | |
1816 void CanvasRenderingContext2D::strokeText(const String& text, float x, float y,
float maxWidth) | |
1817 { | |
1818 drawTextInternal(text, x, y, false, maxWidth, true); | |
1819 } | |
1820 | |
1821 static inline bool isSpaceCharacter(UChar c) | |
1822 { | |
1823 // According to specification all space characters should be replaced with 0
x0020 space character. | |
1824 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-el
ement.html#text-preparation-algorithm | |
1825 // The space characters according to specification are : U+0020, U+0009, U+0
00A, U+000C, and U+000D. | |
1826 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-micros
yntaxes.html#space-character | |
1827 // This function returns true for 0x000B also, so that this is backward comp
atible. | |
1828 // Otherwise, the test tests/canvas/philip/tests/2d.text.draw.space.collapse
.space.html will fail | |
1829 return c == 0x0009 || c == 0x000A || c == 0x000B || c == 0x000C || c == 0x00
0D; | |
1830 } | |
1831 | |
1832 static String normalizeSpaces(const String& text) | |
1833 { | |
1834 unsigned textLength = text.length(); | |
1835 Vector<UChar> charVector(textLength); | |
1836 | |
1837 for (unsigned i = 0; i < textLength; i++) { | |
1838 if (isSpaceCharacter(text[i])) | |
1839 charVector[i] = ' '; | |
1840 else | |
1841 charVector[i] = text[i]; | |
1842 } | |
1843 | |
1844 return String(charVector); | |
1845 } | |
1846 | |
1847 PassRefPtr<TextMetrics> CanvasRenderingContext2D::measureText(const String& text
) | |
1848 { | |
1849 RefPtr<TextMetrics> metrics = TextMetrics::create(); | |
1850 | |
1851 // The style resolution required for rendering text is not available in fram
e-less documents. | |
1852 if (!canvas()->document().frame()) | |
1853 return metrics.release(); | |
1854 | |
1855 FontCachePurgePreventer fontCachePurgePreventer; | |
1856 canvas()->document().updateRenderTreeIfNeeded(); | |
1857 const Font& font = accessFont(); | |
1858 String normalizedText = normalizeSpaces(text); | |
1859 const TextRun textRun(normalizedText); | |
1860 FloatRect textBounds = font.selectionRectForText(textRun, FloatPoint(), font
.fontDescription().computedSize(), 0, -1, true); | |
1861 | |
1862 // x direction | |
1863 metrics->setWidth(font.width(textRun)); | |
1864 metrics->setActualBoundingBoxLeft(-textBounds.x()); | |
1865 metrics->setActualBoundingBoxRight(textBounds.maxX()); | |
1866 | |
1867 // y direction | |
1868 const FontMetrics& fontMetrics = font.fontMetrics(); | |
1869 const float ascent = fontMetrics.floatAscent(); | |
1870 const float descent = fontMetrics.floatDescent(); | |
1871 const float baselineY = getFontBaseline(fontMetrics); | |
1872 | |
1873 metrics->setFontBoundingBoxAscent(ascent - baselineY); | |
1874 metrics->setFontBoundingBoxDescent(descent + baselineY); | |
1875 metrics->setActualBoundingBoxAscent(-textBounds.y() - baselineY); | |
1876 metrics->setActualBoundingBoxDescent(textBounds.maxY() + baselineY); | |
1877 | |
1878 // Note : top/bottom and ascend/descend are currently the same, so there's n
o difference | |
1879 // between the EM box's top and bottom and the font's ascend and desc
end | |
1880 metrics->setEmHeightAscent(0); | |
1881 metrics->setEmHeightDescent(0); | |
1882 | |
1883 metrics->setHangingBaseline(-0.8f * ascent + baselineY); | |
1884 metrics->setAlphabeticBaseline(baselineY); | |
1885 metrics->setIdeographicBaseline(descent + baselineY); | |
1886 return metrics.release(); | |
1887 } | |
1888 | |
1889 void CanvasRenderingContext2D::drawTextInternal(const String& text, float x, flo
at y, bool fill, float maxWidth, bool useMaxWidth) | |
1890 { | |
1891 // The style resolution required for rendering text is not available in fram
e-less documents. | |
1892 if (!canvas()->document().frame()) | |
1893 return; | |
1894 | |
1895 // accessFont needs the style to be up to date, but updating style can cause
script to run, | |
1896 // which can free the GraphicsContext, so update style before grabbing the G
raphicsContext. | |
1897 // TODO(esprehn): This isn't needed in sky. | |
1898 canvas()->document().updateRenderTreeIfNeeded(); | |
1899 | |
1900 GraphicsContext* c = drawingContext(); | |
1901 if (!c) | |
1902 return; | |
1903 if (!state().m_invertibleCTM) | |
1904 return; | |
1905 if (!std::isfinite(x) | !std::isfinite(y)) | |
1906 return; | |
1907 if (useMaxWidth && (!std::isfinite(maxWidth) || maxWidth <= 0)) | |
1908 return; | |
1909 | |
1910 // If gradient size is zero, then paint nothing. | |
1911 Gradient* gradient = c->strokeGradient(); | |
1912 if (!fill && gradient && gradient->isZeroSize()) | |
1913 return; | |
1914 | |
1915 gradient = c->fillGradient(); | |
1916 if (fill && gradient && gradient->isZeroSize()) | |
1917 return; | |
1918 | |
1919 FontCachePurgePreventer fontCachePurgePreventer; | |
1920 | |
1921 const Font& font = accessFont(); | |
1922 const FontMetrics& fontMetrics = font.fontMetrics(); | |
1923 String normalizedText = normalizeSpaces(text); | |
1924 | |
1925 // FIXME: Need to turn off font smoothing. | |
1926 | |
1927 RenderStyle* computedStyle; | |
1928 TextDirection direction = toTextDirection(state().m_direction, &computedStyl
e); | |
1929 bool isRTL = direction == RTL; | |
1930 bool override = computedStyle ? isOverride(computedStyle->unicodeBidi()) : f
alse; | |
1931 | |
1932 TextRun textRun(normalizedText, 0, 0, TextRun::AllowTrailingExpansion, direc
tion, override, true); | |
1933 // Draw the item text at the correct point. | |
1934 FloatPoint location(x, y + getFontBaseline(fontMetrics)); | |
1935 | |
1936 float fontWidth = font.width(TextRun(normalizedText, 0, 0, TextRun::AllowTra
ilingExpansion, direction, override)); | |
1937 | |
1938 useMaxWidth = (useMaxWidth && maxWidth < fontWidth); | |
1939 float width = useMaxWidth ? maxWidth : fontWidth; | |
1940 | |
1941 TextAlign align = state().m_textAlign; | |
1942 if (align == StartTextAlign) | |
1943 align = isRTL ? RightTextAlign : LeftTextAlign; | |
1944 else if (align == EndTextAlign) | |
1945 align = isRTL ? LeftTextAlign : RightTextAlign; | |
1946 | |
1947 switch (align) { | |
1948 case CenterTextAlign: | |
1949 location.setX(location.x() - width / 2); | |
1950 break; | |
1951 case RightTextAlign: | |
1952 location.setX(location.x() - width); | |
1953 break; | |
1954 default: | |
1955 break; | |
1956 } | |
1957 | |
1958 // The slop built in to this mask rect matches the heuristic used in FontCGW
in.cpp for GDI text. | |
1959 TextRunPaintInfo textRunPaintInfo(textRun); | |
1960 textRunPaintInfo.bounds = FloatRect(location.x() - fontMetrics.height() / 2, | |
1961 location.y() - fontMetrics.ascent() - fo
ntMetrics.lineGap(), | |
1962 width + fontMetrics.height(), | |
1963 fontMetrics.lineSpacing()); | |
1964 if (!fill) | |
1965 inflateStrokeRect(textRunPaintInfo.bounds); | |
1966 | |
1967 c->setTextDrawingMode(fill ? TextModeFill : TextModeStroke); | |
1968 | |
1969 GraphicsContextStateSaver stateSaver(*c); | |
1970 if (useMaxWidth) { | |
1971 c->translate(location.x(), location.y()); | |
1972 // We draw when fontWidth is 0 so compositing operations (eg, a "copy" o
p) still work. | |
1973 c->scale((fontWidth > 0 ? (width / fontWidth) : 0), 1); | |
1974 location = FloatPoint(); | |
1975 } | |
1976 | |
1977 FloatRect clipBounds; | |
1978 if (!c->getTransformedClipBounds(&clipBounds)) { | |
1979 return; | |
1980 } | |
1981 | |
1982 FloatRect dirtyRect; | |
1983 if (computeDirtyRect(textRunPaintInfo.bounds, clipBounds, &dirtyRect)) { | |
1984 c->drawBidiText(font, textRunPaintInfo, location, Font::UseFallbackIfFon
tNotReady); | |
1985 didDraw(dirtyRect); | |
1986 } | |
1987 } | |
1988 | |
1989 void CanvasRenderingContext2D::inflateStrokeRect(FloatRect& rect) const | |
1990 { | |
1991 // Fast approximation of the stroke's bounding rect. | |
1992 // This yields a slightly oversized rect but is very fast | |
1993 // compared to Path::strokeBoundingRect(). | |
1994 static const float root2 = sqrtf(2); | |
1995 float delta = state().m_lineWidth / 2; | |
1996 if (state().m_lineJoin == MiterJoin) | |
1997 delta *= state().m_miterLimit; | |
1998 else if (state().m_lineCap == SquareCap) | |
1999 delta *= root2; | |
2000 | |
2001 rect.inflate(delta); | |
2002 } | |
2003 | |
2004 const Font& CanvasRenderingContext2D::accessFont() | |
2005 { | |
2006 // This needs style to be up to date, but can't assert so because drawTextIn
ternal | |
2007 // can invalidate style before this is called (e.g. drawingContext invalidat
es style). | |
2008 if (!state().m_realizedFont) | |
2009 setFont(state().m_unparsedFont); | |
2010 return state().m_font; | |
2011 } | |
2012 | |
2013 int CanvasRenderingContext2D::getFontBaseline(const FontMetrics& fontMetrics) co
nst | |
2014 { | |
2015 switch (state().m_textBaseline) { | |
2016 case TopTextBaseline: | |
2017 return fontMetrics.ascent(); | |
2018 case HangingTextBaseline: | |
2019 // According to http://wiki.apache.org/xmlgraphics-fop/LineLayout/Alignm
entHandling | |
2020 // "FOP (Formatting Objects Processor) puts the hanging baseline at 80%
of the ascender height" | |
2021 return (fontMetrics.ascent() * 4) / 5; | |
2022 case BottomTextBaseline: | |
2023 case IdeographicTextBaseline: | |
2024 return -fontMetrics.descent(); | |
2025 case MiddleTextBaseline: | |
2026 return -fontMetrics.descent() + fontMetrics.height() / 2; | |
2027 case AlphabeticTextBaseline: | |
2028 default: | |
2029 // Do nothing. | |
2030 break; | |
2031 } | |
2032 return 0; | |
2033 } | |
2034 | |
2035 void CanvasRenderingContext2D::setIsHidden(bool hidden) | |
2036 { | |
2037 ImageBuffer* buffer = canvas()->buffer(); | |
2038 if (buffer) | |
2039 buffer->setIsHidden(hidden); | |
2040 } | |
2041 | |
2042 blink::WebLayer* CanvasRenderingContext2D::platformLayer() const | |
2043 { | |
2044 return canvas()->buffer() ? canvas()->buffer()->platformLayer() : 0; | |
2045 } | |
2046 | |
2047 bool CanvasRenderingContext2D::imageSmoothingEnabled() const | |
2048 { | |
2049 return state().m_imageSmoothingEnabled; | |
2050 } | |
2051 | |
2052 void CanvasRenderingContext2D::setImageSmoothingEnabled(bool enabled) | |
2053 { | |
2054 if (enabled == state().m_imageSmoothingEnabled) | |
2055 return; | |
2056 | |
2057 GraphicsContext* c = drawingContext(); | |
2058 realizeSaves(c); | |
2059 modifiableState().m_imageSmoothingEnabled = enabled; | |
2060 if (c) | |
2061 c->setImageInterpolationQuality(enabled ? CanvasDefaultInterpolationQual
ity : InterpolationNone); | |
2062 } | |
2063 | |
2064 PassRefPtr<Canvas2DContextAttributes> CanvasRenderingContext2D::getContextAttrib
utes() const | |
2065 { | |
2066 RefPtr<Canvas2DContextAttributes> attributes = Canvas2DContextAttributes::cr
eate(); | |
2067 return attributes.release(); | |
2068 } | |
2069 | |
2070 void CanvasRenderingContext2D::drawFocusIfNeeded(Element* element) | |
2071 { | |
2072 drawFocusIfNeededInternal(m_path, element); | |
2073 } | |
2074 | |
2075 void CanvasRenderingContext2D::drawFocusIfNeeded(Path2D* path2d, Element* elemen
t) | |
2076 { | |
2077 drawFocusIfNeededInternal(path2d->path(), element); | |
2078 } | |
2079 | |
2080 void CanvasRenderingContext2D::drawFocusIfNeededInternal(const Path& path, Eleme
nt* element) | |
2081 { | |
2082 if (!focusRingCallIsValid(path, element)) | |
2083 return; | |
2084 | |
2085 // Note: we need to check document->focusedElement() rather than just callin
g | |
2086 // element->focused(), because element->focused() isn't updated until after | |
2087 // focus events fire. | |
2088 if (element->document().focusedElement() == element) | |
2089 drawFocusRing(path); | |
2090 } | |
2091 | |
2092 bool CanvasRenderingContext2D::focusRingCallIsValid(const Path& path, Element* e
lement) | |
2093 { | |
2094 ASSERT(element); | |
2095 if (!state().m_invertibleCTM) | |
2096 return false; | |
2097 if (path.isEmpty()) | |
2098 return false; | |
2099 if (!element->isDescendantOf(canvas())) | |
2100 return false; | |
2101 | |
2102 return true; | |
2103 } | |
2104 | |
2105 void CanvasRenderingContext2D::drawFocusRing(const Path& path) | |
2106 { | |
2107 GraphicsContext* c = drawingContext(); | |
2108 if (!c) | |
2109 return; | |
2110 | |
2111 // These should match the style defined in html.css. | |
2112 Color focusRingColor = RenderTheme::theme().focusRingColor(); | |
2113 const int focusRingWidth = 5; | |
2114 const int focusRingOutline = 0; | |
2115 | |
2116 // We need to add focusRingWidth to dirtyRect. | |
2117 StrokeData strokeData; | |
2118 strokeData.setThickness(focusRingWidth); | |
2119 | |
2120 FloatRect dirtyRect; | |
2121 if (!computeDirtyRect(path.strokeBoundingRect(strokeData), &dirtyRect)) | |
2122 return; | |
2123 | |
2124 c->save(); | |
2125 c->setAlphaAsFloat(1.0); | |
2126 c->clearShadow(); | |
2127 c->setCompositeOperation(CompositeSourceOver, blink::WebBlendModeNormal); | |
2128 c->drawFocusRing(path, focusRingWidth, focusRingOutline, focusRingColor); | |
2129 c->restore(); | |
2130 validateStateStack(); | |
2131 didDraw(dirtyRect); | |
2132 } | |
2133 | |
2134 void CanvasRenderingContext2D::addHitRegion(ExceptionState& exceptionState) | |
2135 { | |
2136 HitRegionOptions passOptions; | |
2137 | |
2138 RefPtr<Path2D> path2d; | |
2139 Path hitRegionPath = path2d ? path2d->path() : m_path; | |
2140 | |
2141 FloatRect clipBounds; | |
2142 GraphicsContext* context = drawingContext(); | |
2143 | |
2144 if (hitRegionPath.isEmpty() || !context || !state().m_invertibleCTM | |
2145 || !context->getTransformedClipBounds(&clipBounds)) { | |
2146 exceptionState.ThrowDOMException(NotSupportedError, "The specified path
has no pixels."); | |
2147 return; | |
2148 } | |
2149 | |
2150 hitRegionPath.transform(state().m_transform); | |
2151 | |
2152 if (hasClip()) { | |
2153 // FIXME: The hit regions should take clipping region into account. | |
2154 // However, we have no way to get the region from canvas state stack by
now. | |
2155 // See http://crbug.com/387057 | |
2156 exceptionState.ThrowDOMException(NotSupportedError, "The specified path
has no pixels."); | |
2157 return; | |
2158 } | |
2159 | |
2160 passOptions.path = hitRegionPath; | |
2161 | |
2162 // FIXME(Dictionary): Way to specify fillRule | |
2163 passOptions.fillRule = RULE_NONZERO; | |
2164 | |
2165 addHitRegionInternal(passOptions, exceptionState); | |
2166 } | |
2167 | |
2168 void CanvasRenderingContext2D::addHitRegionInternal(const HitRegionOptions& opti
ons, ExceptionState& exceptionState) | |
2169 { | |
2170 if (!m_hitRegionManager) | |
2171 m_hitRegionManager = HitRegionManager::create(); | |
2172 | |
2173 // Remove previous region (with id or control) | |
2174 m_hitRegionManager->removeHitRegionById(options.id); | |
2175 m_hitRegionManager->removeHitRegionByControl(options.control.get()); | |
2176 | |
2177 RefPtr<HitRegion> hitRegion = HitRegion::create(options); | |
2178 hitRegion->updateAccessibility(canvas()); | |
2179 m_hitRegionManager->addHitRegion(hitRegion.release()); | |
2180 } | |
2181 | |
2182 void CanvasRenderingContext2D::removeHitRegion(const String& id) | |
2183 { | |
2184 if (m_hitRegionManager) | |
2185 m_hitRegionManager->removeHitRegionById(id); | |
2186 } | |
2187 | |
2188 void CanvasRenderingContext2D::clearHitRegions() | |
2189 { | |
2190 if (m_hitRegionManager) | |
2191 m_hitRegionManager->removeAllHitRegions(); | |
2192 } | |
2193 | |
2194 HitRegion* CanvasRenderingContext2D::hitRegionAtPoint(const LayoutPoint& point) | |
2195 { | |
2196 if (m_hitRegionManager) | |
2197 return m_hitRegionManager->getHitRegionAtPoint(point); | |
2198 | |
2199 return 0; | |
2200 } | |
2201 | |
2202 unsigned CanvasRenderingContext2D::hitRegionsCount() const | |
2203 { | |
2204 if (m_hitRegionManager) | |
2205 return m_hitRegionManager->getHitRegionsCount(); | |
2206 | |
2207 return 0; | |
2208 } | |
2209 | |
2210 } // namespace blink | |
OLD | NEW |