// SPDX-License-Identifier: GPL-2.0-or-later /** \file * SVG \ implementation. */ /* * Authors: * Lauris Kaplinski * Jon A. Cruz * Abhishek Sharma * * Copyright (C) 1999-2002 Lauris Kaplinski * Copyright (C) 2000-2001 Ximian, Inc. * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include #include <2geom/transforms.h> #include "attributes.h" #include "print.h" #include "document.h" #include "inkscape-version.h" #include "sp-defs.h" #include "sp-namedview.h" #include "sp-root.h" #include "sp-use.h" #include "display/drawing-group.h" #include "svg/svg.h" #include "xml/repr.h" #include "util/units.h" SPRoot::SPRoot() : SPGroup(), SPViewBox() { this->onload = nullptr; static Inkscape::Version const zero_version(0, 0); sp_version_from_string(SVG_VERSION, &this->original.svg); this->version.svg = zero_version; this->original.svg = zero_version; this->version.inkscape = zero_version; this->original.inkscape = zero_version; this->unset_x_and_y(); this->width.unset(SVGLength::PERCENT, 1.0, 1.0); this->height.unset(SVGLength::PERCENT, 1.0, 1.0); this->defs = nullptr; } SPRoot::~SPRoot() = default; void SPRoot::unset_x_and_y() { this->x.unset(SVGLength::PERCENT, 0.0, 0.0); // Ignored for root SVG element this->y.unset(SVGLength::PERCENT, 0.0, 0.0); } void SPRoot::build(SPDocument *document, Inkscape::XML::Node *repr) { //XML Tree being used directly here while it shouldn't be. if (!this->getRepr()->attribute("version")) { repr->setAttribute("version", SVG_VERSION); } this->readAttr(SPAttr::VERSION); this->readAttr(SPAttr::INKSCAPE_VERSION); /* It is important to parse these here, so objects will have viewport build-time */ this->readAttr(SPAttr::X); this->readAttr(SPAttr::Y); this->readAttr(SPAttr::WIDTH); this->readAttr(SPAttr::HEIGHT); this->readAttr(SPAttr::VIEWBOX); this->readAttr(SPAttr::PRESERVEASPECTRATIO); this->readAttr(SPAttr::ONLOAD); SPGroup::build(document, repr); // Search for first node for (auto& o: children) { if (SP_IS_DEFS(&o)) { this->defs = SP_DEFS(&o); break; } } // clear transform, if any was read in - SVG does not allow transform= on this->transform = Geom::identity(); } void SPRoot::release() { this->defs = nullptr; SPGroup::release(); } void SPRoot::set(SPAttr key, const gchar *value) { switch (key) { case SPAttr::VERSION: if (!sp_version_from_string(value, &this->version.svg)) { this->version.svg = this->original.svg; } break; case SPAttr::INKSCAPE_VERSION: if (!sp_version_from_string(value, &this->version.inkscape)) { this->version.inkscape = this->original.inkscape; } break; case SPAttr::X: /* Valid for non-root SVG elements; ex, em not handled correctly. */ if (!this->x.read(value)) { this->x.unset(SVGLength::PERCENT, 0.0, 0.0); } /* fixme: I am almost sure these do not require viewport flag (Lauris) */ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); break; case SPAttr::Y: /* Valid for non-root SVG elements; ex, em not handled correctly. */ if (!this->y.read(value)) { this->y.unset(SVGLength::PERCENT, 0.0, 0.0); } /* fixme: I am almost sure these do not require viewport flag (Lauris) */ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); break; case SPAttr::WIDTH: if (!this->width.read(value) || !(this->width.computed > 0.0)) { this->width.unset(SVGLength::PERCENT, 1.0, 1.0); } this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); break; case SPAttr::HEIGHT: if (!this->height.read(value) || !(this->height.computed > 0.0)) { this->height.unset(SVGLength::PERCENT, 1.0, 1.0); } this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); break; case SPAttr::VIEWBOX: set_viewBox( value ); this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); break; case SPAttr::PRESERVEASPECTRATIO: set_preserveAspectRatio( value ); this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); break; case SPAttr::ONLOAD: this->onload = (char *) value; break; default: /* Pass the set event to the parent */ SPGroup::set(key, value); break; } } void SPRoot::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { SPGroup::child_added(child, ref); SPObject *co = this->document->getObjectByRepr(child); // NOTE: some XML nodes do not have corresponding SP objects, // for instance inkscape:clipboard used in the clipboard code. // See LP bug #1227827 //g_assert (co != NULL || !strcmp("comment", child->name())); // comment repr node has no object if (co && SP_IS_DEFS(co)) { // We search for first node - it is not beautiful, but works for (auto& c: children) { if (SP_IS_DEFS(&c)) { this->defs = SP_DEFS(&c); break; } } } } void SPRoot::remove_child(Inkscape::XML::Node *child) { if (this->defs && (this->defs->getRepr() == child)) { SPObject *iter = nullptr; // We search for first remaining node - it is not beautiful, but works for (auto& child: children) { iter = &child; if (SP_IS_DEFS(iter) && (SPDefs *)iter != this->defs) { this->defs = (SPDefs *)iter; break; } } if (!iter) { /* we should probably create a new here? */ this->defs = nullptr; } } SPGroup::remove_child(child); } void SPRoot::setRootDimensions() { /* * This is the root SVG element: * * x, y, width, and height apply to positioning the SVG element inside a parent. * For the root SVG in Inkscape there is no parent, thus special rules apply: * If width, height not set, width = 100%, height = 100% (as always). * If width and height are in percent, they are percent of viewBox width/height. * If width, height, and viewBox are not set... pick "random" width/height. * x, y are ignored. * initial viewport = (0 0 width height) */ if( this->viewBox_set ) { if( this->width._set ) { // Check if this is necessary if (this->width.unit == SVGLength::PERCENT) { this->width.computed = this->width.value * this->viewBox.width(); } } else { this->width.set( SVGLength::PX, this->viewBox.width(), this->viewBox.width() ); } if( this->height._set ) { if (this->height.unit == SVGLength::PERCENT) { this->height.computed = this->height.value * this->viewBox.height(); } } else { this->height.set(SVGLength::PX, this->viewBox.height(), this->viewBox.height() ); } } else { if( !this->width._set || this->width.unit == SVGLength::PERCENT) { this->width.set( SVGLength::PX, 300, 300 ); // CSS/SVG default } if( !this->height._set || this->height.unit == SVGLength::PERCENT) { this->height.set( SVGLength::PX, 150, 150 ); // CSS/SVG default } } // Ignore x, y values for root element this->unset_x_and_y(); } void SPRoot::update(SPCtx *ctx, guint flags) { SPItemCtx const *ictx = (SPItemCtx const *) ctx; if( !this->parent ) { this->setRootDimensions(); } // Calculate x, y, width, height from parent/initial viewport this->calcDimsFromParentViewport(ictx, false, cloned ? dynamic_cast(parent) : nullptr); // std::cout << "SPRoot::update: final:" // << " x: " << x.computed // << " y: " << y.computed // << " width: " << width.computed // << " height: " << height.computed << std::endl; // Calculate new viewport SPItemCtx rctx = *ictx; rctx.viewport = Geom::Rect::from_xywh( this->x.computed, this->y.computed, this->width.computed, this->height.computed ); rctx = get_rctx( &rctx, Inkscape::Util::Quantity::convert(1, this->document->getDisplayUnit(), "px") ); /* And invoke parent method */ SPGroup::update((SPCtx *) &rctx, flags); /* As last step set additional transform of drawing group */ for (SPItemView *v = this->display; v != nullptr; v = v->next) { Inkscape::DrawingGroup *g = dynamic_cast(v->arenaitem); g->setChildTransform(this->c2p); } } void SPRoot::modified(unsigned int flags) { SPGroup::modified(flags); if (!this->parent && (flags & SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { // Size of viewport has changed. document->getNamedView()->updateViewPort(); } } Inkscape::XML::Node *SPRoot::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { repr = xml_doc->createElement("svg:svg"); } /* Only update version string on successful write to file. This is handled by 'file_save()'. * if (flags & SP_OBJECT_WRITE_EXT) { * repr->setAttribute("inkscape:version", Inkscape::version_string); * } */ if (!repr->attribute("version")) { gchar *myversion = sp_version_to_string(this->version.svg); repr->setAttribute("version", myversion); g_free(myversion); } if (fabs(this->x.computed) > 1e-9) { repr->setAttributeSvgDouble("x", this->x.computed); } if (fabs(this->y.computed) > 1e-9) { repr->setAttributeSvgDouble("y", this->y.computed); } /* Unlike all other SPObject, here we want to preserve absolute units too (and only here, * according to the recommendation in http://www.w3.org/TR/SVG11/coords.html#Units). */ repr->setAttribute("width", sp_svg_length_write_with_units(this->width)); repr->setAttribute("height", sp_svg_length_write_with_units(this->height)); this->write_viewBox(repr); this->write_preserveAspectRatio(repr); SPGroup::write(xml_doc, repr, flags); return repr; } Inkscape::DrawingItem *SPRoot::show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags) { Inkscape::DrawingItem *ai = SPGroup::show(drawing, key, flags); if (ai) { Inkscape::DrawingGroup *g = dynamic_cast(ai); g->setChildTransform(this->c2p); } // Uncomment to print out XML tree // getRepr()->recursivePrintTree(0); // Uncomment to print out SP Object tree // recursivePrintTree(0); // Uncomment to print out Display Item tree // ai->recursivePrintTree(0); return ai; } void SPRoot::print(SPPrintContext *ctx) { ctx->bind(this->c2p, 1.0); SPGroup::print(ctx); ctx->release(); } const char *SPRoot::typeName() const { return "image"; } const char *SPRoot::displayName() const { return "SVG"; // Do not translate } /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :