[gtk/path-ops: 1/8] Add path convexity api
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/path-ops: 1/8] Add path convexity api
- Date: Fri, 25 Mar 2022 16:57:19 +0000 (UTC)
commit f6d110a841a3148ad77dfac00545128f19774f5e
Author: Matthias Clasen <mclasen redhat com>
Date: Tue Mar 22 00:33:09 2022 -0400
Add path convexity api
gsk/gskcontour.c | 69 ++++++++++++--
gsk/gskcontourprivate.h | 5 +-
gsk/gskconvexity.c | 232 ++++++++++++++++++++++++++++++++++++++++++++++++
gsk/gskenums.h | 7 ++
gsk/gskpath.c | 48 ++++++++++
gsk/gskpath.h | 5 ++
gsk/meson.build | 1 +
testsuite/gsk/path.c | 62 +++++++++++--
8 files changed, 415 insertions(+), 14 deletions(-)
---
diff --git a/gsk/gskcontour.c b/gsk/gskcontour.c
index bcbed9153a..392926aa5b 100644
--- a/gsk/gskcontour.c
+++ b/gsk/gskcontour.c
@@ -98,6 +98,8 @@ struct _GskContourClass
float distance,
GskLineJoin line_join,
float miter_limit);
+ GskConvexity (* get_convexity) (const GskContour *contour);
+ GskConvexity (* compute_convexity) (const GskContour *contour);
};
static gsize
@@ -328,7 +330,7 @@ gsk_rect_contour_get_closest_point (const GskContour *contour,
const GskRectContour *self = (const GskRectContour *) contour;
graphene_point_t t, p;
float distance;
-
+
/* offset coords to be relative to rectangle */
t.x = point->x - self->x;
t.y = point->y - self->y;
@@ -385,7 +387,7 @@ gsk_rect_contour_get_closest_point (const GskContour *contour,
*out_pos = p;
if (out_offset)
- *out_offset = (t.x == 0.0 && self->width > 0 ? 2 - t.y : t.y) * ABS (self->height) +
+ *out_offset = (t.x == 0.0 && self->width > 0 ? 2 - t.y : t.y) * ABS (self->height) +
(t.y == 1.0 ? 2 - t.x : t.x) * ABS (self->width);
if (out_tangent)
@@ -564,6 +566,12 @@ gsk_rect_contour_offset (const GskContour *contour,
gsk_contour_default_offset (contour, builder, distance, line_join, miter_limit);
}
+static GskConvexity
+gsk_rect_contour_get_convexity (const GskContour *contour)
+{
+ return GSK_CONVEXITY_CONVEX;
+}
+
static const GskContourClass GSK_RECT_CONTOUR_CLASS =
{
sizeof (GskRectContour),
@@ -584,7 +592,9 @@ static const GskContourClass GSK_RECT_CONTOUR_CLASS =
gsk_rect_contour_get_winding,
gsk_rect_contour_get_stroke_bounds,
gsk_rect_contour_add_stroke,
- gsk_rect_contour_offset
+ gsk_rect_contour_offset,
+ gsk_rect_contour_get_convexity,
+ gsk_rect_contour_get_convexity,
};
GskContour *
@@ -965,6 +975,12 @@ gsk_circle_contour_offset (const GskContour *contour,
gsk_contour_default_offset (contour, builder, distance, line_join, miter_limit);
}
+static GskConvexity
+gsk_circle_contour_get_convexity (const GskContour *contour)
+{
+ return GSK_CONVEXITY_CONVEX;
+}
+
static const GskContourClass GSK_CIRCLE_CONTOUR_CLASS =
{
sizeof (GskCircleContour),
@@ -985,7 +1001,9 @@ static const GskContourClass GSK_CIRCLE_CONTOUR_CLASS =
gsk_circle_contour_get_winding,
gsk_circle_contour_get_stroke_bounds,
gsk_circle_contour_add_stroke,
- gsk_circle_contour_offset
+ gsk_circle_contour_offset,
+ gsk_circle_contour_get_convexity,
+ gsk_circle_contour_get_convexity,
};
GskContour *
@@ -1019,6 +1037,7 @@ struct _GskStandardContour
GskContour contour;
GskPathFlags flags;
+ GskConvexity convexity;
gsize n_ops;
gsize n_points;
@@ -1445,7 +1464,7 @@ gsk_standard_contour_get_closest_point (const GskContour *contour,
dist = test_dist;
}
//g_print ("!!! %zu: (%g-%g @ %g) dist %g\n", i, measure->start_progress, measure->end_progress,
progress, dist);
- /* double check that the point actually is closer */
+ /* double check that the point actually is closer */
if (dist <= threshold)
{
if (out_distance)
@@ -1555,7 +1574,7 @@ gsk_standard_contour_add_segment (const GskContour *contour,
gsk_curve_builder_to (&cut, builder);
i = start_measure->op + 1;
}
- else
+ else
i = emit_move_to ? 0 : 1;
for (; i < (end_measure ? end_measure->op : self->n_ops - 1); i++)
@@ -1723,6 +1742,27 @@ gsk_standard_contour_offset (const GskContour *contour,
gsk_contour_default_offset (contour, builder, distance, line_join, miter_limit);
}
+static GskConvexity
+gsk_standard_contour_get_convexity (const GskContour *contour)
+{
+ GskStandardContour *self = (GskStandardContour *) contour;
+
+ return self->convexity;
+}
+
+extern GskConvexity compute_convexity (const GskContour *contour);
+
+static GskConvexity
+gsk_standard_contour_compute_convexity (const GskContour *contour)
+{
+ GskStandardContour *self = (GskStandardContour *) contour;
+
+ if (self->convexity == GSK_CONVEXITY_UNKNOWN)
+ self->convexity = compute_convexity (contour);
+
+ return self->convexity;
+}
+
static const GskContourClass GSK_STANDARD_CONTOUR_CLASS =
{
sizeof (GskStandardContour),
@@ -1743,7 +1783,9 @@ static const GskContourClass GSK_STANDARD_CONTOUR_CLASS =
gsk_standard_contour_get_winding,
gsk_standard_contour_get_stroke_bounds,
gsk_standard_contour_add_stroke,
- gsk_standard_contour_offset
+ gsk_standard_contour_offset,
+ gsk_standard_contour_get_convexity,
+ gsk_standard_contour_compute_convexity,
};
/* You must ensure the contour has enough size allocated,
@@ -1765,6 +1807,7 @@ gsk_standard_contour_init (GskContour *contour,
self->contour.klass = &GSK_STANDARD_CONTOUR_CLASS;
self->flags = flags;
+ self->convexity = GSK_CONVEXITY_UNKNOWN;
self->n_ops = n_ops;
self->n_points = n_points;
self->points = (graphene_point_t *) &self->ops[n_ops];
@@ -1960,3 +2003,15 @@ gsk_contour_dup (const GskContour *src)
return copy;
}
+GskConvexity
+gsk_contour_get_convexity (const GskContour *contour)
+{
+ return contour->klass->get_convexity (contour);
+}
+
+GskConvexity
+gsk_contour_compute_convexity (const GskContour *contour)
+{
+ return contour->klass->compute_convexity (contour);
+}
+
diff --git a/gsk/gskcontourprivate.h b/gsk/gskcontourprivate.h
index 3bf233f3fe..965c13b161 100644
--- a/gsk/gskcontourprivate.h
+++ b/gsk/gskcontourprivate.h
@@ -27,7 +27,7 @@
G_BEGIN_DECLS
-typedef enum
+typedef enum
{
GSK_PATH_FLAT,
GSK_PATH_CLOSED
@@ -118,6 +118,9 @@ void gsk_contour_default_offset (const GskContou
GskLineJoin line_join,
float miter_limit);
+GskConvexity gsk_contour_get_convexity (const GskContour *contour);
+GskConvexity gsk_contour_compute_convexity (const GskContour *contour);
+
G_END_DECLS
#endif /* __GSK_CONTOUR_PRIVATE_H__ */
diff --git a/gsk/gskconvexity.c b/gsk/gskconvexity.c
new file mode 100644
index 0000000000..11fc76244a
--- /dev/null
+++ b/gsk/gskconvexity.c
@@ -0,0 +1,232 @@
+#include "config.h"
+
+#include "gskcontourprivate.h"
+
+typedef enum
+{
+ GSK_DIR_CHANGE_UNKNOWN,
+ GSK_DIR_CHANGE_LEFT,
+ GSK_DIR_CHANGE_RIGHT,
+ GSK_DIR_CHANGE_STRAIGHT,
+ GSK_DIR_CHANGE_REVERSE,
+ GSK_DIR_CHANGE_INVALID,
+} GskDirChange;
+
+typedef enum
+{
+ GSK_PATH_DIRECTION_UNKNOWN,
+ GSK_PATH_DIRECTION_CLOCKWISE,
+ GSK_PATH_DIRECTION_COUNTERCLOCKWISE,
+} GskPathDirection;
+
+typedef struct _GskConvexityChecker GskConvexityChecker;
+struct _GskConvexityChecker
+{
+ graphene_point_t first_point;
+ graphene_vec2_t first_vec;
+ graphene_point_t last_point;
+ graphene_vec2_t last_vec;
+ GskDirChange expected_direction;
+ GskPathDirection first_direction;
+ int reversals;
+ gboolean finite;
+ GskConvexity convexity;
+};
+
+static float
+cross_product (graphene_vec2_t *a,
+ graphene_vec2_t *b)
+{
+ float fa[2];
+ float fb[2];
+ graphene_vec2_to_float (a, fa);
+ graphene_vec2_to_float (b, fb);
+ return fa[0] * fb[1] - fa[1] * fb[0];
+}
+
+static GskDirChange
+direction_change (GskConvexityChecker *c,
+ graphene_vec2_t *v)
+{
+ float cross = cross_product (&(c->last_vec), v);
+ if (!finite (cross))
+ return GSK_DIR_CHANGE_UNKNOWN;
+
+ if (cross == 0)
+ return graphene_vec2_dot (&(c->last_vec), v) < 0
+ ? GSK_DIR_CHANGE_REVERSE
+ : GSK_DIR_CHANGE_STRAIGHT;
+
+ return cross > 0
+ ? GSK_DIR_CHANGE_RIGHT
+ : GSK_DIR_CHANGE_LEFT;
+}
+
+static gboolean
+add_vec (GskConvexityChecker *c,
+ graphene_vec2_t *v)
+{
+ GskDirChange dir;
+
+ dir = direction_change (c, v);
+ switch (dir)
+ {
+ case GSK_DIR_CHANGE_LEFT:
+ case GSK_DIR_CHANGE_RIGHT:
+ if (c->expected_direction == GSK_DIR_CHANGE_INVALID)
+ {
+ c->expected_direction = dir;
+ c->first_direction = (dir == GSK_DIR_CHANGE_RIGHT)
+ ? GSK_PATH_DIRECTION_CLOCKWISE
+ : GSK_PATH_DIRECTION_COUNTERCLOCKWISE;
+ }
+ else if (c->expected_direction != dir)
+ {
+ c->first_direction = GSK_PATH_DIRECTION_UNKNOWN;
+ c->convexity = GSK_CONVEXITY_CONCAVE;
+ return FALSE;
+ }
+ graphene_vec2_init_from_vec2 (&c->last_vec, v);
+ break;
+
+ case GSK_DIR_CHANGE_STRAIGHT:
+ break;
+
+ case GSK_DIR_CHANGE_REVERSE:
+ graphene_vec2_init_from_vec2 (&c->last_vec, v);
+ c->reversals++;
+ if (c->reversals > 2)
+ c->convexity = GSK_CONVEXITY_CONCAVE;
+ return c->reversals < 3;
+
+ case GSK_DIR_CHANGE_UNKNOWN:
+ c->finite = FALSE;
+ return FALSE;
+
+ case GSK_DIR_CHANGE_INVALID:
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static void
+gsk_convexity_checker_init (GskConvexityChecker *c)
+{
+ c->first_point = GRAPHENE_POINT_INIT(0,0);
+ c->last_point = GRAPHENE_POINT_INIT(0,0);
+ graphene_vec2_init (&c->first_vec, 0, 0);
+ graphene_vec2_init (&c->last_vec, 0, 0);
+ c->expected_direction = GSK_DIR_CHANGE_INVALID;
+ c->first_direction = GSK_PATH_DIRECTION_UNKNOWN;
+ c->reversals = 0;
+ c->finite = TRUE;
+ c->convexity = GSK_CONVEXITY_UNKNOWN;
+}
+
+static void
+gsk_convexity_checker_move (GskConvexityChecker *c,
+ const graphene_point_t *p)
+{
+ c->first_point = c->last_point = *p;
+ c->expected_direction = GSK_DIR_CHANGE_INVALID;
+ c->convexity = GSK_CONVEXITY_CONVEX;
+}
+
+static gboolean
+gsk_convexity_checker_add_point (GskConvexityChecker *c,
+ const graphene_point_t *p)
+{
+ graphene_vec2_t v;
+
+ if (graphene_point_equal (&c->last_point, p))
+ return TRUE;
+
+ graphene_vec2_init (&v,
+ p->x - c->last_point.x,
+ p->y - c->last_point.y);
+
+ if (graphene_point_equal (&c->first_point, &c->last_point) &&
+ c->expected_direction == GSK_DIR_CHANGE_INVALID)
+ {
+ graphene_vec2_init_from_vec2 (&c->last_vec, &v);
+ graphene_vec2_init_from_vec2 (&c->first_vec, &v);
+ }
+ else if (!add_vec (c, &v))
+ {
+ c->convexity = GSK_CONVEXITY_CONCAVE;
+ return FALSE;
+ }
+
+ c->last_point = *p;
+ return TRUE;
+}
+
+static gboolean
+gsk_convexity_checker_close (GskConvexityChecker *c)
+{
+ if (!(gsk_convexity_checker_add_point (c, &c->first_point) &&
+ add_vec (c, &c->first_vec)))
+ {
+ c->convexity = GSK_CONVEXITY_CONCAVE;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+convex_cb (GskPathOperation op,
+ const graphene_point_t *pts,
+ gsize n_pts,
+ float weight,
+ gpointer user_data)
+{
+ GskConvexityChecker *c = user_data;
+
+ switch (op)
+ {
+ case GSK_PATH_MOVE:
+ gsk_convexity_checker_move (c, &pts[0]);
+ break;
+ case GSK_PATH_CLOSE:
+ if (!gsk_convexity_checker_close (c))
+ return FALSE;
+ break;
+ case GSK_PATH_LINE:
+ if (!gsk_convexity_checker_add_point (c, &pts[1]))
+ return FALSE;
+ break;
+ case GSK_PATH_CURVE:
+ if (!gsk_convexity_checker_add_point (c, &pts[1]) ||
+ !gsk_convexity_checker_add_point (c, &pts[2]) ||
+ !gsk_convexity_checker_add_point (c, &pts[3]))
+ return FALSE;
+ break;
+ case GSK_PATH_CONIC:
+ if (!gsk_convexity_checker_add_point (c, &pts[1]) ||
+ !gsk_convexity_checker_add_point (c, &pts[3]))
+ return FALSE;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+GskConvexity compute_convexity (const GskContour *contour);
+
+GskConvexity
+compute_convexity (const GskContour *contour)
+{
+ GskConvexityChecker c;
+
+ gsk_convexity_checker_init (&c);
+ gsk_contour_foreach (contour, 0.001, convex_cb, &c);
+
+ g_assert (c.convexity != GSK_CONVEXITY_UNKNOWN);
+
+ return c.convexity;
+}
diff --git a/gsk/gskenums.h b/gsk/gskenums.h
index b9aabfc735..b70edeae77 100644
--- a/gsk/gskenums.h
+++ b/gsk/gskenums.h
@@ -354,4 +354,11 @@ typedef enum
GSK_GL_UNIFORM_TYPE_VEC4,
} GskGLUniformType;
+typedef enum
+{
+ GSK_CONVEXITY_UNKNOWN,
+ GSK_CONVEXITY_CONVEX,
+ GSK_CONVEXITY_CONCAVE,
+} GskConvexity;
+
#endif /* __GSK_TYPES_H__ */
diff --git a/gsk/gskpath.c b/gsk/gskpath.c
index 4d5961bf94..9f24637c0c 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -1240,6 +1240,54 @@ gsk_path_get_stroke_bounds (GskPath *path,
return TRUE;
}
+/**
+ * gsk_path_get_convexity:
+ * @self: a `GskPath`
+ *
+ * Returns information about whether this path is convex.
+ *
+ * Note that this function may return `GSK_CONVEXITY_UNKNOWN`
+ * if the information has not been computed yet. To force it
+ * to be computed, use [method Gsk Path.compute_convexity]
+ * instead.
+ *
+ * Returns: the available `GskConvexity` information for @self
+ */
+GskConvexity
+gsk_path_get_convexity (GskPath *self)
+{
+ if (self->n_contours > 1)
+ return GSK_CONVEXITY_CONCAVE;
+
+ return gsk_contour_get_convexity (gsk_path_get_contour (self, 0));
+}
+
+/**
+ * gsk_path_compute_convexity:
+ * @self: a `GskPath`
+ *
+ * Returns information about whether this path is convex.
+ *
+ * Note that this function will compute the convexity information
+ * if it has not available yet, so it will never return
+ * `GSK_CONVEXITY_UNKNOWN`. If you want to avoid the computation
+ * overhead, see [method Gsk Path.get_convexity].
+ *
+ * Returns: `GSK_CONVEXITY_CONVEX` if @self is convex, and
+ * `GSK_CONVEXITY_CONCAVE` if it isn't
+ */
+GskConvexity
+gsk_path_compute_convexity (GskPath *self)
+{
+ if (self->n_contours == 0)
+ return GSK_CONVEXITY_CONVEX;
+
+ if (self->n_contours > 1)
+ return GSK_CONVEXITY_CONCAVE;
+
+ return gsk_contour_compute_convexity (gsk_path_get_contour (self, 0));
+}
+
/**
* gsk_path_stroke:
* @self: a `GskPath`
diff --git a/gsk/gskpath.h b/gsk/gskpath.h
index 4a45b00564..0f1ca0b804 100644
--- a/gsk/gskpath.h
+++ b/gsk/gskpath.h
@@ -103,6 +103,11 @@ gboolean gsk_path_get_stroke_bounds (GskPath
const GskStroke *stroke,
graphene_rect_t *bounds);
+GDK_AVAILABLE_IN_ALL
+GskConvexity gsk_path_get_convexity (GskPath *self);
+GDK_AVAILABLE_IN_ALL
+GskConvexity gsk_path_compute_convexity (GskPath *self);
+
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_foreach (GskPath *self,
GskPathForeachFlags flags,
diff --git a/gsk/meson.build b/gsk/meson.build
index cf255c88c4..b4be720e05 100644
--- a/gsk/meson.build
+++ b/gsk/meson.build
@@ -44,6 +44,7 @@ gsk_private_sources = files([
'gskboundingbox.c',
'gskcairoblur.c',
'gskcontour.c',
+ 'gskconvexity.c',
'gskcurve.c',
'gskcurveintersect.c',
'gskdebug.c',
diff --git a/testsuite/gsk/path.c b/testsuite/gsk/path.c
index 60e45a0941..129d3fbeec 100644
--- a/testsuite/gsk/path.c
+++ b/testsuite/gsk/path.c
@@ -45,7 +45,7 @@ create_random_degenerate_path (guint max_contours)
case 1:
/* a single point */
- gsk_path_builder_move_to (builder,
+ gsk_path_builder_move_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
break;
@@ -54,7 +54,7 @@ create_random_degenerate_path (guint max_contours)
/* N points */
for (i = 0; i < MIN (10, max_contours); i++)
{
- gsk_path_builder_move_to (builder,
+ gsk_path_builder_move_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
}
@@ -62,7 +62,7 @@ create_random_degenerate_path (guint max_contours)
case 3:
/* 1 closed point */
- gsk_path_builder_move_to (builder,
+ gsk_path_builder_move_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
gsk_path_builder_close (builder);
@@ -70,7 +70,7 @@ create_random_degenerate_path (guint max_contours)
case 4:
/* the same point closed N times */
- gsk_path_builder_move_to (builder,
+ gsk_path_builder_move_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
for (i = 0; i < MIN (10, max_contours); i++)
@@ -440,7 +440,7 @@ path_operation_equal (const PathOperation *p1,
}
}
-static gboolean
+static gboolean
collect_path_operation_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
@@ -1115,7 +1115,7 @@ test_in_fill_rotated (void)
GskFillRule fill_rule = g_random_int_range (0, N_FILL_RULES);
float x = g_test_rand_double_range (-1000, 1000);
float y = g_test_rand_double_range (-1000, 1000);
-
+
g_assert_cmpint (gsk_path_measure_in_fill (measures[0], &GRAPHENE_POINT_INIT (x, y), fill_rule),
==,
gsk_path_measure_in_fill (measures[1], &GRAPHENE_POINT_INIT (y, -x), fill_rule));
@@ -1229,6 +1229,55 @@ test_path_stroke_distance (void)
gsk_stroke_free (stroke);
}
+static void
+test_path_convexity (void)
+{
+ GskPathBuilder *builder;
+ GskPath *path;
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 100);
+ path = gsk_path_builder_free_to_path (builder);
+
+ g_assert_true (gsk_path_get_convexity (path) == GSK_CONVEXITY_CONVEX);
+ g_assert_true (gsk_path_compute_convexity (path) == GSK_CONVEXITY_CONVEX);
+
+ gsk_path_unref (path);
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 100);
+ gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 10);
+ path = gsk_path_builder_free_to_path (builder);
+
+ g_assert_true (gsk_path_get_convexity (path) == GSK_CONVEXITY_CONCAVE);
+ g_assert_true (gsk_path_compute_convexity (path) == GSK_CONVEXITY_CONCAVE);
+
+ gsk_path_unref (path);
+
+ builder = gsk_path_builder_new ();
+ gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (9, 0, 100, 100));
+ path = gsk_path_builder_free_to_path (builder);
+
+ g_assert_true (gsk_path_get_convexity (path) == GSK_CONVEXITY_CONVEX);
+ g_assert_true (gsk_path_compute_convexity (path) == GSK_CONVEXITY_CONVEX);
+
+ gsk_path_unref (path);
+
+ path = gsk_path_parse ("M 100 100 C 150 50 200 50 250 100 C 200 150 150 150 100 100 Z");
+
+ g_assert_true (gsk_path_get_convexity (path) == GSK_CONVEXITY_UNKNOWN);
+ g_assert_true (gsk_path_compute_convexity (path) == GSK_CONVEXITY_CONVEX);
+
+ gsk_path_unref (path);
+
+ path = gsk_path_parse ("M 100 100 L 150 50 L 200 100 L 250 50 L 300 100 L 200 200 Z");
+
+ g_assert_true (gsk_path_get_convexity (path) == GSK_CONVEXITY_UNKNOWN);
+ g_assert_true (gsk_path_compute_convexity (path) == GSK_CONVEXITY_CONCAVE);
+
+ gsk_path_unref (path);
+}
+
int
main (int argc,
char *argv[])
@@ -1247,6 +1296,7 @@ main (int argc,
g_test_add_func ("/path/in-fill-union", test_in_fill_union);
g_test_add_func ("/path/in-fill-rotated", test_in_fill_rotated);
g_test_add_func ("/path/stroke/distance", test_path_stroke_distance);
+ g_test_add_func ("/path/convexity", test_path_convexity);
return g_test_run ();
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]