diff options
Diffstat (limited to 'ksvg/impl/SVGDocumentImpl.cpp')
-rw-r--r-- | ksvg/impl/SVGDocumentImpl.cpp | 703 |
1 files changed, 703 insertions, 0 deletions
diff --git a/ksvg/impl/SVGDocumentImpl.cpp b/ksvg/impl/SVGDocumentImpl.cpp new file mode 100644 index 00000000..ce6de16e --- /dev/null +++ b/ksvg/impl/SVGDocumentImpl.cpp @@ -0,0 +1,703 @@ +/* + 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 <kdebug.h> + +#define USE_VALGRIND 0 + +#if USE_VALGRIND +#include <valgrind/calltree.h> +#endif + +#include "SVGEvent.h" +#include "SVGMatrixImpl.h" +#include "SVGWindowImpl.h" +#include "SVGElementImpl.h" +#include "SVGDocumentImpl.moc" +#include "SVGSVGElementImpl.h" +#include "SVGImageElementImpl.h" +#include "SVGScriptElementImpl.h" +#include "SVGTitleElementImpl.h" +#include "SVGAnimationElementImpl.h" + +#include "KSVGReader.h" +#include "KSVGLoader.h" +#include "KSVGCanvas.h" +#include "CanvasItem.h" + +#include <tqpaintdevicemetrics.h> + +using namespace KSVG; + +#include "SVGDocumentImpl.lut.h" +#include "ksvg_scriptinterpreter.h" +#include "ksvg_bridge.h" +#include "ksvg_ecma.h" + +// A sequence of prime numbers that sets the m_elemDict's hash table size as the +// number of elements in the dictionary passes each level. This keeps the lookup +// performance high as the number of elements grows. See the TQDict documentation. +unsigned int SVGDocumentImpl::elemDictHashSizes [] = +{ + 101, + 211, + 401, + 809, + 1601, + 3203, + 6421, + 12809, + 25601, + 51203, + 102407, + 204803, + 409609, + 819229 +}; + +const int SVGDocumentImpl::numElemDictHashSizes = sizeof(elemDictHashSizes) / sizeof(elemDictHashSizes[0]); + +SVGDocumentImpl::SVGDocumentImpl(bool anim, bool fit, SVGImageElementImpl *parentImage) : TQObject(), DOM::DomShared(), DOM::Document(), SVGDOMNodeBridge(static_cast<DOM::Node>(*this)) +{ + m_animations = anim; + + m_reader = 0; + m_loader = 0; + m_canvas = 0; + m_rootElement = 0; + m_lastTarget = 0; + m_window = 0; + + m_elemDictHashSizeIndex = 0; + m_elemDict.resize(elemDictHashSizes[m_elemDictHashSizeIndex]); + + m_timeScheduler = new SVGTimeScheduler(this); + m_ecmaEngine = new KSVGEcma(this); + m_ecmaEngine->setup(); + + m_finishedParsing = false; + m_finishedLoading = false; + m_resortZIndicesOnFinishedLoading = false; + m_fit = fit; + + m_parentImage = parentImage; + if(m_parentImage) + m_parentImage->ref(); +} + +SVGDocumentImpl::~SVGDocumentImpl() +{ + if(rootElement() && rootElement()->hasEventListener(SVGEvent::UNLOAD_EVENT, true)) + rootElement()->dispatchEvent(SVGEvent::UNLOAD_EVENT, false, false); + + TQPtrList<SVGShapeImpl> killList; + + DOM::Node node = firstChild(); + for(; !node.isNull(); node = node.nextSibling()) + { + SVGShapeImpl *shape = dynamic_cast<SVGShapeImpl *>(getElementFromHandle(node.handle())); + if(shape) + killList.append(shape); + } + + SVGShapeImpl *rend = 0; + for(rend = killList.first(); rend; rend = killList.next()) + delete rend; + + delete m_timeScheduler; + delete m_ecmaEngine; + delete m_reader; + delete m_loader; + + if(m_window) + m_window->deref(); + + if(m_parentImage) + m_parentImage->deref(); +} + +SVGWindowImpl *SVGDocumentImpl::window() +{ + if(!m_window) + { + m_window = new SVGWindowImpl(const_cast<SVGDocumentImpl *>(this)); + m_window->ref(); + } + + return m_window; +} + +float SVGDocumentImpl::screenPixelsPerMillimeterX() const +{ + if(canvas() && canvas()->drawWindow()) + { + TQPaintDeviceMetrics metrics(canvas()->drawWindow()); + return float(metrics.width()) / float(metrics.widthMM()); + } + else + return 90.0; +} + +float SVGDocumentImpl::screenPixelsPerMillimeterY() const +{ + if(canvas() && canvas()->drawWindow()) + { + TQPaintDeviceMetrics metrics(canvas()->drawWindow()); + return float(metrics.height()) / float(metrics.heightMM()); + } + else + return 90.0; +} + +DOM::DOMString SVGDocumentImpl::title() const +{ + DOM::Node n; + for(n = rootElement()->firstChild(); !n.isNull(); n = n.nextSibling()) + { + SVGElementImpl *elem = getElementFromHandle(n.handle()); + if(dynamic_cast<SVGTitleElementImpl *>(elem)) + return elem->collectText(); + } + return ""; +} + +DOM::DOMString SVGDocumentImpl::referrer() const +{ + return m_referrer; +} + +DOM::DOMString SVGDocumentImpl::domain() const +{ + return m_baseURL.host(); +} + +DOM::DOMString SVGDocumentImpl::URL() const +{ + return m_baseURL.prettyURL(); +} + +void SVGDocumentImpl::setReferrer(const DOM::DOMString &referrer) +{ + // TODO : better may be to request for referrer instead of storing it + m_referrer = referrer; +} + +void SVGDocumentImpl::setRootElement(SVGSVGElementImpl *elem) +{ + m_rootElement = elem; +} + +SVGSVGElementImpl *SVGDocumentImpl::rootElement() const +{ + return m_rootElement; +} + +SVGElementImpl *SVGDocumentImpl::createElement(const DOM::DOMString &name, DOM::Element impl, SVGDocumentImpl *doc) +{ + DOM::ElementImpl *handle = reinterpret_cast<DOM::ElementImpl *>(impl.handle()); + SVGElementImpl *element = SVGElementImpl::Factory::self()->create(std::string(name.string().latin1()), handle); + + if(!element) + element = new SVGElementImpl(handle); + + element->setOwnerDoc(doc); + element->ref(); + return element; +} + +bool SVGDocumentImpl::open(const ::KURL &url) +{ + if(!url.prettyURL().isEmpty()) + { + m_baseURL = url; + + if(!m_loader) + m_loader = new KSVGLoader(); + + connect(m_loader, TQT_SIGNAL(gotResult(TQIODevice *)), this, TQT_SLOT(slotSVGContent(TQIODevice *))); + m_loader->getSVGContent(url); + } + else + return false; + + return true; +} + +void SVGDocumentImpl::slotSVGContent(TQIODevice *dev) +{ + TQXmlInputSource *inputSource = new TQXmlInputSource(dev); + + if(m_reader) + delete m_reader; + + KSVGReader::ParsingArgs args; + args.fit = m_fit; + args.getURLMode = false; + + TQString url = m_baseURL.prettyURL(); + int pos = url.find('#'); // url can become like this.svg#svgView(viewBox(63,226,74,74)), get part after '#' + if(pos > -1) + args.SVGFragmentId = url.mid(pos + 1); + + m_reader = new KSVGReader(this, m_canvas, args); + connect(m_reader, TQT_SIGNAL(finished(bool, const TQString &)), this, TQT_SLOT(slotFinishedParsing(bool, const TQString &))); + m_t.start(); + +#if USE_VALGRIND + CALLTREE_ZERO_STATS(); +#endif + + m_reader->parse(inputSource); + delete dev; +} + +void SVGDocumentImpl::parseSVG(TQXmlInputSource *inputSource, bool getURLMode) +{ + if(m_reader) + delete m_reader; + + KSVGReader::ParsingArgs args; + args.fit = m_fit; + args.getURLMode = getURLMode; + m_reader = new KSVGReader(this, 0, args); + connect(m_reader, TQT_SIGNAL(finished(bool, const TQString &)), this, TQT_SLOT(slotFinishedParsing(bool, const TQString &))); + +#if USE_VALGRIND + CALLTREE_ZERO_STATS(); +#endif + + m_reader->parse(inputSource); +} + +void SVGDocumentImpl::finishParsing(bool error, const TQString &errorDesc) +{ + if(m_reader) + m_reader->finishParsing(error, errorDesc); +} + +void SVGDocumentImpl::slotFinishedParsing(bool error, const TQString &errorDesc) +{ + kdDebug(26000) << k_funcinfo << "total time : " << m_t.elapsed() << endl; + +#if USE_VALGRIND + CALLTREE_DUMP_STATS(); +#endif + + if(m_animations) + m_timeScheduler->startAnimations(); + + if(m_canvas && !error && rootElement()) + executeScripts(); + + m_finishedParsing = true; + + emit finishedParsing(error, errorDesc); + if(!error) + emit finishedRendering(); + + checkFinishedLoading(); +} + +void SVGDocumentImpl::newImageJob(SVGImageElementImpl *image) +{ + kdDebug(26002) << "SVGDocumentImpl::newImageJob, " << image << endl; + m_loader->newImageJob(image, m_baseURL); +} + +void SVGDocumentImpl::notifyImageLoading(SVGImageElementImpl *image) +{ + m_imagesLoading.append(image); +} + +void SVGDocumentImpl::notifyImageLoaded(SVGImageElementImpl *image) +{ + m_imagesLoading.remove(image); + + if(m_imagesLoading.isEmpty()) + checkFinishedLoading(); +} + +void SVGDocumentImpl::checkFinishedLoading() +{ + if(m_finishedParsing && m_imagesLoading.isEmpty()) + { + m_finishedLoading = true; + + if(m_resortZIndicesOnFinishedLoading) + { + // Only resort if we're the 'outermost' document, i.e. we're not an svg image + // inside another document. We could resort as each image finishes loading, but it + // slows down the parsing phase. + if(m_parentImage == 0 && m_canvas && m_rootElement) + { + m_canvas->setElementItemZIndexRecursive(m_rootElement, 0); + m_canvas->update(); + } + } + + emit finishedLoading(); + } +} + +void SVGDocumentImpl::addForwardReferencingUseElement(SVGUseElementImpl *use) +{ + if(!m_forwardReferencingUseElements.contains(use)) + m_forwardReferencingUseElements.append(use); +} + +void SVGDocumentImpl::slotPaint() +{ + rerender(); +} + +void SVGDocumentImpl::rerender() +{ + m_canvas->update(); + emit finishedRendering(); +} + +void SVGDocumentImpl::attach(KSVG::KSVGCanvas *c) +{ + m_canvas = c; +} + +void SVGDocumentImpl::detach() +{ + m_canvas = 0; +} + +KSVG::KSVGCanvas *SVGDocumentImpl::canvas() const +{ + return m_canvas; +} + +void SVGDocumentImpl::syncCachedMatrices() +{ + if(rootElement()) + { + SVGMatrixImpl *parentMatrix = SVGSVGElementImpl::createSVGMatrix(); + rootElement()->checkCachedScreenCTM(parentMatrix); + parentMatrix->deref(); + } +} + +void SVGDocumentImpl::executeScriptsRecursive(DOM::Node start) +{ + DOM::Node node = start.firstChild(); + + for(; !node.isNull(); node = node.nextSibling()) + { + SVGElementImpl *element = getElementFromHandle(node.handle()); + + SVGContainerImpl *container = dynamic_cast<SVGContainerImpl *>(element); + if(container) + executeScriptsRecursive(node); + + SVGScriptElementImpl *script = dynamic_cast<SVGScriptElementImpl *>(element); + + if(script) + script->executeScript(DOM::Node()); + } +} + +bool SVGDocumentImpl::executeScriptsRecursiveCheck(DOM::Node start) +{ + bool test = true; + + DOM::Node node = start.firstChild(); + + for(; !node.isNull(); node = node.nextSibling()) + { + SVGElementImpl *element = getElementFromHandle(node.handle()); + + SVGContainerImpl *container = dynamic_cast<SVGContainerImpl *>(element); + if(container) + { + if(!executeScriptsRecursiveCheck(node)) + return false; + } + + SVGScriptElementImpl *script = dynamic_cast<SVGScriptElementImpl *>(element); + + if(script) + { + if(!script->canExecuteScript()) + { + test = false; + break; + } + } + } + + return test; +} + +void SVGDocumentImpl::executeScripts() +{ + bool test = executeScriptsRecursiveCheck(*rootElement()); + + if(!test) + TQTimer::singleShot(50, this, TQT_SLOT(executeScripts())); + else + { + executeScriptsRecursive(*rootElement()); + + // mop: only rerender if an loadevent has been found + if(dispatchRecursiveEvent(SVGEvent::LOAD_EVENT, lastChild())) + m_canvas->update(); + } +} + +// Dispatches a non-cancelable, non-bubbles event to every child +bool SVGDocumentImpl::dispatchRecursiveEvent(SVGEvent::EventId id, DOM::Node start) +{ + bool eventExecuted = false; + + // Iterate the tree, backwards, and dispatch the event to every child + DOM::Node node = start; + for(; !node.isNull(); node = node.previousSibling()) + { + SVGElementImpl *element = getElementFromHandle(node.handle()); + + if(element && element->hasChildNodes()) + { + // Dispatch to all children + eventExecuted = dispatchRecursiveEvent(id, element->lastChild()) ? true : eventExecuted; + + // Dispatch, locally + if(element->hasEventListener(id, true)) + { + element->dispatchEvent(id, false, false); + eventExecuted = true; + } + } + else if(element && element->hasEventListener(id, true)) + { + element->dispatchEvent(id, false, false); + eventExecuted = true; + } + } + + return eventExecuted; +} + +SVGEventListener *SVGDocumentImpl::createEventListener(DOM::DOMString type) +{ + return m_ecmaEngine->createEventListener(type); +} + +SVGElementImpl *SVGDocumentImpl::recursiveSearch(DOM::Node start, const DOM::DOMString &id) +{ + DOM::Node node = start.firstChild(); + for(; !node.isNull(); node = node.nextSibling()) + { + SVGElementImpl *test = getElementFromHandle(node.handle()); + + // Look in containers + SVGContainerImpl *container = dynamic_cast<SVGContainerImpl *>(test); + if(container) + { + SVGElementImpl *found = recursiveSearch(node, id); + if(found) + return found; + } + + // Look in SVGSVGElementImpl's + SVGSVGElementImpl *svgtest = dynamic_cast<SVGSVGElementImpl *>(test); + if(svgtest) + { + SVGElementImpl *element = svgtest->getElementById(id); + if(element) + return element; + } + } + + return 0; +} + +/* +@namespace KSVG +@begin SVGDocumentImpl::s_hashTable 9 + title SVGDocumentImpl::Title DontDelete|ReadOnly + referrer SVGDocumentImpl::Referrer DontDelete|ReadOnly + domain SVGDocumentImpl::Domain DontDelete|ReadOnly + URL SVGDocumentImpl::Url DontDelete|ReadOnly + doctype SVGDocumentImpl::DocType DontDelete|ReadOnly + implementation SVGDocumentImpl::Implementation DontDelete|ReadOnly + rootElement SVGDocumentImpl::RootElement DontDelete|ReadOnly + documentElement SVGDocumentImpl::DocumentElement DontDelete|ReadOnly +@end +@namespace KSVG +@begin SVGDocumentImplProto::s_hashTable 7 + createTextNode SVGDocumentImpl::CreateTextNode DontDelete|Function 1 + createElement SVGDocumentImpl::CreateElement DontDelete|Function 1 + createElementNS SVGDocumentImpl::CreateElementNS DontDelete|Function 2 + getElementById SVGDocumentImpl::GetElementById DontDelete|Function 1 + getElementsByTagName SVGDocumentImpl::GetElementsByTagName DontDelete|Function 1 + getElementsByTagNameNS SVGDocumentImpl::GetElementsByTagNameNS DontDelete|Function 2 +@end +*/ + +KSVG_IMPLEMENT_PROTOTYPE("SVGDocument", SVGDocumentImplProto, SVGDocumentImplProtoFunc) + +Value SVGDocumentImpl::getValueProperty(ExecState *exec, int token) const +{ + switch(token) + { + case Title: + return String(title().string()); + case Referrer: + return String(referrer().string()); + case Domain: + return String(domain().string()); + case Url: + return String(URL().string()); + case DocType: + return getDOMNode(exec, doctype()); + case Implementation: + return (new SVGDOMDOMImplementationBridge(implementation()))->cache(exec); + case RootElement: + case DocumentElement: + return m_rootElement->cache(exec); + default: + kdWarning() << "Unhandled token in " << k_funcinfo << " : " << token << endl; + return Undefined(); + } +} + +Value SVGDocumentImplProtoFunc::call(ExecState *exec, Object &thisObj, const List &args) +{ + KSVG_CHECK_THIS(SVGDocumentImpl) + + switch(id) + { + case SVGDocumentImpl::CreateTextNode: + return getDOMNode(exec, obj->createTextNode(args[0].toString(exec).string())); + case SVGDocumentImpl::CreateElement: + case SVGDocumentImpl::CreateElementNS: + { + SVGElementImpl *newElement = 0; + + if(id == SVGDocumentImpl::CreateElement) + newElement = obj->createElement(args[0].toString(exec).qstring(), static_cast<DOM::Document *>(obj)->createElement(args[0].toString(exec).string()), obj); + else if(id == SVGDocumentImpl::CreateElementNS) + newElement = obj->createElement(args[1].toString(exec).qstring(), static_cast<DOM::Document *>(obj)->createElementNS(args[0].toString(exec).string(), args[1].toString(exec).string()), obj); + + newElement->setOwnerSVGElement(obj->rootElement()); // FIXME: Correct in all situations? + newElement->setViewportElement(obj->rootElement()); + newElement->setAttributes(); + + return getDOMNode(exec, *newElement); + } + case SVGDocumentImpl::GetElementById: + { + Value ret; + + SVGElementImpl *element = obj->rootElement()->getElementById(args[0].toString(exec).string()); + + if(element) + ret = getDOMNode(exec, *element); + else + { + element = obj->recursiveSearch(*obj, args[0].toString(exec).string()); + if(!element) + return Null(); + + ret = getDOMNode(exec, *element); + } + + return ret; + } + case SVGDocumentImpl::GetElementsByTagName: + return (new SVGDOMNodeListBridge(obj->getElementsByTagName(args[0].toString(exec).string())))->cache(exec); + case SVGDocumentImpl::GetElementsByTagNameNS: + return (new SVGDOMNodeListBridge(obj->getElementsByTagNameNS(args[0].toString(exec).string(), args[1].toString(exec).string())))->cache(exec); + default: + break; + } + + return KJS::Undefined(); +} + +SVGElementImpl *SVGDocumentImpl::getElementFromHandle(DOM::NodeImpl *handle) const +{ + return m_elemDict[handle]; +} + +void SVGDocumentImpl::addToElemDict(DOM::NodeImpl *handle, SVGElementImpl *obj) +{ + m_elemDict.insert(handle, obj); + + if(m_elemDict.count()>m_elemDict.size() && m_elemDictHashSizeIndex<numElemDictHashSizes-1) + { + // Increase the hash table size to maintain good lookup speed. + m_elemDictHashSizeIndex++; + m_elemDict.resize(elemDictHashSizes[m_elemDictHashSizeIndex]); + } +} + +void SVGDocumentImpl::removeFromElemDict(DOM::NodeImpl *handle) +{ + m_elemDict.remove(handle); +} + +SVGDocumentImpl *SVGDocumentImpl::getDocumentFromHandle(DOM::NodeImpl *handle) const +{ + return m_documentDict[handle]; +} + +void SVGDocumentImpl::addToDocumentDict(DOM::NodeImpl *handle, SVGDocumentImpl *obj) +{ + m_documentDict.insert(handle, obj); +} + +SVGElementImpl *SVGDocumentImpl::getElementByIdRecursive(SVGSVGElementImpl *start, const DOM::DOMString &elementId, bool dontSearch) +{ + SVGElementImpl *element = 0; + + // #1 Look in passed SVGSVGElementImpl + if(start) + { + element = start->getElementById(elementId); + + if(element) + return element; + } + + // #2 Search in all child SVGSVGElementImpl's + element = recursiveSearch(*this, elementId); + + if(element) + return element; + + // #3 Search in other documents + if(!dontSearch) + { + TQPtrDictIterator<SVGDocumentImpl> it(m_documentDict); + for(; it.current(); ++it) + { + SVGElementImpl *temp = it.current()->getElementByIdRecursive(0, elementId, true); + if(temp) + return temp; + } + } + + return element; +} |