[dia] [transform] text rotation for "Standard - Text"



commit ca1bbf458aec08657c8e687db0c5b33ea8a26343
Author: Hans Breuer <hans breuer org>
Date:   Thu Oct 9 19:16:19 2014 +0200

    [transform] text rotation for "Standard - Text"
    
    Rotations is done around the text handle position. This position is
    only known to the object implementation. There is new render API
    draw_rotated_text() taking the rotation center an angle, which is
    much less invasive than making the underlying Text object aware of
    angle and vertical alignment (and than not using it almost anywhere).
    It is assumed that free text rotation will only be directly supported
    by very few objects, so the burden should not be propagated to all
    objects with text.
    The initial version is should be working for almost all renderers
    due to the default implementation of DiaRenderer::draw_rotated_text()
    converting the text to path and rotating that.

 app/object_ops.c           |    2 +-
 lib/diarenderer.c          |  110 ++++++++++++++++++++++++++++++++-
 lib/diarenderer.h          |    3 +
 lib/libdia.def             |    1 +
 lib/text.c                 |    6 ++
 lib/text.h                 |    1 +
 objects/standard/textobj.c |  147 ++++++++++++++++++++++++++++++++++++++++++--
 samples/text-rotate.dia    |  Bin 0 -> 8960 bytes
 8 files changed, 262 insertions(+), 8 deletions(-)
---
diff --git a/app/object_ops.c b/app/object_ops.c
index 8c0d3c8..3d4f049 100644
--- a/app/object_ops.c
+++ b/app/object_ops.c
@@ -37,7 +37,7 @@ object_add_updates(DiaObject *obj, Diagram *dia)
 
   /* Bounding box */
   if (data_object_get_highlight(dia->data,obj) != DIA_HIGHLIGHT_NONE) {
-    diagram_add_update_with_border(dia, &obj->bounding_box, 5);
+    diagram_add_update_with_border(dia, dia_object_get_enclosing_box (obj), 5);
   } else {
     diagram_add_update(dia, dia_object_get_enclosing_box (obj));
   }
diff --git a/lib/diarenderer.c b/lib/diarenderer.c
index 3a2aba8..ea1ebf4 100644
--- a/lib/diarenderer.c
+++ b/lib/diarenderer.c
@@ -19,14 +19,15 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
-
+#include <config.h>
 
 #include "diarenderer.h"
 #include "object.h"
 #include "text.h"
 #include "textline.h"
 #include "diatransformrenderer.h"
-
+#include "standard-path.h" /* text_to_path */
+#include "boundingbox.h" /* PolyBBextra */
 /*
  * redefinition of isnan, for portability, as explained in :
  * http://www.gnu.org/software/autoconf/manual/html_node/Function-Portability.html
@@ -105,6 +106,8 @@ static void draw_text  (DiaRenderer *renderer,
                         Text *text);
 static void draw_text_line  (DiaRenderer *renderer,
                             TextLine *text_line, Point *pos, Alignment alignment, Color *color);
+static void draw_rotated_text (DiaRenderer *renderer, Text *text,
+                              Point *center, real angle);
 
 static void draw_polyline (DiaRenderer *renderer,
                            Point *points, int num_points,
@@ -335,6 +338,7 @@ dia_renderer_class_init (DiaRendererClass *klass)
   renderer_class->draw_polyline  = draw_polyline;
   renderer_class->draw_text      = draw_text;
   renderer_class->draw_text_line = draw_text_line;
+  renderer_class->draw_rotated_text = draw_rotated_text;
 
   /* highest level functions */
   renderer_class->draw_rounded_rect = draw_rounded_rect;
@@ -602,6 +606,108 @@ draw_text (DiaRenderer *renderer,
 }
 
 /*!
+ * \brief Default implementation to draw rotated text
+ *
+ * The default implementation converts the given _Text object to a
+ * path and passes it to draw_beziergon() if the renderer support
+ * rendering wih holes. If not a fallback implementation is used.
+ *
+ * A Renderer with a good own concept of rotated text should
+ * overwrite it.
+ *
+ * \memberof _DiaRenderer
+ */
+static void
+draw_rotated_text (DiaRenderer *renderer, Text *text,
+                  Point *center, real angle)
+{
+  if (angle == 0.0) {
+    /* maybe the fallback should also consider center? */
+    draw_text (renderer, text);
+  } else {
+    GArray *path = g_array_new (FALSE, FALSE, sizeof(BezPoint));
+    if (!text_is_empty (text) && text_to_path (text, path)) {
+      /* Scaling and transformation here */
+      Rectangle bz_bb, tx_bb;
+      PolyBBExtras extra = { 0, };
+      real sx, sy;
+      guint i;
+      real dx = center ? (text->position.x - center->x) : 0;
+      real dy = center ? (text->position.y - center->y) : 0;
+      DiaMatrix m = { 1, 0, 0, 1, 0, 0 };
+      DiaMatrix t = { 1, 0, 0, 1, 0, 0 };
+
+      polybezier_bbox (&g_array_index (path, BezPoint, 0), path->len, &extra, TRUE, &bz_bb);
+      text_calc_boundingbox (text, &tx_bb);
+      sx = (tx_bb.right - tx_bb.left) / (bz_bb.right - bz_bb.left);
+      sy = (tx_bb.bottom - tx_bb.top) / (bz_bb.bottom - bz_bb.top);
+
+      /* move center to origin */
+      if (ALIGN_LEFT == text->alignment)
+       t.x0 = -bz_bb.left;
+      else if (ALIGN_RIGHT == text->alignment)
+       t.x0 = - bz_bb.right;
+      else
+       t.x0 = -(bz_bb.left + bz_bb.right) / 2.0;
+      t.x0 -= dx / sx;
+      t.y0 = - bz_bb.top - (text_get_ascent (text) - dy) / sy;
+      dia_matrix_set_angle_and_scales (&m, G_PI * angle / 180.0, sx, sx);
+      dia_matrix_multiply (&m, &t, &m);
+      /* move back center from origin */
+      if (ALIGN_LEFT == text->alignment)
+       t.x0 = tx_bb.left;
+      else if (ALIGN_RIGHT == text->alignment)
+       t.x0 = tx_bb.right;
+      else
+       t.x0 = (tx_bb.left + tx_bb.right) / 2.0;
+      t.x0 += dx;
+      t.y0 = tx_bb.top + (text_get_ascent (text) - dy);
+      dia_matrix_multiply (&m, &m, &t);
+
+      for (i = 0; i < path->len; ++i) {
+       BezPoint *bp = &g_array_index (path, BezPoint, i);
+       transform_bezpoint (bp, &m);
+      }
+
+      if (DIA_RENDERER_GET_CLASS (renderer)->is_capable_to(renderer, RENDER_HOLES))
+       DIA_RENDERER_GET_CLASS (renderer)->draw_beziergon(renderer,
+                                                         &g_array_index (path, BezPoint, 0),
+                                                         path->len,
+                                                         &text->color, NULL);
+      else
+       bezier_render_fill (renderer,
+                           &g_array_index (path, BezPoint, 0), path->len,
+                           &text->color);
+    } else {
+      Color magenta = { 1.0, 0.0, 1.0, 1.0 };
+      Point pt = center ? *center : text->position;
+      DiaMatrix m = { 1, 0, 0, 1, pt.x, pt.y };
+      DiaMatrix t = { 1, 0, 0, 1, -pt.x, -pt.y };
+      Rectangle tb;
+      Point poly[4];
+      int i;
+
+      text_calc_boundingbox (text, &tb);
+      poly[0].x = tb.left;  poly[0].y = tb.top;
+      poly[1].x = tb.right; poly[1].y = tb.top;
+      poly[2].x = tb.right; poly[2].y = tb.bottom;
+      poly[3].x = tb.left;  poly[3].y = tb.bottom;
+
+      dia_matrix_set_angle_and_scales (&m, G_PI * angle / 180.0, 1.0, 1.0);
+      dia_matrix_multiply (&m, &t, &m);
+
+      for (i = 0; i < 4; ++i)
+       transform_point (&poly[i], &m);
+
+      DIA_RENDERER_GET_CLASS (renderer)->set_linewidth (renderer, 0.0);
+      DIA_RENDERER_GET_CLASS (renderer)->draw_polygon (renderer, poly, 4,
+                                                      NULL, &magenta);
+    }
+    g_array_free (path, TRUE);
+  }
+}
+
+/*!
  * \brief Default implementation of draw_text_line
  *
  * The default implementation of draw_text_line() just calls set_font() and
diff --git a/lib/diarenderer.h b/lib/diarenderer.h
index af2a800..4ee93a2 100644
--- a/lib/diarenderer.h
+++ b/lib/diarenderer.h
@@ -252,6 +252,9 @@ struct _DiaRendererClass
                             RenderCapability cap);
   /*! fill with a pattern, currently only gradient */
   void (*set_pattern) (DiaRenderer *renderer, DiaPattern *pat);
+  /*! draw text rotated around center with given angle in degrees */
+  void (*draw_rotated_text) (DiaRenderer *renderer, Text *text,
+                            Point *center, real angle);
 };
 
 /*
diff --git a/lib/libdia.def b/lib/libdia.def
index 26026dc..fe27357 100644
--- a/lib/libdia.def
+++ b/lib/libdia.def
@@ -744,6 +744,7 @@ EXPORTS
  text_get_ascent
  text_get_attributes
  text_get_descent
+ text_get_height
  text_get_line
  text_get_line_strlen
  text_get_line_width
diff --git a/lib/text.c b/lib/text.c
index 050189f..3d8684b 100644
--- a/lib/text.c
+++ b/lib/text.c
@@ -392,6 +392,12 @@ text_set_height(Text *text, real height)
   calc_ascent_descent(text);
 }
 
+real
+text_get_height(const Text *text)
+{
+  return text->height;
+}
+
 void
 text_set_font(Text *text, DiaFont *font)
 {
diff --git a/lib/text.h b/lib/text.h
index 368cff8..e8bf817 100644
--- a/lib/text.h
+++ b/lib/text.h
@@ -77,6 +77,7 @@ gchar *text_get_line(const Text *text, int line);
 char *text_get_string_copy(const Text *text);
 void text_set_string(Text *text, const char *string);
 void text_set_height(Text *text, real height);
+real text_get_height(const Text *text);
 void text_set_font(Text *text, DiaFont *font);
 void text_set_position(Text *text, Point *pos);
 void text_set_color(Text *text, Color *col);
diff --git a/objects/standard/textobj.c b/objects/standard/textobj.c
index a0a3d25..37eb0f2 100644
--- a/objects/standard/textobj.c
+++ b/objects/standard/textobj.c
@@ -33,6 +33,7 @@
 #include "properties.h"
 #include "diamenu.h"
 #include "create.h"
+#include "message.h" /* just dia_log_message */
 
 #include "tool-icons.h"
 
@@ -66,6 +67,8 @@ struct _Textobj {
   gboolean show_background;
   /*! margin used for background drawing and connection point placement */
   real margin;
+  /*! rotation around text handle position */
+  real text_angle;
 };
 
 static struct _TextobjProperties {
@@ -86,6 +89,7 @@ static DiaObject *textobj_create(Point *startpoint,
                              void *user_data,
                              Handle **handle1,
                              Handle **handle2);
+static DiaObject *textobj_copy(Textobj *textobj);
 static void textobj_destroy(Textobj *textobj);
 
 static void textobj_get_props(Textobj *textobj, GPtrArray *props);
@@ -95,6 +99,7 @@ static void textobj_save(Textobj *textobj, ObjectNode obj_node,
                         DiaContext *ctx);
 static DiaObject *textobj_load(ObjectNode obj_node, int version, DiaContext *ctx);
 static DiaMenu *textobj_get_object_menu(Textobj *textobj, Point *clickedpoint);
+static gboolean textobj_transform(Textobj *textobj, const DiaMatrix *m);
 
 static void textobj_valign_point(Textobj *textobj, Point* p);
 
@@ -115,6 +120,7 @@ PropEnumData prop_text_vert_align_data[] = {
   { NULL, 0 }
 };
 static PropNumData text_margin_range = { 0.0, 10.0, 0.1 };
+static PropNumData text_angle_range = { -180.0, 180.0, 5.0 };
 static PropDescription textobj_props[] = {
   OBJECT_COMMON_PROPERTIES,
   PROP_STD_TEXT_ALIGNMENT,
@@ -123,6 +129,8 @@ static PropDescription textobj_props[] = {
   PROP_STD_TEXT_FONT,
   PROP_STD_TEXT_HEIGHT,
   PROP_STD_TEXT_COLOUR,
+  { "text_angle", PROP_TYPE_REAL, PROP_FLAG_VISIBLE | PROP_FLAG_OPTIONAL,
+    N_("Text angle"), NULL, &text_angle_range },
   PROP_STD_SAVED_TEXT,
   PROP_STD_FILL_COLOUR_OPTIONAL,
   PROP_STD_SHOW_BACKGROUND_OPTIONAL,
@@ -138,6 +146,7 @@ static PropOffset textobj_offsets[] = {
   {PROP_STDNAME_TEXT_HEIGHT,PROP_STDTYPE_TEXT_HEIGHT,offsetof(Textobj,text),offsetof(Text,height)},
   {"text_colour",PROP_TYPE_COLOUR,offsetof(Textobj,text),offsetof(Text,color)},
   {"text_alignment",PROP_TYPE_ENUM,offsetof(Textobj,text),offsetof(Text,alignment)},
+  {"text_angle",PROP_TYPE_REAL,offsetof(Textobj,text_angle)},
   {"text_vert_alignment",PROP_TYPE_ENUM,offsetof(Textobj,vert_align)},
   { "fill_colour", PROP_TYPE_COLOUR, offsetof(Textobj, fill_color) },
   { "show_background", PROP_TYPE_BOOL, offsetof(Textobj, show_background) },
@@ -168,7 +177,7 @@ static ObjectOps textobj_ops = {
   (DrawFunc)            textobj_draw,
   (DistanceFunc)        textobj_distance_from,
   (SelectFunc)          textobj_select,
-  (CopyFunc)            object_copy_using_properties,
+  (CopyFunc)            textobj_copy,
   (MoveFunc)            textobj_move,
   (MoveHandleFunc)      textobj_move_handle,
   (GetPropertiesFunc)   object_create_props_dialog,
@@ -179,6 +188,7 @@ static ObjectOps textobj_ops = {
   (SetPropsFunc)        textobj_set_props,
   (TextEditFunc) 0,
   (ApplyPropertiesListFunc) object_apply_props,
+  (TransformFunc)       textobj_transform,
 };
 
 static void
@@ -194,9 +204,47 @@ textobj_set_props(Textobj *textobj, GPtrArray *props)
   textobj_update_data(textobj);
 }
 
+static void
+_textobj_get_poly (const Textobj *textobj, Point poly[4])
+{
+  Point ul, lr;
+  Point pt = textobj->text_handle.pos;
+  Rectangle box;
+  DiaMatrix m = { 1, 0, 0, 1, pt.x, pt.y };
+  DiaMatrix t = { 1, 0, 0, 1, -pt.x, -pt.y };
+  int i;
+
+  dia_matrix_set_angle_and_scales (&m, G_PI * textobj->text_angle / 180.0, 1.0, 1.0);
+  dia_matrix_multiply (&m, &t, &m);
+
+  text_calc_boundingbox (textobj->text, &box);
+  ul.x = box.left - textobj->margin;
+  ul.y = box.top - textobj->margin;
+  lr.x = box.right + textobj->margin;
+  lr.y = box.bottom + textobj->margin;
+
+  poly[0].x = ul.x;
+  poly[0].y = ul.y;
+  poly[1].x = lr.x;
+  poly[1].y = ul.y;
+  poly[2].x = lr.x;
+  poly[2].y = lr.y;
+  poly[3].x = ul.x;
+  poly[3].y = lr.y;
+
+  for (i = 0; i < 4; ++i)
+    transform_point (&poly[i], &m);
+}
+
 static real
 textobj_distance_from(Textobj *textobj, Point *point)
 {
+  if (textobj->text_angle != 0) {
+    Point poly[4];
+
+    _textobj_get_poly (textobj, poly);
+    return distance_polygon_point(poly, 4, 0.0, point);
+  }
   if (textobj->show_background)
     return distance_rectangle_point(&textobj->object.bounding_box, point);
   return text_distance_from(textobj->text, point); 
@@ -250,9 +298,29 @@ textobj_draw(Textobj *textobj, DiaRenderer *renderer)
     ul.y = box.top - textobj->margin;
     lr.x = box.right + textobj->margin;
     lr.y = box.bottom + textobj->margin;
-    DIA_RENDERER_GET_CLASS (renderer)->draw_rect (renderer, &ul, &lr, &textobj->fill_color, NULL);
+    if (textobj->text_angle == 0) {
+      DIA_RENDERER_GET_CLASS (renderer)->draw_rect (renderer, &ul, &lr, &textobj->fill_color, NULL);
+    } else {
+      Point poly[4];
+
+      _textobj_get_poly (textobj, poly);
+      DIA_RENDERER_GET_CLASS (renderer)->draw_polygon (renderer, poly, 4, &textobj->fill_color, NULL);
+    }
+  }
+  if (textobj->text_angle == 0) {
+    text_draw(textobj->text, renderer);
+  } else {
+    DIA_RENDERER_GET_CLASS (renderer)->draw_rotated_text (renderer, textobj->text,
+                                                         &textobj->text_handle.pos, textobj->text_angle);
+    /* XXX: interactive case not working correctly */
+    if (renderer->is_interactive &&
+        dia_object_is_selected(&textobj->object) &&
+       textobj->text->focus.has_focus) {
+      /* editing is not rotated */
+      text_draw(textobj->text, renderer);
+    }
+
   }
-  text_draw(textobj->text, renderer);
 }
 
 static void
@@ -282,6 +350,7 @@ textobj_update_data(Textobj *textobj)
 {
   Point to2;
   DiaObject *obj = &textobj->object;
+  Rectangle tx_bb;
   
   text_set_position(textobj->text, &obj->position);
   text_calc_boundingbox(textobj->text, &obj->bounding_box);
@@ -298,7 +367,9 @@ textobj_update_data(Textobj *textobj)
   else if (ALIGN_RIGHT == textobj->text->alignment)
     to2.x -= textobj->margin; /* left */
   text_set_position(textobj->text, &to2);
-  text_calc_boundingbox(textobj->text, &obj->bounding_box);
+
+  /* always use the unrotated box ... */
+  text_calc_boundingbox(textobj->text, &tx_bb);
   /* grow the bounding box by 2x margin */
   obj->bounding_box.top    -= textobj->margin;
   obj->bounding_box.left   -= textobj->margin;
@@ -306,6 +377,28 @@ textobj_update_data(Textobj *textobj)
   obj->bounding_box.right  += textobj->margin;
 
   textobj->text_handle.pos = obj->position;
+  if (textobj->text_angle == 0) {
+    obj->bounding_box = tx_bb;
+    g_return_if_fail (obj->enclosing_box != NULL);
+    *obj->enclosing_box = tx_bb;
+  } else {
+    /* ... and grow it when necessary */
+    Point poly[4];
+    int i;
+
+    _textobj_get_poly (textobj, poly);
+    /* we don't want the joined box for boundingbox because
+     * selection would become too greedy than.
+     */
+    obj->bounding_box.left = obj->bounding_box.right = poly[0].x;
+    obj->bounding_box.top = obj->bounding_box.bottom = poly[0].y;
+    for (i = 1; i < 4; ++i)
+      rectangle_add_point (&obj->bounding_box, &poly[i]);
+    g_return_if_fail (obj->enclosing_box != NULL);
+    *obj->enclosing_box = obj->bounding_box;
+    /* join for editing/selection bbox */
+    rectangle_union (obj->enclosing_box, &tx_bb);
+  }
 }
 
 static DiaObject *
@@ -322,7 +415,8 @@ textobj_create(Point *startpoint,
   
   textobj = g_malloc0(sizeof(Textobj));
   obj = &textobj->object;
-  
+  obj->enclosing_box = g_new0 (Rectangle, 1);
+
   obj->type = &textobj_type;
 
   obj->ops = &textobj_ops;
@@ -358,10 +452,21 @@ textobj_create(Point *startpoint,
   return &textobj->object;
 }
 
+static DiaObject *
+textobj_copy(Textobj *textobj)
+{
+  Textobj *copied = (Textobj *)object_copy_using_properties(&textobj->object);
+  copied->object.enclosing_box = g_new (Rectangle, 1);
+  *copied->object.enclosing_box = *textobj->object.enclosing_box;
+  return &copied->object;
+}
+
 static void
 textobj_destroy(Textobj *textobj)
 {
   text_destroy(textobj->text);
+  g_free (textobj->object.enclosing_box);
+  textobj->object.enclosing_box = NULL;
   object_destroy(&textobj->object);
 }
 
@@ -381,6 +486,8 @@ textobj_save(Textobj *textobj, ObjectNode obj_node, DiaContext *ctx)
   }
   if (textobj->margin > 0.0)
     data_add_real(new_attribute(obj_node, "margin"), textobj->margin, ctx);
+  if (textobj->text_angle != 0.0)
+    data_add_real(new_attribute(obj_node, "text_angle"), textobj->text_angle, ctx);
 }
 
 static DiaObject *
@@ -393,6 +500,7 @@ textobj_load(ObjectNode obj_node, int version, DiaContext *ctx)
 
   textobj = g_malloc0(sizeof(Textobj));
   obj = &textobj->object;
+  obj->enclosing_box = g_new0(Rectangle,1);
   
   obj->type = &textobj_type;
   obj->ops = &textobj_ops;
@@ -415,6 +523,9 @@ textobj_load(ObjectNode obj_node, int version, DiaContext *ctx)
   else if (version == 0) {
     textobj->vert_align = VALIGN_FIRST_LINE;
   }
+  attr = object_find_attribute(obj_node, "text_angle");
+  if (attr != NULL)
+    textobj->text_angle = data_real(attribute_first_data(attr), ctx);
 
   /* default visibility must be off to keep compatibility */
   textobj->fill_color = attributes_get_background();
@@ -489,3 +600,29 @@ textobj_get_object_menu(Textobj *textobj, Point *clickedpoint)
 
   return &textobj_menu;
 }
+static gboolean
+textobj_transform(Textobj *textobj, const DiaMatrix *m)
+{
+  real a, sx, sy;
+
+  g_return_val_if_fail(m != NULL, FALSE);
+
+  if (!dia_matrix_get_angle_and_scales (m, &a, &sx, &sy)) {
+    dia_log_message ("textobj_transform() can't convert given matrix");
+    return FALSE;
+  } else {
+    /* XXX: what to do if width!=height */
+    real height = text_get_height (textobj->text) * MIN(sx,sy);
+    real angle = a*180/G_PI;
+    Point p = textobj->object.position;
+
+    /* rotation is invariant to the handle position */
+    transform_point (&p, m);
+    text_set_height (textobj->text, height);
+    textobj->text_angle = angle;
+    textobj->object.position = p;
+ }
+
+  textobj_update_data(textobj);
+  return TRUE;
+}
diff --git a/samples/text-rotate.dia b/samples/text-rotate.dia
new file mode 100644
index 0000000..edd0c7e
Binary files /dev/null and b/samples/text-rotate.dia differ


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]