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.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
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.
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.
34 #include "CanvasRenderingContext2D.h"
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 "DOMMatrixInit.h"
46 #include "DisplayListRecorder.h"
47 #include "DisplayListReplayer.h"
48 #include "FloatQuad.h"
49 #include "HTMLImageElement.h"
50 #include "HTMLVideoElement.h"
51 #include "ImageBuffer.h"
52 #include "ImageData.h"
53 #include "RenderElement.h"
54 #include "RenderImage.h"
55 #include "RenderLayer.h"
56 #include "RenderTheme.h"
57 #include "SecurityOrigin.h"
58 #include "StrokeStyleApplier.h"
59 #include "StyleProperties.h"
60 #include "StyleResolver.h"
61 #include "TextMetrics.h"
63 #include "TextStream.h"
64 #include <wtf/CheckedArithmetic.h>
65 #include <wtf/MathExtras.h>
66 #include <wtf/NeverDestroyed.h>
67 #include <wtf/text/StringBuilder.h>
69 #if USE(CG) && !PLATFORM(IOS)
70 #include <ApplicationServices/ApplicationServices.h>
75 using namespace HTMLNames;
78 const CanvasRenderingContext2D::ImageSmoothingQuality defaultSmoothingQuality = CanvasRenderingContext2D::ImageSmoothingQuality::Low;
80 const CanvasRenderingContext2D::ImageSmoothingQuality defaultSmoothingQuality = CanvasRenderingContext2D::ImageSmoothingQuality::Medium;
83 static const int defaultFontSize = 10;
84 static const char* const defaultFontFamily = "sans-serif";
85 static const char* const defaultFont = "10px sans-serif";
87 struct DisplayListDrawingContext {
88 WTF_MAKE_FAST_ALLOCATED;
90 GraphicsContext context;
91 DisplayList::Recorder recorder;
92 DisplayList::DisplayList displayList;
94 DisplayListDrawingContext(const FloatRect& clip)
95 : recorder(context, displayList, clip, AffineTransform())
100 typedef HashMap<const CanvasRenderingContext2D*, std::unique_ptr<DisplayList::DisplayList>> ContextDisplayListHashMap;
102 static ContextDisplayListHashMap& contextDisplayListMap()
104 static NeverDestroyed<ContextDisplayListHashMap> sharedHashMap;
105 return sharedHashMap;
108 class CanvasStrokeStyleApplier : public StrokeStyleApplier {
110 CanvasStrokeStyleApplier(CanvasRenderingContext2D* canvasContext)
111 : m_canvasContext(canvasContext)
115 void strokeStyle(GraphicsContext* c) override
117 c->setStrokeThickness(m_canvasContext->lineWidth());
118 c->setLineCap(m_canvasContext->getLineCap());
119 c->setLineJoin(m_canvasContext->getLineJoin());
120 c->setMiterLimit(m_canvasContext->miterLimit());
121 const Vector<float>& lineDash = m_canvasContext->getLineDash();
122 DashArray convertedLineDash(lineDash.size());
123 for (size_t i = 0; i < lineDash.size(); ++i)
124 convertedLineDash[i] = static_cast<DashArrayElement>(lineDash[i]);
125 c->setLineDash(convertedLineDash, m_canvasContext->lineDashOffset());
129 CanvasRenderingContext2D* m_canvasContext;
132 CanvasRenderingContext2D::CanvasRenderingContext2D(HTMLCanvasElement& canvas, bool usesCSSCompatibilityParseMode, bool usesDashboardCompatibilityMode)
133 : CanvasRenderingContext(canvas)
135 , m_usesCSSCompatibilityParseMode(usesCSSCompatibilityParseMode)
136 #if ENABLE(DASHBOARD_SUPPORT)
137 , m_usesDashboardCompatibilityMode(usesDashboardCompatibilityMode)
140 #if !ENABLE(DASHBOARD_SUPPORT)
141 ASSERT_UNUSED(usesDashboardCompatibilityMode, !usesDashboardCompatibilityMode);
145 void CanvasRenderingContext2D::unwindStateStack()
147 // Ensure that the state stack in the ImageBuffer's context
148 // is cleared before destruction, to avoid assertions in the
149 // GraphicsContext dtor.
150 if (size_t stackSize = m_stateStack.size()) {
151 if (GraphicsContext* context = canvas().existingDrawingContext()) {
158 CanvasRenderingContext2D::~CanvasRenderingContext2D()
164 if (UNLIKELY(tracksDisplayListReplay()))
165 contextDisplayListMap().remove(this);
168 bool CanvasRenderingContext2D::isAccelerated() const
170 #if USE(IOSURFACE_CANVAS_BACKING_STORE) || ENABLE(ACCELERATED_2D_CANVAS)
171 if (!canvas().hasCreatedImageBuffer())
173 auto* context = drawingContext();
174 return context && context->isAcceleratedContext();
180 void CanvasRenderingContext2D::reset()
183 m_stateStack.resize(1);
184 m_stateStack.first() = State();
186 m_unrealizedSaveCount = 0;
188 m_recordingContext = nullptr;
191 CanvasRenderingContext2D::State::State()
192 : strokeStyle(Color::black)
193 , fillStyle(Color::black)
196 , lineJoin(MiterJoin)
199 , shadowColor(Color::transparent)
201 , globalComposite(CompositeSourceOver)
202 , globalBlend(BlendModeNormal)
203 , hasInvertibleTransform(true)
205 , imageSmoothingEnabled(true)
206 , imageSmoothingQuality(defaultSmoothingQuality)
207 , textAlign(StartTextAlign)
208 , textBaseline(AlphabeticTextBaseline)
209 , direction(Direction::Inherit)
210 , unparsedFont(defaultFont)
214 CanvasRenderingContext2D::State::State(const State& other)
215 : unparsedStrokeColor(other.unparsedStrokeColor)
216 , unparsedFillColor(other.unparsedFillColor)
217 , strokeStyle(other.strokeStyle)
218 , fillStyle(other.fillStyle)
219 , lineWidth(other.lineWidth)
220 , lineCap(other.lineCap)
221 , lineJoin(other.lineJoin)
222 , miterLimit(other.miterLimit)
223 , shadowOffset(other.shadowOffset)
224 , shadowBlur(other.shadowBlur)
225 , shadowColor(other.shadowColor)
226 , globalAlpha(other.globalAlpha)
227 , globalComposite(other.globalComposite)
228 , globalBlend(other.globalBlend)
229 , transform(other.transform)
230 , hasInvertibleTransform(other.hasInvertibleTransform)
231 , lineDashOffset(other.lineDashOffset)
232 , imageSmoothingEnabled(other.imageSmoothingEnabled)
233 , imageSmoothingQuality(other.imageSmoothingQuality)
234 , textAlign(other.textAlign)
235 , textBaseline(other.textBaseline)
236 , direction(other.direction)
237 , unparsedFont(other.unparsedFont)
242 CanvasRenderingContext2D::State& CanvasRenderingContext2D::State::operator=(const State& other)
247 unparsedStrokeColor = other.unparsedStrokeColor;
248 unparsedFillColor = other.unparsedFillColor;
249 strokeStyle = other.strokeStyle;
250 fillStyle = other.fillStyle;
251 lineWidth = other.lineWidth;
252 lineCap = other.lineCap;
253 lineJoin = other.lineJoin;
254 miterLimit = other.miterLimit;
255 shadowOffset = other.shadowOffset;
256 shadowBlur = other.shadowBlur;
257 shadowColor = other.shadowColor;
258 globalAlpha = other.globalAlpha;
259 globalComposite = other.globalComposite;
260 globalBlend = other.globalBlend;
261 transform = other.transform;
262 hasInvertibleTransform = other.hasInvertibleTransform;
263 imageSmoothingEnabled = other.imageSmoothingEnabled;
264 imageSmoothingQuality = other.imageSmoothingQuality;
265 textAlign = other.textAlign;
266 textBaseline = other.textBaseline;
267 direction = other.direction;
268 unparsedFont = other.unparsedFont;
274 CanvasRenderingContext2D::FontProxy::~FontProxy()
277 m_font.fontSelector()->unregisterForInvalidationCallbacks(*this);
280 CanvasRenderingContext2D::FontProxy::FontProxy(const FontProxy& other)
281 : m_font(other.m_font)
284 m_font.fontSelector()->registerForInvalidationCallbacks(*this);
287 auto CanvasRenderingContext2D::FontProxy::operator=(const FontProxy& other) -> FontProxy&
290 m_font.fontSelector()->unregisterForInvalidationCallbacks(*this);
292 m_font = other.m_font;
295 m_font.fontSelector()->registerForInvalidationCallbacks(*this);
300 inline void CanvasRenderingContext2D::FontProxy::update(FontSelector& selector)
302 ASSERT(&selector == m_font.fontSelector()); // This is an invariant. We should only ever be registered for callbacks on m_font.m_fonts.m_fontSelector.
304 m_font.fontSelector()->unregisterForInvalidationCallbacks(*this);
305 m_font.update(&selector);
307 m_font.fontSelector()->registerForInvalidationCallbacks(*this);
308 ASSERT(&selector == m_font.fontSelector());
311 void CanvasRenderingContext2D::FontProxy::fontsNeedUpdate(FontSelector& selector)
313 ASSERT_ARG(selector, &selector == m_font.fontSelector());
319 inline void CanvasRenderingContext2D::FontProxy::initialize(FontSelector& fontSelector, const RenderStyle& newStyle)
321 // Beware! m_font.fontSelector() might not point to document.fontSelector()!
322 ASSERT(newStyle.fontCascade().fontSelector() == &fontSelector);
324 m_font.fontSelector()->unregisterForInvalidationCallbacks(*this);
325 m_font = newStyle.fontCascade();
326 m_font.update(&fontSelector);
327 ASSERT(&fontSelector == m_font.fontSelector());
328 m_font.fontSelector()->registerForInvalidationCallbacks(*this);
331 inline FontMetrics CanvasRenderingContext2D::FontProxy::fontMetrics() const
333 return m_font.fontMetrics();
336 inline const FontCascadeDescription& CanvasRenderingContext2D::FontProxy::fontDescription() const
338 return m_font.fontDescription();
341 inline float CanvasRenderingContext2D::FontProxy::width(const TextRun& textRun) const
343 return m_font.width(textRun);
346 inline void CanvasRenderingContext2D::FontProxy::drawBidiText(GraphicsContext& context, const TextRun& run, const FloatPoint& point, FontCascade::CustomFontNotReadyAction action) const
348 context.drawBidiText(m_font, run, point, action);
351 void CanvasRenderingContext2D::realizeSaves()
353 if (m_unrealizedSaveCount)
356 if (m_unrealizedSaveCount) {
357 static NeverDestroyed<String> consoleMessage(MAKE_STATIC_STRING_IMPL("CanvasRenderingContext2D.save() has been called without a matching restore() too many times. Ignoring save()."));
358 canvas().document().addConsoleMessage(MessageSource::Rendering, MessageLevel::Error, consoleMessage);
362 void CanvasRenderingContext2D::realizeSavesLoop()
364 ASSERT(m_unrealizedSaveCount);
365 ASSERT(m_stateStack.size() >= 1);
366 GraphicsContext* context = drawingContext();
368 if (m_stateStack.size() > MaxSaveCount)
370 m_stateStack.append(state());
373 } while (--m_unrealizedSaveCount);
376 void CanvasRenderingContext2D::restore()
378 if (m_unrealizedSaveCount) {
379 --m_unrealizedSaveCount;
382 ASSERT(m_stateStack.size() >= 1);
383 if (m_stateStack.size() <= 1)
385 m_path.transform(state().transform);
386 m_stateStack.removeLast();
387 if (std::optional<AffineTransform> inverse = state().transform.inverse())
388 m_path.transform(inverse.value());
389 GraphicsContext* c = drawingContext();
395 void CanvasRenderingContext2D::setStrokeStyle(CanvasStyle style)
397 if (!style.isValid())
400 if (state().strokeStyle.isValid() && state().strokeStyle.isEquivalentColor(style))
403 if (style.isCurrentColor()) {
404 if (style.hasOverrideAlpha()) {
405 // FIXME: Should not use RGBA32 here.
406 style = CanvasStyle(colorWithOverrideAlpha(currentColor(&canvas()).rgb(), style.overrideAlpha()));
408 style = CanvasStyle(currentColor(&canvas()));
410 checkOrigin(style.canvasPattern());
413 State& state = modifiableState();
414 state.strokeStyle = style;
415 GraphicsContext* c = drawingContext();
418 state.strokeStyle.applyStrokeColor(*c);
419 state.unparsedStrokeColor = String();
422 void CanvasRenderingContext2D::setFillStyle(CanvasStyle style)
424 if (!style.isValid())
427 if (state().fillStyle.isValid() && state().fillStyle.isEquivalentColor(style))
430 if (style.isCurrentColor()) {
431 if (style.hasOverrideAlpha()) {
432 // FIXME: Should not use RGBA32 here.
433 style = CanvasStyle(colorWithOverrideAlpha(currentColor(&canvas()).rgb(), style.overrideAlpha()));
435 style = CanvasStyle(currentColor(&canvas()));
437 checkOrigin(style.canvasPattern());
440 State& state = modifiableState();
441 state.fillStyle = style;
442 GraphicsContext* c = drawingContext();
445 state.fillStyle.applyFillColor(*c);
446 state.unparsedFillColor = String();
449 float CanvasRenderingContext2D::lineWidth() const
451 return state().lineWidth;
454 void CanvasRenderingContext2D::setLineWidth(float width)
456 if (!(std::isfinite(width) && width > 0))
458 if (state().lineWidth == width)
461 modifiableState().lineWidth = width;
462 GraphicsContext* c = drawingContext();
465 c->setStrokeThickness(width);
468 String CanvasRenderingContext2D::lineCap() const
470 return lineCapName(state().lineCap);
473 void CanvasRenderingContext2D::setLineCap(const String& s)
476 if (!parseLineCap(s, cap))
478 if (state().lineCap == cap)
481 modifiableState().lineCap = cap;
482 GraphicsContext* c = drawingContext();
488 String CanvasRenderingContext2D::lineJoin() const
490 return lineJoinName(state().lineJoin);
493 void CanvasRenderingContext2D::setLineJoin(const String& s)
496 if (!parseLineJoin(s, join))
498 if (state().lineJoin == join)
501 modifiableState().lineJoin = join;
502 GraphicsContext* c = drawingContext();
505 c->setLineJoin(join);
508 float CanvasRenderingContext2D::miterLimit() const
510 return state().miterLimit;
513 void CanvasRenderingContext2D::setMiterLimit(float limit)
515 if (!(std::isfinite(limit) && limit > 0))
517 if (state().miterLimit == limit)
520 modifiableState().miterLimit = limit;
521 GraphicsContext* c = drawingContext();
524 c->setMiterLimit(limit);
527 float CanvasRenderingContext2D::shadowOffsetX() const
529 return state().shadowOffset.width();
532 void CanvasRenderingContext2D::setShadowOffsetX(float x)
534 if (!std::isfinite(x))
536 if (state().shadowOffset.width() == x)
539 modifiableState().shadowOffset.setWidth(x);
543 float CanvasRenderingContext2D::shadowOffsetY() const
545 return state().shadowOffset.height();
548 void CanvasRenderingContext2D::setShadowOffsetY(float y)
550 if (!std::isfinite(y))
552 if (state().shadowOffset.height() == y)
555 modifiableState().shadowOffset.setHeight(y);
559 float CanvasRenderingContext2D::shadowBlur() const
561 return state().shadowBlur;
564 void CanvasRenderingContext2D::setShadowBlur(float blur)
566 if (!(std::isfinite(blur) && blur >= 0))
568 if (state().shadowBlur == blur)
571 modifiableState().shadowBlur = blur;
575 String CanvasRenderingContext2D::shadowColor() const
577 return Color(state().shadowColor).serialized();
580 void CanvasRenderingContext2D::setShadowColor(const String& colorString)
582 Color color = parseColorOrCurrentColor(colorString, &canvas());
583 if (!color.isValid())
585 if (state().shadowColor == color)
588 modifiableState().shadowColor = color;
592 const Vector<float>& CanvasRenderingContext2D::getLineDash() const
594 return state().lineDash;
597 static bool lineDashSequenceIsValid(const Vector<float>& dash)
599 for (size_t i = 0; i < dash.size(); i++) {
600 if (!std::isfinite(dash[i]) || dash[i] < 0)
606 void CanvasRenderingContext2D::setLineDash(const Vector<float>& dash)
608 if (!lineDashSequenceIsValid(dash))
612 modifiableState().lineDash = dash;
613 // Spec requires the concatenation of two copies the dash list when the
614 // number of elements is odd
616 modifiableState().lineDash.appendVector(dash);
621 void CanvasRenderingContext2D::setWebkitLineDash(const Vector<float>& dash)
623 if (!lineDashSequenceIsValid(dash))
627 modifiableState().lineDash = dash;
632 float CanvasRenderingContext2D::lineDashOffset() const
634 return state().lineDashOffset;
637 void CanvasRenderingContext2D::setLineDashOffset(float offset)
639 if (!std::isfinite(offset) || state().lineDashOffset == offset)
643 modifiableState().lineDashOffset = offset;
647 void CanvasRenderingContext2D::applyLineDash() const
649 GraphicsContext* c = drawingContext();
652 DashArray convertedLineDash(state().lineDash.size());
653 for (size_t i = 0; i < state().lineDash.size(); ++i)
654 convertedLineDash[i] = static_cast<DashArrayElement>(state().lineDash[i]);
655 c->setLineDash(convertedLineDash, state().lineDashOffset);
658 float CanvasRenderingContext2D::globalAlpha() const
660 return state().globalAlpha;
663 void CanvasRenderingContext2D::setGlobalAlpha(float alpha)
665 if (!(alpha >= 0 && alpha <= 1))
667 if (state().globalAlpha == alpha)
670 modifiableState().globalAlpha = alpha;
671 GraphicsContext* c = drawingContext();
677 String CanvasRenderingContext2D::globalCompositeOperation() const
679 return compositeOperatorName(state().globalComposite, state().globalBlend);
682 void CanvasRenderingContext2D::setGlobalCompositeOperation(const String& operation)
684 CompositeOperator op = CompositeSourceOver;
685 BlendMode blendMode = BlendModeNormal;
686 if (!parseCompositeAndBlendOperator(operation, op, blendMode))
688 if ((state().globalComposite == op) && (state().globalBlend == blendMode))
691 modifiableState().globalComposite = op;
692 modifiableState().globalBlend = blendMode;
693 GraphicsContext* c = drawingContext();
696 c->setCompositeOperation(op, blendMode);
699 void CanvasRenderingContext2D::scale(float sx, float sy)
701 GraphicsContext* c = drawingContext();
704 if (!state().hasInvertibleTransform)
707 if (!std::isfinite(sx) || !std::isfinite(sy))
710 AffineTransform newTransform = state().transform;
711 newTransform.scaleNonUniform(sx, sy);
712 if (state().transform == newTransform)
718 modifiableState().hasInvertibleTransform = false;
722 modifiableState().transform = newTransform;
723 c->scale(FloatSize(sx, sy));
724 m_path.transform(AffineTransform().scaleNonUniform(1.0 / sx, 1.0 / sy));
727 void CanvasRenderingContext2D::rotate(float angleInRadians)
729 GraphicsContext* c = drawingContext();
732 if (!state().hasInvertibleTransform)
735 if (!std::isfinite(angleInRadians))
738 AffineTransform newTransform = state().transform;
739 newTransform.rotate(angleInRadians / piDouble * 180.0);
740 if (state().transform == newTransform)
745 modifiableState().transform = newTransform;
746 c->rotate(angleInRadians);
747 m_path.transform(AffineTransform().rotate(-angleInRadians / piDouble * 180.0));
750 void CanvasRenderingContext2D::translate(float tx, float ty)
752 GraphicsContext* c = drawingContext();
755 if (!state().hasInvertibleTransform)
758 if (!std::isfinite(tx) | !std::isfinite(ty))
761 AffineTransform newTransform = state().transform;
762 newTransform.translate(tx, ty);
763 if (state().transform == newTransform)
768 modifiableState().transform = newTransform;
769 c->translate(tx, ty);
770 m_path.transform(AffineTransform().translate(-tx, -ty));
773 void CanvasRenderingContext2D::transform(float m11, float m12, float m21, float m22, float dx, float dy)
775 GraphicsContext* c = drawingContext();
778 if (!state().hasInvertibleTransform)
781 if (!std::isfinite(m11) | !std::isfinite(m21) | !std::isfinite(dx) | !std::isfinite(m12) | !std::isfinite(m22) | !std::isfinite(dy))
784 AffineTransform transform(m11, m12, m21, m22, dx, dy);
785 AffineTransform newTransform = state().transform * transform;
786 if (state().transform == newTransform)
791 if (auto inverse = transform.inverse()) {
792 modifiableState().transform = newTransform;
793 c->concatCTM(transform);
794 m_path.transform(inverse.value());
797 modifiableState().hasInvertibleTransform = false;
800 Ref<DOMMatrix> CanvasRenderingContext2D::getTransform() const
802 return DOMMatrix::create(state().transform.toTransformationMatrix(), DOMMatrixReadOnly::Is2D::Yes);
805 void CanvasRenderingContext2D::setTransform(float m11, float m12, float m21, float m22, float dx, float dy)
807 GraphicsContext* c = drawingContext();
811 if (!std::isfinite(m11) | !std::isfinite(m21) | !std::isfinite(dx) | !std::isfinite(m12) | !std::isfinite(m22) | !std::isfinite(dy))
815 transform(m11, m12, m21, m22, dx, dy);
818 ExceptionOr<void> CanvasRenderingContext2D::setTransform(DOMMatrixInit&& matrixInit)
820 auto checkValid = DOMMatrixReadOnly::validateAndFixup(matrixInit);
821 if (checkValid.hasException())
822 return checkValid.releaseException();
824 setTransform(matrixInit.a.value_or(1), matrixInit.b.value_or(0), matrixInit.c.value_or(0), matrixInit.d.value_or(1), matrixInit.e.value_or(0), matrixInit.f.value_or(0));
829 void CanvasRenderingContext2D::resetTransform()
831 GraphicsContext* c = drawingContext();
835 AffineTransform ctm = state().transform;
836 bool hasInvertibleTransform = state().hasInvertibleTransform;
840 c->setCTM(canvas().baseTransform());
841 modifiableState().transform = AffineTransform();
843 if (hasInvertibleTransform)
844 m_path.transform(ctm);
846 modifiableState().hasInvertibleTransform = true;
849 void CanvasRenderingContext2D::setStrokeColor(const String& color, std::optional<float> alpha)
852 setStrokeStyle(CanvasStyle::createFromStringWithOverrideAlpha(color, alpha.value()));
856 if (color == state().unparsedStrokeColor)
860 setStrokeStyle(CanvasStyle::createFromString(color));
861 modifiableState().unparsedStrokeColor = color;
864 void CanvasRenderingContext2D::setStrokeColor(float grayLevel, float alpha)
866 if (state().strokeStyle.isValid() && state().strokeStyle.isEquivalentRGBA(grayLevel, grayLevel, grayLevel, alpha))
868 setStrokeStyle(CanvasStyle(grayLevel, alpha));
871 void CanvasRenderingContext2D::setStrokeColor(float r, float g, float b, float a)
873 if (state().strokeStyle.isValid() && state().strokeStyle.isEquivalentRGBA(r, g, b, a))
875 setStrokeStyle(CanvasStyle(r, g, b, a));
878 void CanvasRenderingContext2D::setStrokeColor(float c, float m, float y, float k, float a)
880 if (state().strokeStyle.isValid() && state().strokeStyle.isEquivalentCMYKA(c, m, y, k, a))
882 setStrokeStyle(CanvasStyle(c, m, y, k, a));
885 void CanvasRenderingContext2D::setFillColor(const String& color, std::optional<float> alpha)
888 setFillStyle(CanvasStyle::createFromStringWithOverrideAlpha(color, alpha.value()));
892 if (color == state().unparsedFillColor)
896 setFillStyle(CanvasStyle::createFromString(color));
897 modifiableState().unparsedFillColor = color;
900 void CanvasRenderingContext2D::setFillColor(float grayLevel, float alpha)
902 if (state().fillStyle.isValid() && state().fillStyle.isEquivalentRGBA(grayLevel, grayLevel, grayLevel, alpha))
904 setFillStyle(CanvasStyle(grayLevel, alpha));
907 void CanvasRenderingContext2D::setFillColor(float r, float g, float b, float a)
909 if (state().fillStyle.isValid() && state().fillStyle.isEquivalentRGBA(r, g, b, a))
911 setFillStyle(CanvasStyle(r, g, b, a));
914 void CanvasRenderingContext2D::setFillColor(float c, float m, float y, float k, float a)
916 if (state().fillStyle.isValid() && state().fillStyle.isEquivalentCMYKA(c, m, y, k, a))
918 setFillStyle(CanvasStyle(c, m, y, k, a));
921 void CanvasRenderingContext2D::beginPath()
926 static bool validateRectForCanvas(float& x, float& y, float& width, float& height)
928 if (!std::isfinite(x) | !std::isfinite(y) | !std::isfinite(width) | !std::isfinite(height))
931 if (!width && !height)
947 inline void CanvasRenderingContext2D::clearPathForDashboardBackwardCompatibilityMode()
949 #if ENABLE(DASHBOARD_SUPPORT)
950 if (m_usesDashboardCompatibilityMode)
955 static bool isFullCanvasCompositeMode(CompositeOperator op)
957 // See 4.8.11.1.3 Compositing
958 // CompositeSourceAtop and CompositeDestinationOut are not listed here as the platforms already
959 // implement the specification's behavior.
960 return op == CompositeSourceIn || op == CompositeSourceOut || op == CompositeDestinationIn || op == CompositeDestinationAtop;
963 static WindRule toWindRule(CanvasRenderingContext2D::WindingRule rule)
965 return rule == CanvasRenderingContext2D::WindingRule::Nonzero ? RULE_NONZERO : RULE_EVENODD;
968 String CanvasRenderingContext2D::stringForWindingRule(WindingRule windingRule)
970 switch (windingRule) {
971 case WindingRule::Nonzero:
972 return ASCIILiteral("nonzero");
973 case WindingRule::Evenodd:
974 return ASCIILiteral("evenodd");
977 ASSERT_NOT_REACHED();
981 void CanvasRenderingContext2D::fill(WindingRule windingRule)
983 fillInternal(m_path, windingRule);
984 clearPathForDashboardBackwardCompatibilityMode();
987 void CanvasRenderingContext2D::stroke()
989 strokeInternal(m_path);
990 clearPathForDashboardBackwardCompatibilityMode();
993 void CanvasRenderingContext2D::clip(WindingRule windingRule)
995 clipInternal(m_path, windingRule);
996 clearPathForDashboardBackwardCompatibilityMode();
999 void CanvasRenderingContext2D::fill(DOMPath& path, WindingRule windingRule)
1001 fillInternal(path.path(), windingRule);
1004 void CanvasRenderingContext2D::stroke(DOMPath& path)
1006 strokeInternal(path.path());
1009 void CanvasRenderingContext2D::clip(DOMPath& path, WindingRule windingRule)
1011 clipInternal(path.path(), windingRule);
1014 void CanvasRenderingContext2D::fillInternal(const Path& path, WindingRule windingRule)
1016 auto* c = drawingContext();
1019 if (!state().hasInvertibleTransform)
1022 // If gradient size is zero, then paint nothing.
1023 auto* gradient = c->fillGradient();
1024 if (gradient && gradient->isZeroSize())
1027 if (!path.isEmpty()) {
1028 auto savedFillRule = c->fillRule();
1029 c->setFillRule(toWindRule(windingRule));
1031 if (isFullCanvasCompositeMode(state().globalComposite)) {
1032 beginCompositeLayer();
1034 endCompositeLayer();
1035 didDrawEntireCanvas();
1036 } else if (state().globalComposite == CompositeCopy) {
1039 didDrawEntireCanvas();
1042 didDraw(path.fastBoundingRect());
1045 c->setFillRule(savedFillRule);
1049 void CanvasRenderingContext2D::strokeInternal(const Path& path)
1051 auto* c = drawingContext();
1054 if (!state().hasInvertibleTransform)
1057 // If gradient size is zero, then paint nothing.
1058 auto* gradient = c->strokeGradient();
1059 if (gradient && gradient->isZeroSize())
1062 if (!path.isEmpty()) {
1063 if (isFullCanvasCompositeMode(state().globalComposite)) {
1064 beginCompositeLayer();
1065 c->strokePath(path);
1066 endCompositeLayer();
1067 didDrawEntireCanvas();
1068 } else if (state().globalComposite == CompositeCopy) {
1070 c->strokePath(path);
1071 didDrawEntireCanvas();
1073 FloatRect dirtyRect = path.fastBoundingRect();
1074 inflateStrokeRect(dirtyRect);
1075 c->strokePath(path);
1081 void CanvasRenderingContext2D::clipInternal(const Path& path, WindingRule windingRule)
1083 auto* c = drawingContext();
1086 if (!state().hasInvertibleTransform)
1090 c->canvasClip(path, toWindRule(windingRule));
1093 inline void CanvasRenderingContext2D::beginCompositeLayer()
1096 drawingContext()->beginTransparencyLayer(1);
1100 inline void CanvasRenderingContext2D::endCompositeLayer()
1103 drawingContext()->endTransparencyLayer();
1107 bool CanvasRenderingContext2D::isPointInPath(float x, float y, WindingRule windingRule)
1109 return isPointInPathInternal(m_path, x, y, windingRule);
1112 bool CanvasRenderingContext2D::isPointInStroke(float x, float y)
1114 return isPointInStrokeInternal(m_path, x, y);
1117 bool CanvasRenderingContext2D::isPointInPath(DOMPath& path, float x, float y, WindingRule windingRule)
1119 return isPointInPathInternal(path.path(), x, y, windingRule);
1122 bool CanvasRenderingContext2D::isPointInStroke(DOMPath& path, float x, float y)
1124 return isPointInStrokeInternal(path.path(), x, y);
1127 bool CanvasRenderingContext2D::isPointInPathInternal(const Path& path, float x, float y, WindingRule windingRule)
1129 auto* c = drawingContext();
1132 if (!state().hasInvertibleTransform)
1135 auto transformedPoint = state().transform.inverse().value_or(AffineTransform()).mapPoint(FloatPoint(x, y));
1137 if (!std::isfinite(transformedPoint.x()) || !std::isfinite(transformedPoint.y()))
1140 return path.contains(transformedPoint, toWindRule(windingRule));
1143 bool CanvasRenderingContext2D::isPointInStrokeInternal(const Path& path, float x, float y)
1145 auto* c = drawingContext();
1148 if (!state().hasInvertibleTransform)
1151 auto transformedPoint = state().transform.inverse().value_or(AffineTransform()).mapPoint(FloatPoint(x, y));
1152 if (!std::isfinite(transformedPoint.x()) || !std::isfinite(transformedPoint.y()))
1155 CanvasStrokeStyleApplier applier(this);
1156 return path.strokeContains(&applier, transformedPoint);
1159 void CanvasRenderingContext2D::clearRect(float x, float y, float width, float height)
1161 if (!validateRectForCanvas(x, y, width, height))
1163 auto* context = drawingContext();
1166 if (!state().hasInvertibleTransform)
1168 FloatRect rect(x, y, width, height);
1171 if (shouldDrawShadows()) {
1174 context->setLegacyShadow(FloatSize(), 0, Color::transparent);
1176 if (state().globalAlpha != 1) {
1181 context->setAlpha(1);
1183 if (state().globalComposite != CompositeSourceOver) {
1188 context->setCompositeOperation(CompositeSourceOver);
1190 context->clearRect(rect);
1196 void CanvasRenderingContext2D::fillRect(float x, float y, float width, float height)
1198 if (!validateRectForCanvas(x, y, width, height))
1201 auto* c = drawingContext();
1204 if (!state().hasInvertibleTransform)
1207 // from the HTML5 Canvas spec:
1208 // If x0 = x1 and y0 = y1, then the linear gradient must paint nothing
1209 // If x0 = x1 and y0 = y1 and r0 = r1, then the radial gradient must paint nothing
1210 auto* gradient = c->fillGradient();
1211 if (gradient && gradient->isZeroSize())
1214 FloatRect rect(x, y, width, height);
1216 if (rectContainsCanvas(rect)) {
1218 didDrawEntireCanvas();
1219 } else if (isFullCanvasCompositeMode(state().globalComposite)) {
1220 beginCompositeLayer();
1222 endCompositeLayer();
1223 didDrawEntireCanvas();
1224 } else if (state().globalComposite == CompositeCopy) {
1227 didDrawEntireCanvas();
1234 void CanvasRenderingContext2D::strokeRect(float x, float y, float width, float height)
1236 if (!validateRectForCanvas(x, y, width, height))
1239 auto* c = drawingContext();
1242 if (!state().hasInvertibleTransform)
1244 if (!(state().lineWidth >= 0))
1247 // If gradient size is zero, then paint nothing.
1248 auto* gradient = c->strokeGradient();
1249 if (gradient && gradient->isZeroSize())
1252 FloatRect rect(x, y, width, height);
1253 if (isFullCanvasCompositeMode(state().globalComposite)) {
1254 beginCompositeLayer();
1255 c->strokeRect(rect, state().lineWidth);
1256 endCompositeLayer();
1257 didDrawEntireCanvas();
1258 } else if (state().globalComposite == CompositeCopy) {
1260 c->strokeRect(rect, state().lineWidth);
1261 didDrawEntireCanvas();
1263 FloatRect boundingRect = rect;
1264 boundingRect.inflate(state().lineWidth / 2);
1265 c->strokeRect(rect, state().lineWidth);
1266 didDraw(boundingRect);
1270 void CanvasRenderingContext2D::setShadow(float width, float height, float blur, const String& colorString, std::optional<float> alpha)
1272 Color color = Color::transparent;
1273 if (!colorString.isNull()) {
1274 color = parseColorOrCurrentColor(colorString, &canvas());
1275 if (!color.isValid())
1278 // FIXME: Should not use RGBA32 here.
1279 setShadow(FloatSize(width, height), blur, colorWithOverrideAlpha(color.rgb(), alpha));
1282 void CanvasRenderingContext2D::setShadow(float width, float height, float blur, float grayLevel, float alpha)
1284 setShadow(FloatSize(width, height), blur, Color(grayLevel, grayLevel, grayLevel, alpha));
1287 void CanvasRenderingContext2D::setShadow(float width, float height, float blur, float r, float g, float b, float a)
1289 setShadow(FloatSize(width, height), blur, Color(r, g, b, a));
1292 void CanvasRenderingContext2D::setShadow(float width, float height, float blur, float c, float m, float y, float k, float a)
1294 setShadow(FloatSize(width, height), blur, Color(c, m, y, k, a));
1297 void CanvasRenderingContext2D::clearShadow()
1299 setShadow(FloatSize(), 0, Color::transparent);
1302 void CanvasRenderingContext2D::setShadow(const FloatSize& offset, float blur, const Color& color)
1304 if (state().shadowOffset == offset && state().shadowBlur == blur && state().shadowColor == color)
1306 bool wasDrawingShadows = shouldDrawShadows();
1308 modifiableState().shadowOffset = offset;
1309 modifiableState().shadowBlur = blur;
1310 modifiableState().shadowColor = color;
1311 if (!wasDrawingShadows && !shouldDrawShadows())
1316 void CanvasRenderingContext2D::applyShadow()
1318 auto* c = drawingContext();
1322 if (shouldDrawShadows()) {
1323 float width = state().shadowOffset.width();
1324 float height = state().shadowOffset.height();
1325 c->setLegacyShadow(FloatSize(width, -height), state().shadowBlur, state().shadowColor);
1327 c->setLegacyShadow(FloatSize(), 0, Color::transparent);
1330 bool CanvasRenderingContext2D::shouldDrawShadows() const
1332 return state().shadowColor.isVisible() && (state().shadowBlur || !state().shadowOffset.isZero());
1335 enum class ImageSizeType { AfterDevicePixelRatio, BeforeDevicePixelRatio };
1336 static LayoutSize size(HTMLImageElement& element, ImageSizeType sizeType = ImageSizeType::BeforeDevicePixelRatio)
1339 if (auto* cachedImage = element.cachedImage()) {
1340 size = cachedImage->imageSizeForRenderer(element.renderer(), 1.0f); // FIXME: Not sure about this.
1341 if (sizeType == ImageSizeType::AfterDevicePixelRatio && is<RenderImage>(element.renderer()) && cachedImage->image() && !cachedImage->image()->hasRelativeWidth())
1342 size.scale(downcast<RenderImage>(*element.renderer()).imageDevicePixelRatio());
1347 static inline FloatSize size(HTMLCanvasElement& canvasElement)
1349 return canvasElement.size();
1354 static inline FloatSize size(HTMLVideoElement& video)
1356 auto* player = video.player();
1359 return player->naturalSize();
1364 static inline FloatRect normalizeRect(const FloatRect& rect)
1366 return FloatRect(std::min(rect.x(), rect.maxX()),
1367 std::min(rect.y(), rect.maxY()),
1368 std::max(rect.width(), -rect.width()),
1369 std::max(rect.height(), -rect.height()));
1372 ExceptionOr<void> CanvasRenderingContext2D::drawImage(CanvasImageSource&& image, float dx, float dy)
1374 return WTF::switchOn(image,
1375 [&] (RefPtr<HTMLImageElement>& imageElement) -> ExceptionOr<void> {
1376 LayoutSize destRectSize = size(*imageElement, ImageSizeType::AfterDevicePixelRatio);
1377 LayoutSize sourceRectSize = size(*imageElement, ImageSizeType::BeforeDevicePixelRatio);
1378 return this->drawImage(*imageElement, FloatRect { 0, 0, sourceRectSize.width(), sourceRectSize.height() }, FloatRect { dx, dy, destRectSize.width(), destRectSize.height() });
1380 [&] (auto& element) -> ExceptionOr<void> {
1381 FloatSize elementSize = size(*element);
1382 return this->drawImage(*element, FloatRect { 0, 0, elementSize.width(), elementSize.height() }, FloatRect { dx, dy, elementSize.width(), elementSize.height() });
1387 ExceptionOr<void> CanvasRenderingContext2D::drawImage(CanvasImageSource&& image, float dx, float dy, float dw, float dh)
1389 return WTF::switchOn(image,
1390 [&] (auto& element) -> ExceptionOr<void> {
1391 FloatSize elementSize = size(*element);
1392 return this->drawImage(*element, FloatRect { 0, 0, elementSize.width(), elementSize.height() }, FloatRect { dx, dy, dw, dh });
1397 ExceptionOr<void> CanvasRenderingContext2D::drawImage(CanvasImageSource&& image, float sx, float sy, float sw, float sh, float dx, float dy, float dw, float dh)
1399 return WTF::switchOn(image,
1400 [&] (auto& element) -> ExceptionOr<void> {
1401 return this->drawImage(*element, FloatRect { sx, sy, sw, sh }, FloatRect { dx, dy, dw, dh });
1406 ExceptionOr<void> CanvasRenderingContext2D::drawImage(HTMLImageElement& imageElement, const FloatRect& srcRect, const FloatRect& dstRect)
1408 return drawImage(imageElement, srcRect, dstRect, state().globalComposite, state().globalBlend);
1411 ExceptionOr<void> CanvasRenderingContext2D::drawImage(HTMLImageElement& imageElement, const FloatRect& srcRect, const FloatRect& dstRect, const CompositeOperator& op, const BlendMode& blendMode)
1413 if (!std::isfinite(dstRect.x()) || !std::isfinite(dstRect.y()) || !std::isfinite(dstRect.width()) || !std::isfinite(dstRect.height())
1414 || !std::isfinite(srcRect.x()) || !std::isfinite(srcRect.y()) || !std::isfinite(srcRect.width()) || !std::isfinite(srcRect.height()))
1417 if (!dstRect.width() || !dstRect.height())
1420 if (!imageElement.complete())
1423 FloatRect normalizedSrcRect = normalizeRect(srcRect);
1424 FloatRect normalizedDstRect = normalizeRect(dstRect);
1426 FloatRect imageRect = FloatRect(FloatPoint(), size(imageElement, ImageSizeType::BeforeDevicePixelRatio));
1427 if (!srcRect.width() || !srcRect.height())
1428 return Exception { IndexSizeError };
1430 // When the source rectangle is outside the source image, the source rectangle must be clipped
1431 // to the source image and the destination rectangle must be clipped in the same proportion.
1432 FloatRect originalNormalizedSrcRect = normalizedSrcRect;
1433 normalizedSrcRect.intersect(imageRect);
1434 if (normalizedSrcRect.isEmpty())
1437 if (normalizedSrcRect != originalNormalizedSrcRect) {
1438 normalizedDstRect.setWidth(normalizedDstRect.width() * normalizedSrcRect.width() / originalNormalizedSrcRect.width());
1439 normalizedDstRect.setHeight(normalizedDstRect.height() * normalizedSrcRect.height() / originalNormalizedSrcRect.height());
1440 if (normalizedDstRect.isEmpty())
1444 GraphicsContext* c = drawingContext();
1447 if (!state().hasInvertibleTransform)
1450 CachedImage* cachedImage = imageElement.cachedImage();
1454 Image* image = cachedImage->imageForRenderer(imageElement.renderer());
1458 ImageObserver* observer = image->imageObserver();
1460 if (image->isSVGImage()) {
1461 image->setImageObserver(nullptr);
1462 image->setContainerSize(imageRect.size());
1465 if (image->isBitmapImage())
1466 downcast<BitmapImage>(*image).updateFromSettings(imageElement.document().settings());
1468 if (rectContainsCanvas(normalizedDstRect)) {
1469 c->drawImage(*image, normalizedDstRect, normalizedSrcRect, ImagePaintingOptions(op, blendMode));
1470 didDrawEntireCanvas();
1471 } else if (isFullCanvasCompositeMode(op)) {
1472 fullCanvasCompositedDrawImage(*image, normalizedDstRect, normalizedSrcRect, op);
1473 didDrawEntireCanvas();
1474 } else if (op == CompositeCopy) {
1476 c->drawImage(*image, normalizedDstRect, normalizedSrcRect, ImagePaintingOptions(op, blendMode));
1477 didDrawEntireCanvas();
1479 c->drawImage(*image, normalizedDstRect, normalizedSrcRect, ImagePaintingOptions(op, blendMode));
1480 didDraw(normalizedDstRect);
1483 if (image->isSVGImage())
1484 image->setImageObserver(observer);
1486 checkOrigin(&imageElement);
1491 ExceptionOr<void> CanvasRenderingContext2D::drawImage(HTMLCanvasElement& sourceCanvas, const FloatRect& srcRect, const FloatRect& dstRect)
1493 FloatRect srcCanvasRect = FloatRect(FloatPoint(), sourceCanvas.size());
1495 if (!srcCanvasRect.width() || !srcCanvasRect.height())
1496 return Exception { InvalidStateError };
1498 if (!srcRect.width() || !srcRect.height())
1499 return Exception { IndexSizeError };
1501 if (!srcCanvasRect.contains(normalizeRect(srcRect)) || !dstRect.width() || !dstRect.height())
1504 GraphicsContext* c = drawingContext();
1507 if (!state().hasInvertibleTransform)
1510 // FIXME: Do this through platform-independent GraphicsContext API.
1511 ImageBuffer* buffer = sourceCanvas.buffer();
1515 checkOrigin(&sourceCanvas);
1517 #if ENABLE(ACCELERATED_2D_CANVAS)
1518 // If we're drawing from one accelerated canvas 2d to another, avoid calling sourceCanvas.makeRenderingResultsAvailable()
1519 // as that will do a readback to software.
1520 CanvasRenderingContext* sourceContext = sourceCanvas.renderingContext();
1521 // FIXME: Implement an accelerated path for drawing from a WebGL canvas to a 2d canvas when possible.
1522 if (!isAccelerated() || !sourceContext || !sourceContext->isAccelerated() || !sourceContext->is2d())
1523 sourceCanvas.makeRenderingResultsAvailable();
1525 sourceCanvas.makeRenderingResultsAvailable();
1528 if (rectContainsCanvas(dstRect)) {
1529 c->drawImageBuffer(*buffer, dstRect, srcRect, ImagePaintingOptions(state().globalComposite, state().globalBlend));
1530 didDrawEntireCanvas();
1531 } else if (isFullCanvasCompositeMode(state().globalComposite)) {
1532 fullCanvasCompositedDrawImage(*buffer, dstRect, srcRect, state().globalComposite);
1533 didDrawEntireCanvas();
1534 } else if (state().globalComposite == CompositeCopy) {
1536 c->drawImageBuffer(*buffer, dstRect, srcRect, ImagePaintingOptions(state().globalComposite, state().globalBlend));
1537 didDrawEntireCanvas();
1539 c->drawImageBuffer(*buffer, dstRect, srcRect, ImagePaintingOptions(state().globalComposite, state().globalBlend));
1548 ExceptionOr<void> CanvasRenderingContext2D::drawImage(HTMLVideoElement& video, const FloatRect& srcRect, const FloatRect& dstRect)
1550 if (video.readyState() == HTMLMediaElement::HAVE_NOTHING || video.readyState() == HTMLMediaElement::HAVE_METADATA)
1553 FloatRect videoRect = FloatRect(FloatPoint(), size(video));
1554 if (!srcRect.width() || !srcRect.height())
1555 return Exception { IndexSizeError };
1557 if (!videoRect.contains(normalizeRect(srcRect)) || !dstRect.width() || !dstRect.height())
1560 GraphicsContext* c = drawingContext();
1563 if (!state().hasInvertibleTransform)
1566 checkOrigin(&video);
1568 #if USE(CG) || (ENABLE(ACCELERATED_2D_CANVAS) && USE(GSTREAMER_GL) && USE(CAIRO))
1569 if (NativeImagePtr image = video.nativeImageForCurrentTime()) {
1570 c->drawNativeImage(image, FloatSize(video.videoWidth(), video.videoHeight()), dstRect, srcRect);
1571 if (rectContainsCanvas(dstRect))
1572 didDrawEntireCanvas();
1580 GraphicsContextStateSaver stateSaver(*c);
1582 c->translate(dstRect.x(), dstRect.y());
1583 c->scale(FloatSize(dstRect.width() / srcRect.width(), dstRect.height() / srcRect.height()));
1584 c->translate(-srcRect.x(), -srcRect.y());
1585 video.paintCurrentFrameInContext(*c, FloatRect(FloatPoint(), size(video)));
1586 stateSaver.restore();
1594 void CanvasRenderingContext2D::drawImageFromRect(HTMLImageElement& imageElement, float sx, float sy, float sw, float sh, float dx, float dy, float dw, float dh, const String& compositeOperation)
1596 CompositeOperator op;
1597 auto blendOp = BlendModeNormal;
1598 if (!parseCompositeAndBlendOperator(compositeOperation, op, blendOp) || blendOp != BlendModeNormal)
1599 op = CompositeSourceOver;
1600 drawImage(imageElement, FloatRect { sx, sy, sw, sh }, FloatRect { dx, dy, dw, dh }, op, BlendModeNormal);
1603 void CanvasRenderingContext2D::setAlpha(float alpha)
1605 setGlobalAlpha(alpha);
1608 void CanvasRenderingContext2D::setCompositeOperation(const String& operation)
1610 setGlobalCompositeOperation(operation);
1613 void CanvasRenderingContext2D::clearCanvas()
1615 auto* c = drawingContext();
1620 c->setCTM(canvas().baseTransform());
1621 c->clearRect(FloatRect(0, 0, canvas().width(), canvas().height()));
1625 Path CanvasRenderingContext2D::transformAreaToDevice(const Path& path) const
1627 Path transformed(path);
1628 transformed.transform(state().transform);
1629 transformed.transform(canvas().baseTransform());
1633 Path CanvasRenderingContext2D::transformAreaToDevice(const FloatRect& rect) const
1637 return transformAreaToDevice(path);
1640 bool CanvasRenderingContext2D::rectContainsCanvas(const FloatRect& rect) const
1642 FloatQuad quad(rect);
1643 FloatQuad canvasQuad(FloatRect(0, 0, canvas().width(), canvas().height()));
1644 return state().transform.mapQuad(quad).containsQuad(canvasQuad);
1647 template<class T> IntRect CanvasRenderingContext2D::calculateCompositingBufferRect(const T& area, IntSize* croppedOffset)
1649 IntRect canvasRect(0, 0, canvas().width(), canvas().height());
1650 canvasRect = canvas().baseTransform().mapRect(canvasRect);
1651 Path path = transformAreaToDevice(area);
1652 IntRect bufferRect = enclosingIntRect(path.fastBoundingRect());
1653 IntPoint originalLocation = bufferRect.location();
1654 bufferRect.intersect(canvasRect);
1656 *croppedOffset = originalLocation - bufferRect.location();
1660 std::unique_ptr<ImageBuffer> CanvasRenderingContext2D::createCompositingBuffer(const IntRect& bufferRect)
1662 return ImageBuffer::create(bufferRect.size(), isAccelerated() ? Accelerated : Unaccelerated);
1665 void CanvasRenderingContext2D::compositeBuffer(ImageBuffer& buffer, const IntRect& bufferRect, CompositeOperator op)
1667 IntRect canvasRect(0, 0, canvas().width(), canvas().height());
1668 canvasRect = canvas().baseTransform().mapRect(canvasRect);
1670 auto* c = drawingContext();
1675 c->setCTM(AffineTransform());
1676 c->setCompositeOperation(op);
1679 c->clipOut(bufferRect);
1680 c->clearRect(canvasRect);
1682 c->drawImageBuffer(buffer, bufferRect.location(), state().globalComposite);
1686 static void drawImageToContext(Image& image, GraphicsContext& context, const FloatRect& dest, const FloatRect& src, CompositeOperator op)
1688 context.drawImage(image, dest, src, op);
1691 static void drawImageToContext(ImageBuffer& imageBuffer, GraphicsContext& context, const FloatRect& dest, const FloatRect& src, CompositeOperator op)
1693 context.drawImageBuffer(imageBuffer, dest, src, op);
1696 template<class T> void CanvasRenderingContext2D::fullCanvasCompositedDrawImage(T& image, const FloatRect& dest, const FloatRect& src, CompositeOperator op)
1698 ASSERT(isFullCanvasCompositeMode(op));
1700 IntSize croppedOffset;
1701 auto bufferRect = calculateCompositingBufferRect(dest, &croppedOffset);
1702 if (bufferRect.isEmpty()) {
1707 auto buffer = createCompositingBuffer(bufferRect);
1711 auto* c = drawingContext();
1715 FloatRect adjustedDest = dest;
1716 adjustedDest.setLocation(FloatPoint(0, 0));
1717 AffineTransform effectiveTransform = c->getCTM();
1718 IntRect transformedAdjustedRect = enclosingIntRect(effectiveTransform.mapRect(adjustedDest));
1719 buffer->context().translate(-transformedAdjustedRect.location().x(), -transformedAdjustedRect.location().y());
1720 buffer->context().translate(croppedOffset.width(), croppedOffset.height());
1721 buffer->context().concatCTM(effectiveTransform);
1722 drawImageToContext(image, buffer->context(), adjustedDest, src, CompositeSourceOver);
1724 compositeBuffer(*buffer, bufferRect, op);
1727 void CanvasRenderingContext2D::prepareGradientForDashboard(CanvasGradient& gradient) const
1729 #if ENABLE(DASHBOARD_SUPPORT)
1730 if (m_usesDashboardCompatibilityMode)
1731 gradient.setDashboardCompatibilityMode();
1733 UNUSED_PARAM(gradient);
1737 static CanvasRenderingContext2D::Style toStyle(const CanvasStyle& style)
1739 if (auto* gradient = style.canvasGradient())
1740 return RefPtr<CanvasGradient> { gradient };
1741 if (auto* pattern = style.canvasPattern())
1742 return RefPtr<CanvasPattern> { pattern };
1743 return style.color();
1746 CanvasRenderingContext2D::Style CanvasRenderingContext2D::strokeStyle() const
1748 return toStyle(state().strokeStyle);
1751 void CanvasRenderingContext2D::setStrokeStyle(CanvasRenderingContext2D::Style&& style)
1753 WTF::switchOn(style,
1754 [this] (const String& string) { this->setStrokeColor(string); },
1755 [this] (const RefPtr<CanvasGradient>& gradient) { this->setStrokeStyle(CanvasStyle(*gradient)); },
1756 [this] (const RefPtr<CanvasPattern>& pattern) { this->setStrokeStyle(CanvasStyle(*pattern)); }
1760 CanvasRenderingContext2D::Style CanvasRenderingContext2D::fillStyle() const
1762 return toStyle(state().fillStyle);
1765 void CanvasRenderingContext2D::setFillStyle(CanvasRenderingContext2D::Style&& style)
1767 WTF::switchOn(style,
1768 [this] (const String& string) { this->setFillColor(string); },
1769 [this] (const RefPtr<CanvasGradient>& gradient) { this->setFillStyle(CanvasStyle(*gradient)); },
1770 [this] (const RefPtr<CanvasPattern>& pattern) { this->setFillStyle(CanvasStyle(*pattern)); }
1774 ExceptionOr<Ref<CanvasGradient>> CanvasRenderingContext2D::createLinearGradient(float x0, float y0, float x1, float y1)
1776 if (!std::isfinite(x0) || !std::isfinite(y0) || !std::isfinite(x1) || !std::isfinite(y1))
1777 return Exception { NotSupportedError };
1779 auto gradient = CanvasGradient::create(FloatPoint(x0, y0), FloatPoint(x1, y1));
1780 prepareGradientForDashboard(gradient.get());
1781 return WTFMove(gradient);
1784 ExceptionOr<Ref<CanvasGradient>> CanvasRenderingContext2D::createRadialGradient(float x0, float y0, float r0, float x1, float y1, float r1)
1786 if (!std::isfinite(x0) || !std::isfinite(y0) || !std::isfinite(r0) || !std::isfinite(x1) || !std::isfinite(y1) || !std::isfinite(r1))
1787 return Exception { NotSupportedError };
1789 if (r0 < 0 || r1 < 0)
1790 return Exception { IndexSizeError };
1792 auto gradient = CanvasGradient::create(FloatPoint(x0, y0), r0, FloatPoint(x1, y1), r1);
1793 prepareGradientForDashboard(gradient.get());
1794 return WTFMove(gradient);
1797 ExceptionOr<RefPtr<CanvasPattern>> CanvasRenderingContext2D::createPattern(CanvasImageSource&& image, const String& repetition)
1799 bool repeatX, repeatY;
1800 if (!CanvasPattern::parseRepetitionType(repetition, repeatX, repeatY))
1801 return Exception { SyntaxError };
1803 return WTF::switchOn(image,
1804 [&] (auto& element) -> ExceptionOr<RefPtr<CanvasPattern>> { return this->createPattern(*element, repeatX, repeatY); }
1808 ExceptionOr<RefPtr<CanvasPattern>> CanvasRenderingContext2D::createPattern(HTMLImageElement& imageElement, bool repeatX, bool repeatY)
1810 auto* cachedImage = imageElement.cachedImage();
1812 // If the image loading hasn't started or the image is not complete, it is not fully decodable.
1813 if (!cachedImage || !imageElement.complete())
1816 if (cachedImage->status() == CachedResource::LoadError)
1817 return Exception { InvalidStateError };
1819 bool originClean = cachedImage->isOriginClean(canvas().securityOrigin());
1821 // FIXME: SVG images with animations can switch between clean and dirty (leaking cross-origin
1822 // data). We should either:
1823 // 1) Take a fixed snapshot of an SVG image when creating a pattern and determine then whether
1824 // the origin is clean.
1825 // 2) Dynamically verify the origin checks at draw time, and dirty the canvas accordingly.
1826 // To be on the safe side, taint the origin for all patterns containing SVG images for now.
1827 if (cachedImage->image()->isSVGImage())
1828 originClean = false;
1830 return RefPtr<CanvasPattern> { CanvasPattern::create(*cachedImage->imageForRenderer(imageElement.renderer()), repeatX, repeatY, originClean) };
1833 ExceptionOr<RefPtr<CanvasPattern>> CanvasRenderingContext2D::createPattern(HTMLCanvasElement& canvas, bool repeatX, bool repeatY)
1835 if (!canvas.width() || !canvas.height() || !canvas.buffer())
1836 return Exception { InvalidStateError };
1838 return RefPtr<CanvasPattern> { CanvasPattern::create(*canvas.copiedImage(), repeatX, repeatY, canvas.originClean()) };
1843 ExceptionOr<RefPtr<CanvasPattern>> CanvasRenderingContext2D::createPattern(HTMLVideoElement& videoElement, bool repeatX, bool repeatY)
1845 if (videoElement.readyState() < HTMLMediaElement::HAVE_CURRENT_DATA)
1848 checkOrigin(&videoElement);
1849 bool originClean = canvas().originClean();
1851 #if USE(CG) || (ENABLE(ACCELERATED_2D_CANVAS) && USE(GSTREAMER_GL) && USE(CAIRO))
1852 if (auto nativeImage = videoElement.nativeImageForCurrentTime())
1853 return RefPtr<CanvasPattern> { CanvasPattern::create(BitmapImage::create(WTFMove(nativeImage)), repeatX, repeatY, originClean) };
1856 auto imageBuffer = ImageBuffer::create(size(videoElement), drawingContext() ? drawingContext()->renderingMode() : Accelerated);
1857 videoElement.paintCurrentFrameInContext(imageBuffer->context(), FloatRect(FloatPoint(), size(videoElement)));
1859 return RefPtr<CanvasPattern> { CanvasPattern::create(ImageBuffer::sinkIntoImage(WTFMove(imageBuffer), Unscaled).releaseNonNull(), repeatX, repeatY, originClean) };
1864 void CanvasRenderingContext2D::didDrawEntireCanvas()
1866 didDraw(FloatRect(FloatPoint::zero(), canvas().size()), CanvasDidDrawApplyClip);
1869 void CanvasRenderingContext2D::didDraw(const FloatRect& r, unsigned options)
1871 auto* c = drawingContext();
1874 if (!state().hasInvertibleTransform)
1877 #if ENABLE(ACCELERATED_2D_CANVAS)
1878 // If we are drawing to hardware and we have a composited layer, just call contentChanged().
1879 if (isAccelerated()) {
1880 RenderBox* renderBox = canvas().renderBox();
1881 if (renderBox && renderBox->hasAcceleratedCompositing()) {
1882 renderBox->contentChanged(CanvasPixelsChanged);
1883 canvas().clearCopiedImage();
1884 canvas().notifyObserversCanvasChanged(r);
1890 FloatRect dirtyRect = r;
1891 if (options & CanvasDidDrawApplyTransform) {
1892 AffineTransform ctm = state().transform;
1893 dirtyRect = ctm.mapRect(r);
1896 if (options & CanvasDidDrawApplyShadow && state().shadowColor.isVisible()) {
1897 // The shadow gets applied after transformation
1898 FloatRect shadowRect(dirtyRect);
1899 shadowRect.move(state().shadowOffset);
1900 shadowRect.inflate(state().shadowBlur);
1901 dirtyRect.unite(shadowRect);
1904 if (options & CanvasDidDrawApplyClip) {
1905 // FIXME: apply the current clip to the rectangle. Unfortunately we can't get the clip
1906 // back out of the GraphicsContext, so to take clip into account for incremental painting,
1907 // we'd have to keep the clip path around.
1910 canvas().didDraw(dirtyRect);
1913 void CanvasRenderingContext2D::setTracksDisplayListReplay(bool tracksDisplayListReplay)
1915 if (tracksDisplayListReplay == m_tracksDisplayListReplay)
1918 m_tracksDisplayListReplay = tracksDisplayListReplay;
1919 if (!m_tracksDisplayListReplay)
1920 contextDisplayListMap().remove(this);
1923 String CanvasRenderingContext2D::displayListAsText(DisplayList::AsTextFlags flags) const
1925 if (!m_recordingContext)
1927 return m_recordingContext->displayList.asText(flags);
1930 String CanvasRenderingContext2D::replayDisplayListAsText(DisplayList::AsTextFlags flags) const
1932 auto* displayList = contextDisplayListMap().get(this);
1935 return displayList->asText(flags);
1938 void CanvasRenderingContext2D::paintRenderingResultsToCanvas()
1940 if (UNLIKELY(m_usesDisplayListDrawing)) {
1941 if (!m_recordingContext)
1944 FloatRect clip(FloatPoint::zero(), canvas().size());
1945 DisplayList::Replayer replayer(*canvas().drawingContext(), m_recordingContext->displayList);
1947 if (UNLIKELY(m_tracksDisplayListReplay)) {
1948 auto replayList = replayer.replay(clip, m_tracksDisplayListReplay);
1949 contextDisplayListMap().add(this, WTFMove(replayList));
1951 replayer.replay(clip);
1953 m_recordingContext->displayList.clear();
1957 GraphicsContext* CanvasRenderingContext2D::drawingContext() const
1959 if (UNLIKELY(m_usesDisplayListDrawing)) {
1960 if (!m_recordingContext)
1961 m_recordingContext = std::make_unique<DisplayListDrawingContext>(FloatRect(FloatPoint::zero(), canvas().size()));
1962 return &m_recordingContext->context;
1965 return canvas().drawingContext();
1968 static RefPtr<ImageData> createEmptyImageData(const IntSize& size)
1970 auto data = ImageData::create(size);
1972 data->data()->zeroFill();
1976 ExceptionOr<RefPtr<ImageData>> CanvasRenderingContext2D::createImageData(ImageData* imageData) const
1979 return Exception { NotSupportedError };
1981 return createEmptyImageData(imageData->size());
1984 ExceptionOr<RefPtr<ImageData>> CanvasRenderingContext2D::createImageData(float sw, float sh) const
1987 return Exception { IndexSizeError };
1989 FloatSize logicalSize(std::abs(sw), std::abs(sh));
1990 if (!logicalSize.isExpressibleAsIntSize())
1993 IntSize size = expandedIntSize(logicalSize);
1994 if (size.width() < 1)
1996 if (size.height() < 1)
1999 return createEmptyImageData(size);
2002 ExceptionOr<RefPtr<ImageData>> CanvasRenderingContext2D::getImageData(float sx, float sy, float sw, float sh) const
2004 return getImageData(ImageBuffer::LogicalCoordinateSystem, sx, sy, sw, sh);
2007 ExceptionOr<RefPtr<ImageData>> CanvasRenderingContext2D::webkitGetImageDataHD(float sx, float sy, float sw, float sh) const
2009 return getImageData(ImageBuffer::BackingStoreCoordinateSystem, sx, sy, sw, sh);
2012 ExceptionOr<RefPtr<ImageData>> CanvasRenderingContext2D::getImageData(ImageBuffer::CoordinateSystem coordinateSystem, float sx, float sy, float sw, float sh) const
2014 if (!canvas().originClean()) {
2015 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."));
2016 canvas().document().addConsoleMessage(MessageSource::Security, MessageLevel::Error, consoleMessage);
2017 return Exception { SecurityError };
2021 return Exception { IndexSizeError };
2032 FloatRect logicalRect(sx, sy, sw, sh);
2033 if (logicalRect.width() < 1)
2034 logicalRect.setWidth(1);
2035 if (logicalRect.height() < 1)
2036 logicalRect.setHeight(1);
2037 if (!logicalRect.isExpressibleAsIntRect())
2040 IntRect imageDataRect = enclosingIntRect(logicalRect);
2041 ImageBuffer* buffer = canvas().buffer();
2043 return createEmptyImageData(imageDataRect.size());
2045 auto byteArray = buffer->getUnmultipliedImageData(imageDataRect, nullptr, coordinateSystem);
2047 StringBuilder consoleMessage;
2048 consoleMessage.appendLiteral("Unable to get image data from canvas. Requested size was ");
2049 consoleMessage.appendNumber(imageDataRect.width());
2050 consoleMessage.appendLiteral(" x ");
2051 consoleMessage.appendNumber(imageDataRect.height());
2053 canvas().document().addConsoleMessage(MessageSource::Rendering, MessageLevel::Error, consoleMessage.toString());
2054 return Exception { InvalidStateError };
2057 return ImageData::create(imageDataRect.size(), byteArray.releaseNonNull());
2060 void CanvasRenderingContext2D::putImageData(ImageData& data, float dx, float dy)
2062 putImageData(data, dx, dy, 0, 0, data.width(), data.height());
2065 void CanvasRenderingContext2D::webkitPutImageDataHD(ImageData& data, float dx, float dy)
2067 webkitPutImageDataHD(data, dx, dy, 0, 0, data.width(), data.height());
2070 void CanvasRenderingContext2D::putImageData(ImageData& data, float dx, float dy, float dirtyX, float dirtyY, float dirtyWidth, float dirtyHeight)
2072 putImageData(data, ImageBuffer::LogicalCoordinateSystem, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight);
2075 void CanvasRenderingContext2D::webkitPutImageDataHD(ImageData& data, float dx, float dy, float dirtyX, float dirtyY, float dirtyWidth, float dirtyHeight)
2077 putImageData(data, ImageBuffer::BackingStoreCoordinateSystem, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight);
2080 void CanvasRenderingContext2D::drawFocusIfNeeded(Element& element)
2082 drawFocusIfNeededInternal(m_path, element);
2085 void CanvasRenderingContext2D::drawFocusIfNeeded(DOMPath& path, Element& element)
2087 drawFocusIfNeededInternal(path.path(), element);
2090 void CanvasRenderingContext2D::drawFocusIfNeededInternal(const Path& path, Element& element)
2092 auto* context = drawingContext();
2093 if (!element.focused() || !state().hasInvertibleTransform || path.isEmpty() || !element.isDescendantOf(canvas()) || !context)
2095 context->drawFocusRing(path, 1, 1, RenderTheme::focusRingColor());
2098 void CanvasRenderingContext2D::putImageData(ImageData& data, ImageBuffer::CoordinateSystem coordinateSystem, float dx, float dy, float dirtyX, float dirtyY, float dirtyWidth, float dirtyHeight)
2100 ImageBuffer* buffer = canvas().buffer();
2104 if (dirtyWidth < 0) {
2105 dirtyX += dirtyWidth;
2106 dirtyWidth = -dirtyWidth;
2109 if (dirtyHeight < 0) {
2110 dirtyY += dirtyHeight;
2111 dirtyHeight = -dirtyHeight;
2114 FloatRect clipRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
2115 clipRect.intersect(IntRect(0, 0, data.width(), data.height()));
2116 IntSize destOffset(static_cast<int>(dx), static_cast<int>(dy));
2117 IntRect destRect = enclosingIntRect(clipRect);
2118 destRect.move(destOffset);
2119 destRect.intersect(IntRect(IntPoint(), coordinateSystem == ImageBuffer::LogicalCoordinateSystem ? buffer->logicalSize() : buffer->internalSize()));
2120 if (destRect.isEmpty())
2122 IntRect sourceRect(destRect);
2123 sourceRect.move(-destOffset);
2124 sourceRect.intersect(IntRect(0, 0, data.width(), data.height()));
2126 if (!sourceRect.isEmpty())
2127 buffer->putByteArray(Unmultiplied, data.data(), IntSize(data.width(), data.height()), sourceRect, IntPoint(destOffset), coordinateSystem);
2129 didDraw(destRect, CanvasDidDrawApplyNone); // ignore transform, shadow and clip
2132 String CanvasRenderingContext2D::font() const
2134 if (!state().font.realized())
2137 StringBuilder serializedFont;
2138 const auto& fontDescription = state().font.fontDescription();
2140 if (fontDescription.italic())
2141 serializedFont.appendLiteral("italic ");
2142 if (fontDescription.variantCaps() == FontVariantCaps::Small)
2143 serializedFont.appendLiteral("small-caps ");
2145 serializedFont.appendNumber(fontDescription.computedPixelSize());
2146 serializedFont.appendLiteral("px");
2148 for (unsigned i = 0; i < fontDescription.familyCount(); ++i) {
2150 serializedFont.append(',');
2151 // FIXME: We should append family directly to serializedFont rather than building a temporary string.
2152 String family = fontDescription.familyAt(i);
2153 if (family.startsWith("-webkit-"))
2154 family = family.substring(8);
2155 if (family.contains(' '))
2156 family = makeString('"', family, '"');
2158 serializedFont.append(' ');
2159 serializedFont.append(family);
2162 return serializedFont.toString();
2165 void CanvasRenderingContext2D::setFont(const String& newFont)
2167 if (newFont == state().unparsedFont && state().font.realized())
2170 auto parsedStyle = MutableStyleProperties::create();
2171 CSSParser::parseValue(parsedStyle, CSSPropertyFont, newFont, true, strictToCSSParserMode(!m_usesCSSCompatibilityParseMode));
2172 if (parsedStyle->isEmpty())
2175 String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont);
2177 // According to http://lists.w3.org/Archives/Public/public-html/2009Jul/0947.html,
2178 // the "inherit" and "initial" values must be ignored.
2179 if (fontValue == "inherit" || fontValue == "initial")
2182 // The parse succeeded.
2183 String newFontSafeCopy(newFont); // Create a string copy since newFont can be deleted inside realizeSaves.
2185 modifiableState().unparsedFont = newFontSafeCopy;
2187 // Map the <canvas> font into the text style. If the font uses keywords like larger/smaller, these will work
2188 // relative to the canvas.
2189 auto newStyle = RenderStyle::createPtr();
2191 Document& document = canvas().document();
2192 document.updateStyleIfNeeded();
2194 if (auto* computedStyle = canvas().computedStyle())
2195 newStyle->setFontDescription(computedStyle->fontDescription());
2197 FontCascadeDescription defaultFontDescription;
2198 defaultFontDescription.setOneFamily(defaultFontFamily);
2199 defaultFontDescription.setSpecifiedSize(defaultFontSize);
2200 defaultFontDescription.setComputedSize(defaultFontSize);
2202 newStyle->setFontDescription(defaultFontDescription);
2205 newStyle->fontCascade().update(&document.fontSelector());
2207 // Now map the font property longhands into the style.
2208 StyleResolver& styleResolver = canvas().styleResolver();
2209 styleResolver.applyPropertyToStyle(CSSPropertyFontFamily, parsedStyle->getPropertyCSSValue(CSSPropertyFontFamily).get(), WTFMove(newStyle));
2210 styleResolver.applyPropertyToCurrentStyle(CSSPropertyFontStyle, parsedStyle->getPropertyCSSValue(CSSPropertyFontStyle).get());
2211 styleResolver.applyPropertyToCurrentStyle(CSSPropertyFontVariantCaps, parsedStyle->getPropertyCSSValue(CSSPropertyFontVariantCaps).get());
2212 styleResolver.applyPropertyToCurrentStyle(CSSPropertyFontWeight, parsedStyle->getPropertyCSSValue(CSSPropertyFontWeight).get());
2214 // As described in BUG66291, setting font-size and line-height on a font may entail a CSSPrimitiveValue::computeLengthDouble call,
2215 // which assumes the fontMetrics are available for the affected font, otherwise a crash occurs (see http://trac.webkit.org/changeset/96122).
2216 // The updateFont() calls below update the fontMetrics and ensure the proper setting of font-size and line-height.
2217 styleResolver.updateFont();
2218 styleResolver.applyPropertyToCurrentStyle(CSSPropertyFontSize, parsedStyle->getPropertyCSSValue(CSSPropertyFontSize).get());
2219 styleResolver.updateFont();
2220 styleResolver.applyPropertyToCurrentStyle(CSSPropertyLineHeight, parsedStyle->getPropertyCSSValue(CSSPropertyLineHeight).get());
2222 modifiableState().font.initialize(document.fontSelector(), *styleResolver.style());
2225 String CanvasRenderingContext2D::textAlign() const
2227 return textAlignName(state().textAlign);
2230 void CanvasRenderingContext2D::setTextAlign(const String& s)
2233 if (!parseTextAlign(s, align))
2235 if (state().textAlign == align)
2238 modifiableState().textAlign = align;
2241 String CanvasRenderingContext2D::textBaseline() const
2243 return textBaselineName(state().textBaseline);
2246 void CanvasRenderingContext2D::setTextBaseline(const String& s)
2248 TextBaseline baseline;
2249 if (!parseTextBaseline(s, baseline))
2251 if (state().textBaseline == baseline)
2254 modifiableState().textBaseline = baseline;
2257 inline TextDirection CanvasRenderingContext2D::toTextDirection(Direction direction, const RenderStyle** computedStyle) const
2259 auto* style = (computedStyle || direction == Direction::Inherit) ? canvas().computedStyle() : nullptr;
2261 *computedStyle = style;
2262 switch (direction) {
2263 case Direction::Inherit:
2264 return style ? style->direction() : LTR;
2265 case Direction::RTL:
2267 case Direction::LTR:
2270 ASSERT_NOT_REACHED();
2274 String CanvasRenderingContext2D::direction() const
2276 if (state().direction == Direction::Inherit)
2277 canvas().document().updateStyleIfNeeded();
2278 return toTextDirection(state().direction) == RTL ? ASCIILiteral("rtl") : ASCIILiteral("ltr");
2281 void CanvasRenderingContext2D::setDirection(const String& directionString)
2283 Direction direction;
2284 if (directionString == "inherit")
2285 direction = Direction::Inherit;
2286 else if (directionString == "rtl")
2287 direction = Direction::RTL;
2288 else if (directionString == "ltr")
2289 direction = Direction::LTR;
2293 if (state().direction == direction)
2297 modifiableState().direction = direction;
2300 void CanvasRenderingContext2D::fillText(const String& text, float x, float y, std::optional<float> maxWidth)
2302 drawTextInternal(text, x, y, true, maxWidth);
2305 void CanvasRenderingContext2D::strokeText(const String& text, float x, float y, std::optional<float> maxWidth)
2307 drawTextInternal(text, x, y, false, maxWidth);
2310 static inline bool isSpaceThatNeedsReplacing(UChar c)
2312 // According to specification all space characters should be replaced with 0x0020 space character.
2313 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-preparation-algorithm
2314 // The space characters according to specification are : U+0020, U+0009, U+000A, U+000C, and U+000D.
2315 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#space-character
2316 // This function returns true for 0x000B also, so that this is backward compatible.
2317 // Otherwise, the test LayoutTests/canvas/philip/tests/2d.text.draw.space.collapse.space.html will fail
2318 return c == 0x0009 || c == 0x000A || c == 0x000B || c == 0x000C || c == 0x000D;
2321 static void normalizeSpaces(String& text)
2323 size_t i = text.find(isSpaceThatNeedsReplacing);
2327 unsigned textLength = text.length();
2328 Vector<UChar> charVector(textLength);
2329 StringView(text).getCharactersWithUpconvert(charVector.data());
2331 charVector[i++] = ' ';
2333 for (; i < textLength; ++i) {
2334 if (isSpaceThatNeedsReplacing(charVector[i]))
2335 charVector[i] = ' ';
2337 text = String::adopt(WTFMove(charVector));
2340 Ref<TextMetrics> CanvasRenderingContext2D::measureText(const String& text)
2342 Ref<TextMetrics> metrics = TextMetrics::create();
2344 String normalizedText = text;
2345 normalizeSpaces(normalizedText);
2347 metrics->setWidth(fontProxy().width(TextRun(normalizedText)));
2352 void CanvasRenderingContext2D::drawTextInternal(const String& text, float x, float y, bool fill, std::optional<float> maxWidth)
2354 auto& fontProxy = this->fontProxy();
2355 const auto& fontMetrics = fontProxy.fontMetrics();
2357 auto* c = drawingContext();
2360 if (!state().hasInvertibleTransform)
2362 if (!std::isfinite(x) | !std::isfinite(y))
2364 if (maxWidth && (!std::isfinite(maxWidth.value()) || maxWidth.value() <= 0))
2367 // If gradient size is zero, then paint nothing.
2368 auto* gradient = c->strokeGradient();
2369 if (!fill && gradient && gradient->isZeroSize())
2372 gradient = c->fillGradient();
2373 if (fill && gradient && gradient->isZeroSize())
2376 String normalizedText = text;
2377 normalizeSpaces(normalizedText);
2379 // FIXME: Need to turn off font smoothing.
2381 const RenderStyle* computedStyle;
2382 auto direction = toTextDirection(state().direction, &computedStyle);
2383 bool isRTL = direction == RTL;
2384 bool override = computedStyle ? isOverride(computedStyle->unicodeBidi()) : false;
2386 TextRun textRun(normalizedText, 0, 0, AllowTrailingExpansion, direction, override, true);
2387 // Draw the item text at the correct point.
2388 FloatPoint location(x, y);
2389 switch (state().textBaseline) {
2390 case TopTextBaseline:
2391 case HangingTextBaseline:
2392 location.setY(y + fontMetrics.ascent());
2394 case BottomTextBaseline:
2395 case IdeographicTextBaseline:
2396 location.setY(y - fontMetrics.descent());
2398 case MiddleTextBaseline:
2399 location.setY(y - fontMetrics.descent() + fontMetrics.height() / 2);
2401 case AlphabeticTextBaseline:
2407 float fontWidth = fontProxy.width(TextRun(normalizedText, 0, 0, AllowTrailingExpansion, direction, override));
2409 bool useMaxWidth = maxWidth && maxWidth.value() < fontWidth;
2410 float width = useMaxWidth ? maxWidth.value() : fontWidth;
2412 auto align = state().textAlign;
2413 if (align == StartTextAlign)
2414 align = isRTL ? RightTextAlign : LeftTextAlign;
2415 else if (align == EndTextAlign)
2416 align = isRTL ? LeftTextAlign : RightTextAlign;
2419 case CenterTextAlign:
2420 location.setX(location.x() - width / 2);
2422 case RightTextAlign:
2423 location.setX(location.x() - width);
2429 // The slop built in to this mask rect matches the heuristic used in FontCGWin.cpp for GDI text.
2430 FloatRect textRect = FloatRect(location.x() - fontMetrics.height() / 2, location.y() - fontMetrics.ascent() - fontMetrics.lineGap(),
2431 width + fontMetrics.height(), fontMetrics.lineSpacing());
2433 inflateStrokeRect(textRect);
2436 const CanvasStyle& drawStyle = fill ? state().fillStyle : state().strokeStyle;
2437 if (drawStyle.canvasGradient() || drawStyle.canvasPattern()) {
2438 IntRect maskRect = enclosingIntRect(textRect);
2440 // If we have a shadow, we need to draw it before the mask operation.
2441 // Follow a procedure similar to paintTextWithShadows in TextPainter.
2443 if (shouldDrawShadows()) {
2444 GraphicsContextStateSaver stateSaver(*c);
2446 FloatSize offset(0, 2 * maskRect.height());
2448 FloatSize shadowOffset;
2451 c->getShadow(shadowOffset, shadowRadius, shadowColor);
2453 FloatRect shadowRect(maskRect);
2454 shadowRect.inflate(shadowRadius * 1.4);
2455 shadowRect.move(shadowOffset * -1);
2456 c->clip(shadowRect);
2458 shadowOffset += offset;
2460 c->setLegacyShadow(shadowOffset, shadowRadius, shadowColor);
2463 c->setFillColor(Color::black);
2465 c->setStrokeColor(Color::black);
2467 fontProxy.drawBidiText(*c, textRun, location + offset, FontCascade::UseFallbackIfFontNotReady);
2470 auto maskImage = ImageBuffer::createCompatibleBuffer(maskRect.size(), ColorSpaceSRGB, *c);
2474 auto& maskImageContext = maskImage->context();
2477 maskImageContext.setFillColor(Color::black);
2479 maskImageContext.setStrokeColor(Color::black);
2480 maskImageContext.setStrokeThickness(c->strokeThickness());
2483 maskImageContext.setTextDrawingMode(fill ? TextModeFill : TextModeStroke);
2486 maskImageContext.translate(location.x() - maskRect.x(), location.y() - maskRect.y());
2487 // We draw when fontWidth is 0 so compositing operations (eg, a "copy" op) still work.
2488 maskImageContext.scale(FloatSize((fontWidth > 0 ? (width / fontWidth) : 0), 1));
2489 fontProxy.drawBidiText(maskImageContext, textRun, FloatPoint(0, 0), FontCascade::UseFallbackIfFontNotReady);
2491 maskImageContext.translate(-maskRect.x(), -maskRect.y());
2492 fontProxy.drawBidiText(maskImageContext, textRun, location, FontCascade::UseFallbackIfFontNotReady);
2495 GraphicsContextStateSaver stateSaver(*c);
2496 c->clipToImageBuffer(*maskImage, maskRect);
2497 drawStyle.applyFillColor(*c);
2498 c->fillRect(maskRect);
2503 c->setTextDrawingMode(fill ? TextModeFill : TextModeStroke);
2505 GraphicsContextStateSaver stateSaver(*c);
2507 c->translate(location.x(), location.y());
2508 // We draw when fontWidth is 0 so compositing operations (eg, a "copy" op) still work.
2509 c->scale(FloatSize((fontWidth > 0 ? (width / fontWidth) : 0), 1));
2510 location = FloatPoint();
2513 if (isFullCanvasCompositeMode(state().globalComposite)) {
2514 beginCompositeLayer();
2515 fontProxy.drawBidiText(*c, textRun, location, FontCascade::UseFallbackIfFontNotReady);
2516 endCompositeLayer();
2517 didDrawEntireCanvas();
2518 } else if (state().globalComposite == CompositeCopy) {
2520 fontProxy.drawBidiText(*c, textRun, location, FontCascade::UseFallbackIfFontNotReady);
2521 didDrawEntireCanvas();
2523 fontProxy.drawBidiText(*c, textRun, location, FontCascade::UseFallbackIfFontNotReady);
2528 void CanvasRenderingContext2D::inflateStrokeRect(FloatRect& rect) const
2530 // Fast approximation of the stroke's bounding rect.
2531 // This yields a slightly oversized rect but is very fast
2532 // compared to Path::strokeBoundingRect().
2533 static const float root2 = sqrtf(2);
2534 float delta = state().lineWidth / 2;
2535 if (state().lineJoin == MiterJoin)
2536 delta *= state().miterLimit;
2537 else if (state().lineCap == SquareCap)
2539 rect.inflate(delta);
2542 auto CanvasRenderingContext2D::fontProxy() -> const FontProxy&
2544 canvas().document().updateStyleIfNeeded();
2545 if (!state().font.realized())
2546 setFont(state().unparsedFont);
2547 return state().font;
2550 #if ENABLE(ACCELERATED_2D_CANVAS)
2552 PlatformLayer* CanvasRenderingContext2D::platformLayer() const
2554 return canvas().buffer() ? canvas().buffer()->platformLayer() : nullptr;
2559 static inline InterpolationQuality smoothingToInterpolationQuality(CanvasRenderingContext2D::ImageSmoothingQuality quality)
2562 case CanvasRenderingContext2D::ImageSmoothingQuality::Low:
2563 return InterpolationLow;
2564 case CanvasRenderingContext2D::ImageSmoothingQuality::Medium:
2565 return InterpolationMedium;
2566 case CanvasRenderingContext2D::ImageSmoothingQuality::High:
2567 return InterpolationHigh;
2570 ASSERT_NOT_REACHED();
2571 return InterpolationLow;
2574 String CanvasRenderingContext2D::stringForImageSmoothingQuality(ImageSmoothingQuality imageSmoothingQuality)
2576 switch (imageSmoothingQuality) {
2577 case ImageSmoothingQuality::Low:
2578 return ASCIILiteral("low");
2579 case ImageSmoothingQuality::Medium:
2580 return ASCIILiteral("medium");
2581 case ImageSmoothingQuality::High:
2582 return ASCIILiteral("high");
2585 ASSERT_NOT_REACHED();
2589 auto CanvasRenderingContext2D::imageSmoothingQuality() const -> ImageSmoothingQuality
2591 return state().imageSmoothingQuality;
2594 void CanvasRenderingContext2D::setImageSmoothingQuality(ImageSmoothingQuality quality)
2596 if (quality == state().imageSmoothingQuality)
2600 modifiableState().imageSmoothingQuality = quality;
2602 if (!state().imageSmoothingEnabled)
2605 if (auto* context = drawingContext())
2606 context->setImageInterpolationQuality(smoothingToInterpolationQuality(quality));
2609 bool CanvasRenderingContext2D::imageSmoothingEnabled() const
2611 return state().imageSmoothingEnabled;
2614 void CanvasRenderingContext2D::setImageSmoothingEnabled(bool enabled)
2616 if (enabled == state().imageSmoothingEnabled)
2620 modifiableState().imageSmoothingEnabled = enabled;
2621 auto* c = drawingContext();
2623 c->setImageInterpolationQuality(enabled ? smoothingToInterpolationQuality(state().imageSmoothingQuality) : InterpolationNone);
2626 } // namespace WebCore