File indexing completed on 2024-05-05 16:10:09

0001 /*
0002  * Copyright (C) 2004 Apple Computer, Inc.  All rights reserved.
0003  * Copyright (C) 2005 Zack Rusin <zack@kde.org>
0004  * Copyright (C) 2007, 2008 Maksim Orlovich <maksim@kde.org>
0005  * Copyright (C) 2007, 2008 Fredrik Höglund <fredrik@kde.org>
0006  *
0007  * This library is free software; you can redistribute it and/or
0008  * modify it under the terms of the GNU Lesser General Public
0009  * License as published by the Free Software Foundation; either
0010  * version 2 of the License, or (at your option) any later version.
0011  *
0012  * This library is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  * Lesser General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU Lesser General Public
0018  * License along with this library; if not, write to the Free Software
0019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0020  *
0021  * Portions of this code are (c) by Apple Computer, Inc. and were licensed
0022  * under the following terms:
0023  *
0024  * Redistribution and use in source and binary forms, with or without
0025  * modification, are permitted provided that the following conditions
0026  * are met:
0027  * 1. Redistributions of source code must retain the above copyright
0028  *    notice, this list of conditions and the following disclaimer.
0029  * 2. Redistributions in binary form must reproduce the above copyright
0030  *    notice, this list of conditions and the following disclaimer in the
0031  *    documentation and/or other materials provided with the distribution.
0032  *
0033  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
0034  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0035  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
0036  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
0037  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0038  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0039  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
0040  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
0041  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0042  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
0043  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0044  */
0045 
0046 #include "html_canvasimpl.h"
0047 #include "html_documentimpl.h"
0048 
0049 #include <khtmlview.h>
0050 #include <khtml_part.h>
0051 
0052 #include <dom/dom_exception.h>
0053 #include <rendering/render_canvasimage.h>
0054 #include <rendering/render_flow.h>
0055 #include <css/cssstyleselector.h>
0056 #include <css/cssproperties.h>
0057 #include <css/cssparser.h>
0058 #include <css/cssvalues.h>
0059 #include <css/csshelper.h>
0060 #include <xml/dom2_eventsimpl.h>
0061 #include <html/html_imageimpl.h>
0062 #include <misc/helper.h> // for colorFromCSSValue
0063 #include <misc/translator.h>
0064 #include <misc/imagefilter.h>
0065 #include <imload/canvasimage.h>
0066 #include <imload/imagemanager.h>
0067 #include <kjs/global.h>
0068 #include <kjs/operations.h> //uglyyy: needs for inf/NaN tests
0069 
0070 #include <QtAlgorithms>
0071 #include <QCharRef>
0072 #include <QPoint>
0073 #include <QLocale>
0074 #include <QImage>
0075 #include "khtml_debug.h"
0076 #include <cmath>
0077 #include <limits>
0078 
0079 using namespace DOM;
0080 using namespace khtml;
0081 using namespace std;
0082 
0083 // on Windows fmod might be a macro so std::fmod will not work
0084 #ifdef fmod
0085 #undef fmod
0086 #endif
0087 
0088 // -------------------------------------------------------------------------
0089 
0090 HTMLCanvasElementImpl::HTMLCanvasElementImpl(DocumentImpl *doc)
0091     : HTMLElementImpl(doc)
0092 {
0093     w = 300;
0094     h = 150;
0095     unsafe = false;
0096 }
0097 
0098 HTMLCanvasElementImpl::~HTMLCanvasElementImpl()
0099 {
0100     if (context) {
0101         context->canvasElement = nullptr;
0102     }
0103 }
0104 
0105 void HTMLCanvasElementImpl::parseAttribute(AttributeImpl *attr)
0106 {
0107     bool ok = false;
0108     int  val;
0109     switch (attr->id()) {
0110     // ### TODO: making them reflect w/h -- how?
0111     case ATTR_WIDTH:
0112         val = attr->val() ? attr->val()->toInt(&ok) : -1;
0113         if (!ok || val <= 0) {
0114             w = 300;
0115         } else {
0116             w = val;
0117         }
0118 
0119         if (context) {
0120             context->resetContext(w, h);
0121         }
0122         setChanged();
0123         break;
0124     case ATTR_HEIGHT:
0125         val = attr->val() ? attr->val()->toInt(&ok) : -1;
0126         if (!ok || val <= 0) {
0127             h = 150;
0128         } else {
0129             h = val;
0130         }
0131 
0132         if (context) {
0133             context->resetContext(w, h);
0134         }
0135         setChanged();
0136         break;
0137     default:
0138         HTMLElementImpl::parseAttribute(attr);
0139     }
0140 }
0141 
0142 NodeImpl::Id HTMLCanvasElementImpl::id() const
0143 {
0144     return ID_CANVAS;
0145 }
0146 
0147 void HTMLCanvasElementImpl::attach()
0148 {
0149     assert(!attached());
0150     assert(!m_render);
0151     assert(parentNode());
0152 
0153     RenderStyle *_style = document()->styleSelector()->styleForElement(this);
0154     _style->ref();
0155     if (parentNode()->renderer() && parentNode()->renderer()->childAllowed() &&
0156             _style->display() != NONE) {
0157         m_render = new(document()->renderArena()) RenderCanvasImage(this);
0158         m_render->setStyle(_style);
0159         parentNode()->renderer()->addChild(m_render, nextRenderer());
0160     }
0161     _style->deref();
0162 
0163     NodeBaseImpl::attach();
0164     if (m_render) {
0165         m_render->updateFromElement();
0166     }
0167 }
0168 
0169 CanvasContext2DImpl *HTMLCanvasElementImpl::getContext2D()
0170 {
0171     if (!context) {
0172         context = new CanvasContext2DImpl(this, w, h);
0173     }
0174     return context.get();
0175 }
0176 
0177 khtmlImLoad::CanvasImage *HTMLCanvasElementImpl::getCanvasImage()
0178 {
0179     return getContext2D()->canvasImage;
0180 }
0181 
0182 bool HTMLCanvasElementImpl::isUnsafe() const
0183 {
0184     return unsafe;
0185 }
0186 
0187 void HTMLCanvasElementImpl::markUnsafe()
0188 {
0189     unsafe = true;
0190 }
0191 
0192 QString HTMLCanvasElementImpl::toDataURL(int &exceptionCode)
0193 {
0194     if (isUnsafe()) {
0195         exceptionCode = DOMException::INVALID_ACCESS_ERR;
0196         return "";
0197     }
0198 
0199     khtmlImLoad::CanvasImage *ci = getCanvasImage();
0200     context->syncBackBuffer();
0201 
0202     QByteArray pngBytes;
0203     QBuffer    pngSink(&pngBytes);
0204     pngSink.open(QIODevice::WriteOnly);
0205     ci->qimage()->save(&pngSink, "PNG");
0206     pngSink.close();
0207 
0208     return QString::fromLatin1("data:image/png;base64,") + pngBytes.toBase64();
0209 }
0210 
0211 // -------------------------------------------------------------------------
0212 CanvasContext2DImpl::CanvasContext2DImpl(HTMLCanvasElementImpl *element, int width, int height):
0213     canvasElement(element), canvasImage(nullptr)
0214 {
0215     resetContext(width, height);
0216 }
0217 
0218 CanvasContext2DImpl::~CanvasContext2DImpl()
0219 {
0220     if (workPainter.isActive()) {
0221         workPainter.end();    // Make sure to stop it before blowing the image away!
0222     }
0223     delete canvasImage;
0224 }
0225 
0226 // Basic infrastructure..
0227 void CanvasContext2DImpl::resetContext(int width, int height)
0228 {
0229     // ### FIXME FIXME: use khtmlImLoad's limit policy
0230     // for physical canvas and transform painter to match logical resolution
0231     if (workPainter.isActive()) {
0232         workPainter.end();
0233     }
0234 
0235     if (canvasImage) {
0236         canvasImage->resizeImage(width, height);
0237     } else {
0238         canvasImage = new khtmlImLoad::CanvasImage(width, height);
0239     }
0240     canvasImage->qimage()->fill(0x00000000); // transparent black is the initial state
0241 
0242     stateStack.clear();
0243 
0244     PaintState defaultState;
0245     beginPath();
0246     defaultState.infinityTransform = false;
0247     defaultState.clipPath = QPainterPath();
0248     defaultState.clipPath.setFillRule(Qt::WindingFill);
0249     defaultState.clipping = false;
0250 
0251     defaultState.globalAlpha = 1.0f;
0252     defaultState.globalCompositeOperation = QPainter::CompositionMode_SourceOver;
0253 
0254     defaultState.strokeStyle = new CanvasColorImpl(QColor(Qt::black));
0255     defaultState.fillStyle   = new CanvasColorImpl(QColor(Qt::black));
0256 
0257     defaultState.lineWidth  = 1.0f;
0258     defaultState.lineCap    = Qt::FlatCap;
0259     defaultState.lineJoin   = Qt::SvgMiterJoin;
0260     defaultState.miterLimit = 10.0f;
0261 
0262     defaultState.shadowOffsetX = 0.0f;
0263     defaultState.shadowOffsetY = 0.0f;
0264     defaultState.shadowBlur    = 0.0f;
0265     defaultState.shadowColor   = QColor(0, 0, 0, 0); // Transparent black
0266 
0267     stateStack.push(defaultState);
0268 
0269     dirty = DrtAll;
0270     needRendererUpdate();
0271     emptyPath = true;
0272 }
0273 
0274 void CanvasContext2DImpl::save()
0275 {
0276     stateStack.push(stateStack.top());
0277 }
0278 
0279 void CanvasContext2DImpl::restore()
0280 {
0281     if (stateStack.size() <= 1) {
0282         return;
0283     }
0284 
0285     stateStack.pop();
0286     dirty = DrtAll;
0287 }
0288 
0289 QPainter *CanvasContext2DImpl::acquirePainter()
0290 {
0291     if (!workPainter.isActive()) {
0292         workPainter.begin(canvasImage->qimage());
0293         workPainter.setRenderHint(QPainter::Antialiasing);
0294         workPainter.setRenderHint(QPainter::SmoothPixmapTransform);
0295         dirty = DrtAll;
0296     }
0297 
0298     PaintState &state = activeState();
0299 
0300     if (dirty & DrtClip) {
0301         if (state.clipping) {
0302             workPainter.setClipPath(state.clipPath);
0303         } else {
0304             workPainter.setClipping(false);
0305         }
0306     }
0307 
0308     if (dirty & DrtAlpha) {
0309         workPainter.setOpacity(state.globalAlpha);
0310     }
0311     if (dirty & DrtCompOp) {
0312         workPainter.setCompositionMode(state.globalCompositeOperation);
0313     }
0314     if (dirty & DrtStroke) {
0315         QPen pen;
0316         pen.setWidth(state.lineWidth);
0317         pen.setCapStyle(state.lineCap);
0318         pen.setJoinStyle(state.lineJoin);
0319         pen.setMiterLimit(state.miterLimit);
0320 
0321         CanvasStyleBaseImpl *style = state.strokeStyle.get();
0322         if (style->type() == CanvasStyleBaseImpl::Color) {
0323             pen.setColor(static_cast<CanvasColorImpl *>(style)->color);
0324         } else {
0325             pen.setBrush(style->toBrush());
0326         }
0327         workPainter.setPen(pen); // ### should I even do this?
0328         // I have a feeling I am mixing up path and
0329         // non-path ops
0330     }
0331     if (dirty & DrtFill) {
0332         workPainter.setBrush(state.fillStyle->toBrush());
0333     }
0334 
0335     dirty = 0;
0336 
0337     needRendererUpdate();
0338     return &workPainter;
0339 }
0340 
0341 QImage CanvasContext2DImpl::extractImage(ElementImpl *el, int &exceptionCode, bool &unsafeOut) const
0342 {
0343     QImage pic;
0344 
0345     exceptionCode = 0;
0346 
0347     unsafeOut = false;
0348     if (el->id() == ID_CANVAS) {
0349         CanvasContext2DImpl *other = static_cast<HTMLCanvasElementImpl *>(el)->getContext2D();
0350         other->syncBackBuffer();
0351         pic = *other->canvasImage->qimage();
0352 
0353         if (static_cast<HTMLCanvasElementImpl *>(el)->isUnsafe()) {
0354             unsafeOut = true;
0355         }
0356     } else if (el->id() == ID_IMG) {
0357         HTMLImageElementImpl *img = static_cast<HTMLImageElementImpl *>(el);
0358         if (img->complete()) {
0359             pic = img->currentImage();
0360         } else {
0361             exceptionCode = DOMException::INVALID_STATE_ERR;
0362         }
0363 
0364         if (img->isUnsafe()) {
0365             unsafeOut = true;
0366         }
0367     } else {
0368         exceptionCode = DOMException::TYPE_MISMATCH_ERR;
0369     }
0370 
0371     return pic;
0372 }
0373 
0374 void CanvasContext2DImpl::needRendererUpdate()
0375 {
0376     needsCommit = true;
0377     if (canvasElement) {
0378         canvasElement->setChanged();
0379     }
0380 }
0381 
0382 void CanvasContext2DImpl::syncBackBuffer()
0383 {
0384     if (workPainter.isActive()) {
0385         workPainter.end();
0386     }
0387 }
0388 
0389 void CanvasContext2DImpl::commit()
0390 {
0391     syncBackBuffer();
0392 
0393     // Flush caches if we have changes.
0394     if (needsCommit) {
0395         canvasImage->contentUpdated();
0396         needsCommit = false;
0397     }
0398 }
0399 
0400 HTMLCanvasElementImpl *CanvasContext2DImpl::canvas() const
0401 {
0402     return canvasElement;
0403 }
0404 
0405 // Transformation ops
0406 //
0407 
0408 static inline float degrees(float radians)
0409 {
0410     return radians * 180.0 / M_PI;
0411 }
0412 
0413 static inline bool isInfArg(float x)
0414 {
0415     return KJS::isInf(x) || KJS::isNaN(x);
0416 }
0417 
0418 void CanvasContext2DImpl::scale(float x, float y)
0419 {
0420     dirty |= DrtTransform;
0421 
0422     bool &infinityTransform = activeState().infinityTransform;
0423     infinityTransform |= isInfArg(x) | isInfArg(y);
0424     if (infinityTransform) {
0425         return;
0426     }
0427 
0428     activeState().transform.scale(x, y);
0429 }
0430 
0431 void CanvasContext2DImpl::rotate(float angle)
0432 {
0433     dirty |= DrtTransform;
0434 
0435     bool &infinityTransform = activeState().infinityTransform;
0436     infinityTransform |= isInfArg(angle);
0437     if (infinityTransform) {
0438         return;
0439     }
0440 
0441     activeState().transform.rotateRadians(angle);
0442 }
0443 
0444 void CanvasContext2DImpl::translate(float x, float y)
0445 {
0446     dirty |= DrtTransform;
0447 
0448     bool &infinityTransform = activeState().infinityTransform;
0449     infinityTransform |= isInfArg(x) | isInfArg(y);
0450     if (infinityTransform) {
0451         return;
0452     }
0453 
0454     activeState().transform.translate(x, y);
0455 }
0456 
0457 void CanvasContext2DImpl::transform(float m11, float m12, float m21, float m22, float dx, float dy)
0458 {
0459     dirty |= DrtTransform;
0460 
0461     bool &infinityTransform = activeState().infinityTransform;
0462     infinityTransform |= isInfArg(m11) | isInfArg(m12) | isInfArg(m21) | isInfArg(m22) |
0463                          isInfArg(dx)  | isInfArg(dy);
0464     if (infinityTransform) {
0465         return;
0466     }
0467 
0468     activeState().transform *= QTransform(m11, m12, 0.0f, m21, m22, 0.0f, dx, dy, 1.0f);
0469 }
0470 
0471 void CanvasContext2DImpl::setTransform(float m11, float m12, float m21, float m22, float dx, float dy)
0472 {
0473     activeState().transform.reset();
0474     activeState().infinityTransform = false; // As cleared the matrix..
0475     transform(m11, m12, m21, m22, dx, dy);
0476 }
0477 
0478 // Composition state setting
0479 //
0480 
0481 float CanvasContext2DImpl::globalAlpha() const
0482 {
0483     return activeState().globalAlpha;
0484 }
0485 
0486 void CanvasContext2DImpl::setGlobalAlpha(float a)
0487 {
0488     if (a < 0.0f || a > 1.0f) {
0489         return;
0490     }
0491 
0492     activeState().globalAlpha = a;
0493     dirty |= DrtAlpha;
0494 }
0495 
0496 static const IDTranslator<QString, QPainter::CompositionMode, const char *>::Info compModeTranslatorTable[] = {
0497     {"source-over", QPainter::CompositionMode_SourceOver},
0498     {"source-out",  QPainter::CompositionMode_SourceOut},
0499     {"source-in",   QPainter::CompositionMode_SourceIn},
0500     {"source-atop", QPainter::CompositionMode_SourceAtop},
0501     {"destination-atop", QPainter::CompositionMode_DestinationAtop},
0502     {"destination-in",   QPainter::CompositionMode_DestinationIn},
0503     {"destination-out",  QPainter::CompositionMode_DestinationOut},
0504     {"destination-over", QPainter::CompositionMode_DestinationOver},
0505     {"lighter", QPainter::CompositionMode_Plus},
0506     {"copy",    QPainter::CompositionMode_Source},
0507     {"xor",     QPainter::CompositionMode_Xor},
0508     {nullptr, (QPainter::CompositionMode)0}
0509 };
0510 
0511 MAKE_TRANSLATOR(compModeTranslator, QString, QPainter::CompositionMode, const char *, compModeTranslatorTable)
0512 
0513 DOM::DOMString CanvasContext2DImpl::globalCompositeOperation() const
0514 {
0515     return compModeTranslator()->toLeft(activeState().globalCompositeOperation);
0516 }
0517 
0518 void CanvasContext2DImpl::setGlobalCompositeOperation(const DOM::DOMString &op)
0519 {
0520     QString opStr = op.string();
0521     if (!compModeTranslator()->hasLeft(opStr)) {
0522         return;    // Ignore unknown
0523     }
0524     activeState().globalCompositeOperation = compModeTranslator()->toRight(opStr);
0525     dirty |= DrtCompOp;
0526 }
0527 
0528 // Colors and styles.
0529 //
0530 
0531 static QColor colorFromString(DOM::DOMString domStr)
0532 {
0533     // We make a temporary CSS decl. object to parse the color using the CSS parser.
0534     CSSStyleDeclarationImpl  tempStyle(nullptr);
0535     if (!tempStyle.setProperty(CSS_PROP_COLOR, domStr)) {
0536         return QColor();
0537     }
0538 
0539     CSSValueImpl *val = tempStyle.getPropertyCSSValue(CSS_PROP_COLOR);
0540     if (!val || val->cssValueType() != CSSValue::CSS_PRIMITIVE_VALUE) {
0541         return QColor();
0542     }
0543 
0544     CSSPrimitiveValueImpl *primVal = static_cast<CSSPrimitiveValueImpl *>(val);
0545 
0546     if (primVal->primitiveType() == CSSPrimitiveValue::CSS_IDENT) {
0547         return colorForCSSValue(primVal->getIdent());
0548     }
0549 
0550     if (primVal->primitiveType() != CSSPrimitiveValue::CSS_RGBCOLOR) {
0551         return QColor();
0552     }
0553     return QColor::fromRgba(primVal->getRGBColorValue());
0554 }
0555 
0556 static DOMString colorToString(const QColor &color)
0557 {
0558     QString str;
0559     if (color.alpha() == 255) {
0560         str.sprintf("#%02x%02x%02x", color.red(), color.green(), color.blue());
0561     } else {
0562         QString alphaColor = QString::number(color.alphaF());
0563         // Ensure we always have a decimal period
0564         if ((int)color.alphaF() == color.alphaF()) {
0565             alphaColor = QString::number((int)color.alphaF()) + ".0";
0566         }
0567 
0568         str.sprintf("rgba(%d, %d, %d, ", color.red(), color.green(), color.blue());
0569         str += alphaColor + ")";
0570     }
0571     return str;
0572 }
0573 
0574 //-------
0575 
0576 DOM::DOMString CanvasColorImpl::toString() const
0577 {
0578     return colorToString(color);
0579 }
0580 
0581 CanvasColorImpl *CanvasColorImpl::fromString(const DOM::DOMString &str)
0582 {
0583     QColor cl = colorFromString(str);
0584     if (!cl.isValid()) {
0585         return nullptr;
0586     }
0587     return new CanvasColorImpl(cl);
0588 }
0589 
0590 //-------
0591 
0592 CanvasGradientImpl::CanvasGradientImpl(QGradient *newGradient, float innerRadius, bool inverse)
0593     : gradient(newGradient), innerRadius(innerRadius), inverse(inverse)
0594 {}
0595 
0596 static qreal adjustPosition(qreal pos, const QGradientStops &stops)
0597 {
0598     QGradientStops::const_iterator itr = stops.constBegin();
0599     const qreal smallDiff = 0.00001;
0600     while (itr != stops.constEnd()) {
0601         const QGradientStop &stop = *itr;
0602         ++itr;
0603         bool atEnd = (itr != stops.constEnd());
0604         if (qFuzzyCompare(pos, stop.first)) {
0605             if (atEnd || !qFuzzyCompare(pos + smallDiff, (*itr).first)) {
0606                 return qMin(pos + smallDiff, qreal(1.0));
0607             }
0608         }
0609     }
0610     return pos;
0611 }
0612 
0613 void CanvasGradientImpl::addColorStop(float offset, const DOM::DOMString &color, int &exceptionCode)
0614 {
0615     // ### we may have to handle the "currentColor" KW here. ouch.
0616 
0617     exceptionCode = 0;
0618     if (isInfArg(offset)) {
0619         exceptionCode = DOMException::INDEX_SIZE_ERR;
0620         return;
0621     }
0622 
0623     //### fuzzy compare (also for alpha)
0624     if (offset < 0 || offset > 1) {
0625         exceptionCode = DOMException::INDEX_SIZE_ERR;
0626         return;
0627     }
0628 
0629     QColor qcolor = colorFromString(color);
0630     if (!qcolor.isValid()) {
0631         exceptionCode = DOMException::SYNTAX_ERR;
0632         return;
0633     }
0634 
0635     // Adjust the position of the stop to emulate an inner radius.
0636     // If the inner radius is larger than the outer, we'll reverse
0637     // the position of the stop.
0638     if (gradient->type() == QGradient::RadialGradient) {
0639         if (inverse) {
0640             offset = 1.0 - offset;
0641         }
0642 
0643         offset = innerRadius + offset * (1.0 - innerRadius);
0644     }
0645 
0646     //<canvas> says that gradient can have two stops at the same position
0647     //Qt doesn't handle that. We hack around that by creating a fake position
0648     //stop.
0649     offset = adjustPosition(offset, gradient->stops());
0650 
0651     gradient->setColorAt(offset, qcolor);
0652 }
0653 
0654 CanvasGradientImpl::~CanvasGradientImpl()
0655 {
0656     delete gradient;
0657 }
0658 
0659 QBrush CanvasGradientImpl::toBrush() const
0660 {
0661     return QBrush(*gradient);
0662 }
0663 
0664 //-------
0665 
0666 CanvasPatternImpl::CanvasPatternImpl(const QImage &inImg, bool unsafe, bool rx, bool ry):
0667     img(inImg), repeatX(rx), repeatY(ry), unsafe(unsafe)
0668 {}
0669 
0670 QBrush CanvasPatternImpl::toBrush() const
0671 {
0672     return QBrush(img);
0673 }
0674 
0675 QRectF CanvasPatternImpl::clipForRepeat(const QPointF &origin, const QRectF &fillBounds) const
0676 {
0677     if (repeatX && repeatY) {
0678         return QRectF();
0679     }
0680 
0681     if (!repeatX && !repeatY) {
0682         return QRectF(origin, img.size());
0683     }
0684 
0685     if (repeatX) {
0686         return QRectF(fillBounds.x(), origin.y(), fillBounds.width(), img.height());
0687     }
0688 
0689     // repeatY
0690     return QRectF(origin.x(), fillBounds.y(), img.width(), fillBounds.height());
0691 }
0692 
0693 //-------
0694 
0695 CanvasImageDataImpl::CanvasImageDataImpl(unsigned width, unsigned height) : data(width, height, QImage::Format_ARGB32)
0696 {}
0697 
0698 CanvasImageDataImpl::CanvasImageDataImpl(const QImage &_data): data(_data)
0699 {}
0700 
0701 CanvasImageDataImpl *CanvasImageDataImpl::clone() const
0702 {
0703     return new CanvasImageDataImpl(data);
0704 }
0705 
0706 unsigned CanvasImageDataImpl::width() const
0707 {
0708     return data.width();
0709 }
0710 
0711 unsigned CanvasImageDataImpl::height() const
0712 {
0713     return data.height();
0714 }
0715 
0716 #if 0
0717 static inline unsigned char unpremulComponent(unsigned original, unsigned alpha)
0718 {
0719     unsigned char val =  alpha ? (unsigned char)(original * 255 / alpha) : 0;
0720     return val;
0721 }
0722 #endif
0723 
0724 QColor CanvasImageDataImpl::pixel(unsigned pixelNum) const
0725 {
0726     int w = data.width();
0727     QRgb code = data.pixel(pixelNum % w, pixelNum / w);
0728     return code;
0729 }
0730 
0731 #if 0
0732 static inline unsigned char premulComponent(unsigned original, unsigned alpha)
0733 {
0734     unsigned product = original * alpha; // this is conceptually 255 * intended value.
0735     return (unsigned char)((product + product / 256 + 128) / 256);
0736 }
0737 #endif
0738 
0739 void CanvasImageDataImpl::setPixel(unsigned pixelNum, const QColor &val)
0740 {
0741     int w = data.width();
0742     data.setPixel(pixelNum % w, pixelNum / w, val.rgba());
0743 }
0744 
0745 void CanvasImageDataImpl::setComponent(unsigned pixelNum, int component,
0746                                        int value)
0747 {
0748     int w = data.width();
0749     int x = pixelNum % w;
0750     int y = pixelNum / w;
0751     // ### could avoid inherent QImage::detach() by a const cast
0752     QRgb *rgb = reinterpret_cast<QRgb *>(data.scanLine(y) + 4 * x);
0753 
0754     switch (component) {
0755     case 0: //Red
0756         *rgb = qRgba(value, qGreen(*rgb), qBlue(*rgb), qAlpha(*rgb));
0757         break;
0758     case 1: //Green
0759         *rgb = qRgba(qRed(*rgb), value, qBlue(*rgb), qAlpha(*rgb));
0760         break;
0761     case 2: //Blue
0762         *rgb = qRgba(qRed(*rgb), qGreen(*rgb), value, qAlpha(*rgb));
0763         break;
0764     case 3: //Alpha
0765     default:
0766         *rgb = qRgba(qRed(*rgb), qGreen(*rgb), qBlue(*rgb), value);
0767         break;
0768     }
0769 }
0770 
0771 //-------
0772 
0773 void CanvasContext2DImpl::setStrokeStyle(CanvasStyleBaseImpl *strokeStyle)
0774 {
0775     if (!strokeStyle) {
0776         return;
0777     }
0778     if (strokeStyle->isUnsafe()) {
0779         canvas()->markUnsafe();
0780     }
0781 
0782     activeState().strokeStyle = strokeStyle;
0783     dirty |= DrtStroke;
0784 }
0785 
0786 CanvasStyleBaseImpl *CanvasContext2DImpl::strokeStyle() const
0787 {
0788     return activeState().strokeStyle.get();
0789 }
0790 
0791 void CanvasContext2DImpl::setFillStyle(CanvasStyleBaseImpl *fillStyle)
0792 {
0793     if (!fillStyle) {
0794         return;
0795     }
0796     if (fillStyle->isUnsafe()) {
0797         canvas()->markUnsafe();
0798     }
0799 
0800     activeState().fillStyle = fillStyle;
0801     dirty |= DrtFill;
0802 }
0803 
0804 CanvasStyleBaseImpl *CanvasContext2DImpl::fillStyle() const
0805 {
0806     return activeState().fillStyle.get();
0807 }
0808 
0809 CanvasGradientImpl *CanvasContext2DImpl::createLinearGradient(float x0, float y0, float x1, float y1) const
0810 {
0811     QLinearGradient *grad = new QLinearGradient(x0, y0, x1, y1);
0812     return new CanvasGradientImpl(grad);
0813 }
0814 
0815 CanvasGradientImpl *CanvasContext2DImpl::createRadialGradient(float x0, float y0, float r0,
0816         float x1, float y1, float r1,
0817         int &exceptionCode) const
0818 {
0819     exceptionCode = 0;
0820     //### fuzzy
0821     if (r0 < 0.0f || r1 < 0.0f) {
0822         exceptionCode = DOMException::INDEX_SIZE_ERR;
0823         return nullptr;
0824     }
0825 
0826     QPointF center, focalPoint;
0827     float radius, innerRadius;
0828     bool inverse;
0829 
0830     // Use the larger of the two radii as the radius in the QGradient.
0831     // The gradient is always supposed to move from r0 to r1, so if r0 is
0832     // larger than r1, we'll use r0 as the radius and reverse the direction
0833     // of the gradient by inverting the positions of the color stops.
0834     // innerRadius is a percentage of the outer radius.
0835     if (r1 > r0) {
0836         center      = QPointF(x1, y1);
0837         focalPoint  = QPointF(x0, y0);
0838         radius      = r1;
0839         innerRadius = (r1 > 0.0f ? r0 / r1 : 0.0f);
0840         inverse     = false;
0841     } else {
0842         center      = QPointF(x0, y0);
0843         focalPoint  = QPointF(x1, y1);
0844         radius      = r0;
0845         innerRadius = (r0 > 0.0f ? r1 / r0 : 0.0f);
0846         inverse     = true;
0847     }
0848 
0849     QGradient *gradient = new QRadialGradient(center, radius, focalPoint);
0850     return new CanvasGradientImpl(gradient, innerRadius, inverse);
0851 }
0852 
0853 CanvasPatternImpl *CanvasContext2DImpl::createPattern(ElementImpl *pat, const DOMString &rpt,
0854         int &exceptionCode) const
0855 {
0856     exceptionCode = 0;
0857 
0858     // Decode repetition..
0859     bool repeatX;
0860     bool repeatY;
0861 
0862     if (rpt == "repeat" || rpt.isEmpty()) {
0863         repeatX = true;
0864         repeatY = true;
0865     } else if (rpt == "repeat-x") {
0866         repeatX = true;
0867         repeatY = false;
0868     } else if (rpt == "repeat-y") {
0869         repeatX = false;
0870         repeatY = true;
0871     } else if (rpt == "no-repeat") {
0872         repeatX = false;
0873         repeatY = false;
0874     } else {
0875         exceptionCode = DOMException::SYNTAX_ERR;
0876         return nullptr;
0877     }
0878 
0879     bool unsafe;
0880     QImage pic = extractImage(pat, exceptionCode, unsafe);
0881     if (exceptionCode) {
0882         return nullptr;
0883     }
0884 
0885     return new CanvasPatternImpl(pic, unsafe, repeatX, repeatY);
0886 }
0887 
0888 // Pen style ops
0889 //
0890 float CanvasContext2DImpl::lineWidth() const
0891 {
0892     return activeState().lineWidth;
0893 }
0894 
0895 void CanvasContext2DImpl::setLineWidth(float newLW)
0896 {
0897     if (newLW <= 0.0) {
0898         return;
0899     }
0900     activeState().lineWidth = newLW;
0901     dirty |= DrtStroke;
0902 }
0903 
0904 static const IDTranslator<QString, Qt::PenCapStyle, const char *>::Info penCapTranslatorTable[] = {
0905     {"round", Qt::RoundCap},
0906     {"square", Qt::SquareCap},
0907     {"butt", Qt::FlatCap},
0908     {nullptr, (Qt::PenCapStyle)0}
0909 };
0910 
0911 MAKE_TRANSLATOR(penCapTranslator, QString, Qt::PenCapStyle, const char *, penCapTranslatorTable)
0912 
0913 DOMString CanvasContext2DImpl::lineCap() const
0914 {
0915     return penCapTranslator()->toLeft(activeState().lineCap);
0916 }
0917 
0918 void CanvasContext2DImpl::setLineCap(const DOM::DOMString &cap)
0919 {
0920     QString capStr = cap.string();
0921     if (!penCapTranslator()->hasLeft(capStr)) {
0922         return;
0923     }
0924     activeState().lineCap = penCapTranslator()->toRight(capStr);
0925     dirty |= DrtStroke;
0926 }
0927 
0928 static const IDTranslator<QString, Qt::PenJoinStyle, const char *>::Info penJoinTranslatorTable[] = {
0929     {"round", Qt::RoundJoin},
0930     {"miter", Qt::SvgMiterJoin},
0931     {"bevel", Qt::BevelJoin},
0932     {nullptr, (Qt::PenJoinStyle)0}
0933 };
0934 
0935 MAKE_TRANSLATOR(penJoinTranslator, QString, Qt::PenJoinStyle, const char *, penJoinTranslatorTable)
0936 
0937 DOMString CanvasContext2DImpl::lineJoin() const
0938 {
0939     return penJoinTranslator()->toLeft(activeState().lineJoin);
0940 }
0941 
0942 void CanvasContext2DImpl::setLineJoin(const DOM::DOMString &join)
0943 {
0944     QString joinStr = join.string();
0945     if (!penJoinTranslator()->hasLeft(joinStr)) {
0946         return;
0947     }
0948     activeState().lineJoin = penJoinTranslator()->toRight(joinStr);
0949     dirty |= DrtStroke;
0950 }
0951 
0952 float CanvasContext2DImpl::miterLimit() const
0953 {
0954     return activeState().miterLimit;
0955 }
0956 
0957 void CanvasContext2DImpl::setMiterLimit(float newML)
0958 {
0959     if (newML <= 0.0) {
0960         return;
0961     }
0962     activeState().miterLimit = newML;
0963     dirty |= DrtStroke;
0964 }
0965 
0966 // Shadow settings
0967 //
0968 float CanvasContext2DImpl::shadowOffsetX() const
0969 {
0970     return activeState().shadowOffsetX;
0971 }
0972 
0973 void  CanvasContext2DImpl::setShadowOffsetX(float newOX)
0974 {
0975     activeState().shadowOffsetX = newOX;
0976 }
0977 
0978 float CanvasContext2DImpl::shadowOffsetY() const
0979 {
0980     return activeState().shadowOffsetY;
0981 }
0982 
0983 void  CanvasContext2DImpl::setShadowOffsetY(float newOY)
0984 {
0985     activeState().shadowOffsetY = newOY;
0986 }
0987 
0988 float CanvasContext2DImpl::shadowBlur() const
0989 {
0990     return activeState().shadowBlur;
0991 }
0992 
0993 void  CanvasContext2DImpl::setShadowBlur(float newBlur)
0994 {
0995     if (newBlur < 0) {
0996         return;
0997     }
0998 
0999     activeState().shadowBlur = newBlur;
1000 }
1001 
1002 DOMString CanvasContext2DImpl::shadowColor() const
1003 {
1004     return colorToString(activeState().shadowColor);
1005 }
1006 
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 //
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 
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 
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 
1065 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 //
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 
1087 void CanvasContext2DImpl::closePath()
1088 {
1089     path.closeSubpath();
1090 }
1091 
1092 void CanvasContext2DImpl::moveTo(float x, float y)
1093 {
1094     path.moveTo(mapToDevice(x, y));
1095     emptyPath = false;
1096 }
1097 
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 
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 
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 
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 
1136 inline bool CanvasContext2DImpl::needsShadow() const
1137 {
1138     return activeState().shadowColor.alpha() > 0;
1139 }
1140 
1141 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 
1164 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 
1214 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 
1297 void CanvasContext2DImpl::fill()
1298 {
1299     QPainter *p = acquirePainter();
1300     drawPath(p, path, DrawFill);
1301 }
1302 
1303 void CanvasContext2DImpl::stroke()
1304 {
1305     QPainter *p = acquirePainter();
1306     drawPath(p, path, DrawStroke);
1307 }
1308 
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 
1326 bool CanvasContext2DImpl::isPointInPath(float x, float y) const
1327 {
1328     return path.contains(QPointF(x, y));
1329 }
1330 
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 
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 
1480 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
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 
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 
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.
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 
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 
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