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