Author: spouliot
Date: 2008-01-22 15:35:40 -0500 (Tue, 22 Jan 2008)
New Revision: 93570
Modified:
trunk/moon/src/ChangeLog
trunk/moon/src/shape.cpp
trunk/moon/src/shape.h
Log:
2008-01-22 Sebastien Pouliot <[EMAIL PROTECTED]>
* shape.cpp|h: First part of DoDraw refactoring. Pass all
regression tests from harness (moon versus moon) but it's possible
that some degenerated cases (without test cases) regress.
Modified: trunk/moon/src/ChangeLog
===================================================================
--- trunk/moon/src/ChangeLog 2008-01-22 20:33:53 UTC (rev 93569)
+++ trunk/moon/src/ChangeLog 2008-01-22 20:35:40 UTC (rev 93570)
@@ -1,3 +1,9 @@
+2008-01-22 Sebastien Pouliot <[EMAIL PROTECTED]>
+
+ * shape.cpp|h: First part of DoDraw refactoring. Pass all
+ regression tests from harness (moon versus moon) but it's possible
+ that some degenerated cases (without test cases) regress.
+
2008-01-22 Larry Ewing <[EMAIL PROTECTED]>
* clock.cpp: initalize the smoothing to max framerate, the first
Modified: trunk/moon/src/shape.cpp
===================================================================
--- trunk/moon/src/shape.cpp 2008-01-22 20:33:53 UTC (rev 93569)
+++ trunk/moon/src/shape.cpp 2008-01-22 20:35:40 UTC (rev 93570)
@@ -1,10 +1,11 @@
/*
* shape.cpp: This match the classes inside System.Windows.Shapes
*
- * Author:
+ * Authors:
* Miguel de Icaza ([EMAIL PROTECTED])
+ * Sebastien Pouliot <[EMAIL PROTECTED]>
*
- * Copyright 2007 Novell, Inc. (http://www.novell.com)
+ * Copyright 2007-2008 Novell, Inc. (http://www.novell.com)
*
* See the LICENSE file included with the distribution for details.
*
@@ -122,23 +123,6 @@
}
bool
-Shape::NeedsClipping ()
-{
- if (!IsDegenerate () && (shape_get_stretch (this) ==
StretchUniformToFill))
- return true;
-
- // some shapes, like Line, Polyline, Polygon and Path, are clipped if
both Height and Width properties are present
- if (!ClipOnHeightAndWidth ())
- return false;
-
- Value *vh = GetValueNoDefault (FrameworkElement::HeightProperty);
- if (!vh)
- return false;
-
- return (GetValueNoDefault (FrameworkElement::WidthProperty) != NULL);
-}
-
-bool
Shape::MixedHeightWidth (Value **height, Value **width)
{
Value *vw = GetValueNoDefault (FrameworkElement::WidthProperty);
@@ -167,17 +151,128 @@
// moon_path_display (path);
}
+// break up operations so we can exclude optional stuff, like:
+// * StrokeStartLineCap & StrokeEndLineCap
+// * StrokeLineJoin & StrokeMiterLimit
+// * Fill
+
+bool
+Shape::SetupLine (cairo_t* cr)
+{
+ double thickness = shape_get_stroke_thickness (this);
+ // check if something will be drawn or return
+ // note: override this method if cairo is used to compute bounds
+ if (thickness == 0)
+ return false;
+
+ if (IsDegenerate ())
+ cairo_set_line_width (cr, 1.0);
+ else
+ cairo_set_line_width (cr, thickness);
+
+ return SetupDashes (cr, thickness);
+}
+
+bool
+Shape::SetupDashes (cairo_t *cr, double thickness)
+{
+ int count = 0;
+ double *dashes = shape_get_stroke_dash_array (this, &count);
+ if (dashes && (count > 0)) {
+ // FIXME: cairo doesn't support line cap for dashes - see
#345894
+
+ double offset = shape_get_stroke_dash_offset (this) * thickness;
+
+ // NOTE: special case - if we continue cairo will stops drawing!
+ if ((count == 1) && (*dashes == 0.0))
+ return false;
+
+ // multiply dashes length with thickness
+ double *dmul = new double [count];
+ for (int i=0; i < count; i++) {
+ dmul [i] = dashes [i] * thickness;
+ }
+ cairo_set_dash (cr, dmul, count, offset);
+ delete [] dmul;
+ } else {
+ cairo_set_dash (cr, NULL, 0, 0.0);
+ }
+ return true;
+}
+
+void
+Shape::SetupLineCaps (cairo_t *cr)
+{
+ // FIXME: cairo doesn't have separate line cap for the start and end -
see #345888
+ PenLineCap cap = shape_get_stroke_end_line_cap (this);
+ if (cap == PenLineCapFlat) {
+ cap = shape_get_stroke_start_line_cap (this);
+ }
+ cairo_set_line_cap (cr, convert_line_cap (cap));
+}
+
+void
+Shape::SetupLineJoinMiter (cairo_t *cr)
+{
+ cairo_set_line_join (cr, convert_line_join (shape_get_stroke_line_join
(this)));
+ cairo_set_miter_limit (cr, shape_get_stroke_miter_limit (this));
+}
+
+// returns true if the path is set on the cairo, false if not
+bool
+Shape::Fill (cairo_t *cr, bool do_op)
+{
+ if (!fill)
+ return false;
+
+ Draw (cr);
+ if (do_op) {
+ fill->SetupBrush (cr, this);
+ cairo_set_fill_rule (cr, convert_fill_rule (GetFillRule ()));
+ cairo_fill_preserve (cr);
+ }
+ return true;
+}
+
+void
+Shape::Stroke (cairo_t *cr, bool do_op)
+{
+ if (do_op) {
+ stroke->SetupBrush (cr, this);
+ if (IsDegenerate ())
+ cairo_fill_preserve (cr);
+ cairo_stroke (cr);
+ }
+}
+
+void
+Shape::Clip (cairo_t *cr)
+{
+ // some shapes, like Line, Polyline, Polygon and Path, are clipped if
both Height and Width properties are present
+ if (ClipOnHeightAndWidth () || (!IsDegenerate () && (shape_get_stretch
(this) == StretchUniformToFill))) {
+ Value *vh = GetValueNoDefault
(FrameworkElement::HeightProperty);
+ if (!vh)
+ return;
+ Value *vw = GetValueNoDefault (FrameworkElement::WidthProperty);
+ if (!vw)
+ return;
+
+ cairo_rectangle (cr, 0, 0, vw->AsDouble (), vh->AsDouble ());
+ cairo_clip (cr);
+ cairo_new_path (cr);
+ }
+}
+
//
// This routine is useful for Shape derivatives: it can be used
// to either get the bounding box from cairo, or to paint it
//
-void
+void
Shape::DoDraw (cairo_t *cr, bool do_op)
{
- if (IsEmpty ()) {
- cairo_new_path (cr);
- return;
- }
+ // quick out if, when building the path, we detected a empty shape
+ if (IsEmpty ())
+ goto cleanpath;
cairo_set_matrix (cr, &absolute_xform);
@@ -189,107 +284,21 @@
// absolute_xform.x0,
// absolute_xform.y0);
- bool drawn = false;
+ Clip (cr);
- // we need to use clipping to implement StretchUniformToFill and paths
that includes Height and Width
- if (NeedsClipping ()) {
- double w = framework_element_get_width (this);
- if (w > 0.0) {
- double h = framework_element_get_height (this);
- if (h > 0.0) {
- cairo_rectangle (cr, 0, 0, w, h);
- cairo_clip (cr);
- cairo_new_path (cr);
- }
- }
+ // if building the path detected a degenerate case
+ if (IsDegenerate ()) {
+ // FIXME: needs a DrawDegenerateShape to get more control over
output
+ if (DrawShape (cr, do_op))
+ return;
+ } else {
+ if (DrawShape (cr, do_op))
+ return;
}
- // getting bounds, using cairo_stroke_extents, doesn't requires us to
fill (consider_fill)
- // unless there is no stroke brush assigned, which requires us to fill
and use cairo_fill_extents
- // also not every shapes can be filled
- if (!IsDegenerate () && IsFilled ()) {
- if (fill) {
- Draw (cr);
- drawn = true;
- if (do_op) {
- fill->SetupBrush (cr, this);
- cairo_set_fill_rule (cr, convert_fill_rule
(GetFillRule ()));
- cairo_fill_preserve (cr);
- }
- }
- }
-
- if (stroke) {
- double thickness = shape_get_stroke_thickness (this);
-
-#if 0
- // this optimization is broken wrt ComputeBoundsSlow since
- // - thickness isn't checked there (fixed) and
- // - the new_path clears the data for a cairo_fill_extents call
anyway
- //
- // See bug #352188 for an example of what this breaks
- if (thickness == 0) {
- if (drawn)
- cairo_new_path (cr);
- return;
- }
-#endif
-
- if (IsDegenerate ())
- cairo_set_line_width (cr, 1.0);
- else
- cairo_set_line_width (cr, thickness);
-
- int count = 0;
- double *dashes = shape_get_stroke_dash_array (this, &count);
- if (dashes && (count > 0)) {
- /* FIXME: cairo doesn't support line cap for dashes */
- double offset = shape_get_stroke_dash_offset (this) *
thickness;
- // special case or cairo stops drawing
- if ((count == 1) && (*dashes == 0.0)) {
- if (drawn)
- cairo_new_path (cr);
- return;
- }
-
- // multiply dashes length with thickness
- double *dmul = new double [count];
- for (int i=0; i < count; i++) {
- dmul [i] = dashes [i] * thickness;
- }
- cairo_set_dash (cr, dmul, count, offset);
- delete [] dmul;
- } else {
- cairo_set_dash (cr, NULL, 0, 0.0);
- }
-
- if (NeedsLineJoin ()) {
- cairo_set_line_join (cr, convert_line_join
(shape_get_stroke_line_join (this)));
- cairo_set_miter_limit (cr, shape_get_stroke_miter_limit
(this));
- }
-
- if (NeedsLineCaps ()) {
- /* FIXME: cairo doesn't have separate line cap for the
start and end */
- PenLineCap cap = shape_get_stroke_end_line_cap (this);
- if (cap == PenLineCapFlat) {
- cap = shape_get_stroke_start_line_cap (this);
- }
- cairo_set_line_cap (cr, convert_line_cap (cap));
- }
-
- if (!drawn)
- Draw (cr);
- if (do_op) {
- stroke->SetupBrush (cr, this);
- if (IsDegenerate ())
- cairo_fill_preserve (cr);
- cairo_stroke (cr);
- }
- }
- else {
- if (drawn && do_op)
- cairo_new_path (cr);
- }
+cleanpath:
+ if (do_op)
+ cairo_new_path (cr);
}
void
@@ -643,6 +652,27 @@
SetValue (Shape::StretchProperty, Value (StretchFill));
}
+// The Ellipse shape can be drawn while ignoring properties:
+// * Shape::StrokeStartLineCap
+// * Shape::StrokeEndLineCap
+// * Shape::StrokeLineJoin
+// * Shape::StrokeMiterLimit
+bool
+Ellipse::DrawShape (cairo_t *cr, bool do_op)
+{
+ bool drawn = Fill (cr, do_op);
+
+ if (!stroke)
+ return drawn;
+ if (!SetupLine (cr))
+ return drawn;
+
+ if (!drawn)
+ Draw (cr);
+ Stroke (cr, do_op);
+ return true;
+}
+
void
Ellipse::BuildPath ()
{
@@ -757,6 +787,32 @@
SetValue (Shape::StretchProperty, Value (StretchFill));
}
+// The Rectangle shape can be drawn while ignoring properties:
+// * Shape::StrokeStartLineCap
+// * Shape::StrokeEndLineCap
+// * Shape::StrokeLineJoin [for rounded-corner rectangles only]
+// * Shape::StrokeMiterLimit [for rounded-corner rectangles only]
+bool
+Rectangle::DrawShape (cairo_t *cr, bool do_op)
+{
+ bool drawn = Fill (cr, do_op);
+
+ if (!stroke)
+ return drawn;
+
+ if (!SetupLine (cr))
+ return drawn;
+
+ // FIXME: is it worth checking for round-corners ?
+ SetupLineJoinMiter (cr);
+
+ // Draw if the path wasn't drawn by the Fill call
+ if (!drawn)
+ Draw (cr);
+ Stroke (cr, do_op);
+ return true;
+}
+
/*
* rendering notes:
* - a Width="0" or a Height="0" can be rendered differently from not
specifying Width or Height
@@ -989,6 +1045,26 @@
DependencyProperty* Line::X2Property;
DependencyProperty* Line::Y2Property;
+// The Line shape can be drawn while ignoring properties:
+// * Shape::StrokeLineJoin
+// * Shape::StrokeMiterLimit
+// * Shape::Fill
+bool
+Line::DrawShape (cairo_t *cr, bool do_op)
+{
+ // no need to clear path since none has been drawn to cairo
+ if (!stroke)
+ return false;
+
+ if (!SetupLine (cr))
+ return false;
+ SetupLineCaps (cr);
+
+ Draw (cr);
+ Stroke (cr, do_op);
+ return true;
+}
+
static void
calc_line_bounds (double x1, double x2, double y1, double y2, double
thickness, Rect* bounds)
{
@@ -1154,6 +1230,26 @@
return polygon_get_fill_rule (this);
}
+// The Polygon shape can be drawn while ignoring properties:
+// * Shape::StrokeStartLineCap
+// * Shape::StrokeEndLineCap
+bool
+Polygon::DrawShape (cairo_t *cr, bool do_op)
+{
+ bool drawn = Fill (cr, do_op);
+
+ if (!stroke)
+ return drawn;
+
+ if (!SetupLine (cr))
+ return drawn;
+ SetupLineJoinMiter (cr);
+
+ Draw (cr);
+ Stroke (cr, do_op);
+ return true;
+}
+
// special case when a polygon has a single line in it (it's drawn longer than
it should)
// e.g. <Polygon Fill="#000000" Stroke="#FF00FF" StrokeThickness="8"
Points="260,80 300,40" />
static void
@@ -1505,6 +1601,63 @@
DependencyProperty* Polyline::FillRuleProperty;
DependencyProperty* Polyline::PointsProperty;
+// The Polyline shape can be drawn while ignoring NO properties
+bool
+Polyline::DrawShape (cairo_t *cr, bool do_op)
+{
+ bool drawn = Fill (cr, do_op);
+
+ if (!stroke)
+ return drawn;
+
+ if (!SetupLine (cr))
+ return drawn;
+ SetupLineJoinMiter (cr);
+
+ // here we hack around #345888 where Cairo doesn't support different
start and end linecaps
+ PenLineCap start = shape_get_stroke_start_line_cap (this);
+ PenLineCap end = shape_get_stroke_end_line_cap (this);
+ if (do_op && (start != end)){
+ // the previous fill, if needed, has preserved the path
+ if (drawn)
+ cairo_new_path (cr);
+
+ // since Draw may not have been called (e.g. no Fill) we must
ensure the path was built
+ if (!drawn || !path || (path->cairo.num_data == 0))
+ BuildPath ();
+
+ cairo_path_data_t *data = path->cairo.data;
+ int length = path->cairo.num_data;
+ // we need to treat a single line scenario differently (like
Line::DrawShape)
+ if (length <= MOON_PATH_MOVE_TO_LENGTH +
MOON_PATH_LINE_TO_LENGTH) {
+ // note: this means either no, one or two points
+// TODO (right now a straight line is drawn (without caps)
+ } else {
+ // draw line #1 with start cap
+ cairo_set_line_cap (cr, convert_line_cap (start));
+ cairo_move_to (cr, data[1].point.x, data[1].point.y);
+ cairo_line_to (cr, data[3].point.x, data[3].point.y);
+ Stroke (cr, do_op);
+
+ // draw last line with end cap
+ cairo_set_line_cap (cr, convert_line_cap (end));
+ cairo_move_to (cr, data[length - 3].point.x,
data[length - 3].point.y);
+ cairo_line_to (cr, data[length - 1].point.x,
data[length - 1].point.y);
+ Stroke (cr, do_op);
+ }
+
+ // now all lines (including first and last) will be drawn with
no caps
+ // note: important for IsInside and other stuff depending on
Cairo context
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
+ } else {
+ cairo_set_line_cap (cr, convert_line_cap (start));
+ }
+
+ Draw (cr);
+ Stroke (cr, do_op);
+ return true;
+}
+
FillRule
Polyline::GetFillRule ()
{
@@ -1702,6 +1855,39 @@
DependencyProperty* Path::DataProperty;
+bool
+Path::SetupLine (cairo_t* cr)
+{
+ // we cannot use the thickness==0 optimization (like Shape::SetupLine
provides)
+ // since we'll be using cairo to compute the path's bounds later
+ // see bug #352188 for an example of what this breaks
+ double thickness = IsDegenerate () ? 1.0 : shape_get_stroke_thickness
(this);
+ cairo_set_line_width (cr, thickness);
+ return SetupDashes (cr, thickness);
+}
+
+// The Polygon shape can be drawn while ignoring properties:
+// * none
+// FIXME: actually it depends on the geometry, another level of optimization
awaits ;-)
+// e.g. close geometries don't need to setup line caps,
+// line join/miter don't applies to curve, like EllipseGeometry
+bool
+Path::DrawShape (cairo_t *cr, bool do_op)
+{
+ bool drawn = Shape::Fill (cr, do_op);
+ if (stroke) {
+ if (!SetupLine (cr))
+ return drawn; // return if we have a path in the
cairo_t
+ SetupLineCaps (cr);
+ SetupLineJoinMiter (cr);
+
+ if (!drawn)
+ Draw (cr);
+ Stroke (cr, do_op);
+ }
+ return true;
+}
+
FillRule
Path::GetFillRule ()
{
Modified: trunk/moon/src/shape.h
===================================================================
--- trunk/moon/src/shape.h 2008-01-22 20:33:53 UTC (rev 93569)
+++ trunk/moon/src/shape.h 2008-01-22 20:35:40 UTC (rev 93570)
@@ -36,6 +36,16 @@
Brush *stroke, *fill;
void DoDraw (cairo_t *cr, bool do_op);
+ void SetupLineCaps (cairo_t *cr);
+ void SetupLineJoinMiter (cairo_t *cr);
+ virtual bool SetupLine (cairo_t* cr);
+ bool SetupDashes (cairo_t *cr, double thickness);
+ bool Fill (cairo_t *cr, bool do_op);
+ void Stroke (cairo_t *cr, bool do_op);
+ void Clip (cairo_t *cr);
+ virtual bool DrawShape (cairo_t *cr, bool do_op) = 0;
+// virtual bool DrawDegenerateShape (cairo_t *cr, bool do_op) = 0;
+
moon_path *path;
virtual void InvalidatePathCache (bool free = false);
public:
@@ -68,19 +78,10 @@
// new virtual methods for shapes
//
- virtual bool IsFilled () { return fill; }
virtual bool IsStroked () { return stroke; }
virtual bool CanFill () { return false; }
virtual FillRule GetFillRule () { return FillRuleNonzero; }
- // Stroke[Start|End]LineCap properties are ignored for some shapes
- // e.g. Ellipse, Rectangle, Polygon and (closed) Path
- virtual bool NeedsLineCaps () { return true; }
-
- // StrokeLineJoin & StrokeMiterLimit properties are ignored for some
shapes
- // e.g. Ellipse, Rectangle (with rounded corners), Line
- virtual bool NeedsLineJoin () { return true; }
-
//
// Draw: draws the Shape in the cairo context (affine transforms are
set before this
// is called).
@@ -132,14 +133,14 @@
// Ellipse
//
class Ellipse : public Shape {
+ protected:
+ virtual bool DrawShape (cairo_t *cr, bool do_op);
public:
Ellipse ();
virtual Type::Kind GetObjectType () { return Type::ELLIPSE; };
virtual void BuildPath ();
virtual bool CanFill () { return true; }
- virtual bool NeedsLineCaps () { return false; }
- virtual bool NeedsLineJoin () { return false; }
virtual void OnPropertyChanged (DependencyProperty *prop);
};
@@ -151,6 +152,8 @@
// Rectangle class
//
class Rectangle : public Shape {
+ protected:
+ virtual bool DrawShape (cairo_t *cr, bool do_op);
public:
static DependencyProperty* RadiusXProperty;
static DependencyProperty* RadiusYProperty;
@@ -164,9 +167,6 @@
bool GetRadius (double *rx, double *ry);
virtual bool CanFill () { return true; }
- virtual bool NeedsLineCaps () { return false; }
- // technically we could override NeedsLineJoin to check for round
corners
- // but that would be just as expensive as setting the unneeded values
};
Rectangle *rectangle_new (void);
@@ -180,6 +180,8 @@
// Line class
//
class Line : public Shape {
+ protected:
+ virtual bool DrawShape (cairo_t *cr, bool do_op);
public:
static DependencyProperty* X1Property;
static DependencyProperty* Y1Property;
@@ -196,9 +198,6 @@
// Line has no center to compute, it's always 0,0 because it provides
it's own start and end
// virtual Point GetTransformOrigin ();
-
- virtual bool IsFilled () { return false; }
- virtual bool NeedsLineJoin () { return false; }
};
Line *line_new (void);
@@ -216,6 +215,8 @@
// Polygon
//
class Polygon : public Shape {
+ protected:
+ virtual bool DrawShape (cairo_t *cr, bool do_op);
public:
static DependencyProperty* FillRuleProperty;
static DependencyProperty* PointsProperty;
@@ -231,7 +232,6 @@
virtual FillRule GetFillRule ();
virtual bool CanFill () { return true; }
- virtual bool NeedsLineCaps () { return false; }
virtual void GetSizeForBrush (cairo_t *cr, double *width, double
*height);
virtual void ComputeBounds ();
@@ -252,6 +252,8 @@
// Polyline
//
class Polyline : public Shape {
+ protected:
+ virtual bool DrawShape (cairo_t *cr, bool do_op);
public:
static DependencyProperty* FillRuleProperty;
static DependencyProperty* PointsProperty;
@@ -286,6 +288,9 @@
// Path
//
class Path : public Shape {
+ protected:
+ virtual bool SetupLine (cairo_t* cr);
+ virtual bool DrawShape (cairo_t *cr, bool do_op);
public:
static DependencyProperty* DataProperty;
_______________________________________________
Mono-patches maillist - [email protected]
http://lists.ximian.com/mailman/listinfo/mono-patches