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