1 /*
2  * Copyright (C) 2004 Apple Computer, Inc.  All rights reserved.
3  * Copyright (C) 2005 Zack Rusin <zack@kde.org>
4  * Copyright (C) 2007, 2008 Maksim Orlovich <maksim@kde.org>
5  * Copyright (C) 2007, 2008 Fredrik Höglund <fredrik@kde.org>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20  *
21  * Portions of this code are (c) by Apple Computer, Inc. and were licensed
22  * under the following terms:
23  *
24  * Redistribution and use in source and binary forms, with or without
25  * modification, are permitted provided that the following conditions
26  * are met:
27  * 1. Redistributions of source code must retain the above copyright
28  *    notice, this list of conditions and the following disclaimer.
29  * 2. Redistributions in binary form must reproduce the above copyright
30  *    notice, this list of conditions and the following disclaimer in the
31  *    documentation and/or other materials provided with the distribution.
32  *
33  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
34  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
35  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
36  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
37  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
38  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
39  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
40  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
41  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
42  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
43  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44  */
45 
46 #include "html_canvasimpl.h"
47 #include "html_documentimpl.h"
48 
49 #include <khtmlview.h>
50 #include <khtml_part.h>
51 
52 #include <dom/dom_exception.h>
53 #include <rendering/render_canvasimage.h>
54 #include <rendering/render_flow.h>
55 #include <css/cssstyleselector.h>
56 #include <css/cssproperties.h>
57 #include <css/cssparser.h>
58 #include <css/cssvalues.h>
59 #include <css/csshelper.h>
60 #include <xml/dom2_eventsimpl.h>
61 #include <html/html_imageimpl.h>
62 #include <misc/helper.h> // for colorFromCSSValue
63 #include <misc/translator.h>
64 #include <misc/imagefilter.h>
65 #include <imload/canvasimage.h>
66 #include <imload/imagemanager.h>
67 #include <kjs/global.h>
68 #include <kjs/operations.h> //uglyyy: needs for inf/NaN tests
69 
70 #include <QtAlgorithms>
71 #include <QCharRef>
72 #include <QPoint>
73 #include <QLocale>
74 #include <QImage>
75 #include "khtml_debug.h"
76 #include <cmath>
77 #include <limits>
78 
79 using namespace DOM;
80 using namespace khtml;
81 using namespace std;
82 
83 // on Windows fmod might be a macro so std::fmod will not work
84 #ifdef fmod
85 #undef fmod
86 #endif
87 
88 // -------------------------------------------------------------------------
89 
HTMLCanvasElementImpl(DocumentImpl * doc)90 HTMLCanvasElementImpl::HTMLCanvasElementImpl(DocumentImpl *doc)
91     : HTMLElementImpl(doc)
92 {
93     w = 300;
94     h = 150;
95     unsafe = false;
96 }
97 
~HTMLCanvasElementImpl()98 HTMLCanvasElementImpl::~HTMLCanvasElementImpl()
99 {
100     if (context) {
101         context->canvasElement = nullptr;
102     }
103 }
104 
parseAttribute(AttributeImpl * attr)105 void HTMLCanvasElementImpl::parseAttribute(AttributeImpl *attr)
106 {
107     bool ok = false;
108     int  val;
109     switch (attr->id()) {
110     // ### TODO: making them reflect w/h -- how?
111     case ATTR_WIDTH:
112         val = attr->val() ? attr->val()->toInt(&ok) : -1;
113         if (!ok || val <= 0) {
114             w = 300;
115         } else {
116             w = val;
117         }
118 
119         if (context) {
120             context->resetContext(w, h);
121         }
122         setChanged();
123         break;
124     case ATTR_HEIGHT:
125         val = attr->val() ? attr->val()->toInt(&ok) : -1;
126         if (!ok || val <= 0) {
127             h = 150;
128         } else {
129             h = val;
130         }
131 
132         if (context) {
133             context->resetContext(w, h);
134         }
135         setChanged();
136         break;
137     default:
138         HTMLElementImpl::parseAttribute(attr);
139     }
140 }
141 
id() const142 NodeImpl::Id HTMLCanvasElementImpl::id() const
143 {
144     return ID_CANVAS;
145 }
146 
attach()147 void HTMLCanvasElementImpl::attach()
148 {
149     assert(!attached());
150     assert(!m_render);
151     assert(parentNode());
152 
153     RenderStyle *_style = document()->styleSelector()->styleForElement(this);
154     _style->ref();
155     if (parentNode()->renderer() && parentNode()->renderer()->childAllowed() &&
156             _style->display() != NONE) {
157         m_render = new(document()->renderArena()) RenderCanvasImage(this);
158         m_render->setStyle(_style);
159         parentNode()->renderer()->addChild(m_render, nextRenderer());
160     }
161     _style->deref();
162 
163     NodeBaseImpl::attach();
164     if (m_render) {
165         m_render->updateFromElement();
166     }
167 }
168 
getContext2D()169 CanvasContext2DImpl *HTMLCanvasElementImpl::getContext2D()
170 {
171     if (!context) {
172         context = new CanvasContext2DImpl(this, w, h);
173     }
174     return context.get();
175 }
176 
getCanvasImage()177 khtmlImLoad::CanvasImage *HTMLCanvasElementImpl::getCanvasImage()
178 {
179     return getContext2D()->canvasImage;
180 }
181 
isUnsafe() const182 bool HTMLCanvasElementImpl::isUnsafe() const
183 {
184     return unsafe;
185 }
186 
markUnsafe()187 void HTMLCanvasElementImpl::markUnsafe()
188 {
189     unsafe = true;
190 }
191 
toDataURL(int & exceptionCode)192 QString HTMLCanvasElementImpl::toDataURL(int &exceptionCode)
193 {
194     if (isUnsafe()) {
195         exceptionCode = DOMException::INVALID_ACCESS_ERR;
196         return "";
197     }
198 
199     khtmlImLoad::CanvasImage *ci = getCanvasImage();
200     context->syncBackBuffer();
201 
202     QByteArray pngBytes;
203     QBuffer    pngSink(&pngBytes);
204     pngSink.open(QIODevice::WriteOnly);
205     ci->qimage()->save(&pngSink, "PNG");
206     pngSink.close();
207 
208     return QString::fromLatin1("data:image/png;base64,") + pngBytes.toBase64();
209 }
210 
211 // -------------------------------------------------------------------------
CanvasContext2DImpl(HTMLCanvasElementImpl * element,int width,int height)212 CanvasContext2DImpl::CanvasContext2DImpl(HTMLCanvasElementImpl *element, int width, int height):
213     canvasElement(element), canvasImage(nullptr)
214 {
215     resetContext(width, height);
216 }
217 
~CanvasContext2DImpl()218 CanvasContext2DImpl::~CanvasContext2DImpl()
219 {
220     if (workPainter.isActive()) {
221         workPainter.end();    // Make sure to stop it before blowing the image away!
222     }
223     delete canvasImage;
224 }
225 
226 // Basic infrastructure..
resetContext(int width,int height)227 void CanvasContext2DImpl::resetContext(int width, int height)
228 {
229     // ### FIXME FIXME: use khtmlImLoad's limit policy
230     // for physical canvas and transform painter to match logical resolution
231     if (workPainter.isActive()) {
232         workPainter.end();
233     }
234 
235     if (canvasImage) {
236         canvasImage->resizeImage(width, height);
237     } else {
238         canvasImage = new khtmlImLoad::CanvasImage(width, height);
239     }
240     canvasImage->qimage()->fill(0x00000000); // transparent black is the initial state
241 
242     stateStack.clear();
243 
244     PaintState defaultState;
245     beginPath();
246     defaultState.infinityTransform = false;
247     defaultState.clipPath = QPainterPath();
248     defaultState.clipPath.setFillRule(Qt::WindingFill);
249     defaultState.clipping = false;
250 
251     defaultState.globalAlpha = 1.0f;
252     defaultState.globalCompositeOperation = QPainter::CompositionMode_SourceOver;
253 
254     defaultState.strokeStyle = new CanvasColorImpl(QColor(Qt::black));
255     defaultState.fillStyle   = new CanvasColorImpl(QColor(Qt::black));
256 
257     defaultState.lineWidth  = 1.0f;
258     defaultState.lineCap    = Qt::FlatCap;
259     defaultState.lineJoin   = Qt::SvgMiterJoin;
260     defaultState.miterLimit = 10.0f;
261 
262     defaultState.shadowOffsetX = 0.0f;
263     defaultState.shadowOffsetY = 0.0f;
264     defaultState.shadowBlur    = 0.0f;
265     defaultState.shadowColor   = QColor(0, 0, 0, 0); // Transparent black
266 
267     stateStack.push(defaultState);
268 
269     dirty = DrtAll;
270     needRendererUpdate();
271     emptyPath = true;
272 }
273 
save()274 void CanvasContext2DImpl::save()
275 {
276     stateStack.push(stateStack.top());
277 }
278 
restore()279 void CanvasContext2DImpl::restore()
280 {
281     if (stateStack.size() <= 1) {
282         return;
283     }
284 
285     stateStack.pop();
286     dirty = DrtAll;
287 }
288 
acquirePainter()289 QPainter *CanvasContext2DImpl::acquirePainter()
290 {
291     if (!workPainter.isActive()) {
292         workPainter.begin(canvasImage->qimage());
293         workPainter.setRenderHint(QPainter::Antialiasing);
294         workPainter.setRenderHint(QPainter::SmoothPixmapTransform);
295         dirty = DrtAll;
296     }
297 
298     PaintState &state = activeState();
299 
300     if (dirty & DrtClip) {
301         if (state.clipping) {
302             workPainter.setClipPath(state.clipPath);
303         } else {
304             workPainter.setClipping(false);
305         }
306     }
307 
308     if (dirty & DrtAlpha) {
309         workPainter.setOpacity(state.globalAlpha);
310     }
311     if (dirty & DrtCompOp) {
312         workPainter.setCompositionMode(state.globalCompositeOperation);
313     }
314     if (dirty & DrtStroke) {
315         QPen pen;
316         pen.setWidth(state.lineWidth);
317         pen.setCapStyle(state.lineCap);
318         pen.setJoinStyle(state.lineJoin);
319         pen.setMiterLimit(state.miterLimit);
320 
321         CanvasStyleBaseImpl *style = state.strokeStyle.get();
322         if (style->type() == CanvasStyleBaseImpl::Color) {
323             pen.setColor(static_cast<CanvasColorImpl *>(style)->color);
324         } else {
325             pen.setBrush(style->toBrush());
326         }
327         workPainter.setPen(pen); // ### should I even do this?
328         // I have a feeling I am mixing up path and
329         // non-path ops
330     }
331     if (dirty & DrtFill) {
332         workPainter.setBrush(state.fillStyle->toBrush());
333     }
334 
335     dirty = 0;
336 
337     needRendererUpdate();
338     return &workPainter;
339 }
340 
extractImage(ElementImpl * el,int & exceptionCode,bool & unsafeOut) const341 QImage CanvasContext2DImpl::extractImage(ElementImpl *el, int &exceptionCode, bool &unsafeOut) const
342 {
343     QImage pic;
344 
345     exceptionCode = 0;
346 
347     unsafeOut = false;
348     if (el->id() == ID_CANVAS) {
349         CanvasContext2DImpl *other = static_cast<HTMLCanvasElementImpl *>(el)->getContext2D();
350         other->syncBackBuffer();
351         pic = *other->canvasImage->qimage();
352 
353         if (static_cast<HTMLCanvasElementImpl *>(el)->isUnsafe()) {
354             unsafeOut = true;
355         }
356     } else if (el->id() == ID_IMG) {
357         HTMLImageElementImpl *img = static_cast<HTMLImageElementImpl *>(el);
358         if (img->complete()) {
359             pic = img->currentImage();
360         } else {
361             exceptionCode = DOMException::INVALID_STATE_ERR;
362         }
363 
364         if (img->isUnsafe()) {
365             unsafeOut = true;
366         }
367     } else {
368         exceptionCode = DOMException::TYPE_MISMATCH_ERR;
369     }
370 
371     return pic;
372 }
373 
needRendererUpdate()374 void CanvasContext2DImpl::needRendererUpdate()
375 {
376     needsCommit = true;
377     if (canvasElement) {
378         canvasElement->setChanged();
379     }
380 }
381 
syncBackBuffer()382 void CanvasContext2DImpl::syncBackBuffer()
383 {
384     if (workPainter.isActive()) {
385         workPainter.end();
386     }
387 }
388 
commit()389 void CanvasContext2DImpl::commit()
390 {
391     syncBackBuffer();
392 
393     // Flush caches if we have changes.
394     if (needsCommit) {
395         canvasImage->contentUpdated();
396         needsCommit = false;
397     }
398 }
399 
canvas() const400 HTMLCanvasElementImpl *CanvasContext2DImpl::canvas() const
401 {
402     return canvasElement;
403 }
404 
405 // Transformation ops
406 //
407 
degrees(float radians)408 static inline float degrees(float radians)
409 {
410     return radians * 180.0 / M_PI;
411 }
412 
isInfArg(float x)413 static inline bool isInfArg(float x)
414 {
415     return KJS::isInf(x) || KJS::isNaN(x);
416 }
417 
scale(float x,float y)418 void CanvasContext2DImpl::scale(float x, float y)
419 {
420     dirty |= DrtTransform;
421 
422     bool &infinityTransform = activeState().infinityTransform;
423     infinityTransform |= isInfArg(x) | isInfArg(y);
424     if (infinityTransform) {
425         return;
426     }
427 
428     activeState().transform.scale(x, y);
429 }
430 
rotate(float angle)431 void CanvasContext2DImpl::rotate(float angle)
432 {
433     dirty |= DrtTransform;
434 
435     bool &infinityTransform = activeState().infinityTransform;
436     infinityTransform |= isInfArg(angle);
437     if (infinityTransform) {
438         return;
439     }
440 
441     activeState().transform.rotateRadians(angle);
442 }
443 
translate(float x,float y)444 void CanvasContext2DImpl::translate(float x, float y)
445 {
446     dirty |= DrtTransform;
447 
448     bool &infinityTransform = activeState().infinityTransform;
449     infinityTransform |= isInfArg(x) | isInfArg(y);
450     if (infinityTransform) {
451         return;
452     }
453 
454     activeState().transform.translate(x, y);
455 }
456 
transform(float m11,float m12,float m21,float m22,float dx,float dy)457 void CanvasContext2DImpl::transform(float m11, float m12, float m21, float m22, float dx, float dy)
458 {
459     dirty |= DrtTransform;
460 
461     bool &infinityTransform = activeState().infinityTransform;
462     infinityTransform |= isInfArg(m11) | isInfArg(m12) | isInfArg(m21) | isInfArg(m22) |
463                          isInfArg(dx)  | isInfArg(dy);
464     if (infinityTransform) {
465         return;
466     }
467 
468     activeState().transform *= QTransform(m11, m12, 0.0f, m21, m22, 0.0f, dx, dy, 1.0f);
469 }
470 
setTransform(float m11,float m12,float m21,float m22,float dx,float dy)471 void CanvasContext2DImpl::setTransform(float m11, float m12, float m21, float m22, float dx, float dy)
472 {
473     activeState().transform.reset();
474     activeState().infinityTransform = false; // As cleared the matrix..
475     transform(m11, m12, m21, m22, dx, dy);
476 }
477 
478 // Composition state setting
479 //
480 
globalAlpha() const481 float CanvasContext2DImpl::globalAlpha() const
482 {
483     return activeState().globalAlpha;
484 }
485 
setGlobalAlpha(float a)486 void CanvasContext2DImpl::setGlobalAlpha(float a)
487 {
488     if (a < 0.0f || a > 1.0f) {
489         return;
490     }
491 
492     activeState().globalAlpha = a;
493     dirty |= DrtAlpha;
494 }
495 
496 static const IDTranslator<QString, QPainter::CompositionMode, const char *>::Info compModeTranslatorTable[] = {
497     {"source-over", QPainter::CompositionMode_SourceOver},
498     {"source-out",  QPainter::CompositionMode_SourceOut},
499     {"source-in",   QPainter::CompositionMode_SourceIn},
500     {"source-atop", QPainter::CompositionMode_SourceAtop},
501     {"destination-atop", QPainter::CompositionMode_DestinationAtop},
502     {"destination-in",   QPainter::CompositionMode_DestinationIn},
503     {"destination-out",  QPainter::CompositionMode_DestinationOut},
504     {"destination-over", QPainter::CompositionMode_DestinationOver},
505     {"lighter", QPainter::CompositionMode_Plus},
506     {"copy",    QPainter::CompositionMode_Source},
507     {"xor",     QPainter::CompositionMode_Xor},
508     {nullptr, (QPainter::CompositionMode)0}
509 };
510 
MAKE_TRANSLATOR(compModeTranslator,QString,QPainter::CompositionMode,const char *,compModeTranslatorTable)511 MAKE_TRANSLATOR(compModeTranslator, QString, QPainter::CompositionMode, const char *, compModeTranslatorTable)
512 
513 DOM::DOMString CanvasContext2DImpl::globalCompositeOperation() const
514 {
515     return compModeTranslator()->toLeft(activeState().globalCompositeOperation);
516 }
517 
setGlobalCompositeOperation(const DOM::DOMString & op)518 void CanvasContext2DImpl::setGlobalCompositeOperation(const DOM::DOMString &op)
519 {
520     QString opStr = op.string();
521     if (!compModeTranslator()->hasLeft(opStr)) {
522         return;    // Ignore unknown
523     }
524     activeState().globalCompositeOperation = compModeTranslator()->toRight(opStr);
525     dirty |= DrtCompOp;
526 }
527 
528 // Colors and styles.
529 //
530 
colorFromString(DOM::DOMString domStr)531 static QColor colorFromString(DOM::DOMString domStr)
532 {
533     // We make a temporary CSS decl. object to parse the color using the CSS parser.
534     CSSStyleDeclarationImpl  tempStyle(nullptr);
535     if (!tempStyle.setProperty(CSS_PROP_COLOR, domStr)) {
536         return QColor();
537     }
538 
539     CSSValueImpl *val = tempStyle.getPropertyCSSValue(CSS_PROP_COLOR);
540     if (!val || val->cssValueType() != CSSValue::CSS_PRIMITIVE_VALUE) {
541         return QColor();
542     }
543 
544     CSSPrimitiveValueImpl *primVal = static_cast<CSSPrimitiveValueImpl *>(val);
545 
546     if (primVal->primitiveType() == CSSPrimitiveValue::CSS_IDENT) {
547         return colorForCSSValue(primVal->getIdent());
548     }
549 
550     if (primVal->primitiveType() != CSSPrimitiveValue::CSS_RGBCOLOR) {
551         return QColor();
552     }
553     return QColor::fromRgba(primVal->getRGBColorValue());
554 }
555 
colorToString(const QColor & color)556 static DOMString colorToString(const QColor &color)
557 {
558     QString str;
559     if (color.alpha() == 255) {
560         str.sprintf("#%02x%02x%02x", color.red(), color.green(), color.blue());
561     } else {
562         QString alphaColor = QString::number(color.alphaF());
563         // Ensure we always have a decimal period
564         if ((int)color.alphaF() == color.alphaF()) {
565             alphaColor = QString::number((int)color.alphaF()) + ".0";
566         }
567 
568         str.sprintf("rgba(%d, %d, %d, ", color.red(), color.green(), color.blue());
569         str += alphaColor + ")";
570     }
571     return str;
572 }
573 
574 //-------
575 
toString() const576 DOM::DOMString CanvasColorImpl::toString() const
577 {
578     return colorToString(color);
579 }
580 
fromString(const DOM::DOMString & str)581 CanvasColorImpl *CanvasColorImpl::fromString(const DOM::DOMString &str)
582 {
583     QColor cl = colorFromString(str);
584     if (!cl.isValid()) {
585         return nullptr;
586     }
587     return new CanvasColorImpl(cl);
588 }
589 
590 //-------
591 
CanvasGradientImpl(QGradient * newGradient,float innerRadius,bool inverse)592 CanvasGradientImpl::CanvasGradientImpl(QGradient *newGradient, float innerRadius, bool inverse)
593     : gradient(newGradient), innerRadius(innerRadius), inverse(inverse)
594 {}
595 
adjustPosition(qreal pos,const QGradientStops & stops)596 static qreal adjustPosition(qreal pos, const QGradientStops &stops)
597 {
598     QGradientStops::const_iterator itr = stops.constBegin();
599     const qreal smallDiff = 0.00001;
600     while (itr != stops.constEnd()) {
601         const QGradientStop &stop = *itr;
602         ++itr;
603         bool atEnd = (itr != stops.constEnd());
604         if (qFuzzyCompare(pos, stop.first)) {
605             if (atEnd || !qFuzzyCompare(pos + smallDiff, (*itr).first)) {
606                 return qMin(pos + smallDiff, qreal(1.0));
607             }
608         }
609     }
610     return pos;
611 }
612 
addColorStop(float offset,const DOM::DOMString & color,int & exceptionCode)613 void CanvasGradientImpl::addColorStop(float offset, const DOM::DOMString &color, int &exceptionCode)
614 {
615     // ### we may have to handle the "currentColor" KW here. ouch.
616 
617     exceptionCode = 0;
618     if (isInfArg(offset)) {
619         exceptionCode = DOMException::INDEX_SIZE_ERR;
620         return;
621     }
622 
623     //### fuzzy compare (also for alpha)
624     if (offset < 0 || offset > 1) {
625         exceptionCode = DOMException::INDEX_SIZE_ERR;
626         return;
627     }
628 
629     QColor qcolor = colorFromString(color);
630     if (!qcolor.isValid()) {
631         exceptionCode = DOMException::SYNTAX_ERR;
632         return;
633     }
634 
635     // Adjust the position of the stop to emulate an inner radius.
636     // If the inner radius is larger than the outer, we'll reverse
637     // the position of the stop.
638     if (gradient->type() == QGradient::RadialGradient) {
639         if (inverse) {
640             offset = 1.0 - offset;
641         }
642 
643         offset = innerRadius + offset * (1.0 - innerRadius);
644     }
645 
646     //<canvas> says that gradient can have two stops at the same position
647     //Qt doesn't handle that. We hack around that by creating a fake position
648     //stop.
649     offset = adjustPosition(offset, gradient->stops());
650 
651     gradient->setColorAt(offset, qcolor);
652 }
653 
~CanvasGradientImpl()654 CanvasGradientImpl::~CanvasGradientImpl()
655 {
656     delete gradient;
657 }
658 
toBrush() const659 QBrush CanvasGradientImpl::toBrush() const
660 {
661     return QBrush(*gradient);
662 }
663 
664 //-------
665 
CanvasPatternImpl(const QImage & inImg,bool unsafe,bool rx,bool ry)666 CanvasPatternImpl::CanvasPatternImpl(const QImage &inImg, bool unsafe, bool rx, bool ry):
667     img(inImg), repeatX(rx), repeatY(ry), unsafe(unsafe)
668 {}
669 
toBrush() const670 QBrush CanvasPatternImpl::toBrush() const
671 {
672     return QBrush(img);
673 }
674 
clipForRepeat(const QPointF & origin,const QRectF & fillBounds) const675 QRectF CanvasPatternImpl::clipForRepeat(const QPointF &origin, const QRectF &fillBounds) const
676 {
677     if (repeatX && repeatY) {
678         return QRectF();
679     }
680 
681     if (!repeatX && !repeatY) {
682         return QRectF(origin, img.size());
683     }
684 
685     if (repeatX) {
686         return QRectF(fillBounds.x(), origin.y(), fillBounds.width(), img.height());
687     }
688 
689     // repeatY
690     return QRectF(origin.x(), fillBounds.y(), img.width(), fillBounds.height());
691 }
692 
693 //-------
694 
CanvasImageDataImpl(unsigned width,unsigned height)695 CanvasImageDataImpl::CanvasImageDataImpl(unsigned width, unsigned height) : data(width, height, QImage::Format_ARGB32)
696 {}
697 
CanvasImageDataImpl(const QImage & _data)698 CanvasImageDataImpl::CanvasImageDataImpl(const QImage &_data): data(_data)
699 {}
700 
clone() const701 CanvasImageDataImpl *CanvasImageDataImpl::clone() const
702 {
703     return new CanvasImageDataImpl(data);
704 }
705 
width() const706 unsigned CanvasImageDataImpl::width() const
707 {
708     return data.width();
709 }
710 
height() const711 unsigned CanvasImageDataImpl::height() const
712 {
713     return data.height();
714 }
715 
716 #if 0
717 static inline unsigned char unpremulComponent(unsigned original, unsigned alpha)
718 {
719     unsigned char val =  alpha ? (unsigned char)(original * 255 / alpha) : 0;
720     return val;
721 }
722 #endif
723 
pixel(unsigned pixelNum) const724 QColor CanvasImageDataImpl::pixel(unsigned pixelNum) const
725 {
726     int w = data.width();
727     QRgb code = data.pixel(pixelNum % w, pixelNum / w);
728     return code;
729 }
730 
731 #if 0
732 static inline unsigned char premulComponent(unsigned original, unsigned alpha)
733 {
734     unsigned product = original * alpha; // this is conceptually 255 * intended value.
735     return (unsigned char)((product + product / 256 + 128) / 256);
736 }
737 #endif
738 
setPixel(unsigned pixelNum,const QColor & val)739 void CanvasImageDataImpl::setPixel(unsigned pixelNum, const QColor &val)
740 {
741     int w = data.width();
742     data.setPixel(pixelNum % w, pixelNum / w, val.rgba());
743 }
744 
setComponent(unsigned pixelNum,int component,int value)745 void CanvasImageDataImpl::setComponent(unsigned pixelNum, int component,
746                                        int value)
747 {
748     int w = data.width();
749     int x = pixelNum % w;
750     int y = pixelNum / w;
751     // ### could avoid inherent QImage::detach() by a const cast
752     QRgb *rgb = reinterpret_cast<QRgb *>(data.scanLine(y) + 4 * x);
753 
754     switch (component) {
755     case 0: //Red
756         *rgb = qRgba(value, qGreen(*rgb), qBlue(*rgb), qAlpha(*rgb));
757         break;
758     case 1: //Green
759         *rgb = qRgba(qRed(*rgb), value, qBlue(*rgb), qAlpha(*rgb));
760         break;
761     case 2: //Blue
762         *rgb = qRgba(qRed(*rgb), qGreen(*rgb), value, qAlpha(*rgb));
763         break;
764     case 3: //Alpha
765     default:
766         *rgb = qRgba(qRed(*rgb), qGreen(*rgb), qBlue(*rgb), value);
767         break;
768     }
769 }
770 
771 //-------
772 
setStrokeStyle(CanvasStyleBaseImpl * strokeStyle)773 void CanvasContext2DImpl::setStrokeStyle(CanvasStyleBaseImpl *strokeStyle)
774 {
775     if (!strokeStyle) {
776         return;
777     }
778     if (strokeStyle->isUnsafe()) {
779         canvas()->markUnsafe();
780     }
781 
782     activeState().strokeStyle = strokeStyle;
783     dirty |= DrtStroke;
784 }
785 
strokeStyle() const786 CanvasStyleBaseImpl *CanvasContext2DImpl::strokeStyle() const
787 {
788     return activeState().strokeStyle.get();
789 }
790 
setFillStyle(CanvasStyleBaseImpl * fillStyle)791 void CanvasContext2DImpl::setFillStyle(CanvasStyleBaseImpl *fillStyle)
792 {
793     if (!fillStyle) {
794         return;
795     }
796     if (fillStyle->isUnsafe()) {
797         canvas()->markUnsafe();
798     }
799 
800     activeState().fillStyle = fillStyle;
801     dirty |= DrtFill;
802 }
803 
fillStyle() const804 CanvasStyleBaseImpl *CanvasContext2DImpl::fillStyle() const
805 {
806     return activeState().fillStyle.get();
807 }
808 
createLinearGradient(float x0,float y0,float x1,float y1) const809 CanvasGradientImpl *CanvasContext2DImpl::createLinearGradient(float x0, float y0, float x1, float y1) const
810 {
811     QLinearGradient *grad = new QLinearGradient(x0, y0, x1, y1);
812     return new CanvasGradientImpl(grad);
813 }
814 
createRadialGradient(float x0,float y0,float r0,float x1,float y1,float r1,int & exceptionCode) const815 CanvasGradientImpl *CanvasContext2DImpl::createRadialGradient(float x0, float y0, float r0,
816         float x1, float y1, float r1,
817         int &exceptionCode) const
818 {
819     exceptionCode = 0;
820     //### fuzzy
821     if (r0 < 0.0f || r1 < 0.0f) {
822         exceptionCode = DOMException::INDEX_SIZE_ERR;
823         return nullptr;
824     }
825 
826     QPointF center, focalPoint;
827     float radius, innerRadius;
828     bool inverse;
829 
830     // Use the larger of the two radii as the radius in the QGradient.
831     // The gradient is always supposed to move from r0 to r1, so if r0 is
832     // larger than r1, we'll use r0 as the radius and reverse the direction
833     // of the gradient by inverting the positions of the color stops.
834     // innerRadius is a percentage of the outer radius.
835     if (r1 > r0) {
836         center      = QPointF(x1, y1);
837         focalPoint  = QPointF(x0, y0);
838         radius      = r1;
839         innerRadius = (r1 > 0.0f ? r0 / r1 : 0.0f);
840         inverse     = false;
841     } else {
842         center      = QPointF(x0, y0);
843         focalPoint  = QPointF(x1, y1);
844         radius      = r0;
845         innerRadius = (r0 > 0.0f ? r1 / r0 : 0.0f);
846         inverse     = true;
847     }
848 
849     QGradient *gradient = new QRadialGradient(center, radius, focalPoint);
850     return new CanvasGradientImpl(gradient, innerRadius, inverse);
851 }
852 
createPattern(ElementImpl * pat,const DOMString & rpt,int & exceptionCode) const853 CanvasPatternImpl *CanvasContext2DImpl::createPattern(ElementImpl *pat, const DOMString &rpt,
854         int &exceptionCode) const
855 {
856     exceptionCode = 0;
857 
858     // Decode repetition..
859     bool repeatX;
860     bool repeatY;
861 
862     if (rpt == "repeat" || rpt.isEmpty()) {
863         repeatX = true;
864         repeatY = true;
865     } else if (rpt == "repeat-x") {
866         repeatX = true;
867         repeatY = false;
868     } else if (rpt == "repeat-y") {
869         repeatX = false;
870         repeatY = true;
871     } else if (rpt == "no-repeat") {
872         repeatX = false;
873         repeatY = false;
874     } else {
875         exceptionCode = DOMException::SYNTAX_ERR;
876         return nullptr;
877     }
878 
879     bool unsafe;
880     QImage pic = extractImage(pat, exceptionCode, unsafe);
881     if (exceptionCode) {
882         return nullptr;
883     }
884 
885     return new CanvasPatternImpl(pic, unsafe, repeatX, repeatY);
886 }
887 
888 // Pen style ops
889 //
lineWidth() const890 float CanvasContext2DImpl::lineWidth() const
891 {
892     return activeState().lineWidth;
893 }
894 
setLineWidth(float newLW)895 void CanvasContext2DImpl::setLineWidth(float newLW)
896 {
897     if (newLW <= 0.0) {
898         return;
899     }
900     activeState().lineWidth = newLW;
901     dirty |= DrtStroke;
902 }
903 
904 static const IDTranslator<QString, Qt::PenCapStyle, const char *>::Info penCapTranslatorTable[] = {
905     {"round", Qt::RoundCap},
906     {"square", Qt::SquareCap},
907     {"butt", Qt::FlatCap},
908     {nullptr, (Qt::PenCapStyle)0}
909 };
910 
MAKE_TRANSLATOR(penCapTranslator,QString,Qt::PenCapStyle,const char *,penCapTranslatorTable)911 MAKE_TRANSLATOR(penCapTranslator, QString, Qt::PenCapStyle, const char *, penCapTranslatorTable)
912 
913 DOMString CanvasContext2DImpl::lineCap() const
914 {
915     return penCapTranslator()->toLeft(activeState().lineCap);
916 }
917 
setLineCap(const DOM::DOMString & cap)918 void CanvasContext2DImpl::setLineCap(const DOM::DOMString &cap)
919 {
920     QString capStr = cap.string();
921     if (!penCapTranslator()->hasLeft(capStr)) {
922         return;
923     }
924     activeState().lineCap = penCapTranslator()->toRight(capStr);
925     dirty |= DrtStroke;
926 }
927 
928 static const IDTranslator<QString, Qt::PenJoinStyle, const char *>::Info penJoinTranslatorTable[] = {
929     {"round", Qt::RoundJoin},
930     {"miter", Qt::SvgMiterJoin},
931     {"bevel", Qt::BevelJoin},
932     {nullptr, (Qt::PenJoinStyle)0}
933 };
934 
MAKE_TRANSLATOR(penJoinTranslator,QString,Qt::PenJoinStyle,const char *,penJoinTranslatorTable)935 MAKE_TRANSLATOR(penJoinTranslator, QString, Qt::PenJoinStyle, const char *, penJoinTranslatorTable)
936 
937 DOMString CanvasContext2DImpl::lineJoin() const
938 {
939     return penJoinTranslator()->toLeft(activeState().lineJoin);
940 }
941 
setLineJoin(const DOM::DOMString & join)942 void CanvasContext2DImpl::setLineJoin(const DOM::DOMString &join)
943 {
944     QString joinStr = join.string();
945     if (!penJoinTranslator()->hasLeft(joinStr)) {
946         return;
947     }
948     activeState().lineJoin = penJoinTranslator()->toRight(joinStr);
949     dirty |= DrtStroke;
950 }
951 
miterLimit() const952 float CanvasContext2DImpl::miterLimit() const
953 {
954     return activeState().miterLimit;
955 }
956 
setMiterLimit(float newML)957 void CanvasContext2DImpl::setMiterLimit(float newML)
958 {
959     if (newML <= 0.0) {
960         return;
961     }
962     activeState().miterLimit = newML;
963     dirty |= DrtStroke;
964 }
965 
966 // Shadow settings
967 //
shadowOffsetX() const968 float CanvasContext2DImpl::shadowOffsetX() const
969 {
970     return activeState().shadowOffsetX;
971 }
972 
setShadowOffsetX(float newOX)973 void  CanvasContext2DImpl::setShadowOffsetX(float newOX)
974 {
975     activeState().shadowOffsetX = newOX;
976 }
977 
shadowOffsetY() const978 float CanvasContext2DImpl::shadowOffsetY() const
979 {
980     return activeState().shadowOffsetY;
981 }
982 
setShadowOffsetY(float newOY)983 void  CanvasContext2DImpl::setShadowOffsetY(float newOY)
984 {
985     activeState().shadowOffsetY = newOY;
986 }
987 
shadowBlur() const988 float CanvasContext2DImpl::shadowBlur() const
989 {
990     return activeState().shadowBlur;
991 }
992 
setShadowBlur(float newBlur)993 void  CanvasContext2DImpl::setShadowBlur(float newBlur)
994 {
995     if (newBlur < 0) {
996         return;
997     }
998 
999     activeState().shadowBlur = newBlur;
1000 }
1001 
shadowColor() const1002 DOMString CanvasContext2DImpl::shadowColor() const
1003 {
1004     return colorToString(activeState().shadowColor);
1005 }
1006 
setShadowColor(const DOMString & newColor)1007 void CanvasContext2DImpl::setShadowColor(const DOMString &newColor)
1008 {
1009     // This not specified, it seems, but I presume setting
1010     // and invalid color does not change the state
1011     QColor cl = colorFromString(newColor);
1012     if (cl.isValid()) {
1013         activeState().shadowColor = cl;
1014     }
1015 }
1016 
1017 // Rectangle ops
1018 //
clearRect(float x,float y,float w,float h,int & exceptionCode)1019 void CanvasContext2DImpl::clearRect(float x, float y, float w, float h, int &exceptionCode)
1020 {
1021     exceptionCode = 0;
1022     if (w == 0.0f || h == 0.0f) {
1023         return;
1024     }
1025 
1026     QPainter *p = acquirePainter();
1027     p->setCompositionMode(QPainter::CompositionMode_Source);
1028     dirty |= DrtCompOp; // We messed it up..
1029 
1030     p->fillRect(QRectF(x, y, w, h), Qt::transparent);
1031 }
1032 
fillRect(float x,float y,float w,float h,int & exceptionCode)1033 void CanvasContext2DImpl::fillRect(float x, float y, float w, float h, int &exceptionCode)
1034 {
1035     exceptionCode = 0;
1036     if (w == 0.0f || h == 0.0f) {
1037         return;
1038     }
1039 
1040     QPainter *p = acquirePainter();
1041 
1042     QPainterPath path;
1043     path.addPolygon(QRectF(x, y, w, h) * activeState().transform);
1044     path.closeSubpath();
1045 
1046     drawPath(p, path, DrawFill);
1047 }
1048 
strokeRect(float x,float y,float w,float h,int & exceptionCode)1049 void CanvasContext2DImpl::strokeRect(float x, float y, float w, float h, int &exceptionCode)
1050 {
1051     exceptionCode = 0;
1052     if (w == 0.0f && h == 0.0f) {
1053         return;
1054     }
1055 
1056     QPainter *p = acquirePainter();
1057 
1058     QPainterPath path;
1059     path.addPolygon(QRectF(x, y, w, h) * activeState().transform);
1060     path.closeSubpath();
1061 
1062     drawPath(p, path, DrawStroke);
1063 }
1064 
isPathEmpty() const1065 inline bool CanvasContext2DImpl::isPathEmpty() const
1066 {
1067     // For an explanation of this, see the comment in beginPath()
1068     return emptyPath;
1069 }
1070 
1071 // Path ops
1072 //
beginPath()1073 void CanvasContext2DImpl::beginPath()
1074 {
1075     path = QPainterPath();
1076     path.setFillRule(Qt::WindingFill);
1077 
1078     // QPainterPath always contains an initial MoveTo element to (0, 0), and there is
1079     // no way to tell.
1080     // We used to insert a Inf/Inf element to tell if its empty. But that no longer
1081     // works with Qt newer than 2011-01-21
1082     // https://code.qt.io/cgit/qt/qt.git/commit/?id=972fcb6de69fb7ed3ae8147498ceb5d2ac79f057
1083     // Now go with a extra bool to check if its really empty.
1084     emptyPath = true;
1085 }
1086 
closePath()1087 void CanvasContext2DImpl::closePath()
1088 {
1089     path.closeSubpath();
1090 }
1091 
moveTo(float x,float y)1092 void CanvasContext2DImpl::moveTo(float x, float y)
1093 {
1094     path.moveTo(mapToDevice(x, y));
1095     emptyPath = false;
1096 }
1097 
lineTo(float x,float y)1098 void CanvasContext2DImpl::lineTo(float x, float y)
1099 {
1100     if (isPathEmpty()) {
1101         return;
1102     }
1103 
1104     path.lineTo(mapToDevice(x, y));
1105     emptyPath = false;
1106 }
1107 
quadraticCurveTo(float cpx,float cpy,float x,float y)1108 void CanvasContext2DImpl::quadraticCurveTo(float cpx, float cpy, float x, float y)
1109 {
1110     if (isPathEmpty()) {
1111         return;
1112     }
1113 
1114     path.quadTo(mapToDevice(cpx, cpy), mapToDevice(x, y));
1115     emptyPath = false;
1116 }
1117 
bezierCurveTo(float cp1x,float cp1y,float cp2x,float cp2y,float x,float y)1118 void CanvasContext2DImpl::bezierCurveTo(float cp1x, float cp1y, float cp2x, float cp2y, float x, float y)
1119 {
1120     if (isPathEmpty()) {
1121         return;
1122     }
1123 
1124     path.cubicTo(mapToDevice(cp1x, cp1y), mapToDevice(cp2x, cp2y), mapToDevice(x, y));
1125     emptyPath = false;
1126 }
1127 
rect(float x,float y,float w,float h,int & exceptionCode)1128 void CanvasContext2DImpl::rect(float x, float y, float w, float h, int &exceptionCode)
1129 {
1130     exceptionCode = 0;
1131 
1132     path.addPolygon(QRectF(x, y, w, h) * activeState().transform);
1133     path.closeSubpath();
1134 }
1135 
needsShadow() const1136 inline bool CanvasContext2DImpl::needsShadow() const
1137 {
1138     return activeState().shadowColor.alpha() > 0;
1139 }
1140 
clipForPatternRepeat(QPainter * p,PathPaintOp op) const1141 QPainterPath CanvasContext2DImpl::clipForPatternRepeat(QPainter *p, PathPaintOp op) const
1142 {
1143     const CanvasStyleBaseImpl *style = op == DrawFill ?
1144                                        activeState().fillStyle.get() : activeState().strokeStyle.get();
1145 
1146     if (style->type() != CanvasStyleBaseImpl::Pattern) {
1147         return QPainterPath();
1148     }
1149 
1150     const CanvasPatternImpl *pattern = static_cast<const CanvasPatternImpl *>(style);
1151     const QTransform &ctm = activeState().transform;
1152     const QRectF fillBounds = ctm.inverted().mapRect(QRectF(QPointF(), canvasImage->size()));
1153     const QRectF clipRect = pattern->clipForRepeat(p->brushOrigin(), fillBounds);
1154 
1155     if (clipRect.isEmpty()) {
1156         return QPainterPath();
1157     }
1158 
1159     QPainterPath path;
1160     path.addRect(clipRect);
1161     return path * ctm;
1162 }
1163 
drawPath(QPainter * p,const QPainterPath & path,const PathPaintOp op) const1164 void CanvasContext2DImpl::drawPath(QPainter *p, const QPainterPath &path, const PathPaintOp op) const
1165 {
1166     const PaintState &state = activeState();
1167     QPainterPathStroker stroker;
1168     QPainterPath fillPath;
1169     QBrush brush;
1170 
1171     if (state.infinityTransform) {
1172         return;
1173     }
1174 
1175     switch (op) {
1176     case DrawStroke:
1177         brush = p->pen().brush();
1178         stroker.setCapStyle(state.lineCap);
1179         stroker.setJoinStyle(state.lineJoin);
1180         stroker.setMiterLimit(state.miterLimit);
1181         stroker.setWidth(state.lineWidth);
1182         if (!state.transform.isIdentity() && state.transform.isInvertible()) {
1183             fillPath = stroker.createStroke(path * state.transform.inverted()) * state.transform;
1184         } else {
1185             fillPath = stroker.createStroke(path);
1186         }
1187         break;
1188 
1189     case DrawFill:
1190         brush = p->brush();
1191         fillPath = path;
1192         break;
1193     }
1194 
1195     brush.setTransform(state.transform);
1196 
1197     p->save();
1198     p->setPen(Qt::NoPen);
1199     p->setBrush(brush);
1200 
1201     if (needsShadow()) {
1202         drawPathWithShadow(p, fillPath, op);
1203     } else {
1204         const QPainterPath repeatClip = clipForPatternRepeat(p, op);
1205         if (!repeatClip.isEmpty()) {
1206             p->setClipPath(repeatClip, Qt::IntersectClip);
1207         }
1208 
1209         p->drawPath(fillPath);
1210     }
1211     p->restore();
1212 }
1213 
drawPathWithShadow(QPainter * p,const QPainterPath & path,PathPaintOp op,PaintFlags flags) const1214 void CanvasContext2DImpl::drawPathWithShadow(QPainter *p, const QPainterPath &path, PathPaintOp op, PaintFlags flags) const
1215 {
1216     const PaintState &state = activeState();
1217     float radius = shadowBlur();
1218 
1219     // This seems to produce a shadow that's a fairly close approximation
1220     // to the shadows rendered by CoreGraphics.
1221     if (radius > 7) {
1222         radius = qMin(7 + std::pow(float(radius - 7.0), float(.7)), float(127.0));
1223     }
1224 
1225     const qreal offset = radius * 2;
1226     const QPainterPath repeatClip = (flags & NotUsingCanvasPattern) ?
1227                                     QPainterPath() : clipForPatternRepeat(p, op);
1228 
1229     QRect shapeBounds;
1230     if (!repeatClip.isEmpty()) {
1231         shapeBounds = path.intersected(repeatClip).controlPointRect().toAlignedRect();
1232     } else {
1233         shapeBounds = path.controlPointRect().toAlignedRect();
1234     }
1235 
1236     QRect clipRect;
1237     if (state.clipping) {
1238         clipRect = state.clipPath.controlPointRect().toAlignedRect();
1239         clipRect &= QRect(QPoint(), canvasImage->size());
1240     } else {
1241         clipRect = QRect(QPoint(), canvasImage->size());
1242     }
1243 
1244     const QRect shadowRect = shapeBounds.translated(shadowOffsetX(), shadowOffsetY())
1245                              .adjusted(-offset, -offset, offset, offset) &
1246                              clipRect.adjusted(-offset, -offset, offset, offset);
1247 
1248     const QRect shapeRect = QRect(shapeBounds & clipRect) |
1249                             (shadowRect.translated(-shadowOffsetX(), -shadowOffsetY()) & shapeBounds);
1250 
1251     if (!shapeRect.isValid()) {
1252         return;
1253     }
1254 
1255     QPainter painter;
1256 
1257     // Create the image for the original shape
1258     QImage shape(shapeRect.size(), QImage::Format_ARGB32_Premultiplied);
1259     shape.fill(0);
1260 
1261     // Draw the shape
1262     painter.begin(&shape);
1263     painter.setRenderHints(p->renderHints());
1264     painter.setBrushOrigin(p->brushOrigin());
1265     painter.setBrush(p->brush());
1266     painter.setPen(Qt::NoPen);
1267     painter.translate(-shapeRect.x(), -shapeRect.y());
1268     if (!repeatClip.isEmpty()) {
1269         painter.setClipPath(repeatClip);
1270     }
1271     painter.drawPath(path);
1272     painter.end();
1273 
1274     // Create the shadow image and draw the original image on it
1275     if (shadowRect.isValid()) {
1276         QImage shadow(shadowRect.size(), QImage::Format_ARGB32_Premultiplied);
1277         shadow.fill(0);
1278 
1279         painter.begin(&shadow);
1280         painter.setCompositionMode(QPainter::CompositionMode_Source);
1281         painter.translate(-shadowRect.x(), -shadowRect.y());
1282         painter.drawImage(shapeRect.x() + shadowOffsetX(),
1283                           shapeRect.y() + shadowOffsetY(), shape);
1284         painter.end();
1285 
1286         // Blur the alpha channel
1287         ImageFilter::shadowBlur(shadow, radius, state.shadowColor);
1288 
1289         // Draw the shadow on the canvas
1290         p->drawImage(shadowRect.topLeft(), shadow);
1291     }
1292 
1293     // Composite the original image over the shadow.
1294     p->drawImage(shapeRect.topLeft(), shape);
1295 }
1296 
fill()1297 void CanvasContext2DImpl::fill()
1298 {
1299     QPainter *p = acquirePainter();
1300     drawPath(p, path, DrawFill);
1301 }
1302 
stroke()1303 void CanvasContext2DImpl::stroke()
1304 {
1305     QPainter *p = acquirePainter();
1306     drawPath(p, path, DrawStroke);
1307 }
1308 
clip()1309 void CanvasContext2DImpl::clip()
1310 {
1311     PaintState &state = activeState();
1312     QPainterPath pathCopy = path;
1313     pathCopy.closeSubpath();
1314 
1315     if (state.clipping) {
1316         state.clipPath = state.clipPath.intersected(pathCopy);
1317     } else {
1318         state.clipPath = pathCopy;
1319     }
1320 
1321     state.clipPath.setFillRule(Qt::WindingFill);
1322     state.clipping = true;
1323     dirty |= DrtClip;
1324 }
1325 
isPointInPath(float x,float y) const1326 bool CanvasContext2DImpl::isPointInPath(float x, float y) const
1327 {
1328     return path.contains(QPointF(x, y));
1329 }
1330 
arcTo(float x1,float y1,float x2,float y2,float radius,int & exceptionCode)1331 void CanvasContext2DImpl::arcTo(float x1, float y1, float x2, float y2, float radius, int &exceptionCode)
1332 {
1333     exceptionCode = 0;
1334 
1335     if (radius <= 0) {
1336         exceptionCode = DOMException::INDEX_SIZE_ERR;
1337         return;
1338     }
1339 
1340     if (isPathEmpty()) {
1341         moveTo(x1, y1);
1342     }
1343     emptyPath = false;
1344 
1345     QLineF line1(QPointF(x1, y1), mapToUser(path.currentPosition()));
1346     QLineF line2(QPointF(x1, y1), QPointF(x2, y2));
1347 
1348     // If the first line is a point, we'll do nothing.
1349     if (line1.p1() == line1.p2()) {
1350         return;
1351     }
1352 
1353     // If the second line is a point, we'll add a line segment to (x1, y1).
1354     if (line2.p1() == line2.p2()) {
1355         path.lineTo(mapToDevice(x1, y1));
1356         return;
1357     }
1358 
1359     float angle1 = std::atan2(line1.dy(), line1.dx());
1360     float angle2 = std::atan2(line2.dy(), line2.dx());
1361 
1362     // The smallest angle between the lines
1363     float theta = angle2 - angle1;
1364     if (theta < -M_PI) {
1365         theta = (2 * M_PI + theta);
1366     } else if (theta > M_PI) {
1367         theta = -(2 * M_PI - theta);
1368     }
1369 
1370     // If the angle between the lines is 180 degrees, the span of the arc becomes
1371     // zero, causing the tangent points to converge to the same point at (x1, y1).
1372     if (qFuzzyCompare(qAbs(theta), float(M_PI))) {
1373         path.lineTo(mapToDevice(x1, y1));
1374         return;
1375     }
1376 
1377     // The length of the hypotenuse of the right triangle formed by the points
1378     // (x1, y1), the center point of the circle, and either of the two tangent points.
1379     float h = radius / std::sin(qAbs(theta / 2.0));
1380 
1381     // The distance from (x1, y1) to the tangent points on line1 and line2.
1382     float tDist = std::cos(theta / 2.0) * h;
1383 
1384     // As theta approaches 0, the distance to the two tangent points approach infinity.
1385     // If we exceeded the data type limit, draw a long line toward the first tangent point.
1386     // This matches CoreGraphics and Postscript behavior.
1387     if (KJS::isInf(h) || KJS::isInf(tDist)) {
1388         QPointF point(line1.p2().x() + std::cos(angle1) * 1e10,
1389                       line1.p2().y() + std::sin(angle1) * 1e10);
1390         path.lineTo(mapToDevice(point));
1391         return;
1392     }
1393 
1394     // The center point of the circle
1395     float angle = angle1 + theta / 2.0;
1396     QPointF centerPoint(x1 + std::cos(angle) * h, y1 + std::sin(angle) * h);
1397 
1398     // Note that we don't check if the lines are long enough for the circle to actually
1399     // tangent them; like CoreGraphics and Postscript, we treat the points as points on
1400     // two infinitely long lines that intersect one another at (x1, y1).
1401     float startAngle = theta < 0 ? angle1 + M_PI_2 : angle1 - M_PI_2;
1402     float endAngle   = theta < 0 ? angle2 - M_PI_2 : angle2 + M_PI_2;
1403     bool counterClockWise = theta > 0;
1404 
1405     int dummy; // Exception code from arc()
1406     arc(centerPoint.x(), centerPoint.y(), radius, startAngle, endAngle, counterClockWise, dummy);
1407 }
1408 
arc(float x,float y,float radius,float startAngle,float endAngle,bool counterClockWise,int & exceptionCode)1409 void CanvasContext2DImpl::arc(float x, float y, float radius, float startAngle, float endAngle,
1410                               bool counterClockWise, int &exceptionCode)
1411 {
1412     exceptionCode = 0;
1413 
1414     if (radius <= 0) {
1415         exceptionCode = DOMException::INDEX_SIZE_ERR;
1416         return;
1417     }
1418 
1419     const QRectF rect(x - radius, y - radius, radius * 2, radius * 2);
1420     float sweepLength = -degrees(endAngle - startAngle);
1421     startAngle = -degrees(startAngle);
1422 
1423     if (counterClockWise && (sweepLength < 0 || sweepLength > 360)) {
1424         sweepLength = 360 + std::fmod(sweepLength, float(360.0));
1425         if (qFuzzyCompare(sweepLength + 1, 1)) {
1426             sweepLength = 360;
1427         }
1428     } else if (!counterClockWise && (sweepLength > 0 || sweepLength < -360)) {
1429         sweepLength = -(360 - std::fmod(sweepLength, float(360.0)));
1430         if (qFuzzyCompare(sweepLength + 1, 1)) {
1431             sweepLength = 360;
1432         }
1433     }
1434 
1435     QPainterPath arcPath;
1436     arcPath.arcMoveTo(rect, startAngle);
1437     arcPath.arcTo(rect, startAngle, sweepLength);
1438 
1439     // When drawing the arc, Safari will loop around the circle several times if
1440     // the sweep length is greater than 360 degrees, leaving the current position
1441     // in the path at endAngle. QPainterPath::arcTo() will stop when it reaches
1442     // 360 degrees, thus leaving the current position at that point. To match
1443     // Safari behavior, we call QPainterPath::arcTo() twice in this case, to make
1444     // the arc continue to the intended end point. Adding a MoveTo element will
1445     // not suffice, since this will not produce correct results if additional
1446     // elements are added to the path before it is stroked or filled.
1447     if (sweepLength > 360.0 || sweepLength < -360.0) {
1448         if (sweepLength < 0) {
1449             sweepLength += 360.0;
1450             startAngle -= 360.0;
1451         } else {
1452             sweepLength -= 360.0;
1453             startAngle += 360.0;
1454         }
1455 
1456         arcPath.arcTo(rect, startAngle, sweepLength);
1457     }
1458 
1459     // Add the transformed arc to the path
1460     if (isPathEmpty()) {
1461         path.addPath(arcPath * activeState().transform);
1462     } else {
1463         if (path.elementAt(path.elementCount() - 1).type != QPainterPath::MoveToElement) {
1464             path.connectPath(arcPath * activeState().transform);
1465         } else {
1466             // ### This is needed to work around buggy behavior in QPainterPath::connectPath()
1467             //     when the last element in the path being added to is a MoveToElement.
1468             arcPath = arcPath * activeState().transform;
1469             path.lineTo(arcPath.elementAt(0));
1470 
1471             if (arcPath.elementCount() > 1)
1472                 for (int i = 1; i < arcPath.elementCount(); i += 3)
1473                     path.cubicTo(arcPath.elementAt(i), arcPath.elementAt(i + 1),
1474                                  arcPath.elementAt(i + 2));
1475         }
1476     }
1477     emptyPath = false;
1478 }
1479 
drawImage(QPainter * p,const QRectF & dstRect,const QImage & image,const QRectF & srcRect) const1480 void CanvasContext2DImpl::drawImage(QPainter *p, const QRectF &dstRect, const QImage &image, const QRectF &srcRect) const
1481 {
1482     if (activeState().infinityTransform) {
1483         return;
1484     }
1485 
1486     if (!needsShadow()) {
1487         p->setTransform(activeState().transform);
1488         p->drawImage(dstRect, image, srcRect);
1489         p->resetTransform();
1490         return;
1491     }
1492 
1493     float xscale = dstRect.width() / srcRect.width();
1494     float yscale = dstRect.height() / srcRect.height();
1495     float dx = dstRect.x() - srcRect.x() * xscale;
1496     float dy = dstRect.y() - srcRect.y() * yscale;
1497 
1498     QTransform transform;
1499     transform.translate(dx, dy);
1500     transform.scale(xscale, yscale);
1501 
1502     QBrush brush(image);
1503     brush.setTransform(transform * activeState().transform);
1504 
1505     QPainterPath path;
1506     path.addRect(dstRect);
1507     path = path * activeState().transform;
1508 
1509     p->save();
1510     p->setBrush(brush);
1511     p->setPen(Qt::NoPen);
1512     drawPathWithShadow(p, path, DrawFill, NotUsingCanvasPattern);
1513     p->restore();
1514 }
1515 
1516 // Image stuff
drawImage(ElementImpl * image,float dx,float dy,int & exceptionCode)1517 void CanvasContext2DImpl::drawImage(ElementImpl *image, float dx, float dy, int &exceptionCode)
1518 {
1519     exceptionCode = 0;
1520     bool unsafe;
1521     QImage img = extractImage(image, exceptionCode, unsafe);
1522     if (unsafe) {
1523         canvas()->markUnsafe();
1524     }
1525     if (exceptionCode) {
1526         return;
1527     }
1528 
1529     QPainter *p = acquirePainter();
1530     drawImage(p, QRectF(dx, dy, img.width(), img.height()), img, img.rect());
1531 }
1532 
drawImage(ElementImpl * image,float dx,float dy,float dw,float dh,int & exceptionCode)1533 void CanvasContext2DImpl::drawImage(ElementImpl *image, float dx, float dy, float dw, float dh,
1534                                     int &exceptionCode)
1535 {
1536     //### do we need DoS protection here?
1537     exceptionCode = 0;
1538     bool unsafe;
1539     QImage img = extractImage(image, exceptionCode, unsafe);
1540     if (unsafe) {
1541         canvas()->markUnsafe();
1542     }
1543     if (exceptionCode) {
1544         return;
1545     }
1546 
1547     if (dw < 0 || dh < 0) {
1548         exceptionCode = DOMException::INDEX_SIZE_ERR;
1549         return;
1550     }
1551 
1552     if (qFuzzyCompare(dw + 1, 1) || qFuzzyCompare(dh + 1, 1)) {
1553         return;
1554     }
1555 
1556     QPainter *p = acquirePainter();
1557     drawImage(p, QRectF(dx, dy, dw, dh), img, img.rect());
1558 }
1559 
drawImage(ElementImpl * image,float sx,float sy,float sw,float sh,float dx,float dy,float dw,float dh,int & exceptionCode)1560 void CanvasContext2DImpl::drawImage(ElementImpl *image,
1561                                     float sx, float sy, float sw, float sh,
1562                                     float dx, float dy, float dw, float dh,
1563                                     int &exceptionCode)
1564 {
1565     //### do we need DoS protection here?
1566     exceptionCode = 0;
1567     bool unsafe;
1568     QImage img = extractImage(image, exceptionCode, unsafe);
1569     if (unsafe) {
1570         canvas()->markUnsafe();
1571     }
1572     if (exceptionCode) {
1573         return;
1574     }
1575 
1576     if (sx < 0 || sy < 0 || sw < 0 || sh < 0 || dw < 0 || dh < 0 ||
1577             sx + sw > img.width() || sy + sh > img.height()) {
1578         exceptionCode = DOMException::INDEX_SIZE_ERR;
1579         return;
1580     }
1581 
1582     if (qFuzzyCompare(sw + 1, 1) || qFuzzyCompare(sh + 1, 1) ||
1583             qFuzzyCompare(dw + 1, 1) || qFuzzyCompare(dh + 1, 1)) {
1584         return;
1585     }
1586 
1587     QPainter *p = acquirePainter();
1588     drawImage(p, QRectF(dx, dy, dw, dh), img, QRectF(sx, sy, sw, sh));
1589 }
1590 
1591 // Pixel stuff.
getImageData(float sx,float sy,float sw,float sh,int & exceptionCode)1592 CanvasImageDataImpl *CanvasContext2DImpl::getImageData(float sx, float sy, float sw, float sh, int &exceptionCode)
1593 {
1594     int w = qRound(sw);
1595     int h = qRound(sh);
1596 
1597     if (canvas()->isUnsafe()) {
1598         exceptionCode = DOMException::INVALID_ACCESS_ERR;
1599         return nullptr;
1600     }
1601 
1602     if (w <= 0 || h <= 0) {
1603         exceptionCode = DOMException::INDEX_SIZE_ERR;
1604         return nullptr;
1605     }
1606 
1607     if (!khtmlImLoad::ImageManager::isAcceptableSize(unsigned(w), unsigned(h))) {
1608         exceptionCode = DOMException::INDEX_SIZE_ERR;
1609         return nullptr;
1610     }
1611 
1612     int x = qRound(sx);
1613     int y = qRound(sy);
1614 
1615     CanvasImageDataImpl *id = new CanvasImageDataImpl(w, h);
1616     id->data.fill(Qt::transparent);
1617 
1618     // Clip the source rect again the viewport.
1619 
1620     QRect srcRect = QRect(x, y, w, h);
1621     QRect clpRect = srcRect & QRect(0, 0, canvasElement->width(), canvasElement->height());
1622     if (!clpRect.isEmpty()) {
1623         QPainter p(&id->data);
1624         p.setCompositionMode(QPainter::CompositionMode_Source);
1625 
1626         // Flush our data..
1627         syncBackBuffer();
1628 
1629         // Copy it over..
1630         QImage *backBuffer = canvasImage->qimage();
1631         p.drawImage(clpRect.topLeft() - srcRect.topLeft(), *backBuffer, clpRect);
1632         p.end();
1633     }
1634 
1635     return id;
1636 }
1637 
putImageData(CanvasImageDataImpl * id,float dx,float dy,int & exceptionCode)1638 void CanvasContext2DImpl::putImageData(CanvasImageDataImpl *id, float dx, float dy, int &exceptionCode)
1639 {
1640     if (!id) {
1641         exceptionCode = DOMException::TYPE_MISMATCH_ERR;
1642         return;
1643     }
1644 
1645     // Flush any previous changes
1646     syncBackBuffer();
1647 
1648     // We use our own painter here since we should not be affected by clipping, etc.
1649     // Hence we need to mark ourselves dirty, too
1650     needRendererUpdate();
1651     QPainter p(canvasImage->qimage());
1652     int x = qRound(dx);
1653     int y = qRound(dy);
1654     p.setCompositionMode(QPainter::CompositionMode_Source);
1655     p.drawImage(x, y, id->data);
1656 }
1657 
createImageData(float sw,float sh,int & exceptionCode)1658 CanvasImageDataImpl *CanvasContext2DImpl::createImageData(float sw, float sh, int &exceptionCode)
1659 {
1660     int w = qRound(qAbs(sw));
1661     int h = qRound(qAbs(sh));
1662 
1663     if (w == 0 || h == 0) {
1664         exceptionCode = DOMException::INDEX_SIZE_ERR;
1665         return nullptr;
1666     }
1667 
1668     CanvasImageDataImpl *id = new CanvasImageDataImpl(w, h);
1669     id->data.fill(Qt::transparent);
1670 
1671     return id;
1672 }
1673 
1674