1 #define __SP_PATTERN_C__
3 /*
4 * SVG <pattern> implementation
5 *
6 * Author:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * Copyright (C) 2002 Lauris Kaplinski
11 *
12 * Released under GNU GPL, read the file 'COPYING' for more information
13 */
15 #include "config.h"
17 #include <libnr/nr-matrix-ops.h>
18 #include "libnr/nr-matrix-fns.h"
19 #include <libnr/nr-translate-matrix-ops.h>
20 #include "macros.h"
21 #include "svg/svg.h"
22 #include "display/nr-arena.h"
23 #include "display/nr-arena-group.h"
24 #include "attributes.h"
25 #include "document-private.h"
26 #include "uri.h"
27 #include "sp-pattern.h"
28 #include "xml/repr.h"
30 /*
31 * Pattern
32 */
34 class SPPatPainter;
36 struct SPPatPainter {
37 SPPainter painter;
38 SPPattern *pat;
40 NRMatrix ps2px;
41 NRMatrix px2ps;
42 NRMatrix pcs2px;
44 NRArena *arena;
45 unsigned int dkey;
46 NRArenaItem *root;
48 bool use_cached_tile;
49 NRMatrix ca2pa;
50 NRMatrix pa2ca;
51 NRRectL cached_bbox;
52 NRPixBlock cached_tile;
53 };
55 static void sp_pattern_class_init (SPPatternClass *klass);
56 static void sp_pattern_init (SPPattern *gr);
58 static void sp_pattern_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
59 static void sp_pattern_release (SPObject *object);
60 static void sp_pattern_set (SPObject *object, unsigned int key, const gchar *value);
61 static void sp_pattern_child_added (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref);
62 static void sp_pattern_update (SPObject *object, SPCtx *ctx, unsigned int flags);
63 static void sp_pattern_modified (SPObject *object, unsigned int flags);
65 static void pattern_ref_changed(SPObject *old_ref, SPObject *ref, SPPattern *pat);
66 static void pattern_ref_modified (SPObject *ref, guint flags, SPPattern *pattern);
68 static SPPainter *sp_pattern_painter_new (SPPaintServer *ps, NR::Matrix const &full_transform, NR::Matrix const &parent_transform, const NRRect *bbox);
69 static void sp_pattern_painter_free (SPPaintServer *ps, SPPainter *painter);
71 static SPPaintServerClass * pattern_parent_class;
73 GType
74 sp_pattern_get_type (void)
75 {
76 static GType pattern_type = 0;
77 if (!pattern_type) {
78 GTypeInfo pattern_info = {
79 sizeof (SPPatternClass),
80 NULL, /* base_init */
81 NULL, /* base_finalize */
82 (GClassInitFunc) sp_pattern_class_init,
83 NULL, /* class_finalize */
84 NULL, /* class_data */
85 sizeof (SPPattern),
86 16, /* n_preallocs */
87 (GInstanceInitFunc) sp_pattern_init,
88 NULL, /* value_table */
89 };
90 pattern_type = g_type_register_static (SP_TYPE_PAINT_SERVER, "SPPattern", &pattern_info, (GTypeFlags)0);
91 }
92 return pattern_type;
93 }
95 static void
96 sp_pattern_class_init (SPPatternClass *klass)
97 {
98 SPObjectClass *sp_object_class;
99 SPPaintServerClass *ps_class;
101 sp_object_class = (SPObjectClass *) klass;
102 ps_class = (SPPaintServerClass *) klass;
104 pattern_parent_class = (SPPaintServerClass*)g_type_class_ref (SP_TYPE_PAINT_SERVER);
106 sp_object_class->build = sp_pattern_build;
107 sp_object_class->release = sp_pattern_release;
108 sp_object_class->set = sp_pattern_set;
109 sp_object_class->child_added = sp_pattern_child_added;
110 sp_object_class->update = sp_pattern_update;
111 sp_object_class->modified = sp_pattern_modified;
113 // do we need _write? seems to work without it
115 ps_class->painter_new = sp_pattern_painter_new;
116 ps_class->painter_free = sp_pattern_painter_free;
117 }
119 static void
120 sp_pattern_init (SPPattern *pat)
121 {
122 pat->ref = new SPPatternReference(SP_OBJECT(pat));
123 pat->ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(pattern_ref_changed), pat));
125 pat->patternUnits = SP_PATTERN_UNITS_OBJECTBOUNDINGBOX;
126 pat->patternUnits_set = FALSE;
128 pat->patternContentUnits = SP_PATTERN_UNITS_USERSPACEONUSE;
129 pat->patternContentUnits_set = FALSE;
131 pat->patternTransform = NR::identity();
132 pat->patternTransform_set = FALSE;
134 pat->x.unset();
135 pat->y.unset();
136 pat->width.unset();
137 pat->height.unset();
139 pat->viewBox_set = FALSE;
140 }
142 static void
143 sp_pattern_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
144 {
145 if (((SPObjectClass *) pattern_parent_class)->build)
146 (* ((SPObjectClass *) pattern_parent_class)->build) (object, document, repr);
148 sp_object_read_attr (object, "patternUnits");
149 sp_object_read_attr (object, "patternContentUnits");
150 sp_object_read_attr (object, "patternTransform");
151 sp_object_read_attr (object, "x");
152 sp_object_read_attr (object, "y");
153 sp_object_read_attr (object, "width");
154 sp_object_read_attr (object, "height");
155 sp_object_read_attr (object, "viewBox");
156 sp_object_read_attr (object, "xlink:href");
158 /* Register ourselves */
159 sp_document_add_resource (document, "pattern", object);
160 }
162 static void
163 sp_pattern_release (SPObject *object)
164 {
165 SPPattern *pat;
167 pat = (SPPattern *) object;
169 if (SP_OBJECT_DOCUMENT (object)) {
170 /* Unregister ourselves */
171 sp_document_remove_resource (SP_OBJECT_DOCUMENT (object), "pattern", SP_OBJECT (object));
172 }
174 if (pat->ref) {
175 if (pat->ref->getObject())
176 sp_signal_disconnect_by_data(pat->ref->getObject(), pat);
177 pat->ref->detach();
178 delete pat->ref;
179 pat->ref = NULL;
180 }
182 if (((SPObjectClass *) pattern_parent_class)->release)
183 ((SPObjectClass *) pattern_parent_class)->release (object);
184 }
186 static void
187 sp_pattern_set (SPObject *object, unsigned int key, const gchar *value)
188 {
189 SPPattern *pat = SP_PATTERN (object);
191 switch (key) {
192 case SP_ATTR_PATTERNUNITS:
193 if (value) {
194 if (!strcmp (value, "userSpaceOnUse")) {
195 pat->patternUnits = SP_PATTERN_UNITS_USERSPACEONUSE;
196 } else {
197 pat->patternUnits = SP_PATTERN_UNITS_OBJECTBOUNDINGBOX;
198 }
199 pat->patternUnits_set = TRUE;
200 } else {
201 pat->patternUnits_set = FALSE;
202 }
203 object->requestModified(SP_OBJECT_MODIFIED_FLAG);
204 break;
205 case SP_ATTR_PATTERNCONTENTUNITS:
206 if (value) {
207 if (!strcmp (value, "userSpaceOnUse")) {
208 pat->patternContentUnits = SP_PATTERN_UNITS_USERSPACEONUSE;
209 } else {
210 pat->patternContentUnits = SP_PATTERN_UNITS_OBJECTBOUNDINGBOX;
211 }
212 pat->patternContentUnits_set = TRUE;
213 } else {
214 pat->patternContentUnits_set = FALSE;
215 }
216 object->requestModified(SP_OBJECT_MODIFIED_FLAG);
217 break;
218 case SP_ATTR_PATTERNTRANSFORM: {
219 NR::Matrix t;
220 if (value && sp_svg_transform_read (value, &t)) {
221 pat->patternTransform = t;
222 pat->patternTransform_set = TRUE;
223 } else {
224 pat->patternTransform = NR::identity();
225 pat->patternTransform_set = FALSE;
226 }
227 object->requestModified(SP_OBJECT_MODIFIED_FLAG);
228 break;
229 }
230 case SP_ATTR_X:
231 pat->x.readOrUnset(value);
232 object->requestModified(SP_OBJECT_MODIFIED_FLAG);
233 break;
234 case SP_ATTR_Y:
235 pat->y.readOrUnset(value);
236 object->requestModified(SP_OBJECT_MODIFIED_FLAG);
237 break;
238 case SP_ATTR_WIDTH:
239 pat->width.readOrUnset(value);
240 object->requestModified(SP_OBJECT_MODIFIED_FLAG);
241 break;
242 case SP_ATTR_HEIGHT:
243 pat->height.readOrUnset(value);
244 object->requestModified(SP_OBJECT_MODIFIED_FLAG);
245 break;
246 case SP_ATTR_VIEWBOX: {
247 /* fixme: Think (Lauris) */
248 double x, y, width, height;
249 char *eptr;
251 if (value) {
252 eptr = (gchar *) value;
253 x = g_ascii_strtod (eptr, &eptr);
254 while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++;
255 y = g_ascii_strtod (eptr, &eptr);
256 while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++;
257 width = g_ascii_strtod (eptr, &eptr);
258 while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++;
259 height = g_ascii_strtod (eptr, &eptr);
260 while (*eptr && ((*eptr == ',') || (*eptr == ' '))) eptr++;
261 if ((width > 0) && (height > 0)) {
262 pat->viewBox.x0 = x;
263 pat->viewBox.y0 = y;
264 pat->viewBox.x1 = x + width;
265 pat->viewBox.y1 = y + height;
266 pat->viewBox_set = TRUE;
267 } else {
268 pat->viewBox_set = FALSE;
269 }
270 } else {
271 pat->viewBox_set = FALSE;
272 }
273 object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG);
274 break;
275 }
276 case SP_ATTR_XLINK_HREF:
277 if ( value && pat->href && ( strcmp(value, pat->href) == 0 ) ) {
278 /* Href unchanged, do nothing. */
279 } else {
280 g_free(pat->href);
281 pat->href = NULL;
282 if (value) {
283 // First, set the href field; it's only used in the "unchanged" check above.
284 pat->href = g_strdup(value);
285 // Now do the attaching, which emits the changed signal.
286 if (value) {
287 try {
288 pat->ref->attach(Inkscape::URI(value));
289 } catch (Inkscape::BadURIException &e) {
290 g_warning("%s", e.what());
291 pat->ref->detach();
292 }
293 } else {
294 pat->ref->detach();
295 }
296 }
297 }
298 break;
299 default:
300 if (((SPObjectClass *) pattern_parent_class)->set)
301 ((SPObjectClass *) pattern_parent_class)->set (object, key, value);
302 break;
303 }
304 }
306 static void
307 sp_pattern_child_added (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref)
308 {
309 SPPattern *pat = SP_PATTERN (object);
311 if (((SPObjectClass *) (pattern_parent_class))->child_added)
312 (* ((SPObjectClass *) (pattern_parent_class))->child_added) (object, child, ref);
314 SPObject *ochild = sp_object_get_child_by_repr(object, child);
315 if (SP_IS_ITEM (ochild)) {
317 SPPaintServer *ps = SP_PAINT_SERVER (pat);
318 unsigned position = sp_item_pos_in_parent(SP_ITEM(ochild));
320 for (SPPainter *p = ps->painters; p != NULL; p = p->next) {
322 SPPatPainter *pp = (SPPatPainter *) p;
323 NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (ochild), pp->arena, pp->dkey, SP_ITEM_REFERENCE_FLAGS);
325 if (ai) {
326 nr_arena_item_add_child (pp->root, ai, NULL);
327 nr_arena_item_set_order (ai, position);
328 nr_arena_item_unref (ai);
329 }
330 }
331 }
332 }
334 /* TODO: do we need a ::remove_child handler? */
336 /* fixme: We need ::order_changed handler too (Lauris) */
338 GSList *
339 pattern_getchildren (SPPattern *pat)
340 {
341 GSList *l = NULL;
343 for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
344 if (sp_object_first_child(SP_OBJECT(pat_i))) { // find the first one with children
345 for (SPObject *child = sp_object_first_child(SP_OBJECT (pat)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
346 l = g_slist_prepend (l, child);
347 }
348 break; // do not go further up the chain if children are found
349 }
350 }
352 return l;
353 }
355 static void
356 sp_pattern_update (SPObject *object, SPCtx *ctx, unsigned int flags)
357 {
358 SPPattern *pat = SP_PATTERN (object);
360 if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
361 flags &= SP_OBJECT_MODIFIED_CASCADE;
363 GSList *l = pattern_getchildren (pat);
364 l = g_slist_reverse (l);
366 while (l) {
367 SPObject *child = SP_OBJECT (l->data);
368 sp_object_ref (child, NULL);
369 l = g_slist_remove (l, child);
370 if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
371 child->updateDisplay(ctx, flags);
372 }
373 sp_object_unref (child, NULL);
374 }
375 }
377 static void
378 sp_pattern_modified (SPObject *object, guint flags)
379 {
380 SPPattern *pat = SP_PATTERN (object);
382 if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
383 flags &= SP_OBJECT_MODIFIED_CASCADE;
385 GSList *l = pattern_getchildren (pat);
386 l = g_slist_reverse (l);
388 while (l) {
389 SPObject *child = SP_OBJECT (l->data);
390 sp_object_ref (child, NULL);
391 l = g_slist_remove (l, child);
392 if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
393 child->emitModified(flags);
394 }
395 sp_object_unref (child, NULL);
396 }
397 }
399 /**
400 Gets called when the pattern is reattached to another <pattern>
401 */
402 static void
403 pattern_ref_changed(SPObject *old_ref, SPObject *ref, SPPattern *pat)
404 {
405 if (old_ref) {
406 sp_signal_disconnect_by_data(old_ref, pat);
407 }
408 if (SP_IS_PATTERN (ref)) {
409 g_signal_connect(G_OBJECT (ref), "modified", G_CALLBACK (pattern_ref_modified), pat);
410 }
412 pattern_ref_modified (ref, 0, pat);
413 }
415 /**
416 Gets called when the referenced <pattern> is changed
417 */
418 static void
419 pattern_ref_modified (SPObject *ref, guint flags, SPPattern *pattern)
420 {
421 if (SP_IS_OBJECT (pattern))
422 SP_OBJECT (pattern)->requestModified(SP_OBJECT_MODIFIED_FLAG);
423 }
425 guint
426 pattern_users (SPPattern *pattern)
427 {
428 return SP_OBJECT (pattern)->hrefcount;
429 }
431 SPPattern *
432 pattern_chain (SPPattern *pattern)
433 {
434 SPDocument *document = SP_OBJECT_DOCUMENT (pattern);
435 Inkscape::XML::Node *defsrepr = SP_OBJECT_REPR (SP_DOCUMENT_DEFS (document));
437 Inkscape::XML::Node *repr = sp_repr_new ("svg:pattern");
438 repr->setAttribute("inkscape:collect", "always");
439 gchar *parent_ref = g_strconcat ("#", SP_OBJECT_REPR(pattern)->attribute("id"), NULL);
440 repr->setAttribute("xlink:href", parent_ref);
441 g_free (parent_ref);
443 defsrepr->addChild(repr, NULL);
444 const gchar *child_id = repr->attribute("id");
445 SPObject *child = document->getObjectById(child_id);
446 g_assert (SP_IS_PATTERN (child));
448 return SP_PATTERN (child);
449 }
451 SPPattern *
452 sp_pattern_clone_if_necessary (SPItem *item, SPPattern *pattern, const gchar *property)
453 {
454 if (pattern_users(pattern) > 1) {
455 pattern = pattern_chain (pattern);
456 gchar *href = g_strconcat ("url(#", SP_OBJECT_REPR (pattern)->attribute("id"), ")", NULL);
458 SPCSSAttr *css = sp_repr_css_attr_new ();
459 sp_repr_css_set_property (css, property, href);
460 sp_repr_css_change_recursive (SP_OBJECT_REPR (item), css, "style");
461 }
462 return pattern;
463 }
465 void
466 sp_pattern_transform_multiply (SPPattern *pattern, NR::Matrix postmul, bool set)
467 {
468 // this formula is for a different interpretation of pattern transforms as described in (*) in sp-pattern.cpp
469 // for it to work, we also need sp_object_read_attr (SP_OBJECT (item), "transform");
470 //pattern->patternTransform = premul * item->transform * pattern->patternTransform * item->transform.inverse() * postmul;
472 // otherwise the formula is much simpler
473 if (set) {
474 pattern->patternTransform = postmul;
475 } else {
476 pattern->patternTransform = pattern_patternTransform(pattern) * postmul;
477 }
478 pattern->patternTransform_set = TRUE;
480 gchar c[256];
481 if (sp_svg_transform_write(c, 256, pattern->patternTransform)) {
482 SP_OBJECT_REPR(pattern)->setAttribute("patternTransform", c);
483 } else {
484 SP_OBJECT_REPR(pattern)->setAttribute("patternTransform", NULL);
485 }
486 }
488 const gchar *
489 pattern_tile (GSList *reprs, NR::Rect bounds, SPDocument *document, NR::Matrix transform, NR::Matrix move)
490 {
491 Inkscape::XML::Node *defsrepr = SP_OBJECT_REPR (SP_DOCUMENT_DEFS (document));
493 Inkscape::XML::Node *repr = sp_repr_new ("svg:pattern");
494 repr->setAttribute("patternUnits", "userSpaceOnUse");
495 sp_repr_set_svg_double(repr, "width", bounds.extent(NR::X));
496 sp_repr_set_svg_double(repr, "height", bounds.extent(NR::Y));
498 gchar t[256];
499 if (sp_svg_transform_write(t, 256, transform)) {
500 repr->setAttribute("patternTransform", t);
501 } else {
502 repr->setAttribute("patternTransform", NULL);
503 }
506 defsrepr->appendChild(repr);
507 const gchar *pat_id = repr->attribute("id");
508 SPObject *pat_object = document->getObjectById(pat_id);
510 for (GSList *i = reprs; i != NULL; i = i->next) {
511 Inkscape::XML::Node *node = (Inkscape::XML::Node *)(i->data);
512 SPItem *copy = SP_ITEM(pat_object->appendChildRepr(node));
514 NR::Matrix dup_transform;
515 if (!sp_svg_transform_read (node->attribute("transform"), &dup_transform))
516 dup_transform = NR::identity();
517 dup_transform *= move;
519 sp_item_write_transform(copy, SP_OBJECT_REPR(copy), dup_transform);
520 }
522 Inkscape::GC::release(repr);
523 return pat_id;
524 }
526 SPPattern *
527 pattern_getroot (SPPattern *pat)
528 {
529 for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
530 if (sp_object_first_child(SP_OBJECT(pat_i))) { // find the first one with children
531 return pat_i;
532 }
533 }
534 return pat; // document is broken, we can't get to root; but at least we can return pat which is supposedly a valid pattern
535 }
539 // Access functions that look up fields up the chain of referenced patterns and return the first one which is set
541 guint pattern_patternUnits (SPPattern *pat)
542 {
543 for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
544 if (pat_i->patternUnits_set)
545 return pat_i->patternUnits;
546 }
547 return pat->patternUnits;
548 }
550 guint pattern_patternContentUnits (SPPattern *pat)
551 {
552 for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
553 if (pat_i->patternContentUnits_set)
554 return pat_i->patternContentUnits;
555 }
556 return pat->patternContentUnits;
557 }
559 NR::Matrix const &pattern_patternTransform(SPPattern const *pat)
560 {
561 for (SPPattern const *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
562 if (pat_i->patternTransform_set)
563 return pat_i->patternTransform;
564 }
565 return pat->patternTransform;
566 }
568 gdouble pattern_x (SPPattern *pat)
569 {
570 for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
571 if (pat_i->x._set)
572 return pat_i->x.computed;
573 }
574 return 0;
575 }
577 gdouble pattern_y (SPPattern *pat)
578 {
579 for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
580 if (pat_i->y._set)
581 return pat_i->y.computed;
582 }
583 return 0;
584 }
586 gdouble pattern_width (SPPattern *pat)
587 {
588 for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
589 if (pat_i->width._set)
590 return pat_i->width.computed;
591 }
592 return 0;
593 }
595 gdouble pattern_height (SPPattern *pat)
596 {
597 for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
598 if (pat_i->height._set)
599 return pat_i->height.computed;
600 }
601 return 0;
602 }
604 NRRect *pattern_viewBox (SPPattern *pat)
605 {
606 for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
607 if (pat_i->viewBox_set)
608 return &(pat_i->viewBox);
609 }
610 return &(pat->viewBox);
611 }
613 bool pattern_hasItemChildren (SPPattern *pat)
614 {
615 for (SPObject *child = sp_object_first_child(SP_OBJECT(pat)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
616 if (SP_IS_ITEM (child)) {
617 return true;
618 }
619 }
620 return false;
621 }
625 /* Painter */
627 static void sp_pat_fill (SPPainter *painter, NRPixBlock *pb);
629 /**
630 Creates a painter (i.e. the thing that does actual filling at the given zoom).
631 See (*) below for why the parent_transform may be necessary.
632 */
633 static SPPainter *
634 sp_pattern_painter_new (SPPaintServer *ps, NR::Matrix const &full_transform, NR::Matrix const &parent_transform, const NRRect *bbox)
635 {
636 SPPattern *pat = SP_PATTERN (ps);
637 SPPatPainter *pp = g_new (SPPatPainter, 1);
639 pp->painter.type = SP_PAINTER_IND;
640 pp->painter.fill = sp_pat_fill;
642 pp->pat = pat;
644 if (pattern_patternUnits (pat) == SP_PATTERN_UNITS_OBJECTBOUNDINGBOX) {
645 /* BBox to user coordinate system */
646 NR::Matrix bbox2user (bbox->x1 - bbox->x0, 0.0, 0.0, bbox->y1 - bbox->y0, bbox->x0, bbox->y0);
648 // the final patternTransform, taking into account bbox
649 NR::Matrix const ps2user(pattern_patternTransform(pat) * bbox2user);
651 // see (*) comment below
652 NR::Matrix ps2px = ps2user * full_transform;
654 ps2px.copyto (&pp->ps2px);
656 } else {
657 /* Problem: What to do, if we have mixed lengths and percentages? */
658 /* Currently we do ignore percentages at all, but that is not good (lauris) */
660 /* fixme: We may try to normalize here too, look at linearGradient (Lauris) */
662 // (*) The spec says, "This additional transformation matrix [patternTransform] is
663 // post-multiplied to (i.e., inserted to the right of) any previously defined
664 // transformations, including the implicit transformation necessary to convert from
665 // object bounding box units to user space." To me, this means that the order should be:
666 // item_transform * patternTransform * parent_transform
667 // However both Batik and Adobe plugin use:
668 // patternTransform * item_transform * parent_transform
669 // So here I comply with the majority opinion, but leave my interpretation commented out below.
670 // (To get item_transform, I subtract parent from full.)
672 //NR::Matrix ps2px = (full_transform / parent_transform) * pattern_patternTransform(pat) * parent_transform;
673 NR::Matrix ps2px = pattern_patternTransform(pat) * full_transform;
675 ps2px.copyto (&pp->ps2px);
676 }
678 nr_matrix_invert (&pp->px2ps, &pp->ps2px);
680 if (pat->viewBox_set) {
681 gdouble tmp_x = (pattern_viewBox(pat)->x1 - pattern_viewBox(pat)->x0) / pattern_width (pat);
682 gdouble tmp_y = (pattern_viewBox(pat)->y1 - pattern_viewBox(pat)->y0) / pattern_height (pat);
684 // FIXME: preserveAspectRatio must be taken into account here too!
685 NR::Matrix vb2ps (tmp_x, 0.0, 0.0, tmp_y, pattern_x(pat) - pattern_viewBox(pat)->x0, pattern_y(pat) - pattern_viewBox(pat)->y0);
687 NR::Matrix vb2us = vb2ps * pattern_patternTransform(pat);
689 // see (*)
690 NR::Matrix pcs2px = vb2us * full_transform;
692 pcs2px.copyto (&pp->pcs2px);
693 } else {
694 NR::Matrix pcs2px;
696 /* No viewbox, have to parse units */
697 if (pattern_patternContentUnits (pat) == SP_PATTERN_UNITS_OBJECTBOUNDINGBOX) {
698 /* BBox to user coordinate system */
699 NR::Matrix bbox2user (bbox->x1 - bbox->x0, 0.0, 0.0, bbox->y1 - bbox->y0, bbox->x0, bbox->y0);
701 NR::Matrix pcs2user = pattern_patternTransform(pat) * bbox2user;
703 // see (*)
704 pcs2px = pcs2user * full_transform;
705 } else {
706 // see (*)
707 //pcs2px = (full_transform / parent_transform) * pattern_patternTransform(pat) * parent_transform;
708 pcs2px = pattern_patternTransform(pat) * full_transform;
709 }
711 pcs2px = NR::translate (pattern_x (pat), pattern_y (pat)) * pcs2px;
713 pcs2px.copyto (&pp->pcs2px);
714 }
716 /* Create arena */
717 pp->arena = NRArena::create();
719 pp->dkey = sp_item_display_key_new (1);
721 /* Create group */
722 pp->root = NRArenaGroup::create(pp->arena);
724 /* Show items */
725 for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
726 if (pat_i && SP_IS_OBJECT (pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children
727 for (SPObject *child = sp_object_first_child(SP_OBJECT(pat_i)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
728 if (SP_IS_ITEM (child)) {
729 NRArenaItem *cai;
730 cai = sp_item_invoke_show (SP_ITEM (child), pp->arena, pp->dkey, SP_ITEM_REFERENCE_FLAGS);
731 nr_arena_item_append_child (pp->root, cai);
732 nr_arena_item_unref (cai);
733 }
734 }
735 break; // do not go further up the chain if children are found
736 }
737 }
739 {
740 NRRect one_tile,tr_tile;
741 one_tile.x0=pattern_x(pp->pat);
742 one_tile.y0=pattern_y(pp->pat);
743 one_tile.x1=one_tile.x0+pattern_width (pp->pat);
744 one_tile.y1=one_tile.y0+pattern_height (pp->pat);
745 nr_rect_d_matrix_transform (&tr_tile, &one_tile, &pp->ps2px);
746 int tr_width=(int)ceil(1.3*(tr_tile.x1-tr_tile.x0));
747 int tr_height=(int)ceil(1.3*(tr_tile.y1-tr_tile.y0));
748 // if ( tr_width < 10000 && tr_height < 10000 && tr_width*tr_height < 1000000 ) {
749 pp->use_cached_tile=false;//true;
750 if ( tr_width > 1000 ) tr_width=1000;
751 if ( tr_height > 1000 ) tr_height=1000;
752 pp->cached_bbox.x0=0;
753 pp->cached_bbox.y0=0;
754 pp->cached_bbox.x1=tr_width;
755 pp->cached_bbox.y1=tr_height;
757 if (pp->use_cached_tile) {
758 nr_pixblock_setup (&pp->cached_tile,NR_PIXBLOCK_MODE_R8G8B8A8N, pp->cached_bbox.x0, pp->cached_bbox.y0, pp->cached_bbox.x1, pp->cached_bbox.y1,TRUE);
759 }
761 pp->pa2ca.c[0]=((double)tr_width)/(one_tile.x1-one_tile.x0);
762 pp->pa2ca.c[1]=0;
763 pp->pa2ca.c[2]=0;
764 pp->pa2ca.c[3]=((double)tr_height)/(one_tile.y1-one_tile.y0);
765 pp->pa2ca.c[4]=-one_tile.x0*pp->pa2ca.c[0];
766 pp->pa2ca.c[5]=-one_tile.y0*pp->pa2ca.c[1];
767 pp->ca2pa.c[0]=(one_tile.x1-one_tile.x0)/((double)tr_width);
768 pp->ca2pa.c[1]=0;
769 pp->ca2pa.c[2]=0;
770 pp->ca2pa.c[3]=(one_tile.y1-one_tile.y0)/((double)tr_height);
771 pp->ca2pa.c[4]=one_tile.x0;
772 pp->ca2pa.c[5]=one_tile.y0;
773 // } else {
774 // pp->use_cached_tile=false;
775 // }
776 }
778 NRGC gc(NULL);
779 if ( pp->use_cached_tile ) {
780 gc.transform=pp->pa2ca;
781 } else {
782 gc.transform = pp->pcs2px;
783 }
784 nr_arena_item_invoke_update (pp->root, NULL, &gc, NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_ALL);
785 if ( pp->use_cached_tile ) {
786 nr_arena_item_invoke_render (pp->root, &pp->cached_bbox, &pp->cached_tile, 0);
787 } else {
788 // nothing to do now
789 }
791 return (SPPainter *) pp;
792 }
794 static void
795 sp_pattern_painter_free (SPPaintServer *ps, SPPainter *painter)
796 {
797 SPPatPainter *pp = (SPPatPainter *) painter;
798 SPPattern *pat = pp->pat;
800 for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
801 if (pat_i && SP_IS_OBJECT (pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children
802 for (SPObject *child = sp_object_first_child(SP_OBJECT(pat_i)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
803 if (SP_IS_ITEM (child)) {
804 sp_item_invoke_hide (SP_ITEM (child), pp->dkey);
805 }
806 }
807 break; // do not go further up the chain if children are found
808 }
809 }
810 if ( pp->use_cached_tile ) nr_pixblock_release(&pp->cached_tile);
811 g_free (pp);
812 }
814 void
815 get_cached_tile_pixel(SPPatPainter* pp,double x,double y,unsigned char &r,unsigned char &g,unsigned char &b,unsigned char &a)
816 {
817 int ca_h=(int)floor(x);
818 int ca_v=(int)floor(y);
819 int r_x=(int)floor(16*(x-floor(x)));
820 int r_y=(int)floor(16*(y-floor(y)));
821 unsigned int tl_m=(16-r_x)*(16-r_y);
822 unsigned int bl_m=(16-r_x)*r_y;
823 unsigned int tr_m=r_x*(16-r_y);
824 unsigned int br_m=r_x*r_y;
825 int cb_h=ca_h+1;
826 int cb_v=ca_v+1;
827 if ( cb_h >= pp->cached_bbox.x1 ) cb_h=0;
828 if ( cb_v >= pp->cached_bbox.y1 ) cb_v=0;
830 unsigned char* tlx=NR_PIXBLOCK_PX(&pp->cached_tile)+(ca_v*pp->cached_tile.rs)+4*ca_h;
831 unsigned char* trx=NR_PIXBLOCK_PX(&pp->cached_tile)+(ca_v*pp->cached_tile.rs)+4*cb_h;
832 unsigned char* blx=NR_PIXBLOCK_PX(&pp->cached_tile)+(cb_v*pp->cached_tile.rs)+4*ca_h;
833 unsigned char* brx=NR_PIXBLOCK_PX(&pp->cached_tile)+(cb_v*pp->cached_tile.rs)+4*cb_h;
835 unsigned int tl_c=tlx[0];
836 unsigned int tr_c=trx[0];
837 unsigned int bl_c=blx[0];
838 unsigned int br_c=brx[0];
839 unsigned int f_c=(tl_m*tl_c+tr_m*tr_c+bl_m*bl_c+br_m*br_c)>>8;
840 r=f_c;
841 tl_c=tlx[1];
842 tr_c=trx[1];
843 bl_c=blx[1];
844 br_c=brx[1];
845 f_c=(tl_m*tl_c+tr_m*tr_c+bl_m*bl_c+br_m*br_c)>>8;
846 g=f_c;
847 tl_c=tlx[2];
848 tr_c=trx[2];
849 bl_c=blx[2];
850 br_c=brx[2];
851 f_c=(tl_m*tl_c+tr_m*tr_c+bl_m*bl_c+br_m*br_c)>>8;
852 b=f_c;
853 tl_c=tlx[3];
854 tr_c=trx[3];
855 bl_c=blx[3];
856 br_c=brx[3];
857 f_c=(tl_m*tl_c+tr_m*tr_c+bl_m*bl_c+br_m*br_c)>>8;
858 a=f_c;
859 }
861 static void
862 sp_pat_fill (SPPainter *painter, NRPixBlock *pb)
863 {
864 SPPatPainter *pp;
865 NRRect ba, psa;
866 NRRectL area;
867 double x, y;
869 pp = (SPPatPainter *) painter;
871 if (pattern_width (pp->pat) < NR_EPSILON) return;
872 if (pattern_height (pp->pat) < NR_EPSILON) return;
874 /* Find buffer area in gradient space */
875 /* fixme: This is suboptimal (Lauris) */
877 if ( pp->use_cached_tile ) {
878 double pat_w=pattern_width (pp->pat);
879 double pat_h=pattern_height (pp->pat);
880 if ( pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8N || pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8P ) { // same thing because it's filling an empty pixblock
881 unsigned char* lpx=NR_PIXBLOCK_PX(pb);
882 double px_y=pb->area.y0;
883 for (int j=pb->area.y0;j<pb->area.y1;j++) {
884 unsigned char* cpx=lpx;
885 double px_x = pb->area.x0;
887 double ps_x=pp->px2ps.c[0]*px_x+pp->px2ps.c[2]*px_y+pp->px2ps.c[4];
888 double ps_y=pp->px2ps.c[1]*px_x+pp->px2ps.c[3]*px_y+pp->px2ps.c[5];
889 for (int i=pb->area.x0;i<pb->area.x1;i++) {
890 while ( ps_x > pat_w ) ps_x-=pat_w;
891 while ( ps_x < 0 ) ps_x+=pat_w;
892 while ( ps_y > pat_h ) ps_y-=pat_h;
893 while ( ps_y < 0 ) ps_y+=pat_h;
894 double ca_x=pp->pa2ca.c[0]*ps_x+pp->pa2ca.c[2]*ps_y+pp->pa2ca.c[4];
895 double ca_y=pp->pa2ca.c[1]*ps_x+pp->pa2ca.c[3]*ps_y+pp->pa2ca.c[5];
896 unsigned char n_a,n_r,n_g,n_b;
897 get_cached_tile_pixel(pp,ca_x,ca_y,n_r,n_g,n_b,n_a);
898 cpx[0]=n_r;
899 cpx[1]=n_g;
900 cpx[2]=n_b;
901 cpx[3]=n_a;
903 px_x+=1.0;
904 ps_x+=pp->px2ps.c[0];
905 ps_y+=pp->px2ps.c[1];
906 cpx+=4;
907 }
908 px_y+=1.0;
909 lpx+=pb->rs;
910 }
911 } else if ( pb->mode == NR_PIXBLOCK_MODE_R8G8B8 ) {
912 unsigned char* lpx=NR_PIXBLOCK_PX(pb);
913 double px_y=pb->area.y0;
914 for (int j=pb->area.y0;j<pb->area.y1;j++) {
915 unsigned char* cpx=lpx;
916 double px_x = pb->area.x0;
918 double ps_x=pp->px2ps.c[0]*px_x+pp->px2ps.c[2]*px_y+pp->px2ps.c[4];
919 double ps_y=pp->px2ps.c[1]*px_x+pp->px2ps.c[3]*px_y+pp->px2ps.c[5];
920 for (int i=pb->area.x0;i<pb->area.x1;i++) {
921 while ( ps_x > pat_w ) ps_x-=pat_w;
922 while ( ps_x < 0 ) ps_x+=pat_w;
923 while ( ps_y > pat_h ) ps_y-=pat_h;
924 while ( ps_y < 0 ) ps_y+=pat_h;
925 double ca_x=pp->pa2ca.c[0]*ps_x+pp->pa2ca.c[2]*ps_y+pp->pa2ca.c[4];
926 double ca_y=pp->pa2ca.c[1]*ps_x+pp->pa2ca.c[3]*ps_y+pp->pa2ca.c[5];
927 unsigned char n_a,n_r,n_g,n_b;
928 get_cached_tile_pixel(pp,ca_x,ca_y,n_r,n_g,n_b,n_a);
929 cpx[0]=n_r;
930 cpx[1]=n_g;
931 cpx[2]=n_b;
933 px_x+=1.0;
934 ps_x+=pp->px2ps.c[0];
935 ps_y+=pp->px2ps.c[1];
936 cpx+=4;
937 }
938 px_y+=1.0;
939 lpx+=pb->rs;
940 }
941 }
942 } else {
943 ba.x0 = pb->area.x0;
944 ba.y0 = pb->area.y0;
945 ba.x1 = pb->area.x1;
946 ba.y1 = pb->area.y1;
947 nr_rect_d_matrix_transform (&psa, &ba, &pp->px2ps);
949 psa.x0 = floor ((psa.x0 - pattern_x (pp->pat)) / pattern_width (pp->pat)) -1;
950 psa.y0 = floor ((psa.y0 - pattern_y (pp->pat)) / pattern_height (pp->pat)) -1;
951 psa.x1 = ceil ((psa.x1 - pattern_x (pp->pat)) / pattern_width (pp->pat)) +1;
952 psa.y1 = ceil ((psa.y1 - pattern_y (pp->pat)) / pattern_height (pp->pat)) +1;
954 for (y = psa.y0; y < psa.y1; y++) {
955 for (x = psa.x0; x < psa.x1; x++) {
956 NRPixBlock ppb;
957 double psx, psy;
959 psx = x * pattern_width (pp->pat);
960 psy = y * pattern_height (pp->pat);
962 area.x0 = (gint32)(pb->area.x0 - (pp->ps2px.c[0] * psx + pp->ps2px.c[2] * psy));
963 area.y0 = (gint32)(pb->area.y0 - (pp->ps2px.c[1] * psx + pp->ps2px.c[3] * psy));
964 area.x1 = area.x0 + pb->area.x1 - pb->area.x0;
965 area.y1 = area.y0 + pb->area.y1 - pb->area.y0;
967 // We do not update here anymore
969 // Set up buffer
970 // fixme: (Lauris)
971 nr_pixblock_setup_extern (&ppb, pb->mode, area.x0, area.y0, area.x1, area.y1, NR_PIXBLOCK_PX (pb), pb->rs, FALSE, FALSE);
973 nr_arena_item_invoke_render (pp->root, &area, &ppb, 0);
975 nr_pixblock_release (&ppb);
976 }
977 }
978 }
979 }