--- a/gfx/thebes/public/gfxContext.h
+++ b/gfx/thebes/public/gfxContext.h
@@ -169,19 +169,24 @@ public:
/**
* Draws a line from the current point to pt.
*
* @see MoveTo
*/
void LineTo(const gfxPoint& pt);
/**
+ * Draws a cubic Bézier curve with control points pt1, pt2 and pt3.
+ */
+ void CurveTo(const gfxPoint& pt1, const gfxPoint& pt2, const gfxPoint& pt3);
+
+ /**
* Draws a quadratic Bézier curve with control points pt1, pt2 and pt3.
*/
- void CurveTo(const gfxPoint& pt1, const gfxPoint& pt2, const gfxPoint& pt3);
+ void QuadraticCurveTo(const gfxPoint& pt1, const gfxPoint& pt2);
/**
* Draws a clockwise arc (i.e. a circle segment).
* @param center The center of the circle
* @param radius The radius of the circle
* @param angle1 Starting angle for the segment
* @param angle2 Ending angle
*/
@@ -205,19 +210,42 @@ public:
*/
void Line(const gfxPoint& start, const gfxPoint& end); // XXX snapToPixels option?
/**
* Draws the rectangle given by rect.
* @param snapToPixels ?
*/
void Rectangle(const gfxRect& rect, PRBool snapToPixels = PR_FALSE);
+
+ /**
+ * Draw an ellipse at the center corner with the given dimensions.
+ * It extends dimensions.width / 2.0 in the horizontal direction
+ * from the center, and dimensions.height / 2.0 in the vertical
+ * direction.
+ */
void Ellipse(const gfxPoint& center, const gfxSize& dimensions);
+
+ /**
+ * Draw a polygon from the given points
+ */
void Polygon(const gfxPoint *points, PRUint32 numPoints);
+ /*
+ * Draw a rounded rectangle, with the given outer rect and
+ * corners. The corners specify the radii of the two axes of an
+ * ellipse (the horizontal and vertical directions given by the
+ * width and height, respectively). By default the ellipse is
+ * drawn in a clockwise direction; if draw_clockwise is PR_FALSE,
+ * then it's drawn counterclockwise.
+ */
+ void RoundedRectangle(const gfxRect& rect,
+ const gfxCornerSizes& corners,
+ PRBool draw_clockwise = PR_TRUE);
+
/**
** Transformation Matrix manipulation
**/
/**
* Adds a translation to the current matrix. This translation takes place
* before the previously set transformations.
*/
--- a/gfx/thebes/public/gfxRect.h
+++ b/gfx/thebes/public/gfxRect.h
@@ -36,16 +36,29 @@
* ***** END LICENSE BLOCK ***** */
#ifndef GFX_RECT_H
#define GFX_RECT_H
#include "gfxTypes.h"
#include "gfxPoint.h"
+struct THEBES_API gfxCorner {
+ typedef int Corner;
+ enum {
+ // this order is important!
+ TOP_LEFT = 0,
+ TOP_RIGHT = 1,
+ BOTTOM_RIGHT = 2,
+ BOTTOM_LEFT = 3,
+ NUM_CORNERS = 4
+ };
+};
+
+
struct THEBES_API gfxRect {
// pt? point?
gfxPoint pos;
gfxSize size;
gfxRect() {}
gfxRect(const gfxRect& s) : pos(s.pos), size(s.size) {}
gfxRect(const gfxPoint& _pos, const gfxSize& _size) : pos(_pos), size(_size) {}
@@ -137,16 +150,29 @@ struct THEBES_API gfxRect {
void RoundOut();
// grabbing specific points
gfxPoint TopLeft() const { return gfxPoint(pos); }
gfxPoint TopRight() const { return pos + gfxSize(size.width, 0.0); }
gfxPoint BottomLeft() const { return pos + gfxSize(0.0, size.height); }
gfxPoint BottomRight() const { return pos + size; }
+ gfxPoint Corner(gfxCorner::Corner corner) const {
+ switch (corner) {
+ case gfxCorner::TOP_LEFT: return TopLeft();
+ case gfxCorner::TOP_RIGHT: return TopRight();
+ case gfxCorner::BOTTOM_LEFT: return BottomLeft();
+ case gfxCorner::BOTTOM_RIGHT: return BottomRight();
+ default:
+ NS_ERROR("Invalid corner!");
+ break;
+ }
+ return gfxPoint(0.0, 0.0);
+ }
+
/* Conditions this border to Cairo's max coordinate space.
* The caller can check IsEmpty() after Condition() -- if it's TRUE,
* the caller can possibly avoid doing any extra rendering.
*/
void Condition();
void Scale(gfxFloat k) {
NS_ASSERTION(k >= 0.0, "Invalid (negative) scale factor");
@@ -169,9 +195,53 @@ struct THEBES_API gfxRect {
NS_ASSERTION(k > 0.0, "Invalid (negative) scale factor");
pos.x /= k;
pos.y /= k;
size.width /= k;
size.height /= k;
}
};
+struct THEBES_API gfxCornerSizes {
+ gfxSize sizes[gfxCorner::NUM_CORNERS];
+
+ gfxCornerSizes () { }
+
+ gfxCornerSizes (gfxFloat v) {
+ for (int i = 0; i < gfxCorner::NUM_CORNERS; i++)
+ sizes[i].SizeTo(v, v);
+ }
+
+ gfxCornerSizes (gfxFloat tl, gfxFloat tr, gfxFloat br, gfxFloat bl) {
+ sizes[gfxCorner::TOP_LEFT].SizeTo(tl, tl);
+ sizes[gfxCorner::TOP_RIGHT].SizeTo(tr, tr);
+ sizes[gfxCorner::BOTTOM_RIGHT].SizeTo(br, br);
+ sizes[gfxCorner::BOTTOM_LEFT].SizeTo(bl, bl);
+ }
+
+ gfxCornerSizes (const gfxSize& tl, const gfxSize& tr, const gfxSize& br, const gfxSize& bl) {
+ sizes[gfxCorner::TOP_LEFT] = tl;
+ sizes[gfxCorner::TOP_RIGHT] = tr;
+ sizes[gfxCorner::BOTTOM_RIGHT] = br;
+ sizes[gfxCorner::BOTTOM_LEFT] = bl;
+ }
+
+ const gfxSize& operator[] (gfxCorner::Corner index) const {
+ return sizes[index];
+ }
+
+ gfxSize& operator[] (gfxCorner::Corner index) {
+ return sizes[index];
+ }
+
+ const gfxSize TopLeft() const { return sizes[gfxCorner::TOP_LEFT]; }
+ gfxSize& TopLeft() { return sizes[gfxCorner::TOP_LEFT]; }
+
+ const gfxSize TopRight() const { return sizes[gfxCorner::TOP_RIGHT]; }
+ gfxSize& TopRight() { return sizes[gfxCorner::TOP_RIGHT]; }
+
+ const gfxSize BottomLeft() const { return sizes[gfxCorner::BOTTOM_LEFT]; }
+ gfxSize& BottomLeft() { return sizes[gfxCorner::BOTTOM_LEFT]; }
+
+ const gfxSize BottomRight() const { return sizes[gfxCorner::BOTTOM_RIGHT]; }
+ gfxSize& BottomRight() { return sizes[gfxCorner::BOTTOM_RIGHT]; }
+};
#endif /* GFX_RECT_H */
--- a/gfx/thebes/src/gfxContext.cpp
+++ b/gfx/thebes/src/gfxContext.cpp
@@ -168,16 +168,30 @@ gfxContext::LineTo(const gfxPoint& pt)
void
gfxContext::CurveTo(const gfxPoint& pt1, const gfxPoint& pt2, const gfxPoint& pt3)
{
cairo_curve_to(mCairo, pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y);
}
void
+gfxContext::QuadraticCurveTo(const gfxPoint& pt1, const gfxPoint& pt2)
+{
+ double cx, cy;
+ cairo_get_current_point(mCairo, &cx, &cy);
+ cairo_curve_to(mCairo,
+ (cx + pt1.x * 2.0) / 3.0,
+ (cy + pt1.y * 2.0) / 3.0,
+ (pt1.x * 2.0 + pt2.x) / 3.0,
+ (pt1.y * 2.0 + pt2.y) / 3.0,
+ pt2.x,
+ pt2.y);
+}
+
+void
gfxContext::Arc(const gfxPoint& center, gfxFloat radius,
gfxFloat angle1, gfxFloat angle2)
{
cairo_arc(mCairo, center.x, center.y, radius, angle1, angle2);
}
void
gfxContext::NegativeArc(const gfxPoint& center, gfxFloat radius,
@@ -215,47 +229,21 @@ gfxContext::Rectangle(const gfxRect& rec
}
cairo_rectangle(mCairo, rect.pos.x, rect.pos.y, rect.size.width, rect.size.height);
}
void
gfxContext::Ellipse(const gfxPoint& center, const gfxSize& dimensions)
{
- // circle?
- if (dimensions.width == dimensions.height) {
- double radius = dimensions.width / 2.0;
-
- cairo_arc(mCairo, center.x, center.y, radius, 0, 2.0 * M_PI);
- } else {
- double x = center.x;
- double y = center.y;
- double w = dimensions.width;
- double h = dimensions.height;
-
- cairo_new_path(mCairo);
- cairo_move_to(mCairo, x + w/2.0, y);
+ gfxSize halfDim = dimensions / 2.0;
+ gfxRect r(center - halfDim, dimensions);
+ gfxCornerSizes c(halfDim, halfDim, halfDim, halfDim);
- cairo_rel_curve_to(mCairo,
- 0, 0,
- w / 2.0, 0,
- w / 2.0, h / 2.0);
- cairo_rel_curve_to(mCairo,
- 0, 0,
- 0, h / 2.0,
- - w / 2.0, h / 2.0);
- cairo_rel_curve_to(mCairo,
- 0, 0,
- - w / 2.0, 0,
- - w / 2.0, - h / 2.0);
- cairo_rel_curve_to(mCairo,
- 0, 0,
- 0, - h / 2.0,
- w / 2.0, - h / 2.0);
- }
+ RoundedRectangle (r, c);
}
void
gfxContext::Polygon(const gfxPoint *points, PRUint32 numPoints)
{
if (numPoints == 0)
return;
@@ -791,8 +779,106 @@ gfxContext::GetFlattenedPath()
return path;
}
PRBool
gfxContext::HasError()
{
return cairo_status(mCairo) != CAIRO_STATUS_SUCCESS;
}
+
+void
+gfxContext::RoundedRectangle(const gfxRect& rect,
+ const gfxCornerSizes& corners,
+ PRBool draw_clockwise)
+{
+ //
+ // For CW drawing, this looks like:
+ //
+ // ...******0** 1 C
+ // ****
+ // *** 2
+ // **
+ // *
+ // *
+ // 3
+ // *
+ // *
+ //
+ // Where 0, 1, 2, 3 are the control points of the Bezier curve for the corner,
+ // and C is the actual corner point.
+ //
+ // For details about representing an elliptical arc as a cubic Bezier curve,
+ // see http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf
+ //
+ // At the start of the loop, the current point is assumed to be
+ // the point adjacent to the top left corner on the top
+ // horizontal. Note that corner indices start at the top left and
+ // continue clockwise, whereas in our loop i = 0 refers to the top
+ // right corner.
+ //
+ // When going CCW, the control points are swapped, and the first corner
+ // that's drawn is the top left (along with the top segment).
+
+ // This is (sqrt(7) - 1) / 3; this ends up falling out of the equations
+ // given in the above paper -- it's the value of alpha at the end of section
+ // 3.4.1 when n2 and n1 are 90 degrees apart. For the various corners, the
+ // axes the sign of this value changes, or it might be 0 -- it's multiplied by
+ // the appropriate multiplier from the list before using.
+ const gfxFloat alpha = 0.54858377035486361;
+
+ typedef struct { gfxFloat a, b; } twoFloats;
+
+ twoFloats cwCornerMults[4] = { { -1, 0 },
+ { 0, -1 },
+ { +1, 0 },
+ { 0, +1 } };
+ twoFloats ccwCornerMults[4] = { { +1, 0 },
+ { 0, -1 },
+ { -1, 0 },
+ { 0, +1 } };
+
+ twoFloats *cornerMults = draw_clockwise ? cwCornerMults : ccwCornerMults;
+
+ gfxPoint pc, p0, p1, p2, p3;
+
+ if (draw_clockwise)
+ cairo_move_to(mCairo, rect.pos.x + corners[gfxCorner::TOP_LEFT].width, rect.pos.y);
+ else
+ cairo_move_to(mCairo, rect.pos.x + rect.size.width - corners[gfxCorner::TOP_RIGHT].width, rect.pos.y);
+
+ for (int i = 0; i < gfxCorner::NUM_CORNERS; i++) {
+ // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
+ int c = draw_clockwise ? ((i+1) % 4) : ((4-i) % 4);
+
+ // i+2 and i+3 respectively. These are used to index into the corner
+ // multiplier table, and were deduced by calculating out the long form
+ // of each corner and finding a pattern in the signs and values.
+ int i2 = (i+2) % 4;
+ int i3 = (i+3) % 4;
+
+ pc = rect.Corner(c);
+
+ if (corners[c].width > 0.0 && corners[c].height > 0.0) {
+ p0.x = pc.x + cornerMults[i].a * corners[c].width;
+ p0.y = pc.y + cornerMults[i].b * corners[c].height;
+
+ p3.x = pc.x + cornerMults[i3].a * corners[c].width;
+ p3.y = pc.y + cornerMults[i3].b * corners[c].height;
+
+ p1.x = p0.x + alpha * cornerMults[i2].a * corners[c].width;
+ p1.y = p0.y + alpha * cornerMults[i2].b * corners[c].height;
+
+ p2.x = p3.x - alpha * cornerMults[i3].a * corners[c].width;
+ p2.y = p3.y - alpha * cornerMults[i3].b * corners[c].height;
+
+ cairo_line_to (mCairo, p0.x, p0.y);
+ cairo_curve_to (mCairo,
+ p1.x, p1.y,
+ p2.x, p2.y,
+ p3.x, p3.y);
+ } else {
+ cairo_line_to (mCairo, pc.x, pc.y);
+ }
+ }
+
+ cairo_close_path (mCairo);
+}