diff --git a/khtml/html/html_canvasimpl.cpp b/khtml/html/html_canvasimpl.cpp
index ea2f0bea9d..a7480c232a 100644
--- a/khtml/html/html_canvasimpl.cpp
+++ b/khtml/html/html_canvasimpl.cpp
@@ -1,1569 +1,1568 @@
/*
* Copyright (C) 2004 Apple Computer, Inc. All rights reserved.
* Copyright (C) 2005 Zack Rusin
* Copyright (C) 2007, 2008 Maksim Orlovich
* Copyright (C) 2007, 2008 Fredrik Höglund
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Portions of this code are (c) by Apple Computer, Inc. and were licensed
* under the following terms:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "html_canvasimpl.h"
#include "html_documentimpl.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include // for colorFromCSSValue
#include
#include
#include
#include
#include
#include
#include
#include //uglyyy: needs for inf/NaN tests
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace DOM;
using namespace khtml;
using namespace std;
// -------------------------------------------------------------------------
HTMLCanvasElementImpl::HTMLCanvasElementImpl(DocumentImpl *doc)
: HTMLElementImpl(doc)
{
w = 300;
h = 150;
unsafe = false;
}
HTMLCanvasElementImpl::~HTMLCanvasElementImpl()
{
if (context)
context->canvasElement = 0;
}
void HTMLCanvasElementImpl::parseAttribute(AttributeImpl* attr)
{
bool ok = false;
int val;
switch (attr->id())
{
// ### TODO: making them reflect w/h -- how?
case ATTR_WIDTH:
val = attr->val() ? attr->val()->toInt(&ok) : -1;
if (!ok || val <= 0)
w = 300;
else
w = val;
if (context)
context->resetContext(w, h);
setChanged();
break;
case ATTR_HEIGHT:
val = attr->val() ? attr->val()->toInt(&ok) : -1;
if (!ok || val <= 0)
h = 150;
else
h = val;
if (context)
context->resetContext(w, h);
setChanged();
break;
default:
HTMLElementImpl::parseAttribute(attr);
}
}
NodeImpl::Id HTMLCanvasElementImpl::id() const
{
return ID_CANVAS;
}
void HTMLCanvasElementImpl::attach()
{
assert(!attached());
assert(!m_render);
assert(parentNode());
RenderStyle* _style = getDocument()->styleSelector()->styleForElement(this);
_style->ref();
if (parentNode()->renderer() && parentNode()->renderer()->childAllowed() &&
_style->display() != NONE)
{
m_render = new (getDocument()->renderArena()) RenderCanvasImage(this);
m_render->setStyle(_style);
parentNode()->renderer()->addChild(m_render, nextRenderer());
}
_style->deref();
NodeBaseImpl::attach();
if (m_render)
m_render->updateFromElement();
}
CanvasContext2DImpl* HTMLCanvasElementImpl::getContext2D()
{
if (!context)
context = new CanvasContext2DImpl(this, w, h);;
return context.get();
}
khtmlImLoad::CanvasImage* HTMLCanvasElementImpl::getCanvasImage()
{
return getContext2D()->canvasImage;
}
bool HTMLCanvasElementImpl::isUnsafe() const
{
return unsafe;
}
void HTMLCanvasElementImpl::markUnsafe()
{
unsafe = true;
}
QString HTMLCanvasElementImpl::toDataURL(int& exceptionCode)
{
if (isUnsafe()) {
exceptionCode = DOMException::INVALID_ACCESS_ERR;
return "";
}
khtmlImLoad::CanvasImage* ci = getCanvasImage();
context->syncBackBuffer();
QByteArray pngBytes;
QBuffer pngSink(&pngBytes);
pngSink.open(QIODevice::WriteOnly);
ci->qimage()->save(&pngSink, "PNG");
pngSink.close();
return QString::fromLatin1("data:image/png;base64,") + pngBytes.toBase64();
}
// -------------------------------------------------------------------------
CanvasContext2DImpl::CanvasContext2DImpl(HTMLCanvasElementImpl* element, int width, int height):
canvasElement(element), canvasImage(0)
{
resetContext(width, height);
}
CanvasContext2DImpl::~CanvasContext2DImpl()
{
if (workPainter.isActive())
workPainter.end(); // Make sure to stop it before blowing the image away!
delete canvasImage;
}
// Basic infrastructure..
void CanvasContext2DImpl::resetContext(int width, int height)
{
// ### FIXME FIXME: use khtmlImLoad's limit policy
// for physical canvas and transform painter to match logical resolution
if (workPainter.isActive())
workPainter.end();
if (canvasImage)
canvasImage->resizeImage(width, height);
else
canvasImage = new khtmlImLoad::CanvasImage(width, height);
canvasImage->qimage()->fill(0x00000000); // transparent black is the initial state
stateStack.clear();
PaintState defaultState;
beginPath();
defaultState.infinityTransform = false;
defaultState.clipPath = QPainterPath();
defaultState.clipPath.setFillRule(Qt::WindingFill);
defaultState.clipping = false;
defaultState.globalAlpha = 1.0f;
defaultState.globalCompositeOperation = QPainter::CompositionMode_SourceOver;
defaultState.strokeStyle = new CanvasColorImpl(QColor(Qt::black));
defaultState.fillStyle = new CanvasColorImpl(QColor(Qt::black));
defaultState.lineWidth = 1.0f;
defaultState.lineCap = Qt::FlatCap;
defaultState.lineJoin = Qt::SvgMiterJoin;
defaultState.miterLimit = 10.0f;
defaultState.shadowOffsetX = 0.0f;
defaultState.shadowOffsetY = 0.0f;
defaultState.shadowBlur = 0.0f;
defaultState.shadowColor = QColor(0, 0, 0, 0); // Transparent black
stateStack.push(defaultState);
dirty = DrtAll;
needRendererUpdate();
}
void CanvasContext2DImpl::save()
{
stateStack.push(stateStack.top());
}
void CanvasContext2DImpl::restore()
{
if (stateStack.size() <= 1)
return;
stateStack.pop();
dirty = DrtAll;
}
QPainter* CanvasContext2DImpl::acquirePainter()
{
if (!workPainter.isActive()) {
workPainter.begin(canvasImage->qimage());
workPainter.setRenderHint(QPainter::Antialiasing);
workPainter.setRenderHint(QPainter::SmoothPixmapTransform);
dirty = DrtAll;
}
PaintState& state = activeState();
if (dirty & DrtClip) {
if (state.clipping)
workPainter.setClipPath(state.clipPath);
else
workPainter.setClipping(false);
}
if (dirty & DrtAlpha)
workPainter.setOpacity(state.globalAlpha);
if (dirty & DrtCompOp)
workPainter.setCompositionMode(state.globalCompositeOperation);
if (dirty & DrtStroke) {
QPen pen;
pen.setWidth(state.lineWidth);
pen.setCapStyle (state.lineCap);
pen.setJoinStyle(state.lineJoin);
pen.setMiterLimit(state.miterLimit);
CanvasStyleBaseImpl* style = state.strokeStyle.get();
if (style->type() == CanvasStyleBaseImpl::Color)
pen.setColor(static_cast(style)->color);
else
pen.setBrush(style->toBrush());
workPainter.setPen(pen); // ### should I even do this?
// I have a feeling I am mixing up path and
// non-path ops
}
if (dirty & DrtFill)
workPainter.setBrush(state.fillStyle->toBrush());
dirty = 0;
needRendererUpdate();
return &workPainter;
}
QImage CanvasContext2DImpl::extractImage(ElementImpl* el, int& exceptionCode, bool& unsafeOut) const
{
QImage pic;
exceptionCode = 0;
unsafeOut = false;
if (el->id() == ID_CANVAS) {
CanvasContext2DImpl* other = static_cast(el)->getContext2D();
other->syncBackBuffer();
pic = *other->canvasImage->qimage();
if (static_cast(el)->isUnsafe())
unsafeOut = true;
} else if (el->id() == ID_IMG) {
HTMLImageElementImpl* img = static_cast(el);
if (img->complete())
pic = img->currentImage();
else
exceptionCode = DOMException::INVALID_STATE_ERR;
if (img->isUnsafe())
unsafeOut = true;
} else {
exceptionCode = DOMException::TYPE_MISMATCH_ERR;
}
return pic;
}
void CanvasContext2DImpl::needRendererUpdate()
{
needsCommit = true;
if (canvasElement)
canvasElement->setChanged();
}
void CanvasContext2DImpl::syncBackBuffer()
{
if (workPainter.isActive())
workPainter.end();
}
void CanvasContext2DImpl::commit()
{
syncBackBuffer();
// Flush caches if we have changes.
if (needsCommit) {
canvasImage->contentUpdated();
needsCommit = false;
}
}
HTMLCanvasElementImpl* CanvasContext2DImpl::canvas() const
{
return canvasElement;
}
// Transformation ops
//
static inline float degrees(float radians)
{
return radians * 180.0 / M_PI;
}
static inline bool isInfArg(float x)
{
return KJS::isInf(x) || KJS::isNaN(x);
}
void CanvasContext2DImpl::scale(float x, float y)
{
dirty |= DrtTransform;
bool& infinityTransform = activeState().infinityTransform;
infinityTransform |= isInfArg(x) | isInfArg(y);
if (infinityTransform) return;
activeState().transform.scale(x, y);
}
void CanvasContext2DImpl::rotate(float angle)
{
dirty |= DrtTransform;
bool& infinityTransform = activeState().infinityTransform;
infinityTransform |= isInfArg(angle);
if (infinityTransform) return;
activeState().transform.rotateRadians(angle);
}
void CanvasContext2DImpl::translate(float x, float y)
{
dirty |= DrtTransform;
bool& infinityTransform = activeState().infinityTransform;
infinityTransform |= isInfArg(x) | isInfArg(y);
if (infinityTransform) return;
activeState().transform.translate(x, y);
}
void CanvasContext2DImpl::transform(float m11, float m12, float m21, float m22, float dx, float dy)
{
dirty |= DrtTransform;
bool& infinityTransform = activeState().infinityTransform;
infinityTransform |= isInfArg(m11) | isInfArg(m12) | isInfArg(m21) | isInfArg(m22) |
isInfArg(dx) | isInfArg(dy);
if (infinityTransform) return;
activeState().transform *= QTransform(m11, m12, 0.0f, m21, m22, 0.0f, dx, dy, 1.0f);
}
void CanvasContext2DImpl::setTransform(float m11, float m12, float m21, float m22, float dx, float dy)
{
activeState().transform.reset();
activeState().infinityTransform = false; // As cleared the matrix..
transform(m11, m12, m21, m22, dx, dy);
}
// Composition state setting
//
float CanvasContext2DImpl::globalAlpha() const
{
return activeState().globalAlpha;
}
void CanvasContext2DImpl::setGlobalAlpha(float a)
{
if (a < 0.0f || a > 1.0f)
return;
activeState().globalAlpha = a;
dirty |= DrtAlpha;
}
static const IDTranslator::Info compModeTranslatorTable[] = {
{"source-over", QPainter::CompositionMode_SourceOver},
{"source-out", QPainter::CompositionMode_SourceOut},
{"source-in", QPainter::CompositionMode_SourceIn},
{"source-atop", QPainter::CompositionMode_SourceAtop},
{"destination-atop", QPainter::CompositionMode_DestinationAtop},
{"destination-in", QPainter::CompositionMode_DestinationIn},
{"destination-out", QPainter::CompositionMode_DestinationOut},
{"destination-over", QPainter::CompositionMode_DestinationOver},
{"lighter", QPainter::CompositionMode_Lighten},
{"copy", QPainter::CompositionMode_Source},
{"xor", QPainter::CompositionMode_Xor},
{0, (QPainter::CompositionMode)0}
};
MAKE_TRANSLATOR(compModeTranslator, QString, QPainter::CompositionMode, const char*, compModeTranslatorTable)
DOM::DOMString CanvasContext2DImpl::globalCompositeOperation() const
{
return compModeTranslator()->toLeft(activeState().globalCompositeOperation);
}
void CanvasContext2DImpl::setGlobalCompositeOperation(const DOM::DOMString& op)
{
QString opStr = op.string();
if (!compModeTranslator()->hasLeft(opStr))
return; // Ignore unknown
activeState().globalCompositeOperation = compModeTranslator()->toRight(opStr);
dirty |= DrtCompOp;
}
// Colors and styles.
//
static QColor colorFromString(DOM::DOMString domStr)
{
// We make a temporary CSS decl. object to parse the color using the CSS parser.
CSSStyleDeclarationImpl tempStyle(0);
if (!tempStyle.setProperty(CSS_PROP_COLOR, domStr))
return QColor();
CSSValueImpl* val = tempStyle.getPropertyCSSValue(CSS_PROP_COLOR);
if (!val || val->cssValueType() != CSSValue::CSS_PRIMITIVE_VALUE)
return QColor();
CSSPrimitiveValueImpl* primVal = static_cast(val);
if (primVal->primitiveType() == CSSPrimitiveValue::CSS_IDENT)
return colorForCSSValue(primVal->getIdent());
if (primVal->primitiveType() != CSSPrimitiveValue::CSS_RGBCOLOR)
return QColor();
return QColor::fromRgba(primVal->getRGBColorValue());
}
static DOMString colorToString(const QColor& color)
{
QString str;
if (color.alpha() == 255)
str.sprintf("#%02x%02x%02x", color.red(), color.green(), color.blue());
else {
QString alphaColor = QString::number(color.alphaF());
// Ensure we always have a decimal period
if ((int)color.alphaF() == color.alphaF())
alphaColor = QString::number((int)color.alphaF()) + ".0";
str.sprintf("rgba(%d, %d, %d, ", color.red(), color.green(), color.blue());
str += alphaColor + ")";
}
return str;
}
//-------
DOM::DOMString CanvasColorImpl::toString() const
{
return colorToString(color);
}
CanvasColorImpl* CanvasColorImpl::fromString(const DOM::DOMString& str)
{
QColor cl = colorFromString(str);
if (!cl.isValid())
return 0;
return new CanvasColorImpl(cl);
}
//-------
CanvasGradientImpl::CanvasGradientImpl(QGradient* newGradient, float innerRadius, bool inverse)
: gradient(newGradient), innerRadius(innerRadius), inverse(inverse)
{}
static qreal adjustPosition( qreal pos, const QGradientStops &stops )
{
QGradientStops::const_iterator itr = stops.constBegin();
const qreal smallDiff = 0.00001;
while ( itr != stops.constEnd() ) {
const QGradientStop &stop = *itr;
++itr;
bool atEnd = ( itr != stops.constEnd() );
if ( qFuzzyCompare( pos, stop.first ) ) {
if ( atEnd || !qFuzzyCompare( pos + smallDiff, ( *itr ).first ) ) {
return qMin(pos + smallDiff, qreal(1.0));
}
}
}
return pos;
}
void CanvasGradientImpl::addColorStop(float offset, const DOM::DOMString& color, int& exceptionCode)
{
// ### we may have to handle the "currentColor" KW here. ouch.
exceptionCode = 0;
//### fuzzy compare (also for alpha)
if (offset < 0 || offset > 1) {
exceptionCode = DOMException::INDEX_SIZE_ERR;
return;
}
QColor qcolor = colorFromString(color);
if (!qcolor.isValid()) {
exceptionCode = DOMException::SYNTAX_ERR;
return;
}
// Adjust the position of the stop to emulate an inner radius.
// If the inner radius is larger than the outer, we'll reverse
// the position of the stop.
if (gradient->type() == QGradient::RadialGradient) {
if (inverse)
offset = 1.0 - offset;
offset = innerRadius + offset * (1.0 - innerRadius);
}
//