/* 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 #include #include #include #include #include #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 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(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(element); if(langSpace) t = langSpace->handleText(ch); } if(!t.isEmpty()) { DOM::Text impl = static_cast(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(Helper::self()->doc())->createElementNS(namespaceURI, qName); newElement = SVGDocumentImpl::createElement(qName, impl, Helper::self()->doc()); svg = dynamic_cast(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(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(attrs).append("width", TQString::null, "width", TQString::number(Helper::self()->canvas()->width())); const_cast(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 root element")); return false; } DOM::Element impl = static_cast(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(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(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(element); SVGTestsImpl *tests = dynamic_cast(element); SVGStylableImpl *style = dynamic_cast(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 forwardReferencingUseElements = Helper::self()->doc()->forwardReferencingUseElements(); if(!forwardReferencingUseElements.isEmpty()) { // Create the elements again now that we have parsed the whole document. TQValueList::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; }