1/*
2 * Copyright (C) 2004-2017 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 Intel Corporation. All rights reserved.
9 * Copyright (C) 2013, 2014 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 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 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 "config.h"
34#include "CanvasRenderingContext2DBase.h"
35
36#include "BitmapImage.h"
37#include "CSSFontSelector.h"
38#include "CSSParser.h"
39#include "CSSPropertyNames.h"
40#include "CachedImage.h"
41#include "CanvasGradient.h"
42#include "CanvasPattern.h"
43#include "DOMMatrix.h"
44#include "DOMMatrix2DInit.h"
45#include "DisplayListRecorder.h"
46#include "DisplayListReplayer.h"
47#include "FloatQuad.h"
48#include "HTMLCanvasElement.h"
49#include "HTMLImageElement.h"
50#include "HTMLVideoElement.h"
51#include "ImageBitmap.h"
52#include "ImageBuffer.h"
53#include "ImageData.h"
54#include "Path2D.h"
55#include "RenderElement.h"
56#include "RenderImage.h"
57#include "RenderLayer.h"
58#include "RenderTheme.h"
59#include "SecurityOrigin.h"
60#include "StrokeStyleApplier.h"
61#include "StyleProperties.h"
62#include "StyleResolver.h"
63#include "TextMetrics.h"
64#include "TextRun.h"
65#include "TypedOMCSSImageValue.h"
66#include <wtf/CheckedArithmetic.h>
67#include <wtf/IsoMallocInlines.h>
68#include <wtf/MathExtras.h>
69#include <wtf/NeverDestroyed.h>
70#include <wtf/text/StringBuilder.h>
71#include <wtf/text/TextStream.h>
72
73namespace WebCore {
74
75using namespace HTMLNames;
76
77WTF_MAKE_ISO_ALLOCATED_IMPL(CanvasRenderingContext2DBase);
78
79#if USE(CG)
80const ImageSmoothingQuality defaultSmoothingQuality = ImageSmoothingQuality::Low;
81#else
82const ImageSmoothingQuality defaultSmoothingQuality = ImageSmoothingQuality::Medium;
83#endif
84
85const int CanvasRenderingContext2DBase::DefaultFontSize = 10;
86const char* const CanvasRenderingContext2DBase::DefaultFontFamily = "sans-serif";
87const char* const CanvasRenderingContext2DBase::DefaultFont = "10px sans-serif";
88
89struct DisplayListDrawingContext {
90 WTF_MAKE_FAST_ALLOCATED;
91public:
92 GraphicsContext context;
93 DisplayList::DisplayList displayList;
94
95 DisplayListDrawingContext(GraphicsContext& context, const FloatRect& clip)
96 : DisplayListDrawingContext(context.state(), clip)
97 {
98 }
99
100 DisplayListDrawingContext(const GraphicsContextState& state, const FloatRect& clip)
101 : context([&](GraphicsContext& displayListContext) {
102 return std::make_unique<DisplayList::Recorder>(displayListContext, displayList, state, clip, AffineTransform());
103 })
104 {
105 }
106};
107
108typedef HashMap<const CanvasRenderingContext2DBase*, std::unique_ptr<DisplayList::DisplayList>> ContextDisplayListHashMap;
109
110static ContextDisplayListHashMap& contextDisplayListMap()
111{
112 static NeverDestroyed<ContextDisplayListHashMap> sharedHashMap;
113 return sharedHashMap;
114}
115
116class CanvasStrokeStyleApplier : public StrokeStyleApplier {
117public:
118 CanvasStrokeStyleApplier(CanvasRenderingContext2DBase* canvasContext)
119 : m_canvasContext(canvasContext)
120 {
121 }
122
123 void strokeStyle(GraphicsContext* c) override
124 {
125 c->setStrokeThickness(m_canvasContext->lineWidth());
126 c->setLineCap(m_canvasContext->getLineCap());
127 c->setLineJoin(m_canvasContext->getLineJoin());
128 c->setMiterLimit(m_canvasContext->miterLimit());
129 const Vector<float>& lineDash = m_canvasContext->getLineDash();
130 DashArray convertedLineDash(lineDash.size());
131 for (size_t i = 0; i < lineDash.size(); ++i)
132 convertedLineDash[i] = static_cast<DashArrayElement>(lineDash[i]);
133 c->setLineDash(convertedLineDash, m_canvasContext->lineDashOffset());
134 }
135
136private:
137 CanvasRenderingContext2DBase* m_canvasContext;
138};
139
140CanvasRenderingContext2DBase::CanvasRenderingContext2DBase(CanvasBase& canvas, bool usesCSSCompatibilityParseMode, bool usesDashboardCompatibilityMode)
141 : CanvasRenderingContext(canvas)
142 , m_stateStack(1)
143 , m_usesCSSCompatibilityParseMode(usesCSSCompatibilityParseMode)
144#if ENABLE(DASHBOARD_SUPPORT)
145 , m_usesDashboardCompatibilityMode(usesDashboardCompatibilityMode)
146#endif
147{
148#if !ENABLE(DASHBOARD_SUPPORT)
149 ASSERT_UNUSED(usesDashboardCompatibilityMode, !usesDashboardCompatibilityMode);
150#endif
151}
152
153void CanvasRenderingContext2DBase::unwindStateStack()
154{
155 // Ensure that the state stack in the ImageBuffer's context
156 // is cleared before destruction, to avoid assertions in the
157 // GraphicsContext dtor.
158 if (size_t stackSize = m_stateStack.size()) {
159 if (auto* context = canvasBase().existingDrawingContext()) {
160 while (--stackSize)
161 context->restore();
162 }
163 }
164}
165
166CanvasRenderingContext2DBase::~CanvasRenderingContext2DBase()
167{
168#if !ASSERT_DISABLED
169 unwindStateStack();
170#endif
171
172 if (UNLIKELY(tracksDisplayListReplay()))
173 contextDisplayListMap().remove(this);
174}
175
176bool CanvasRenderingContext2DBase::isAccelerated() const
177{
178#if USE(IOSURFACE_CANVAS_BACKING_STORE) || ENABLE(ACCELERATED_2D_CANVAS)
179 auto* context = canvasBase().existingDrawingContext();
180 return context && context->isAcceleratedContext();
181#else
182 return false;
183#endif
184}
185
186void CanvasRenderingContext2DBase::reset()
187{
188 unwindStateStack();
189 m_stateStack.resize(1);
190 m_stateStack.first() = State();
191 m_path.clear();
192 m_unrealizedSaveCount = 0;
193
194 m_recordingContext = nullptr;
195}
196
197CanvasRenderingContext2DBase::State::State()
198 : strokeStyle(Color::black)
199 , fillStyle(Color::black)
200 , lineWidth(1)
201 , lineCap(ButtCap)
202 , lineJoin(MiterJoin)
203 , miterLimit(10)
204 , shadowBlur(0)
205 , shadowColor(Color::transparent)
206 , globalAlpha(1)
207 , globalComposite(CompositeSourceOver)
208 , globalBlend(BlendMode::Normal)
209 , hasInvertibleTransform(true)
210 , lineDashOffset(0)
211 , imageSmoothingEnabled(true)
212 , imageSmoothingQuality(defaultSmoothingQuality)
213 , textAlign(StartTextAlign)
214 , textBaseline(AlphabeticTextBaseline)
215 , direction(Direction::Inherit)
216 , unparsedFont(DefaultFont)
217{
218}
219
220CanvasRenderingContext2DBase::State::State(const State& other)
221 : unparsedStrokeColor(other.unparsedStrokeColor)
222 , unparsedFillColor(other.unparsedFillColor)
223 , strokeStyle(other.strokeStyle)
224 , fillStyle(other.fillStyle)
225 , lineWidth(other.lineWidth)
226 , lineCap(other.lineCap)
227 , lineJoin(other.lineJoin)
228 , miterLimit(other.miterLimit)
229 , shadowOffset(other.shadowOffset)
230 , shadowBlur(other.shadowBlur)
231 , shadowColor(other.shadowColor)
232 , globalAlpha(other.globalAlpha)
233 , globalComposite(other.globalComposite)
234 , globalBlend(other.globalBlend)
235 , transform(other.transform)
236 , hasInvertibleTransform(other.hasInvertibleTransform)
237 , lineDashOffset(other.lineDashOffset)
238 , imageSmoothingEnabled(other.imageSmoothingEnabled)
239 , imageSmoothingQuality(other.imageSmoothingQuality)
240 , textAlign(other.textAlign)
241 , textBaseline(other.textBaseline)
242 , direction(other.direction)
243 , unparsedFont(other.unparsedFont)
244 , font(other.font)
245{
246}
247
248CanvasRenderingContext2DBase::State& CanvasRenderingContext2DBase::State::operator=(const State& other)
249{
250 if (this == &other)
251 return *this;
252
253 unparsedStrokeColor = other.unparsedStrokeColor;
254 unparsedFillColor = other.unparsedFillColor;
255 strokeStyle = other.strokeStyle;
256 fillStyle = other.fillStyle;
257 lineWidth = other.lineWidth;
258 lineCap = other.lineCap;
259 lineJoin = other.lineJoin;
260 miterLimit = other.miterLimit;
261 shadowOffset = other.shadowOffset;
262 shadowBlur = other.shadowBlur;
263 shadowColor = other.shadowColor;
264 globalAlpha = other.globalAlpha;
265 globalComposite = other.globalComposite;
266 globalBlend = other.globalBlend;
267 transform = other.transform;
268 hasInvertibleTransform = other.hasInvertibleTransform;
269 imageSmoothingEnabled = other.imageSmoothingEnabled;
270 imageSmoothingQuality = other.imageSmoothingQuality;
271 textAlign = other.textAlign;
272 textBaseline = other.textBaseline;
273 direction = other.direction;
274 unparsedFont = other.unparsedFont;
275 font = other.font;
276
277 return *this;
278}
279
280CanvasRenderingContext2DBase::FontProxy::~FontProxy()
281{
282 if (realized())
283 m_font.fontSelector()->unregisterForInvalidationCallbacks(*this);
284}
285
286CanvasRenderingContext2DBase::FontProxy::FontProxy(const FontProxy& other)
287 : m_font(other.m_font)
288{
289 if (realized())
290 m_font.fontSelector()->registerForInvalidationCallbacks(*this);
291}
292
293auto CanvasRenderingContext2DBase::FontProxy::operator=(const FontProxy& other) -> FontProxy&
294{
295 if (realized())
296 m_font.fontSelector()->unregisterForInvalidationCallbacks(*this);
297
298 m_font = other.m_font;
299
300 if (realized())
301 m_font.fontSelector()->registerForInvalidationCallbacks(*this);
302
303 return *this;
304}
305
306inline void CanvasRenderingContext2DBase::FontProxy::update(FontSelector& selector)
307{
308 ASSERT(&selector == m_font.fontSelector()); // This is an invariant. We should only ever be registered for callbacks on m_font.m_fonts.m_fontSelector.
309 if (realized())
310 m_font.fontSelector()->unregisterForInvalidationCallbacks(*this);
311 m_font.update(&selector);
312 if (realized())
313 m_font.fontSelector()->registerForInvalidationCallbacks(*this);
314 ASSERT(&selector == m_font.fontSelector());
315}
316
317void CanvasRenderingContext2DBase::FontProxy::fontsNeedUpdate(FontSelector& selector)
318{
319 ASSERT_ARG(selector, &selector == m_font.fontSelector());
320 ASSERT(realized());
321
322 update(selector);
323}
324
325void CanvasRenderingContext2DBase::FontProxy::initialize(FontSelector& fontSelector, const RenderStyle& newStyle)
326{
327 // Beware! m_font.fontSelector() might not point to document.fontSelector()!
328 ASSERT(newStyle.fontCascade().fontSelector() == &fontSelector);
329 if (realized())
330 m_font.fontSelector()->unregisterForInvalidationCallbacks(*this);
331 m_font = newStyle.fontCascade();
332 m_font.update(&fontSelector);
333 ASSERT(&fontSelector == m_font.fontSelector());
334 m_font.fontSelector()->registerForInvalidationCallbacks(*this);
335}
336
337const FontMetrics& CanvasRenderingContext2DBase::FontProxy::fontMetrics() const
338{
339 return m_font.fontMetrics();
340}
341
342const FontCascadeDescription& CanvasRenderingContext2DBase::FontProxy::fontDescription() const
343{
344 return m_font.fontDescription();
345}
346
347float CanvasRenderingContext2DBase::FontProxy::width(const TextRun& textRun, GlyphOverflow* overflow) const
348{
349 return m_font.width(textRun, 0, overflow);
350}
351
352void CanvasRenderingContext2DBase::FontProxy::drawBidiText(GraphicsContext& context, const TextRun& run, const FloatPoint& point, FontCascade::CustomFontNotReadyAction action) const
353{
354 context.drawBidiText(m_font, run, point, action);
355}
356
357void CanvasRenderingContext2DBase::realizeSaves()
358{
359 if (m_unrealizedSaveCount)
360 realizeSavesLoop();
361
362 if (m_unrealizedSaveCount) {
363 static NeverDestroyed<String> consoleMessage(MAKE_STATIC_STRING_IMPL("CanvasRenderingContext2D.save() has been called without a matching restore() too many times. Ignoring save()."));
364
365 canvasBase().scriptExecutionContext()->addConsoleMessage(MessageSource::Rendering, MessageLevel::Error, consoleMessage);
366 }
367}
368
369void CanvasRenderingContext2DBase::realizeSavesLoop()
370{
371 ASSERT(m_unrealizedSaveCount);
372 ASSERT(m_stateStack.size() >= 1);
373 GraphicsContext* context = drawingContext();
374 do {
375 if (m_stateStack.size() > MaxSaveCount)
376 break;
377 m_stateStack.append(state());
378 if (context)
379 context->save();
380 } while (--m_unrealizedSaveCount);
381}
382
383void CanvasRenderingContext2DBase::restore()
384{
385 if (m_unrealizedSaveCount) {
386 --m_unrealizedSaveCount;
387 return;
388 }
389 ASSERT(m_stateStack.size() >= 1);
390 if (m_stateStack.size() <= 1)
391 return;
392 m_path.transform(state().transform);
393 m_stateStack.removeLast();
394 if (Optional<AffineTransform> inverse = state().transform.inverse())
395 m_path.transform(inverse.value());
396 GraphicsContext* c = drawingContext();
397 if (!c)
398 return;
399 c->restore();
400}
401
402void CanvasRenderingContext2DBase::setStrokeStyle(CanvasStyle style)
403{
404 if (!style.isValid())
405 return;
406
407 if (state().strokeStyle.isValid() && state().strokeStyle.isEquivalentColor(style))
408 return;
409
410 if (style.isCurrentColor() && is<HTMLCanvasElement>(canvasBase())) {
411 auto& canvas = downcast<HTMLCanvasElement>(canvasBase());
412
413 if (style.hasOverrideAlpha()) {
414 // FIXME: Should not use RGBA32 here.
415 style = CanvasStyle(colorWithOverrideAlpha(currentColor(&canvas).rgb(), style.overrideAlpha()));
416 } else
417 style = CanvasStyle(currentColor(&canvas));
418 } else
419 checkOrigin(style.canvasPattern().get());
420
421 realizeSaves();
422 State& state = modifiableState();
423 state.strokeStyle = style;
424 GraphicsContext* c = drawingContext();
425 if (!c)
426 return;
427 state.strokeStyle.applyStrokeColor(*c);
428 state.unparsedStrokeColor = String();
429}
430
431void CanvasRenderingContext2DBase::setFillStyle(CanvasStyle style)
432{
433 if (!style.isValid())
434 return;
435
436 if (state().fillStyle.isValid() && state().fillStyle.isEquivalentColor(style))
437 return;
438
439 if (style.isCurrentColor() && is<HTMLCanvasElement>(canvasBase())) {
440 auto& canvas = downcast<HTMLCanvasElement>(canvasBase());
441
442 if (style.hasOverrideAlpha()) {
443 // FIXME: Should not use RGBA32 here.
444 style = CanvasStyle(colorWithOverrideAlpha(currentColor(&canvas).rgb(), style.overrideAlpha()));
445 } else
446 style = CanvasStyle(currentColor(&canvas));
447 } else
448 checkOrigin(style.canvasPattern().get());
449
450 realizeSaves();
451 State& state = modifiableState();
452 state.fillStyle = style;
453 GraphicsContext* c = drawingContext();
454 if (!c)
455 return;
456 state.fillStyle.applyFillColor(*c);
457 state.unparsedFillColor = String();
458}
459
460float CanvasRenderingContext2DBase::lineWidth() const
461{
462 return state().lineWidth;
463}
464
465void CanvasRenderingContext2DBase::setLineWidth(float width)
466{
467 if (!(std::isfinite(width) && width > 0))
468 return;
469 if (state().lineWidth == width)
470 return;
471 realizeSaves();
472 modifiableState().lineWidth = width;
473 GraphicsContext* c = drawingContext();
474 if (!c)
475 return;
476 c->setStrokeThickness(width);
477}
478
479static CanvasLineCap toCanvasLineCap(LineCap lineCap)
480{
481 switch (lineCap) {
482 case ButtCap:
483 return CanvasLineCap::Butt;
484 case RoundCap:
485 return CanvasLineCap::Round;
486 case SquareCap:
487 return CanvasLineCap::Square;
488 }
489
490 ASSERT_NOT_REACHED();
491 return CanvasLineCap::Butt;
492}
493
494static LineCap fromCanvasLineCap(CanvasLineCap canvasLineCap)
495{
496 switch (canvasLineCap) {
497 case CanvasLineCap::Butt:
498 return ButtCap;
499 case CanvasLineCap::Round:
500 return RoundCap;
501 case CanvasLineCap::Square:
502 return SquareCap;
503 }
504
505 ASSERT_NOT_REACHED();
506 return ButtCap;
507}
508
509CanvasLineCap CanvasRenderingContext2DBase::lineCap() const
510{
511 return toCanvasLineCap(state().lineCap);
512}
513
514void CanvasRenderingContext2DBase::setLineCap(CanvasLineCap canvasLineCap)
515{
516 auto lineCap = fromCanvasLineCap(canvasLineCap);
517 if (state().lineCap == lineCap)
518 return;
519 realizeSaves();
520 modifiableState().lineCap = lineCap;
521 GraphicsContext* c = drawingContext();
522 if (!c)
523 return;
524 c->setLineCap(lineCap);
525}
526
527void CanvasRenderingContext2DBase::setLineCap(const String& stringValue)
528{
529 CanvasLineCap cap;
530 if (stringValue == "butt")
531 cap = CanvasLineCap::Butt;
532 else if (stringValue == "round")
533 cap = CanvasLineCap::Round;
534 else if (stringValue == "square")
535 cap = CanvasLineCap::Square;
536 else
537 return;
538
539 setLineCap(cap);
540}
541
542static CanvasLineJoin toCanvasLineJoin(LineJoin lineJoin)
543{
544 switch (lineJoin) {
545 case RoundJoin:
546 return CanvasLineJoin::Round;
547 case BevelJoin:
548 return CanvasLineJoin::Bevel;
549 case MiterJoin:
550 return CanvasLineJoin::Miter;
551 }
552
553 ASSERT_NOT_REACHED();
554 return CanvasLineJoin::Round;
555}
556
557static LineJoin fromCanvasLineJoin(CanvasLineJoin canvasLineJoin)
558{
559 switch (canvasLineJoin) {
560 case CanvasLineJoin::Round:
561 return RoundJoin;
562 case CanvasLineJoin::Bevel:
563 return BevelJoin;
564 case CanvasLineJoin::Miter:
565 return MiterJoin;
566 }
567
568 ASSERT_NOT_REACHED();
569 return RoundJoin;
570}
571
572CanvasLineJoin CanvasRenderingContext2DBase::lineJoin() const
573{
574 return toCanvasLineJoin(state().lineJoin);
575}
576
577void CanvasRenderingContext2DBase::setLineJoin(CanvasLineJoin canvasLineJoin)
578{
579 auto lineJoin = fromCanvasLineJoin(canvasLineJoin);
580 if (state().lineJoin == lineJoin)
581 return;
582 realizeSaves();
583 modifiableState().lineJoin = lineJoin;
584 GraphicsContext* c = drawingContext();
585 if (!c)
586 return;
587 c->setLineJoin(lineJoin);
588}
589
590void CanvasRenderingContext2DBase::setLineJoin(const String& stringValue)
591{
592 CanvasLineJoin join;
593 if (stringValue == "round")
594 join = CanvasLineJoin::Round;
595 else if (stringValue == "bevel")
596 join = CanvasLineJoin::Bevel;
597 else if (stringValue == "miter")
598 join = CanvasLineJoin::Miter;
599 else
600 return;
601
602 setLineJoin(join);
603}
604
605float CanvasRenderingContext2DBase::miterLimit() const
606{
607 return state().miterLimit;
608}
609
610void CanvasRenderingContext2DBase::setMiterLimit(float limit)
611{
612 if (!(std::isfinite(limit) && limit > 0))
613 return;
614 if (state().miterLimit == limit)
615 return;
616 realizeSaves();
617 modifiableState().miterLimit = limit;
618 GraphicsContext* c = drawingContext();
619 if (!c)
620 return;
621 c->setMiterLimit(limit);
622}
623
624float CanvasRenderingContext2DBase::shadowOffsetX() const
625{
626 return state().shadowOffset.width();
627}
628
629void CanvasRenderingContext2DBase::setShadowOffsetX(float x)
630{
631 if (!std::isfinite(x))
632 return;
633 if (state().shadowOffset.width() == x)
634 return;
635 realizeSaves();
636 modifiableState().shadowOffset.setWidth(x);
637 applyShadow();
638}
639
640float CanvasRenderingContext2DBase::shadowOffsetY() const
641{
642 return state().shadowOffset.height();
643}
644
645void CanvasRenderingContext2DBase::setShadowOffsetY(float y)
646{
647 if (!std::isfinite(y))
648 return;
649 if (state().shadowOffset.height() == y)
650 return;
651 realizeSaves();
652 modifiableState().shadowOffset.setHeight(y);
653 applyShadow();
654}
655
656float CanvasRenderingContext2DBase::shadowBlur() const
657{
658 return state().shadowBlur;
659}
660
661void CanvasRenderingContext2DBase::setShadowBlur(float blur)
662{
663 if (!(std::isfinite(blur) && blur >= 0))
664 return;
665 if (state().shadowBlur == blur)
666 return;
667 realizeSaves();
668 modifiableState().shadowBlur = blur;
669 applyShadow();
670}
671
672String CanvasRenderingContext2DBase::shadowColor() const
673{
674 return Color(state().shadowColor).serialized();
675}
676
677void CanvasRenderingContext2DBase::setShadowColor(const String& colorString)
678{
679 auto& canvas = downcast<HTMLCanvasElement>(canvasBase());
680 Color color = parseColorOrCurrentColor(colorString, &canvas);
681 if (!color.isValid())
682 return;
683 if (state().shadowColor == color)
684 return;
685 realizeSaves();
686 modifiableState().shadowColor = color;
687 applyShadow();
688}
689
690const Vector<float>& CanvasRenderingContext2DBase::getLineDash() const
691{
692 return state().lineDash;
693}
694
695static bool lineDashSequenceIsValid(const Vector<float>& dash)
696{
697 for (size_t i = 0; i < dash.size(); i++) {
698 if (!std::isfinite(dash[i]) || dash[i] < 0)
699 return false;
700 }
701 return true;
702}
703
704void CanvasRenderingContext2DBase::setLineDash(const Vector<float>& dash)
705{
706 if (!lineDashSequenceIsValid(dash))
707 return;
708
709 realizeSaves();
710 modifiableState().lineDash = dash;
711 // Spec requires the concatenation of two copies the dash list when the
712 // number of elements is odd
713 if (dash.size() % 2)
714 modifiableState().lineDash.appendVector(dash);
715
716 applyLineDash();
717}
718
719void CanvasRenderingContext2DBase::setWebkitLineDash(const Vector<float>& dash)
720{
721 if (!lineDashSequenceIsValid(dash))
722 return;
723
724 realizeSaves();
725 modifiableState().lineDash = dash;
726
727 applyLineDash();
728}
729
730float CanvasRenderingContext2DBase::lineDashOffset() const
731{
732 return state().lineDashOffset;
733}
734
735void CanvasRenderingContext2DBase::setLineDashOffset(float offset)
736{
737 if (!std::isfinite(offset) || state().lineDashOffset == offset)
738 return;
739
740 realizeSaves();
741 modifiableState().lineDashOffset = offset;
742 applyLineDash();
743}
744
745void CanvasRenderingContext2DBase::applyLineDash() const
746{
747 GraphicsContext* c = drawingContext();
748 if (!c)
749 return;
750 DashArray convertedLineDash(state().lineDash.size());
751 for (size_t i = 0; i < state().lineDash.size(); ++i)
752 convertedLineDash[i] = static_cast<DashArrayElement>(state().lineDash[i]);
753 c->setLineDash(convertedLineDash, state().lineDashOffset);
754}
755
756float CanvasRenderingContext2DBase::globalAlpha() const
757{
758 return state().globalAlpha;
759}
760
761void CanvasRenderingContext2DBase::setGlobalAlpha(float alpha)
762{
763 if (!(alpha >= 0 && alpha <= 1))
764 return;
765 if (state().globalAlpha == alpha)
766 return;
767 realizeSaves();
768 modifiableState().globalAlpha = alpha;
769 GraphicsContext* c = drawingContext();
770 if (!c)
771 return;
772 c->setAlpha(alpha);
773}
774
775String CanvasRenderingContext2DBase::globalCompositeOperation() const
776{
777 return compositeOperatorName(state().globalComposite, state().globalBlend);
778}
779
780void CanvasRenderingContext2DBase::setGlobalCompositeOperation(const String& operation)
781{
782 CompositeOperator op = CompositeSourceOver;
783 BlendMode blendMode = BlendMode::Normal;
784 if (!parseCompositeAndBlendOperator(operation, op, blendMode))
785 return;
786 if ((state().globalComposite == op) && (state().globalBlend == blendMode))
787 return;
788 realizeSaves();
789 modifiableState().globalComposite = op;
790 modifiableState().globalBlend = blendMode;
791 GraphicsContext* c = drawingContext();
792 if (!c)
793 return;
794 c->setCompositeOperation(op, blendMode);
795}
796
797void CanvasRenderingContext2DBase::scale(float sx, float sy)
798{
799 GraphicsContext* c = drawingContext();
800 if (!c)
801 return;
802 if (!state().hasInvertibleTransform)
803 return;
804
805 if (!std::isfinite(sx) || !std::isfinite(sy))
806 return;
807
808 AffineTransform newTransform = state().transform;
809 newTransform.scaleNonUniform(sx, sy);
810 if (state().transform == newTransform)
811 return;
812
813 realizeSaves();
814
815 if (!sx || !sy) {
816 modifiableState().hasInvertibleTransform = false;
817 return;
818 }
819
820 modifiableState().transform = newTransform;
821 c->scale(FloatSize(sx, sy));
822 m_path.transform(AffineTransform().scaleNonUniform(1.0 / sx, 1.0 / sy));
823}
824
825void CanvasRenderingContext2DBase::rotate(float angleInRadians)
826{
827 GraphicsContext* c = drawingContext();
828 if (!c)
829 return;
830 if (!state().hasInvertibleTransform)
831 return;
832
833 if (!std::isfinite(angleInRadians))
834 return;
835
836 AffineTransform newTransform = state().transform;
837 newTransform.rotate(angleInRadians / piDouble * 180.0);
838 if (state().transform == newTransform)
839 return;
840
841 realizeSaves();
842
843 modifiableState().transform = newTransform;
844 c->rotate(angleInRadians);
845 m_path.transform(AffineTransform().rotate(-angleInRadians / piDouble * 180.0));
846}
847
848void CanvasRenderingContext2DBase::translate(float tx, float ty)
849{
850 GraphicsContext* c = drawingContext();
851 if (!c)
852 return;
853 if (!state().hasInvertibleTransform)
854 return;
855
856 if (!std::isfinite(tx) | !std::isfinite(ty))
857 return;
858
859 AffineTransform newTransform = state().transform;
860 newTransform.translate(tx, ty);
861 if (state().transform == newTransform)
862 return;
863
864 realizeSaves();
865
866 modifiableState().transform = newTransform;
867 c->translate(tx, ty);
868 m_path.transform(AffineTransform().translate(-tx, -ty));
869}
870
871void CanvasRenderingContext2DBase::transform(float m11, float m12, float m21, float m22, float dx, float dy)
872{
873 GraphicsContext* c = drawingContext();
874 if (!c)
875 return;
876 if (!state().hasInvertibleTransform)
877 return;
878
879 if (!std::isfinite(m11) | !std::isfinite(m21) | !std::isfinite(dx) | !std::isfinite(m12) | !std::isfinite(m22) | !std::isfinite(dy))
880 return;
881
882 AffineTransform transform(m11, m12, m21, m22, dx, dy);
883 AffineTransform newTransform = state().transform * transform;
884 if (state().transform == newTransform)
885 return;
886
887 realizeSaves();
888
889 if (auto inverse = transform.inverse()) {
890 modifiableState().transform = newTransform;
891 c->concatCTM(transform);
892 m_path.transform(inverse.value());
893 return;
894 }
895 modifiableState().hasInvertibleTransform = false;
896}
897
898Ref<DOMMatrix> CanvasRenderingContext2DBase::getTransform() const
899{
900 return DOMMatrix::create(state().transform.toTransformationMatrix(), DOMMatrixReadOnly::Is2D::Yes);
901}
902
903void CanvasRenderingContext2DBase::setTransform(float m11, float m12, float m21, float m22, float dx, float dy)
904{
905 GraphicsContext* c = drawingContext();
906 if (!c)
907 return;
908
909 if (!std::isfinite(m11) | !std::isfinite(m21) | !std::isfinite(dx) | !std::isfinite(m12) | !std::isfinite(m22) | !std::isfinite(dy))
910 return;
911
912 resetTransform();
913 transform(m11, m12, m21, m22, dx, dy);
914}
915
916ExceptionOr<void> CanvasRenderingContext2DBase::setTransform(DOMMatrix2DInit&& matrixInit)
917{
918 auto checkValid = DOMMatrixReadOnly::validateAndFixup(matrixInit);
919 if (checkValid.hasException())
920 return checkValid.releaseException();
921
922 setTransform(matrixInit.a.valueOr(1), matrixInit.b.valueOr(0), matrixInit.c.valueOr(0), matrixInit.d.valueOr(1), matrixInit.e.valueOr(0), matrixInit.f.valueOr(0));
923 return { };
924}
925
926void CanvasRenderingContext2DBase::resetTransform()
927{
928 GraphicsContext* c = drawingContext();
929 if (!c)
930 return;
931
932 AffineTransform ctm = state().transform;
933 bool hasInvertibleTransform = state().hasInvertibleTransform;
934
935 realizeSaves();
936
937 c->setCTM(canvasBase().baseTransform());
938 modifiableState().transform = AffineTransform();
939
940 if (hasInvertibleTransform)
941 m_path.transform(ctm);
942
943 modifiableState().hasInvertibleTransform = true;
944}
945
946void CanvasRenderingContext2DBase::setStrokeColor(const String& color, Optional<float> alpha)
947{
948 if (alpha) {
949 setStrokeStyle(CanvasStyle::createFromStringWithOverrideAlpha(color, alpha.value()));
950 return;
951 }
952
953 if (color == state().unparsedStrokeColor)
954 return;
955
956 realizeSaves();
957 setStrokeStyle(CanvasStyle::createFromString(color));
958 modifiableState().unparsedStrokeColor = color;
959}
960
961void CanvasRenderingContext2DBase::setStrokeColor(float grayLevel, float alpha)
962{
963 if (state().strokeStyle.isValid() && state().strokeStyle.isEquivalentRGBA(grayLevel, grayLevel, grayLevel, alpha))
964 return;
965 setStrokeStyle(CanvasStyle(grayLevel, alpha));
966}
967
968void CanvasRenderingContext2DBase::setStrokeColor(float r, float g, float b, float a)
969{
970 if (state().strokeStyle.isValid() && state().strokeStyle.isEquivalentRGBA(r, g, b, a))
971 return;
972 setStrokeStyle(CanvasStyle(r, g, b, a));
973}
974
975void CanvasRenderingContext2DBase::setStrokeColor(float c, float m, float y, float k, float a)
976{
977 if (state().strokeStyle.isValid() && state().strokeStyle.isEquivalentCMYKA(c, m, y, k, a))
978 return;
979 setStrokeStyle(CanvasStyle(c, m, y, k, a));
980}
981
982void CanvasRenderingContext2DBase::setFillColor(const String& color, Optional<float> alpha)
983{
984 if (alpha) {
985 setFillStyle(CanvasStyle::createFromStringWithOverrideAlpha(color, alpha.value()));
986 return;
987 }
988
989 if (color == state().unparsedFillColor)
990 return;
991
992 realizeSaves();
993 setFillStyle(CanvasStyle::createFromString(color));
994 modifiableState().unparsedFillColor = color;
995}
996
997void CanvasRenderingContext2DBase::setFillColor(float grayLevel, float alpha)
998{
999 if (state().fillStyle.isValid() && state().fillStyle.isEquivalentRGBA(grayLevel, grayLevel, grayLevel, alpha))
1000 return;
1001 setFillStyle(CanvasStyle(grayLevel, alpha));
1002}
1003
1004void CanvasRenderingContext2DBase::setFillColor(float r, float g, float b, float a)
1005{
1006 if (state().fillStyle.isValid() && state().fillStyle.isEquivalentRGBA(r, g, b, a))
1007 return;
1008 setFillStyle(CanvasStyle(r, g, b, a));
1009}
1010
1011void CanvasRenderingContext2DBase::setFillColor(float c, float m, float y, float k, float a)
1012{
1013 if (state().fillStyle.isValid() && state().fillStyle.isEquivalentCMYKA(c, m, y, k, a))
1014 return;
1015 setFillStyle(CanvasStyle(c, m, y, k, a));
1016}
1017
1018void CanvasRenderingContext2DBase::beginPath()
1019{
1020 m_path.clear();
1021}
1022
1023static bool validateRectForCanvas(float& x, float& y, float& width, float& height)
1024{
1025 if (!std::isfinite(x) | !std::isfinite(y) | !std::isfinite(width) | !std::isfinite(height))
1026 return false;
1027
1028 if (!width && !height)
1029 return false;
1030
1031 if (width < 0) {
1032 width = -width;
1033 x -= width;
1034 }
1035
1036 if (height < 0) {
1037 height = -height;
1038 y -= height;
1039 }
1040
1041 return true;
1042}
1043
1044bool CanvasRenderingContext2DBase::isFullCanvasCompositeMode(CompositeOperator op)
1045{
1046 // See 4.8.11.1.3 Compositing
1047 // CompositeSourceAtop and CompositeDestinationOut are not listed here as the platforms already
1048 // implement the specification's behavior.
1049 return op == CompositeSourceIn || op == CompositeSourceOut || op == CompositeDestinationIn || op == CompositeDestinationAtop;
1050}
1051
1052static WindRule toWindRule(CanvasFillRule rule)
1053{
1054 return rule == CanvasFillRule::Nonzero ? WindRule::NonZero : WindRule::EvenOdd;
1055}
1056
1057void CanvasRenderingContext2DBase::fill(CanvasFillRule windingRule)
1058{
1059 fillInternal(m_path, windingRule);
1060 clearPathForDashboardBackwardCompatibilityMode();
1061}
1062
1063void CanvasRenderingContext2DBase::stroke()
1064{
1065 strokeInternal(m_path);
1066 clearPathForDashboardBackwardCompatibilityMode();
1067}
1068
1069void CanvasRenderingContext2DBase::clip(CanvasFillRule windingRule)
1070{
1071 clipInternal(m_path, windingRule);
1072 clearPathForDashboardBackwardCompatibilityMode();
1073}
1074
1075void CanvasRenderingContext2DBase::fill(Path2D& path, CanvasFillRule windingRule)
1076{
1077 fillInternal(path.path(), windingRule);
1078}
1079
1080void CanvasRenderingContext2DBase::stroke(Path2D& path)
1081{
1082 strokeInternal(path.path());
1083}
1084
1085void CanvasRenderingContext2DBase::clip(Path2D& path, CanvasFillRule windingRule)
1086{
1087 clipInternal(path.path(), windingRule);
1088}
1089
1090void CanvasRenderingContext2DBase::fillInternal(const Path& path, CanvasFillRule windingRule)
1091{
1092 auto* c = drawingContext();
1093 if (!c)
1094 return;
1095 if (!state().hasInvertibleTransform)
1096 return;
1097
1098 // If gradient size is zero, then paint nothing.
1099 auto gradient = c->fillGradient();
1100 if (gradient && gradient->isZeroSize())
1101 return;
1102
1103 if (!path.isEmpty()) {
1104 auto savedFillRule = c->fillRule();
1105 c->setFillRule(toWindRule(windingRule));
1106
1107 if (isFullCanvasCompositeMode(state().globalComposite)) {
1108 beginCompositeLayer();
1109 c->fillPath(path);
1110 endCompositeLayer();
1111 didDrawEntireCanvas();
1112 } else if (state().globalComposite == CompositeCopy) {
1113 clearCanvas();
1114 c->fillPath(path);
1115 didDrawEntireCanvas();
1116 } else {
1117 c->fillPath(path);
1118 didDraw(path.fastBoundingRect());
1119 }
1120
1121 c->setFillRule(savedFillRule);
1122 }
1123}
1124
1125void CanvasRenderingContext2DBase::strokeInternal(const Path& path)
1126{
1127 auto* c = drawingContext();
1128 if (!c)
1129 return;
1130 if (!state().hasInvertibleTransform)
1131 return;
1132
1133 // If gradient size is zero, then paint nothing.
1134 auto gradient = c->strokeGradient();
1135 if (gradient && gradient->isZeroSize())
1136 return;
1137
1138 if (!path.isEmpty()) {
1139 if (isFullCanvasCompositeMode(state().globalComposite)) {
1140 beginCompositeLayer();
1141 c->strokePath(path);
1142 endCompositeLayer();
1143 didDrawEntireCanvas();
1144 } else if (state().globalComposite == CompositeCopy) {
1145 clearCanvas();
1146 c->strokePath(path);
1147 didDrawEntireCanvas();
1148 } else {
1149 FloatRect dirtyRect = path.fastBoundingRect();
1150 inflateStrokeRect(dirtyRect);
1151 c->strokePath(path);
1152 didDraw(dirtyRect);
1153 }
1154 }
1155}
1156
1157void CanvasRenderingContext2DBase::clipInternal(const Path& path, CanvasFillRule windingRule)
1158{
1159 auto* c = drawingContext();
1160 if (!c)
1161 return;
1162 if (!state().hasInvertibleTransform)
1163 return;
1164
1165 realizeSaves();
1166 c->canvasClip(path, toWindRule(windingRule));
1167}
1168
1169void CanvasRenderingContext2DBase::beginCompositeLayer()
1170{
1171#if !USE(CAIRO)
1172 drawingContext()->beginTransparencyLayer(1);
1173#endif
1174}
1175
1176void CanvasRenderingContext2DBase::endCompositeLayer()
1177{
1178#if !USE(CAIRO)
1179 drawingContext()->endTransparencyLayer();
1180#endif
1181}
1182
1183bool CanvasRenderingContext2DBase::isPointInPath(float x, float y, CanvasFillRule windingRule)
1184{
1185 return isPointInPathInternal(m_path, x, y, windingRule);
1186}
1187
1188bool CanvasRenderingContext2DBase::isPointInStroke(float x, float y)
1189{
1190 return isPointInStrokeInternal(m_path, x, y);
1191}
1192
1193bool CanvasRenderingContext2DBase::isPointInPath(Path2D& path, float x, float y, CanvasFillRule windingRule)
1194{
1195 return isPointInPathInternal(path.path(), x, y, windingRule);
1196}
1197
1198bool CanvasRenderingContext2DBase::isPointInStroke(Path2D& path, float x, float y)
1199{
1200 return isPointInStrokeInternal(path.path(), x, y);
1201}
1202
1203bool CanvasRenderingContext2DBase::isPointInPathInternal(const Path& path, float x, float y, CanvasFillRule windingRule)
1204{
1205 auto* c = drawingContext();
1206 if (!c)
1207 return false;
1208 if (!state().hasInvertibleTransform)
1209 return false;
1210
1211 auto transformedPoint = state().transform.inverse().valueOr(AffineTransform()).mapPoint(FloatPoint(x, y));
1212
1213 if (!std::isfinite(transformedPoint.x()) || !std::isfinite(transformedPoint.y()))
1214 return false;
1215
1216 return path.contains(transformedPoint, toWindRule(windingRule));
1217}
1218
1219bool CanvasRenderingContext2DBase::isPointInStrokeInternal(const Path& path, float x, float y)
1220{
1221 auto* c = drawingContext();
1222 if (!c)
1223 return false;
1224 if (!state().hasInvertibleTransform)
1225 return false;
1226
1227 auto transformedPoint = state().transform.inverse().valueOr(AffineTransform()).mapPoint(FloatPoint(x, y));
1228 if (!std::isfinite(transformedPoint.x()) || !std::isfinite(transformedPoint.y()))
1229 return false;
1230
1231 CanvasStrokeStyleApplier applier(this);
1232 return path.strokeContains(&applier, transformedPoint);
1233}
1234
1235void CanvasRenderingContext2DBase::clearRect(float x, float y, float width, float height)
1236{
1237 if (!validateRectForCanvas(x, y, width, height))
1238 return;
1239 auto* context = drawingContext();
1240 if (!context)
1241 return;
1242 if (!state().hasInvertibleTransform)
1243 return;
1244 FloatRect rect(x, y, width, height);
1245
1246 bool saved = false;
1247 if (shouldDrawShadows()) {
1248 context->save();
1249 saved = true;
1250 context->setLegacyShadow(FloatSize(), 0, Color::transparent);
1251 }
1252 if (state().globalAlpha != 1) {
1253 if (!saved) {
1254 context->save();
1255 saved = true;
1256 }
1257 context->setAlpha(1);
1258 }
1259 if (state().globalComposite != CompositeSourceOver) {
1260 if (!saved) {
1261 context->save();
1262 saved = true;
1263 }
1264 context->setCompositeOperation(CompositeSourceOver);
1265 }
1266 context->clearRect(rect);
1267 if (saved)
1268 context->restore();
1269 didDraw(rect);
1270}
1271
1272void CanvasRenderingContext2DBase::fillRect(float x, float y, float width, float height)
1273{
1274 if (!validateRectForCanvas(x, y, width, height))
1275 return;
1276
1277 auto* c = drawingContext();
1278 if (!c)
1279 return;
1280 if (!state().hasInvertibleTransform)
1281 return;
1282
1283 // from the HTML5 Canvas spec:
1284 // If x0 = x1 and y0 = y1, then the linear gradient must paint nothing
1285 // If x0 = x1 and y0 = y1 and r0 = r1, then the radial gradient must paint nothing
1286 auto gradient = c->fillGradient();
1287 if (gradient && gradient->isZeroSize())
1288 return;
1289
1290 FloatRect rect(x, y, width, height);
1291
1292 if (rectContainsCanvas(rect)) {
1293 c->fillRect(rect);
1294 didDrawEntireCanvas();
1295 } else if (isFullCanvasCompositeMode(state().globalComposite)) {
1296 beginCompositeLayer();
1297 c->fillRect(rect);
1298 endCompositeLayer();
1299 didDrawEntireCanvas();
1300 } else if (state().globalComposite == CompositeCopy) {
1301 clearCanvas();
1302 c->fillRect(rect);
1303 didDrawEntireCanvas();
1304 } else {
1305 c->fillRect(rect);
1306 didDraw(rect);
1307 }
1308}
1309
1310void CanvasRenderingContext2DBase::strokeRect(float x, float y, float width, float height)
1311{
1312 if (!validateRectForCanvas(x, y, width, height))
1313 return;
1314
1315 auto* c = drawingContext();
1316 if (!c)
1317 return;
1318 if (!state().hasInvertibleTransform)
1319 return;
1320 if (!(state().lineWidth >= 0))
1321 return;
1322
1323 // If gradient size is zero, then paint nothing.
1324 auto gradient = c->strokeGradient();
1325 if (gradient && gradient->isZeroSize())
1326 return;
1327
1328 FloatRect rect(x, y, width, height);
1329 if (isFullCanvasCompositeMode(state().globalComposite)) {
1330 beginCompositeLayer();
1331 c->strokeRect(rect, state().lineWidth);
1332 endCompositeLayer();
1333 didDrawEntireCanvas();
1334 } else if (state().globalComposite == CompositeCopy) {
1335 clearCanvas();
1336 c->strokeRect(rect, state().lineWidth);
1337 didDrawEntireCanvas();
1338 } else {
1339 FloatRect boundingRect = rect;
1340 boundingRect.inflate(state().lineWidth / 2);
1341 c->strokeRect(rect, state().lineWidth);
1342 didDraw(boundingRect);
1343 }
1344}
1345
1346void CanvasRenderingContext2DBase::setShadow(float width, float height, float blur, const String& colorString, Optional<float> alpha)
1347{
1348 Color color = Color::transparent;
1349 if (!colorString.isNull()) {
1350 auto& canvas = downcast<HTMLCanvasElement>(canvasBase());
1351 color = parseColorOrCurrentColor(colorString, &canvas);
1352 if (!color.isValid())
1353 return;
1354 }
1355 // FIXME: Should not use RGBA32 here.
1356 setShadow(FloatSize(width, height), blur, colorWithOverrideAlpha(color.rgb(), alpha));
1357}
1358
1359void CanvasRenderingContext2DBase::setShadow(float width, float height, float blur, float grayLevel, float alpha)
1360{
1361 setShadow(FloatSize(width, height), blur, Color(grayLevel, grayLevel, grayLevel, alpha));
1362}
1363
1364void CanvasRenderingContext2DBase::setShadow(float width, float height, float blur, float r, float g, float b, float a)
1365{
1366 setShadow(FloatSize(width, height), blur, Color(r, g, b, a));
1367}
1368
1369void CanvasRenderingContext2DBase::setShadow(float width, float height, float blur, float c, float m, float y, float k, float a)
1370{
1371 setShadow(FloatSize(width, height), blur, Color(c, m, y, k, a));
1372}
1373
1374void CanvasRenderingContext2DBase::clearShadow()
1375{
1376 setShadow(FloatSize(), 0, Color::transparent);
1377}
1378
1379void CanvasRenderingContext2DBase::setShadow(const FloatSize& offset, float blur, const Color& color)
1380{
1381 if (state().shadowOffset == offset && state().shadowBlur == blur && state().shadowColor == color)
1382 return;
1383 bool wasDrawingShadows = shouldDrawShadows();
1384 realizeSaves();
1385 modifiableState().shadowOffset = offset;
1386 modifiableState().shadowBlur = blur;
1387 modifiableState().shadowColor = color;
1388 if (!wasDrawingShadows && !shouldDrawShadows())
1389 return;
1390 applyShadow();
1391}
1392
1393void CanvasRenderingContext2DBase::applyShadow()
1394{
1395 auto* c = drawingContext();
1396 if (!c)
1397 return;
1398
1399 if (shouldDrawShadows()) {
1400 float width = state().shadowOffset.width();
1401 float height = state().shadowOffset.height();
1402 c->setLegacyShadow(FloatSize(width, -height), state().shadowBlur, state().shadowColor);
1403 } else
1404 c->setLegacyShadow(FloatSize(), 0, Color::transparent);
1405}
1406
1407bool CanvasRenderingContext2DBase::shouldDrawShadows() const
1408{
1409 return state().shadowColor.isVisible() && (state().shadowBlur || !state().shadowOffset.isZero());
1410}
1411
1412enum class ImageSizeType { AfterDevicePixelRatio, BeforeDevicePixelRatio };
1413static LayoutSize size(HTMLImageElement& element, ImageSizeType sizeType = ImageSizeType::BeforeDevicePixelRatio)
1414{
1415 LayoutSize size;
1416 if (auto* cachedImage = element.cachedImage()) {
1417 size = cachedImage->imageSizeForRenderer(element.renderer(), 1.0f); // FIXME: Not sure about this.
1418 if (sizeType == ImageSizeType::AfterDevicePixelRatio && is<RenderImage>(element.renderer()) && cachedImage->image() && !cachedImage->image()->hasRelativeWidth())
1419 size.scale(downcast<RenderImage>(*element.renderer()).imageDevicePixelRatio());
1420 }
1421 return size;
1422}
1423
1424static inline FloatSize size(HTMLCanvasElement& canvasElement)
1425{
1426 return canvasElement.size();
1427}
1428
1429static inline FloatSize size(ImageBitmap& imageBitmap)
1430{
1431 return FloatSize { static_cast<float>(imageBitmap.width()), static_cast<float>(imageBitmap.height()) };
1432}
1433
1434#if ENABLE(VIDEO)
1435
1436static inline FloatSize size(HTMLVideoElement& video)
1437{
1438 auto player = video.player();
1439 if (!player)
1440 return { };
1441 return player->naturalSize();
1442}
1443
1444#endif
1445
1446#if ENABLE(CSS_TYPED_OM)
1447static inline FloatSize size(TypedOMCSSImageValue& image)
1448{
1449 auto* cachedImage = image.image();
1450 if (!cachedImage)
1451 return FloatSize();
1452
1453 return cachedImage->imageSizeForRenderer(nullptr, 1.0f);
1454}
1455#endif
1456
1457static inline FloatRect normalizeRect(const FloatRect& rect)
1458{
1459 return FloatRect(std::min(rect.x(), rect.maxX()),
1460 std::min(rect.y(), rect.maxY()),
1461 std::max(rect.width(), -rect.width()),
1462 std::max(rect.height(), -rect.height()));
1463}
1464
1465ExceptionOr<void> CanvasRenderingContext2DBase::drawImage(CanvasImageSource&& image, float dx, float dy)
1466{
1467 return WTF::switchOn(image,
1468 [&] (RefPtr<HTMLImageElement>& imageElement) -> ExceptionOr<void> {
1469 LayoutSize destRectSize = size(*imageElement, ImageSizeType::AfterDevicePixelRatio);
1470 LayoutSize sourceRectSize = size(*imageElement, ImageSizeType::BeforeDevicePixelRatio);
1471 return this->drawImage(*imageElement, FloatRect { 0, 0, sourceRectSize.width(), sourceRectSize.height() }, FloatRect { dx, dy, destRectSize.width(), destRectSize.height() });
1472 },
1473 [&] (auto& element) -> ExceptionOr<void> {
1474 FloatSize elementSize = size(*element);
1475 return this->drawImage(*element, FloatRect { 0, 0, elementSize.width(), elementSize.height() }, FloatRect { dx, dy, elementSize.width(), elementSize.height() });
1476 }
1477 );
1478}
1479
1480ExceptionOr<void> CanvasRenderingContext2DBase::drawImage(CanvasImageSource&& image, float dx, float dy, float dw, float dh)
1481{
1482 return WTF::switchOn(image,
1483 [&] (auto& element) -> ExceptionOr<void> {
1484 FloatSize elementSize = size(*element);
1485 return this->drawImage(*element, FloatRect { 0, 0, elementSize.width(), elementSize.height() }, FloatRect { dx, dy, dw, dh });
1486 }
1487 );
1488}
1489
1490ExceptionOr<void> CanvasRenderingContext2DBase::drawImage(CanvasImageSource&& image, float sx, float sy, float sw, float sh, float dx, float dy, float dw, float dh)
1491{
1492 return WTF::switchOn(image,
1493 [&] (auto& element) -> ExceptionOr<void> {
1494 return this->drawImage(*element, FloatRect { sx, sy, sw, sh }, FloatRect { dx, dy, dw, dh });
1495 }
1496 );
1497}
1498
1499ExceptionOr<void> CanvasRenderingContext2DBase::drawImage(HTMLImageElement& imageElement, const FloatRect& srcRect, const FloatRect& dstRect)
1500{
1501 return drawImage(imageElement, srcRect, dstRect, state().globalComposite, state().globalBlend);
1502}
1503
1504ExceptionOr<void> CanvasRenderingContext2DBase::drawImage(HTMLImageElement& imageElement, const FloatRect& srcRect, const FloatRect& dstRect, const CompositeOperator& op, const BlendMode& blendMode)
1505{
1506 if (!imageElement.complete())
1507 return { };
1508 FloatRect imageRect = FloatRect(FloatPoint(), size(imageElement, ImageSizeType::BeforeDevicePixelRatio));
1509
1510 auto result = drawImage(imageElement.document(), imageElement.cachedImage(), imageElement.renderer(), imageRect, srcRect, dstRect, op, blendMode);
1511
1512 if (!result.hasException())
1513 checkOrigin(&imageElement);
1514 return result;
1515}
1516
1517#if ENABLE(CSS_TYPED_OM)
1518ExceptionOr<void> CanvasRenderingContext2DBase::drawImage(TypedOMCSSImageValue& image, const FloatRect& srcRect, const FloatRect& dstRect)
1519{
1520 auto* cachedImage = image.image();
1521 if (!cachedImage || !image.document())
1522 return { };
1523 FloatRect imageRect = FloatRect(FloatPoint(), size(image));
1524
1525 auto result = drawImage(*image.document(), cachedImage, nullptr, imageRect, srcRect, dstRect, state().globalComposite, state().globalBlend);
1526
1527 if (!result.hasException())
1528 checkOrigin(image);
1529 return result;
1530}
1531#endif
1532
1533ExceptionOr<void> CanvasRenderingContext2DBase::drawImage(Document& document, CachedImage* cachedImage, const RenderObject* renderer, const FloatRect& imageRect, const FloatRect& srcRect, const FloatRect& dstRect, const CompositeOperator& op, const BlendMode& blendMode)
1534{
1535 if (!std::isfinite(dstRect.x()) || !std::isfinite(dstRect.y()) || !std::isfinite(dstRect.width()) || !std::isfinite(dstRect.height())
1536 || !std::isfinite(srcRect.x()) || !std::isfinite(srcRect.y()) || !std::isfinite(srcRect.width()) || !std::isfinite(srcRect.height()))
1537 return { };
1538
1539 if (!dstRect.width() || !dstRect.height())
1540 return { };
1541
1542 FloatRect normalizedSrcRect = normalizeRect(srcRect);
1543 FloatRect normalizedDstRect = normalizeRect(dstRect);
1544
1545 if (!srcRect.width() || !srcRect.height())
1546 return Exception { IndexSizeError };
1547
1548 // When the source rectangle is outside the source image, the source rectangle must be clipped
1549 // to the source image and the destination rectangle must be clipped in the same proportion.
1550 FloatRect originalNormalizedSrcRect = normalizedSrcRect;
1551 normalizedSrcRect.intersect(imageRect);
1552 if (normalizedSrcRect.isEmpty())
1553 return { };
1554
1555 if (normalizedSrcRect != originalNormalizedSrcRect) {
1556 normalizedDstRect.setWidth(normalizedDstRect.width() * normalizedSrcRect.width() / originalNormalizedSrcRect.width());
1557 normalizedDstRect.setHeight(normalizedDstRect.height() * normalizedSrcRect.height() / originalNormalizedSrcRect.height());
1558 if (normalizedDstRect.isEmpty())
1559 return { };
1560 }
1561
1562 GraphicsContext* c = drawingContext();
1563 if (!c)
1564 return { };
1565 if (!state().hasInvertibleTransform)
1566 return { };
1567
1568 if (!cachedImage)
1569 return { };
1570
1571 RefPtr<Image> image = cachedImage->imageForRenderer(renderer);
1572 if (!image)
1573 return { };
1574
1575 ImageObserver* observer = image->imageObserver();
1576
1577 if (image->isSVGImage()) {
1578 image->setImageObserver(nullptr);
1579 image->setContainerSize(imageRect.size());
1580 }
1581
1582 if (image->isBitmapImage())
1583 downcast<BitmapImage>(*image).updateFromSettings(document.settings());
1584
1585 if (rectContainsCanvas(normalizedDstRect)) {
1586 c->drawImage(*image, normalizedDstRect, normalizedSrcRect, ImagePaintingOptions(op, blendMode));
1587 didDrawEntireCanvas();
1588 } else if (isFullCanvasCompositeMode(op)) {
1589 fullCanvasCompositedDrawImage(*image, normalizedDstRect, normalizedSrcRect, op);
1590 didDrawEntireCanvas();
1591 } else if (op == CompositeCopy) {
1592 clearCanvas();
1593 c->drawImage(*image, normalizedDstRect, normalizedSrcRect, ImagePaintingOptions(op, blendMode));
1594 didDrawEntireCanvas();
1595 } else {
1596 c->drawImage(*image, normalizedDstRect, normalizedSrcRect, ImagePaintingOptions(op, blendMode));
1597 didDraw(normalizedDstRect);
1598 }
1599
1600 if (image->isSVGImage())
1601 image->setImageObserver(observer);
1602
1603 return { };
1604}
1605
1606ExceptionOr<void> CanvasRenderingContext2DBase::drawImage(HTMLCanvasElement& sourceCanvas, const FloatRect& srcRect, const FloatRect& dstRect)
1607{
1608 FloatRect srcCanvasRect = FloatRect(FloatPoint(), sourceCanvas.size());
1609
1610 if (!srcCanvasRect.width() || !srcCanvasRect.height())
1611 return Exception { InvalidStateError };
1612
1613 if (!srcRect.width() || !srcRect.height())
1614 return Exception { IndexSizeError };
1615
1616 if (!srcCanvasRect.contains(normalizeRect(srcRect)) || !dstRect.width() || !dstRect.height())
1617 return { };
1618
1619 GraphicsContext* c = drawingContext();
1620 if (!c)
1621 return { };
1622 if (!state().hasInvertibleTransform)
1623 return { };
1624
1625 // FIXME: Do this through platform-independent GraphicsContext API.
1626 ImageBuffer* buffer = sourceCanvas.buffer();
1627 if (!buffer)
1628 return { };
1629
1630 checkOrigin(&sourceCanvas);
1631
1632#if ENABLE(ACCELERATED_2D_CANVAS)
1633 // If we're drawing from one accelerated canvas 2d to another, avoid calling sourceCanvas.makeRenderingResultsAvailable()
1634 // as that will do a readback to software.
1635 RefPtr<CanvasRenderingContext> sourceContext = sourceCanvas.renderingContext();
1636 // FIXME: Implement an accelerated path for drawing from a WebGL canvas to a 2d canvas when possible.
1637 if (!isAccelerated() || !sourceContext || !sourceContext->isAccelerated() || !sourceContext->is2d())
1638 sourceCanvas.makeRenderingResultsAvailable();
1639#else
1640 sourceCanvas.makeRenderingResultsAvailable();
1641#endif
1642
1643 if (rectContainsCanvas(dstRect)) {
1644 c->drawImageBuffer(*buffer, dstRect, srcRect, ImagePaintingOptions(state().globalComposite, state().globalBlend));
1645 didDrawEntireCanvas();
1646 } else if (isFullCanvasCompositeMode(state().globalComposite)) {
1647 fullCanvasCompositedDrawImage(*buffer, dstRect, srcRect, state().globalComposite);
1648 didDrawEntireCanvas();
1649 } else if (state().globalComposite == CompositeCopy) {
1650 if (&sourceCanvas == &canvasBase()) {
1651 if (auto copy = buffer->copyRectToBuffer(srcRect, ColorSpaceSRGB, *c)) {
1652 clearCanvas();
1653 c->drawImageBuffer(*copy, dstRect, { { }, srcRect.size() }, ImagePaintingOptions(state().globalComposite, state().globalBlend));
1654 }
1655 } else {
1656 clearCanvas();
1657 c->drawImageBuffer(*buffer, dstRect, srcRect, ImagePaintingOptions(state().globalComposite, state().globalBlend));
1658 }
1659 didDrawEntireCanvas();
1660 } else {
1661 c->drawImageBuffer(*buffer, dstRect, srcRect, ImagePaintingOptions(state().globalComposite, state().globalBlend));
1662 didDraw(dstRect);
1663 }
1664
1665 return { };
1666}
1667
1668#if ENABLE(VIDEO)
1669
1670ExceptionOr<void> CanvasRenderingContext2DBase::drawImage(HTMLVideoElement& video, const FloatRect& srcRect, const FloatRect& dstRect)
1671{
1672 if (video.readyState() == HTMLMediaElement::HAVE_NOTHING || video.readyState() == HTMLMediaElement::HAVE_METADATA)
1673 return { };
1674
1675 FloatRect videoRect = FloatRect(FloatPoint(), size(video));
1676 if (!srcRect.width() || !srcRect.height())
1677 return Exception { IndexSizeError };
1678
1679 if (!videoRect.contains(normalizeRect(srcRect)) || !dstRect.width() || !dstRect.height())
1680 return { };
1681
1682 GraphicsContext* c = drawingContext();
1683 if (!c)
1684 return { };
1685 if (!state().hasInvertibleTransform)
1686 return { };
1687
1688 checkOrigin(&video);
1689
1690#if USE(CG) || (ENABLE(ACCELERATED_2D_CANVAS) && USE(GSTREAMER_GL) && USE(CAIRO))
1691 if (NativeImagePtr image = video.nativeImageForCurrentTime()) {
1692 c->drawNativeImage(image, FloatSize(video.videoWidth(), video.videoHeight()), dstRect, srcRect);
1693 if (rectContainsCanvas(dstRect))
1694 didDrawEntireCanvas();
1695 else
1696 didDraw(dstRect);
1697
1698 return { };
1699 }
1700#endif
1701
1702 GraphicsContextStateSaver stateSaver(*c);
1703 c->clip(dstRect);
1704 c->translate(dstRect.location());
1705 c->scale(FloatSize(dstRect.width() / srcRect.width(), dstRect.height() / srcRect.height()));
1706 c->translate(-srcRect.location());
1707 video.paintCurrentFrameInContext(*c, FloatRect(FloatPoint(), size(video)));
1708 stateSaver.restore();
1709 didDraw(dstRect);
1710
1711 return { };
1712}
1713
1714#endif
1715
1716ExceptionOr<void> CanvasRenderingContext2DBase::drawImage(ImageBitmap& imageBitmap, const FloatRect& srcRect, const FloatRect& dstRect)
1717{
1718 if (!imageBitmap.width() || !imageBitmap.height())
1719 return Exception { InvalidStateError };
1720
1721 if (!srcRect.width() || !srcRect.height())
1722 return Exception { IndexSizeError };
1723
1724 FloatRect srcBitmapRect = FloatRect(FloatPoint(), FloatSize(imageBitmap.width(), imageBitmap.height()));
1725
1726 if (!srcBitmapRect.contains(normalizeRect(srcRect)) || !dstRect.width() || !dstRect.height())
1727 return { };
1728
1729 GraphicsContext* c = drawingContext();
1730 if (!c)
1731 return { };
1732 if (!state().hasInvertibleTransform)
1733 return { };
1734
1735 ImageBuffer* buffer = imageBitmap.buffer();
1736 if (!buffer)
1737 return { };
1738
1739 checkOrigin(&imageBitmap);
1740
1741 if (rectContainsCanvas(dstRect)) {
1742 c->drawImageBuffer(*buffer, dstRect, srcRect, ImagePaintingOptions(state().globalComposite, state().globalBlend));
1743 didDrawEntireCanvas();
1744 } else if (isFullCanvasCompositeMode(state().globalComposite)) {
1745 fullCanvasCompositedDrawImage(*buffer, dstRect, srcRect, state().globalComposite);
1746 didDrawEntireCanvas();
1747 } else if (state().globalComposite == CompositeCopy) {
1748 clearCanvas();
1749 c->drawImageBuffer(*buffer, dstRect, srcRect, ImagePaintingOptions(state().globalComposite, state().globalBlend));
1750 didDrawEntireCanvas();
1751 } else {
1752 c->drawImageBuffer(*buffer, dstRect, srcRect, ImagePaintingOptions(state().globalComposite, state().globalBlend));
1753 didDraw(dstRect);
1754 }
1755
1756 return { };
1757}
1758
1759void CanvasRenderingContext2DBase::drawImageFromRect(HTMLImageElement& imageElement, float sx, float sy, float sw, float sh, float dx, float dy, float dw, float dh, const String& compositeOperation)
1760{
1761 CompositeOperator op;
1762 auto blendOp = BlendMode::Normal;
1763 if (!parseCompositeAndBlendOperator(compositeOperation, op, blendOp) || blendOp != BlendMode::Normal)
1764 op = CompositeSourceOver;
1765 drawImage(imageElement, FloatRect { sx, sy, sw, sh }, FloatRect { dx, dy, dw, dh }, op, BlendMode::Normal);
1766}
1767
1768void CanvasRenderingContext2DBase::clearCanvas()
1769{
1770 auto* c = drawingContext();
1771 if (!c)
1772 return;
1773
1774 c->save();
1775 c->setCTM(canvasBase().baseTransform());
1776 c->clearRect(FloatRect(0, 0, canvasBase().width(), canvasBase().height()));
1777 c->restore();
1778}
1779
1780Path CanvasRenderingContext2DBase::transformAreaToDevice(const Path& path) const
1781{
1782 Path transformed(path);
1783 transformed.transform(state().transform);
1784 transformed.transform(canvasBase().baseTransform());
1785 return transformed;
1786}
1787
1788Path CanvasRenderingContext2DBase::transformAreaToDevice(const FloatRect& rect) const
1789{
1790 Path path;
1791 path.addRect(rect);
1792 return transformAreaToDevice(path);
1793}
1794
1795bool CanvasRenderingContext2DBase::rectContainsCanvas(const FloatRect& rect) const
1796{
1797 FloatQuad quad(rect);
1798 FloatQuad canvasQuad(FloatRect(0, 0, canvasBase().width(), canvasBase().height()));
1799 return state().transform.mapQuad(quad).containsQuad(canvasQuad);
1800}
1801
1802template<class T> IntRect CanvasRenderingContext2DBase::calculateCompositingBufferRect(const T& area, IntSize* croppedOffset)
1803{
1804 IntRect canvasRect(0, 0, canvasBase().width(), canvasBase().height());
1805 canvasRect = canvasBase().baseTransform().mapRect(canvasRect);
1806 Path path = transformAreaToDevice(area);
1807 IntRect bufferRect = enclosingIntRect(path.fastBoundingRect());
1808 IntPoint originalLocation = bufferRect.location();
1809 bufferRect.intersect(canvasRect);
1810 if (croppedOffset)
1811 *croppedOffset = originalLocation - bufferRect.location();
1812 return bufferRect;
1813}
1814
1815std::unique_ptr<ImageBuffer> CanvasRenderingContext2DBase::createCompositingBuffer(const IntRect& bufferRect)
1816{
1817 return ImageBuffer::create(bufferRect.size(), isAccelerated() ? Accelerated : Unaccelerated);
1818}
1819
1820void CanvasRenderingContext2DBase::compositeBuffer(ImageBuffer& buffer, const IntRect& bufferRect, CompositeOperator op)
1821{
1822 IntRect canvasRect(0, 0, canvasBase().width(), canvasBase().height());
1823 canvasRect = canvasBase().baseTransform().mapRect(canvasRect);
1824
1825 auto* c = drawingContext();
1826 if (!c)
1827 return;
1828
1829 c->save();
1830 c->setCTM(AffineTransform());
1831 c->setCompositeOperation(op);
1832
1833 c->save();
1834 c->clipOut(bufferRect);
1835 c->clearRect(canvasRect);
1836 c->restore();
1837 c->drawImageBuffer(buffer, bufferRect.location(), state().globalComposite);
1838 c->restore();
1839}
1840
1841static void drawImageToContext(Image& image, GraphicsContext& context, const FloatRect& dest, const FloatRect& src, CompositeOperator op)
1842{
1843 context.drawImage(image, dest, src, op);
1844}
1845
1846static void drawImageToContext(ImageBuffer& imageBuffer, GraphicsContext& context, const FloatRect& dest, const FloatRect& src, CompositeOperator op)
1847{
1848 context.drawImageBuffer(imageBuffer, dest, src, op);
1849}
1850
1851template<class T> void CanvasRenderingContext2DBase::fullCanvasCompositedDrawImage(T& image, const FloatRect& dest, const FloatRect& src, CompositeOperator op)
1852{
1853 ASSERT(isFullCanvasCompositeMode(op));
1854
1855 IntSize croppedOffset;
1856 auto bufferRect = calculateCompositingBufferRect(dest, &croppedOffset);
1857 if (bufferRect.isEmpty()) {
1858 clearCanvas();
1859 return;
1860 }
1861
1862 auto buffer = createCompositingBuffer(bufferRect);
1863 if (!buffer)
1864 return;
1865
1866 auto* c = drawingContext();
1867 if (!c)
1868 return;
1869
1870 FloatRect adjustedDest = dest;
1871 adjustedDest.setLocation(FloatPoint(0, 0));
1872 AffineTransform effectiveTransform = c->getCTM();
1873 IntRect transformedAdjustedRect = enclosingIntRect(effectiveTransform.mapRect(adjustedDest));
1874 buffer->context().translate(-transformedAdjustedRect.location());
1875 buffer->context().translate(croppedOffset);
1876 buffer->context().concatCTM(effectiveTransform);
1877 drawImageToContext(image, buffer->context(), adjustedDest, src, CompositeSourceOver);
1878
1879 compositeBuffer(*buffer, bufferRect, op);
1880}
1881
1882void CanvasRenderingContext2DBase::prepareGradientForDashboard(CanvasGradient& gradient) const
1883{
1884#if ENABLE(DASHBOARD_SUPPORT)
1885 if (m_usesDashboardCompatibilityMode)
1886 gradient.setDashboardCompatibilityMode();
1887#else
1888 UNUSED_PARAM(gradient);
1889#endif
1890}
1891
1892static CanvasRenderingContext2DBase::Style toStyle(const CanvasStyle& style)
1893{
1894 if (auto gradient = style.canvasGradient())
1895 return gradient;
1896 if (auto pattern = style.canvasPattern())
1897 return pattern;
1898 return style.color();
1899}
1900
1901CanvasRenderingContext2DBase::Style CanvasRenderingContext2DBase::strokeStyle() const
1902{
1903 return toStyle(state().strokeStyle);
1904}
1905
1906void CanvasRenderingContext2DBase::setStrokeStyle(CanvasRenderingContext2DBase::Style&& style)
1907{
1908 WTF::switchOn(style,
1909 [this] (const String& string) { this->setStrokeColor(string); },
1910 [this] (const RefPtr<CanvasGradient>& gradient) { this->setStrokeStyle(CanvasStyle(*gradient)); },
1911 [this] (const RefPtr<CanvasPattern>& pattern) { this->setStrokeStyle(CanvasStyle(*pattern)); }
1912 );
1913}
1914
1915CanvasRenderingContext2DBase::Style CanvasRenderingContext2DBase::fillStyle() const
1916{
1917 return toStyle(state().fillStyle);
1918}
1919
1920void CanvasRenderingContext2DBase::setFillStyle(CanvasRenderingContext2DBase::Style&& style)
1921{
1922 WTF::switchOn(style,
1923 [this] (const String& string) { this->setFillColor(string); },
1924 [this] (const RefPtr<CanvasGradient>& gradient) { this->setFillStyle(CanvasStyle(*gradient)); },
1925 [this] (const RefPtr<CanvasPattern>& pattern) { this->setFillStyle(CanvasStyle(*pattern)); }
1926 );
1927}
1928
1929ExceptionOr<Ref<CanvasGradient>> CanvasRenderingContext2DBase::createLinearGradient(float x0, float y0, float x1, float y1)
1930{
1931 if (!std::isfinite(x0) || !std::isfinite(y0) || !std::isfinite(x1) || !std::isfinite(y1))
1932 return Exception { NotSupportedError };
1933
1934 auto gradient = CanvasGradient::create(FloatPoint(x0, y0), FloatPoint(x1, y1));
1935 prepareGradientForDashboard(gradient.get());
1936 return gradient;
1937}
1938
1939ExceptionOr<Ref<CanvasGradient>> CanvasRenderingContext2DBase::createRadialGradient(float x0, float y0, float r0, float x1, float y1, float r1)
1940{
1941 if (!std::isfinite(x0) || !std::isfinite(y0) || !std::isfinite(r0) || !std::isfinite(x1) || !std::isfinite(y1) || !std::isfinite(r1))
1942 return Exception { NotSupportedError };
1943
1944 if (r0 < 0 || r1 < 0)
1945 return Exception { IndexSizeError };
1946
1947 auto gradient = CanvasGradient::create(FloatPoint(x0, y0), r0, FloatPoint(x1, y1), r1);
1948 prepareGradientForDashboard(gradient.get());
1949 return gradient;
1950}
1951
1952ExceptionOr<RefPtr<CanvasPattern>> CanvasRenderingContext2DBase::createPattern(CanvasImageSource&& image, const String& repetition)
1953{
1954 bool repeatX, repeatY;
1955 if (!CanvasPattern::parseRepetitionType(repetition, repeatX, repeatY))
1956 return Exception { SyntaxError };
1957
1958 return WTF::switchOn(image,
1959 [&] (auto& element) -> ExceptionOr<RefPtr<CanvasPattern>> { return this->createPattern(*element, repeatX, repeatY); }
1960 );
1961}
1962
1963ExceptionOr<RefPtr<CanvasPattern>> CanvasRenderingContext2DBase::createPattern(HTMLImageElement& imageElement, bool repeatX, bool repeatY)
1964{
1965 auto* cachedImage = imageElement.cachedImage();
1966
1967 // If the image loading hasn't started or the image is not complete, it is not fully decodable.
1968 if (!cachedImage || !imageElement.complete())
1969 return nullptr;
1970
1971 if (cachedImage->status() == CachedResource::LoadError)
1972 return Exception { InvalidStateError };
1973
1974 bool originClean = cachedImage->isOriginClean(canvasBase().securityOrigin());
1975
1976 // FIXME: SVG images with animations can switch between clean and dirty (leaking cross-origin
1977 // data). We should either:
1978 // 1) Take a fixed snapshot of an SVG image when creating a pattern and determine then whether
1979 // the origin is clean.
1980 // 2) Dynamically verify the origin checks at draw time, and dirty the canvas accordingly.
1981 // To be on the safe side, taint the origin for all patterns containing SVG images for now.
1982 if (cachedImage->image()->isSVGImage())
1983 originClean = false;
1984
1985 return RefPtr<CanvasPattern> { CanvasPattern::create(*cachedImage->imageForRenderer(imageElement.renderer()), repeatX, repeatY, originClean) };
1986}
1987
1988ExceptionOr<RefPtr<CanvasPattern>> CanvasRenderingContext2DBase::createPattern(CanvasBase& canvas, bool repeatX, bool repeatY)
1989{
1990 if (!canvas.width() || !canvas.height())
1991 return Exception { InvalidStateError };
1992 auto* copiedImage = canvas.copiedImage();
1993
1994 if (!copiedImage)
1995 return Exception { InvalidStateError };
1996
1997 return RefPtr<CanvasPattern> { CanvasPattern::create(*copiedImage, repeatX, repeatY, canvas.originClean()) };
1998}
1999
2000#if ENABLE(VIDEO)
2001
2002ExceptionOr<RefPtr<CanvasPattern>> CanvasRenderingContext2DBase::createPattern(HTMLVideoElement& videoElement, bool repeatX, bool repeatY)
2003{
2004 if (videoElement.readyState() < HTMLMediaElement::HAVE_CURRENT_DATA)
2005 return nullptr;
2006
2007 checkOrigin(&videoElement);
2008 bool originClean = canvasBase().originClean();
2009
2010#if USE(CG) || (ENABLE(ACCELERATED_2D_CANVAS) && USE(GSTREAMER_GL) && USE(CAIRO))
2011 if (auto nativeImage = videoElement.nativeImageForCurrentTime())
2012 return RefPtr<CanvasPattern> { CanvasPattern::create(BitmapImage::create(WTFMove(nativeImage)), repeatX, repeatY, originClean) };
2013#endif
2014
2015 auto imageBuffer = ImageBuffer::create(size(videoElement), drawingContext() ? drawingContext()->renderingMode() : Accelerated);
2016 if (!imageBuffer)
2017 return nullptr;
2018
2019 videoElement.paintCurrentFrameInContext(imageBuffer->context(), FloatRect(FloatPoint(), size(videoElement)));
2020
2021 return RefPtr<CanvasPattern> { CanvasPattern::create(ImageBuffer::sinkIntoImage(WTFMove(imageBuffer), PreserveResolution::Yes).releaseNonNull(), repeatX, repeatY, originClean) };
2022}
2023
2024#endif
2025
2026ExceptionOr<RefPtr<CanvasPattern>> CanvasRenderingContext2DBase::createPattern(ImageBitmap&, bool, bool)
2027{
2028 // FIXME: Implement.
2029 return Exception { TypeError };
2030}
2031
2032#if ENABLE(CSS_TYPED_OM)
2033ExceptionOr<RefPtr<CanvasPattern>> CanvasRenderingContext2DBase::createPattern(TypedOMCSSImageValue&, bool, bool)
2034{
2035 // FIXME: Implement.
2036 return Exception { TypeError };
2037}
2038#endif
2039
2040void CanvasRenderingContext2DBase::didDrawEntireCanvas()
2041{
2042 didDraw(FloatRect(FloatPoint::zero(), canvasBase().size()), CanvasDidDrawApplyClip);
2043}
2044
2045void CanvasRenderingContext2DBase::didDraw(const FloatRect& r, unsigned options)
2046{
2047 auto* c = drawingContext();
2048 if (!c)
2049 return;
2050 if (!state().hasInvertibleTransform)
2051 return;
2052
2053#if ENABLE(ACCELERATED_2D_CANVAS)
2054 // If we are drawing to hardware and we have a composited layer, just call contentChanged().
2055 if (isAccelerated()) {
2056 auto& canvas = downcast<HTMLCanvasElement>(canvasBase());
2057 RenderBox* renderBox = canvas.renderBox();
2058 if (renderBox && renderBox->hasAcceleratedCompositing()) {
2059 renderBox->contentChanged(CanvasPixelsChanged);
2060 canvas.clearCopiedImage();
2061 canvas.notifyObserversCanvasChanged(r);
2062 return;
2063 }
2064 }
2065#endif
2066
2067 FloatRect dirtyRect = r;
2068 if (options & CanvasDidDrawApplyTransform) {
2069 AffineTransform ctm = state().transform;
2070 dirtyRect = ctm.mapRect(r);
2071 }
2072
2073 if (options & CanvasDidDrawApplyShadow && state().shadowColor.isVisible()) {
2074 // The shadow gets applied after transformation
2075 FloatRect shadowRect(dirtyRect);
2076 shadowRect.move(state().shadowOffset);
2077 shadowRect.inflate(state().shadowBlur);
2078 dirtyRect.unite(shadowRect);
2079 }
2080
2081 if (options & CanvasDidDrawApplyClip) {
2082 // FIXME: apply the current clip to the rectangle. Unfortunately we can't get the clip
2083 // back out of the GraphicsContext, so to take clip into account for incremental painting,
2084 // we'd have to keep the clip path around.
2085 }
2086
2087 canvasBase().didDraw(dirtyRect);
2088}
2089
2090void CanvasRenderingContext2DBase::setTracksDisplayListReplay(bool tracksDisplayListReplay)
2091{
2092 if (tracksDisplayListReplay == m_tracksDisplayListReplay)
2093 return;
2094
2095 m_tracksDisplayListReplay = tracksDisplayListReplay;
2096 if (!m_tracksDisplayListReplay)
2097 contextDisplayListMap().remove(this);
2098}
2099
2100String CanvasRenderingContext2DBase::displayListAsText(DisplayList::AsTextFlags flags) const
2101{
2102 if (!m_recordingContext)
2103 return { };
2104 return m_recordingContext->displayList.asText(flags);
2105}
2106
2107String CanvasRenderingContext2DBase::replayDisplayListAsText(DisplayList::AsTextFlags flags) const
2108{
2109 auto* displayList = contextDisplayListMap().get(this);
2110 if (!displayList)
2111 return { };
2112 return displayList->asText(flags);
2113}
2114
2115const Vector<CanvasRenderingContext2DBase::State, 1>& CanvasRenderingContext2DBase::stateStack()
2116{
2117 realizeSaves();
2118 return m_stateStack;
2119}
2120
2121void CanvasRenderingContext2DBase::paintRenderingResultsToCanvas()
2122{
2123 if (UNLIKELY(m_usesDisplayListDrawing)) {
2124 if (!m_recordingContext)
2125 return;
2126
2127 FloatRect clip(FloatPoint::zero(), canvasBase().size());
2128 DisplayList::Replayer replayer(*canvasBase().drawingContext(), m_recordingContext->displayList);
2129
2130 if (UNLIKELY(m_tracksDisplayListReplay)) {
2131 auto replayList = replayer.replay(clip, m_tracksDisplayListReplay);
2132 contextDisplayListMap().add(this, WTFMove(replayList));
2133 } else
2134 replayer.replay(clip);
2135
2136 m_recordingContext->displayList.clear();
2137 }
2138}
2139
2140GraphicsContext* CanvasRenderingContext2DBase::drawingContext() const
2141{
2142 if (UNLIKELY(m_usesDisplayListDrawing)) {
2143 if (!m_recordingContext)
2144 m_recordingContext = std::make_unique<DisplayListDrawingContext>(GraphicsContextState(), FloatRect(FloatPoint::zero(), canvasBase().size()));
2145 return &m_recordingContext->context;
2146 }
2147
2148 return canvasBase().drawingContext();
2149}
2150
2151static RefPtr<ImageData> createEmptyImageData(const IntSize& size)
2152{
2153 auto data = ImageData::create(size);
2154 if (data)
2155 data->data()->zeroFill();
2156 return data;
2157}
2158
2159RefPtr<ImageData> CanvasRenderingContext2DBase::createImageData(ImageData& imageData) const
2160{
2161 return createEmptyImageData(imageData.size());
2162}
2163
2164ExceptionOr<RefPtr<ImageData>> CanvasRenderingContext2DBase::createImageData(float sw, float sh) const
2165{
2166 if (!sw || !sh)
2167 return Exception { IndexSizeError };
2168
2169 FloatSize logicalSize(std::abs(sw), std::abs(sh));
2170 if (!logicalSize.isExpressibleAsIntSize())
2171 return nullptr;
2172
2173 IntSize size = expandedIntSize(logicalSize);
2174 if (size.width() < 1)
2175 size.setWidth(1);
2176 if (size.height() < 1)
2177 size.setHeight(1);
2178
2179 return createEmptyImageData(size);
2180}
2181
2182ExceptionOr<RefPtr<ImageData>> CanvasRenderingContext2DBase::getImageData(float sx, float sy, float sw, float sh) const
2183{
2184 return getImageData(ImageBuffer::LogicalCoordinateSystem, sx, sy, sw, sh);
2185}
2186
2187ExceptionOr<RefPtr<ImageData>> CanvasRenderingContext2DBase::getImageData(ImageBuffer::CoordinateSystem coordinateSystem, float sx, float sy, float sw, float sh) const
2188{
2189 if (!canvasBase().originClean()) {
2190 static NeverDestroyed<String> consoleMessage(MAKE_STATIC_STRING_IMPL("Unable to get image data from canvas because the canvas has been tainted by cross-origin data."));
2191 canvasBase().scriptExecutionContext()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, consoleMessage);
2192 return Exception { SecurityError };
2193 }
2194
2195 if (!sw || !sh)
2196 return Exception { IndexSizeError };
2197
2198 if (sw < 0) {
2199 sx += sw;
2200 sw = -sw;
2201 }
2202 if (sh < 0) {
2203 sy += sh;
2204 sh = -sh;
2205 }
2206
2207 FloatRect logicalRect(sx, sy, sw, sh);
2208 if (logicalRect.width() < 1)
2209 logicalRect.setWidth(1);
2210 if (logicalRect.height() < 1)
2211 logicalRect.setHeight(1);
2212 if (!logicalRect.isExpressibleAsIntRect())
2213 return nullptr;
2214
2215 IntRect imageDataRect = enclosingIntRect(logicalRect);
2216 auto& canvas = downcast<HTMLCanvasElement>(canvasBase());
2217
2218 ImageBuffer* buffer = canvas.buffer();
2219 if (!buffer)
2220 return createEmptyImageData(imageDataRect.size());
2221
2222 auto byteArray = buffer->getUnmultipliedImageData(imageDataRect, nullptr, coordinateSystem);
2223 if (!byteArray) {
2224 StringBuilder consoleMessage;
2225 consoleMessage.appendLiteral("Unable to get image data from canvas. Requested size was ");
2226 consoleMessage.appendNumber(imageDataRect.width());
2227 consoleMessage.appendLiteral(" x ");
2228 consoleMessage.appendNumber(imageDataRect.height());
2229
2230 canvasBase().scriptExecutionContext()->addConsoleMessage(MessageSource::Rendering, MessageLevel::Error, consoleMessage.toString());
2231 return Exception { InvalidStateError };
2232 }
2233
2234 return ImageData::create(imageDataRect.size(), byteArray.releaseNonNull());
2235}
2236
2237void CanvasRenderingContext2DBase::putImageData(ImageData& data, float dx, float dy)
2238{
2239 putImageData(data, dx, dy, 0, 0, data.width(), data.height());
2240}
2241
2242void CanvasRenderingContext2DBase::putImageData(ImageData& data, float dx, float dy, float dirtyX, float dirtyY, float dirtyWidth, float dirtyHeight)
2243{
2244 putImageData(data, ImageBuffer::LogicalCoordinateSystem, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight);
2245}
2246
2247void CanvasRenderingContext2DBase::putImageData(ImageData& data, ImageBuffer::CoordinateSystem coordinateSystem, float dx, float dy, float dirtyX, float dirtyY, float dirtyWidth, float dirtyHeight)
2248{
2249 auto& canvas = downcast<HTMLCanvasElement>(canvasBase());
2250
2251 ImageBuffer* buffer = canvas.buffer();
2252 if (!buffer)
2253 return;
2254
2255 if (!data.data())
2256 return;
2257
2258 if (dirtyWidth < 0) {
2259 dirtyX += dirtyWidth;
2260 dirtyWidth = -dirtyWidth;
2261 }
2262
2263 if (dirtyHeight < 0) {
2264 dirtyY += dirtyHeight;
2265 dirtyHeight = -dirtyHeight;
2266 }
2267
2268 FloatRect clipRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
2269 clipRect.intersect(IntRect(0, 0, data.width(), data.height()));
2270 IntSize destOffset(static_cast<int>(dx), static_cast<int>(dy));
2271 IntRect destRect = enclosingIntRect(clipRect);
2272 destRect.move(destOffset);
2273 destRect.intersect(IntRect(IntPoint(), coordinateSystem == ImageBuffer::LogicalCoordinateSystem ? buffer->logicalSize() : buffer->internalSize()));
2274 if (destRect.isEmpty())
2275 return;
2276 IntRect sourceRect(destRect);
2277 sourceRect.move(-destOffset);
2278 sourceRect.intersect(IntRect(0, 0, data.width(), data.height()));
2279
2280 if (!sourceRect.isEmpty())
2281 buffer->putByteArray(*data.data(), AlphaPremultiplication::Unpremultiplied, IntSize(data.width(), data.height()), sourceRect, IntPoint(destOffset), coordinateSystem);
2282
2283 didDraw(destRect, CanvasDidDrawApplyNone); // ignore transform, shadow and clip
2284}
2285
2286void CanvasRenderingContext2DBase::inflateStrokeRect(FloatRect& rect) const
2287{
2288 // Fast approximation of the stroke's bounding rect.
2289 // This yields a slightly oversized rect but is very fast
2290 // compared to Path::strokeBoundingRect().
2291 static const float root2 = sqrtf(2);
2292 float delta = state().lineWidth / 2;
2293 if (state().lineJoin == MiterJoin)
2294 delta *= state().miterLimit;
2295 else if (state().lineCap == SquareCap)
2296 delta *= root2;
2297 rect.inflate(delta);
2298}
2299
2300#if ENABLE(ACCELERATED_2D_CANVAS)
2301
2302PlatformLayer* CanvasRenderingContext2DBase::platformLayer() const
2303{
2304 auto& canvas = downcast<HTMLCanvasElement>(canvasBase());
2305
2306 return canvas.buffer() ? canvas.buffer()->platformLayer() : nullptr;
2307}
2308
2309#endif
2310
2311static inline InterpolationQuality smoothingToInterpolationQuality(ImageSmoothingQuality quality)
2312{
2313 switch (quality) {
2314 case ImageSmoothingQuality::Low:
2315 return InterpolationLow;
2316 case ImageSmoothingQuality::Medium:
2317 return InterpolationMedium;
2318 case ImageSmoothingQuality::High:
2319 return InterpolationHigh;
2320 }
2321
2322 ASSERT_NOT_REACHED();
2323 return InterpolationLow;
2324};
2325
2326auto CanvasRenderingContext2DBase::imageSmoothingQuality() const -> ImageSmoothingQuality
2327{
2328 return state().imageSmoothingQuality;
2329}
2330
2331void CanvasRenderingContext2DBase::setImageSmoothingQuality(ImageSmoothingQuality quality)
2332{
2333 if (quality == state().imageSmoothingQuality)
2334 return;
2335
2336 realizeSaves();
2337 modifiableState().imageSmoothingQuality = quality;
2338
2339 if (!state().imageSmoothingEnabled)
2340 return;
2341
2342 if (auto* context = drawingContext())
2343 context->setImageInterpolationQuality(smoothingToInterpolationQuality(quality));
2344}
2345
2346bool CanvasRenderingContext2DBase::imageSmoothingEnabled() const
2347{
2348 return state().imageSmoothingEnabled;
2349}
2350
2351void CanvasRenderingContext2DBase::setImageSmoothingEnabled(bool enabled)
2352{
2353 if (enabled == state().imageSmoothingEnabled)
2354 return;
2355
2356 realizeSaves();
2357 modifiableState().imageSmoothingEnabled = enabled;
2358 auto* c = drawingContext();
2359 if (c)
2360 c->setImageInterpolationQuality(enabled ? smoothingToInterpolationQuality(state().imageSmoothingQuality) : InterpolationNone);
2361}
2362
2363void CanvasRenderingContext2DBase::setPath(Path2D& path)
2364{
2365 m_path = path.path();
2366}
2367
2368Ref<Path2D> CanvasRenderingContext2DBase::getPath() const
2369{
2370 return Path2D::create(m_path);
2371}
2372
2373inline void CanvasRenderingContext2DBase::clearPathForDashboardBackwardCompatibilityMode()
2374{
2375#if ENABLE(DASHBOARD_SUPPORT)
2376 if (m_usesDashboardCompatibilityMode)
2377 m_path.clear();
2378#endif
2379}
2380
2381} // namespace WebCore
2382