/** * @file * SVG implementation */ /* * Authors: * Tomasz Boczkowski * Jon A. Cruz * * Copyright (C) 2014 Tomasz Boczkowski * * Released under GNU GPL, read the file 'COPYING' for more information */ #include #include #include <2geom/transforms.h> #include #include "svg/svg.h" #include "display/cairo-utils.h" #include "display/drawing-context.h" #include "display/drawing-surface.h" #include "display/drawing.h" #include "display/drawing-pattern.h" #include "attributes.h" #include "document-private.h" #include "sp-hatch.h" #include "sp-hatch-path.h" SPHatch::SPHatch() : SPPaintServer(), href(), ref(NULL), // avoiding 'this' in initializer list _hatchUnits(UNITS_OBJECTBOUNDINGBOX), _hatchUnits_set(false), _hatchContentUnits(UNITS_USERSPACEONUSE), _hatchContentUnits_set(false), _hatchTransform(Geom::identity()), _hatchTransform_set(false), _x(), _y(), _pitch(), _rotate(), _modified_connection(), _display() { ref = new SPHatchReference(this); ref->changedSignal().connect(sigc::mem_fun(this, &SPHatch::_onRefChanged)); // TODO check that these should start already as unset: _x.unset(); _y.unset(); _pitch.unset(); _rotate.unset(); } SPHatch::~SPHatch() { } void SPHatch::build(SPDocument* doc, Inkscape::XML::Node* repr) { SPPaintServer::build(doc, repr); readAttr("hatchUnits"); readAttr("hatchContentUnits"); readAttr("hatchTransform"); readAttr("x"); readAttr("y"); readAttr("pitch"); readAttr("rotate"); readAttr("xlink:href"); readAttr( "style" ); // Register ourselves doc->addResource("hatch", this); } void SPHatch::release() { if (document) { // Unregister ourselves document->removeResource("hatch", this); } std::vector children(hatchPaths()); for (ViewIterator view_iter = _display.begin(); view_iter != _display.end(); ++view_iter) { for (ChildIterator child_iter = children.begin(); child_iter != children.end(); ++child_iter) { SPHatchPath *child = *child_iter; child->hide(view_iter->key); } delete view_iter->arenaitem; view_iter->arenaitem = NULL; } if (ref) { _modified_connection.disconnect(); ref->detach(); delete ref; ref = NULL; } SPPaintServer::release(); } void SPHatch::child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref) { SPObject::child_added(child, ref); SPHatchPath *path_child = dynamic_cast(document->getObjectByRepr(child)); if (path_child) { for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { Geom::OptInterval extents = _calculateStripExtents(iter->bbox); Inkscape::DrawingItem *ac = path_child->show(iter->arenaitem->drawing(), iter->key, extents); path_child->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); if (ac) { iter->arenaitem->prependChild(ac); } } } //FIXME: notify all hatches that refer to this child set } void SPHatch::set(unsigned int key, const gchar* value) { switch (key) { case SP_ATTR_HATCHUNITS: if (value) { if (!strcmp(value, "userSpaceOnUse")) { _hatchUnits = UNITS_USERSPACEONUSE; } else { _hatchUnits = UNITS_OBJECTBOUNDINGBOX; } _hatchUnits_set = true; } else { _hatchUnits_set = false; } requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_HATCHCONTENTUNITS: if (value) { if (!strcmp(value, "userSpaceOnUse")) { _hatchContentUnits = UNITS_USERSPACEONUSE; } else { _hatchContentUnits = UNITS_OBJECTBOUNDINGBOX; } _hatchContentUnits_set = true; } else { _hatchContentUnits_set = false; } requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_HATCHTRANSFORM: { Geom::Affine t; if (value && sp_svg_transform_read(value, &t)) { _hatchTransform = t; _hatchTransform_set = true; } else { _hatchTransform = Geom::identity(); _hatchTransform_set = false; } requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; } case SP_ATTR_X: _x.readOrUnset(value); requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_Y: _y.readOrUnset(value); requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_PITCH: _pitch.readOrUnset(value); requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_ROTATE: _rotate.readOrUnset(value); requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_XLINK_HREF: if (value && href == value) { // Href unchanged, do nothing. } else { href.clear(); if (value) { // First, set the href field; it's only used in the "unchanged" check above. href = value; // Now do the attaching, which emits the changed signal. if (value) { try { ref->attach(Inkscape::URI(value)); } catch (Inkscape::BadURIException &e) { g_warning("%s", e.what()); ref->detach(); } } else { ref->detach(); } } } requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; default: if (SP_ATTRIBUTE_IS_CSS(key)) { style->readFromObject( this ); requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); } else { SPPaintServer::set(key, value); } break; } } bool SPHatch::_hasHatchPatchChildren(SPHatch const *hatch) { bool matched = false; for (SPObject const *child = hatch->firstChild(); child && !matched; child = child->getNext() ) { SPHatchPath const *hatchPath = dynamic_cast(child); if (hatchPath) { matched = true; } } return matched; } std::vector SPHatch::hatchPaths() { std::vector list; SPHatch *src = chase_hrefs(this, sigc::ptr_fun(&_hasHatchPatchChildren)); if (src) { for (SPObject *child = src->firstChild(); child; child = child->getNext()) { SPHatchPath *hatchPath = dynamic_cast(child); if (hatchPath) { list.push_back(hatchPath); } } } return list; } std::vector SPHatch::hatchPaths() const { std::vector list; SPHatch const *src = chase_hrefs(this, sigc::ptr_fun(&_hasHatchPatchChildren)); if (src) { for (SPObject const *child = src->firstChild(); child; child = child->getNext()) { SPHatchPath const *hatchPath = dynamic_cast(child); if (hatchPath) { list.push_back(hatchPath); } } } return list; } // TODO: ::remove_child and ::order_changed handles - see SPPattern void SPHatch::update(SPCtx* ctx, unsigned int flags) { typedef std::list::iterator ViewIterator; if (flags & SP_OBJECT_MODIFIED_FLAG) { flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; } flags &= SP_OBJECT_MODIFIED_CASCADE; std::vector children(hatchPaths()); for (ChildIterator iter = children.begin(); iter != children.end(); ++iter) { SPHatchPath* child = *iter; sp_object_ref(child, NULL); for (ViewIterator view_iter = _display.begin(); view_iter != _display.end(); ++view_iter) { Geom::OptInterval strip_extents = _calculateStripExtents(view_iter->bbox); child->setStripExtents(view_iter->key, strip_extents); } if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { child->updateDisplay(ctx, flags); } sp_object_unref(child, NULL); } for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { _updateView(*iter); } } void SPHatch::modified(unsigned int flags) { if (flags & SP_OBJECT_MODIFIED_FLAG) { flags |= SP_OBJECT_PARENT_MODIFIED_FLAG; } flags &= SP_OBJECT_MODIFIED_CASCADE; std::vector children(hatchPaths()); for (ChildIterator iter = children.begin(); iter != children.end(); ++iter) { SPObject *child = *iter; sp_object_ref(child, NULL); if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { child->emitModified(flags); } sp_object_unref(child, NULL); } } void SPHatch::_onRefChanged(SPObject *old_ref, SPObject *ref) { typedef std::list::iterator ViewIterator; if (old_ref) { _modified_connection.disconnect(); } SPHatch *hatch = dynamic_cast(ref); if (hatch) { _modified_connection = ref->connectModified(sigc::mem_fun(this, &SPHatch::_onRefModified)); } if (!_hasHatchPatchChildren(this)) { SPHatch *old_shown = NULL; SPHatch *new_shown = NULL; std::vector oldhatchPaths; std::vector newhatchPaths; SPHatch *old_hatch = dynamic_cast(old_ref); if (old_hatch) { old_shown = old_hatch->rootHatch(); oldhatchPaths = old_shown->hatchPaths(); } if (hatch) { new_shown = hatch->rootHatch(); newhatchPaths = new_shown->hatchPaths(); } if (old_shown != new_shown) { for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { Geom::OptInterval extents = _calculateStripExtents(iter->bbox); for (ChildIterator child_iter = oldhatchPaths.begin(); child_iter != oldhatchPaths.end(); ++child_iter) { SPHatchPath *child = *child_iter; child->hide(iter->key); } for (ChildIterator child_iter = newhatchPaths.begin(); child_iter != newhatchPaths.end(); ++child_iter) { SPHatchPath *child = *child_iter; Inkscape::DrawingItem *cai = child->show(iter->arenaitem->drawing(), iter->key, extents); child->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); if (cai) { iter->arenaitem->appendChild(cai); } } } } } _onRefModified(ref, 0); } void SPHatch::_onRefModified(SPObject */*ref*/, guint /*flags*/) { requestModified(SP_OBJECT_MODIFIED_FLAG); // Conditional to avoid causing infinite loop if there's a cycle in the href chain. } SPHatch *SPHatch::rootHatch() { SPHatch *src = chase_hrefs(this, sigc::ptr_fun(&_hasHatchPatchChildren)); return src ? src : this; // document is broken, we can't get to root; but at least we can return pat which is supposedly a valid hatch } // Access functions that look up fields up the chain of referenced hatchs and return the first one which is set // FIXME: all of them must use chase_hrefs as children() and rootHatch() SPHatch::HatchUnits SPHatch::hatchUnits() const { HatchUnits units = _hatchUnits; for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) { if (pat_i->_hatchUnits_set) { units = pat_i->_hatchUnits; break; } } return units; } SPHatch::HatchUnits SPHatch::hatchContentUnits() const { HatchUnits units = _hatchContentUnits; for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) { if (pat_i->_hatchContentUnits_set) { units = pat_i->_hatchContentUnits; break; } } return units; } Geom::Affine const &SPHatch::hatchTransform() const { for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) { if (pat_i->_hatchTransform_set) { return pat_i->_hatchTransform; } } return _hatchTransform; } gdouble SPHatch::x() const { gdouble val = 0; for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) { if (pat_i->_x._set) { val = pat_i->_x.computed; break; } } return val; } gdouble SPHatch::y() const { gdouble val = 0; for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) { if (pat_i->_y._set) { val = pat_i->_y.computed; break; } } return val; } gdouble SPHatch::pitch() const { gdouble val = 0; for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) { if (pat_i->_pitch._set) { val = pat_i->_pitch.computed; break; } } return val; } gdouble SPHatch::rotate() const { gdouble val = 0; for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : NULL) { if (pat_i->_rotate._set) { val = pat_i->_rotate.computed; break; } } return val; } bool SPHatch::isValid() const { bool valid = false; if (pitch() > 0) { std::vector children(hatchPaths()); if (!children.empty()) { valid = true; for (ConstChildIterator iter = children.begin(); (iter != children.end()) && valid; ++iter) { SPHatchPath const *child = *iter; valid = child->isValid(); } } } return valid; } Inkscape::DrawingPattern *SPHatch::show(Inkscape::Drawing &drawing, unsigned int key, Geom::OptRect bbox) { Inkscape::DrawingPattern *ai = new Inkscape::DrawingPattern(drawing); //TODO: set some debug flag to see DrawingPattern _display.push_front(View(ai, key)); _display.front().bbox = bbox; std::vector children(hatchPaths()); Geom::OptInterval extents = _calculateStripExtents(bbox); for (ChildIterator iter = children.begin(); iter != children.end(); ++iter) { SPHatchPath *child = *iter; Inkscape::DrawingItem *cai = child->show(drawing, key, extents); if (cai) { ai->appendChild(cai); } } View& view = _display.front(); _updateView(view); return ai; } void SPHatch::hide(unsigned int key) { std::vector children(hatchPaths()); for (ChildIterator iter = children.begin(); iter != children.end(); ++iter) { SPHatchPath *child = *iter; child->hide(key); } for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { if (iter->key == key) { delete iter->arenaitem; _display.erase(iter); return; } } g_assert_not_reached(); } Geom::Interval SPHatch::bounds() const { Geom::Interval result; std::vector children(hatchPaths()); for (ConstChildIterator iter = children.begin(); iter != children.end(); ++iter) { SPHatchPath const *child = *iter; if (result.extent() == 0) { result = child->bounds(); } else { result |= child->bounds(); } } return result; } SPHatch::RenderInfo SPHatch::calculateRenderInfo(unsigned key) const { RenderInfo info; for (ConstViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { if (iter->key == key) { return _calculateRenderInfo(*iter); } } g_assert_not_reached(); return info; } void SPHatch::_updateView(View &view) { RenderInfo info = _calculateRenderInfo(view); //The rendering of hatch overflow is implemented by repeated drawing //of hatch paths over one strip. Within each iteration paths are moved by pitch value. //The movement progresses from right to left. This gives the same result //as drawing whole strips in left-to-right order. view.arenaitem->setChildTransform(info.child_transform); view.arenaitem->setPatternToUserTransform(info.pattern_to_user_transform); view.arenaitem->setTileRect(info.tile_rect); view.arenaitem->setStyle(style); view.arenaitem->setOverflow(info.overflow_initial_transform, info.overflow_steps, info.overflow_step_transform); } SPHatch::RenderInfo SPHatch::_calculateRenderInfo(View const &view) const { RenderInfo info; Geom::OptInterval extents = _calculateStripExtents(view.bbox); if (extents) { double tile_x = x(); double tile_y = y(); double tile_width = pitch(); double tile_height = extents->max() - extents->min(); double tile_rotate = rotate(); double tile_render_y = extents->min(); if (view.bbox && (hatchUnits() == UNITS_OBJECTBOUNDINGBOX)) { tile_x *= view.bbox->width(); tile_y *= view.bbox->height(); tile_width *= view.bbox->width(); tile_height *= view.bbox->height(); tile_render_y *= view.bbox->height(); } // Pattern size in hatch space Geom::Rect hatch_tile = Geom::Rect::from_xywh(0, tile_render_y, tile_width, tile_height); // Content to bbox Geom::Affine content2ps; if (view.bbox && (hatchContentUnits() == UNITS_OBJECTBOUNDINGBOX)) { content2ps = Geom::Affine(view.bbox->width(), 0.0, 0.0, view.bbox->height(), 0, 0); } // Tile (hatch space) to user. Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * Geom::Rotate::from_degrees(tile_rotate) * hatchTransform(); info.child_transform = content2ps; info.pattern_to_user_transform = ps2user; info.tile_rect = hatch_tile; if (style->overflow.computed == SP_CSS_OVERFLOW_VISIBLE) { Geom::Interval bounds = this->bounds(); gdouble pitch = this->pitch(); gdouble overflow_right_strip = floor(bounds.max() / pitch) * pitch; info.overflow_steps = ceil((overflow_right_strip - bounds.min()) / pitch) + 1; info.overflow_step_transform = Geom::Translate(pitch, 0.0); info.overflow_initial_transform = Geom::Translate(-overflow_right_strip, 0.0); } else { info.overflow_steps = 1; } } return info; } //calculates strip extents in content space Geom::OptInterval SPHatch::_calculateStripExtents(Geom::OptRect const &bbox) const { if (!bbox || (bbox->area() == 0)) { return Geom::OptInterval(); } else { double tile_x = x(); double tile_y = y(); double tile_rotate = rotate(); Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * Geom::Rotate::from_degrees(tile_rotate) * hatchTransform(); Geom::Affine user2ps = ps2user.inverse(); Geom::Interval extents; for (int i = 0; i < 4; ++i) { Geom::Point corner = bbox->corner(i); Geom::Point corner_ps = corner * user2ps; if (i == 0 || corner_ps.y() < extents.min()) { extents.setMin(corner_ps.y()); } if (i == 0 || corner_ps.y() > extents.max()) { extents.setMax(corner_ps.y()); } } if (hatchUnits() == UNITS_OBJECTBOUNDINGBOX) { extents /= bbox->height(); } return extents; } } cairo_pattern_t* SPHatch::pattern_new(cairo_t * /*base_ct*/, Geom::OptRect const &/*bbox*/, double /*opacity*/) { //this code should not be used //it is however required by the fact that SPPaintServer::hatch_new is pure virtual return cairo_pattern_create_rgb(0.5, 0.5, 1.0); } void SPHatch::setBBox(unsigned int key, Geom::OptRect const &bbox) { for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) { if (iter->key == key) { iter->bbox = bbox; break; } } } // SPHatch::RenderInfo::RenderInfo() : child_transform(), pattern_to_user_transform(), tile_rect(), overflow_steps(0), overflow_step_transform(), overflow_initial_transform() { } SPHatch::RenderInfo::~RenderInfo() { } // SPHatch::View::View(Inkscape::DrawingPattern *arenaitem, int key) : arenaitem(arenaitem), bbox(), key(key) { } SPHatch::View::~View() { // remember, do not delete arenaitem here arenaitem = NULL; } /* 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 :