Commits

Lenard Lindstrom committed 419b074

error check pygame.freetype.Font scalable font properties (toward Issue #75)

The *rotation*, *oblique*, and *strong* font transformations are only
available to scalable (outline) fonts. Make these properties read-only
for a bitmap font. Also check the corresponding *Font* method arguments.

Comments (0)

Files changed (7)

docs/reST/ref/freetype.rst

 
       The rendering is done using the font's default size in points and its
       default style, without any rotation, and taking into account fonts which
-      are set to be drawn vertically via the :meth:`Font.vertical` attribute.
+      are set to be drawn vertically via the :attr:`vertical` attribute.
       Optionally you may specify another point size to use via the 'size'
       argument, a text rotation via the 'rotation' argument, or a new text
-      style via the 'style' argument.
+      style via the 'style' argument. See the attr :attr:`size`,
+      :attr:`rotation`, and :attr:`style` attributes.
 
       If text is a char (byte) string, then its encoding is assumed to be
       ``LATIN1``.
       Optionally, the default style can be modified or obtained accessing the
       individual style attributes (underline, oblique, strong).
 
+      The ``STYLE_OBLIQUE`` and ``STYLE_STRONG`` styles are for scalable fonts
+      only. An attempt to set either for a bitmap font raises an AttributeError.
+      An attempt to set either for an inactive font, as returned by
+      ``Font.__new__()``, raises a RuntimeError.
+
    .. attribute:: underline
 
       | :sl:`The state of the font's underline style flag`
       calculations unless overridden specifically in the \`render()` or
       \`get_size()` calls, via the 'style' parameter.
 
+      The oblique style is only supported for scalable (outline) fonts.
+      An attempt to set this property will raise an AttributeError.
+      If the font object is inactive, as returned by Font.__new__,
+      setting this property raises a RuntimeError.
+
    .. attribute:: wide
 
       | :sl:`The state of the font's wide style flag`
       dimensions are enlarged. A wide style of strength 1/12 is
       equivalent to the font.Font bold style. The default is 1/36.
 
+      The strength style is only supported for scalable (outline) fonts.
+      An attempt to set this property will raise an AttributeError.
+      If the font object is inactive, as returned by Font.__new__,
+      setting this property raises a RuntimeError.
+
    .. attribute:: underline_adjustment
 
       | :sl:`Adjustment factor for the underline position`
       are used. Setting ``False`` disables the loading of any bitmap strike.
       Setting ``True``, the default value, allows bitmap strikes for an
       unrotated render when no style other than :attr:`wide` or
-      :attr:`underline` is set. This property has no effect on bitmap files.
+      :attr:`underline` is set. This property has no effect on bitmap fonts.
 
       See also :attr:`fixed_sizes` and :meth:`get_sizes`.
 
       corresponding angle within the range (eg. 390 -> 390 - 360 -> 30,
       -45 -> 360 + -45 -> 315, 720 -> 720 - (2 * 360) -> 0).
 
+      Text rotation is only supported for scalable (outline) fonts. An attempt
+      to change the rotation of a bitmap font raises an AttributeError.
+      An attempt to change the rotation of an inactive font objects, as
+      returned by Font.__new__(), raises a RuntimeError.
+
    .. attribute:: origin
 
       | :sl:`Font render to text origin mode`
         obj->id.open_args.pathname = 0;
         obj->path = 0;
         obj->resolution = 0;
+        obj->is_scalable = 0;
         obj->_internals = 0;
         obj->face_size = 0;
         obj->style = FT_STYLE_NORMAL;
     _PGFT_UnloadFont(ft, self);
     Py_XDECREF(self->path);
     self->path = 0;
+    self->is_scalable = 0;
 
     self->face_size = face_size;
     if (ucs4) {
         return -1;
     }
 
+    if ((style_flag & FT_STYLES_SCALABLE_ONLY) && !self->is_scalable) {
+        if (PgFont_IS_ALIVE(self)) {
+            PyErr_SetString(PyExc_AttributeError,
+                            "this style is unsupported for a bitmap font");
+        }
+        else {
+            PyErr_SetString(PyExc_RuntimeError,
+                            MODULE_NAME "." FONT_TYPE_NAME
+                            " instance is not initialized");
+        }
+        return -1;
+    }
     if (PyObject_IsTrue(value)) {
         self->style |= (FT_UInt16)style_flag;
     }
                      "Invalid style value %x", (int)style);
         return -1;
     }
+    if ((style & FT_STYLES_SCALABLE_ONLY) && !self->is_scalable) {
+        if (PgFont_IS_ALIVE(self)) {
+            PyErr_SetString(PyExc_AttributeError,
+                            "this style is unsupported for a bitmap font");
+        }
+        else {
+            PyErr_SetString(PyExc_RuntimeError,
+                            MODULE_NAME "." FONT_TYPE_NAME
+                            " instance is not initialized");
+        }
+        return -1;
+    }
 
     self->style = (FT_UInt16)style;
     return 0;
 static PyObject *
 _ftfont_getscalable(PgFontObject *self, void *closure)
 {
-    FreeTypeInstance *ft;
-    long scalable;
-    ASSERT_GRAB_FREETYPE(ft, 0);
-
-    ASSERT_SELF_IS_ALIVE(self);
-    scalable = _PGFT_Font_IsScalable(ft, self);
-    return scalable >= 0 ? PyBool_FromLong(scalable) : 0;
+    ASSERT_SELF_IS_ALIVE(self)
+    return PyBool_FromLong(self->is_scalable);
 }
 
 static PyObject *
 static int
 _ftfont_setrotation(PgFontObject *self, PyObject *value, void *closure)
 {
+    if (!self->is_scalable) {
+        if (PgFont_IS_ALIVE(self)) {
+            PyErr_SetString(PyExc_AttributeError,
+                            "rotation is unsupported for a bitmap font");
+        }
+        else {
+            PyErr_SetString(PyExc_RuntimeError,
+                            MODULE_NAME "." FONT_TYPE_NAME
+                            " instance is not initialized");
+        }
+        return -1;
+    }
     return obj_to_rotation(value, &self->rotation) ? 0 : -1;
 }
 
     PyObject_HEAD
     PgFontId id;
     PyObject *path;
+    int is_scalable;
 
     Scale_t face_size;
     FT_Int16 style;

src/freetype/ft_render.c

 
         mode->style = (FT_UInt16)style;
     }
+    if ((mode->style & FT_STYLES_SCALABLE_ONLY) && !fontobj->is_scalable ) {
+        PyErr_SetString(PyExc_ValueError,
+                        "Unsupported style(s) for a bitmap font");
+        return -1;
+    }
     mode->strength = DBL_TO_FX16(fontobj->strength);
     mode->underline_adjustment = DBL_TO_FX16(fontobj->underline_adjustment);
     mode->render_flags = fontobj->render_flags;
     mode->transform = fontobj->transform;
 
     if (mode->rotation_angle != 0) {
+        if (!fontobj->is_scalable) {
+            PyErr_SetString(PyExc_ValueError,
+                  "rotated text is unsupported for a bitmap font");
+            return -1;
+        }
         if (mode->style & FT_STYLE_WIDE) {
             PyErr_SetString(PyExc_ValueError,
                   "the wide style is unsupported for rotated text");

src/freetype/ft_wrap.c

  *
  *********************************************************/
 int
-_PGFT_Font_IsScalable(FreeTypeInstance *ft, PgFontObject *fontobj)
-{
-    FT_Face font = _PGFT_GetFont(ft, fontobj);
-
-    if (!font) {
-        RAISE(PyExc_RuntimeError, _PGFT_GetError(ft));
-        return -1;
-    }
-    return FT_IS_SCALABLE(font) ? 1 : 0;
-}
-
-int
 _PGFT_Font_IsFixedWidth(FreeTypeInstance *ft, PgFontObject *fontobj)
 {
     FT_Face font = _PGFT_GetFont(ft, fontobj);
 
 static int init(FreeTypeInstance *ft, PgFontObject *fontobj)
 {
+    FT_Face font;
     fontobj->_internals = 0;
 
-    if (!_PGFT_GetFont(ft, fontobj)) {
+    font = _PGFT_GetFont(ft, fontobj);
+    if (!font) {
         RAISE(PyExc_IOError, _PGFT_GetError(ft));
         return -1;
     }
+    fontobj->is_scalable = FT_IS_SCALABLE(font) ? ~0 : 0;
 
     fontobj->_internals = _PGFT_malloc(sizeof(FontInternals));
     if (!fontobj->_internals) {

src/freetype/ft_wrap.h

 #include "../_pygame.h"
 #include "../freetype.h"
 
+
 /**********************************************************
  * Internal module defines
  **********************************************************/
 
 #define PGFT_DBL_DEFAULT_STRENGTH (1.0 / 36.0)
 
+/* Rendering styles unsupported for bitmap fonts */
+#define FT_STYLES_SCALABLE_ONLY  (FT_STYLE_STRONG | FT_STYLE_OBLIQUE)
+
 /**********************************************************
  * Internal basic types
  **********************************************************/
                                Scale_t);
 long _PGFT_Font_GetGlyphHeightSized(FreeTypeInstance *, PgFontObject *,
                                     Scale_t);
-int _PGFT_Font_IsScalable(FreeTypeInstance *, PgFontObject *);
 int _PGFT_Font_IsFixedWidth(FreeTypeInstance *, PgFontObject *);
 int _PGFT_Font_NumFixedSizes(FreeTypeInstance *, PgFontObject *);
 int _PGFT_Font_GetAvailableSize(FreeTypeInstance *, PgFontObject *, unsigned,

test/freetype_test.py

         finally:
             f.use_bitmap_strikes = True
 
+    def test_freetype_Font_bitmap_files(self):
+        """Ensure bitmap file restrictions are caught"""
+        f = self._TEST_FONTS['bmp']
+        f_null = nullfont()
+        s = pygame.Surface((10, 10), 0, 32)
+        a = s.get_view('3')
+
+        exception = AttributeError
+        self.assertRaises(exception, setattr, f, 'strong', True)
+        self.assertRaises(exception, setattr, f, 'oblique', True)
+        self.assertRaises(exception, setattr, f, 'style', ft.STYLE_STRONG)
+        self.assertRaises(exception, setattr, f, 'style', ft.STYLE_OBLIQUE)
+        exception = RuntimeError
+        self.assertRaises(exception, setattr, f_null, 'strong', True)
+        self.assertRaises(exception, setattr, f_null, 'oblique', True)
+        self.assertRaises(exception, setattr, f_null, 'style', ft.STYLE_STRONG)
+        self.assertRaises(exception, setattr, f_null, 'style', ft.STYLE_OBLIQUE)
+        exception = ValueError
+        self.assertRaises(exception, f.render,
+                          'A', (0, 0, 0), size=8, rotation=1)
+        self.assertRaises(exception, f.render,
+                          'A', (0, 0, 0), size=8, style=ft.STYLE_OBLIQUE)
+        self.assertRaises(exception, f.render,
+                          'A', (0, 0, 0), size=8, style=ft.STYLE_STRONG)
+        self.assertRaises(exception, f.render_raw, 'A', size=8, rotation=1)
+        self.assertRaises(exception, f.render_raw,
+                          'A', size=8, style=ft.STYLE_OBLIQUE)
+        self.assertRaises(exception, f.render_raw,
+                          'A', size=8, style=ft.STYLE_STRONG)
+        self.assertRaises(exception, f.render_to,
+                          s, (0, 0), 'A', (0, 0, 0), size=8, rotation=1)
+        self.assertRaises(exception, f.render_to,
+                          s, (0, 0), 'A', (0, 0, 0), size=8,
+                          style=ft.STYLE_OBLIQUE)
+        self.assertRaises(exception, f.render_to,
+                          s, (0, 0), 'A', (0, 0, 0), size=8,
+                          style=ft.STYLE_STRONG)
+        self.assertRaises(exception, f.render_raw_to,
+                          a, 'A', size=8, rotation=1)
+        self.assertRaises(exception, f.render_raw_to,
+                          a, 'A', size=8, style=ft.STYLE_OBLIQUE)
+        self.assertRaises(exception, f.render_raw_to,
+                          a, 'A', size=8, style=ft.STYLE_STRONG)
+        self.assertRaises(exception, f.get_rect, 'A', size=8, rotation=1)
+        self.assertRaises(exception, f.get_rect,
+                          'A', size=8, style=ft.STYLE_OBLIQUE)
+        self.assertRaises(exception, f.get_rect,
+                          'A', size=8, style=ft.STYLE_STRONG)
+
     def test_freetype_Font_get_metrics(self):
 
         font = self._TEST_FONTS['sans']