KHtml

html_canvasimpl.cpp
1 /*
2  * Copyright (C) 2004 Apple Computer, Inc. All rights reserved.
3  * Copyright (C) 2005 Zack Rusin <[email protected]>
4  * Copyright (C) 2007, 2008 Maksim Orlovich <[email protected]>
5  * Copyright (C) 2007, 2008 Fredrik Höglund <[email protected]>
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 
90 HTMLCanvasElementImpl::HTMLCanvasElementImpl(DocumentImpl *doc)
91  : HTMLElementImpl(doc)
92 {
93  w = 300;
94  h = 150;
95  unsafe = false;
96 }
97 
98 HTMLCanvasElementImpl::~HTMLCanvasElementImpl()
99 {
100  if (context) {
101  context->canvasElement = nullptr;
102  }
103 }
104 
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 
142 NodeImpl::Id HTMLCanvasElementImpl::id() const
143 {
144  return ID_CANVAS;
145 }
146 
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 
169 CanvasContext2DImpl *HTMLCanvasElementImpl::getContext2D()
170 {
171  if (!context) {
172  context = new CanvasContext2DImpl(this, w, h);
173  }
174  return context.get();
175 }
176 
177 khtmlImLoad::CanvasImage *HTMLCanvasElementImpl::getCanvasImage()
178 {
179  return getContext2D()->canvasImage;
180 }
181 
182 bool HTMLCanvasElementImpl::isUnsafe() const
183 {
184  return unsafe;
185 }
186 
187 void HTMLCanvasElementImpl::markUnsafe()
188 {
189  unsafe = true;
190 }
191 
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 // -------------------------------------------------------------------------
212 CanvasContext2DImpl::CanvasContext2DImpl(HTMLCanvasElementImpl *element, int width, int height):
213  canvasElement(element), canvasImage(nullptr)
214 {
215  resetContext(width, height);
216 }
217 
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..
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 
274 void CanvasContext2DImpl::save()
275 {
276  stateStack.push(stateStack.top());
277 }
278 
279 void CanvasContext2DImpl::restore()
280 {
281  if (stateStack.size() <= 1) {
282  return;
283  }
284 
285  stateStack.pop();
286  dirty = DrtAll;
287 }
288 
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 
341 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 
374 void CanvasContext2DImpl::needRendererUpdate()
375 {
376  needsCommit = true;
377  if (canvasElement) {
378  canvasElement->setChanged();
379  }
380 }
381 
382 void CanvasContext2DImpl::syncBackBuffer()
383 {
384  if (workPainter.isActive()) {
385  workPainter.end();
386  }
387 }
388 
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 
400 HTMLCanvasElementImpl *CanvasContext2DImpl::canvas() const
401 {
402  return canvasElement;
403 }
404 
405 // Transformation ops
406 //
407 
408 static inline float degrees(float radians)
409 {
410  return radians * 180.0 / M_PI;
411 }
412 
413 static inline bool isInfArg(float x)
414 {
415  return KJS::isInf(x) || KJS::isNaN(x);
416 }
417 
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 
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 
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 
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 
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 
481 float CanvasContext2DImpl::globalAlpha() const
482 {
483  return activeState().globalAlpha;
484 }
485 
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},
508  {nullptr, (QPainter::CompositionMode)0}
509 };
510 
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 
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 
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 
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 
576 DOM::DOMString CanvasColorImpl::toString() const
577 {
578  return colorToString(color);
579 }
580 
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 
592 CanvasGradientImpl::CanvasGradientImpl(QGradient *newGradient, float innerRadius, bool inverse)
593  : gradient(newGradient), innerRadius(innerRadius), inverse(inverse)
594 {}
595 
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 
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 
654 CanvasGradientImpl::~CanvasGradientImpl()
655 {
656  delete gradient;
657 }
658 
659 QBrush CanvasGradientImpl::toBrush() const
660 {
661  return QBrush(*gradient);
662 }
663 
664 //-------
665 
666 CanvasPatternImpl::CanvasPatternImpl(const QImage &inImg, bool unsafe, bool rx, bool ry):
667  img(inImg), repeatX(rx), repeatY(ry), unsafe(unsafe)
668 {}
669 
670 QBrush CanvasPatternImpl::toBrush() const
671 {
672  return QBrush(img);
673 }
674 
675 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 
695 CanvasImageDataImpl::CanvasImageDataImpl(unsigned width, unsigned height) : data(width, height, QImage::Format_ARGB32)
696 {}
697 
698 CanvasImageDataImpl::CanvasImageDataImpl(const QImage &_data): data(_data)
699 {}
700 
701 CanvasImageDataImpl *CanvasImageDataImpl::clone() const
702 {
703  return new CanvasImageDataImpl(data);
704 }
705 
706 unsigned CanvasImageDataImpl::width() const
707 {
708  return data.width();
709 }
710 
711 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 
724 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 
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 
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 
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 
786 CanvasStyleBaseImpl *CanvasContext2DImpl::strokeStyle() const
787 {
788  return activeState().strokeStyle.get();
789 }
790 
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 
804 CanvasStyleBaseImpl *CanvasContext2DImpl::fillStyle() const
805 {
806  return activeState().fillStyle.get();
807 }
808 
809 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 
815 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 
853 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 //
890 float CanvasContext2DImpl::lineWidth() const
891 {
892  return activeState().lineWidth;
893 }
894 
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 
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 
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 
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 
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 
952 float CanvasContext2DImpl::miterLimit() const
953 {
954  return activeState().miterLimit;
955 }
956 
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 //
968 float CanvasContext2DImpl::shadowOffsetX() const
969 {
970  return activeState().shadowOffsetX;
971 }
972 
973 void CanvasContext2DImpl::setShadowOffsetX(float newOX)
974 {
975  activeState().shadowOffsetX = newOX;
976 }
977 
978 float CanvasContext2DImpl::shadowOffsetY() const
979 {
980  return activeState().shadowOffsetY;
981 }
982 
983 void CanvasContext2DImpl::setShadowOffsetY(float newOY)
984 {
985  activeState().shadowOffsetY = newOY;
986 }
987 
988 float CanvasContext2DImpl::shadowBlur() const
989 {
990  return activeState().shadowBlur;
991 }
992 
993 void CanvasContext2DImpl::setShadowBlur(float newBlur)
994 {
995  if (newBlur < 0) {
996  return;
997  }
998 
999  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();
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();
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);
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 
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);
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);
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 
void addPath(const QPainterPath &path)
void setTransform(const QTransform &transform, bool combine)
void setWidth(qreal width)
QSize size() const const
bool end()
QPointF currentPosition() const const
void setClipPath(const QPainterPath &path, Qt::ClipOperation operation)
void fillRect(const QRectF &rectangle, const QBrush &brush)
void setCompositionMode(QPainter::CompositionMode mode)
qreal alphaF() const const
CSSStyleDeclaration style()
Introduced in DOM Level 2 This method is from the CSSStyleDeclaration interface.
Format_ARGB32_Premultiplied
QPainter::RenderHints renderHints() const const
qreal x() const const
qreal y() const const
void closeSubpath()
bool save(const QString &fileName, const char *format, int quality) const const
This file is part of the HTML rendering engine for KDE.
WindingFill
bool contains(const QPointF &point) const const
void save()
void cubicTo(const QPointF &c1, const QPointF &c2, const QPointF &endPoint)
QPainterPath::Element elementAt(int index) const const
void setJoinStyle(Qt::PenJoinStyle style)
void moveTo(const QPointF &point)
int x() const const
int y() const const
KDOCTOOLS_EXPORT QString transform(const QString &file, const QString &stylesheet, const QVector< const char * > &params=QVector< const char * >())
void setBrush(const QBrush &brush)
int elementCount() const const
void setBrushOrigin(int x, int y)
QTransform inverted(bool *invertible) const const
QString & sprintf(const char *cformat,...)
void resetTransform()
void setCapStyle(Qt::PenCapStyle style)
IntersectClip
QTransform & translate(qreal dx, qreal dy)
QString number(int n, int base)
qreal x() const const
qreal y() const const
void addPolygon(const QPolygonF &polygon)
void setTransform(const QTransform &matrix)
QTransform & scale(qreal sx, qreal sy)
Node parentNode() const
The parent of this node.
Definition: dom_node.cpp:250
void fill(uint pixelValue)
void setFillRule(Qt::FillRule fillRule)
QString pattern(Mode mode=Reading)
KGuiItem stop()
void connectPath(const QPainterPath &path)
This file is part of the KHTML renderer for KDE.
Definition: translator.h:31
int red() const const
void setPen(const QColor &color)
QRectF controlPointRect() const const
int width() const const
void lineTo(const QPointF &endPoint)
void setMiterLimit(qreal limit)
QRect rect() const const
void addRect(const QRectF &rectangle)
void arcMoveTo(const QRectF &rectangle, qreal angle)
QTextStream & center(QTextStream &stream)
void setMiterLimit(qreal limit)
void setBrush(const QBrush &brush)
A CanvasImage encapsulates a QImage that will be painted on externally, in order to permit scaling of...
Definition: canvasimage.h:39
This class implements the basic string we use in the DOM.
Definition: dom_string.h:44
QPainterPath createStroke(const QPainterPath &path) const const
QRect translated(int dx, int dy) const const
QPoint brushOrigin() const const
void setColor(const QColor &color)
DOMString id() const
The element&#39;s identifier.
void setJoinStyle(Qt::PenJoinStyle style)
int alpha() const const
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
int green() const const
CompositionMode_SourceOver
QColor fromRgba(QRgb rgba)
const QBrush & brush() const const
SvgMiterJoin
bool isEmpty() const const
bool isEmpty() const const
bool isValid() const const
void setRenderHints(QPainter::RenderHints hints, bool on)
void restore()
This library provides a full-featured HTML parser and widget.
int blue() const const
void drawImage(const QRectF &target, const QImage &image, const QRectF &source, Qt::ImageConversionFlags flags)
qreal width() const const
void drawPath(const QPainterPath &path)
void setWidth(int width)
void setCapStyle(Qt::PenCapStyle style)
bool isEmpty() const const
QPoint topLeft() const const
QRect toAlignedRect() const const
void translate(const QPointF &offset)
QString fromLatin1(const char *str, int size)
qreal height() const const
int height() const const
QBrush brush() const const
void quadTo(const QPointF &c, const QPointF &endPoint)
QImage * qimage() const
Returns the image of basic content.
Definition: image.cpp:467
bool begin(QPaintDevice *device)
void arcTo(const QRectF &rectangle, qreal startAngle, qreal sweepLength)
const QPen & pen() const const
QRect mapRect(const QRect &rectangle) const const
QByteArray toBase64(QByteArray::Base64Options options) const const
QPainterPath intersected(const QPainterPath &p) const const
QRgb rgba() const const
bool isValid() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Oct 26 2021 22:48:01 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.