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