You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegraphics/ksvg/core/KSVGReader.cc

507 lines
14 KiB

/*
Copyright (C) 2001-2003 KSVG Team
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., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <dom/dom_exception.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <tqmap.h>
#include <ksimpleconfig.h>
#include <KSVGCanvas.h>
#include "KSVGReader.moc"
#include "SVGSVGElementImpl.h"
#include "SVGViewSpecImpl.h"
#include "SVGDocumentImpl.h"
#include "SVGMatrixImpl.h"
#include "SVGShapeImpl.h"
#include "SVGLengthImpl.h"
#include "SVGImageElementImpl.h"
#include "SVGAnimatedLengthImpl.h"
#include "SVGUseElementImpl.h"
namespace KSVG
{
class Helper
{
public:
static Helper *self(KSVGReader *reader = 0);
void destroy();
void setFit(bool bFit = true) { m_bFit = bFit; }
bool fit() { return m_bFit; }
SVGDocumentImpl *doc() const { return m_reader->doc(); }
KSVGCanvas *canvas() const { return m_reader->canvas(); }
void addSVGElement(SVGSVGElementImpl *one, DOM::NodeImpl *two) { m_svgMap.insert(two, one); }
SVGSVGElementImpl *nextSVGElement(SVGElementImpl *elem);
SVGSVGElementImpl *nextSVGElement(DOM::Node elem);
void setFinished(bool error, const TQString &errorDesc = "") { m_reader->setFinished(error, errorDesc); }
// Error handling
void setErrorDescription(const TQString &err) { m_errorDesc = err; }
TQString errorDescription() { return m_errorDesc; }
bool hasError() const { return !m_errorDesc.isEmpty(); }
bool getURLMode() const { return m_getURLMode; }
void setGetURLMode(bool mode) { m_getURLMode = mode; }
TQString SVGFragmentId() const { return m_SVGFragmentId; }
void setSVGFragmentId(const TQString &SVGFragmentId) { m_SVGFragmentId = SVGFragmentId; }
protected:
Helper(KSVGReader *reader);
private:
Helper();
Helper(const Helper &rhs);
Helper &operator=(const Helper &rhs);
static Helper *m_instance;
TQMap<DOM::NodeImpl *, SVGSVGElementImpl *> m_svgMap;
KSVGReader *m_reader;
bool m_bFit;
bool m_getURLMode;
TQString m_errorDesc;
TQString m_SVGFragmentId;
};
class InputHandler : public TQXmlDefaultHandler
{
public:
virtual bool startDocument();
virtual bool endDocument();
virtual bool startElement(const TQString &namespaceURI,
const TQString &localName,
const TQString &qName,
const TQXmlAttributes &atts);
virtual bool endElement(const TQString &namespaceURI,
const TQString &localName,
const TQString &qName);
virtual bool characters(const TQString &ch);
virtual bool warning(const TQXmlParseException &e);
virtual bool error(const TQXmlParseException &e);
virtual bool fatalError(const TQXmlParseException &e);
private:
DOM::Node *m_rootNode;
DOM::Node *m_currentNode;
DOM::Node m_parentNode;
bool m_noRendering, m_progressive;
};
}
using namespace KSVG;
Helper *Helper::m_instance = 0;
Helper::Helper(KSVGReader *reader)
{
m_reader = reader;
}
Helper *Helper::self(KSVGReader *reader)
{
if(m_instance && reader != 0)
m_instance->m_reader = reader;
if(!m_instance)
{
Q_ASSERT(reader != 0);
m_instance = new Helper(reader);
}
return m_instance;
}
void Helper::destroy()
{
m_svgMap.clear();
}
SVGSVGElementImpl *Helper::nextSVGElement(SVGElementImpl *elem)
{
return nextSVGElement(*elem);
}
SVGSVGElementImpl *Helper::nextSVGElement(DOM::Node elem)
{
DOM::Node foundSVG;
DOM::Node shape = elem.parentNode();
for(; !shape.isNull(); shape = shape.parentNode())
{
if(reinterpret_cast<DOM::Element &>(shape).nodeName() == "svg")
{
foundSVG = shape;
break;
}
}
SVGSVGElementImpl *svg = m_svgMap[foundSVG.handle()];
return svg;
}
bool InputHandler::startDocument()
{
m_rootNode = 0;
m_currentNode = 0;
m_noRendering = false;
KSimpleConfig config("ksvgpluginrc");
config.setGroup("Rendering");
m_progressive = config.readBoolEntry("ProgressiveRendering", true);
if(Helper::self()->canvas())
Helper::self()->canvas()->setImmediateUpdate(m_progressive);
return true;
}
bool InputHandler::endDocument()
{
Helper::self()->setFinished(false);
if (Helper::self()->canvas())
Helper::self()->canvas()->setImmediateUpdate(false);
return true;
}
bool InputHandler::characters(const TQString &ch)
{
kdDebug(26001) << "InputHandler::characters, read " << ch << endl;
if(ch.simplifyWhiteSpace().isEmpty())
return true;
TQString t = ch;
SVGSVGElementImpl *root = Helper::self()->nextSVGElement(*m_currentNode);
if(root)
{
SVGElementImpl *element = root->ownerDoc()->getElementFromHandle(m_currentNode->handle());
SVGLangSpaceImpl *langSpace = dynamic_cast<SVGLangSpaceImpl *>(element);
if(langSpace)
t = langSpace->handleText(ch);
}
if(!t.isEmpty())
{
DOM::Text impl = static_cast<DOM::Document *>(Helper::self()->doc())->createTextNode(t);
m_currentNode->appendChild(impl);
}
return true;
}
bool InputHandler::startElement(const TQString &namespaceURI, const TQString &, const TQString &qName, const TQXmlAttributes &attrs)
{
kdDebug(26001) << "InputHandler::startElement, namespaceURI " << namespaceURI << " qName " << qName << endl;
SVGElementImpl *newElement = 0;
SVGSVGElementImpl *svg = 0;
if(qName == "svg")
{
DOM::Element impl = static_cast<DOM::Document *>(Helper::self()->doc())->createElementNS(namespaceURI, qName);
newElement = SVGDocumentImpl::createElement(qName, impl, Helper::self()->doc());
svg = dynamic_cast<SVGSVGElementImpl *>(newElement);
Helper::self()->addSVGElement(svg, impl.handle());
// Need this before we can find our ownerSVGElement (AP)
if(m_currentNode != 0)
{
m_currentNode->appendChild(*svg);
}
else
{
if(Helper::self()->fit())
{ // handle fitting of svg into small drawing area(thumb)
// TODO : aspectratio? and what about svgs that dont provide width and height?
if(attrs.value("viewBox").isEmpty())
{
SVGLengthImpl *width = SVGSVGElementImpl::createSVGLength();
SVGLengthImpl *height = SVGSVGElementImpl::createSVGLength();
width->setValueAsString(attrs.value("width"));
height->setValueAsString(attrs.value("height"));
TQString viewbox = TQString("0 0 %1 %2").arg(width->value()).arg(height->value());
//kdDebug(26001) << "VIEWBOX : " << viewbox.latin1() << endl;
// HACK
// Does the existing attribute need to be deleted before appending the new attribute?
const_cast<TQXmlAttributes&>(attrs).append("viewBox", TQString::null, "viewBox", viewbox);
width->deref();
height->deref();
}
// HACK
// Does the existing attribute need to be deleted before appending the new attribute?
const_cast<TQXmlAttributes&>(attrs).append("width", TQString::null, "width", TQString::number(Helper::self()->canvas()->width()));
const_cast<TQXmlAttributes&>(attrs).append("height", TQString::null, "height", TQString::number(Helper::self()->canvas()->height()));
}
if(!Helper::self()->SVGFragmentId().isEmpty())
{
if(svg->currentView()->parseViewSpec(Helper::self()->SVGFragmentId()))
{
svg->setUseCurrentView(true);
}
}
}
if(m_rootNode == 0)
{
Helper::self()->doc()->appendChild(*svg);
Helper::self()->doc()->setRootElement(svg);
m_rootNode = svg;
}
}
else
{
if(!m_rootNode && !Helper::self()->getURLMode())
{
Helper::self()->setErrorDescription(i18n("A legal svg document requires a <svg> root element"));
return false;
}
DOM::Element impl = static_cast<DOM::Document *>(Helper::self()->doc())->createElementNS(namespaceURI, qName);
newElement = SVGDocumentImpl::createElement(qName, impl, Helper::self()->doc());
// m_currentNode == 0 if we are dynamically extending the dom (parsexml...)
// and the file doesn't have a root element
if(m_currentNode != 0)
m_currentNode->appendChild(*newElement);
else
Helper::self()->doc()->appendChild(*newElement);
// Special logics:
if(qName == "switch" || qName == "pattern" || qName == "mask")
m_noRendering = true;
}
newElement->setOwnerSVGElement(Helper::self()->nextSVGElement(newElement));
newElement->setViewportElement(newElement->ownerSVGElement());
newElement->setAttributes(attrs);
if(svg && svg->ownerSVGElement() == 0)
{
SVGImageElementImpl *parentImage = Helper::self()->doc()->parentImage();
if(parentImage)
{
// We're being displayed in a document via an 'image' element. Set
// us up to fit into it's rectangle.
parentImage->setupSVGElement(svg);
}
}
SVGLocatableImpl *locatable = dynamic_cast<SVGLocatableImpl *>(newElement);
if(locatable)
{
// Set up the cached screenCTM
SVGLocatableImpl *locatableParent = 0;
DOM::Node parentNode = newElement->parentNode();
if(!parentNode.isNull())
{
SVGElementImpl *parent = Helper::self()->doc()->getElementFromHandle(parentNode.handle());
if(parent)
locatableParent = dynamic_cast<SVGLocatableImpl *>(parent);
}
SVGMatrixImpl *parentMatrix = 0;
if(locatableParent)
parentMatrix = locatableParent->getScreenCTM();
else
parentMatrix = SVGSVGElementImpl::createSVGMatrix();
locatable->updateCachedScreenCTM(parentMatrix);
parentMatrix->deref();
}
m_currentNode = newElement;
return !Helper::self()->hasError();
}
bool InputHandler::endElement(const TQString &, const TQString &, const TQString &qName)
{
kdDebug(26001) << "InputHandler::endElement, qName " << qName << endl;
bool haveCanvas = Helper::self()->canvas();
SVGSVGElementImpl *root = Helper::self()->nextSVGElement(*m_currentNode);
SVGElementImpl *element = root ? root->ownerDoc()->getElementFromHandle(m_currentNode->handle()) : Helper::self()->doc()->getElementFromHandle(m_currentNode->handle());
SVGShapeImpl *shape = dynamic_cast<SVGShapeImpl *>(element);
SVGTestsImpl *tests = dynamic_cast<SVGTestsImpl *>(element);
SVGStylableImpl *style = dynamic_cast<SVGStylableImpl *>(element);
if(qName != "script" && !m_noRendering && !Helper::self()->getURLMode())
{
if(!root)
{
if(haveCanvas)
{
if(!m_progressive)
Helper::self()->canvas()->update();
Helper::self()->canvas()->blit();
TQValueList<SVGUseElementImpl *> forwardReferencingUseElements = Helper::self()->doc()->forwardReferencingUseElements();
if(!forwardReferencingUseElements.isEmpty())
{
// Create the elements again now that we have parsed the whole document.
TQValueList<SVGUseElementImpl *>::iterator it;
Helper::self()->canvas()->setImmediateUpdate(false);
for(it = forwardReferencingUseElements.begin(); it != forwardReferencingUseElements.end(); it++)
(*it)->createItem(Helper::self()->canvas());
// The newly created items will need to be moved into their correct z-order positions.
Helper::self()->doc()->resortZIndicesOnFinishedLoading();
}
}
return true; // we have reached the bottom
}
if(haveCanvas && (tests ? tests->ok() : true))
{
if((shape && !shape->isContainer()) || (!shape && element))
element->createItem();
}
}
// Special logics:
if(qName == "switch" || qName == "pattern" || qName == "mask")
{
m_noRendering = false;
bool ok = tests ? tests->ok() : true;
if((haveCanvas && element && style && ok && style->getDisplay() && style->getVisible() && (qName == "pattern")) || (shape && shape->directRender()))
element->createItem();
}
m_parentNode = m_currentNode->parentNode(); // this is needed since otherwise we get temporary vars
m_currentNode = &m_parentNode;
return true;
}
bool InputHandler::warning(const TQXmlParseException &e)
{
kdDebug(26001) << "[" << e.lineNumber() << ":" << e.columnNumber() << "]: WARNING: " << e.message() << endl;
return true;
}
bool InputHandler::error(const TQXmlParseException &e)
{
kdDebug(26001) << "[" << e.lineNumber() << ":" << e.columnNumber() << "]: ERROR: " << e.message() << endl;
return true;
}
bool InputHandler::fatalError(const TQXmlParseException &e)
{
TQString error;
if(Helper::self()->hasError())
{
error = Helper::self()->errorDescription();
Helper::self()->setErrorDescription(TQString());
}
else
error = TQString("[%1:%2]: FATAL ERROR: %3").arg(e.lineNumber()).arg(e.columnNumber()).arg(e.message());
kdDebug(26001) << "InputHandler::fatalError, " << error << endl;
Helper::self()->setFinished(true, error);
return true;
}
struct KSVGReader::Private
{
TQXmlSimpleReader *reader;
InputHandler *inputHandler;
SVGDocumentImpl *doc;
KSVGCanvas *canvas;
};
KSVGReader::KSVGReader(SVGDocumentImpl *doc, KSVGCanvas *canvas, ParsingArgs args) : TQObject(), d(new Private)
{
d->doc = doc;
d->canvas = canvas;
d->reader = new TQXmlSimpleReader();
d->inputHandler = new InputHandler();
Helper::self(this);
Helper::self()->setFit(args.fit);
Helper::self()->setGetURLMode(args.getURLMode);
Helper::self()->setSVGFragmentId(args.SVGFragmentId);
d->reader->setContentHandler(d->inputHandler);
d->reader->setErrorHandler(d->inputHandler);
}
KSVGReader::~KSVGReader()
{
Helper::self()->destroy();
delete d->reader;
delete d->inputHandler;
delete d;
}
void KSVGReader::parse(TQXmlInputSource *source)
{
d->reader->parse(source);
}
void KSVGReader::finishParsing(bool, const TQString &errorDesc)
{
Helper::self()->setErrorDescription(errorDesc);
}
void KSVGReader::setFinished(bool error, const TQString &errorDesc)
{
kdDebug(26001) << "KSVGReader::setFinished" << endl;
emit finished(error, errorDesc);
}
SVGDocumentImpl *KSVGReader::doc()
{
return d->doc;
}
KSVG::KSVGCanvas *KSVGReader::canvas()
{
return d->canvas;
}
// vim:ts=4:noet