1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3  * @file
4  * SVG <hatch> implementation
5  */
6 /*
7  * Authors:
8  *   Tomasz Boczkowski <penginsbacon@gmail.com>
9  *   Jon A. Cruz <jon@joncruz.org>
10  *
11  * Copyright (C) 2014 Tomasz Boczkowski
12  *
13  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14  */
15 
16 #include "sp-hatch.h"
17 
18 #include <cstring>
19 #include <string>
20 
21 #include <2geom/transforms.h>
22 #include <sigc++/functors/mem_fun.h>
23 
24 #include "attributes.h"
25 #include "bad-uri-exception.h"
26 #include "document.h"
27 
28 #include "display/cairo-utils.h"
29 #include "display/drawing-context.h"
30 #include "display/drawing-surface.h"
31 #include "display/drawing.h"
32 #include "display/drawing-pattern.h"
33 
34 #include "sp-defs.h"
35 #include "sp-hatch-path.h"
36 #include "sp-item.h"
37 
38 #include "svg/svg.h"
39 
SPHatch()40 SPHatch::SPHatch()
41     : SPPaintServer(),
42       href(),
43       ref(nullptr), // avoiding 'this' in initializer list
44       _hatchUnits(UNITS_OBJECTBOUNDINGBOX),
45       _hatchUnits_set(false),
46       _hatchContentUnits(UNITS_USERSPACEONUSE),
47       _hatchContentUnits_set(false),
48       _hatchTransform(Geom::identity()),
49       _hatchTransform_set(false),
50       _x(),
51       _y(),
52       _pitch(),
53       _rotate(),
54       _modified_connection(),
55       _display()
56 {
57     ref = new SPHatchReference(this);
58     ref->changedSignal().connect(sigc::mem_fun(this, &SPHatch::_onRefChanged));
59 
60     // TODO check that these should start already as unset:
61     _x.unset();
62     _y.unset();
63     _pitch.unset();
64     _rotate.unset();
65 }
66 
67 SPHatch::~SPHatch() = default;
68 
build(SPDocument * doc,Inkscape::XML::Node * repr)69 void SPHatch::build(SPDocument* doc, Inkscape::XML::Node* repr)
70 {
71     SPPaintServer::build(doc, repr);
72 
73     readAttr(SPAttr::HATCHUNITS);
74     readAttr(SPAttr::HATCHCONTENTUNITS);
75     readAttr(SPAttr::HATCHTRANSFORM);
76     readAttr(SPAttr::X);
77     readAttr(SPAttr::Y);
78     readAttr(SPAttr::PITCH);
79     readAttr(SPAttr::ROTATE);
80     readAttr(SPAttr::XLINK_HREF);
81     readAttr(SPAttr::STYLE);
82 
83     // Register ourselves
84     doc->addResource("hatch", this);
85 }
86 
release()87 void SPHatch::release()
88 {
89     if (document) {
90         // Unregister ourselves
91         document->removeResource("hatch", this);
92     }
93 
94     std::vector<SPHatchPath *> children(hatchPaths());
95     for (auto & view_iter : _display) {
96         for (auto child : children) {
97             child->hide(view_iter.key);
98         }
99         delete view_iter.arenaitem;
100         view_iter.arenaitem = nullptr;
101     }
102 
103     if (ref) {
104         _modified_connection.disconnect();
105         ref->detach();
106         delete ref;
107         ref = nullptr;
108     }
109 
110     SPPaintServer::release();
111 }
112 
child_added(Inkscape::XML::Node * child,Inkscape::XML::Node * ref)113 void SPHatch::child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref)
114 {
115     SPObject::child_added(child, ref);
116 
117     SPHatchPath *path_child = dynamic_cast<SPHatchPath *>(document->getObjectByRepr(child));
118 
119     if (path_child) {
120         for (auto & iter : _display) {
121             Geom::OptInterval extents = _calculateStripExtents(iter.bbox);
122             Inkscape::DrawingItem *ac = path_child->show(iter.arenaitem->drawing(), iter.key, extents);
123 
124             path_child->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
125             if (ac) {
126                 iter.arenaitem->prependChild(ac);
127             }
128         }
129     }
130     //FIXME: notify all hatches that refer to this child set
131 }
132 
set(SPAttr key,const gchar * value)133 void SPHatch::set(SPAttr key, const gchar* value)
134 {
135     switch (key) {
136     case SPAttr::HATCHUNITS:
137         if (value) {
138             if (!strcmp(value, "userSpaceOnUse")) {
139                 _hatchUnits = UNITS_USERSPACEONUSE;
140             } else {
141                 _hatchUnits = UNITS_OBJECTBOUNDINGBOX;
142             }
143 
144             _hatchUnits_set = true;
145         } else {
146             _hatchUnits_set = false;
147         }
148 
149         requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
150         break;
151 
152     case SPAttr::HATCHCONTENTUNITS:
153         if (value) {
154             if (!strcmp(value, "userSpaceOnUse")) {
155                 _hatchContentUnits = UNITS_USERSPACEONUSE;
156             } else {
157                 _hatchContentUnits = UNITS_OBJECTBOUNDINGBOX;
158             }
159 
160             _hatchContentUnits_set = true;
161         } else {
162             _hatchContentUnits_set = false;
163         }
164 
165         requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
166         break;
167 
168     case SPAttr::HATCHTRANSFORM: {
169         Geom::Affine t;
170 
171         if (value && sp_svg_transform_read(value, &t)) {
172             _hatchTransform = t;
173             _hatchTransform_set = true;
174         } else {
175             _hatchTransform = Geom::identity();
176             _hatchTransform_set = false;
177         }
178 
179         requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
180         break;
181     }
182     case SPAttr::X:
183         _x.readOrUnset(value);
184         requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
185         break;
186 
187     case SPAttr::Y:
188         _y.readOrUnset(value);
189         requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
190         break;
191 
192     case SPAttr::PITCH:
193         _pitch.readOrUnset(value);
194         requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
195         break;
196 
197     case SPAttr::ROTATE:
198         _rotate.readOrUnset(value);
199         requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
200         break;
201 
202     case SPAttr::XLINK_HREF:
203         if (value && href == value) {
204             // Href unchanged, do nothing.
205         } else {
206             href.clear();
207 
208             if (value) {
209                 // First, set the href field; it's only used in the "unchanged" check above.
210                 href = value;
211                 // Now do the attaching, which emits the changed signal.
212                 if (value) {
213                     try {
214                         ref->attach(Inkscape::URI(value));
215                     } catch (Inkscape::BadURIException &e) {
216                         g_warning("%s", e.what());
217                         ref->detach();
218                     }
219                 } else {
220                     ref->detach();
221                 }
222             }
223         }
224         requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
225         break;
226 
227     default:
228         if (SP_ATTRIBUTE_IS_CSS(key)) {
229             style->clear(key);
230             requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
231         } else {
232             SPPaintServer::set(key, value);
233         }
234         break;
235     }
236 }
237 
_hasHatchPatchChildren(SPHatch const * hatch)238 bool SPHatch::_hasHatchPatchChildren(SPHatch const *hatch)
239 {
240     for (auto& child: hatch->children) {
241         SPHatchPath const *hatchPath = dynamic_cast<SPHatchPath const *>(&child);
242         if (hatchPath) {
243             return true;
244         }
245     }
246     return false;
247 }
248 
hatchPaths()249 std::vector<SPHatchPath*> SPHatch::hatchPaths()
250 {
251     std::vector<SPHatchPath*> list;
252     SPHatch *src = chase_hrefs<SPHatch>(this, sigc::ptr_fun(&_hasHatchPatchChildren));
253 
254     if (src) {
255         for (auto& child: src->children) {
256             SPHatchPath *hatchPath = dynamic_cast<SPHatchPath *>(&child);
257             if (hatchPath) {
258                 list.push_back(hatchPath);
259             }
260         }
261     }
262     return list;
263 }
264 
hatchPaths() const265 std::vector<SPHatchPath const*> SPHatch::hatchPaths() const
266 {
267     std::vector<SPHatchPath const*> list;
268     SPHatch const *src = chase_hrefs<SPHatch const>(this, sigc::ptr_fun(&_hasHatchPatchChildren));
269 
270     if (src) {
271         for (auto& child: src->children) {
272             SPHatchPath const *hatchPath = dynamic_cast<SPHatchPath const*>(&child);
273             if (hatchPath) {
274                 list.push_back(hatchPath);
275             }
276         }
277     }
278     return list;
279 }
280 
281 // TODO: ::remove_child and ::order_changed handles - see SPPattern
282 
283 
update(SPCtx * ctx,unsigned int flags)284 void SPHatch::update(SPCtx* ctx, unsigned int flags)
285 {
286     if (flags & SP_OBJECT_MODIFIED_FLAG) {
287         flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
288     }
289 
290     flags &= SP_OBJECT_MODIFIED_CASCADE;
291 
292     std::vector<SPHatchPath *> children(hatchPaths());
293 
294     for (auto child : children) {
295         sp_object_ref(child, nullptr);
296 
297         for (auto & view_iter : _display) {
298             Geom::OptInterval strip_extents = _calculateStripExtents(view_iter.bbox);
299             child->setStripExtents(view_iter.key, strip_extents);
300         }
301 
302         if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
303 
304             child->updateDisplay(ctx, flags);
305         }
306 
307         sp_object_unref(child, nullptr);
308     }
309 
310     for (auto & iter : _display) {
311         _updateView(iter);
312     }
313 }
314 
modified(unsigned int flags)315 void SPHatch::modified(unsigned int flags)
316 {
317     if (flags & SP_OBJECT_MODIFIED_FLAG) {
318         flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
319     }
320 
321     flags &= SP_OBJECT_MODIFIED_CASCADE;
322 
323     std::vector<SPHatchPath *> children(hatchPaths());
324 
325     for (auto child : children) {
326         sp_object_ref(child, nullptr);
327 
328         if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
329             child->emitModified(flags);
330         }
331 
332         sp_object_unref(child, nullptr);
333     }
334 }
335 
_onRefChanged(SPObject * old_ref,SPObject * ref)336 void SPHatch::_onRefChanged(SPObject *old_ref, SPObject *ref)
337 {
338     if (old_ref) {
339         _modified_connection.disconnect();
340     }
341 
342     SPHatch *hatch = dynamic_cast<SPHatch *>(ref);
343     if (hatch) {
344         _modified_connection = ref->connectModified(sigc::mem_fun(this, &SPHatch::_onRefModified));
345     }
346 
347     if (!_hasHatchPatchChildren(this)) {
348         SPHatch *old_shown = nullptr;
349         SPHatch *new_shown = nullptr;
350         std::vector<SPHatchPath *> oldhatchPaths;
351         std::vector<SPHatchPath *> newhatchPaths;
352 
353         SPHatch *old_hatch = dynamic_cast<SPHatch *>(old_ref);
354         if (old_hatch) {
355             old_shown = old_hatch->rootHatch();
356             oldhatchPaths = old_shown->hatchPaths();
357         }
358         if (hatch) {
359             new_shown = hatch->rootHatch();
360             newhatchPaths = new_shown->hatchPaths();
361         }
362         if (old_shown != new_shown) {
363 
364             for (auto & iter : _display) {
365                 Geom::OptInterval extents = _calculateStripExtents(iter.bbox);
366 
367                 for (auto child : oldhatchPaths) {
368                     child->hide(iter.key);
369                 }
370                 for (auto child : newhatchPaths) {
371                     Inkscape::DrawingItem *cai = child->show(iter.arenaitem->drawing(), iter.key, extents);
372                     child->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
373                     if (cai) {
374                         iter.arenaitem->appendChild(cai);
375                     }
376 
377                 }
378             }
379         }
380     }
381 
382     _onRefModified(ref, 0);
383 }
384 
_onRefModified(SPObject *,guint)385 void SPHatch::_onRefModified(SPObject */*ref*/, guint /*flags*/)
386 {
387     requestModified(SP_OBJECT_MODIFIED_FLAG);
388     // Conditional to avoid causing infinite loop if there's a cycle in the href chain.
389 }
390 
391 
rootHatch()392 SPHatch *SPHatch::rootHatch()
393 {
394     SPHatch *src = chase_hrefs<SPHatch>(this, sigc::ptr_fun(&_hasHatchPatchChildren));
395     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
396 }
397 
398 // Access functions that look up fields up the chain of referenced hatchs and return the first one which is set
399 // FIXME: all of them must use chase_hrefs as children() and rootHatch()
400 
hatchUnits() const401 SPHatch::HatchUnits SPHatch::hatchUnits() const
402 {
403     HatchUnits units = _hatchUnits;
404     for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : nullptr) {
405         if (pat_i->_hatchUnits_set) {
406             units = pat_i->_hatchUnits;
407             break;
408         }
409     }
410     return units;
411 }
412 
hatchContentUnits() const413 SPHatch::HatchUnits SPHatch::hatchContentUnits() const
414 {
415     HatchUnits units = _hatchContentUnits;
416     for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : nullptr) {
417         if (pat_i->_hatchContentUnits_set) {
418             units = pat_i->_hatchContentUnits;
419             break;
420         }
421     }
422     return units;
423 }
424 
hatchTransform() const425 Geom::Affine const &SPHatch::hatchTransform() const
426 {
427     for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : nullptr) {
428         if (pat_i->_hatchTransform_set) {
429             return pat_i->_hatchTransform;
430         }
431     }
432     return _hatchTransform;
433 }
434 
x() const435 gdouble SPHatch::x() const
436 {
437     gdouble val = 0;
438     for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : nullptr) {
439         if (pat_i->_x._set) {
440             val = pat_i->_x.computed;
441             break;
442         }
443     }
444     return val;
445 }
446 
y() const447 gdouble SPHatch::y() const
448 {
449     gdouble val = 0;
450     for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : nullptr) {
451         if (pat_i->_y._set) {
452             val = pat_i->_y.computed;
453             break;
454         }
455     }
456     return val;
457 }
458 
pitch() const459 gdouble SPHatch::pitch() const
460 {
461     gdouble val = 0;
462     for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : nullptr) {
463         if (pat_i->_pitch._set) {
464             val = pat_i->_pitch.computed;
465             break;
466         }
467     }
468     return val;
469 }
470 
rotate() const471 gdouble SPHatch::rotate() const
472 {
473     gdouble val = 0;
474     for (SPHatch const *pat_i = this; pat_i; pat_i = (pat_i->ref) ? pat_i->ref->getObject() : nullptr) {
475         if (pat_i->_rotate._set) {
476             val = pat_i->_rotate.computed;
477             break;
478         }
479     }
480     return val;
481 }
482 
_countHrefs(SPObject * o) const483 guint SPHatch::_countHrefs(SPObject *o) const
484 {
485     if (!o)
486         return 1;
487 
488     guint i = 0;
489 
490     SPStyle *style = o->style;
491     if (style && style->fill.isPaintserver() && SP_IS_HATCH(SP_STYLE_FILL_SERVER(style)) &&
492         SP_HATCH(SP_STYLE_FILL_SERVER(style)) == this) {
493         i++;
494     }
495     if (style && style->stroke.isPaintserver() && SP_IS_HATCH(SP_STYLE_STROKE_SERVER(style)) &&
496         SP_HATCH(SP_STYLE_STROKE_SERVER(style)) == this) {
497         i++;
498     }
499 
500     for (auto &child : o->children) {
501         i += _countHrefs(&child);
502     }
503 
504     return i;
505 }
506 
clone_if_necessary(SPItem * item,const gchar * property)507 SPHatch *SPHatch::clone_if_necessary(SPItem *item, const gchar *property)
508 {
509     SPHatch *hatch = this;
510     if (hatch->href.empty() || hatch->hrefcount > _countHrefs(item)) {
511         Inkscape::XML::Document *xml_doc = document->getReprDoc();
512         Inkscape::XML::Node *defsrepr = document->getDefs()->getRepr();
513 
514         Inkscape::XML::Node *repr = xml_doc->createElement("svg:hatch");
515         repr->setAttribute("inkscape:collect", "always");
516         Glib::ustring parent_ref = Glib::ustring::compose("#%1", getRepr()->attribute("id"));
517         repr->setAttribute("xlink:href", parent_ref);
518 
519         defsrepr->addChild(repr, nullptr);
520         const gchar *child_id = repr->attribute("id");
521         SPObject *child = document->getObjectById(child_id);
522         g_assert(SP_IS_HATCH(child));
523 
524         hatch = SP_HATCH(child);
525 
526         Glib::ustring href = Glib::ustring::compose("url(#%1)", hatch->getRepr()->attribute("id"));
527 
528         SPCSSAttr *css = sp_repr_css_attr_new();
529         sp_repr_css_set_property(css, property, href.c_str());
530         sp_repr_css_change_recursive(item->getRepr(), css, "style");
531     }
532 
533     return hatch;
534 }
535 
transform_multiply(Geom::Affine postmul,bool set)536 void SPHatch::transform_multiply(Geom::Affine postmul, bool set)
537 {
538     if (set) {
539         _hatchTransform = postmul;
540     } else {
541         _hatchTransform = hatchTransform() * postmul;
542     }
543 
544     _hatchTransform_set = true;
545 
546     setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(_hatchTransform));
547 }
548 
isValid() const549 bool SPHatch::isValid() const
550 {
551     bool valid = false;
552 
553     if (pitch() > 0) {
554         std::vector<SPHatchPath const *> children(hatchPaths());
555         if (!children.empty()) {
556             valid = true;
557             for (ConstChildIterator iter = children.begin(); (iter != children.end()) && valid; ++iter) {
558                 SPHatchPath const *child = *iter;
559                 valid = child->isValid();
560             }
561         }
562     }
563 
564     return valid;
565 }
566 
show(Inkscape::Drawing & drawing,unsigned int key,Geom::OptRect bbox)567 Inkscape::DrawingPattern *SPHatch::show(Inkscape::Drawing &drawing, unsigned int key, Geom::OptRect bbox)
568 {
569     Inkscape::DrawingPattern *ai = new Inkscape::DrawingPattern(drawing);
570     //TODO: set some debug flag to see DrawingPattern
571     _display.push_front(View(ai, key));
572     _display.front().bbox = bbox;
573 
574     std::vector<SPHatchPath *> children(hatchPaths());
575 
576     Geom::OptInterval extents = _calculateStripExtents(bbox);
577     for (auto child : children) {
578         Inkscape::DrawingItem *cai = child->show(drawing, key, extents);
579         if (cai) {
580             ai->appendChild(cai);
581         }
582     }
583 
584     View& view = _display.front();
585     _updateView(view);
586 
587     return ai;
588 }
589 
hide(unsigned int key)590 void SPHatch::hide(unsigned int key)
591 {
592     std::vector<SPHatchPath *> children(hatchPaths());
593 
594     for (auto child : children) {
595         child->hide(key);
596     }
597 
598     for (ViewIterator iter = _display.begin(); iter != _display.end(); ++iter) {
599         if (iter->key == key) {
600             delete iter->arenaitem;
601             _display.erase(iter);
602             return;
603         }
604     }
605 
606     g_assert_not_reached();
607 }
608 
609 
bounds() const610 Geom::Interval SPHatch::bounds() const
611 {
612     Geom::Interval result;
613     std::vector<SPHatchPath const *> children(hatchPaths());
614 
615     for (auto child : children) {
616         if (result.extent() == 0) {
617             result = child->bounds();
618         } else {
619             result |= child->bounds();
620         }
621     }
622     return result;
623 }
624 
calculateRenderInfo(unsigned key) const625 SPHatch::RenderInfo SPHatch::calculateRenderInfo(unsigned key) const
626 {
627     RenderInfo info;
628     for (const auto & iter : _display) {
629         if (iter.key == key) {
630             return _calculateRenderInfo(iter);
631         }
632     }
633     g_assert_not_reached();
634     return info;
635 }
636 
_updateView(View & view)637 void SPHatch::_updateView(View &view)
638 {
639     RenderInfo info = _calculateRenderInfo(view);
640     //The rendering of hatch overflow is implemented by repeated drawing
641     //of hatch paths over one strip. Within each iteration paths are moved by pitch value.
642     //The movement progresses from right to left. This gives the same result
643     //as drawing whole strips in left-to-right order.
644 
645 
646     view.arenaitem->setChildTransform(info.child_transform);
647     view.arenaitem->setPatternToUserTransform(info.pattern_to_user_transform);
648     view.arenaitem->setTileRect(info.tile_rect);
649     view.arenaitem->setStyle(style);
650     view.arenaitem->setOverflow(info.overflow_initial_transform, info.overflow_steps,
651                                 info.overflow_step_transform);
652 }
653 
_calculateRenderInfo(View const & view) const654 SPHatch::RenderInfo SPHatch::_calculateRenderInfo(View const &view) const
655 {
656     RenderInfo info;
657 
658     Geom::OptInterval extents = _calculateStripExtents(view.bbox);
659     if (extents) {
660         double tile_x = x();
661         double tile_y = y();
662         double tile_width = pitch();
663         double tile_height = extents->max() - extents->min();
664         double tile_rotate = rotate();
665         double tile_render_y = extents->min();
666 
667         if (view.bbox && (hatchUnits() == UNITS_OBJECTBOUNDINGBOX)) {
668             tile_x *= view.bbox->width();
669             tile_y *= view.bbox->height();
670             tile_width *= view.bbox->width();
671         }
672 
673         // Extent calculated using content units, need to correct.
674         if (view.bbox && (hatchContentUnits() == UNITS_OBJECTBOUNDINGBOX)) {
675             tile_height *= view.bbox->height();
676             tile_render_y *= view.bbox->height();
677         }
678 
679         // Pattern size in hatch space
680         Geom::Rect hatch_tile = Geom::Rect::from_xywh(0, tile_render_y, tile_width, tile_height);
681 
682         // Content to bbox
683         Geom::Affine content2ps;
684         if (view.bbox && (hatchContentUnits() == UNITS_OBJECTBOUNDINGBOX)) {
685             content2ps = Geom::Affine(view.bbox->width(), 0.0, 0.0, view.bbox->height(), 0, 0);
686         }
687 
688         // Tile (hatch space) to user.
689         Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * Geom::Rotate::from_degrees(tile_rotate) * hatchTransform();
690 
691         info.child_transform = content2ps;
692         info.pattern_to_user_transform = ps2user;
693         info.tile_rect = hatch_tile;
694 
695         if (style->overflow.computed == SP_CSS_OVERFLOW_VISIBLE) {
696             Geom::Interval bounds = this->bounds();
697             gdouble pitch = this->pitch();
698             if (view.bbox) {
699                 if (hatchUnits() == UNITS_OBJECTBOUNDINGBOX) {
700                     pitch *= view.bbox->width();
701                 }
702                 if (hatchContentUnits() == UNITS_OBJECTBOUNDINGBOX) {
703                     bounds *= view.bbox->width();
704                 }
705             }
706             gdouble overflow_right_strip = floor(bounds.max() / pitch) * pitch;
707             info.overflow_steps = ceil((overflow_right_strip - bounds.min()) / pitch) + 1;
708             info.overflow_step_transform = Geom::Translate(pitch, 0.0);
709             info.overflow_initial_transform = Geom::Translate(-overflow_right_strip, 0.0);
710         } else {
711             info.overflow_steps = 1;
712         }
713     }
714 
715     return info;
716 }
717 
718 //calculates strip extents in content space
_calculateStripExtents(Geom::OptRect const & bbox) const719 Geom::OptInterval SPHatch::_calculateStripExtents(Geom::OptRect const &bbox) const
720 {
721     if (!bbox || (bbox->area() == 0)) {
722         return Geom::OptInterval();
723     } else {
724         double tile_x = x();
725         double tile_y = y();
726         double tile_rotate = rotate();
727 
728         Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * Geom::Rotate::from_degrees(tile_rotate) * hatchTransform();
729         Geom::Affine user2ps = ps2user.inverse();
730 
731         Geom::Interval extents;
732         for (int i = 0; i < 4; ++i) {
733             Geom::Point corner = bbox->corner(i);
734             Geom::Point corner_ps  =  corner * user2ps;
735             if (i == 0 || corner_ps.y() < extents.min()) {
736                 extents.setMin(corner_ps.y());
737             }
738             if (i == 0 || corner_ps.y() > extents.max()) {
739                 extents.setMax(corner_ps.y());
740             }
741         }
742 
743         if (hatchContentUnits() == UNITS_OBJECTBOUNDINGBOX) {
744             extents /= bbox->height();
745         }
746 
747         return extents;
748     }
749 }
750 
pattern_new(cairo_t *,Geom::OptRect const &,double)751 cairo_pattern_t* SPHatch::pattern_new(cairo_t * /*base_ct*/, Geom::OptRect const &/*bbox*/, double /*opacity*/)
752 {
753     //this code should not be used
754     //it is however required by the fact that SPPaintServer::hatch_new is pure virtual
755     return cairo_pattern_create_rgb(0.5, 0.5, 1.0);
756 }
757 
setBBox(unsigned int key,Geom::OptRect const & bbox)758 void SPHatch::setBBox(unsigned int key, Geom::OptRect const &bbox)
759 {
760     for (auto & iter : _display) {
761         if (iter.key == key) {
762             iter.bbox = bbox;
763             break;
764         }
765     }
766 }
767 
768 //
769 
RenderInfo()770 SPHatch::RenderInfo::RenderInfo()
771     : child_transform(),
772       pattern_to_user_transform(),
773       tile_rect(),
774       overflow_steps(0),
775       overflow_step_transform(),
776       overflow_initial_transform()
777 {
778 }
779 
780 SPHatch::RenderInfo::~RenderInfo()
781 = default;
782 
783 //
784 
View(Inkscape::DrawingPattern * arenaitem,int key)785 SPHatch::View::View(Inkscape::DrawingPattern *arenaitem, int key)
786     : arenaitem(arenaitem),
787       bbox(),
788       key(key)
789 {
790 }
791 
~View()792 SPHatch::View::~View()
793 {
794     // remember, do not delete arenaitem here
795     arenaitem = nullptr;
796 }
797 
798 /*
799  Local Variables:
800  mode:c++
801  c-file-style:"stroustrup"
802  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
803  indent-tabs-mode:nil
804  fill-column:99
805  End:
806  */
807 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
808