Commits

Anonymous committed 4ac12fc

Redesigned face caching/loading to fix some bugs/performance issues.
Added unit tests for the FreeType font module.

  • Participants
  • Parent commits 01a044b
  • Branches pgreloaded

Comments (0)

Files changed (7)

File src/freetype/ft_font.c

     if ((ft = _get_freetype()) != NULL)
         PGFT_UnloadFont(ft, self);
 
+    free(self->id.open_args.pathname);
+
     ((PyObject*)self)->ob_type->tp_free((PyObject *)self);
 }
 
     if (IsTextObj(file))
     {
         PyObject *tmp;
-        char *filename;
+        char *filename, *filename_alloc;
+        size_t file_len;
 
         if (!UTF8FromObject(file, &filename, &tmp))
         {
             PyErr_SetString(PyExc_ValueError, "Failed to decode file name");
             return -1;
         }
+
+        file_len = strlen(filename);
+        filename_alloc = malloc(file_len + 1);
+
+        strcpy(filename_alloc, filename);
+        filename_alloc[file_len] = 0;
         
         font->id.open_args.flags = FT_OPEN_PATHNAME;
-        font->id.open_args.pathname = filename; 
+        font->id.open_args.pathname = filename_alloc;
         Py_XDECREF(tmp);
     }
     else
 static PyObject*
 _ftfont_repr(PyObject *self)
 {
-    return Text_FromFormat("%s (FreeType %s font)", 
+    return Text_FromUTF8("font");
+/*    return Text_FromFormat("%s (FreeType %s font)", 
         PGFT_Face_GetName((PyFreeTypeFont *)self),
-        PGFT_Face_GetFormat((PyFreeTypeFont *)self));
+        PGFT_Face_GetFormat((PyFreeTypeFont *)self)); */
 }
 
 
 static PyObject*
 _ftfont_getheight(PyObject *self, void *closure)
 {
-    return PyInt_FromLong(PGFT_Face_GetHeight((PyFreeTypeFont *)self));
+    FreeTypeInstance *ft;
+    ASSERT_GRAB_FREETYPE(ft, NULL);
+
+    return PyInt_FromLong(PGFT_Face_GetHeight(ft, (PyFreeTypeFont *)self));
 }
 
 static PyObject*
 _ftfont_getname(PyObject *self, void *closure)
 {
-    return Text_FromUTF8(PGFT_Face_GetName((PyFreeTypeFont *)self));
+    FreeTypeInstance *ft;
+    ASSERT_GRAB_FREETYPE(ft, NULL);
+
+    return Text_FromUTF8(PGFT_Face_GetName(ft, (PyFreeTypeFont *)self));
 }
 
 static PyObject*
 _ftfont_getfixedwidth(PyObject *self, void *closure)
 {
-    return PyBool_FromLong(PGFT_Face_IsFixedWidth((PyFreeTypeFont *)self));
+    FreeTypeInstance *ft;
+    ASSERT_GRAB_FREETYPE(ft, NULL);
+
+    return PyBool_FromLong(PGFT_Face_IsFixedWidth(ft, (PyFreeTypeFont *)self));
 }
 
 
 _ftfont_getmetrics(PyObject *self, PyObject* args, PyObject *kwds)
 {
     PyObject *text, *list;
-    void *buf;
+    void *buf = NULL;
     int isunicode = 0;
     int text_size, char_id, length, i;
     int minx, miny, maxx, maxy, advance;

File src/freetype/ft_wrap.c

     FT_Pointer request_data, 
     FT_Face *aface)
 {
-    FontId *id = GET_FONT_ID(face_id); 
-    FT_Error error = 0;
+    FontId *id = (FontId *)face_id; 
+    FT_Error error;
     
     Py_BEGIN_ALLOW_THREADS;
         error = FT_Open_Face(library, &id->open_args, id->face_index, aface);
 }
 
 int
-PGFT_Face_IsFixedWidth(PyFreeTypeFont *font)
+PGFT_Face_IsFixedWidth(FreeTypeInstance *ft, PyFreeTypeFont *font)
 {
-    return FT_IS_FIXED_WIDTH(font->face);
+    FT_Face face;
+    face = _PGFT_GetFace(ft, font);
+
+    return face ? FT_IS_FIXED_WIDTH(face) : 0;
 }
 
 const char *
-PGFT_Face_GetName(PyFreeTypeFont *font)
+PGFT_Face_GetName(FreeTypeInstance *ft, PyFreeTypeFont *font)
 {
-    return font->face->family_name;
-}
+    FT_Face face;
+    face = _PGFT_GetFace(ft, font);
 
-
-const char *
-PGFT_Face_GetFormat(PyFreeTypeFont *font)
-{
-#ifdef HAS_X11
-    return FT_Get_X11_Font_Format(font->face);
-#else
-    return ""; /* FIXME: Find a portable solution for native Win32 freetype */
-#endif
+    return face ? face->family_name : ""; 
 }
 
 int
-PGFT_Face_GetHeight(PyFreeTypeFont *font)
+PGFT_Face_GetHeight(FreeTypeInstance *ft, PyFreeTypeFont *font)
 {
-    return font->face->height;
+    FT_Face face;
+    face = _PGFT_GetFace(ft, font);
+
+    return face ? face->height : 0;
 }
 
 FT_Face
     FT_Face face;
 
     error = FTC_Manager_LookupFace(ft->cache_manager,
-            (FTC_FaceID)font,
+            (FTC_FaceID)(&font->id),
             &face);
 
     if (error)
 void
 _PGFT_BuildScaler(PyFreeTypeFont *font, FTC_Scaler scale, int size)
 {
-    scale->face_id = (FTC_FaceID)font;
+    scale->face_id = (FTC_FaceID)(&font->id);
     scale->width = scale->height = (pguint32)(size * 64);
     scale->pixel = 0;
     scale->x_res = scale->y_res = 0;
 
     char_index = FTC_CMapCache_Lookup(
             ft->cache_charmap, 
-            (FTC_FaceID)font,
+            (FTC_FaceID)(&font->id),
             -1, (FT_UInt32)character);
 
     if (_index)
 {
     FT_Error error;
     FTC_ScalerRec scale;
-    FT_Face face;
     FT_Glyph glyph;
 
     _PGFT_BuildScaler(font, &scale, font_size);
-    face = font->face;
 
     error = _PGFT_LoadGlyph(ft, font, &scale, character, &glyph, NULL);
 
     /* FIXME: Some way to set the system's default ? */
     swapped = 0;
     x = 0;
-    face = font->face;
+    face = _PGFT_GetFace(ft, font);
+
+    if (!face)
+        return -1;
 
     minx = maxx = 0;
     miny = maxy = 0;
 int
 PGFT_TryLoadFont(FreeTypeInstance *ft, PyFreeTypeFont *font)
 {
-    FT_Face face;
-    face = _PGFT_GetFace(ft, font);
-
-    if (!face)
-        return -1;
-
-    font->face = face;
-    return 0;
+    return _PGFT_GetFace(ft, font) ? 0 : -1;
 }
 
 void
 PGFT_UnloadFont(FreeTypeInstance *ft, PyFreeTypeFont *font)
 {
-    FTC_Manager_RemoveFaceID(ft->cache_manager, (FTC_FaceID)font);
+    FTC_Manager_RemoveFaceID(ft->cache_manager, (FTC_FaceID)(&font->id));
 }
 
 void

File src/freetype/ft_wrap.h

 int     PGFT_TryLoadFont(FreeTypeInstance *, PyFreeTypeFont *);
 void    PGFT_UnloadFont(FreeTypeInstance *, PyFreeTypeFont *);
 
-int     PGFT_Face_GetHeight(PyFreeTypeFont *);
-int     PGFT_Face_IsFixedWidth(PyFreeTypeFont *);
-const char * PGFT_Face_GetName(PyFreeTypeFont *);
-const char * PGFT_Face_GetFormat(PyFreeTypeFont *);
+int     PGFT_Face_GetHeight(FreeTypeInstance *ft, PyFreeTypeFont *);
+int     PGFT_Face_IsFixedWidth(FreeTypeInstance *ft, PyFreeTypeFont *);
+const char * PGFT_Face_GetName(FreeTypeInstance *ft, PyFreeTypeFont *);
+const char * PGFT_Face_GetFormat(FreeTypeInstance *ft, PyFreeTypeFont *);
 
 FT_UInt16 *PGFT_BuildUnicodeString(PyObject *, int *);
 

File src/freetype/pgfreetype.h

 {
     PyFont pyfont;
     FontId id;
-    FT_Face face;
 } PyFreeTypeFont;
 
 #define PyFreeTypeFont_AsFont(x) (((PyFreeTypeFont *)x)->font)

File test/freetype_font_test.py

+try:
+    import pygame2.test.pgunittest as unittest
+except:
+    import pgunittest as unittest
+
+import pygame2
+import pygame2.freetype as ft
+
+ft.init()
+
+class FreeTypeFontTest(unittest.TestCase):
+    _TEST_FONTS = {
+            # Inconsolata is an open-source font designed by Raph Levien
+            # Licensed under the Open Font License
+            # http://www.levien.com/type/myfonts/inconsolata.html
+            'fixed' : ft.Font('test_fixed.otf'),
+
+            # Liberation Sans is an open-source font designed by Steve Matteson
+            # Licensed under the GNU GPL
+            # https://fedorahosted.org/liberation-fonts/
+            'sans'  : ft.Font('test_sans.ttf'),
+    }
+
+    def test_pygame2_freetype_Font_init(self):
+        self.assertRaises(RuntimeError, ft.Font, 'nonexistant.ttf')
+
+        f = self._TEST_FONTS['sans']
+        self.assertTrue(isinstance(f, pygame2.base.Font))
+        self.assertTrue(isinstance(f, ft.Font))
+
+        f = self._TEST_FONTS['fixed']
+        self.assertTrue(isinstance(f, pygame2.base.Font))
+        self.assertTrue(isinstance(f, ft.Font))
+
+
+    def test_pygame2_freetype_Font_fixed_width(self):
+
+        # __doc__ (as of 2009-05-25) for pygame2.freetype.Font.fixed_width:
+
+        # Returns whether this font is a fixed-width (bitmap) font
+
+        f = self._TEST_FONTS['sans']
+        self.assertFalse(f.fixed_width)
+
+        f = self._TEST_FONTS['fixed']
+        self.assertFalse(f.fixed_width)
+
+        # TODO: Find a real fixed width font to test with
+
+    def test_pygame2_freetype_Font_get_metrics(self):
+
+        # __doc__ (as of 2009-05-25) for pygame2.freetype.Font.get_metrics:
+
+        # get_metrics(pt, text) -> [(int, int, int ...), ...]
+        # 
+        # Returns the glyph metrics for each character in 'text'.
+
+        TEST_VALUES_FIXED = {
+            12 : [
+                (0, 6, 0, 8, 6), (0, 6, 0, 8, 6), (0, 6, 0, 8, 6),
+                (0, 6, 0, 8, 6), (0, 6, 0, 6, 6), (0, 6, 0, 8, 6),
+                (0, 6, 0, 6, 6), (0, 6, 0, 8, 6)],
+            18 : [
+                (0, 9, 0, 12, 9), (0, 9, 0, 12, 9), (0, 9, 0, 12, 9),
+                (0, 9, 0, 12, 9), (0, 8, 0, 9, 9), (1, 9, 0, 12, 9),
+                (0, 9, 0, 9, 9), (0, 8, 0, 12, 9)],
+            24 : [
+                (0, 12, 0, 16, 12), (1, 11, 0, 15, 12), (0, 12, 0, 15, 12),
+                (1, 12, 0, 15, 12), (1, 11, 0, 11, 12), (1, 11, 0, 16, 12),
+                (1, 11, 0, 11, 12), (1, 11, 0, 16, 12)],
+            32 : [
+                (0, 16, 0, 21, 16), (1, 15, 0, 20, 16), (1, 15, 0, 20, 16),
+                (1, 15, 0, 20, 16), (1, 14, 0, 15, 16), (1, 15, 0, 22, 16),
+                (1, 15, 0, 15, 16), (1, 15, 0, 22, 16)],
+            48 : [
+                (0, 24, -1, 31, 24), (2, 22, 0, 30, 24), (1, 23, -1, 31, 24),
+                (2, 23, -1, 30, 24), (2, 21, -1, 23, 24), (2, 22, -1, 32, 24),
+                (2, 22, -1, 23, 24), (2, 22, -1, 32, 24)],
+        }
+
+        TEST_VALUES_SANS = {
+            12 : [
+                (0, 7, 0, 9, 7), (1, 7, 0, 9, 8), (1, 8, 0, 9, 9),
+                (1, 8, 0, 9, 9), (1, 7, 0, 7, 7), (0, 6, 0, 9, 7),
+                (1, 5, 0, 7, 6), (1, 7, 0, 9, 7)],
+            18 : [
+                (0, 11, 0, 12, 11), (1, 11, 0, 12, 12), (1, 12, 0, 12, 13),
+                (1, 12, 0, 12, 13), (1, 10, 0, 10, 10), (0, 9, 0, 13, 10),
+                (1, 8, 0, 10, 9), (1, 10, 0, 13, 10)],
+            24 : [
+                (0, 15, 0, 17, 15), (2, 15, 0, 17, 16), (1, 16, 0, 17, 17),
+                (2, 16, 0, 17, 17), (1, 13, 0, 13, 13), (1, 13, 0, 17, 14),
+                (1, 11, 0, 13, 12), (1, 13, 0, 17, 14)],
+            32 : [
+                (0, 21, 0, 22, 21), (3, 19, 0, 22, 21), (2, 22, 0, 22, 23),
+                (3, 21, 0, 22, 23), (1, 18, 0, 17, 17), (1, 16, 0, 23, 17),
+                (1, 15, 0, 17, 16), (1, 16, 0, 23, 17)],
+            48 : [
+                (0, 32, 0, 33, 32), (4, 29, 0, 33, 32), (2, 33, 0, 33, 35),
+                (4, 32, 0, 33, 35), (2, 27, 0, 26, 27), (2, 25, 0, 35, 27),
+                (2, 22, 0, 26, 24), (2, 25, 0, 35, 27)],
+        }
+
+        f = self._TEST_FONTS['fixed']
+        for (ptsize, test_data) in TEST_VALUES_FIXED.items():
+            self.assertEquals(f.get_metrics(ptsize, 'ABCDabcd'), test_data)
+
+        f = self._TEST_FONTS['sans']
+        for (ptsize, test_data) in TEST_VALUES_SANS.items():
+            self.assertEquals(f.get_metrics(ptsize, 'ABCDabcd'), test_data)
+
+    def todo_test_pygame2_freetype_Font_get_size(self):
+
+        # __doc__ (as of 2009-05-25) for pygame2.freetype.Font.get_size:
+
+        # get_size(pt, text) -> int, int
+        # 
+        # Gets the size which 'text' will occupy when rendered using
+        # this Font.
+
+        self.fail() 
+
+    def test_pygame2_freetype_Font_height(self):
+
+        # __doc__ (as of 2009-05-25) for pygame2.freetype.Font.height:
+
+        # Gets the height of the Font.
+
+        f = self._TEST_FONTS['sans']
+        self.assertEquals(f.height, 2355)
+
+        f = self._TEST_FONTS['fixed']
+        self.assertEquals(f.height, 1100)
+        
+
+    def test_pygame2_freetype_Font_name(self):
+
+        # __doc__ (as of 2009-05-25) for pygame2.freetype.Font.name:
+
+        # Gets the name of the font face.
+        
+        f = self._TEST_FONTS['sans']
+        self.assertEquals(f.name, 'Liberation Sans')
+
+        f = self._TEST_FONTS['fixed']
+        self.assertEquals(f.name, 'Inconsolata')
+
+
+    def todo_test_pygame2_freetype_Font_render(self):
+
+        # __doc__ (as of 2009-05-25) for pygame2.freetype.Font.render:
+
+        # render(text, fgcolor[, bgcolor, renderflag]) -> Surface
+        # 
+        # Renders a text to a Surface.
+        # 
+        # *TODO*
+
+        self.fail() 
+
+    def todo_test_pygame2_freetype_Font_style(self):
+
+        # __doc__ (as of 2009-05-25) for pygame2.freetype.Font.style:
+
+        # Gets or sets the style of the font. *TODO*
+
+        self.fail()
+
+if __name__ == '__main__':
+    unittest.main()

File test/test_fixed.otf

Binary file added.

File test/test_sans.ttf

Binary file added.