blob: baf2607f00121236f6c9c561be8c4f416751b90c [file] [log] [blame]
/*
Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org>
2004, 2005 Rob Buis <buis@kde.org>
This file is part of the KDE project
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
#include "config.h"
#ifdef SVG_SUPPORT
#include "SVGPatternElement.h"
#include "Attr.h"
#include "Document.h"
#include "GraphicsContext.h"
#include "RenderSVGContainer.h"
#include "KCanvasImage.h"
#include "KCanvasRenderingStyle.h"
#include "KRenderingDevice.h"
#include "KRenderingPaintServerPattern.h"
#include "SVGLength.h"
#include "SVGTransformList.h"
#include "SVGHelper.h"
#include "SVGMatrix.h"
#include "SVGNames.h"
#include "SVGSVGElement.h"
#include "SVGTransformList.h"
#include "SVGTransformable.h"
#include "ksvg.h"
#include <wtf/OwnPtr.h>
#include <math.h>
namespace WebCore {
SVGPatternElement::SVGPatternElement(const QualifiedName& tagName, Document *doc)
: SVGStyledLocatableElement(tagName, doc)
, SVGURIReference()
, SVGTests()
, SVGLangSpace()
, SVGExternalResourcesRequired()
, SVGFitToViewBox()
, KCanvasResourceListener()
, m_x(new SVGLength(this, LM_WIDTH, viewportElement()))
, m_y(new SVGLength(this, LM_HEIGHT, viewportElement()))
, m_width(new SVGLength(this, LM_WIDTH, viewportElement()))
, m_height(new SVGLength(this, LM_HEIGHT, viewportElement()))
, m_patternUnits(SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
, m_patternContentUnits(SVG_UNIT_TYPE_USERSPACEONUSE)
, m_patternTransform(new SVGTransformList(this))
{
m_tile = 0;
m_paintServer = 0;
m_ignoreAttributeChanges = false;
}
SVGPatternElement::~SVGPatternElement()
{
delete m_paintServer;
}
ANIMATED_PROPERTY_DEFINITIONS(SVGPatternElement, int, Enumeration, enumeration, PatternUnits, patternUnits, SVGNames::patternUnitsAttr.localName(), m_patternUnits)
ANIMATED_PROPERTY_DEFINITIONS(SVGPatternElement, int, Enumeration, enumeration, PatternContentUnits, patternContentUnits, SVGNames::patternContentUnitsAttr.localName(), m_patternContentUnits)
ANIMATED_PROPERTY_DEFINITIONS(SVGPatternElement, SVGLength*, Length, length, X, x, SVGNames::xAttr.localName(), m_x.get())
ANIMATED_PROPERTY_DEFINITIONS(SVGPatternElement, SVGLength*, Length, length, Y, y, SVGNames::yAttr.localName(), m_y.get())
ANIMATED_PROPERTY_DEFINITIONS(SVGPatternElement, SVGLength*, Length, length, Width, width, SVGNames::widthAttr.localName(), m_width.get())
ANIMATED_PROPERTY_DEFINITIONS(SVGPatternElement, SVGLength*, Length, length, Height, height, SVGNames::heightAttr.localName(), m_height.get())
ANIMATED_PROPERTY_DEFINITIONS(SVGPatternElement, SVGTransformList*, TransformList, transformList, PatternTransform, patternTransform, SVGNames::patternTransformAttr.localName(), m_patternTransform.get())
void SVGPatternElement::parseMappedAttribute(MappedAttribute *attr)
{
const AtomicString &value = attr->value();
if (attr->name() == SVGNames::patternUnitsAttr) {
if (value == "userSpaceOnUse")
setPatternUnitsBaseValue(SVG_UNIT_TYPE_USERSPACEONUSE);
else if (value == "objectBoundingBox")
setPatternUnitsBaseValue(SVG_UNIT_TYPE_OBJECTBOUNDINGBOX);
} else if (attr->name() == SVGNames::patternContentUnitsAttr) {
if (value == "userSpaceOnUse")
setPatternContentUnitsBaseValue(SVG_UNIT_TYPE_USERSPACEONUSE);
else if (value == "objectBoundingBox")
setPatternContentUnitsBaseValue(SVG_UNIT_TYPE_OBJECTBOUNDINGBOX);
} else if (attr->name() == SVGNames::patternTransformAttr) {
SVGTransformList *patternTransforms = patternTransformBaseValue();
SVGTransformable::parseTransformAttribute(patternTransforms, value);
} else if (attr->name() == SVGNames::xAttr)
xBaseValue()->setValueAsString(value);
else if (attr->name() == SVGNames::yAttr)
yBaseValue()->setValueAsString(value);
else if (attr->name() == SVGNames::widthAttr)
widthBaseValue()->setValueAsString(value);
else if (attr->name() == SVGNames::heightAttr)
heightBaseValue()->setValueAsString(value);
else {
if (SVGURIReference::parseMappedAttribute(attr))
return;
if (SVGTests::parseMappedAttribute(attr))
return;
if (SVGLangSpace::parseMappedAttribute(attr))
return;
if (SVGExternalResourcesRequired::parseMappedAttribute(attr))
return;
if (SVGFitToViewBox::parseMappedAttribute(attr))
return;
SVGStyledElement::parseMappedAttribute(attr);
}
}
const SVGStyledElement* SVGPatternElement::pushAttributeContext(const SVGStyledElement* context)
{
// All attribute's contexts are equal (so just take the one from 'x').
const SVGStyledElement* restore = x()->context();
x()->setContext(context);
y()->setContext(context);
width()->setContext(context);
height()->setContext(context);
return restore;
}
void SVGPatternElement::resourceNotification() const
{
// We're referenced by a "client", calculate the tile now...
notifyAttributeChange();
}
void SVGPatternElement::fillAttributesFromReferencePattern(const SVGPatternElement* target, AffineTransform& patternTransformMatrix)
{
DeprecatedString ref = href().deprecatedString();
KRenderingPaintServer *refServer = getPaintServerById(document(), ref.mid(1));
if (!refServer || refServer->type() != PS_PATTERN)
return;
KRenderingPaintServerPattern *refPattern = static_cast<KRenderingPaintServerPattern *>(refServer);
if (!hasAttribute(SVGNames::patternUnitsAttr)) {
const AtomicString& value = target->getAttribute(SVGNames::patternUnitsAttr);
if (value == "userSpaceOnUse")
setPatternUnitsBaseValue(SVG_UNIT_TYPE_USERSPACEONUSE);
else if (value == "objectBoundingBox")
setPatternUnitsBaseValue(SVG_UNIT_TYPE_OBJECTBOUNDINGBOX);
}
if (!hasAttribute(SVGNames::patternContentUnitsAttr)) {
const AtomicString& value = target->getAttribute(SVGNames::patternContentUnitsAttr);
if (value == "userSpaceOnUse")
setPatternContentUnitsBaseValue(SVG_UNIT_TYPE_USERSPACEONUSE);
else if (value == "objectBoundingBox")
setPatternContentUnitsBaseValue(SVG_UNIT_TYPE_OBJECTBOUNDINGBOX);
}
if (!hasAttribute(SVGNames::patternTransformAttr))
patternTransformMatrix = refPattern->patternTransform();
}
void SVGPatternElement::drawPatternContentIntoTile(const SVGPatternElement* target, const IntSize& newSize, AffineTransform patternTransformMatrix)
{
KRenderingDevice* device = renderingDevice();
SVGStyledElement* activeElement = static_cast<SVGStyledElement*>(m_paintServer->activeClient()->element());
bool bbox = (patternUnits() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX);
const SVGStyledElement* savedContext = 0;
if (bbox) {
if (width()->unitType() != SVGLength::SVG_LENGTHTYPE_PERCENTAGE)
width()->newValueSpecifiedUnits(SVGLength::SVG_LENGTHTYPE_PERCENTAGE, width()->value() * 100.);
if (height()->unitType() != SVGLength::SVG_LENGTHTYPE_PERCENTAGE)
height()->newValueSpecifiedUnits(SVGLength::SVG_LENGTHTYPE_PERCENTAGE, height()->value() * 100.);
if (activeElement)
savedContext = const_cast<SVGPatternElement* >(this)->pushAttributeContext(activeElement);
}
delete m_tile;
m_tile = static_cast<KCanvasImage*>(device->createResource(RS_IMAGE));
m_tile->init(newSize);
KRenderingDeviceContext* patternContext = device->contextForImage(m_tile);
device->pushContext(patternContext);
FloatRect rect(x()->value(), y()->value(), width()->value(), height()->value());
m_paintServer->setBbox(rect);
m_paintServer->setPatternTransform(patternTransformMatrix);
m_paintServer->setTile(m_tile);
OwnPtr<GraphicsContext> context(patternContext->createGraphicsContext());
for(Node *n = target->firstChild(); n != 0; n = n->nextSibling())
{
SVGElement* elem = svg_dynamic_cast(n);
if (!elem || !elem->isStyled())
continue;
SVGStyledElement* e = static_cast<SVGStyledElement* >(elem);
RenderObject *item = e->renderer();
if (!item)
continue;
#if 0
// FIXME: None of this code seems to be necessary
// to pass the w3c tests (and infact breaks them)
// However, I'm leaving it #if 0'd for now until
// I can quiz WildFox on the subject -- ecs 11/24/05
// It's possible that W3C-SVG-1.1/coords-units-01-b
// is failing due to lack of this code.
KCanvasMatrix savedMatrix = item->localTransform();
const SVGStyledElement* savedContext = 0;
if (patternContentUnits() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
{
if (activeElement)
savedContext = e->pushAttributeContext(activeElement);
}
// Take into account viewportElement's viewBox, if existant...
if (viewportElement() && viewportElement()->hasTagName(SVGNames::svgTag))
{
SVGSVGElement* svgElement = static_cast<SVGSVGElement* >(viewportElement());
RefPtr<SVGMatrix> svgCTM = svgElement->getCTM();
RefPtr<SVGMatrix> ctm = getCTM();
KCanvasMatrix newMatrix(svgCTM->matrix());
newMatrix.multiply(savedMatrix);
newMatrix.scale(1.0 / ctm->a(), 1.0 / ctm->d());
item->setLocalTransform(newMatrix.matrix());
}
#endif
RenderObject::PaintInfo info(context.get(), IntRect(), PaintPhaseForeground, 0, 0, 0);
item->paint(info, 0, 0);
#if 0
if (savedContext)
e->pushAttributeContext(savedContext);
item->setLocalTransform(savedMatrix.matrix());
#endif
}
if (savedContext)
const_cast<SVGPatternElement* >(this)->pushAttributeContext(savedContext);
device->popContext();
delete patternContext;
}
void SVGPatternElement::notifyClientsToRepaint() const
{
const RenderPathList &clients = m_paintServer->clients();
RenderPathList::ConstIterator it = clients.begin();
RenderPathList::ConstIterator end = clients.end();
for(; it != end; ++it)
{
const RenderPath *current = (*it);
SVGStyledElement* styled = (current ? static_cast<SVGStyledElement* >(current->element()) : 0);
if (styled)
{
styled->setChanged(true);
if (styled->renderer())
styled->renderer()->repaint();
}
}
}
void SVGPatternElement::notifyAttributeChange() const
{
if (!m_paintServer || !m_paintServer->activeClient() || m_ignoreAttributeChanges)
return;
IntSize newSize = IntSize(lroundf(width()->value()), lroundf(height()->value()));
if (m_tile && (m_tile->size() == newSize) || newSize.width() < 1 || newSize.height() < 1)
return;
m_ignoreAttributeChanges = true;
// FIXME: This whole "target" idea seems completely broken to me
// basically it seems we're effectively trying to change the "this" pointer
// for the rest of the method... why don't we just? Or better yet, why don't
// we call some method on the "target" and each target in the chain? -- ECS 11/21/05
// Find first pattern def that has children
const SVGPatternElement* target = this;
const Node *test = this;
while(test && !test->hasChildNodes())
{
DeprecatedString ref = target->href().deprecatedString();
test = ownerDocument()->getElementById(String(ref.mid(1)).impl());
if (test && test->hasTagName(SVGNames::patternTag))
target = static_cast<const SVGPatternElement* >(test);
}
unsigned short savedPatternUnits = patternUnits();
unsigned short savedPatternContentUnits = patternContentUnits();
AffineTransform patternTransformMatrix;
if (patternTransform()->numberOfItems() > 0)
patternTransformMatrix = patternTransform()->consolidate()->matrix()->matrix();
SVGPatternElement *nonConstThis = const_cast<SVGPatternElement*>(this);
nonConstThis->fillAttributesFromReferencePattern(target, patternTransformMatrix);
nonConstThis->drawPatternContentIntoTile(target, newSize, patternTransformMatrix);
nonConstThis->setPatternUnitsBaseValue(savedPatternUnits);
nonConstThis->setPatternContentUnitsBaseValue(savedPatternContentUnits);
notifyClientsToRepaint();
m_ignoreAttributeChanges = false;
}
RenderObject* SVGPatternElement::createRenderer(RenderArena* arena, RenderStyle*)
{
RenderSVGContainer* patternContainer = new (arena) RenderSVGContainer(this);
patternContainer->setDrawsContents(false);
return patternContainer;
}
KRenderingPaintServerPattern *SVGPatternElement::canvasResource()
{
if (!m_paintServer) {
KRenderingPaintServer *pserver = renderingDevice()->createPaintServer(KCPaintServerType(PS_PATTERN));
m_paintServer = static_cast<KRenderingPaintServerPattern *>(pserver);
m_paintServer->setListener(const_cast<SVGPatternElement* >(this));
}
return m_paintServer;
}
SVGMatrix *SVGPatternElement::getCTM() const
{
SVGMatrix *mat = SVGSVGElement::createSVGMatrix();
if (mat) {
RefPtr<SVGMatrix> viewBox = viewBoxToViewTransform(width()->value(), height()->value());
mat->multiply(viewBox.get());
}
return mat;
}
}
// vim:ts=4:noet
#endif // SVG_SUPPORT