2009-11-13 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / shape.cpp
blob3463eb1923033fabbb17e0f9ed0ebe0bc7cfb413
1 /*
2 * shape.cpp: This match the classes inside System.Windows.Shapes
4 * Contact:
5 * Moonlight List (moonlight-list@lists.ximian.com)
7 * Copyright 2007-2008 Novell, Inc. (http://www.novell.com)
9 * See the LICENSE file included with the distribution for details.
13 #include <config.h>
15 #include <cairo.h>
17 #include <math.h>
19 #include "runtime.h"
20 #include "shape.h"
21 #include "brush.h"
22 #include "utils.h"
23 #include "ptr.h"
26 // SL-Cairo convertion and helper routines
29 #define EXACT_BOUNDS 1
31 static cairo_line_join_t
32 convert_line_join (PenLineJoin pen_line_join)
34 switch (pen_line_join) {
35 default:
36 /* note: invalid values should be trapped in SetValue (see bug #340799) */
37 g_warning ("Invalid value (%d) specified for PenLineJoin, using default.", pen_line_join);
38 /* at this stage we use the default value (Miter) for Shape */
39 case PenLineJoinMiter:
40 return CAIRO_LINE_JOIN_MITER;
41 case PenLineJoinBevel:
42 return CAIRO_LINE_JOIN_BEVEL;
43 case PenLineJoinRound:
44 return CAIRO_LINE_JOIN_ROUND;
48 /* NOTE: Triangle doesn't exist in Cairo - unless you patched it using https://bugzilla.novell.com/show_bug.cgi?id=345892 */
49 #ifndef HAVE_CAIRO_LINE_CAP_TRIANGLE
50 #define CAIRO_LINE_CAP_TRIANGLE CAIRO_LINE_CAP_ROUND
51 #endif
53 static cairo_line_cap_t
54 convert_line_cap (PenLineCap pen_line_cap)
56 switch (pen_line_cap) {
57 default:
58 /* note: invalid values should be trapped in SetValue (see bug #340799) */
59 g_warning ("Invalid value (%d) specified for PenLineCap, using default.", pen_line_cap);
60 /* at this stage we use the default value (Flat) for Shape */
61 case PenLineCapFlat:
62 return CAIRO_LINE_CAP_BUTT;
63 case PenLineCapSquare:
64 return CAIRO_LINE_CAP_SQUARE;
65 case PenLineCapRound:
66 return CAIRO_LINE_CAP_ROUND;
67 case PenLineCapTriangle:
68 return CAIRO_LINE_CAP_TRIANGLE;
72 cairo_fill_rule_t
73 convert_fill_rule (FillRule fill_rule)
75 switch (fill_rule) {
76 default:
77 /* note: invalid values should be trapped in SetValue (see bug #340799) */
78 g_warning ("Invalid value (%d) specified for FillRule, using default.", fill_rule);
79 /* at this stage we use the default value (EvenOdd) for Geometry */
80 case FillRuleEvenOdd:
81 return CAIRO_FILL_RULE_EVEN_ODD;
82 case FillRuleNonzero:
83 return CAIRO_FILL_RULE_WINDING;
88 // Shape
91 Shape::Shape ()
93 SetObjectType (Type::SHAPE);
95 stroke = NULL;
96 fill = NULL;
97 path = NULL;
98 cached_surface = NULL;
99 SetShapeFlags (UIElement::SHAPE_NORMAL);
100 cairo_matrix_init_identity (&stretch_transform);
102 SetStrokeDashArray (DOPtr<DoubleCollection> (new DoubleCollection ()));
105 Shape::~Shape ()
107 // That also destroys the cached surface
108 InvalidatePathCache (true);
111 Point
112 Shape::GetTransformOrigin ()
114 if (GetStretch () != StretchNone)
115 return FrameworkElement::GetTransformOrigin ();
117 return Point (0,0);
120 Transform *
121 Shape::GetGeometryTransform ()
123 Matrix *matrix = new Matrix (&stretch_transform);
125 MatrixTransform *transform = new MatrixTransform ();
127 transform->SetValue (MatrixTransform::MatrixProperty, matrix);
128 matrix->unref ();
130 return transform;
133 void
134 Shape::Draw (cairo_t *cr)
136 if (!path || (path->cairo.num_data == 0))
137 BuildPath ();
139 cairo_save (cr);
140 cairo_transform (cr, &stretch_transform);
142 cairo_new_path (cr);
143 cairo_append_path (cr, &path->cairo);
145 cairo_restore (cr);
148 // break up operations so we can exclude optional stuff, like:
149 // * StrokeStartLineCap & StrokeEndLineCap
150 // * StrokeLineJoin & StrokeMiterLimit
151 // * Fill
153 bool
154 Shape::SetupLine (cairo_t *cr)
156 double thickness = GetStrokeThickness ();
158 // check if something will be drawn or return
159 // note: override this method if cairo is used to compute bounds
160 if (thickness == 0)
161 return false;
163 cairo_set_line_width (cr, thickness);
165 return SetupDashes (cr, thickness);
168 bool
169 Shape::SetupDashes (cairo_t *cr, double thickness)
171 return Shape::SetupDashes (cr, thickness, GetStrokeDashOffset () * thickness);
174 bool
175 Shape::SetupDashes (cairo_t *cr, double thickness, double offset)
177 DoubleCollection *dashes = GetStrokeDashArray ();
178 if (dashes && (dashes->GetCount() > 0)) {
179 int count = dashes->GetCount();
181 // NOTE: special case - if we continue cairo will stops drawing!
182 if ((count == 1) && (dashes->GetValueAt(0)->AsDouble() == 0.0))
183 return false;
185 // multiply dashes length with thickness
186 double *dmul = new double [count];
187 for (int i=0; i < count; i++) {
188 dmul [i] = dashes->GetValueAt(i)->AsDouble() * thickness;
191 cairo_set_dash (cr, dmul, count, offset);
192 delete [] dmul;
193 } else {
194 cairo_set_dash (cr, NULL, 0, 0.0);
196 return true;
199 void
200 Shape::SetupLineCaps (cairo_t *cr)
202 // Setting the cap to dash_cap. the endcaps (if different) are handled elsewhere
203 PenLineCap cap = GetStrokeDashCap ();
205 cairo_set_line_cap (cr, convert_line_cap (cap));
208 void
209 Shape::SetupLineJoinMiter (cairo_t *cr)
211 PenLineJoin join = GetStrokeLineJoin ();
212 double limit = GetStrokeMiterLimit ();
214 cairo_set_line_join (cr, convert_line_join (join));
215 cairo_set_miter_limit (cr, limit);
218 // returns true if the path is set on the cairo, false if not
219 bool
220 Shape::Fill (cairo_t *cr, bool do_op)
222 if (!fill)
223 return false;
225 Draw (cr);
226 if (do_op) {
227 fill->SetupBrush (cr, GetStretchExtents ());
228 cairo_set_fill_rule (cr, convert_fill_rule (GetFillRule ()));
229 fill->Fill (cr, true);
231 return true;
234 Rect
235 Shape::ComputeStretchBounds ()
238 * NOTE: this code is extremely fragile don't make a change here without
239 * checking the results of the test harness on with MOON_DRT_CATEGORIES=stretch
242 bool autodim = isnan (GetWidth ());
244 Stretch stretch = GetStretch ();
245 Rect shape_bounds = GetNaturalBounds ();
247 if (shape_bounds.width <= 0.0 || shape_bounds.height <= 0.0) {
248 SetShapeFlags (UIElement::SHAPE_EMPTY);
249 return Rect();
252 Size framework (GetActualWidth (), GetActualHeight ());
253 Size specified (GetWidth (), GetHeight ());
255 if (specified.width <= 0.0 || specified.height <= 0.0) {
256 SetShapeFlags (UIElement::SHAPE_EMPTY);
257 return Rect ();
260 if (GetVisualParent () && GetVisualParent()->Is (Type::CANVAS)) {
261 if (!isnan (specified.width))
262 framework.width = specified.width;
263 if (!isnan (specified.height))
264 framework.height = specified.height;
267 framework.width = framework.width == 0.0 ? shape_bounds.width : framework.width;
268 framework.height = framework.height == 0.0 ? shape_bounds.height : framework.height;
270 if (stretch != StretchNone) {
271 Rect logical_bounds = ComputeShapeBounds (true, NULL);
273 bool adj_x = logical_bounds.width != 0.0;
274 bool adj_y = logical_bounds.height != 0.0;
276 double diff_x = shape_bounds.width - logical_bounds.width;
277 double diff_y = shape_bounds.height - logical_bounds.height;
278 double sw = adj_x ? (framework.width - diff_x) / logical_bounds.width : 1.0;
279 double sh = adj_y ? (framework.height - diff_y) / logical_bounds.height : 1.0;
281 bool center = false;
283 switch (stretch) {
284 case StretchFill:
285 center = true;
286 break;
287 case StretchUniform:
288 sw = sh = (sw < sh) ? sw : sh;
289 center = true;
290 break;
291 case StretchUniformToFill:
292 sw = sh = (sw > sh) ? sw : sh;
293 break;
294 case StretchNone:
295 /* not reached */
296 break;
299 // trying to avoid the *VERY*SLOW* adjustments
300 // e.g. apps like Silverlight World have a ratio up to 50 unneeded for 1 needed adjustment
301 // so it all boilds down are we gonna change bounds anyway ?
302 #define IS_SIGNIFICANT(dx,x) (IS_ZERO(dx) && (fabs(dx) * x - x > 1.0))
303 if ((adj_x && IS_SIGNIFICANT((sw - 1), shape_bounds.width)) || (adj_y && IS_SIGNIFICANT((sh - 1), shape_bounds.height))) {
304 // FIXME: this IS still UBER slow
305 // hereafter we're doing a second pass to refine the sw and sh we guessed
306 // the first time. This usually gives pixel-recise stretches for Paths
307 cairo_matrix_t temp;
308 cairo_matrix_init_scale (&temp, adj_x ? sw : 1.0, adj_y ? sh : 1.0);
309 Rect stretch_bounds = ComputeShapeBounds (false, &temp);
310 if (stretch_bounds.width != shape_bounds.width && stretch_bounds.height != shape_bounds.height) {
311 sw *= adj_x ? (framework.width - stretch_bounds.width + logical_bounds.width * sw) / (logical_bounds.width * sw): 1.0;
312 sh *= adj_y ? (framework.height - stretch_bounds.height + logical_bounds.height * sh) / (logical_bounds.height * sh): 1.0;
314 switch (stretch) {
315 case StretchUniform:
316 sw = sh = (sw < sh) ? sw : sh;
317 break;
318 case StretchUniformToFill:
319 sw = sh = (sw > sh) ? sw : sh;
320 break;
321 default:
322 break;
325 // end of the 2nd pass code
328 double x = !autodim || adj_x ? shape_bounds.x : 0;
329 double y = !autodim || adj_y ? shape_bounds.y : 0;
331 if (center)
332 cairo_matrix_translate (&stretch_transform,
333 adj_x ? framework.width * 0.5 : 0,
334 adj_y ? framework.height * 0.5 : 0);
335 else //UniformToFill
336 cairo_matrix_translate (&stretch_transform,
337 adj_x ? (logical_bounds.width * sw + diff_x) * .5 : 0,
338 adj_y ? (logical_bounds.height * sh + diff_y) * .5: 0);
340 cairo_matrix_scale (&stretch_transform,
341 adj_x ? sw : 1.0,
342 adj_y ? sh : 1.0);
344 cairo_matrix_translate (&stretch_transform,
345 adj_x ? -shape_bounds.width * 0.5 : 0,
346 adj_y ? -shape_bounds.height * 0.5 : 0);
348 if (!Is (Type::LINE) || !autodim)
349 cairo_matrix_translate (&stretch_transform, -x, -y);
351 // Double check our math
352 cairo_matrix_t test = stretch_transform;
353 if (cairo_matrix_invert (&test)) {
354 g_warning ("Unable to compute stretch transform %f %f %f %f \n", sw, sh, shape_bounds.x, shape_bounds.y);
358 shape_bounds = shape_bounds.Transform (&stretch_transform);
360 return shape_bounds;
363 void
364 Shape::Stroke (cairo_t *cr, bool do_op)
366 if (do_op) {
367 stroke->SetupBrush (cr, GetStretchExtents ());
368 stroke->Stroke (cr);
372 void
373 Shape::Clip (cairo_t *cr)
375 Rect specified = Rect (0, 0, GetWidth (), GetHeight ());
376 Rect paint = Rect (0, 0, GetActualWidth (), GetActualHeight ());
377 UIElement *parent = GetVisualParent ();
378 bool in_flow = parent && !parent->Is (Type::CANVAS);
381 * NOTE the clumbsy rounding up to 1 here is based on tests like
382 * test-shape-path-stretch.xaml where it silverlight attempts
383 * to make sure something is always drawn a better mechanism
384 * is warranted
386 if (!IsDegenerate ()) {
387 bool clip_bounds = false;
388 if (!isnan (specified.width) && specified.width >= 1) { // && paint.width > specified.width) {
389 paint.width = specified.width;
390 if (!in_flow)
391 paint.height = isnan (specified.height) ? 0 : MAX (1, specified.height);
392 clip_bounds = true;
395 if (!isnan (specified.height) && specified.height >= 1) { // && paint.height > specified.height) {
396 paint.height = specified.height;
398 if (!in_flow)
399 paint.width = isnan (specified.width) ? 0 : MAX (1, specified.width);
400 clip_bounds = true;
403 if (clip_bounds) {
404 paint.Draw (cr);
405 cairo_clip (cr);
408 RenderLayoutClip (cr);
412 // Returns TRUE if surface is a good candidate for caching.
413 // We accept a little bit of scaling.
415 bool
416 Shape::IsCandidateForCaching (void)
418 if (IsEmpty ())
419 return FALSE;
421 if (! GetSurface ())
422 return FALSE;
425 * these fill and stroke short circuits are attempts be smart
426 * about determining when the cost of caching is greater than
427 * the cost of simply drawing all the choices here should really
428 * have best and worst case perf tests associated with them
429 * but they don't right now
431 bool gradient_fill = false;
432 /* XXX FIXME this should be a property on the shape */
433 bool simple = Is (Type::RECTANGLE) || Is (Type::ELLIPSE);
435 if (fill) {
436 if (fill->IsAnimating ())
437 return FALSE;
439 gradient_fill |= fill->Is (Type::GRADIENTBRUSH);
443 if (stroke && stroke->IsAnimating ())
444 return FALSE;
446 if (simple && !gradient_fill)
447 return FALSE;
449 // This is not 100% correct check -- the actual surface size might be
450 // a tiny little bit larger. It's not a problem though if we go few
451 // bytes above the cache limit.
452 if (!GetSurface ()->VerifyWithCacheSizeCounter ((int) bounds.width, (int) bounds.height))
453 return FALSE;
455 // one last line of defense, lets not cache things
456 // much larger than the screen.
457 if (bounds.width * bounds.height > 4000000)
458 return FALSE;
460 return TRUE;
464 // This routine is useful for Shape derivatives: it can be used
465 // to either get the bounding box from cairo, or to paint it
467 void
468 Shape::DoDraw (cairo_t *cr, bool do_op)
470 bool ret = FALSE;
472 // quick out if, when building the path, we detected an empty shape
473 if (IsEmpty ())
474 goto cleanpath;
476 if (do_op && cached_surface == NULL && IsCandidateForCaching ()) {
477 Rect cache_extents = bounds.RoundOut ();
478 cairo_t *cached_cr = NULL;
480 // g_warning ("bounds (%f, %f), extents (%f, %f), cache_extents (%f, %f)",
481 // bounds.width, bounds.height,
482 // extents.width, extents.height,
483 // cache_extents.width, cache_extents.height);
485 cached_surface = image_brush_create_similar (cr, (int) cache_extents.width, (int) cache_extents.height);
486 cairo_surface_set_device_offset (cached_surface, -cache_extents.x, -cache_extents.y);
487 cached_cr = cairo_create (cached_surface);
489 cairo_set_matrix (cached_cr, &absolute_xform);
491 ret = DrawShape (cached_cr, do_op);
493 cairo_destroy (cached_cr);
495 // Increase our cache size
496 cached_size = GetSurface ()->AddToCacheSizeCounter ((int) cache_extents.width, (int) cache_extents.height);
499 if (do_op && cached_surface) {
500 cairo_pattern_t *cached_pattern = NULL;
502 cached_pattern = cairo_pattern_create_for_surface (cached_surface);
503 cairo_set_matrix (cr, &absolute_xform);
504 if (do_op)
505 Clip (cr);
507 cairo_identity_matrix (cr);
508 cairo_set_source (cr, cached_pattern);
509 cairo_pattern_destroy (cached_pattern);
510 cairo_paint (cr);
511 } else {
512 cairo_set_matrix (cr, &absolute_xform);
513 if (do_op)
514 Clip (cr);
516 if (DrawShape (cr, do_op))
517 return;
520 cleanpath:
521 if (do_op)
522 cairo_new_path (cr);
525 void
526 Shape::Render (cairo_t *cr, Region *region, bool path_only)
528 cairo_save (cr);
529 DoDraw (cr, true && !path_only);
530 cairo_restore (cr);
533 void
534 Shape::ShiftPosition (Point p)
536 double dx = bounds.x - p.x;
537 double dy = bounds.y - p.y;
539 // FIXME this is much less than ideal but we must invalidate the surface cache
540 // if the shift is not an integer otherwise we can potentially drow outside our
541 // rounded out bounds.
542 if (cached_surface && (dx == trunc(dx)) && (dy == trunc(dy))) {
543 cairo_surface_set_device_offset (cached_surface, trunc (-p.x), trunc (-p.y));
544 } else {
545 InvalidateSurfaceCache ();
548 FrameworkElement::ShiftPosition (p);
552 Size
553 Shape::ComputeActualSize ()
555 Size desired = FrameworkElement::ComputeActualSize ();
556 Rect shape_bounds = GetNaturalBounds ();
557 double sx = 1.0;
558 double sy = 1.0;
559 UIElement *parent = GetVisualParent ();
561 if (parent && !parent->Is (Type::CANVAS))
562 if (LayoutInformation::GetPreviousConstraint (this) || LayoutInformation::GetLayoutSlot (this))
563 return desired;
565 if (!GetSurface ())
566 return desired;
568 if (shape_bounds.width <= 0 && shape_bounds.height <= 0)
569 return desired;
571 if (GetStretch () == StretchNone && shape_bounds.width > 0 && shape_bounds.height > 0)
572 return Size (shape_bounds.width, shape_bounds.height);
574 /* don't stretch to infinite size */
575 if (isinf (desired.width))
576 desired.width = shape_bounds.width;
577 if (isinf (desired.height))
578 desired.height = shape_bounds.height;
580 /* compute the scaling */
581 if (shape_bounds.width > 0)
582 sx = desired.width / shape_bounds.width;
583 if (shape_bounds.height > 0)
584 sy = desired.height / shape_bounds.height;
586 switch (GetStretch ()) {
587 case StretchUniform:
588 sx = sy = MIN (sx, sy);
589 break;
590 case StretchUniformToFill:
591 sx = sy = MAX (sx, sy);
592 break;
593 default:
594 break;
597 desired = desired.Min (shape_bounds.width * sx, shape_bounds.height * sy);
599 return desired;
602 Size
603 Shape::MeasureOverride (Size availableSize)
605 Size desired = availableSize;
606 Rect shape_bounds = GetNaturalBounds ();
607 double sx = 0.0;
608 double sy = 0.0;
610 if (GetStretch () == StretchNone)
611 return Size (shape_bounds.x + shape_bounds.width, shape_bounds.y + shape_bounds.height);
613 if (Is (Type::RECTANGLE) || Is (Type::ELLIPSE)) {
614 desired = Size (0,0);
617 /* don't stretch to infinite size */
618 if (isinf (availableSize.width))
619 desired.width = shape_bounds.width;
620 if (isinf (availableSize.height))
621 desired.height = shape_bounds.height;
623 /* compute the scaling */
624 if (shape_bounds.width > 0)
625 sx = desired.width / shape_bounds.width;
626 if (shape_bounds.height > 0)
627 sy = desired.height / shape_bounds.height;
629 /* don't use infinite dimensions as constraints */
630 if (isinf (availableSize.width))
631 sx = sy;
632 if (isinf (availableSize.height))
633 sy = sx;
635 switch (GetStretch ()) {
636 case StretchUniform:
637 sx = sy = MIN (sx, sy);
638 break;
639 case StretchUniformToFill:
640 sx = sy = MAX (sx, sy);
641 break;
642 case StretchFill:
643 if (isinf (availableSize.width))
644 sx = 1.0;
645 if (isinf (availableSize.height))
646 sy = 1.0;
647 break;
648 default:
649 break;
652 desired = Size (shape_bounds.width * sx, shape_bounds.height * sy);
654 return desired;
657 Size
658 Shape::ArrangeOverride (Size finalSize)
660 Size arranged = finalSize;
661 double sx = 1.0;
662 double sy = 1.0;
664 Rect shape_bounds = GetNaturalBounds ();
666 InvalidateStretch ();
668 if (GetStretch () == StretchNone)
669 return arranged.Max (Size (shape_bounds.x + shape_bounds.width, shape_bounds.y + shape_bounds.height));
671 /* compute the scaling */
672 if (shape_bounds.width == 0)
673 shape_bounds.width = arranged.width;
674 if (shape_bounds.height == 0)
675 shape_bounds.height = arranged.height;
677 if (shape_bounds.width != arranged.width)
678 sx = arranged.width / shape_bounds.width;
679 if (shape_bounds.height != arranged.height)
680 sy = arranged.height / shape_bounds.height;
682 switch (GetStretch ()) {
683 case StretchUniform:
684 sx = sy = MIN (sx, sy);
685 break;
686 case StretchUniformToFill:
687 sx = sy = MAX (sx, sy);
688 break;
689 default:
690 break;
693 arranged = Size (shape_bounds.width * sx, shape_bounds.height * sy);
695 return arranged;
698 void
699 Shape::TransformBounds (cairo_matrix_t *old, cairo_matrix_t *current)
701 InvalidateSurfaceCache ();
702 bounds_with_children = bounds = IntersectBoundsWithClipPath (GetStretchExtents (), false).Transform (current);
705 void
706 Shape::ComputeBounds ()
708 bounds_with_children = bounds = IntersectBoundsWithClipPath (GetStretchExtents (), false).Transform (&absolute_xform);
709 //printf ("%f,%f,%f,%f\n", bounds.x, bounds.y, bounds.width, bounds.height);
712 Rect
713 Shape::ComputeShapeBounds (bool logical, cairo_matrix_t *matrix)
715 double thickness = (logical || !IsStroked ()) ? 0.0 : GetStrokeThickness ();
716 if (Is (Type::RECTANGLE) || Is (Type::ELLIPSE)) {
717 return Rect (0,0,1.0,1.0);
720 if (!path || (path->cairo.num_data == 0))
721 BuildPath ();
723 if (IsEmpty ())
724 return Rect ();
726 cairo_t *cr = measuring_context_create ();
727 if (matrix)
728 cairo_set_matrix (cr, matrix);
730 cairo_set_line_width (cr, thickness);
732 if (thickness > 0.0) {
733 //FIXME: still not 100% precise since it could be different from the end cap
734 PenLineCap cap = GetStrokeStartLineCap ();
735 if (cap == PenLineCapFlat)
736 cap = GetStrokeEndLineCap ();
737 cairo_set_line_cap (cr, convert_line_cap (cap));
740 cairo_append_path (cr, &path->cairo);
742 cairo_identity_matrix (cr);
744 double x1, y1, x2, y2;
746 if (logical) {
747 cairo_path_extents (cr, &x1, &y1, &x2, &y2);
748 } else if (thickness > 0) {
749 cairo_stroke_extents (cr, &x1, &y1, &x2, &y2);
750 } else {
751 cairo_fill_extents (cr, &x1, &y1, &x2, &y2);
754 Rect bounds = Rect (MIN (x1, x2), MIN (y1, y2), fabs (x2 - x1), fabs (y2 - y1));
756 measuring_context_destroy (cr);
758 return bounds;
761 void
762 Shape::GetSizeForBrush (cairo_t *cr, double *width, double *height)
764 *height = GetStretchExtents ().height;
765 *width = GetStretchExtents ().width;
768 bool
769 Shape::InsideObject (cairo_t *cr, double x, double y)
771 bool ret = false;
773 if (!InsideLayoutClip (x, y))
774 return false;
776 if (!InsideClip (cr, x, y))
777 return false;
779 TransformPoint (&x, &y);
780 if (!GetStretchExtents ().PointInside (x, y))
781 return false;
783 cairo_save (cr);
784 DoDraw (cr, false);
786 // don't check in_stroke without a stroke or in_fill without a fill (even if it can be filled)
787 if (fill && CanFill ())
788 ret |= cairo_in_fill (cr, x, y);
789 if (!ret && stroke)
790 ret |= cairo_in_stroke (cr, x, y);
792 cairo_new_path (cr);
793 cairo_restore (cr);
795 return ret;
798 void
799 Shape::CacheInvalidateHint (void)
801 // Also kills the surface cache
802 InvalidatePathCache ();
805 void
806 Shape::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
808 if (args->GetProperty ()->GetOwnerType() != Type::SHAPE) {
809 if ((args->GetId () == FrameworkElement::HeightProperty)
810 || (args->GetId () == FrameworkElement::WidthProperty))
811 InvalidateStretch ();
813 // CacheInvalidateHint should handle visibility changes in
814 // DirtyRenderVisibility
816 FrameworkElement::OnPropertyChanged (args, error);
817 return;
820 if (args->GetId () == Shape::StretchProperty) {
821 InvalidateMeasure ();
822 InvalidateStretch ();
824 else if (args->GetId () == Shape::StrokeProperty) {
825 Brush *new_stroke = args->GetNewValue() ? args->GetNewValue()->AsBrush () : NULL;
827 if (!stroke || !new_stroke) {
828 // If the stroke changes from null to
829 // <something> or <something> to null, then
830 // some shapes need to reclaculate the offset
831 // (based on stroke thickness) to start
832 // painting.
833 InvalidateStrokeBounds ();
834 } else
835 InvalidateSurfaceCache ();
837 stroke = new_stroke;
838 } else if (args->GetId () == Shape::FillProperty) {
839 Brush *new_fill = args->GetNewValue() ? args->GetNewValue()->AsBrush () : NULL;
841 if (!fill || !new_fill) {
842 InvalidateFillBounds ();
843 } else
844 InvalidateSurfaceCache ();
846 fill = args->GetNewValue() ? args->GetNewValue()->AsBrush() : NULL;
847 } else if (args->GetId () == Shape::StrokeThicknessProperty) {
848 // do we invalidate the path here for Type::RECT and Type::ELLIPSE
849 // in case they degenerate? Or do we need it for line caps too
850 InvalidateStrokeBounds ();
851 } else if (args->GetId () == Shape::StrokeDashCapProperty
852 || args->GetId () == Shape::StrokeEndLineCapProperty
853 || args->GetId () == Shape::StrokeLineJoinProperty
854 || args->GetId () == Shape::StrokeMiterLimitProperty
855 || args->GetId () == Shape::StrokeStartLineCapProperty) {
856 InvalidateStrokeBounds ();
859 Invalidate ();
861 NotifyListenersOfPropertyChange (args, error);
864 void
865 Shape::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
867 if (prop && (prop->GetId () == Shape::FillProperty || prop->GetId () == Shape::StrokeProperty)) {
868 Invalidate ();
869 InvalidateSurfaceCache ();
871 else
872 FrameworkElement::OnSubPropertyChanged (prop, obj, subobj_args);
875 void
876 Shape::InvalidateStretch ()
878 extents = Rect (0, 0, -INFINITY, -INFINITY);
879 cairo_matrix_init_identity (&stretch_transform);
880 InvalidatePathCache ();
883 Rect
884 Shape::GetStretchExtents ()
886 if (extents.IsEmpty ())
887 extents = ComputeStretchBounds ();
889 return extents;
892 void
893 Shape::InvalidateStrokeBounds ()
895 InvalidateNaturalBounds ();
898 void
899 Shape::InvalidateFillBounds ()
901 InvalidateNaturalBounds ();
904 void
905 Shape::InvalidateNaturalBounds ()
907 natural_bounds = Rect (0, 0, -INFINITY, -INFINITY);
908 InvalidateStretch ();
911 Rect
912 Shape::GetNaturalBounds ()
914 if (natural_bounds.IsEmpty ())
915 natural_bounds = ComputeShapeBounds (false, NULL);
917 return natural_bounds;
920 void
921 Shape::InvalidatePathCache (bool free)
923 //SetShapeFlags (UIElement::SHAPE_NORMAL);
924 if (path) {
925 if (free) {
926 moon_path_destroy (path);
927 path = NULL;
928 } else {
929 moon_path_clear (path);
933 // we always pass true here because in some cases
934 // while the bounds may not have change the rendering
935 // still may have
936 UpdateBounds (true);
937 //InvalidateMeasure ();
938 //InvalidateArrange ();
939 InvalidateSurfaceCache ();
942 void
943 Shape::InvalidateSurfaceCache (void)
945 if (cached_surface) {
946 cairo_surface_destroy (cached_surface);
947 if (GetSurface ())
948 GetSurface ()->RemoveFromCacheSizeCounter (cached_size);
949 cached_surface = NULL;
950 cached_size = 0;
954 Value *
955 Shape::CreateDefaultStretch (DependencyObject *instance, DependencyProperty *property)
957 if (instance->Is (Type::RECTANGLE) || instance->Is (Type::ELLIPSE))
958 return new Value (StretchFill);
959 else
960 return new Value (StretchNone);
964 // Ellipse
967 Ellipse::Ellipse ()
969 SetObjectType (Type::ELLIPSE);
973 * Ellipses (like Rectangles) are special and they don't need to participate
974 * in the other stretch logic
976 Rect
977 Ellipse::ComputeStretchBounds ()
979 return ComputeShapeBounds (false);
982 Rect
983 Ellipse::ComputeShapeBounds (bool logical)
985 Rect rect = Rect (0, 0, GetActualWidth (), GetActualHeight ());
986 SetShapeFlags (UIElement::SHAPE_NORMAL);
987 double t = GetStrokeThickness ();
989 if (rect.width < 0.0 || rect.height < 0.0 || GetWidth () <= 0.0 || GetHeight () <= 0.0) {
990 SetShapeFlags (UIElement::SHAPE_EMPTY);
991 return Rect ();
994 if (GetVisualParent () && GetVisualParent ()->Is (Type::CANVAS)) {
995 if (isnan (GetWidth ()) != isnan (GetHeight ())) {
996 SetShapeFlags (UIElement::SHAPE_EMPTY);
997 return Rect ();
1001 switch (GetStretch ()) {
1002 case StretchNone:
1003 rect.width = rect.height = 0.0;
1004 break;
1005 case StretchUniform:
1006 rect.width = rect.height = (rect.width < rect.height) ? rect.width : rect.height;
1007 break;
1008 case StretchUniformToFill:
1009 rect.width = rect.height = (rect.width > rect.height) ? rect.width : rect.height;
1010 break;
1011 case StretchFill:
1012 /* nothing needed here. the assignment of w/h above
1013 is correct for this case. */
1014 break;
1017 if (rect.width <= t || rect.height <= t){
1018 rect.width = MAX (rect.width, t + t * 0.001);
1019 rect.height = MAX (rect.height, t + t * 0.001);
1020 SetShapeFlags (UIElement::SHAPE_DEGENERATE);
1021 } else
1022 SetShapeFlags (UIElement::SHAPE_NORMAL);
1024 return rect;
1027 // * Shape::StrokeStartLineCap
1028 // * Shape::StrokeEndLineCap
1029 // * Shape::StrokeLineJoin
1030 // * Shape::StrokeMiterLimit
1031 bool
1032 Ellipse::DrawShape (cairo_t *cr, bool do_op)
1034 bool drawn = Fill (cr, do_op);
1036 if (!stroke)
1037 return drawn;
1038 if (!SetupLine (cr))
1039 return drawn;
1040 SetupLineCaps (cr);
1042 if (!drawn)
1043 Draw (cr);
1044 Stroke (cr, do_op);
1045 return true;
1048 void
1049 Ellipse::BuildPath ()
1051 Stretch stretch = GetStretch ();
1052 double t = IsStroked () ? GetStrokeThickness () : 0.0;
1053 Rect rect = Rect (0.0, 0.0, GetActualWidth (), GetActualHeight ());
1055 if (rect.width < 0.0 || rect.height < 0.0 || GetWidth () <= 0.0 || GetHeight () <= 0.0) {
1056 SetShapeFlags (UIElement::SHAPE_EMPTY);
1057 return;
1061 SetShapeFlags (UIElement::SHAPE_NORMAL);
1063 switch (stretch) {
1064 case StretchNone:
1065 rect.width = rect.height = 0.0;
1066 break;
1067 case StretchUniform:
1068 rect.width = rect.height = (rect.width < rect.height) ? rect.width : rect.height;
1069 break;
1070 case StretchUniformToFill:
1071 rect.width = rect.height = (rect.width > rect.height) ? rect.width : rect.height;
1072 break;
1073 case StretchFill:
1074 /* nothing needed here. the assignment of w/h above
1075 is correct for this case. */
1076 break;
1079 if (rect.width <= t || rect.height <= t){
1080 rect.width = MAX (rect.width, t + t * 0.001);
1081 rect.height = MAX (rect.height, t + t * 0.001);
1082 SetShapeFlags (UIElement::SHAPE_DEGENERATE);
1083 } else
1084 SetShapeFlags (UIElement::SHAPE_NORMAL);
1086 rect = rect.GrowBy ( -t/2, -t/2);
1088 path = moon_path_renew (path, MOON_PATH_ELLIPSE_LENGTH);
1089 moon_ellipse (path, rect.x, rect.y, rect.width, rect.height);
1092 void
1093 Ellipse::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1096 DependencyProperty *prop = args->GetProperty ();
1098 if ((prop->GetId () == Shape::StrokeThicknessProperty) || (prop->GetId () == Shape::StretchProperty) ||
1099 (prop->GetId () == FrameworkElement::WidthProperty) || (prop->GetId () == FrameworkElement::HeightProperty)) {
1100 // FIXME why are we building the path here?
1101 BuildPath ();
1102 InvalidateSurfaceCache ();
1106 // Ellipse has no property of it's own
1107 Shape::OnPropertyChanged (args, error);
1111 // Rectangle
1114 Rectangle::Rectangle ()
1116 SetObjectType (Type::RECTANGLE);
1120 * Rectangles (like Ellipses) are special and they don't need to participate
1121 * in the other stretch logic
1123 Rect
1124 Rectangle::ComputeStretchBounds ()
1126 Rect shape_bounds = ComputeShapeBounds (false);
1127 return ComputeShapeBounds (false);
1130 Rect
1131 Rectangle::ComputeShapeBounds (bool logical)
1133 Rect rect = Rect (0, 0, GetActualWidth (), GetActualHeight ());
1134 SetShapeFlags (UIElement::SHAPE_NORMAL);
1136 if (rect.width < 0.0 || rect.height < 0.0 || GetWidth () <= 0.0 || GetHeight () <= 0.0) {
1137 SetShapeFlags (UIElement::SHAPE_EMPTY);
1138 return Rect ();
1141 if (GetVisualParent () && GetVisualParent ()->Is (Type::CANVAS)) {
1142 if (isnan (GetWidth ()) != isnan (GetHeight ())) {
1143 SetShapeFlags (UIElement::SHAPE_EMPTY);
1144 return Rect ();
1148 double t = IsStroked () ? GetStrokeThickness () : 0.0;
1149 switch (GetStretch ()) {
1150 case StretchNone:
1151 rect.width = rect.height = 0.0;
1152 break;
1153 case StretchUniform:
1154 rect.width = rect.height = MIN (rect.width, rect.height);
1155 break;
1156 case StretchUniformToFill:
1157 // this gets an rectangle larger than it's dimension, relative
1158 // scaling is ok but we need Shape::Draw to clip to it's original size
1159 rect.width = rect.height = MAX (rect.width, rect.height);
1160 break;
1161 case StretchFill:
1162 /* nothing needed here. the assignment of w/h above
1163 is correct for this case. */
1164 break;
1167 if (rect.width == 0)
1168 rect.x = t *.5;
1169 if (rect.height == 0)
1170 rect.y = t *.5;
1172 if (t >= rect.width || t >= rect.height) {
1173 SetShapeFlags (UIElement::SHAPE_DEGENERATE);
1174 rect = rect.GrowBy (t * .5005, t * .5005);
1175 } else {
1176 SetShapeFlags (UIElement::SHAPE_NORMAL);
1179 return rect;
1182 Rect
1183 Rectangle::GetCoverageBounds ()
1185 Brush *fill = GetFill ();
1187 if (fill != NULL && fill->IsOpaque()) {
1188 /* make it a little easier - only consider the rectangle inside the corner radii.
1189 we're also a little more conservative than we need to be, regarding stroke
1190 thickness. */
1191 double xr = (GetRadiusX () + GetStrokeThickness () / 2);
1192 double yr = (GetRadiusY () + GetStrokeThickness () / 2);
1194 return bounds.GrowBy (-xr, -yr).RoundIn ();
1197 return Rect ();
1201 // The Rectangle shape can be drawn while ignoring properties:
1202 // * Shape::StrokeStartLineCap
1203 // * Shape::StrokeEndLineCap
1204 // * Shape::StrokeLineJoin [for rounded-corner rectangles only]
1205 // * Shape::StrokeMiterLimit [for rounded-corner rectangles only]
1206 bool
1207 Rectangle::DrawShape (cairo_t *cr, bool do_op)
1209 bool drawn = Fill (cr, do_op);
1211 if (!stroke)
1212 return drawn;
1214 if (!SetupLine (cr))
1215 return drawn;
1217 SetupLineCaps (cr);
1219 if (!HasRadii ())
1220 SetupLineJoinMiter (cr);
1222 // Draw if the path wasn't drawn by the Fill call
1223 if (!drawn)
1224 Draw (cr);
1225 Stroke (cr, do_op);
1226 return true;
1230 * rendering notes:
1231 * - a Width="0" or a Height="0" can be rendered differently from not specifying Width or Height
1232 * - if a rectangle has only a Width or only a Height it is NEVER rendered
1234 void
1235 Rectangle::BuildPath ()
1237 Stretch stretch = GetStretch ();
1238 double t = IsStroked () ? GetStrokeThickness () : 0.0;
1240 // nothing is drawn (nor filled) if no StrokeThickness="0"
1241 // unless both Width and Height are specified or when no streching is required
1242 Rect rect = Rect (0, 0, GetActualWidth (), GetActualHeight ());
1244 double radius_x = GetRadiusX ();
1245 double radius_y = GetRadiusY ();
1247 switch (stretch) {
1248 case StretchNone:
1249 rect.width = rect.height = 0;
1250 break;
1251 case StretchUniform:
1252 rect.width = rect.height = MIN (rect.width, rect.height);
1253 break;
1254 case StretchUniformToFill:
1255 // this gets an rectangle larger than it's dimension, relative
1256 // scaling is ok but we need Shape::Draw to clip to it's original size
1257 rect.width = rect.height = MAX (rect.width, rect.height);
1258 break;
1259 case StretchFill:
1260 /* nothing needed here. the assignment of w/h above
1261 is correct for this case. */
1262 break;
1265 if (rect.width == 0)
1266 rect.x = t *.5;
1267 if (rect.height == 0)
1268 rect.y = t *.5;
1270 if (t >= rect.width || t >= rect.height) {
1271 rect = rect.GrowBy (t * 0.001, t * 0.001);
1272 SetShapeFlags (UIElement::SHAPE_DEGENERATE);
1273 } else {
1274 rect = rect.GrowBy (-t * 0.5, -t * 0.5);
1275 SetShapeFlags (UIElement::SHAPE_NORMAL);
1278 path = moon_path_renew (path, MOON_PATH_ROUNDED_RECTANGLE_LENGTH);
1279 moon_rounded_rectangle (path, rect.x, rect.y, rect.width, rect.height, radius_x, radius_y);
1282 void
1283 Rectangle::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1285 if (args->GetProperty ()->GetOwnerType() != Type::RECTANGLE) {
1286 Shape::OnPropertyChanged (args, error);
1287 return;
1290 if ((args->GetId () == Rectangle::RadiusXProperty) || (args->GetId () == Rectangle::RadiusYProperty)) {
1291 InvalidateMeasure ();
1292 InvalidatePathCache ();
1295 Invalidate ();
1296 NotifyListenersOfPropertyChange (args, error);
1300 // Line
1302 // rules
1303 // * the internal path must be rebuilt when
1304 // - Line::X1Property, Line::Y1Property, Line::X2Property or Line::Y2Property is changed
1306 // * bounds calculation is based on
1307 // - Line::X1Property, Line::Y1Property, Line::X2Property and Line::Y2Property
1308 // - Shape::StrokeThickness
1311 #define LINECAP_SMALL_OFFSET 0.1
1313 //Draw the start cap. Shared with Polyline
1314 static void
1315 line_draw_cap (cairo_t *cr, Shape* shape, PenLineCap cap, double x1, double y1, double x2, double y2)
1317 double sx1, sy1;
1318 if (cap == PenLineCapFlat)
1319 return;
1321 cairo_save (cr);
1322 cairo_transform (cr, &(shape->stretch_transform));
1323 if (cap == PenLineCapRound) {
1324 cairo_move_to (cr, x1, y1);
1325 cairo_line_to (cr, x1, y1);
1326 cairo_restore (cr);
1327 cairo_set_line_cap (cr, convert_line_cap (cap));
1328 shape->Stroke (cr, true);
1329 return;
1332 if (x1 == x2) {
1333 // vertical line
1334 sx1 = x1;
1335 if (y1 > y2)
1336 sy1 = y1 + LINECAP_SMALL_OFFSET;
1337 else
1338 sy1 = y1 - LINECAP_SMALL_OFFSET;
1339 } else if (y1 == y2) {
1340 // horizontal line
1341 sy1 = y1;
1342 if (x1 > x2)
1343 sx1 = x1 + LINECAP_SMALL_OFFSET;
1344 else
1345 sx1 = x1 - LINECAP_SMALL_OFFSET;
1346 } else {
1347 double m = (y1 - y2) / (x1 - x2);
1348 if (x1 > x2) {
1349 sx1 = x1 + LINECAP_SMALL_OFFSET;
1350 } else {
1351 sx1 = x1 - LINECAP_SMALL_OFFSET;
1353 sy1 = m * sx1 + y1 - (m * x1);
1355 cairo_move_to (cr, x1, y1);
1356 cairo_line_to (cr, sx1, sy1);
1357 cairo_restore (cr);
1358 cairo_set_line_cap (cr, convert_line_cap (cap));
1359 shape->Stroke (cr, true);
1362 // The Line shape can be drawn while ignoring properties:
1363 // * Shape::StrokeLineJoin
1364 // * Shape::StrokeMiterLimit
1365 // * Shape::Fill
1366 bool
1367 Line::DrawShape (cairo_t *cr, bool do_op)
1369 // no need to clear path since none has been drawn to cairo
1370 if (!stroke)
1371 return false;
1373 if (!SetupLine (cr))
1374 return false;
1376 // here we hack around #345888 where Cairo doesn't support different start and end linecaps
1377 PenLineCap start = GetStrokeStartLineCap ();
1378 PenLineCap end = GetStrokeEndLineCap ();
1379 PenLineCap dash = GetStrokeDashCap ();
1380 bool dashed = false;
1381 DoubleCollection *dashes = GetStrokeDashArray ();
1383 if (dashes && (dashes->GetCount() > 0))
1384 dashed = true;
1387 //if (do_op && !(start == end && start == dash)) {
1388 if (do_op && (start != end || (dashed && !(start == end && start == dash)))) {
1389 double x1 = GetX1 ();
1390 double y1 = GetY1 ();
1391 double x2 = GetX2 ();
1392 double y2 = GetY2 ();
1394 // draw start and end line caps
1395 if (start != PenLineCapFlat)
1396 line_draw_cap (cr, this, start, x1, y1, x2, y2);
1398 if (end != PenLineCapFlat) {
1399 //don't draw the end cap if it's in an "off" segment
1400 double thickness = GetStrokeThickness ();
1401 double offset = GetStrokeDashOffset ();
1403 SetupDashes (cr, thickness, sqrt ((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)) + offset * thickness);
1404 line_draw_cap (cr, this, end, x2, y2, x1, y1);
1405 SetupLine (cr);
1408 cairo_set_line_cap (cr, convert_line_cap (dash));
1409 } else
1410 cairo_set_line_cap (cr, convert_line_cap (start));
1412 Draw (cr);
1413 Stroke (cr, do_op);
1414 return true;
1417 void
1418 calc_line_bounds (double x1, double x2, double y1, double y2, double thickness, PenLineCap start_cap, PenLineCap end_cap, Rect* bounds)
1420 if (x1 == x2) {
1421 bounds->x = x1 - thickness / 2.0;
1422 bounds->y = MIN (y1, y2) - (y1 < y2 && start_cap != PenLineCapFlat ? thickness / 2.0 : 0.0) - (y1 >= y2 && end_cap != PenLineCapFlat ? thickness / 2.0 : 0.0);
1423 bounds->width = thickness;
1424 bounds->height = fabs (y2 - y1) + (start_cap != PenLineCapFlat ? thickness / 2.0 : 0.0) + (end_cap != PenLineCapFlat ? thickness / 2.0 : 0.0);
1425 } else if (y1 == y2) {
1426 bounds->x = MIN (x1, x2) - (x1 < x2 && start_cap != PenLineCapFlat ? thickness / 2.0 : 0.0) - (x1 >= x2 && end_cap != PenLineCapFlat ? thickness / 2.0 : 0.0);
1427 bounds->y = y1 - thickness / 2.0;
1428 bounds->width = fabs (x2 - x1) + (start_cap != PenLineCapFlat ? thickness / 2.0 : 0.0) + (end_cap != PenLineCapFlat ? thickness / 2.0 : 0.0);
1429 bounds->height = thickness;
1430 } else {
1431 double m = fabs ((y1 - y2) / (x1 - x2));
1432 #if EXACT_BOUNDS
1433 double dx = sin (atan (m)) * thickness;
1434 double dy = cos (atan (m)) * thickness;
1435 #else
1436 double dx = (m > 1.0) ? thickness : thickness * m;
1437 double dy = (m < 1.0) ? thickness : thickness / m;
1438 #endif
1439 if (x1 < x2)
1440 switch (start_cap) {
1441 case PenLineCapSquare:
1442 bounds->x = MIN (x1, x2) - (dx + dy) / 2.0;
1443 break;
1444 case PenLineCapTriangle: //FIXME, reverting to Round for now
1445 case PenLineCapRound:
1446 bounds->x = MIN (x1, x2) - thickness / 2.0;
1447 break;
1448 default: //PenLineCapFlat
1449 bounds->x = MIN (x1, x2) - dx / 2.0;
1451 else
1452 switch (end_cap) {
1453 case PenLineCapSquare:
1454 bounds->x = MIN (x1, x2) - (dx + dy) / 2.0;
1455 break;
1456 case PenLineCapTriangle: //FIXME, reverting to Round for now
1457 case PenLineCapRound:
1458 bounds->x = MIN (x1, x2) - thickness / 2.0;
1459 break;
1460 default: //PenLineCapFlat
1461 bounds->x = MIN (x1, x2) - dx / 2.0;
1463 if (y1 < y2)
1464 switch (start_cap) {
1465 case PenLineCapSquare:
1466 bounds->y = MIN (y1, y2) - (dx + dy) / 2.0;
1467 break;
1468 case PenLineCapTriangle: //FIXME, reverting to Round for now
1469 case PenLineCapRound:
1470 bounds->y = MIN (y1, y2) - thickness / 2.0;
1471 break;
1472 default: //PenLineCapFlat
1473 bounds->y = MIN (y1, y2) - dy / 2.0;
1475 else
1476 switch (end_cap) {
1477 case PenLineCapSquare:
1478 bounds->y = MIN (y1, y2) - (dx + dy) / 2.0;
1479 break;
1480 case PenLineCapTriangle: //FIXME, reverting to Round for now
1481 case PenLineCapRound:
1482 bounds->y = MIN (y1, y2) - thickness / 2.0;
1483 break;
1484 default: //PenLineCapFlat
1485 bounds->y = MIN (y1, y2) - dy / 2.0;
1487 bounds->width = fabs (x2 - x1);
1488 bounds->height = fabs (y2 - y1);
1489 switch (start_cap) {
1490 case PenLineCapSquare:
1491 bounds->width += (dx + dy) / 2.0;
1492 bounds->height += (dx + dy) / 2.0;
1493 break;
1494 case PenLineCapTriangle: //FIXME, reverting to Round for now
1495 case PenLineCapRound:
1496 bounds->width += thickness / 2.0;
1497 bounds->height += thickness / 2.0;
1498 break;
1499 default: //PenLineCapFlat
1500 bounds->width += dx/2.0;
1501 bounds->height += dy/2.0;
1503 switch (end_cap) {
1504 case PenLineCapSquare:
1505 bounds->width += (dx + dy) / 2.0;
1506 bounds->height += (dx + dy) / 2.0;
1507 break;
1508 case PenLineCapTriangle: //FIXME, reverting to Round for now
1509 case PenLineCapRound:
1510 bounds->width += thickness / 2.0;
1511 bounds->height += thickness / 2.0;
1512 break;
1513 default: //PenLineCapFlat
1514 bounds->width += dx/2.0;
1515 bounds->height += dy/2.0;
1520 void
1521 Line::BuildPath ()
1523 SetShapeFlags (UIElement::SHAPE_NORMAL);
1525 path = moon_path_renew (path, MOON_PATH_MOVE_TO_LENGTH + MOON_PATH_LINE_TO_LENGTH);
1527 double x1 = GetX1 ();
1528 double y1 = GetY1 ();
1529 double x2 = GetX2 ();
1530 double y2 = GetY2 ();
1532 moon_move_to (path, x1, y1);
1533 moon_line_to (path, x2, y2);
1536 Rect
1537 Line::ComputeShapeBounds (bool logical)
1539 Rect shape_bounds = Rect ();
1540 double thickness;
1542 if (!logical)
1543 thickness = GetStrokeThickness ();
1544 else
1545 thickness = 0.0;
1547 PenLineCap start_cap, end_cap;
1548 if (!logical) {
1549 start_cap = GetStrokeStartLineCap ();
1550 end_cap = GetStrokeEndLineCap ();
1551 } else
1552 start_cap = end_cap = PenLineCapFlat;
1554 if (thickness <= 0.0 && !logical)
1555 return shape_bounds;
1557 double x1 = GetX1 ();
1558 double y1 = GetY1 ();
1559 double x2 = GetX2 ();
1560 double y2 = GetY2 ();
1562 calc_line_bounds (x1, x2, y1, y2, thickness, start_cap, end_cap, &shape_bounds);
1564 return shape_bounds;
1567 void
1568 Line::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1570 if (args->GetProperty ()->GetOwnerType() != Type::LINE) {
1571 Shape::OnPropertyChanged (args, error);
1572 return;
1575 if (args->GetId () == Line::X1Property ||
1576 args->GetId () == Line::X2Property ||
1577 args->GetId () == Line::Y1Property ||
1578 args->GetId () == Line::Y2Property) {
1579 InvalidateNaturalBounds ();
1582 NotifyListenersOfPropertyChange (args, error);
1586 // Polygon
1588 // rules
1589 // * the internal path must be rebuilt when
1590 // - Polygon::PointsProperty is changed
1591 // - Shape::StretchProperty is changed
1593 // * bounds calculation is based on
1594 // - Polygon::PointsProperty
1595 // - Shape::StretchProperty
1596 // - Shape::StrokeThickness
1599 Polygon::Polygon ()
1601 SetObjectType (Type::POLYGON);
1604 // The Polygon shape can be drawn while ignoring properties:
1605 // * Shape::StrokeStartLineCap
1606 // * Shape::StrokeEndLineCap
1607 bool
1608 Polygon::DrawShape (cairo_t *cr, bool do_op)
1610 bool drawn = Fill (cr, do_op);
1612 if (!stroke)
1613 return drawn;
1615 if (!SetupLine (cr))
1616 return drawn;
1617 SetupLineCaps (cr);
1618 SetupLineJoinMiter (cr);
1620 Draw (cr);
1621 Stroke (cr, do_op);
1622 return true;
1625 // special case when a polygon has a single line in it (it's drawn longer than it should)
1626 // e.g. <Polygon Fill="#000000" Stroke="#FF00FF" StrokeThickness="8" Points="260,80 300,40" />
1627 static void
1628 polygon_extend_line (double *x1, double *x2, double *y1, double *y2, double thickness)
1630 // not sure why it's a 5 ? afaik it's not related to the line length or any other property
1631 double t5 = thickness * 5.0;
1632 double dx = *x1 - *x2;
1633 double dy = *y1 - *y2;
1635 if (dy == 0.0) {
1636 t5 -= thickness / 2.0;
1637 if (dx > 0.0) {
1638 *x1 += t5;
1639 *x2 -= t5;
1640 } else {
1641 *x1 -= t5;
1642 *x2 += t5;
1644 } else if (dx == 0.0) {
1645 t5 -= thickness / 2.0;
1646 if (dy > 0.0) {
1647 *y1 += t5;
1648 *y2 -= t5;
1649 } else {
1650 *y1 -= t5;
1651 *y2 += t5;
1653 } else {
1654 double angle = atan (dy / dx);
1655 double ax = fabs (sin (angle) * t5);
1656 if (dx > 0.0) {
1657 *x1 += ax;
1658 *x2 -= ax;
1659 } else {
1660 *x1 -= ax;
1661 *x2 += ax;
1663 double ay = fabs (sin ((M_PI / 2.0) - angle)) * t5;
1664 if (dy > 0.0) {
1665 *y1 += ay;
1666 *y2 -= ay;
1667 } else {
1668 *y1 -= ay;
1669 *y2 += ay;
1674 void
1675 Polygon::BuildPath ()
1677 PointCollection *col = GetPoints ();
1679 // the first point is a move to, resulting in an empty shape
1680 if (!col || (col->GetCount() < 2)) {
1681 SetShapeFlags (UIElement::SHAPE_EMPTY);
1682 return;
1685 int i, count = col->GetCount();
1686 GPtrArray* points = col->Array();
1688 SetShapeFlags (UIElement::SHAPE_NORMAL);
1690 // 2 data per [move|line]_to + 1 for close path
1691 path = moon_path_renew (path, count * 2 + 1);
1693 // special case, both the starting and ending points are 5 * thickness than the actual points
1694 if (count == 2) {
1695 double thickness = GetStrokeThickness ();
1696 double x1 = ((Value*)g_ptr_array_index(points, 0))->AsPoint()->x;
1697 double y1 = ((Value*)g_ptr_array_index(points, 0))->AsPoint()->y;
1698 double x2 = ((Value*)g_ptr_array_index(points, 1))->AsPoint()->x;
1699 double y2 = ((Value*)g_ptr_array_index(points, 1))->AsPoint()->y;
1701 polygon_extend_line (&x1, &x2, &y1, &y2, thickness);
1703 moon_move_to (path, x1, y1);
1704 moon_line_to (path, x2, y2);
1705 } else {
1706 moon_move_to (path,
1707 ((Value*)g_ptr_array_index(points, 0))->AsPoint()->x,
1708 ((Value*)g_ptr_array_index(points, 0))->AsPoint()->y);
1709 for (i = 1; i < count; i++)
1710 moon_line_to (path,
1711 ((Value*)g_ptr_array_index(points, i))->AsPoint()->x,
1712 ((Value*)g_ptr_array_index(points, i))->AsPoint()->y);
1714 moon_close_path (path);
1717 void
1718 Polygon::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1720 if (args->GetProperty ()->GetOwnerType() != Type::POLYGON) {
1721 Shape::OnPropertyChanged (args, error);
1722 return;
1725 if (args->GetId () == Polygon::PointsProperty) {
1726 InvalidateNaturalBounds ();
1729 Invalidate ();
1730 NotifyListenersOfPropertyChange (args, error);
1733 void
1734 Polygon::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
1736 Shape::OnCollectionChanged (col, args);
1738 InvalidateNaturalBounds ();
1741 void
1742 Polygon::OnCollectionItemChanged (Collection *col, DependencyObject *obj, PropertyChangedEventArgs *args)
1744 Shape::OnCollectionItemChanged (col, obj, args);
1746 InvalidateNaturalBounds ();
1750 // Polyline
1752 // rules
1753 // * the internal path must be rebuilt when
1754 // - Polyline::PointsProperty is changed
1755 // - Shape::StretchProperty is changed
1757 // * bounds calculation is based on
1758 // - Polyline::PointsProperty
1759 // - Shape::StretchProperty
1760 // - Shape::StrokeThickness
1762 Polyline::Polyline ()
1764 SetObjectType (Type::POLYLINE);
1767 // The Polyline shape can be drawn while ignoring NO properties
1768 bool
1769 Polyline::DrawShape (cairo_t *cr, bool do_op)
1771 bool drawn = Fill (cr, do_op);
1773 if (!stroke)
1774 return drawn;
1776 if (!SetupLine (cr))
1777 return drawn;
1778 SetupLineJoinMiter (cr);
1780 // here we hack around #345888 where Cairo doesn't support different start and end linecaps
1781 PenLineCap start = GetStrokeStartLineCap ();
1782 PenLineCap end = GetStrokeEndLineCap ();
1783 PenLineCap dash = GetStrokeDashCap ();
1785 if (do_op && ! (start == end && start == dash)){
1786 // the previous fill, if needed, has preserved the path
1787 if (drawn)
1788 cairo_new_path (cr);
1790 // since Draw may not have been called (e.g. no Fill) we must ensure the path was built
1791 if (!drawn || !path || (path->cairo.num_data == 0))
1792 BuildPath ();
1794 cairo_path_data_t *data = path->cairo.data;
1795 int length = path->cairo.num_data;
1796 // single point polylines are not rendered
1797 if (length >= MOON_PATH_MOVE_TO_LENGTH + MOON_PATH_LINE_TO_LENGTH) {
1798 // draw line #1 with start cap
1799 if (start != PenLineCapFlat) {
1800 line_draw_cap (cr, this, start, data[1].point.x, data[1].point.y, data[3].point.x, data[3].point.y);
1802 // draw last line with end cap
1803 if (end != PenLineCapFlat) {
1804 line_draw_cap (cr, this, end, data[length-1].point.x, data[length-1].point.y, data[length-3].point.x, data[length-3].point.y);
1808 cairo_set_line_cap (cr, convert_line_cap (dash));
1810 Draw (cr);
1811 Stroke (cr, do_op);
1812 return true;
1815 void
1816 Polyline::BuildPath ()
1818 PointCollection *col = GetPoints ();
1820 // the first point is a move to, resulting in an empty shape
1821 if (!col || (col->GetCount() < 2)) {
1822 SetShapeFlags (UIElement::SHAPE_EMPTY);
1823 return;
1826 int i, count = col->GetCount();
1827 GPtrArray *points = col->Array();
1829 SetShapeFlags (UIElement::SHAPE_NORMAL);
1831 // 2 data per [move|line]_to
1832 path = moon_path_renew (path, count * 2);
1834 moon_move_to (path,
1835 ((Value*)g_ptr_array_index(points, 0))->AsPoint()->x,
1836 ((Value*)g_ptr_array_index(points, 0))->AsPoint()->y);
1838 for (i = 1; i < count; i++)
1839 moon_line_to (path,
1840 ((Value*)g_ptr_array_index(points, i))->AsPoint()->x,
1841 ((Value*)g_ptr_array_index(points, i))->AsPoint()->y);
1844 void
1845 Polyline::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1847 if (args->GetProperty ()->GetOwnerType() != Type::POLYLINE) {
1848 Shape::OnPropertyChanged (args, error);
1849 return;
1852 if (args->GetId () == Polyline::PointsProperty) {
1853 InvalidateNaturalBounds ();
1856 Invalidate ();
1857 NotifyListenersOfPropertyChange (args, error);
1860 void
1861 Polyline::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
1863 if (col != GetPoints ()) {
1864 Shape::OnCollectionChanged (col, args);
1865 return;
1868 InvalidateNaturalBounds ();
1871 void
1872 Polyline::OnCollectionItemChanged (Collection *col, DependencyObject *obj, PropertyChangedEventArgs *args)
1874 Shape::OnCollectionItemChanged (col, obj, args);
1876 InvalidateNaturalBounds ();
1880 // Path
1882 bool
1883 Path::SetupLine (cairo_t* cr)
1885 // we cannot use the thickness==0 optimization (like Shape::SetupLine provides)
1886 // since we'll be using cairo to compute the path's bounds later
1887 // see bug #352188 for an example of what this breaks
1888 double thickness = IsDegenerate () ? 1.0 : GetStrokeThickness ();
1889 cairo_set_line_width (cr, thickness);
1890 return SetupDashes (cr, thickness);
1893 // The Polygon shape can be drawn while ignoring properties:
1894 // * none
1895 // FIXME: actually it depends on the geometry, another level of optimization awaits ;-)
1896 // e.g. close geometries don't need to setup line caps,
1898 // line join/miter don't applies to curve, like EllipseGeometry
1899 bool
1900 Path::DrawShape (cairo_t *cr, bool do_op)
1902 bool drawn = Shape::Fill (cr, do_op);
1904 if (stroke) {
1905 if (!SetupLine (cr))
1906 return drawn; // return if we have a path in the cairo_t
1907 SetupLineCaps (cr);
1908 SetupLineJoinMiter (cr);
1910 if (!drawn)
1911 Draw (cr);
1912 Stroke (cr, do_op);
1915 return true;
1918 FillRule
1919 Path::GetFillRule ()
1921 Geometry *geometry;
1923 if (!(geometry = GetData ()))
1924 return Shape::GetFillRule ();
1926 return geometry->GetFillRule ();
1929 Rect
1930 Path::ComputeShapeBounds (bool logical, cairo_matrix_t *matrix)
1932 Rect shape_bounds = Rect ();
1933 Geometry *geometry;
1935 if (!(geometry = GetData ())) {
1936 SetShapeFlags (UIElement::SHAPE_EMPTY);
1937 return shape_bounds;
1940 if (logical)
1941 return geometry->GetBounds ();
1943 double thickness = !IsStroked () ? 0.0 : GetStrokeThickness ();
1945 cairo_t *cr = measuring_context_create ();
1946 cairo_set_line_width (cr, thickness);
1948 if (thickness > 0.0) {
1949 //FIXME: still not 100% precise since it could be different from the end cap
1950 PenLineCap cap = GetStrokeStartLineCap ();
1951 if (cap == PenLineCapFlat)
1952 cap = GetStrokeEndLineCap ();
1953 cairo_set_line_cap (cr, convert_line_cap (cap));
1956 if (matrix)
1957 cairo_set_matrix (cr, matrix);
1958 geometry->Draw (cr);
1960 cairo_identity_matrix (cr);
1962 double x1, y1, x2, y2;
1964 if (thickness > 0) {
1965 cairo_stroke_extents (cr, &x1, &y1, &x2, &y2);
1966 } else {
1967 cairo_fill_extents (cr, &x1, &y1, &x2, &y2);
1970 shape_bounds = Rect (MIN (x1, x2), MIN (y1, y2), fabs (x2 - x1), fabs (y2 - y1));
1972 measuring_context_destroy (cr);
1974 return shape_bounds;
1977 void
1978 Path::Draw (cairo_t *cr)
1980 cairo_new_path (cr);
1982 Geometry *geometry;
1984 if (!(geometry = GetData ()))
1985 return;
1987 cairo_save (cr);
1988 cairo_transform (cr, &stretch_transform);
1989 geometry->Draw (cr);
1990 cairo_restore (cr);
1993 void
1994 Path::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1996 if (args->GetProperty ()->GetOwnerType() != Type::PATH) {
1997 Shape::OnPropertyChanged (args, error);
1998 return;
2001 InvalidateNaturalBounds ();
2003 NotifyListenersOfPropertyChange (args, error);
2006 void
2007 Path::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
2009 if (prop && prop->GetId () == Path::DataProperty) {
2010 InvalidateNaturalBounds ();
2012 else
2013 Shape::OnSubPropertyChanged (prop, obj, subobj_args);
2017 * Right now implementing Path::ComputeLargestRectangle doesn't seems like a good idea. That would require
2018 * - checking the path for curves (and either flatten it or return an empty Rect)
2019 * - checking for polygon simplicity (finding intersections)
2020 * - checking for a convex polygon (if concave we can turn it into several convex or return an empty Rect)
2021 * - find the largest rectangle inside the (or each) convex polygon(s)
2022 * http://cgm.cs.mcgill.ca/~athens/cs507/Projects/2003/DanielSud/complete.html