1 #define __NR_ARENA_SHAPE_C__
3 /*
4 * RGBA display list system for inkscape
5 *
6 * Author:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 *
9 * Copyright (C) 2001-2002 Lauris Kaplinski
10 * Copyright (C) 2001 Ximian, Inc.
11 *
12 * Released under GNU GPL, read the file 'COPYING' for more information
13 */
15 #include <2geom/svg-path.h>
16 #include <2geom/svg-path-parser.h>
17 #include <display/canvas-arena.h>
18 #include <display/nr-arena.h>
19 #include <display/nr-arena-shape.h>
20 #include "display/curve.h"
21 #include <libnr/n-art-bpath.h>
22 #include <libnr/nr-path.h>
23 #include <libnr/nr-pixops.h>
24 #include <libnr/nr-matrix-ops.h>
25 #include <libnr/nr-matrix-fns.h>
26 #include <libnr/nr-blit.h>
27 #include <libnr/nr-convert2geom.h>
28 #include <2geom/pathvector.h>
29 #include <2geom/curves.h>
30 #include <livarot/Path.h>
31 #include <livarot/float-line.h>
32 #include <livarot/int-line.h>
33 #include <style.h>
34 #include "prefs-utils.h"
35 #include "inkscape-cairo.h"
36 #include "helper/geom.h"
37 #include "sp-filter.h"
38 #include "sp-filter-reference.h"
39 #include "display/nr-filter.h"
40 #include <typeinfo>
41 #include <cairo.h>
43 #include <glib.h>
44 #include "svg/svg.h"
45 #include <fenv.h>
47 //int showRuns=0;
48 void nr_pixblock_render_shape_mask_or(NRPixBlock &m,Shape* theS);
50 static void nr_arena_shape_class_init(NRArenaShapeClass *klass);
51 static void nr_arena_shape_init(NRArenaShape *shape);
52 static void nr_arena_shape_finalize(NRObject *object);
54 static NRArenaItem *nr_arena_shape_children(NRArenaItem *item);
55 static void nr_arena_shape_add_child(NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref);
56 static void nr_arena_shape_remove_child(NRArenaItem *item, NRArenaItem *child);
57 static void nr_arena_shape_set_child_position(NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref);
59 static guint nr_arena_shape_update(NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset);
60 static unsigned int nr_arena_shape_render(cairo_t *ct, NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags);
61 static guint nr_arena_shape_clip(NRArenaItem *item, NRRectL *area, NRPixBlock *pb);
62 static NRArenaItem *nr_arena_shape_pick(NRArenaItem *item, NR::Point p, double delta, unsigned int sticky);
64 static NRArenaItemClass *shape_parent_class;
66 NRType
67 nr_arena_shape_get_type(void)
68 {
69 static NRType type = 0;
70 if (!type) {
71 type = nr_object_register_type(NR_TYPE_ARENA_ITEM,
72 "NRArenaShape",
73 sizeof(NRArenaShapeClass),
74 sizeof(NRArenaShape),
75 (void (*)(NRObjectClass *)) nr_arena_shape_class_init,
76 (void (*)(NRObject *)) nr_arena_shape_init);
77 }
78 return type;
79 }
81 static void
82 nr_arena_shape_class_init(NRArenaShapeClass *klass)
83 {
84 NRObjectClass *object_class;
85 NRArenaItemClass *item_class;
87 object_class = (NRObjectClass *) klass;
88 item_class = (NRArenaItemClass *) klass;
90 shape_parent_class = (NRArenaItemClass *) ((NRObjectClass *) klass)->parent;
92 object_class->finalize = nr_arena_shape_finalize;
93 object_class->cpp_ctor = NRObject::invoke_ctor<NRArenaShape>;
95 item_class->children = nr_arena_shape_children;
96 item_class->add_child = nr_arena_shape_add_child;
97 item_class->set_child_position = nr_arena_shape_set_child_position;
98 item_class->remove_child = nr_arena_shape_remove_child;
99 item_class->update = nr_arena_shape_update;
100 item_class->render = nr_arena_shape_render;
101 item_class->clip = nr_arena_shape_clip;
102 item_class->pick = nr_arena_shape_pick;
103 }
105 /**
106 * Initializes the arena shape, setting all parameters to null, 0, false,
107 * or other defaults
108 */
109 static void
110 nr_arena_shape_init(NRArenaShape *shape)
111 {
112 shape->curve = NULL;
113 shape->style = NULL;
114 shape->paintbox.x0 = shape->paintbox.y0 = 0.0F;
115 shape->paintbox.x1 = shape->paintbox.y1 = 256.0F;
117 shape->ctm.set_identity();
118 shape->fill_painter = NULL;
119 shape->stroke_painter = NULL;
120 shape->cached_fill = NULL;
121 shape->cached_stroke = NULL;
122 shape->cached_fpartialy = false;
123 shape->cached_spartialy = false;
124 shape->fill_shp = NULL;
125 shape->stroke_shp = NULL;
127 shape->delayed_shp = false;
129 shape->approx_bbox.x0 = shape->approx_bbox.y0 = 0;
130 shape->approx_bbox.x1 = shape->approx_bbox.y1 = 0;
131 shape->cached_fctm.set_identity();
132 shape->cached_sctm.set_identity();
134 shape->markers = NULL;
136 shape->last_pick = NULL;
137 shape->repick_after = 0;
138 }
140 static void
141 nr_arena_shape_finalize(NRObject *object)
142 {
143 NRArenaShape *shape = (NRArenaShape *) object;
145 if (shape->fill_shp) delete shape->fill_shp;
146 if (shape->stroke_shp) delete shape->stroke_shp;
147 if (shape->cached_fill) delete shape->cached_fill;
148 if (shape->cached_stroke) delete shape->cached_stroke;
149 if (shape->fill_painter) sp_painter_free(shape->fill_painter);
150 if (shape->stroke_painter) sp_painter_free(shape->stroke_painter);
152 if (shape->style) sp_style_unref(shape->style);
153 if (shape->curve) shape->curve->unref();
155 ((NRObjectClass *) shape_parent_class)->finalize(object);
156 }
158 /**
159 * Retrieves the markers from the item
160 */
161 static NRArenaItem *
162 nr_arena_shape_children(NRArenaItem *item)
163 {
164 NRArenaShape *shape = (NRArenaShape *) item;
166 return shape->markers;
167 }
169 /**
170 * Attaches child to item, and if ref is not NULL, sets it and ref->next as
171 * the prev and next items. If ref is NULL, then it sets the item's markers
172 * as the next items.
173 */
174 static void
175 nr_arena_shape_add_child(NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref)
176 {
177 NRArenaShape *shape = (NRArenaShape *) item;
179 if (!ref) {
180 shape->markers = nr_arena_item_attach(item, child, NULL, shape->markers);
181 } else {
182 ref->next = nr_arena_item_attach(item, child, ref, ref->next);
183 }
185 nr_arena_item_request_update(item, NR_ARENA_ITEM_STATE_ALL, FALSE);
186 }
188 /**
189 * Removes child from the shape. If there are no prev items in
190 * the child, it sets items' markers to the next item in the child.
191 */
192 static void
193 nr_arena_shape_remove_child(NRArenaItem *item, NRArenaItem *child)
194 {
195 NRArenaShape *shape = (NRArenaShape *) item;
197 if (child->prev) {
198 nr_arena_item_detach(item, child);
199 } else {
200 shape->markers = nr_arena_item_detach(item, child);
201 }
203 nr_arena_item_request_update(item, NR_ARENA_ITEM_STATE_ALL, FALSE);
204 }
206 /**
207 * Detaches child from item, and if there are no previous items in child, it
208 * sets item's markers to the child. It then attaches the child back onto the item.
209 * If ref is null, it sets the markers to be the next item, otherwise it uses
210 * the next/prev items in ref.
211 */
212 static void
213 nr_arena_shape_set_child_position(NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref)
214 {
215 NRArenaShape *shape = (NRArenaShape *) item;
217 if (child->prev) {
218 nr_arena_item_detach(item, child);
219 } else {
220 shape->markers = nr_arena_item_detach(item, child);
221 }
223 if (!ref) {
224 shape->markers = nr_arena_item_attach(item, child, NULL, shape->markers);
225 } else {
226 ref->next = nr_arena_item_attach(item, child, ref, ref->next);
227 }
229 nr_arena_item_request_render(child);
230 }
232 void nr_arena_shape_update_stroke(NRArenaShape *shape, NRGC* gc, NRRectL *area);
233 void nr_arena_shape_update_fill(NRArenaShape *shape, NRGC *gc, NRRectL *area, bool force_shape = false);
234 void nr_arena_shape_add_bboxes(NRArenaShape* shape, Geom::Rect &bbox);
236 /**
237 * Updates the arena shape 'item' and all of its children, including the markers.
238 */
239 static guint
240 nr_arena_shape_update(NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset)
241 {
242 Geom::Rect boundingbox;
244 NRArenaShape *shape = NR_ARENA_SHAPE(item);
246 unsigned int beststate = NR_ARENA_ITEM_STATE_ALL;
248 unsigned int newstate;
249 for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) {
250 newstate = nr_arena_item_invoke_update(child, area, gc, state, reset);
251 beststate = beststate & newstate;
252 }
254 if (!(state & NR_ARENA_ITEM_STATE_RENDER)) {
255 /* We do not have to create rendering structures */
256 shape->ctm = gc->transform;
257 if (state & NR_ARENA_ITEM_STATE_BBOX) {
258 if (shape->curve) {
259 boundingbox = bounds_exact_transformed(shape->curve->get_pathvector(), to_2geom(gc->transform));
260 item->bbox.x0 = (gint32)(boundingbox[0][0] - 1.0F);
261 item->bbox.y0 = (gint32)(boundingbox[1][0] - 1.0F);
262 item->bbox.x1 = (gint32)(boundingbox[0][1] + 1.9999F);
263 item->bbox.y1 = (gint32)(boundingbox[1][1] + 1.9999F);
264 }
265 if (beststate & NR_ARENA_ITEM_STATE_BBOX) {
266 for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) {
267 nr_rect_l_union(&item->bbox, &item->bbox, &child->bbox);
268 }
269 }
270 }
271 return (state | item->state);
272 }
274 shape->delayed_shp=true;
275 shape->ctm = gc->transform;
276 boundingbox[0][0] = boundingbox[1][0] = NR_HUGE;
277 boundingbox[0][1] = boundingbox[1][1] = -NR_HUGE;
279 bool outline = (NR_ARENA_ITEM(shape)->arena->rendermode == Inkscape::RENDERMODE_OUTLINE);
281 if (shape->curve) {
282 boundingbox = bounds_exact_transformed(shape->curve->get_pathvector(), to_2geom(gc->transform));
284 if (shape->_stroke.paint.type() != NRArenaShape::Paint::NONE || outline) {
285 float width, scale;
286 scale = NR::expansion(gc->transform);
287 width = MAX(0.125, shape->_stroke.width * scale);
288 if ( fabs(shape->_stroke.width * scale) > 0.01 ) { // sinon c'est 0=oon veut pas de bord
289 boundingbox.expandBy(width);
290 }
291 // those pesky miters, now
292 float miterMax=width*shape->_stroke.mitre_limit;
293 if ( miterMax > 0.01 ) {
294 // grunt mode. we should compute the various miters instead (one for each point on the curve)
295 boundingbox.expandBy(miterMax);
296 }
297 }
298 } else {
299 }
300 shape->approx_bbox.x0 = (gint32)(boundingbox[0][0] - 1.0F);
301 shape->approx_bbox.y0 = (gint32)(boundingbox[1][0] - 1.0F);
302 shape->approx_bbox.x1 = (gint32)(boundingbox[0][1] + 1.9999F);
303 shape->approx_bbox.y1 = (gint32)(boundingbox[1][1] + 1.9999F);
304 if ( area && nr_rect_l_test_intersect(area, &shape->approx_bbox) ) shape->delayed_shp=false;
306 /* Release state data */
307 if (TRUE || !NR::transform_equalp(gc->transform, shape->ctm, NR_EPSILON)) {
308 /* Concept test */
309 if (shape->fill_shp) {
310 delete shape->fill_shp;
311 shape->fill_shp = NULL;
312 }
313 }
314 if (shape->stroke_shp) {
315 delete shape->stroke_shp;
316 shape->stroke_shp = NULL;
317 }
318 if (shape->fill_painter) {
319 sp_painter_free(shape->fill_painter);
320 shape->fill_painter = NULL;
321 }
322 if (shape->stroke_painter) {
323 sp_painter_free(shape->stroke_painter);
324 shape->stroke_painter = NULL;
325 }
327 if (!shape->curve ||
328 !shape->style ||
329 shape->curve->is_empty() ||
330 (( shape->_fill.paint.type() == NRArenaShape::Paint::NONE ) &&
331 ( shape->_stroke.paint.type() == NRArenaShape::Paint::NONE && !outline) ))
332 {
333 item->bbox = shape->approx_bbox;
334 return NR_ARENA_ITEM_STATE_ALL;
335 }
337 /* Build state data */
338 if ( shape->delayed_shp ) {
339 item->bbox=shape->approx_bbox;
340 } else {
341 nr_arena_shape_update_stroke(shape, gc, area);
342 nr_arena_shape_update_fill(shape, gc, area);
344 boundingbox[0][0] = boundingbox[0][1] = boundingbox[1][0] = boundingbox[1][1] = 0.0;
345 nr_arena_shape_add_bboxes(shape, boundingbox);
347 shape->approx_bbox.x0 = (gint32)(boundingbox[0][0] - 1.0F);
348 shape->approx_bbox.y0 = (gint32)(boundingbox[1][0] - 1.0F);
349 shape->approx_bbox.x1 = (gint32)(boundingbox[0][1] + 1.9999F);
350 shape->approx_bbox.y1 = (gint32)(boundingbox[1][1] + 1.9999F);
351 }
353 if (boundingbox.isEmpty())
354 return NR_ARENA_ITEM_STATE_ALL;
356 item->bbox.x0 = (gint32)(boundingbox[0][0] - 1.0F);
357 item->bbox.y0 = (gint32)(boundingbox[1][0] - 1.0F);
358 item->bbox.x1 = (gint32)(boundingbox[0][1] + 1.0F);
359 item->bbox.y1 = (gint32)(boundingbox[1][1] + 1.0F);
360 nr_arena_request_render_rect(item->arena, &item->bbox);
362 item->render_opacity = TRUE;
363 if ( shape->_fill.paint.type() == NRArenaShape::Paint::SERVER ) {
364 if (gc && gc->parent) {
365 shape->fill_painter = sp_paint_server_painter_new(shape->_fill.paint.server(),
366 gc->transform, gc->parent->transform,
367 &shape->paintbox);
368 }
369 item->render_opacity = FALSE;
370 }
371 if ( shape->_stroke.paint.type() == NRArenaShape::Paint::SERVER ) {
372 if (gc && gc->parent) {
373 shape->stroke_painter = sp_paint_server_painter_new(shape->_stroke.paint.server(),
374 gc->transform, gc->parent->transform,
375 &shape->paintbox);
376 }
377 item->render_opacity = FALSE;
378 }
379 if ( (shape->_fill.paint.type() != NRArenaShape::Paint::NONE &&
380 shape->_stroke.paint.type() != NRArenaShape::Paint::NONE)
381 || (shape->markers)
382 )
383 {
384 // don't merge item opacity with paint opacity if there is a stroke on the fill, or markers on stroke
385 item->render_opacity = FALSE;
386 }
388 if (beststate & NR_ARENA_ITEM_STATE_BBOX) {
389 for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) {
390 nr_rect_l_union(&item->bbox, &item->bbox, &child->bbox);
391 }
392 }
394 return NR_ARENA_ITEM_STATE_ALL;
395 }
397 int matrix_is_isometry(NR::Matrix p) {
398 NR::Matrix tp;
399 // transposition
400 tp[0]=p[0];
401 tp[1]=p[2];
402 tp[2]=p[1];
403 tp[3]=p[3];
404 for (int i = 4; i < 6; i++) // shut valgrind up :)
405 tp[i] = p[i] = 0;
406 NR::Matrix isom = tp*p; // A^T * A = adjunct?
407 // Is the adjunct nearly an identity function?
408 if (isom.is_translation(0.01)) {
409 // the transformation is an isometry -> no need to recompute
410 // the uncrossed polygon
411 if ( p.det() < 0 )
412 return -1;
413 else
414 return 1;
415 }
416 return 0;
417 }
419 static bool is_inner_area(NRRectL const &outer, NRRectL const &inner) {
420 return (outer.x0 <= inner.x0 && outer.y0 <= inner.y0 && outer.x1 >= inner.x1 && outer.y1 >= inner.y1);
421 }
423 /* returns true if the pathvector has a region that needs fill.
424 * is for optimizing purposes, so should be fast and can falsely return true.
425 * CANNOT falsely return false. */
426 static bool has_inner_area(Geom::PathVector const & pv) {
427 // return false for the cases where there is surely no region to be filled
428 if (pv.empty())
429 return false;
431 if ( (pv.size() == 1) && (pv.front().size() <= 1) ) {
432 // vector has only one path with only one segment, see if that's a non-curve segment: that would mean no internal region
433 Geom::Curve const * c = & pv.front().front();
434 if ( dynamic_cast<Geom::LineSegment const*>(c) ||
435 dynamic_cast<Geom::HLineSegment const*>(c) ||
436 dynamic_cast<Geom::VLineSegment const*>(c) )
437 {
438 return false;
439 }
440 }
442 return true; //too costly to see if it has region to be filled, so return true.
443 }
445 /** force_shape is used for clipping paths, when we need the shape for clipping even if it's not filled */
446 void
447 nr_arena_shape_update_fill(NRArenaShape *shape, NRGC *gc, NRRectL *area, bool force_shape)
448 {
449 if ((shape->_fill.paint.type() != NRArenaShape::Paint::NONE || force_shape) &&
450 // ((shape->curve->get_length() > 2) || (SP_CURVE_BPATH(shape->curve)[1].code == NR_CURVETO)) ) { // <-- this used to be the old code, i think it has to determine that the path has a sort of 'internal region' where fill would occur
451 has_inner_area(shape->curve->get_pathvector()) ) {
452 if (TRUE || !shape->fill_shp) {
453 NR::Matrix cached_to_new = NR::identity();
454 int isometry = 0;
455 if ( shape->cached_fill ) {
456 if (shape->cached_fctm == gc->transform) {
457 isometry = 2; // identity
458 } else {
459 cached_to_new = shape->cached_fctm.inverse() * gc->transform;
460 isometry = matrix_is_isometry(cached_to_new);
461 }
462 if (0 != isometry && !is_inner_area(shape->cached_farea, *area))
463 isometry = 0;
464 }
465 if ( isometry == 0 ) {
466 if ( shape->cached_fill == NULL ) shape->cached_fill=new Shape;
467 shape->cached_fill->Reset();
469 Path* thePath=new Path;
470 Shape* theShape=new Shape;
471 {
472 Geom::Matrix tempMat(to_2geom(gc->transform));
473 thePath->LoadPathVector(shape->curve->get_pathvector(), tempMat, true);
474 }
476 if (is_inner_area(*area, NR_ARENA_ITEM(shape)->bbox)) {
477 thePath->Convert(1.0);
478 shape->cached_fpartialy = false;
479 } else {
480 thePath->Convert(area, 1.0);
481 shape->cached_fpartialy = true;
482 }
484 thePath->Fill(theShape, 0);
486 if ( shape->_fill.rule == NRArenaShape::EVEN_ODD ) {
487 shape->cached_fill->ConvertToShape(theShape, fill_oddEven);
488 // alternatively, this speeds up rendering of oddeven shapes but disables AA :(
489 //shape->cached_fill->Copy(theShape);
490 } else {
491 shape->cached_fill->ConvertToShape(theShape, fill_nonZero);
492 }
493 shape->cached_fctm=gc->transform;
494 shape->cached_farea = *area;
495 delete theShape;
496 delete thePath;
497 if ( shape->fill_shp == NULL )
498 shape->fill_shp = new Shape;
500 shape->fill_shp->Copy(shape->cached_fill);
502 } else if ( 2 == isometry ) {
503 if ( shape->fill_shp == NULL ) {
504 shape->fill_shp = new Shape;
505 shape->fill_shp->Copy(shape->cached_fill);
506 }
507 } else {
509 if ( shape->fill_shp == NULL )
510 shape->fill_shp = new Shape;
512 shape->fill_shp->Reset(shape->cached_fill->numberOfPoints(),
513 shape->cached_fill->numberOfEdges());
514 for (int i = 0; i < shape->cached_fill->numberOfPoints(); i++)
515 shape->fill_shp->AddPoint(shape->cached_fill->getPoint(i).x * cached_to_new);
516 if ( isometry == 1 ) {
517 for (int i = 0; i < shape->cached_fill->numberOfEdges(); i++)
518 shape->fill_shp->AddEdge(shape->cached_fill->getEdge(i).st,
519 shape->cached_fill->getEdge(i).en);
520 } else if ( isometry == -1 ) { // need to flip poly.
521 for (int i = 0; i < shape->cached_fill->numberOfEdges(); i++)
522 shape->fill_shp->AddEdge(shape->cached_fill->getEdge(i).en,
523 shape->cached_fill->getEdge(i).st);
524 }
525 shape->fill_shp->ForceToPolygon();
526 shape->fill_shp->needPointsSorting();
527 shape->fill_shp->needEdgesSorting();
528 }
529 shape->delayed_shp |= shape->cached_fpartialy;
530 }
531 }
532 }
534 void
535 nr_arena_shape_update_stroke(NRArenaShape *shape,NRGC* gc, NRRectL *area)
536 {
537 SPStyle* style = shape->style;
539 float const scale = NR::expansion(gc->transform);
541 bool outline = (NR_ARENA_ITEM(shape)->arena->rendermode == Inkscape::RENDERMODE_OUTLINE);
543 if (outline) {
544 // cairo does not need the livarot path for rendering
545 return;
546 }
548 // after switching normal stroke rendering to cairo too, optimize this: lower tolerance, disregard dashes
549 // (since it will only be used for picking, not for rendering)
551 if (outline ||
552 ((shape->_stroke.paint.type() != NRArenaShape::Paint::NONE) &&
553 ( fabs(shape->_stroke.width * scale) > 0.01 ))) { // sinon c'est 0=oon veut pas de bord
555 float style_width = MAX(0.125, shape->_stroke.width * scale);
556 float width;
557 if (outline) {
558 width = 0.5; // 1 pixel wide, independent of zoom
559 } else {
560 width = style_width;
561 }
563 NR::Matrix cached_to_new = NR::identity();
565 int isometry = 0;
566 if ( shape->cached_stroke ) {
567 if (shape->cached_sctm == gc->transform) {
568 isometry = 2; // identity
569 } else {
570 cached_to_new = shape->cached_sctm.inverse() * gc->transform;
571 isometry = matrix_is_isometry(cached_to_new);
572 }
573 if (0 != isometry && !is_inner_area(shape->cached_sarea, *area))
574 isometry = 0;
575 if (0 != isometry && width != shape->cached_width) {
576 // if this happens without setting style, we have just switched to outline or back
577 isometry = 0;
578 }
579 }
581 if ( isometry == 0 ) {
582 if ( shape->cached_stroke == NULL ) shape->cached_stroke=new Shape;
583 shape->cached_stroke->Reset();
584 Path* thePath = new Path;
585 Shape* theShape = new Shape;
586 {
587 Geom::Matrix tempMat( to_2geom(gc->transform) );
588 thePath->LoadPathVector(shape->curve->get_pathvector(), tempMat, true);
589 }
591 // add some padding to the rendering area, so clipped path does not go into a render area
592 NRRectL padded_area = *area;
593 padded_area.x0 -= (NR::ICoord)width;
594 padded_area.x1 += (NR::ICoord)width;
595 padded_area.y0 -= (NR::ICoord)width;
596 padded_area.y1 += (NR::ICoord)width;
597 if ((style->stroke_dash.n_dash && !outline) || is_inner_area(padded_area, NR_ARENA_ITEM(shape)->bbox)) {
598 thePath->Convert((outline) ? 4.0 : 1.0);
599 shape->cached_spartialy = false;
600 }
601 else {
602 thePath->Convert(&padded_area, (outline) ? 4.0 : 1.0);
603 shape->cached_spartialy = true;
604 }
606 if (style->stroke_dash.n_dash && !outline) {
607 thePath->DashPolylineFromStyle(style, scale, 1.0);
608 }
610 ButtType butt=butt_straight;
611 switch (shape->_stroke.cap) {
612 case NRArenaShape::BUTT_CAP:
613 butt = butt_straight;
614 break;
615 case NRArenaShape::ROUND_CAP:
616 butt = butt_round;
617 break;
618 case NRArenaShape::SQUARE_CAP:
619 butt = butt_square;
620 break;
621 }
622 JoinType join=join_straight;
623 switch (shape->_stroke.join) {
624 case NRArenaShape::MITRE_JOIN:
625 join = join_pointy;
626 break;
627 case NRArenaShape::ROUND_JOIN:
628 join = join_round;
629 break;
630 case NRArenaShape::BEVEL_JOIN:
631 join = join_straight;
632 break;
633 }
635 if (outline) {
636 butt = butt_straight;
637 join = join_straight;
638 }
640 thePath->Stroke(theShape, false, 0.5*width, join, butt,
641 0.5*width*shape->_stroke.mitre_limit);
644 if (outline) {
645 // speeds it up, but uses evenodd for the stroke shape (which does not matter for 1-pixel wide outline)
646 shape->cached_stroke->Copy(theShape);
647 } else {
648 shape->cached_stroke->ConvertToShape(theShape, fill_nonZero);
649 }
651 shape->cached_width = width;
653 shape->cached_sctm=gc->transform;
654 shape->cached_sarea = *area;
655 delete thePath;
656 delete theShape;
657 if ( shape->stroke_shp == NULL ) shape->stroke_shp=new Shape;
659 shape->stroke_shp->Copy(shape->cached_stroke);
661 } else if ( 2 == isometry ) {
662 if ( shape->stroke_shp == NULL ) {
663 shape->stroke_shp=new Shape;
664 shape->stroke_shp->Copy(shape->cached_stroke);
665 }
666 } else {
667 if ( shape->stroke_shp == NULL )
668 shape->stroke_shp=new Shape;
669 shape->stroke_shp->Reset(shape->cached_stroke->numberOfPoints(), shape->cached_stroke->numberOfEdges());
670 for (int i = 0; i < shape->cached_stroke->numberOfPoints(); i++)
671 shape->stroke_shp->AddPoint(shape->cached_stroke->getPoint(i).x * cached_to_new);
672 if ( isometry == 1 ) {
673 for (int i = 0; i < shape->cached_stroke->numberOfEdges(); i++)
674 shape->stroke_shp->AddEdge(shape->cached_stroke->getEdge(i).st,
675 shape->cached_stroke->getEdge(i).en);
676 } else if ( isometry == -1 ) {
677 for (int i = 0; i < shape->cached_stroke->numberOfEdges(); i++)
678 shape->stroke_shp->AddEdge(shape->cached_stroke->getEdge(i).en,
679 shape->cached_stroke->getEdge(i).st);
680 }
681 shape->stroke_shp->ForceToPolygon();
682 shape->stroke_shp->needPointsSorting();
683 shape->stroke_shp->needEdgesSorting();
684 }
685 shape->delayed_shp |= shape->cached_spartialy;
686 }
687 }
690 void
691 nr_arena_shape_add_bboxes(NRArenaShape* shape, Geom::Rect &bbox)
692 {
693 /* TODO: are these two if's mutually exclusive? ( i.e. "shape->stroke_shp <=> !shape->fill_shp" )
694 * if so, then this can be written much more compact ! */
696 if ( shape->stroke_shp ) {
697 Shape *larger = shape->stroke_shp;
698 larger->CalcBBox();
699 larger->leftX = floor(larger->leftX);
700 larger->rightX = ceil(larger->rightX);
701 larger->topY = floor(larger->topY);
702 larger->bottomY = ceil(larger->bottomY);
703 Geom::Rect stroke_bbox( Geom::Interval(larger->leftX, larger->rightX),
704 Geom::Interval(larger->topY, larger->bottomY) );
705 bbox.unionWith(stroke_bbox);
706 }
708 if ( shape->fill_shp ) {
709 Shape *larger = shape->fill_shp;
710 larger->CalcBBox();
711 larger->leftX = floor(larger->leftX);
712 larger->rightX = ceil(larger->rightX);
713 larger->topY = floor(larger->topY);
714 larger->bottomY = ceil(larger->bottomY);
715 Geom::Rect fill_bbox( Geom::Interval(larger->leftX, larger->rightX),
716 Geom::Interval(larger->topY, larger->bottomY) );
717 bbox.unionWith(fill_bbox);
718 }
719 }
721 // cairo outline rendering:
722 static unsigned int
723 cairo_arena_shape_render_outline(cairo_t *ct, NRArenaItem *item, NR::Maybe<NR::Rect> area)
724 {
725 NRArenaShape *shape = NR_ARENA_SHAPE(item);
727 if (!ct)
728 return item->state;
730 guint32 rgba = NR_ARENA_ITEM(shape)->arena->outlinecolor;
731 // FIXME: we use RGBA buffers but cairo writes BGRA (on i386), so we must cheat
732 // by setting color channels in the "wrong" order
733 cairo_set_source_rgba(ct, SP_RGBA32_B_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_R_F(rgba), SP_RGBA32_A_F(rgba));
735 cairo_set_line_width(ct, 0.5);
736 cairo_set_tolerance(ct, 1.25); // low quality, but good enough for outline mode
737 cairo_new_path(ct);
739 feed_pathvector_to_cairo (ct, shape->curve->get_pathvector(), to_2geom(shape->ctm), area, true, 0);
741 cairo_stroke(ct);
743 return item->state;
744 }
746 // cairo stroke rendering (flat color only so far!):
747 // works on canvas, but wrongs the colors in nonpremul buffers: icons and png export
748 // (need to switch them to premul before this can be enabled)
749 void
750 cairo_arena_shape_render_stroke(NRArenaItem *item, NRRectL *area, NRPixBlock *pb)
751 {
752 NRArenaShape *shape = NR_ARENA_SHAPE(item);
753 SPStyle const *style = shape->style;
755 float const scale = NR::expansion(shape->ctm);
757 if (fabs(shape->_stroke.width * scale) < 0.01)
758 return;
760 cairo_t *ct = nr_create_cairo_context (area, pb);
762 if (!ct)
763 return;
765 guint32 rgba;
766 if ( item->render_opacity ) {
767 rgba = shape->_stroke.paint.color().toRGBA32( shape->_stroke.opacity *
768 SP_SCALE24_TO_FLOAT(style->opacity.value) );
769 } else {
770 rgba = shape->_stroke.paint.color().toRGBA32( shape->_stroke.opacity );
771 }
773 // FIXME: we use RGBA buffers but cairo writes BGRA (on i386), so we must cheat
774 // by setting color channels in the "wrong" order
775 cairo_set_source_rgba(ct, SP_RGBA32_B_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_R_F(rgba), SP_RGBA32_A_F(rgba));
777 float style_width = MAX(0.125, shape->_stroke.width * scale);
778 cairo_set_line_width(ct, style_width);
780 switch (shape->_stroke.cap) {
781 case NRArenaShape::BUTT_CAP:
782 cairo_set_line_cap(ct, CAIRO_LINE_CAP_BUTT);
783 break;
784 case NRArenaShape::ROUND_CAP:
785 cairo_set_line_cap(ct, CAIRO_LINE_CAP_ROUND);
786 break;
787 case NRArenaShape::SQUARE_CAP:
788 cairo_set_line_cap(ct, CAIRO_LINE_CAP_SQUARE);
789 break;
790 }
791 switch (shape->_stroke.join) {
792 case NRArenaShape::MITRE_JOIN:
793 cairo_set_line_join(ct, CAIRO_LINE_JOIN_MITER);
794 break;
795 case NRArenaShape::ROUND_JOIN:
796 cairo_set_line_join(ct, CAIRO_LINE_JOIN_ROUND);
797 break;
798 case NRArenaShape::BEVEL_JOIN:
799 cairo_set_line_join(ct, CAIRO_LINE_JOIN_BEVEL);
800 break;
801 }
803 cairo_set_miter_limit (ct, style->stroke_miterlimit.value);
805 if (style->stroke_dash.n_dash) {
806 NRVpathDash dash;
807 dash.offset = style->stroke_dash.offset * scale;
808 dash.n_dash = style->stroke_dash.n_dash;
809 dash.dash = g_new(double, dash.n_dash);
810 for (int i = 0; i < dash.n_dash; i++) {
811 dash.dash[i] = style->stroke_dash.dash[i] * scale;
812 }
813 cairo_set_dash (ct, dash.dash, dash.n_dash, dash.offset);
814 g_free(dash.dash);
815 }
817 cairo_set_tolerance(ct, 0.1);
818 cairo_new_path(ct);
820 feed_pathvector_to_cairo (ct, shape->curve->get_pathvector(), to_2geom(shape->ctm), area->upgrade(), true, style_width);
822 cairo_stroke(ct);
824 cairo_surface_t *cst = cairo_get_target(ct);
825 cairo_destroy (ct);
826 cairo_surface_finish (cst);
827 cairo_surface_destroy (cst);
829 pb->empty = FALSE;
830 }
833 /**
834 * Renders the item. Markers are just composed into the parent buffer.
835 */
836 static unsigned int
837 nr_arena_shape_render(cairo_t *ct, NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags)
838 {
839 NRArenaShape *shape = NR_ARENA_SHAPE(item);
841 if (!shape->curve) return item->state;
842 if (!shape->style) return item->state;
844 bool outline = (NR_ARENA_ITEM(shape)->arena->rendermode == Inkscape::RENDERMODE_OUTLINE);
846 if (outline) { // cairo outline rendering
848 pb->empty = FALSE;
849 unsigned int ret = cairo_arena_shape_render_outline (ct, item, (&pb->area)->upgrade());
850 if (ret & NR_ARENA_ITEM_STATE_INVALID) return ret;
852 } else {
854 if ( shape->delayed_shp ) {
855 if ( nr_rect_l_test_intersect(area, &item->bbox) ) {
856 NRGC tempGC(NULL);
857 tempGC.transform=shape->ctm;
858 shape->delayed_shp = false;
859 nr_arena_shape_update_stroke(shape,&tempGC,&pb->visible_area);
860 nr_arena_shape_update_fill(shape,&tempGC,&pb->visible_area);
861 /* NRRect bbox;
862 bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0.0;
863 nr_arena_shape_add_bboxes(shape,bbox);
864 item->bbox.x0 = (gint32)(bbox.x0 - 1.0F);
865 item->bbox.y0 = (gint32)(bbox.y0 - 1.0F);
866 item->bbox.x1 = (gint32)(bbox.x1 + 1.0F);
867 item->bbox.y1 = (gint32)(bbox.y1 + 1.0F);
868 shape->approx_bbox=item->bbox;*/
869 } else {
870 return item->state;
871 }
872 }
874 SPStyle const *style = shape->style;
875 if (shape->fill_shp) {
876 NRPixBlock m;
877 guint32 rgba;
879 nr_pixblock_setup_fast(&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE);
881 // if memory allocation failed, abort render
882 if (m.size != NR_PIXBLOCK_SIZE_TINY && m.data.px == NULL) {
883 nr_pixblock_release (&m);
884 return (item->state);
885 }
887 m.visible_area = pb->visible_area;
888 nr_pixblock_render_shape_mask_or(m,shape->fill_shp);
889 m.empty = FALSE;
891 if (shape->_fill.paint.type() == NRArenaShape::Paint::NONE) {
892 // do not render fill in any way
893 } else if (shape->_fill.paint.type() == NRArenaShape::Paint::COLOR) {
894 if ( item->render_opacity ) {
895 rgba = shape->_fill.paint.color().toRGBA32( shape->_fill.opacity *
896 SP_SCALE24_TO_FLOAT(style->opacity.value) );
897 } else {
898 rgba = shape->_fill.paint.color().toRGBA32( shape->_fill.opacity );
899 }
900 nr_blit_pixblock_mask_rgba32(pb, &m, rgba);
901 pb->empty = FALSE;
902 } else if (shape->_fill.paint.type() == NRArenaShape::Paint::SERVER) {
903 if (shape->fill_painter) {
904 nr_arena_render_paintserver_fill(pb, area, shape->fill_painter, shape->_fill.opacity, &m);
905 }
906 }
908 nr_pixblock_release(&m);
909 }
911 if (shape->stroke_shp && shape->_stroke.paint.type() == NRArenaShape::Paint::COLOR) {
913 // cairo_arena_shape_render_stroke(item, area, pb);
915 guint32 rgba;
916 NRPixBlock m;
918 nr_pixblock_setup_fast(&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE);
920 // if memory allocation failed, abort render
921 if (m.size != NR_PIXBLOCK_SIZE_TINY && m.data.px == NULL) {
922 nr_pixblock_release (&m);
923 return (item->state);
924 }
926 m.visible_area = pb->visible_area;
927 nr_pixblock_render_shape_mask_or(m, shape->stroke_shp);
928 m.empty = FALSE;
930 if ( item->render_opacity ) {
931 rgba = shape->_stroke.paint.color().toRGBA32( shape->_stroke.opacity *
932 SP_SCALE24_TO_FLOAT(style->opacity.value) );
933 } else {
934 rgba = shape->_stroke.paint.color().toRGBA32( shape->_stroke.opacity );
935 }
936 nr_blit_pixblock_mask_rgba32(pb, &m, rgba);
937 pb->empty = FALSE;
939 nr_pixblock_release(&m);
941 } else if (shape->stroke_shp && shape->_stroke.paint.type() == NRArenaShape::Paint::SERVER) {
943 NRPixBlock m;
945 nr_pixblock_setup_fast(&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE);
947 // if memory allocation failed, abort render
948 if (m.size != NR_PIXBLOCK_SIZE_TINY && m.data.px == NULL) {
949 nr_pixblock_release (&m);
950 return (item->state);
951 }
953 m.visible_area = pb->visible_area;
954 nr_pixblock_render_shape_mask_or(m, shape->stroke_shp);
955 m.empty = FALSE;
957 if (shape->stroke_painter) {
958 nr_arena_render_paintserver_fill(pb, area, shape->stroke_painter, shape->_stroke.opacity, &m);
959 }
961 nr_pixblock_release(&m);
962 }
964 } // non-cairo non-outline branch
966 /* Render markers into parent buffer */
967 for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) {
968 unsigned int ret = nr_arena_item_invoke_render(ct, child, area, pb, flags);
969 if (ret & NR_ARENA_ITEM_STATE_INVALID) return ret;
970 }
972 return item->state;
973 }
976 // cairo clipping: this basically works except for the stride-must-be-divisible-by-4 cairo bug;
977 // reenable this when the bug is fixed and remove the rest of this function
978 // TODO
979 #if defined(DEADCODE) && !defined(DEADCODE)
980 static guint
981 cairo_arena_shape_clip(NRArenaItem *item, NRRectL *area, NRPixBlock *pb)
982 {
983 NRArenaShape *shape = NR_ARENA_SHAPE(item);
984 if (!shape->curve) return item->state;
986 cairo_t *ct = nr_create_cairo_context (area, pb);
988 if (!ct)
989 return item->state;
991 cairo_set_source_rgba(ct, 0, 0, 0, 1);
993 cairo_new_path(ct);
995 feed_pathvector_to_cairo (ct, shape->curve->get_pathvector(), to_2geom(shape->ctm), (area)->upgrade(), false, 0);
997 cairo_fill(ct);
999 cairo_surface_t *cst = cairo_get_target(ct);
1000 cairo_destroy (ct);
1001 cairo_surface_finish (cst);
1002 cairo_surface_destroy (cst);
1004 pb->empty = FALSE;
1006 return item->state;
1007 }
1008 #endif //defined(DEADCODE) && !defined(DEADCODE)
1011 static guint
1012 nr_arena_shape_clip(NRArenaItem *item, NRRectL *area, NRPixBlock *pb)
1013 {
1014 //return cairo_arena_shape_clip(item, area, pb);
1016 NRArenaShape *shape = NR_ARENA_SHAPE(item);
1017 if (!shape->curve) return item->state;
1019 if ( shape->delayed_shp || shape->fill_shp == NULL) { // we need a fill shape no matter what
1020 if ( nr_rect_l_test_intersect(area, &item->bbox) ) {
1021 NRGC tempGC(NULL);
1022 tempGC.transform=shape->ctm;
1023 shape->delayed_shp = false;
1024 nr_arena_shape_update_fill(shape, &tempGC, &pb->visible_area, true);
1025 } else {
1026 return item->state;
1027 }
1028 }
1030 if ( shape->fill_shp ) {
1031 NRPixBlock m;
1033 /* fixme: We can OR in one step (Lauris) */
1034 nr_pixblock_setup_fast(&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE);
1036 // if memory allocation failed, abort
1037 if (m.size != NR_PIXBLOCK_SIZE_TINY && m.data.px == NULL) {
1038 nr_pixblock_release (&m);
1039 return (item->state);
1040 }
1042 m.visible_area = pb->visible_area;
1043 nr_pixblock_render_shape_mask_or(m,shape->fill_shp);
1045 for (int y = area->y0; y < area->y1; y++) {
1046 unsigned char *s, *d;
1047 s = NR_PIXBLOCK_PX(&m) + (y - area->y0) * m.rs;
1048 d = NR_PIXBLOCK_PX(pb) + (y - area->y0) * pb->rs;
1049 for (int x = area->x0; x < area->x1; x++) {
1050 *d = NR_COMPOSEA_111(*s, *d);
1051 d ++;
1052 s ++;
1053 }
1054 }
1055 nr_pixblock_release(&m);
1056 pb->empty = FALSE;
1057 }
1059 return item->state;
1060 }
1062 static NRArenaItem *
1063 nr_arena_shape_pick(NRArenaItem *item, NR::Point p, double delta, unsigned int /*sticky*/)
1064 {
1065 NRArenaShape *shape = NR_ARENA_SHAPE(item);
1067 if (shape->repick_after > 0)
1068 shape->repick_after--;
1070 if (shape->repick_after > 0) // we are a slow, huge path. skip this pick, returning what was returned last time
1071 return shape->last_pick;
1073 if (!shape->curve) return NULL;
1074 if (!shape->style) return NULL;
1076 bool outline = (NR_ARENA_ITEM(shape)->arena->rendermode == Inkscape::RENDERMODE_OUTLINE);
1078 if (SP_SCALE24_TO_FLOAT(shape->style->opacity.value) == 0 && !outline)
1079 // fully transparent, no pick unless outline mode
1080 return NULL;
1082 GTimeVal tstart, tfinish;
1083 g_get_current_time (&tstart);
1085 double width;
1086 if (outline) {
1087 width = 0.5;
1088 } else if (shape->_stroke.paint.type() != NRArenaShape::Paint::NONE && shape->_stroke.opacity > 1e-3) {
1089 float const scale = NR::expansion(shape->ctm);
1090 width = MAX(0.125, shape->_stroke.width * scale) / 2;
1091 } else {
1092 width = 0;
1093 }
1095 double dist = NR_HUGE;
1096 int wind = 0;
1097 bool needfill = (shape->_fill.paint.type() != NRArenaShape::Paint::NONE
1098 && shape->_fill.opacity > 1e-3 && !outline);
1100 if (item->arena->canvasarena) {
1101 Geom::Rect viewbox = to_2geom(item->arena->canvasarena->item.canvas->getViewbox());
1102 viewbox.expandBy (width);
1103 pathv_matrix_point_bbox_wind_distance(shape->curve->get_pathvector(), to_2geom(shape->ctm), to_2geom(p), NULL, needfill? &wind : NULL, &dist, 0.5, &viewbox);
1104 } else {
1105 pathv_matrix_point_bbox_wind_distance(shape->curve->get_pathvector(), to_2geom(shape->ctm), to_2geom(p), NULL, needfill? &wind : NULL, &dist, 0.5, NULL);
1106 }
1108 g_get_current_time (&tfinish);
1109 glong this_pick = (tfinish.tv_sec - tstart.tv_sec) * 1000000 + (tfinish.tv_usec - tstart.tv_usec);
1110 //g_print ("pick time %lu\n", this_pick);
1112 if (this_pick > 10000) { // slow picking, remember to skip several new picks
1113 shape->repick_after = this_pick / 5000;
1114 }
1116 // covered by fill?
1117 if (needfill) {
1118 if (!shape->style->fill_rule.computed) {
1119 if (wind != 0) {
1120 shape->last_pick = item;
1121 return item;
1122 }
1123 } else {
1124 if (wind & 0x1) {
1125 shape->last_pick = item;
1126 return item;
1127 }
1128 }
1129 }
1131 // close to the edge, as defined by strokewidth and delta?
1132 // this ignores dashing (as if the stroke is solid) and always works as if caps are round
1133 if (needfill || width > 0) { // if either fill or stroke visible,
1134 if ((dist - width) < delta) {
1135 shape->last_pick = item;
1136 return item;
1137 }
1138 }
1140 // if not picked on the shape itself, try its markers
1141 for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) {
1142 NRArenaItem *ret = nr_arena_item_invoke_pick(child, p, delta, 0);
1143 if (ret) {
1144 shape->last_pick = item;
1145 return item;
1146 }
1147 }
1149 shape->last_pick = NULL;
1150 return NULL;
1151 }
1153 /**
1154 *
1155 * Requests a render of the shape, then if the shape is already a curve it
1156 * unrefs the old curve; if the new curve is valid it creates a copy of the
1157 * curve and adds it to the shape. Finally, it requests an update of the
1158 * arena for the shape.
1159 */
1160 void nr_arena_shape_set_path(NRArenaShape *shape, SPCurve *curve,bool justTrans)
1161 {
1162 g_return_if_fail(shape != NULL);
1163 g_return_if_fail(NR_IS_ARENA_SHAPE(shape));
1165 if ( justTrans == false ) {
1166 // dirty cached versions
1167 if ( shape->cached_fill ) {
1168 delete shape->cached_fill;
1169 shape->cached_fill=NULL;
1170 }
1171 if ( shape->cached_stroke ) {
1172 delete shape->cached_stroke;
1173 shape->cached_stroke=NULL;
1174 }
1175 }
1177 nr_arena_item_request_render(NR_ARENA_ITEM(shape));
1179 if (shape->curve) {
1180 shape->curve->unref();
1181 shape->curve = NULL;
1182 }
1184 if (curve) {
1185 shape->curve = curve;
1186 curve->ref();
1187 }
1189 nr_arena_item_request_update(NR_ARENA_ITEM(shape), NR_ARENA_ITEM_STATE_ALL, FALSE);
1190 }
1192 void NRArenaShape::setFill(SPPaintServer *server) {
1193 _fill.paint.set(server);
1194 _invalidateCachedFill();
1195 }
1197 void NRArenaShape::setFill(SPColor const &color) {
1198 _fill.paint.set(color);
1199 _invalidateCachedFill();
1200 }
1202 void NRArenaShape::setFillOpacity(double opacity) {
1203 _fill.opacity = opacity;
1204 _invalidateCachedFill();
1205 }
1207 void NRArenaShape::setFillRule(NRArenaShape::FillRule rule) {
1208 _fill.rule = rule;
1209 _invalidateCachedFill();
1210 }
1212 void NRArenaShape::setStroke(SPPaintServer *server) {
1213 _stroke.paint.set(server);
1214 _invalidateCachedStroke();
1215 }
1217 void NRArenaShape::setStroke(SPColor const &color) {
1218 _stroke.paint.set(color);
1219 _invalidateCachedStroke();
1220 }
1222 void NRArenaShape::setStrokeOpacity(double opacity) {
1223 _stroke.opacity = opacity;
1224 _invalidateCachedStroke();
1225 }
1227 void NRArenaShape::setStrokeWidth(double width) {
1228 _stroke.width = width;
1229 _invalidateCachedStroke();
1230 }
1232 void NRArenaShape::setMitreLimit(double limit) {
1233 _stroke.mitre_limit = limit;
1234 _invalidateCachedStroke();
1235 }
1237 void NRArenaShape::setLineCap(NRArenaShape::CapType cap) {
1238 _stroke.cap = cap;
1239 _invalidateCachedStroke();
1240 }
1242 void NRArenaShape::setLineJoin(NRArenaShape::JoinType join) {
1243 _stroke.join = join;
1244 _invalidateCachedStroke();
1245 }
1247 /** nr_arena_shape_set_style
1248 *
1249 * Unrefs any existing style and ref's to the given one, then requests an update of the arena
1250 */
1251 void
1252 nr_arena_shape_set_style(NRArenaShape *shape, SPStyle *style)
1253 {
1254 g_return_if_fail(shape != NULL);
1255 g_return_if_fail(NR_IS_ARENA_SHAPE(shape));
1257 if (style) sp_style_ref(style);
1258 if (shape->style) sp_style_unref(shape->style);
1259 shape->style = style;
1261 if ( style->fill.isPaintserver() ) {
1262 shape->setFill(style->getFillPaintServer());
1263 } else if ( style->fill.isColor() ) {
1264 shape->setFill(style->fill.value.color);
1265 } else if ( style->fill.isNone() ) {
1266 shape->setFill(NULL);
1267 } else {
1268 g_assert_not_reached();
1269 }
1270 shape->setFillOpacity(SP_SCALE24_TO_FLOAT(style->fill_opacity.value));
1271 switch (style->fill_rule.computed) {
1272 case SP_WIND_RULE_EVENODD: {
1273 shape->setFillRule(NRArenaShape::EVEN_ODD);
1274 break;
1275 }
1276 case SP_WIND_RULE_NONZERO: {
1277 shape->setFillRule(NRArenaShape::NONZERO);
1278 break;
1279 }
1280 default: {
1281 g_assert_not_reached();
1282 }
1283 }
1285 if ( style->stroke.isPaintserver() ) {
1286 shape->setStroke(style->getStrokePaintServer());
1287 } else if ( style->stroke.isColor() ) {
1288 shape->setStroke(style->stroke.value.color);
1289 } else if ( style->stroke.isNone() ) {
1290 shape->setStroke(NULL);
1291 } else {
1292 g_assert_not_reached();
1293 }
1294 shape->setStrokeWidth(style->stroke_width.computed);
1295 shape->setStrokeOpacity(SP_SCALE24_TO_FLOAT(style->stroke_opacity.value));
1296 switch (style->stroke_linecap.computed) {
1297 case SP_STROKE_LINECAP_ROUND: {
1298 shape->setLineCap(NRArenaShape::ROUND_CAP);
1299 break;
1300 }
1301 case SP_STROKE_LINECAP_SQUARE: {
1302 shape->setLineCap(NRArenaShape::SQUARE_CAP);
1303 break;
1304 }
1305 case SP_STROKE_LINECAP_BUTT: {
1306 shape->setLineCap(NRArenaShape::BUTT_CAP);
1307 break;
1308 }
1309 default: {
1310 g_assert_not_reached();
1311 }
1312 }
1313 switch (style->stroke_linejoin.computed) {
1314 case SP_STROKE_LINEJOIN_ROUND: {
1315 shape->setLineJoin(NRArenaShape::ROUND_JOIN);
1316 break;
1317 }
1318 case SP_STROKE_LINEJOIN_BEVEL: {
1319 shape->setLineJoin(NRArenaShape::BEVEL_JOIN);
1320 break;
1321 }
1322 case SP_STROKE_LINEJOIN_MITER: {
1323 shape->setLineJoin(NRArenaShape::MITRE_JOIN);
1324 break;
1325 }
1326 default: {
1327 g_assert_not_reached();
1328 }
1329 }
1330 shape->setMitreLimit(style->stroke_miterlimit.value);
1332 //if shape has a filter
1333 if (style->filter.set && style->getFilter()) {
1334 if (!shape->filter) {
1335 int primitives = sp_filter_primitive_count(SP_FILTER(style->getFilter()));
1336 shape->filter = new NR::Filter(primitives);
1337 }
1338 sp_filter_build_renderer(SP_FILTER(style->getFilter()), shape->filter);
1339 } else {
1340 //no filter set for this shape
1341 delete shape->filter;
1342 shape->filter = NULL;
1343 }
1345 nr_arena_item_request_update(shape, NR_ARENA_ITEM_STATE_ALL, FALSE);
1346 }
1348 void
1349 nr_arena_shape_set_paintbox(NRArenaShape *shape, NRRect const *pbox)
1350 {
1351 g_return_if_fail(shape != NULL);
1352 g_return_if_fail(NR_IS_ARENA_SHAPE(shape));
1353 g_return_if_fail(pbox != NULL);
1355 if ((pbox->x0 < pbox->x1) && (pbox->y0 < pbox->y1)) {
1356 shape->paintbox = *pbox;
1357 } else {
1358 /* fixme: We kill warning, although not sure what to do here (Lauris) */
1359 shape->paintbox.x0 = shape->paintbox.y0 = 0.0F;
1360 shape->paintbox.x1 = shape->paintbox.y1 = 256.0F;
1361 }
1363 nr_arena_item_request_update(shape, NR_ARENA_ITEM_STATE_ALL, FALSE);
1364 }
1366 void NRArenaShape::setPaintBox(NR::Rect const &pbox)
1367 {
1368 paintbox.x0 = pbox.min()[NR::X];
1369 paintbox.y0 = pbox.min()[NR::Y];
1370 paintbox.x1 = pbox.max()[NR::X];
1371 paintbox.y1 = pbox.max()[NR::Y];
1373 nr_arena_item_request_update(this, NR_ARENA_ITEM_STATE_ALL, FALSE);
1374 }
1376 static void
1377 shape_run_A8_OR(raster_info &dest,void */*data*/,int st,float vst,int en,float ven)
1378 {
1379 if ( st >= en ) return;
1380 if ( vst < 0 ) vst=0;
1381 if ( vst > 1 ) vst=1;
1382 if ( ven < 0 ) ven=0;
1383 if ( ven > 1 ) ven=1;
1384 float sv=vst;
1385 float dv=ven-vst;
1386 int len=en-st;
1387 unsigned char* d=(unsigned char*)dest.buffer;
1388 d+=(st-dest.startPix);
1389 if ( fabs(dv) < 0.001 ) {
1390 if ( vst > 0.999 ) {
1391 /* Simple copy */
1392 while (len > 0) {
1393 d[0] = 255;
1394 d += 1;
1395 len -= 1;
1396 }
1397 } else {
1398 sv*=256;
1399 unsigned int c0_24=(int)sv;
1400 c0_24&=0xFF;
1401 while (len > 0) {
1402 /* Draw */
1403 d[0] = NR_COMPOSEA_111(c0_24,d[0]);
1404 d += 1;
1405 len -= 1;
1406 }
1407 }
1408 } else {
1409 if ( en <= st+1 ) {
1410 sv=0.5*(vst+ven);
1411 sv*=256;
1412 unsigned int c0_24=(int)sv;
1413 c0_24&=0xFF;
1414 /* Draw */
1415 d[0] = NR_COMPOSEA_111(c0_24,d[0]);
1416 } else {
1417 dv/=len;
1418 sv+=0.5*dv; // correction trapezoidale
1419 sv*=16777216;
1420 dv*=16777216;
1421 int c0_24 = static_cast<int>(CLAMP(sv, 0, 16777216));
1422 int s0_24 = static_cast<int>(dv);
1423 while (len > 0) {
1424 unsigned int ca;
1425 /* Draw */
1426 ca = c0_24 >> 16;
1427 if ( ca > 255 ) ca=255;
1428 d[0] = NR_COMPOSEA_111(ca,d[0]);
1429 d += 1;
1430 c0_24 += s0_24;
1431 c0_24 = CLAMP(c0_24, 0, 16777216);
1432 len -= 1;
1433 }
1434 }
1435 }
1436 }
1438 void nr_pixblock_render_shape_mask_or(NRPixBlock &m,Shape* theS)
1439 {
1440 theS->CalcBBox();
1441 float l = theS->leftX, r = theS->rightX, t = theS->topY, b = theS->bottomY;
1442 int il,ir,it,ib;
1443 il=(int)floor(l);
1444 ir=(int)ceil(r);
1445 it=(int)floor(t);
1446 ib=(int)ceil(b);
1448 if ( il >= m.area.x1 || ir <= m.area.x0 || it >= m.area.y1 || ib <= m.area.y0 ) return;
1449 if ( il < m.area.x0 ) il=m.area.x0;
1450 if ( it < m.area.y0 ) it=m.area.y0;
1451 if ( ir > m.area.x1 ) ir=m.area.x1;
1452 if ( ib > m.area.y1 ) ib=m.area.y1;
1454 /* This is the FloatLigne version. See svn (prior to Apr 2006) for versions using BitLigne or direct BitLigne. */
1455 int curPt;
1456 float curY;
1457 theS->BeginQuickRaster(curY, curPt);
1459 FloatLigne *theI = new FloatLigne();
1460 IntLigne *theIL = new IntLigne();
1462 theS->DirectQuickScan(curY, curPt, (float) it, true, 1.0);
1464 char *mdata = (char*)m.data.px;
1465 if ( m.size == NR_PIXBLOCK_SIZE_TINY ) mdata=(char*)m.data.p;
1466 uint32_t *ligStart = ((uint32_t*)(mdata + ((il - m.area.x0) + m.rs * (it - m.area.y0))));
1467 for (int y = it; y < ib; y++) {
1468 theI->Reset();
1469 theS->QuickScan(curY, curPt, ((float)(y+1)), theI, 1.0);
1470 theI->Flatten();
1471 theIL->Copy(theI);
1473 raster_info dest;
1474 dest.startPix=il;
1475 dest.endPix=ir;
1476 dest.sth=il;
1477 dest.stv=y;
1478 dest.buffer=ligStart;
1479 theIL->Raster(dest, NULL, shape_run_A8_OR);
1480 ligStart=((uint32_t*)(((char*)ligStart)+m.rs));
1481 }
1482 theS->EndQuickRaster();
1483 delete theI;
1484 delete theIL;
1485 }
1488 /*
1489 Local Variables:
1490 mode:c++
1491 c-file-style:"stroustrup"
1492 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1493 indent-tabs-mode:nil
1494 fill-column:99
1495 End:
1496 */
1497 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :