2009-10-09 Chris Toshok <toshok@ximian.com>
[moon.git] / src / shape.cpp
blobfc70bd70337cd21366604f4f733ddb90a9191f40
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);
409 RenderLayoutClip (cr);
413 // Returns TRUE if surface is a good candidate for caching.
414 // We accept a little bit of scaling.
416 bool
417 Shape::IsCandidateForCaching (void)
419 if (IsEmpty ())
420 return FALSE;
422 if (! GetSurface ())
423 return FALSE;
426 * these fill and stroke short circuits are attempts be smart
427 * about determining when the cost of caching is greater than
428 * the cost of simply drawing all the choices here should really
429 * have best and worst case perf tests associated with them
430 * but they don't right now
432 bool gradient_fill = false;
433 /* XXX FIXME this should be a property on the shape */
434 bool simple = Is (Type::RECTANGLE) || Is (Type::ELLIPSE);
436 if (fill) {
437 if (fill->IsAnimating ())
438 return FALSE;
440 gradient_fill |= fill->Is (Type::GRADIENTBRUSH);
444 if (stroke && stroke->IsAnimating ())
445 return FALSE;
447 if (simple && !gradient_fill)
448 return FALSE;
450 // This is not 100% correct check -- the actual surface size might be
451 // a tiny little bit larger. It's not a problem though if we go few
452 // bytes above the cache limit.
453 if (!GetSurface ()->VerifyWithCacheSizeCounter ((int) bounds.width, (int) bounds.height))
454 return FALSE;
456 // one last line of defense, lets not cache things
457 // much larger than the screen.
458 if (bounds.width * bounds.height > 4000000)
459 return FALSE;
461 return TRUE;
465 // This routine is useful for Shape derivatives: it can be used
466 // to either get the bounding box from cairo, or to paint it
468 void
469 Shape::DoDraw (cairo_t *cr, bool do_op)
471 bool ret = FALSE;
473 // quick out if, when building the path, we detected an empty shape
474 if (IsEmpty ())
475 goto cleanpath;
477 if (do_op && cached_surface == NULL && IsCandidateForCaching ()) {
478 Rect cache_extents = bounds.RoundOut ();
479 cairo_t *cached_cr = NULL;
481 // g_warning ("bounds (%f, %f), extents (%f, %f), cache_extents (%f, %f)",
482 // bounds.width, bounds.height,
483 // extents.width, extents.height,
484 // cache_extents.width, cache_extents.height);
486 cached_surface = image_brush_create_similar (cr, (int) cache_extents.width, (int) cache_extents.height);
487 cairo_surface_set_device_offset (cached_surface, -cache_extents.x, -cache_extents.y);
488 cached_cr = cairo_create (cached_surface);
490 cairo_set_matrix (cached_cr, &absolute_xform);
492 ret = DrawShape (cached_cr, do_op);
494 cairo_destroy (cached_cr);
496 // Increase our cache size
497 cached_size = GetSurface ()->AddToCacheSizeCounter ((int) cache_extents.width, (int) cache_extents.height);
500 if (do_op && cached_surface) {
501 cairo_pattern_t *cached_pattern = NULL;
503 cached_pattern = cairo_pattern_create_for_surface (cached_surface);
504 cairo_set_matrix (cr, &absolute_xform);
505 if (do_op)
506 Clip (cr);
508 cairo_identity_matrix (cr);
509 cairo_set_source (cr, cached_pattern);
510 cairo_pattern_destroy (cached_pattern);
511 cairo_paint (cr);
512 } else {
513 cairo_set_matrix (cr, &absolute_xform);
514 if (do_op)
515 Clip (cr);
517 if (DrawShape (cr, do_op))
518 return;
521 cleanpath:
522 if (do_op)
523 cairo_new_path (cr);
526 void
527 Shape::Render (cairo_t *cr, Region *region, bool path_only)
529 cairo_save (cr);
530 DoDraw (cr, true && !path_only);
531 cairo_restore (cr);
534 void
535 Shape::ShiftPosition (Point p)
537 double dx = bounds.x - p.x;
538 double dy = bounds.y - p.y;
540 // FIXME this is much less than ideal but we must invalidate the surface cache
541 // if the shift is not an integer otherwise we can potentially drow outside our
542 // rounded out bounds.
543 if (cached_surface && (dx == trunc(dx)) && (dy == trunc(dy))) {
544 cairo_surface_set_device_offset (cached_surface, trunc (-p.x), trunc (-p.y));
545 } else {
546 InvalidateSurfaceCache ();
549 FrameworkElement::ShiftPosition (p);
553 Size
554 Shape::ComputeActualSize ()
556 Size desired = FrameworkElement::ComputeActualSize ();
557 Rect shape_bounds = GetNaturalBounds ();
558 double sx = 1.0;
559 double sy = 1.0;
561 if (!GetSurface ()) //|| LayoutInformation::GetPreviousConstraint (this) != NULL)
562 return desired;
564 if (shape_bounds.width <= 0 && shape_bounds.height <= 0)
565 return desired;
567 if (GetStretch () == StretchNone && shape_bounds.width > 0 && shape_bounds.height > 0)
568 return Size (shape_bounds.width, shape_bounds.height);
570 /* don't stretch to infinite size */
571 if (isinf (desired.width))
572 desired.width = shape_bounds.width;
573 if (isinf (desired.height))
574 desired.height = shape_bounds.height;
576 /* compute the scaling */
577 if (shape_bounds.width > 0)
578 sx = desired.width / shape_bounds.width;
579 if (shape_bounds.height > 0)
580 sy = desired.height / shape_bounds.height;
582 switch (GetStretch ()) {
583 case StretchUniform:
584 sx = sy = MIN (sx, sy);
585 break;
586 case StretchUniformToFill:
587 sx = sy = MAX (sx, sy);
588 break;
589 default:
590 break;
593 desired = desired.Min (shape_bounds.width * sx, shape_bounds.height * sy);
595 return desired;
598 Size
599 Shape::MeasureOverride (Size availableSize)
601 Size desired = availableSize;
602 Rect shape_bounds = GetNaturalBounds ();
603 double sx = 0.0;
604 double sy = 0.0;
606 if (GetStretch () == StretchNone)
607 return ApplySizeConstraints (Size (shape_bounds.x + shape_bounds.width, shape_bounds.y + shape_bounds.height));
609 if (Is (Type::RECTANGLE) || Is (Type::ELLIPSE)) {
610 desired = Size (0,0);
613 /* don't stretch to infinite size */
614 if (isinf (availableSize.width))
615 desired.width = shape_bounds.width;
616 if (isinf (availableSize.height))
617 desired.height = shape_bounds.height;
619 /* compute the scaling */
620 if (shape_bounds.width > 0)
621 sx = desired.width / shape_bounds.width;
622 if (shape_bounds.height > 0)
623 sy = desired.height / shape_bounds.height;
625 /* don't use infinite dimensions as constraints */
626 if (isinf (availableSize.width))
627 sx = sy;
628 if (isinf (availableSize.height))
629 sy = sx;
631 switch (GetStretch ()) {
632 case StretchUniform:
633 sx = sy = MIN (sx, sy);
634 break;
635 case StretchUniformToFill:
636 sx = sy = MAX (sx, sy);
637 break;
638 case StretchFill:
639 if (isinf (availableSize.width))
640 sx = 1.0;
641 if (isinf (availableSize.height))
642 sy = 1.0;
643 break;
646 desired = Size (shape_bounds.width * sx, shape_bounds.height * sy);
648 return desired.Min (availableSize);
651 Size
652 Shape::ArrangeOverride (Size finalSize)
654 Size arranged = finalSize;
655 Rect shape_bounds = GetNaturalBounds ();
656 double sx = 1.0;
657 double sy = 1.0;
659 //if (!LayoutInformation::GetPreviousConstraint (this))
660 // MeasureOverride (finalSize);
662 if (GetStretch () == StretchNone) {
663 arranged = Size (shape_bounds.x + shape_bounds.width,
664 shape_bounds.y + shape_bounds.height);
666 arranged = arranged.Max (finalSize);
668 return arranged;
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 if ((Is (Type::RECTANGLE) || Is (Type::ELLIPSE)) && LayoutInformation::GetPreviousConstraint (this)) {
696 arranged = ApplySizeConstraints (arranged);
698 extents = Rect (0,0, arranged.width, arranged.height);
701 // We need to clear any existing path so that it will be correctly
702 // rendered later
703 if (path)
704 moon_path_clear (path);
706 UpdateBounds ();
708 return arranged;
711 void
712 Shape::TransformBounds (cairo_matrix_t *old, cairo_matrix_t *current)
714 InvalidateSurfaceCache ();
715 bounds_with_children = bounds = IntersectBoundsWithClipPath (GetStretchExtents (), false).Transform (current);
718 void
719 Shape::ComputeBounds ()
721 bounds_with_children = bounds = IntersectBoundsWithClipPath (GetStretchExtents (), false).Transform (&absolute_xform);
722 //printf ("%f,%f,%f,%f\n", bounds.x, bounds.y, bounds.width, bounds.height);
725 Rect
726 Shape::ComputeShapeBounds (bool logical, cairo_matrix_t *matrix)
728 if (Is (Type::RECTANGLE) || Is (Type::ELLIPSE))
729 return Rect ();
731 if (!path || (path->cairo.num_data == 0))
732 BuildPath ();
734 if (IsEmpty ())
735 return Rect ();
737 double thickness = (logical || !IsStroked ()) ? 0.0 : GetStrokeThickness ();
739 cairo_t *cr = measuring_context_create ();
740 if (matrix)
741 cairo_set_matrix (cr, matrix);
743 cairo_set_line_width (cr, thickness);
745 if (thickness > 0.0) {
746 //FIXME: still not 100% precise since it could be different from the end cap
747 PenLineCap cap = GetStrokeStartLineCap ();
748 if (cap == PenLineCapFlat)
749 cap = GetStrokeEndLineCap ();
750 cairo_set_line_cap (cr, convert_line_cap (cap));
753 cairo_append_path (cr, &path->cairo);
755 cairo_identity_matrix (cr);
757 double x1, y1, x2, y2;
759 if (logical) {
760 cairo_path_extents (cr, &x1, &y1, &x2, &y2);
761 } else if (thickness > 0) {
762 cairo_stroke_extents (cr, &x1, &y1, &x2, &y2);
763 } else {
764 cairo_fill_extents (cr, &x1, &y1, &x2, &y2);
767 Rect bounds = Rect (MIN (x1, x2), MIN (y1, y2), fabs (x2 - x1), fabs (y2 - y1));
769 measuring_context_destroy (cr);
771 return bounds;
774 void
775 Shape::GetSizeForBrush (cairo_t *cr, double *width, double *height)
777 *height = GetStretchExtents ().height;
778 *width = GetStretchExtents ().width;
781 bool
782 Shape::InsideObject (cairo_t *cr, double x, double y)
784 bool ret = false;
786 TransformPoint (&x, &y);
787 if (!GetStretchExtents ().PointInside (x, y))
788 return false;
790 Geometry *clip = GetClip ();
791 if (clip) {
792 cairo_save (cr);
793 cairo_new_path (cr);
794 clip->Draw (cr);
795 ret = cairo_in_fill (cr, x, y);
796 if (!ret)
797 ret = cairo_in_stroke (cr, x, y);
798 cairo_restore (cr);
799 if (!ret)
800 return false;
803 cairo_save (cr);
804 DoDraw (cr, false);
806 // don't check in_stroke without a stroke or in_fill without a fill (even if it can be filled)
807 if (fill && CanFill ())
808 ret |= cairo_in_fill (cr, x, y);
809 if (stroke || true)
810 ret |= cairo_in_stroke (cr, x, y);
811 cairo_new_path (cr);
812 cairo_restore (cr);
814 return ret;
817 void
818 Shape::CacheInvalidateHint (void)
820 // Also kills the surface cache
821 InvalidatePathCache ();
824 void
825 Shape::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
827 if (args->GetProperty ()->GetOwnerType() != Type::SHAPE) {
828 if ((args->GetId () == FrameworkElement::HeightProperty)
829 || (args->GetId () == FrameworkElement::WidthProperty))
830 InvalidateStretch ();
832 // CacheInvalidateHint should handle visibility changes in
833 // DirtyRenderVisibility
835 FrameworkElement::OnPropertyChanged (args, error);
836 return;
839 if (args->GetId () == Shape::StretchProperty) {
840 InvalidateStretch ();
842 else if (args->GetId () == Shape::StrokeProperty) {
843 Brush *new_stroke = args->GetNewValue() ? args->GetNewValue()->AsBrush () : NULL;
845 if (!stroke || !new_stroke) {
846 // If the stroke changes from null to
847 // <something> or <something> to null, then
848 // some shapes need to reclaculate the offset
849 // (based on stroke thickness) to start
850 // painting.
851 InvalidateStrokeBounds ();
852 } else
853 InvalidateSurfaceCache ();
855 stroke = new_stroke;
856 } else if (args->GetId () == Shape::FillProperty) {
857 Brush *new_fill = args->GetNewValue() ? args->GetNewValue()->AsBrush () : NULL;
859 if (!fill || !new_fill) {
860 InvalidateFillBounds ();
861 } else
862 InvalidateSurfaceCache ();
864 fill = args->GetNewValue() ? args->GetNewValue()->AsBrush() : NULL;
865 } else if (args->GetId () == Shape::StrokeThicknessProperty) {
866 // do we invalidate the path here for Type::RECT and Type::ELLIPSE
867 // in case they degenerate? Or do we need it for line caps too
868 InvalidateStrokeBounds ();
869 } else if (args->GetId () == Shape::StrokeDashCapProperty
870 || args->GetId () == Shape::StrokeEndLineCapProperty
871 || args->GetId () == Shape::StrokeLineJoinProperty
872 || args->GetId () == Shape::StrokeMiterLimitProperty
873 || args->GetId () == Shape::StrokeStartLineCapProperty) {
874 InvalidateStrokeBounds ();
877 Invalidate ();
879 NotifyListenersOfPropertyChange (args, error);
882 void
883 Shape::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
885 if (prop && (prop->GetId () == Shape::FillProperty || prop->GetId () == Shape::StrokeProperty)) {
886 Invalidate ();
887 InvalidateSurfaceCache ();
889 else
890 FrameworkElement::OnSubPropertyChanged (prop, obj, subobj_args);
893 void
894 Shape::InvalidateStretch ()
896 extents = Rect (0, 0, -INFINITY, -INFINITY);
897 cairo_matrix_init_identity (&stretch_transform);
898 InvalidatePathCache ();
899 InvalidateMeasure ();
902 Rect
903 Shape::GetStretchExtents ()
905 if (extents.IsEmpty ())
906 extents = ComputeStretchBounds ();
908 return extents;
911 void
912 Shape::InvalidateStrokeBounds ()
914 InvalidateNaturalBounds ();
917 void
918 Shape::InvalidateFillBounds ()
920 InvalidateNaturalBounds ();
923 void
924 Shape::InvalidateNaturalBounds ()
926 natural_bounds = Rect (0, 0, -INFINITY, -INFINITY);
927 InvalidateStretch ();
930 Rect
931 Shape::GetNaturalBounds ()
933 if (natural_bounds.IsEmpty ())
934 natural_bounds = ComputeShapeBounds (false, NULL);
936 return natural_bounds;
939 void
940 Shape::InvalidatePathCache (bool free)
942 //SetShapeFlags (UIElement::SHAPE_NORMAL);
943 if (path) {
944 if (free) {
945 moon_path_destroy (path);
946 path = NULL;
947 } else {
948 moon_path_clear (path);
952 // we always pass true here because in some cases
953 // while the bounds may not have change the rendering
954 // still may have
955 UpdateBounds (true);
956 InvalidateMeasure ();
957 InvalidateArrange ();
958 InvalidateSurfaceCache ();
961 void
962 Shape::InvalidateSurfaceCache (void)
964 if (cached_surface) {
965 cairo_surface_destroy (cached_surface);
966 if (GetSurface ())
967 GetSurface ()->RemoveFromCacheSizeCounter (cached_size);
968 cached_surface = NULL;
969 cached_size = 0;
973 Value *
974 Shape::CreateDefaultStretch (DependencyObject *instance, DependencyProperty *property)
976 if (instance->Is (Type::RECTANGLE) || instance->Is (Type::ELLIPSE))
977 return new Value (StretchFill);
978 else
979 return new Value (StretchNone);
983 // Ellipse
986 Ellipse::Ellipse ()
988 SetObjectType (Type::ELLIPSE);
992 * Ellipses (like Rectangles) are special and they don't need to participate
993 * in the other stretch logic
995 Rect
996 Ellipse::ComputeStretchBounds ()
998 return ComputeShapeBounds (false);
1001 Rect
1002 Ellipse::ComputeShapeBounds (bool logical)
1004 Rect rect = Rect (0, 0, GetActualWidth (), GetActualHeight ());
1005 SetShapeFlags (UIElement::SHAPE_NORMAL);
1006 double t = GetStrokeThickness ();
1008 if (rect.width < 0.0 || rect.height < 0.0 || GetWidth () <= 0.0 || GetHeight () <= 0.0) {
1009 SetShapeFlags (UIElement::SHAPE_EMPTY);
1010 return Rect ();
1013 if (GetVisualParent () && GetVisualParent ()->Is (Type::CANVAS)) {
1014 if (isnan (GetWidth ()) != isnan (GetHeight ())) {
1015 SetShapeFlags (UIElement::SHAPE_EMPTY);
1016 return Rect ();
1020 switch (GetStretch ()) {
1021 case StretchNone:
1022 rect.width = rect.height = 0.0;
1023 break;
1024 case StretchUniform:
1025 rect.width = rect.height = (rect.width < rect.height) ? rect.width : rect.height;
1026 break;
1027 case StretchUniformToFill:
1028 rect.width = rect.height = (rect.width > rect.height) ? rect.width : rect.height;
1029 break;
1030 case StretchFill:
1031 /* nothing needed here. the assignment of w/h above
1032 is correct for this case. */
1033 break;
1036 if (rect.width <= t || rect.height <= t){
1037 rect.width = MAX (rect.width, t + t * 0.001);
1038 rect.height = MAX (rect.height, t + t * 0.001);
1039 SetShapeFlags (UIElement::SHAPE_DEGENERATE);
1040 } else
1041 SetShapeFlags (UIElement::SHAPE_NORMAL);
1043 return rect;
1046 // * Shape::StrokeStartLineCap
1047 // * Shape::StrokeEndLineCap
1048 // * Shape::StrokeLineJoin
1049 // * Shape::StrokeMiterLimit
1050 bool
1051 Ellipse::DrawShape (cairo_t *cr, bool do_op)
1053 bool drawn = Fill (cr, do_op);
1055 if (!stroke)
1056 return drawn;
1057 if (!SetupLine (cr))
1058 return drawn;
1059 SetupLineCaps (cr);
1061 if (!drawn)
1062 Draw (cr);
1063 Stroke (cr, do_op);
1064 return true;
1067 void
1068 Ellipse::BuildPath ()
1070 Stretch stretch = GetStretch ();
1071 double t = IsStroked () ? GetStrokeThickness () : 0.0;
1072 Rect rect = Rect (0.0, 0.0, GetActualWidth (), GetActualHeight ());
1074 if (rect.width < 0.0 || rect.height < 0.0 || GetWidth () <= 0.0 || GetHeight () <= 0.0) {
1075 SetShapeFlags (UIElement::SHAPE_EMPTY);
1076 return;
1080 SetShapeFlags (UIElement::SHAPE_NORMAL);
1082 switch (stretch) {
1083 case StretchNone:
1084 rect.width = rect.height = 0.0;
1085 break;
1086 case StretchUniform:
1087 rect.width = rect.height = (rect.width < rect.height) ? rect.width : rect.height;
1088 break;
1089 case StretchUniformToFill:
1090 rect.width = rect.height = (rect.width > rect.height) ? rect.width : rect.height;
1091 break;
1092 case StretchFill:
1093 /* nothing needed here. the assignment of w/h above
1094 is correct for this case. */
1095 break;
1098 if (rect.width <= t || rect.height <= t){
1099 rect.width = MAX (rect.width, t + t * 0.001);
1100 rect.height = MAX (rect.height, t + t * 0.001);
1101 SetShapeFlags (UIElement::SHAPE_DEGENERATE);
1102 } else
1103 SetShapeFlags (UIElement::SHAPE_NORMAL);
1105 rect = rect.GrowBy ( -t/2, -t/2);
1107 path = moon_path_renew (path, MOON_PATH_ELLIPSE_LENGTH);
1108 moon_ellipse (path, rect.x, rect.y, rect.width, rect.height);
1111 void
1112 Ellipse::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1115 DependencyProperty *prop = args->GetProperty ();
1117 if ((prop->GetId () == Shape::StrokeThicknessProperty) || (prop->GetId () == Shape::StretchProperty) ||
1118 (prop->GetId () == FrameworkElement::WidthProperty) || (prop->GetId () == FrameworkElement::HeightProperty)) {
1119 // FIXME why are we building the path here?
1120 BuildPath ();
1121 InvalidateSurfaceCache ();
1125 // Ellipse has no property of it's own
1126 Shape::OnPropertyChanged (args, error);
1130 // Rectangle
1133 Rectangle::Rectangle ()
1135 SetObjectType (Type::RECTANGLE);
1139 * Rectangles (like Ellipses) are special and they don't need to participate
1140 * in the other stretch logic
1142 Rect
1143 Rectangle::ComputeStretchBounds ()
1145 Rect shape_bounds = ComputeShapeBounds (false);
1146 return ComputeShapeBounds (false);
1149 Rect
1150 Rectangle::ComputeShapeBounds (bool logical)
1152 Rect rect = Rect (0, 0, GetActualWidth (), GetActualHeight ());
1153 SetShapeFlags (UIElement::SHAPE_NORMAL);
1155 if (rect.width < 0.0 || rect.height < 0.0 || GetWidth () <= 0.0 || GetHeight () <= 0.0) {
1156 SetShapeFlags (UIElement::SHAPE_EMPTY);
1157 return Rect ();
1160 if (GetVisualParent () && GetVisualParent ()->Is (Type::CANVAS)) {
1161 if (isnan (GetWidth ()) != isnan (GetHeight ())) {
1162 SetShapeFlags (UIElement::SHAPE_EMPTY);
1163 return Rect ();
1167 double t = IsStroked () ? GetStrokeThickness () : 0.0;
1168 switch (GetStretch ()) {
1169 case StretchNone:
1170 rect.width = rect.height = 0.0;
1171 break;
1172 case StretchUniform:
1173 rect.width = rect.height = MIN (rect.width, rect.height);
1174 break;
1175 case StretchUniformToFill:
1176 // this gets an rectangle larger than it's dimension, relative
1177 // scaling is ok but we need Shape::Draw to clip to it's original size
1178 rect.width = rect.height = MAX (rect.width, rect.height);
1179 break;
1180 case StretchFill:
1181 /* nothing needed here. the assignment of w/h above
1182 is correct for this case. */
1183 break;
1186 if (rect.width == 0)
1187 rect.x = t *.5;
1188 if (rect.height == 0)
1189 rect.y = t *.5;
1191 if (t >= rect.width || t >= rect.height) {
1192 SetShapeFlags (UIElement::SHAPE_DEGENERATE);
1193 rect = rect.GrowBy (t * .5005, t * .5005);
1194 } else {
1195 SetShapeFlags (UIElement::SHAPE_NORMAL);
1198 return rect;
1201 Rect
1202 Rectangle::GetCoverageBounds ()
1204 Brush *fill = GetFill ();
1206 if (fill != NULL && fill->IsOpaque()) {
1207 /* make it a little easier - only consider the rectangle inside the corner radii.
1208 we're also a little more conservative than we need to be, regarding stroke
1209 thickness. */
1210 double xr = (GetRadiusX () + GetStrokeThickness () / 2);
1211 double yr = (GetRadiusY () + GetStrokeThickness () / 2);
1213 return bounds.GrowBy (-xr, -yr).RoundIn ();
1216 return Rect ();
1220 // The Rectangle shape can be drawn while ignoring properties:
1221 // * Shape::StrokeStartLineCap
1222 // * Shape::StrokeEndLineCap
1223 // * Shape::StrokeLineJoin [for rounded-corner rectangles only]
1224 // * Shape::StrokeMiterLimit [for rounded-corner rectangles only]
1225 bool
1226 Rectangle::DrawShape (cairo_t *cr, bool do_op)
1228 bool drawn = Fill (cr, do_op);
1230 if (!stroke)
1231 return drawn;
1233 if (!SetupLine (cr))
1234 return drawn;
1236 SetupLineCaps (cr);
1238 if (!HasRadii ())
1239 SetupLineJoinMiter (cr);
1241 // Draw if the path wasn't drawn by the Fill call
1242 if (!drawn)
1243 Draw (cr);
1244 Stroke (cr, do_op);
1245 return true;
1249 * rendering notes:
1250 * - a Width="0" or a Height="0" can be rendered differently from not specifying Width or Height
1251 * - if a rectangle has only a Width or only a Height it is NEVER rendered
1253 void
1254 Rectangle::BuildPath ()
1256 Stretch stretch = GetStretch ();
1257 double t = IsStroked () ? GetStrokeThickness () : 0.0;
1259 // nothing is drawn (nor filled) if no StrokeThickness="0"
1260 // unless both Width and Height are specified or when no streching is required
1261 Rect rect = Rect (0, 0, GetActualWidth (), GetActualHeight ());
1263 double radius_x = GetRadiusX ();
1264 double radius_y = GetRadiusY ();
1266 switch (stretch) {
1267 case StretchNone:
1268 rect.width = rect.height = 0;
1269 break;
1270 case StretchUniform:
1271 rect.width = rect.height = MIN (rect.width, rect.height);
1272 break;
1273 case StretchUniformToFill:
1274 // this gets an rectangle larger than it's dimension, relative
1275 // scaling is ok but we need Shape::Draw to clip to it's original size
1276 rect.width = rect.height = MAX (rect.width, rect.height);
1277 break;
1278 case StretchFill:
1279 /* nothing needed here. the assignment of w/h above
1280 is correct for this case. */
1281 break;
1284 if (rect.width == 0)
1285 rect.x = t *.5;
1286 if (rect.height == 0)
1287 rect.y = t *.5;
1289 if (t >= rect.width || t >= rect.height) {
1290 rect = rect.GrowBy (t * 0.001, t * 0.001);
1291 SetShapeFlags (UIElement::SHAPE_DEGENERATE);
1292 } else {
1293 rect = rect.GrowBy (-t * 0.5, -t * 0.5);
1294 SetShapeFlags (UIElement::SHAPE_NORMAL);
1297 path = moon_path_renew (path, MOON_PATH_ROUNDED_RECTANGLE_LENGTH);
1298 moon_rounded_rectangle (path, rect.x, rect.y, rect.width, rect.height, radius_x, radius_y);
1301 void
1302 Rectangle::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1304 if (args->GetProperty ()->GetOwnerType() != Type::RECTANGLE) {
1305 Shape::OnPropertyChanged (args, error);
1306 return;
1309 if ((args->GetId () == Rectangle::RadiusXProperty) || (args->GetId () == Rectangle::RadiusYProperty)) {
1310 InvalidatePathCache ();
1313 Invalidate ();
1314 NotifyListenersOfPropertyChange (args, error);
1318 // Line
1320 // rules
1321 // * the internal path must be rebuilt when
1322 // - Line::X1Property, Line::Y1Property, Line::X2Property or Line::Y2Property is changed
1324 // * bounds calculation is based on
1325 // - Line::X1Property, Line::Y1Property, Line::X2Property and Line::Y2Property
1326 // - Shape::StrokeThickness
1329 #define LINECAP_SMALL_OFFSET 0.1
1331 //Draw the start cap. Shared with Polyline
1332 static void
1333 line_draw_cap (cairo_t *cr, Shape* shape, PenLineCap cap, double x1, double y1, double x2, double y2)
1335 double sx1, sy1;
1336 if (cap == PenLineCapFlat)
1337 return;
1339 cairo_save (cr);
1340 cairo_transform (cr, &(shape->stretch_transform));
1341 if (cap == PenLineCapRound) {
1342 cairo_move_to (cr, x1, y1);
1343 cairo_line_to (cr, x1, y1);
1344 cairo_restore (cr);
1345 cairo_set_line_cap (cr, convert_line_cap (cap));
1346 shape->Stroke (cr, true);
1347 return;
1350 if (x1 == x2) {
1351 // vertical line
1352 sx1 = x1;
1353 if (y1 > y2)
1354 sy1 = y1 + LINECAP_SMALL_OFFSET;
1355 else
1356 sy1 = y1 - LINECAP_SMALL_OFFSET;
1357 } else if (y1 == y2) {
1358 // horizontal line
1359 sy1 = y1;
1360 if (x1 > x2)
1361 sx1 = x1 + LINECAP_SMALL_OFFSET;
1362 else
1363 sx1 = x1 - LINECAP_SMALL_OFFSET;
1364 } else {
1365 double m = (y1 - y2) / (x1 - x2);
1366 if (x1 > x2) {
1367 sx1 = x1 + LINECAP_SMALL_OFFSET;
1368 } else {
1369 sx1 = x1 - LINECAP_SMALL_OFFSET;
1371 sy1 = m * sx1 + y1 - (m * x1);
1373 cairo_move_to (cr, x1, y1);
1374 cairo_line_to (cr, sx1, sy1);
1375 cairo_restore (cr);
1376 cairo_set_line_cap (cr, convert_line_cap (cap));
1377 shape->Stroke (cr, true);
1380 // The Line shape can be drawn while ignoring properties:
1381 // * Shape::StrokeLineJoin
1382 // * Shape::StrokeMiterLimit
1383 // * Shape::Fill
1384 bool
1385 Line::DrawShape (cairo_t *cr, bool do_op)
1387 // no need to clear path since none has been drawn to cairo
1388 if (!stroke)
1389 return false;
1391 if (!SetupLine (cr))
1392 return false;
1394 // here we hack around #345888 where Cairo doesn't support different start and end linecaps
1395 PenLineCap start = GetStrokeStartLineCap ();
1396 PenLineCap end = GetStrokeEndLineCap ();
1397 PenLineCap dash = GetStrokeDashCap ();
1398 bool dashed = false;
1399 DoubleCollection *dashes = GetStrokeDashArray ();
1401 if (dashes && (dashes->GetCount() > 0))
1402 dashed = true;
1405 //if (do_op && !(start == end && start == dash)) {
1406 if (do_op && (start != end || (dashed && !(start == end && start == dash)))) {
1407 double x1 = GetX1 ();
1408 double y1 = GetY1 ();
1409 double x2 = GetX2 ();
1410 double y2 = GetY2 ();
1412 // draw start and end line caps
1413 if (start != PenLineCapFlat)
1414 line_draw_cap (cr, this, start, x1, y1, x2, y2);
1416 if (end != PenLineCapFlat) {
1417 //don't draw the end cap if it's in an "off" segment
1418 double thickness = GetStrokeThickness ();
1419 double offset = GetStrokeDashOffset ();
1421 SetupDashes (cr, thickness, sqrt ((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)) + offset * thickness);
1422 line_draw_cap (cr, this, end, x2, y2, x1, y1);
1423 SetupLine (cr);
1426 cairo_set_line_cap (cr, convert_line_cap (dash));
1427 } else
1428 cairo_set_line_cap (cr, convert_line_cap (start));
1430 Draw (cr);
1431 Stroke (cr, do_op);
1432 return true;
1435 void
1436 calc_line_bounds (double x1, double x2, double y1, double y2, double thickness, PenLineCap start_cap, PenLineCap end_cap, Rect* bounds)
1438 if (x1 == x2) {
1439 bounds->x = x1 - thickness / 2.0;
1440 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);
1441 bounds->width = thickness;
1442 bounds->height = fabs (y2 - y1) + (start_cap != PenLineCapFlat ? thickness / 2.0 : 0.0) + (end_cap != PenLineCapFlat ? thickness / 2.0 : 0.0);
1443 } else if (y1 == y2) {
1444 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);
1445 bounds->y = y1 - thickness / 2.0;
1446 bounds->width = fabs (x2 - x1) + (start_cap != PenLineCapFlat ? thickness / 2.0 : 0.0) + (end_cap != PenLineCapFlat ? thickness / 2.0 : 0.0);
1447 bounds->height = thickness;
1448 } else {
1449 double m = fabs ((y1 - y2) / (x1 - x2));
1450 #if EXACT_BOUNDS
1451 double dx = sin (atan (m)) * thickness;
1452 double dy = cos (atan (m)) * thickness;
1453 #else
1454 double dx = (m > 1.0) ? thickness : thickness * m;
1455 double dy = (m < 1.0) ? thickness : thickness / m;
1456 #endif
1457 if (x1 < x2)
1458 switch (start_cap) {
1459 case PenLineCapSquare:
1460 bounds->x = MIN (x1, x2) - (dx + dy) / 2.0;
1461 break;
1462 case PenLineCapTriangle: //FIXME, reverting to Round for now
1463 case PenLineCapRound:
1464 bounds->x = MIN (x1, x2) - thickness / 2.0;
1465 break;
1466 default: //PenLineCapFlat
1467 bounds->x = MIN (x1, x2) - dx / 2.0;
1469 else
1470 switch (end_cap) {
1471 case PenLineCapSquare:
1472 bounds->x = MIN (x1, x2) - (dx + dy) / 2.0;
1473 break;
1474 case PenLineCapTriangle: //FIXME, reverting to Round for now
1475 case PenLineCapRound:
1476 bounds->x = MIN (x1, x2) - thickness / 2.0;
1477 break;
1478 default: //PenLineCapFlat
1479 bounds->x = MIN (x1, x2) - dx / 2.0;
1481 if (y1 < y2)
1482 switch (start_cap) {
1483 case PenLineCapSquare:
1484 bounds->y = MIN (y1, y2) - (dx + dy) / 2.0;
1485 break;
1486 case PenLineCapTriangle: //FIXME, reverting to Round for now
1487 case PenLineCapRound:
1488 bounds->y = MIN (y1, y2) - thickness / 2.0;
1489 break;
1490 default: //PenLineCapFlat
1491 bounds->y = MIN (y1, y2) - dy / 2.0;
1493 else
1494 switch (end_cap) {
1495 case PenLineCapSquare:
1496 bounds->y = MIN (y1, y2) - (dx + dy) / 2.0;
1497 break;
1498 case PenLineCapTriangle: //FIXME, reverting to Round for now
1499 case PenLineCapRound:
1500 bounds->y = MIN (y1, y2) - thickness / 2.0;
1501 break;
1502 default: //PenLineCapFlat
1503 bounds->y = MIN (y1, y2) - dy / 2.0;
1505 bounds->width = fabs (x2 - x1);
1506 bounds->height = fabs (y2 - y1);
1507 switch (start_cap) {
1508 case PenLineCapSquare:
1509 bounds->width += (dx + dy) / 2.0;
1510 bounds->height += (dx + dy) / 2.0;
1511 break;
1512 case PenLineCapTriangle: //FIXME, reverting to Round for now
1513 case PenLineCapRound:
1514 bounds->width += thickness / 2.0;
1515 bounds->height += thickness / 2.0;
1516 break;
1517 default: //PenLineCapFlat
1518 bounds->width += dx/2.0;
1519 bounds->height += dy/2.0;
1521 switch (end_cap) {
1522 case PenLineCapSquare:
1523 bounds->width += (dx + dy) / 2.0;
1524 bounds->height += (dx + dy) / 2.0;
1525 break;
1526 case PenLineCapTriangle: //FIXME, reverting to Round for now
1527 case PenLineCapRound:
1528 bounds->width += thickness / 2.0;
1529 bounds->height += thickness / 2.0;
1530 break;
1531 default: //PenLineCapFlat
1532 bounds->width += dx/2.0;
1533 bounds->height += dy/2.0;
1538 void
1539 Line::BuildPath ()
1541 SetShapeFlags (UIElement::SHAPE_NORMAL);
1543 path = moon_path_renew (path, MOON_PATH_MOVE_TO_LENGTH + MOON_PATH_LINE_TO_LENGTH);
1545 double x1 = GetX1 ();
1546 double y1 = GetY1 ();
1547 double x2 = GetX2 ();
1548 double y2 = GetY2 ();
1550 moon_move_to (path, x1, y1);
1551 moon_line_to (path, x2, y2);
1554 Rect
1555 Line::ComputeShapeBounds (bool logical)
1557 Rect shape_bounds = Rect ();
1558 double thickness;
1560 if (!logical)
1561 thickness = GetStrokeThickness ();
1562 else
1563 thickness = 0.0;
1565 PenLineCap start_cap, end_cap;
1566 if (!logical) {
1567 start_cap = GetStrokeStartLineCap ();
1568 end_cap = GetStrokeEndLineCap ();
1569 } else
1570 start_cap = end_cap = PenLineCapFlat;
1572 if (thickness <= 0.0 && !logical)
1573 return shape_bounds;
1575 double x1 = GetX1 ();
1576 double y1 = GetY1 ();
1577 double x2 = GetX2 ();
1578 double y2 = GetY2 ();
1580 calc_line_bounds (x1, x2, y1, y2, thickness, start_cap, end_cap, &shape_bounds);
1582 return shape_bounds;
1585 void
1586 Line::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1588 if (args->GetProperty ()->GetOwnerType() != Type::LINE) {
1589 Shape::OnPropertyChanged (args, error);
1590 return;
1593 if (args->GetId () == Line::X1Property ||
1594 args->GetId () == Line::X2Property ||
1595 args->GetId () == Line::Y1Property ||
1596 args->GetId () == Line::Y2Property) {
1597 InvalidateNaturalBounds ();
1600 NotifyListenersOfPropertyChange (args, error);
1604 // Polygon
1606 // rules
1607 // * the internal path must be rebuilt when
1608 // - Polygon::PointsProperty is changed
1609 // - Shape::StretchProperty is changed
1611 // * bounds calculation is based on
1612 // - Polygon::PointsProperty
1613 // - Shape::StretchProperty
1614 // - Shape::StrokeThickness
1617 Polygon::Polygon ()
1619 SetObjectType (Type::POLYGON);
1622 // The Polygon shape can be drawn while ignoring properties:
1623 // * Shape::StrokeStartLineCap
1624 // * Shape::StrokeEndLineCap
1625 bool
1626 Polygon::DrawShape (cairo_t *cr, bool do_op)
1628 bool drawn = Fill (cr, do_op);
1630 if (!stroke)
1631 return drawn;
1633 if (!SetupLine (cr))
1634 return drawn;
1635 SetupLineCaps (cr);
1636 SetupLineJoinMiter (cr);
1638 Draw (cr);
1639 Stroke (cr, do_op);
1640 return true;
1643 // special case when a polygon has a single line in it (it's drawn longer than it should)
1644 // e.g. <Polygon Fill="#000000" Stroke="#FF00FF" StrokeThickness="8" Points="260,80 300,40" />
1645 static void
1646 polygon_extend_line (double *x1, double *x2, double *y1, double *y2, double thickness)
1648 // not sure why it's a 5 ? afaik it's not related to the line length or any other property
1649 double t5 = thickness * 5.0;
1650 double dx = *x1 - *x2;
1651 double dy = *y1 - *y2;
1653 if (dy == 0.0) {
1654 t5 -= thickness / 2.0;
1655 if (dx > 0.0) {
1656 *x1 += t5;
1657 *x2 -= t5;
1658 } else {
1659 *x1 -= t5;
1660 *x2 += t5;
1662 } else if (dx == 0.0) {
1663 t5 -= thickness / 2.0;
1664 if (dy > 0.0) {
1665 *y1 += t5;
1666 *y2 -= t5;
1667 } else {
1668 *y1 -= t5;
1669 *y2 += t5;
1671 } else {
1672 double angle = atan (dy / dx);
1673 double ax = fabs (sin (angle) * t5);
1674 if (dx > 0.0) {
1675 *x1 += ax;
1676 *x2 -= ax;
1677 } else {
1678 *x1 -= ax;
1679 *x2 += ax;
1681 double ay = fabs (sin ((M_PI / 2.0) - angle)) * t5;
1682 if (dy > 0.0) {
1683 *y1 += ay;
1684 *y2 -= ay;
1685 } else {
1686 *y1 -= ay;
1687 *y2 += ay;
1692 void
1693 Polygon::BuildPath ()
1695 PointCollection *col = GetPoints ();
1697 // the first point is a move to, resulting in an empty shape
1698 if (!col || (col->GetCount() < 2)) {
1699 SetShapeFlags (UIElement::SHAPE_EMPTY);
1700 return;
1703 int i, count = col->GetCount();
1704 GPtrArray* points = col->Array();
1706 SetShapeFlags (UIElement::SHAPE_NORMAL);
1708 // 2 data per [move|line]_to + 1 for close path
1709 path = moon_path_renew (path, count * 2 + 1);
1711 // special case, both the starting and ending points are 5 * thickness than the actual points
1712 if (count == 2) {
1713 double thickness = GetStrokeThickness ();
1714 double x1 = ((Value*)g_ptr_array_index(points, 0))->AsPoint()->x;
1715 double y1 = ((Value*)g_ptr_array_index(points, 0))->AsPoint()->y;
1716 double x2 = ((Value*)g_ptr_array_index(points, 1))->AsPoint()->x;
1717 double y2 = ((Value*)g_ptr_array_index(points, 1))->AsPoint()->y;
1719 polygon_extend_line (&x1, &x2, &y1, &y2, thickness);
1721 moon_move_to (path, x1, y1);
1722 moon_line_to (path, x2, y2);
1723 } else {
1724 moon_move_to (path,
1725 ((Value*)g_ptr_array_index(points, 0))->AsPoint()->x,
1726 ((Value*)g_ptr_array_index(points, 0))->AsPoint()->y);
1727 for (i = 1; i < count; i++)
1728 moon_line_to (path,
1729 ((Value*)g_ptr_array_index(points, i))->AsPoint()->x,
1730 ((Value*)g_ptr_array_index(points, i))->AsPoint()->y);
1732 moon_close_path (path);
1735 void
1736 Polygon::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1738 if (args->GetProperty ()->GetOwnerType() != Type::POLYGON) {
1739 Shape::OnPropertyChanged (args, error);
1740 return;
1743 if (args->GetId () == Polygon::PointsProperty) {
1744 InvalidateNaturalBounds ();
1747 Invalidate ();
1748 NotifyListenersOfPropertyChange (args, error);
1751 void
1752 Polygon::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
1754 Shape::OnCollectionChanged (col, args);
1756 InvalidateNaturalBounds ();
1759 void
1760 Polygon::OnCollectionItemChanged (Collection *col, DependencyObject *obj, PropertyChangedEventArgs *args)
1762 Shape::OnCollectionItemChanged (col, obj, args);
1764 InvalidateNaturalBounds ();
1768 // Polyline
1770 // rules
1771 // * the internal path must be rebuilt when
1772 // - Polyline::PointsProperty is changed
1773 // - Shape::StretchProperty is changed
1775 // * bounds calculation is based on
1776 // - Polyline::PointsProperty
1777 // - Shape::StretchProperty
1778 // - Shape::StrokeThickness
1780 Polyline::Polyline ()
1782 SetObjectType (Type::POLYLINE);
1785 // The Polyline shape can be drawn while ignoring NO properties
1786 bool
1787 Polyline::DrawShape (cairo_t *cr, bool do_op)
1789 bool drawn = Fill (cr, do_op);
1791 if (!stroke)
1792 return drawn;
1794 if (!SetupLine (cr))
1795 return drawn;
1796 SetupLineJoinMiter (cr);
1798 // here we hack around #345888 where Cairo doesn't support different start and end linecaps
1799 PenLineCap start = GetStrokeStartLineCap ();
1800 PenLineCap end = GetStrokeEndLineCap ();
1801 PenLineCap dash = GetStrokeDashCap ();
1803 if (do_op && ! (start == end && start == dash)){
1804 // the previous fill, if needed, has preserved the path
1805 if (drawn)
1806 cairo_new_path (cr);
1808 // since Draw may not have been called (e.g. no Fill) we must ensure the path was built
1809 if (!drawn || !path || (path->cairo.num_data == 0))
1810 BuildPath ();
1812 cairo_path_data_t *data = path->cairo.data;
1813 int length = path->cairo.num_data;
1814 // single point polylines are not rendered
1815 if (length >= MOON_PATH_MOVE_TO_LENGTH + MOON_PATH_LINE_TO_LENGTH) {
1816 // draw line #1 with start cap
1817 if (start != PenLineCapFlat) {
1818 line_draw_cap (cr, this, start, data[1].point.x, data[1].point.y, data[3].point.x, data[3].point.y);
1820 // draw last line with end cap
1821 if (end != PenLineCapFlat) {
1822 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);
1826 cairo_set_line_cap (cr, convert_line_cap (dash));
1828 Draw (cr);
1829 Stroke (cr, do_op);
1830 return true;
1833 void
1834 Polyline::BuildPath ()
1836 PointCollection *col = GetPoints ();
1838 // the first point is a move to, resulting in an empty shape
1839 if (!col || (col->GetCount() < 2)) {
1840 SetShapeFlags (UIElement::SHAPE_EMPTY);
1841 return;
1844 int i, count = col->GetCount();
1845 GPtrArray *points = col->Array();
1847 SetShapeFlags (UIElement::SHAPE_NORMAL);
1849 // 2 data per [move|line]_to
1850 path = moon_path_renew (path, count * 2);
1852 moon_move_to (path,
1853 ((Value*)g_ptr_array_index(points, 0))->AsPoint()->x,
1854 ((Value*)g_ptr_array_index(points, 0))->AsPoint()->y);
1856 for (i = 1; i < count; i++)
1857 moon_line_to (path,
1858 ((Value*)g_ptr_array_index(points, i))->AsPoint()->x,
1859 ((Value*)g_ptr_array_index(points, i))->AsPoint()->y);
1862 void
1863 Polyline::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1865 if (args->GetProperty ()->GetOwnerType() != Type::POLYLINE) {
1866 Shape::OnPropertyChanged (args, error);
1867 return;
1870 if (args->GetId () == Polyline::PointsProperty) {
1871 InvalidateNaturalBounds ();
1874 Invalidate ();
1875 NotifyListenersOfPropertyChange (args, error);
1878 void
1879 Polyline::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
1881 if (col != GetPoints ()) {
1882 Shape::OnCollectionChanged (col, args);
1883 return;
1886 InvalidateNaturalBounds ();
1889 void
1890 Polyline::OnCollectionItemChanged (Collection *col, DependencyObject *obj, PropertyChangedEventArgs *args)
1892 Shape::OnCollectionItemChanged (col, obj, args);
1894 InvalidateNaturalBounds ();
1898 // Path
1900 bool
1901 Path::SetupLine (cairo_t* cr)
1903 // we cannot use the thickness==0 optimization (like Shape::SetupLine provides)
1904 // since we'll be using cairo to compute the path's bounds later
1905 // see bug #352188 for an example of what this breaks
1906 double thickness = IsDegenerate () ? 1.0 : GetStrokeThickness ();
1907 cairo_set_line_width (cr, thickness);
1908 return SetupDashes (cr, thickness);
1911 // The Polygon shape can be drawn while ignoring properties:
1912 // * none
1913 // FIXME: actually it depends on the geometry, another level of optimization awaits ;-)
1914 // e.g. close geometries don't need to setup line caps,
1916 // line join/miter don't applies to curve, like EllipseGeometry
1917 bool
1918 Path::DrawShape (cairo_t *cr, bool do_op)
1920 bool drawn = Shape::Fill (cr, do_op);
1922 if (stroke) {
1923 if (!SetupLine (cr))
1924 return drawn; // return if we have a path in the cairo_t
1925 SetupLineCaps (cr);
1926 SetupLineJoinMiter (cr);
1928 if (!drawn)
1929 Draw (cr);
1930 Stroke (cr, do_op);
1933 return true;
1936 FillRule
1937 Path::GetFillRule ()
1939 Geometry *geometry;
1941 if (!(geometry = GetData ()))
1942 return Shape::GetFillRule ();
1944 return geometry->GetFillRule ();
1947 Rect
1948 Path::ComputeShapeBounds (bool logical, cairo_matrix_t *matrix)
1950 Rect shape_bounds = Rect ();
1951 Geometry *geometry;
1953 if (!(geometry = GetData ())) {
1954 SetShapeFlags (UIElement::SHAPE_EMPTY);
1955 return shape_bounds;
1958 if (logical)
1959 return geometry->GetBounds ();
1961 double thickness = !IsStroked () ? 0.0 : GetStrokeThickness ();
1963 cairo_t *cr = measuring_context_create ();
1964 cairo_set_line_width (cr, thickness);
1966 if (thickness > 0.0) {
1967 //FIXME: still not 100% precise since it could be different from the end cap
1968 PenLineCap cap = GetStrokeStartLineCap ();
1969 if (cap == PenLineCapFlat)
1970 cap = GetStrokeEndLineCap ();
1971 cairo_set_line_cap (cr, convert_line_cap (cap));
1974 if (matrix)
1975 cairo_set_matrix (cr, matrix);
1976 geometry->Draw (cr);
1978 cairo_identity_matrix (cr);
1980 double x1, y1, x2, y2;
1982 if (thickness > 0) {
1983 cairo_stroke_extents (cr, &x1, &y1, &x2, &y2);
1984 } else {
1985 cairo_fill_extents (cr, &x1, &y1, &x2, &y2);
1988 shape_bounds = Rect (MIN (x1, x2), MIN (y1, y2), fabs (x2 - x1), fabs (y2 - y1));
1990 measuring_context_destroy (cr);
1992 return shape_bounds;
1995 void
1996 Path::Draw (cairo_t *cr)
1998 cairo_new_path (cr);
2000 Geometry *geometry;
2002 if (!(geometry = GetData ()))
2003 return;
2005 cairo_save (cr);
2006 cairo_transform (cr, &stretch_transform);
2007 geometry->Draw (cr);
2008 cairo_restore (cr);
2011 void
2012 Path::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
2014 if (args->GetProperty ()->GetOwnerType() != Type::PATH) {
2015 Shape::OnPropertyChanged (args, error);
2016 return;
2019 InvalidateNaturalBounds ();
2021 NotifyListenersOfPropertyChange (args, error);
2024 void
2025 Path::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
2027 if (prop && prop->GetId () == Path::DataProperty) {
2028 InvalidateNaturalBounds ();
2030 else
2031 Shape::OnSubPropertyChanged (prop, obj, subobj_args);
2035 * Right now implementing Path::ComputeLargestRectangle doesn't seems like a good idea. That would require
2036 * - checking the path for curves (and either flatten it or return an empty Rect)
2037 * - checking for polygon simplicity (finding intersections)
2038 * - checking for a convex polygon (if concave we can turn it into several convex or return an empty Rect)
2039 * - find the largest rectangle inside the (or each) convex polygon(s)
2040 * http://cgm.cs.mcgill.ca/~athens/cs507/Projects/2003/DanielSud/complete.html