Commits

Lenard Lindstrom committed d2feb84

improve pygame.freetype.Font text rotation handling (towards Issue #75)

Allow any Python long value for a *rotation* argument. Add a rotation property
to pygame.freetype.Font.

Comments (0)

Files changed (7)

docs/reST/ref/freetype.rst

       contain the necessary metrics to draw glyphs vertically, so drawing in
       those cases will give unspecified results.
 
+   .. attribute:: rotation
+
+      | :sl:`text rotation in degrees counterclockwise`
+      | :sg:`rotation -> int`
+
+      Get or set the baseline angle of the rendered text. The angle is
+      represented as integer degrees. The default angle is 0, with horizontal
+      text rendered along the X axis, and vertical text along the Y axis.
+      A non-zero value rotates these axes counterclockwise that many degrees.
+      Degree values outside of the range 0 to 359 inclusive are reduced to the
+      corresponding angle within the range (eg. 390 -> 390 - 360 -> 30,
+      -45 -> 360 + -45 -> 315, 720 -> 720 - (2 * 360) -> 0).
+
    .. attribute:: origin
 
       | :sl:`Font render to text origin mode`
 static int _ftfont_setstrength(PgFontObject *, PyObject *, void *);
 static PyObject *_ftfont_getunderlineadjustment(PgFontObject *, void *);
 static int _ftfont_setunderlineadjustment(PgFontObject *, PyObject *, void *);
+static PyObject *_ftfont_getrotation(PgFontObject *, void *);
+static int _ftfont_setrotation(PgFontObject *, PyObject *, void *);
 
 static PyObject *_ftfont_getresolution(PgFontObject *, void *);
 
 static PyObject *load_font_res(const char *);
 static int parse_dest(PyObject *, int *, int *);
 static int obj_to_scale(PyObject *, void *);
+static int obj_to_rotation(PyObject *, void *);
 
 /*
  * Auxiliar defines
     return 0;
 }
 
-/* Point size PyArg_ParseTuple converter: int -> Scale_t */
+/** Point size PyArg_ParseTuple converter: int -> Scale_t */
 static int
 obj_to_scale(PyObject *o, void *p)
 {
     return rval;
 }
 
+/** rotation: int -> Angle_t */
+int
+obj_to_rotation(PyObject *o, void *p)
+{
+    PyObject *full_circle_obj = 0;
+    PyObject *angle_obj = 0;
+    long angle;
+    int rval = 0;
+
+    if (PyLong_Check(o)) {
+        ;
+    }
+#if PY2
+    else if (PyInt_Check(o)) {
+        ;
+    }
+#endif
+    else {
+        PyErr_Format(PyExc_TypeError, "integer rotation expected, got %s",
+                     Py_TYPE(o)->tp_name);
+        goto finish;
+    }
+    full_circle_obj = PyLong_FromLong(360L);
+    if (!full_circle_obj) goto finish;
+    angle_obj = PyNumber_Remainder(o, full_circle_obj);
+    if (!angle_obj) goto finish;
+    angle = PyLong_AsLong(angle_obj);
+    if (angle == -1) goto finish;
+    *(Scale_t *)p = (Scale_t)INT_TO_FX16(angle);
+    rval = 1;
+
+  finish:
+    Py_XDECREF(full_circle_obj);
+    Py_XDECREF(angle_obj);
+    return rval;
+}
+
 /*
  * FREETYPE MODULE METHODS TABLE
  */
         0
     },
     {
+        "rotation",
+        (getter)_ftfont_getrotation,
+        (setter)_ftfont_setrotation,
+        DOC_FONTROTATION,
+        0
+    },
+    {
         "origin",
         (getter)_ftfont_getrender_flag,
         (setter)_ftfont_setrender_flag,
         obj->render_flags = FT_RFLAG_DEFAULTS;
         obj->strength = PGFT_DBL_DEFAULT_STRENGTH;
         obj->underline_adjustment = 1.0;
+        obj->rotation = 0;
         obj->transform.xx = FX16_ONE;
         obj->transform.xy = 0;
         obj->transform.yx = 0;
 }
 
 
+/** text rotation attribute */
+static PyObject *
+_ftfont_getrotation(PgFontObject *self, void *closure)
+{
+    return PyLong_FromLong((long)FX16_ROUND_TO_INT(self->rotation));
+}
+
+static int
+_ftfont_setrotation(PgFontObject *self, PyObject *value, void *closure)
+{
+    return obj_to_rotation(value, &self->rotation) ? 0 : -1;
+}
+
 /** testing and debugging */
 #if defined(PGFT_DEBUG_CACHE)
 static PyObject *
     SDL_Rect r;
 
     FontRenderMode render;
-    int rotation = 0;
+    Angle_t rotation = self->rotation;
     int style = 0;
 
     FreeTypeInstance *ft;
     ASSERT_GRAB_FREETYPE(ft, 0);
 
-    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iiO&", kwlist,
-                                     &textobj, &style, &rotation,
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iO&O&", kwlist,
+                                     &textobj, &style,
+                                     obj_to_rotation, (void *)&rotation,
                                      obj_to_scale, (void *)&face_size)) {
         return 0;
     }
     PyObject *textobj;
     PGFT_String *text;
     int style = FT_STYLE_DEFAULT;
-    int rotation = 0;
+    Angle_t rotation = self->rotation;
     Scale_t face_size = 0;
     int invert = 0;
 
     FreeTypeInstance *ft;
     ASSERT_GRAB_FREETYPE(ft, 0);
 
-    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iiO&i", kwlist,
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iO&O&i", kwlist,
                                      &textobj,
-                                     &style, &rotation,
+                                     &style,
+                                     obj_to_rotation, (void *)&rotation,
                                      obj_to_scale, (void *)&face_size,
                                      &invert)) {
         return 0;
     int xpos = 0;
     int ypos = 0;
     int style = FT_STYLE_DEFAULT;
-    int rotation = 0;
+    Angle_t rotation = self->rotation;
     Scale_t face_size = 0;
     int invert = 0;
 
     FreeTypeInstance *ft;
     ASSERT_GRAB_FREETYPE(ft, 0);
 
-    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|OiiO&i", kwlist,
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|OiO&O&i", kwlist,
                                      &arrayobj, &textobj,
-                                     &dest, &style, &rotation,
+                                     &dest, &style,
+                                     obj_to_rotation, (void *)&rotation,
                                      obj_to_scale, (void *)&face_size,
                                      &invert)) {
         return 0;
     Scale_t face_size = 0;
     PyObject *fg_color_obj = 0;
     PyObject *bg_color_obj = 0;
-    int rotation = 0;
+    Angle_t rotation = self->rotation;
     int style = FT_STYLE_DEFAULT;
 
     /* output arguments */
     FreeTypeInstance *ft;
     ASSERT_GRAB_FREETYPE(ft, 0);
 
-    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|OiiO&", kwlist,
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|OiO&O&", kwlist,
                                      /* required */
                                      &textobj, &fg_color_obj,
                                      /* optional */
                                      &bg_color_obj, &style,
-                                     &rotation,
+                                     obj_to_rotation, (void *)&rotation,
                                      obj_to_scale, (void *)&face_size)) {
         return 0;
     }
     int ypos = 0;
     PyObject *fg_color_obj = 0;
     PyObject *bg_color_obj = 0;
-    int rotation = 0;
+    Angle_t rotation = self->rotation;
     int style = FT_STYLE_DEFAULT;
     SDL_Surface *surface = 0;
 
     FreeTypeInstance *ft;
     ASSERT_GRAB_FREETYPE(ft, 0);
 
-    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!OOO|OiiO&", kwlist,
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!OOO|OiO&O&", kwlist,
                                      /* required */
                                      &PySurface_Type, &surface_obj, &dest,
                                      &textobj, &fg_color_obj,
                                      /* optional */
                                      &bg_color_obj, &style,
-                                     &rotation,
+                                     obj_to_rotation, (void *)&rotation,
                                      obj_to_scale, (void *)&face_size)) {
         return 0;
     }

src/doc/freetype_doc.h

 
 #define DOC_FONTVERTICAL "vertical -> bool\nFont vertical mode"
 
+#define DOC_FONTROTATION "rotation -> int\ntext rotation in degrees counterclockwise"
+
 #define DOC_FONTORIGIN "origin -> bool\nFont render to text origin mode"
 
 #define DOC_FONTPAD "pad -> bool\npadded boundary mode"
  vertical -> bool
 Font vertical mode
 
+pygame.freetype.Font.rotation
+ rotation -> int
+text rotation in degrees counterclockwise
+
 pygame.freetype.Font.origin
  origin -> bool
 Font render to text origin mode
  **********************************************************/
 
 typedef FT_UInt Scale_t;
+typedef FT_Angle Angle_t;
 
 typedef struct {
     FT_Long font_index;
     double strength;
     double underline_adjustment;
     FT_UInt resolution;
+    Angle_t rotation;
     FT_Matrix transform;
 
     void *_internals;

src/freetype/ft_render.c

 int
 _PGFT_BuildRenderMode(FreeTypeInstance *ft,
                       PgFontObject *fontobj, FontRenderMode *mode,
-                      Scale_t face_size, int style, int rotation)
+                      Scale_t face_size, int style, Angle_t rotation)
 {
-    int angle;
-
     if (face_size == 0) {
         if (fontobj->face_size == 0) {
             PyErr_SetString(PyExc_ValueError,
     mode->strength = DBL_TO_FX16(fontobj->strength);
     mode->underline_adjustment = DBL_TO_FX16(fontobj->underline_adjustment);
     mode->render_flags = fontobj->render_flags;
-    angle = rotation % 360;
-    while (angle < 0) angle += 360;
-    mode->rotation_angle = INT_TO_FX16(angle);
+    mode->rotation_angle = rotation;
     mode->transform = fontobj->transform;
 
     if (mode->rotation_angle != 0) {

src/freetype/ft_wrap.h

 #define FX6_ROUND(x) (((x) + 32L) & -64L)
 #define FX6_TRUNC(x)  ((x) >> 6)
 #define FX16_CEIL_TO_FX6(x) (((x) + 1023L) >> 10)
+#define FX16_ROUND_TO_INT(x) (((x) + 32768L) >> 16)
 #define INT_TO_FX6(i) ((FT_Fixed)((i) << 6))
 #define INT_TO_FX16(i) ((FT_Fixed)((i) << 16))
 #define FX16_TO_DBL(x) ((x) * 1.52587890625e-5 /* 2.0^-16 */)
                        const FontRenderMode *, PyObject *,
                        PGFT_String *, int, int, int, SDL_Rect *);
 int _PGFT_BuildRenderMode(FreeTypeInstance *, PgFontObject *,
-                          FontRenderMode *, Scale_t, int, int);
+                          FontRenderMode *, Scale_t, int, Angle_t);
 int _PGFT_CheckStyle(FT_UInt32);
 
 

test/freetype_test.py

                           (max_point_size + 1))
         self.assertRaises(TypeError, setattr, f, 'size', 0.0)
 
+    def test_freetype_Font_rotation(self):
+
+        test_angles = [(30, 30),
+                       (360, 0), (390, 30),
+                       (720, 0), (764, 44),
+                       (-30, 330),
+                       (-360, 0), (-390, 330),
+                       (-720, 0), (-764, 316)]
+
+        f = ft.Font(None)
+        self.assertEqual(f.rotation, 0)
+        for r, r_reduced in test_angles:
+            f.rotation = r
+            self.assertEqual(f.rotation, r_reduced,
+                             "for angle %d: %d != %d" %
+                             (r, f.rotation, r_reduced))
+        self.assertRaises(TypeError, setattr, f, 'rotation', '12')
+
     def test_freetype_Font_render_to(self):
         # Rendering to an existing target surface is equivalent to
         # blitting a surface returned by Font.render with the target.