1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * SVG <pattern> implementation
4  *
5  * Author:
6  *   Lauris Kaplinski <lauris@kaplinski.com>
7  *   bulia byak <buliabyak@users.sf.net>
8  *   Jon A. Cruz <jon@joncruz.org>
9  *   Abhishek Sharma
10  *
11  * Copyright (C) 2002 Lauris Kaplinski
12  *
13  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14  */
15 
16 #include "sp-pattern.h"
17 
18 #include <cstring>
19 #include <string>
20 
21 #include <glibmm.h>
22 
23 #include <2geom/transforms.h>
24 
25 #include "attributes.h"
26 #include "bad-uri-exception.h"
27 #include "document.h"
28 
29 #include "sp-defs.h"
30 #include "sp-factory.h"
31 #include "sp-item.h"
32 
33 #include "display/cairo-utils.h"
34 #include "display/drawing-context.h"
35 #include "display/drawing-surface.h"
36 #include "display/drawing.h"
37 #include "display/drawing-group.h"
38 
39 #include "svg/svg.h"
40 
SPPattern()41 SPPattern::SPPattern()
42     : SPPaintServer()
43     , SPViewBox()
44 {
45     this->ref = new SPPatternReference(this);
46     this->ref->changedSignal().connect(sigc::mem_fun(this, &SPPattern::_onRefChanged));
47 
48     this->_pattern_units = UNITS_OBJECTBOUNDINGBOX;
49     this->_pattern_units_set = false;
50 
51     this->_pattern_content_units = UNITS_USERSPACEONUSE;
52     this->_pattern_content_units_set = false;
53 
54     this->_pattern_transform = Geom::identity();
55     this->_pattern_transform_set = false;
56 
57     this->_x.unset();
58     this->_y.unset();
59     this->_width.unset();
60     this->_height.unset();
61 }
62 
63 SPPattern::~SPPattern() = default;
64 
build(SPDocument * doc,Inkscape::XML::Node * repr)65 void SPPattern::build(SPDocument *doc, Inkscape::XML::Node *repr)
66 {
67     SPPaintServer::build(doc, repr);
68 
69     this->readAttr(SPAttr::PATTERNUNITS);
70     this->readAttr(SPAttr::PATTERNCONTENTUNITS);
71     this->readAttr(SPAttr::PATTERNTRANSFORM);
72     this->readAttr(SPAttr::X);
73     this->readAttr(SPAttr::Y);
74     this->readAttr(SPAttr::WIDTH);
75     this->readAttr(SPAttr::HEIGHT);
76     this->readAttr(SPAttr::VIEWBOX);
77     this->readAttr(SPAttr::PRESERVEASPECTRATIO);
78     this->readAttr(SPAttr::XLINK_HREF);
79     this->readAttr(SPAttr::STYLE);
80 
81     /* Register ourselves */
82     doc->addResource("pattern", this);
83 }
84 
release()85 void SPPattern::release()
86 {
87     if (this->document) {
88         // Unregister ourselves
89         this->document->removeResource("pattern", this);
90     }
91 
92     if (this->ref) {
93         this->_modified_connection.disconnect();
94         this->ref->detach();
95         delete this->ref;
96         this->ref = nullptr;
97     }
98 
99     SPPaintServer::release();
100 }
101 
set(SPAttr key,const gchar * value)102 void SPPattern::set(SPAttr key, const gchar *value)
103 {
104     switch (key) {
105         case SPAttr::PATTERNUNITS:
106             if (value) {
107                 if (!strcmp(value, "userSpaceOnUse")) {
108                     this->_pattern_units = UNITS_USERSPACEONUSE;
109                 }
110                 else {
111                     this->_pattern_units = UNITS_OBJECTBOUNDINGBOX;
112                 }
113 
114                 this->_pattern_units_set = true;
115             }
116             else {
117                 this->_pattern_units_set = false;
118             }
119 
120             this->requestModified(SP_OBJECT_MODIFIED_FLAG);
121             break;
122 
123         case SPAttr::PATTERNCONTENTUNITS:
124             if (value) {
125                 if (!strcmp(value, "userSpaceOnUse")) {
126                     this->_pattern_content_units = UNITS_USERSPACEONUSE;
127                 }
128                 else {
129                     this->_pattern_content_units = UNITS_OBJECTBOUNDINGBOX;
130                 }
131 
132                 this->_pattern_content_units_set = true;
133             }
134             else {
135                 this->_pattern_content_units_set = false;
136             }
137 
138             this->requestModified(SP_OBJECT_MODIFIED_FLAG);
139             break;
140 
141         case SPAttr::PATTERNTRANSFORM: {
142             Geom::Affine t;
143 
144             if (value && sp_svg_transform_read(value, &t)) {
145                 this->_pattern_transform = t;
146                 this->_pattern_transform_set = true;
147             }
148             else {
149                 this->_pattern_transform = Geom::identity();
150                 this->_pattern_transform_set = false;
151             }
152 
153             this->requestModified(SP_OBJECT_MODIFIED_FLAG);
154             break;
155         }
156         case SPAttr::X:
157             this->_x.readOrUnset(value);
158             this->requestModified(SP_OBJECT_MODIFIED_FLAG);
159             break;
160 
161         case SPAttr::Y:
162             this->_y.readOrUnset(value);
163             this->requestModified(SP_OBJECT_MODIFIED_FLAG);
164             break;
165 
166         case SPAttr::WIDTH:
167             this->_width.readOrUnset(value);
168             this->requestModified(SP_OBJECT_MODIFIED_FLAG);
169             break;
170 
171         case SPAttr::HEIGHT:
172             this->_height.readOrUnset(value);
173             this->requestModified(SP_OBJECT_MODIFIED_FLAG);
174             break;
175 
176         case SPAttr::VIEWBOX:
177             set_viewBox(value);
178             this->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG);
179             break;
180 
181         case SPAttr::PRESERVEASPECTRATIO:
182             set_preserveAspectRatio(value);
183             this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG);
184             break;
185 
186         case SPAttr::XLINK_HREF:
187             if (value && this->href == value) {
188                 /* Href unchanged, do nothing. */
189             }
190             else {
191                 this->href.clear();
192 
193                 if (value) {
194                     // First, set the href field; it's only used in the "unchanged" check above.
195                     this->href = value;
196                     // Now do the attaching, which emits the changed signal.
197                     if (value) {
198                         try {
199                             this->ref->attach(Inkscape::URI(value));
200                         }
201                         catch (Inkscape::BadURIException &e) {
202                             g_warning("%s", e.what());
203                             this->ref->detach();
204                         }
205                     }
206                     else {
207                         this->ref->detach();
208                     }
209                 }
210             }
211             break;
212 
213         default:
214             SPPaintServer::set(key, value);
215             break;
216     }
217 }
218 
219 
220 /* TODO: do we need a ::remove_child handler? */
221 
222 /* fixme: We need ::order_changed handler too (Lauris) */
223 
_getChildren(std::list<SPObject * > & l)224 void SPPattern::_getChildren(std::list<SPObject *> &l)
225 {
226     for (SPPattern *pat_i = this; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
227         if (pat_i->firstChild()) { // find the first one with children
228             for (auto& child: pat_i->children) {
229                 l.push_back(&child);
230             }
231             break; // do not go further up the chain if children are found
232         }
233     }
234 }
235 
update(SPCtx * ctx,unsigned int flags)236 void SPPattern::update(SPCtx *ctx, unsigned int flags)
237 {
238     if (flags & SP_OBJECT_MODIFIED_FLAG) {
239         flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
240     }
241 
242     flags &= SP_OBJECT_MODIFIED_CASCADE;
243 
244     std::list<SPObject *> l;
245     _getChildren(l);
246 
247     for (auto child : l) {
248         sp_object_ref(child, nullptr);
249 
250         if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
251             child->updateDisplay(ctx, flags);
252         }
253 
254         sp_object_unref(child, nullptr);
255     }
256 }
257 
modified(unsigned int flags)258 void SPPattern::modified(unsigned int flags)
259 {
260     if (flags & SP_OBJECT_MODIFIED_FLAG) {
261         flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
262     }
263 
264     flags &= SP_OBJECT_MODIFIED_CASCADE;
265 
266     std::list<SPObject *> l;
267     _getChildren(l);
268 
269     for (auto child : l) {
270         sp_object_ref(child, nullptr);
271 
272         if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
273             child->emitModified(flags);
274         }
275 
276         sp_object_unref(child, nullptr);
277     }
278 }
279 
_onRefChanged(SPObject * old_ref,SPObject * ref)280 void SPPattern::_onRefChanged(SPObject *old_ref, SPObject *ref)
281 {
282     if (old_ref) {
283         _modified_connection.disconnect();
284     }
285 
286     if (SP_IS_PATTERN(ref)) {
287         _modified_connection = ref->connectModified(sigc::mem_fun(this, &SPPattern::_onRefModified));
288     }
289 
290     _onRefModified(ref, 0);
291 }
292 
_onRefModified(SPObject *,guint)293 void SPPattern::_onRefModified(SPObject * /*ref*/, guint /*flags*/)
294 {
295     requestModified(SP_OBJECT_MODIFIED_FLAG);
296     // Conditional to avoid causing infinite loop if there's a cycle in the href chain.
297 }
298 
_countHrefs(SPObject * o) const299 guint SPPattern::_countHrefs(SPObject *o) const
300 {
301     if (!o)
302         return 1;
303 
304     guint i = 0;
305 
306     SPStyle *style = o->style;
307     if (style && style->fill.isPaintserver() && SP_IS_PATTERN(SP_STYLE_FILL_SERVER(style)) &&
308         SP_PATTERN(SP_STYLE_FILL_SERVER(style)) == this) {
309         i++;
310     }
311     if (style && style->stroke.isPaintserver() && SP_IS_PATTERN(SP_STYLE_STROKE_SERVER(style)) &&
312         SP_PATTERN(SP_STYLE_STROKE_SERVER(style)) == this) {
313         i++;
314     }
315 
316     for (auto& child: o->children) {
317         i += _countHrefs(&child);
318     }
319 
320     return i;
321 }
322 
_chain() const323 SPPattern *SPPattern::_chain() const
324 {
325     Inkscape::XML::Document *xml_doc = document->getReprDoc();
326     Inkscape::XML::Node *defsrepr = document->getDefs()->getRepr();
327 
328     Inkscape::XML::Node *repr = xml_doc->createElement("svg:pattern");
329     repr->setAttribute("inkscape:collect", "always");
330     Glib::ustring parent_ref = Glib::ustring::compose("#%1", getRepr()->attribute("id"));
331     repr->setAttribute("xlink:href", parent_ref);
332 
333     defsrepr->addChild(repr, nullptr);
334     SPObject *child = document->getObjectByRepr(repr);
335     assert(child == document->getObjectById(repr->attribute("id")));
336     g_assert(SP_IS_PATTERN(child));
337 
338     return SP_PATTERN(child);
339 }
340 
clone_if_necessary(SPItem * item,const gchar * property)341 SPPattern *SPPattern::clone_if_necessary(SPItem *item, const gchar *property)
342 {
343     SPPattern *pattern = this;
344     if (pattern->href.empty() || pattern->hrefcount > _countHrefs(item)) {
345         pattern = _chain();
346         Glib::ustring href = Glib::ustring::compose("url(#%1)", pattern->getRepr()->attribute("id"));
347 
348         SPCSSAttr *css = sp_repr_css_attr_new();
349         sp_repr_css_set_property(css, property, href.c_str());
350         sp_repr_css_change_recursive(item->getRepr(), css, "style");
351     }
352     return pattern;
353 }
354 
transform_multiply(Geom::Affine postmul,bool set)355 void SPPattern::transform_multiply(Geom::Affine postmul, bool set)
356 {
357     // this formula is for a different interpretation of pattern transforms as described in (*) in sp-pattern.cpp
358     // for it to work, we also need    sp_object_read_attr( item, "transform");
359     // pattern->patternTransform = premul * item->transform * pattern->patternTransform * item->transform.inverse() *
360     // postmul;
361 
362     // otherwise the formula is much simpler
363     if (set) {
364         _pattern_transform = postmul;
365     }
366     else {
367         _pattern_transform = getTransform() * postmul;
368     }
369     _pattern_transform_set = true;
370 
371     setAttributeOrRemoveIfEmpty("patternTransform", sp_svg_transform_write(_pattern_transform));
372 }
373 
produce(const std::vector<Inkscape::XML::Node * > & reprs,Geom::Rect bounds,SPDocument * document,Geom::Affine transform,Geom::Affine move)374 const gchar *SPPattern::produce(const std::vector<Inkscape::XML::Node *> &reprs, Geom::Rect bounds,
375                                 SPDocument *document, Geom::Affine transform, Geom::Affine move)
376 {
377     Inkscape::XML::Document *xml_doc = document->getReprDoc();
378     Inkscape::XML::Node *defsrepr = document->getDefs()->getRepr();
379 
380     Inkscape::XML::Node *repr = xml_doc->createElement("svg:pattern");
381     repr->setAttribute("patternUnits", "userSpaceOnUse");
382     sp_repr_set_svg_double(repr, "width", bounds.dimensions()[Geom::X]);
383     sp_repr_set_svg_double(repr, "height", bounds.dimensions()[Geom::Y]);
384     repr->setAttributeOrRemoveIfEmpty("patternTransform", sp_svg_transform_write(transform));
385     defsrepr->appendChild(repr);
386     const gchar *pat_id = repr->attribute("id");
387     SPObject *pat_object = document->getObjectById(pat_id);
388 
389     for (auto node : reprs) {
390         SPItem *copy = SP_ITEM(pat_object->appendChildRepr(node));
391 
392         Geom::Affine dup_transform;
393         if (!sp_svg_transform_read(node->attribute("transform"), &dup_transform))
394             dup_transform = Geom::identity();
395         dup_transform *= move;
396 
397         copy->doWriteTransform(dup_transform, nullptr, false);
398     }
399 
400     Inkscape::GC::release(repr);
401     return pat_id;
402 }
403 
rootPattern()404 SPPattern *SPPattern::rootPattern()
405 {
406     for (SPPattern *pat_i = this; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
407         if (pat_i->firstChild()) { // find the first one with children
408             return pat_i;
409         }
410     }
411     return this; // document is broken, we can't get to root; but at least we can return pat which is supposedly a valid
412                  // pattern
413 }
414 
415 
416 
417 // Access functions that look up fields up the chain of referenced patterns and return the first one which is set
418 // FIXME: all of them must use chase_hrefs the same as in SPGradient, to avoid lockup on circular refs
419 
patternUnits() const420 SPPattern::PatternUnits SPPattern::patternUnits() const
421 {
422     for (SPPattern const *pat_i = this; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
423         if (pat_i->_pattern_units_set)
424             return pat_i->_pattern_units;
425     }
426     return _pattern_units;
427 }
428 
patternContentUnits() const429 SPPattern::PatternUnits SPPattern::patternContentUnits() const
430 {
431     for (SPPattern const *pat_i = this; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
432         if (pat_i->_pattern_content_units_set)
433             return pat_i->_pattern_content_units;
434     }
435     return _pattern_content_units;
436 }
437 
getTransform() const438 Geom::Affine const &SPPattern::getTransform() const
439 {
440     for (SPPattern const *pat_i = this; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
441         if (pat_i->_pattern_transform_set)
442             return pat_i->_pattern_transform;
443     }
444     return _pattern_transform;
445 }
446 
x() const447 gdouble SPPattern::x() const
448 {
449     for (SPPattern const *pat_i = this; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
450         if (pat_i->_x._set)
451             return pat_i->_x.computed;
452     }
453     return 0;
454 }
455 
y() const456 gdouble SPPattern::y() const
457 {
458     for (SPPattern const *pat_i = this; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
459         if (pat_i->_y._set)
460             return pat_i->_y.computed;
461     }
462     return 0;
463 }
464 
width() const465 gdouble SPPattern::width() const
466 {
467     for (SPPattern const *pat_i = this; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
468         if (pat_i->_width._set)
469             return pat_i->_width.computed;
470     }
471     return 0;
472 }
473 
height() const474 gdouble SPPattern::height() const
475 {
476     for (SPPattern const *pat_i = this; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
477         if (pat_i->_height._set)
478             return pat_i->_height.computed;
479     }
480     return 0;
481 }
482 
viewbox() const483 Geom::OptRect SPPattern::viewbox() const
484 {
485     Geom::OptRect viewbox;
486     for (SPPattern const *pat_i = this; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
487         if (pat_i->viewBox_set) {
488             viewbox = pat_i->viewBox;
489             break;
490         }
491     }
492     return viewbox;
493 }
494 
_hasItemChildren() const495 bool SPPattern::_hasItemChildren() const
496 {
497     for (auto& child: children) {
498         if (SP_IS_ITEM(&child)) {
499             return true;
500         }
501     }
502 
503     return false;
504 }
505 
isValid() const506 bool SPPattern::isValid() const
507 {
508     double tile_width = width();
509     double tile_height = height();
510 
511     if (tile_width <= 0 || tile_height <= 0)
512         return false;
513     return true;
514 }
515 
pattern_new(cairo_t * base_ct,Geom::OptRect const & bbox,double opacity)516 cairo_pattern_t *SPPattern::pattern_new(cairo_t *base_ct, Geom::OptRect const &bbox, double opacity)
517 {
518 
519     bool needs_opacity = (1.0 - opacity) >= 1e-3;
520     bool visible = opacity >= 1e-3;
521 
522     if (!visible) {
523         return nullptr;
524     }
525 
526     /* Show items */
527     SPPattern *shown = nullptr;
528 
529     for (SPPattern *pat_i = this; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
530         // find the first one with item children
531         if (pat_i && SP_IS_OBJECT(pat_i) && pat_i->_hasItemChildren()) {
532             shown = pat_i;
533             break; // do not go further up the chain if children are found
534         }
535     }
536 
537     if (!shown) {
538         return cairo_pattern_create_rgba(0, 0, 0, 0);
539     }
540 
541     /* Create drawing for rendering */
542     Inkscape::Drawing drawing;
543     unsigned int dkey = SPItem::display_key_new(1);
544     Inkscape::DrawingGroup *root = new Inkscape::DrawingGroup(drawing);
545     drawing.setRoot(root);
546 
547     for (auto& child: shown->children) {
548         if (SP_IS_ITEM(&child)) {
549             // for each item in pattern, show it on our drawing, add to the group,
550             // and connect to the release signal in case the item gets deleted
551             Inkscape::DrawingItem *cai;
552             cai = SP_ITEM(&child)->invoke_show(drawing, dkey, SP_ITEM_SHOW_DISPLAY);
553             root->appendChild(cai);
554         }
555     }
556 
557     //                 ****** Geometry ******
558     //
559     // * "width" and "height" determine tile size.
560     // * "viewBox" (if defined) or "patternContentUnits" determines placement of content inside
561     //   tile.
562     // * "x", "y", and "patternTransform" transform tile to user space after tile is generated.
563 
564     // These functions recursively search up the tree to find the values.
565     double tile_x = x();
566     double tile_y = y();
567     double tile_width = width();
568     double tile_height = height();
569     if (bbox && (patternUnits() == UNITS_OBJECTBOUNDINGBOX)) {
570         tile_x *= bbox->width();
571         tile_y *= bbox->height();
572         tile_width *= bbox->width();
573         tile_height *= bbox->height();
574     }
575 
576     // Pattern size in pattern space
577     Geom::Rect pattern_tile = Geom::Rect::from_xywh(0, 0, tile_width, tile_height);
578 
579     // Content to tile (pattern space)
580     Geom::Affine content2ps;
581     Geom::OptRect effective_view_box = viewbox();
582     if (effective_view_box) {
583         // viewBox to pattern server (using SPViewBox)
584         viewBox = *effective_view_box;
585         c2p.setIdentity();
586         apply_viewbox(pattern_tile);
587         content2ps = c2p;
588     }
589     else {
590 
591         // Content to bbox
592         if (bbox && (patternContentUnits() == UNITS_OBJECTBOUNDINGBOX)) {
593             content2ps = Geom::Affine(bbox->width(), 0.0, 0.0, bbox->height(), 0, 0);
594         }
595     }
596 
597 
598     // Tile (pattern space) to user.
599     Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * getTransform();
600 
601 
602     // Transform of object with pattern (includes screen scaling)
603     cairo_matrix_t cm;
604     cairo_get_matrix(base_ct, &cm);
605     Geom::Affine full(cm.xx, cm.yx, cm.xy, cm.yy, 0, 0);
606 
607     // The DrawingSurface class handles the mapping from "logical space"
608     // (coordinates in the rendering) to "physical space" (surface pixels).
609     // An oversampling is done as the pattern may not pixel align with the final surface.
610     // The cairo surface is created when the DrawingContext is declared.
611 
612     // Oversample the pattern
613     // TODO: find optimum value
614     // TODO: this is lame. instead of using descrim(), we should extract
615     //       the scaling component from the complete matrix and use it
616     //       to find the optimum tile size for rendering
617     // c is number of pixels in buffer x and y.
618     // Scale factor of 1.1 is too small... see bug #1251039
619     Geom::Point c(pattern_tile.dimensions() * ps2user.descrim() * full.descrim() * 2.0);
620 
621     // Create drawing surface with size of pattern tile (in pattern space) but with number of pixels
622     // based on required resolution (c).
623     Inkscape::DrawingSurface pattern_surface(pattern_tile, c.ceil());
624     Inkscape::DrawingContext dc(pattern_surface);
625 
626     pattern_tile *= pattern_surface.drawingTransform();
627     Geom::IntRect one_tile = pattern_tile.roundOutwards();
628 
629     // Render pattern.
630     if (needs_opacity) {
631         dc.pushGroup(); // this group is for pattern + opacity
632     }
633 
634     // TODO: make sure there are no leaks.
635     dc.transform(pattern_surface.drawingTransform().inverse());
636     root->setTransform(content2ps * pattern_surface.drawingTransform());
637     drawing.update();
638 
639     // Render drawing to pattern_surface via drawing context, this calls root->render
640     // which is really DrawingItem->render().
641     drawing.render(dc, one_tile);
642     for (auto& child: shown->children) {
643         if (SP_IS_ITEM(&child)) {
644             SP_ITEM(&child)->invoke_hide(dkey);
645         }
646     }
647 
648     // Uncomment to debug
649     // cairo_surface_t* raw = pattern_surface.raw();
650     // std::cout << "  cairo_surface (sp-pattern): "
651     //           << " width: "  << cairo_image_surface_get_width( raw )
652     //           << " height: " << cairo_image_surface_get_height( raw )
653     //           << std::endl;
654     // std::string filename = "sp-pattern-" + (std::string)getId() + ".png";
655     // cairo_surface_write_to_png( pattern_surface.raw(), filename.c_str() );
656 
657     if (needs_opacity) {
658         dc.popGroupToSource(); // pop raw pattern
659         dc.paint(opacity);     // apply opacity
660     }
661 
662     // Apply transformation to user space. Also compensate for oversampling.
663     Geom::Affine raw_transform = ps2user.inverse() * pattern_surface.drawingTransform();
664 
665     // Cairo doesn't like large values of x0 and y0. We can replace x0 and y0 by equivalent
666     // values close to zero (since one tile on a grid is the same as another it doesn't
667     // matter which tile is used as the base tile).
668     int w = one_tile[Geom::X].extent();
669     int h = one_tile[Geom::Y].extent();
670     int m = raw_transform[4] / w;
671     int n = raw_transform[5] / h;
672     raw_transform *= Geom::Translate( -m*w, -n*h );
673 
674     cairo_pattern_t *cp = cairo_pattern_create_for_surface(pattern_surface.raw());
675     ink_cairo_pattern_set_matrix(cp, raw_transform);
676     cairo_pattern_set_extend(cp, CAIRO_EXTEND_REPEAT);
677 
678     return cp;
679 }
680 
681 /*
682   Local Variables:
683   mode:c++
684   c-file-style:"stroustrup"
685   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
686   indent-tabs-mode:nil
687   fill-column:99
688   End:
689 */
690 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
691