Commits

Lenard Lindstrom  committed e0a9058

enable freetype.Font.get_rect() to reuse layouts with render methods (towards Issue #75)

When None is passed as the text argument to one of get_rect(), render(),
render_to(), render_raw(), and render_raw_to(), it will use the text
string passed to a previous method call.

Internally, freetype.Font will try to use the layout calculated by the previous
call, saving overhead. To implement this feature, ft_layout.c underwent
significant refactoring. Hopefully, this makes the code easier to follow.

  • Participants
  • Parent commits bb1dac8

Comments (0)

Files changed (9)

File docs/reST/ref/freetype.rst

       If text is a char (byte) string, then its encoding is assumed to be
       ``LATIN1``.
 
+      Optionally, text can be :const:`None`, which will return the bounding
+      rectangle for the text passed to a previous :meth:`get_rect`,
+      :meth:`render`, :meth:`render_to`, :meth:`render_raw`, or
+      :meth:`render_raw_to` call. See :meth:`render_to` for more
+      details.
+
    .. method:: get_metrics
 
       | :sl:`Return the glyph metrics for the given text`
       in the color given by 'fgcolor'. If ``bgcolor`` is given, the surface
       will be filled with this color. If no background color is given,
       the surface is filled with zero alpha opacity. Normally the returned
-      surface has a 32 bit pixel size. However, if ``bgcolor`` is ``None``
+      surface has a 32 bit pixel size. However, if ``bgcolor`` is :const:`None`
       and anti-aliasing is disabled a two color 8 bit surface with colorkey
       set for the background color is returned.
 
       rectangle giving the size and origin of the rendered text.
 
       If an empty string is passed for text then the returned Rect is zero
-      width and the height of the font. If dest is None the returned surface is
-      the same dimensions as the boundary rect. The rect will test False.
+      width and the height of the font. If dest is :const:`None` the returned
+      surface is the same dimensions as the boundary rect.
+      The rect will test False.
 
       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
       If text is a char (byte) string, then its encoding is assumed to be
       ``LATIN1``.
 
+      Optionally, text can be None, which will return the bounding
+      rectangle for the text passed to a previous :meth:`get_rect`,
+      :meth:`render`, :meth:`render_to`, :meth:`render_raw`, or
+      :meth:`render_raw_to` call. See :meth:`render_to` for more
+      details.
+
    .. method:: render_to
 
       | :sl:`Render text onto an existing surface`
 
       Argument 'dest' is an (x, y) surface coordinate pair. If either x
       or y is not an integer it is converted to one if possible.
-      Any sequence, including Rect, for which the first two elements are
-      positions x and y is accepted.
+      Any sequence, including :class:`Rect`, for which the first
+      two elements are positions x and y is accepted.
 
       If a background color is given, the surface is first filled with that
       color. The text is blitted next. Both the background fill and text
       The return value is a rectangle giving the size and position of the
       rendered text within the surface.
 
-      If an empty string is passed for text then the returned Rect is zero
-      width and the height of the font. The rect will test False.
+      If an empty string is passed for text then the returned :class:`Rect`
+      is zero width and the height of the font. The rect will test False.
+
+      Optionally, text can be set :const:`None`, which will re-render text
+      passed to a previous :meth:`render_to`, :meth:`get_rect`, :meth:`render`,
+      :meth:`render_raw`, or :meth:`render_raw_to` call. Primarily, this
+      feature is an aid to using :meth:`render_to` in combination with
+      :meth:`get_rect`. An example: ::
+
+	  def word_wrap(surf, text, font, color=(0, 0, 0)):
+              font.origin = True
+              words = text.split(' ')
+              width, height = surf.get_size()
+              line_spacing = font.get_sized_height() + 2
+              x, y = 0, line_spacing
+              space = font.get_rect(' ' * 2)  # second space given width 0
+              for word in words:
+                  bounds = font.get_rect(word)
+                  if x + bounds.width + bounds.x >= width:
+                      x, y = 0, y + line_spacing
+                  if x + bounds.width + bounds.x >= width:
+                      raise ValueError("word too wide for the surface")
+                  if y + bounds.height - bounds.y >= height:
+                      raise ValueError("text to long for the surface")
+                  font.render_to(surf, (x, y), None, color)
+                  x += bounds.width + space.width
+              return x, y
+
+      When :meth:`render_to` is called with the same
+      font properties ― :attr:`size`, :attr:`style`, :attr:`strength`,
+      :attr:`wide`, :attr:`antialiase`, :attr:`vertical`, :attr:`rotation`,
+      :attr:`kerning`, and :attr:`use_bitmap_strikes` ― as :meth:`get_rect`,
+      :meth:`render_to` will use the layout calculated by :meth:`get_rect`.
+      Otherwise, :meth:`render_to` will recalculate the layout if called
+      with a text string or one of the above properties has changed
+      after the :meth:`get_rect` call.
 
       By default, the point size and style set for the font are used
       if not passed as arguments. The text is unrotated unless a non-zero
       | :sl:`Return rendered text as a string of bytes`
       | :sg:`render_raw(text, style=STYLE_DEFAULT, rotation=0, size=0, invert=False) -> (bytes, (int, int))`
 
-      Like ``Font.render()`` but the tuple returned is an 8 bit
+      Like :meth:`render` but the tuple returned is an 8 bit
       monochrome string of bytes and its size. The foreground color is 255, the
       background 0, useful as an alpha mask for a foreground pattern.
 
 
       Render to an array object exposing an array struct interface. The array
       must be two dimensional with integer items. The default dest value, None,
-      is equivalent to (0, 0).
+      is equivalent to (0, 0). See :meth:`render_to`.
 
    .. attribute:: style
 

File src/_freetype.c

 static int build_scale(PyObject *, PyObject *, Scale_t *);
 static FT_UInt number_to_FX6_unsigned(PyObject *);
 static int obj_to_rotation(PyObject *, void *);
+static void free_string(PGFT_String *);
 
 /*
  * Auxiliar defines
     return rval;
 }
 
+/** This accepts a NULL PGFT_String pointer */
+static void
+free_string(PGFT_String *p) {
+    if (p) _PGFT_FreeString(p);
+}
+
 /*
  * FREETYPE MODULE METHODS TABLE
  */
     };
 
     PyObject *textobj;
-    PGFT_String *text;
-    PyObject *rectobj = 0;
-    FT_Error error;
+    PGFT_String *text = 0;
     Scale_t face_size = FACE_SIZE_NONE;
     SDL_Rect r;
 
     if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iO&O&", kwlist,
                                      &textobj, &style,
                                      obj_to_rotation, (void *)&rotation,
-                                     obj_to_scale, (void *)&face_size)) {
-        return 0;
-    }
+                                     obj_to_scale, (void *)&face_size))
+        goto error;
 
     /* Encode text */
-    text = _PGFT_EncodePyString(textobj, self->render_flags & FT_RFLAG_UCS4);
-    if (!text) {
-        return 0;
+    if (textobj != Py_None) {
+        text = _PGFT_EncodePyString(textobj,
+                                    self->render_flags & FT_RFLAG_UCS4);
+        if (!text) goto error;
     }
 
     ASSERT_SELF_IS_ALIVE(self);
 
     /* Build rendering mode, always anti-aliased by default */
     if (_PGFT_BuildRenderMode(ft, self, &render,
-                              face_size, style, rotation)) {
-        return 0;
-    }
+                              face_size, style, rotation)) goto error;
 
-    error = _PGFT_GetTextRect(ft, self, &render, text, &r);
-    _PGFT_FreeString(text);
+    if (_PGFT_GetTextRect(ft, self, &render, text, &r)) goto error;
+    free_string(text);
 
-    if (!error) {
-        rectobj = PyRect_New(&r);
-    }
+    return PyRect_New(&r);
 
-    return rectobj;
+  error:
+    free_string(text);
+    return 0;
 }
 
 static PyObject *
 
     /* arguments */
     PyObject *textobj;
-    PGFT_String *text;
+    PGFT_String *text = 0;
     Scale_t face_size = FACE_SIZE_NONE;;
 
     /* grab freetype */
 
     /* parse args */
     if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O&", kwlist, &textobj,
-                                     obj_to_scale, (void *)&face_size)) {
-        return 0;
-    }
+                                     obj_to_scale, (void *)&face_size))
+        goto error;
 
     /* Encode text */
-    text = _PGFT_EncodePyString(textobj, self->render_flags & FT_RFLAG_UCS4);
-    if (!text) {
-        return 0;
-    }
+    text = _PGFT_EncodePyString(textobj,
+                                self->render_flags & FT_RFLAG_UCS4);
+    if (!text) goto error;
 
     ASSERT_SELF_IS_ALIVE(self);
 
      * Build the render mode with the given size and no
      * rotation/styles/vertical text
      */
-    if (_PGFT_BuildRenderMode(ft, self, &render, face_size, FT_STYLE_NORMAL, 0)) {
-        _PGFT_FreeString(text);
-        return 0;
-    }
+    if (_PGFT_BuildRenderMode(ft, self, &render,
+                              face_size, FT_STYLE_NORMAL, 0)) goto error;
 
     /* get metrics */
     list = get_metrics(ft, &render, self, text);
+    if (!list) goto error;
+    free_string(text);
 
-    _PGFT_FreeString(text);
     return list;
+
+  error:
+    free_string(text);
+    Py_XDECREF(list);
+    return 0;
 }
 
 static PyObject *
 
     /* input arguments */
     PyObject *textobj;
-    PGFT_String *text;
+    PGFT_String *text = 0;
     int style = FT_STYLE_DEFAULT;
     Angle_t rotation = self->rotation;
     Scale_t face_size = FACE_SIZE_NONE;;
 
     /* output arguments */
     PyObject *rbuffer = 0;
-    PyObject *rtuple;
+    PyObject *rtuple = 0;
     int width, height;
 
     FreeTypeInstance *ft;
                                      &style,
                                      obj_to_rotation, (void *)&rotation,
                                      obj_to_scale, (void *)&face_size,
-                                     &invert)) {
-        return 0;
-    }
+                                     &invert))
+        goto error;
 
     /* Encode text */
-    text = _PGFT_EncodePyString(textobj, self->render_flags & FT_RFLAG_UCS4);
-    if (!text) {
-        return 0;
+    if (textobj != Py_None) {
+        text = _PGFT_EncodePyString(textobj,
+                                    self->render_flags & FT_RFLAG_UCS4);
+        if (!text) goto error;
     }
 
     ASSERT_SELF_IS_ALIVE(self);
      * Build the render mode with the given size and no
      * rotation/styles/vertical text
      */
-    if (_PGFT_BuildRenderMode(ft, self, &mode, face_size, style, rotation)) {
-        _PGFT_FreeString(text);
-        return 0;
-    }
+    if (_PGFT_BuildRenderMode(ft, self, &mode, face_size, style, rotation))
+        goto error;
 
     rbuffer = _PGFT_Render_PixelArray(ft, self, &mode, text, invert,
                                       &width, &height);
-    _PGFT_FreeString(text);
+    if (!rbuffer) goto error;
+    free_string(text);
+    rtuple = Py_BuildValue("O(ii)", rbuffer, width, height);
+    if (!rtuple) goto error;
+    Py_DECREF(rbuffer);
 
-    if (!rbuffer) {
-        return 0;
-    }
-    rtuple = Py_BuildValue("O(ii)", rbuffer, width, height);
-    Py_DECREF(rbuffer);
     return rtuple;
+
+  error:
+    free_string(text);
+    Py_XDECREF(rbuffer);
+    Py_XDECREF(rtuple);
+    return 0;
 }
 
 static PyObject *
     /* input arguments */
     PyObject *arrayobj;
     PyObject *textobj;
-    PGFT_String *text;
+    PGFT_String *text = 0;
     PyObject *dest = 0;
     int xpos = 0;
     int ypos = 0;
     SDL_Rect r;
 
     /* internal */
-    int rcode;
     FreeTypeInstance *ft;
+    ASSERT_SELF_IS_ALIVE(self);
     ASSERT_GRAB_FREETYPE(ft, 0);
 
     if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|OiO&O&i", kwlist,
                                      &dest, &style,
                                      obj_to_rotation, (void *)&rotation,
                                      obj_to_scale, (void *)&face_size,
-                                     &invert)) {
-        return 0;
-    }
+                                     &invert))
+        goto error;
 
     if (dest && dest != Py_None) {
-        if (parse_dest(dest, &xpos, &ypos)) {
-            return 0;
-        }
+        if (parse_dest(dest, &xpos, &ypos)) goto error;
     }
 
     /* Encode text */
-    text = _PGFT_EncodePyString(textobj, self->render_flags & FT_RFLAG_UCS4);
-    if (!text) {
-        return 0;
+    if (textobj != Py_None) {
+        text = _PGFT_EncodePyString(textobj,
+                                    self->render_flags & FT_RFLAG_UCS4);
+        if (!text) goto error;
     }
 
-    ASSERT_SELF_IS_ALIVE(self);
-
     /*
      * Build the render mode with the given size and no
      * rotation/styles/vertical text
      */
-    if (_PGFT_BuildRenderMode(ft, self, &mode, face_size, style, rotation)) {
-        _PGFT_FreeString(text);
-        return 0;
-    }
+    if (_PGFT_BuildRenderMode(ft, self, &mode, face_size, style, rotation))
+        goto error;
 
-    rcode = _PGFT_Render_Array(ft, self, &mode,
-                               arrayobj, text, invert, xpos, ypos, &r);
-    _PGFT_FreeString(text);
-    if (rcode) {
-        return 0;
-    }
+    if (_PGFT_Render_Array(ft, self, &mode,
+                           arrayobj, text, invert, xpos, ypos, &r)) goto error;
+    free_string(text);
+
     return PyRect_New(&r);
+
+  error:
+    free_string(text);
+    return 0;
 }
 
 static PyObject *
 
     /* input arguments */
     PyObject *textobj = 0;
-    PGFT_String *text;
+    PGFT_String *text = 0;
     Scale_t face_size = FACE_SIZE_NONE;;
     PyObject *fg_color_obj = 0;
     PyObject *bg_color_obj = 0;
     int style = FT_STYLE_DEFAULT;
 
     /* output arguments */
-    SDL_Surface *surface;
+    SDL_Surface *surface = 0;
     PyObject *surface_obj = 0;
     PyObject *rtuple = 0;
     SDL_Rect r;
-    PyObject *rect_obj;
+    PyObject *rect_obj = 0;
 
     FontColor fg_color;
     FontColor bg_color;
     FontRenderMode render;
 
     FreeTypeInstance *ft;
+    ASSERT_SELF_IS_ALIVE(self);
     ASSERT_GRAB_FREETYPE(ft, 0);
 
     if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|OiO&O&", kwlist,
                                      /* optional */
                                      &bg_color_obj, &style,
                                      obj_to_rotation, (void *)&rotation,
-                                     obj_to_scale, (void *)&face_size)) {
-        return 0;
-    }
+                                     obj_to_scale, (void *)&face_size))
+        goto error;
 
     if (!RGBAFromColorObj(fg_color_obj, (Uint8 *)&fg_color)) {
         PyErr_SetString(PyExc_TypeError, "fgcolor must be a Color");
-        return 0;
+        goto error;
     }
     if (bg_color_obj) {
         if (bg_color_obj == Py_None) {
         }
         else if (!RGBAFromColorObj(bg_color_obj, (Uint8 *)&bg_color)) {
             PyErr_SetString(PyExc_TypeError, "bgcolor must be a Color");
-            return 0;
+            goto error;
         }
     }
 
     /* Encode text */
-    text = _PGFT_EncodePyString(textobj, self->render_flags & FT_RFLAG_UCS4);
-    if (!text) {
-        return 0;
+    if (textobj != Py_None) {
+        text = _PGFT_EncodePyString(textobj,
+                                    self->render_flags & FT_RFLAG_UCS4);
+        if (!text) goto error;
     }
 
-    ASSERT_SELF_IS_ALIVE(self);
-
-    if (_PGFT_BuildRenderMode(ft, self, &render, face_size, style, rotation)) {
-        _PGFT_FreeString(text);
-        return 0;
-    }
+    if (_PGFT_BuildRenderMode(ft, self, &render, face_size, style, rotation))
+        goto error;
 
     surface = _PGFT_Render_NewSurface(ft, self, &render, text, &fg_color,
                                       bg_color_obj ? &bg_color : 0, &r);
-    _PGFT_FreeString(text);
-    if (!surface) {
-        return 0;
-    }
+    if (!surface) goto error;
+    free_string(text);
     surface_obj = PySurface_New(surface);
-    if (!surface_obj) {
-        SDL_FreeSurface(surface);
-        return 0;
-    }
+    if (!surface_obj) goto error;
 
     rect_obj = PyRect_New(&r);
-    if (rect_obj) {
-        rtuple = PyTuple_Pack(2, surface_obj, rect_obj);
-        Py_DECREF(rect_obj);
-    }
+    if (!rect_obj) goto error;
+    rtuple = PyTuple_Pack(2, surface_obj, rect_obj);
+    if (!rtuple) goto error;
     Py_DECREF(surface_obj);
+    Py_DECREF(rect_obj);
 
     return rtuple;
 
+  error:
+    free_string(text);
+    if (surface_obj) {
+        Py_DECREF(surface_obj);
+    }
+    else if (surface) {
+        SDL_FreeSurface(surface);
+    }
+    Py_XDECREF(rect_obj);
+    Py_XDECREF(rtuple);
+    return 0;
+
 #endif // HAVE_PYGAME_SDL_VIDEO
 }
 
     /* input arguments */
     PyObject *surface_obj = 0;
     PyObject *textobj = 0;
-    PGFT_String *text;
+    PGFT_String *text = 0;
     Scale_t face_size = FACE_SIZE_NONE;;
     PyObject *dest = 0;
     int xpos = 0;
 
     /* output arguments */
     SDL_Rect r;
-    int rcode;
 
     FontColor fg_color;
     FontColor bg_color;
                                      /* optional */
                                      &bg_color_obj, &style,
                                      obj_to_rotation, (void *)&rotation,
-                                     obj_to_scale, (void *)&face_size)) {
-        return 0;
-    }
+                                     obj_to_scale, (void *)&face_size))
+        goto error;
 
-    if (parse_dest(dest, &xpos, &ypos)) {
-        return 0;
-    }
+    if (parse_dest(dest, &xpos, &ypos)) goto error;
     if (!RGBAFromColorObj(fg_color_obj, (Uint8 *)&fg_color)) {
         PyErr_SetString(PyExc_TypeError, "fgcolor must be a Color");
-        return 0;
+        goto error;
     }
     if (bg_color_obj) {
         if (bg_color_obj == Py_None) {
         }
         else if (!RGBAFromColorObj(bg_color_obj, (Uint8 *)&bg_color)) {
             PyErr_SetString(PyExc_TypeError, "bgcolor must be a Color");
-            return 0;
+            goto error;
         }
     }
 
     ASSERT_SELF_IS_ALIVE(self);
 
     /* Encode text */
-    text = _PGFT_EncodePyString(textobj, self->render_flags & FT_RFLAG_UCS4);
-    if (!text) {
-        return 0;
+    if (textobj != Py_None) {
+        text = _PGFT_EncodePyString(textobj,
+                                    self->render_flags & FT_RFLAG_UCS4);
+        if (!text) goto error;
     }
 
-    if (_PGFT_BuildRenderMode(ft, self, &render, face_size, style, rotation)) {
-        _PGFT_FreeString(text);
-        return 0;
-    }
+    if (_PGFT_BuildRenderMode(ft, self, &render, face_size, style, rotation))
+        goto error;
 
     surface = PySurface_AsSurface(surface_obj);
-    rcode = _PGFT_Render_ExistingSurface(ft, self, &render, text, surface,
-                                         xpos, ypos, &fg_color,
-                                         bg_color_obj ? &bg_color : 0, &r);
-    _PGFT_FreeString(text);
-    if (rcode) {
-        return 0;
-    }
+    if (_PGFT_Render_ExistingSurface(ft, self, &render, text, surface,
+                                     xpos, ypos, &fg_color,
+                                     bg_color_obj ? &bg_color : 0, &r))
+        goto error;
+    free_string(text);
 
     return PyRect_New(&r);
 
+  error:
+    free_string(text);
+    return 0;
 #endif // HAVE_PYGAME_SDL_VIDEO
 }
 

File src/freetype.h

 } Scale_t;
 typedef FT_Angle Angle_t;
 
+struct fontinternals_;
+
 typedef struct {
     FT_Long font_index;
     FT_Open_Args open_args;
     Angle_t rotation;
     FT_Matrix transform;
 
-    void *_internals;
+    struct fontinternals_ *_internals;
 } PgFontObject;
 
 #define PgFont_IS_ALIVE(o) \

File src/freetype/ft_cache.c

 #include FT_MODULE_H
 
 typedef struct keyfields_ {
-    PGFT_char ch;
+    GlyphIndex_t id;
     Scale_t face_size;
     unsigned short style;
     unsigned short render_flags;
 
 static FT_UInt32 get_hash(const NodeKey *);
 static CacheNode *allocate_node(FontCache *,
-                                    const FontRenderMode *,
-                                    FT_UInt, void *);
+                                const FontRenderMode *,
+                                GlyphIndex_t, void *);
 static void free_node(FontCache *, CacheNode *);
-static void set_node_key(NodeKey *, PGFT_char, const FontRenderMode *);
+static void set_node_key(NodeKey *, GlyphIndex_t, const FontRenderMode *);
 static int equal_node_keys(const NodeKey *, const NodeKey *);
 
 const int render_flags_mask = (FT_RFLAG_ANTIALIAS |
                                FT_RFLAG_AUTOHINT);
 
 static void
-set_node_key(NodeKey *key, PGFT_char ch, const FontRenderMode *mode)
+set_node_key(NodeKey *key, GlyphIndex_t id, const FontRenderMode *mode)
 {
     KeyFields *fields = &key->fields;
     const FT_UInt16 style_mask = ~(FT_STYLE_UNDERLINE);
     const FT_UInt16 rflag_mask = ~(FT_RFLAG_VERTICAL | FT_RFLAG_KERNING);
     unsigned short rot = (unsigned short)FX6_TRUNC(mode->rotation_angle);
 
-    memset(key, 0, sizeof(key));
-    fields->ch = ch;
+    memset(key, 0, sizeof(*key));
+    fields->id = id;
     fields->face_size = mode->face_size;
     fields->style = mode->style & style_mask;
     fields->render_flags = mode->render_flags & rflag_mask;
 }
 
 FontGlyph *
-_PGFT_Cache_FindGlyph(PGFT_char character, const FontRenderMode *render,
-                     FontCache *cache, void *internal)
+_PGFT_Cache_FindGlyph(GlyphIndex_t id, const FontRenderMode *render,
+                      FontCache *cache, void *internal)
 {
     CacheNode **nodes = cache->nodes;
     CacheNode *node, *prev;
     FT_UInt32 hash;
     FT_UInt32 bucket;
 
-    set_node_key(&key, character, render);
+    set_node_key(&key, id, render);
     hash = get_hash(&key);
     bucket = hash & cache->size_mask;
     node = nodes[bucket];
         node = node->next;
     }
 
-    node = allocate_node(cache, render, character, internal);
+    node = allocate_node(cache, render, id, internal);
 
 #ifdef PGFT_DEBUG_CACHE
     cache->_debug_miss++;
 
 static CacheNode *
 allocate_node(FontCache *cache, const FontRenderMode *render,
-              PGFT_char character, void *internal)
+              GlyphIndex_t id, void *internal)
 {
     CacheNode *node = _PGFT_malloc(sizeof(CacheNode));
     FT_UInt32 bucket;
     }
     memset(node, 0, sizeof(CacheNode));
 
-    if (_PGFT_LoadGlyph(&node->glyph, character, render, internal)) {
+    if (_PGFT_LoadGlyph(&node->glyph, id, render, internal)) {
         goto cleanup;
     }
 
-    set_node_key(&node->key, character, render);
+    set_node_key(&node->key, id, render);
     node->hash = get_hash(&node->key);
     bucket = node->hash & cache->size_mask;
     node->next = cache->nodes[bucket];

File src/freetype/ft_layout.c

 #define FX16_BOLD_FACTOR (FX16_ONE / 36)
 #define UNICODE_SPACE ((PGFT_char)' ')
 
+typedef enum {
+    UPDATE_NONE,
+    UPDATE_LAYOUT,
+    UPDATE_GLYPHS
+} UpdateLevel_t;
+
+/** render modes requiring glyph reloading and repositioning */
+static const FT_UInt16 GLYPH_RENDER_FLAGS = (FT_RFLAG_ANTIALIAS |
+                                             FT_RFLAG_AUTOHINT |
+                                             FT_RFLAG_TRANSFORM |
+                                             FT_RFLAG_USE_BITMAP_STRIKES);
+/** render modes requiring only glyph repositioning */
+static const FT_UInt16 LAYOUT_RENDER_FLAGS = (FT_RFLAG_VERTICAL |
+                                              FT_RFLAG_HINTED |
+                                              FT_RFLAG_KERNING |
+                                              FT_RFLAG_PAD);
+/** render styles requiring glyph reloading and repositioning */
+static const FT_UInt16 GLYPH_STYLE_FLAGS = (FT_STYLE_OBLIQUE |
+                                            FT_STYLE_STRONG |
+                                            FT_STYLE_WIDE);
+
 static FT_UInt32 get_load_flags(const FontRenderMode *);
 static void fill_metrics(FontMetrics *, FT_Pos, FT_Pos,
                          FT_Vector *, FT_Vector *);
                          const PgFontObject *,
                          const FontRenderMode *,
                          const FT_Face);
+static int size_text(Layout *,
+                     FreeTypeInstance *,
+                     TextContext *,
+                     const PGFT_String *);
+static int load_glyphs(Layout *, TextContext *, FontCache *);
+static void position_glyphs(Layout *);
+static void fill_text_bounding_box(Layout *,
+                                   FT_Vector,
+                                   FT_Pos, FT_Pos, FT_Pos, FT_Pos, FT_Pos);
+static UpdateLevel_t mode_compare(const FontRenderMode *,
+                                  const FontRenderMode *);
+static int same_sizes(const Scale_t *, const Scale_t * );
+static int same_transforms(const FT_Matrix *, const FT_Matrix *);
+static void copy_mode(FontRenderMode *, const FontRenderMode *);
+
 
 int
-_PGFT_FontTextInit(FreeTypeInstance *ft, PgFontObject *fontobj)
+_PGFT_LayoutInit(FreeTypeInstance *ft, PgFontObject *fontobj)
 {
-    FontText *ftext = &(PGFT_INTERNALS(fontobj)->active_text);
+    Layout *ftext = &fontobj->_internals->active_text;
+    FontCache *cache = &fontobj->_internals->glyph_cache;
 
     ftext->buffer_size = 0;
     ftext->glyphs = 0;
-    ftext->posns = 0;
 
-    if (_PGFT_Cache_Init(ft, &ftext->glyph_cache)) {
+    if (_PGFT_Cache_Init(ft, cache)) {
         PyErr_NoMemory();
         return -1;
     }
 }
 
 void
-_PGFT_FontTextFree(PgFontObject *fontobj)
+_PGFT_LayoutFree(PgFontObject *fontobj)
 {
-    FontText *ftext = &(PGFT_INTERNALS(fontobj)->active_text);
+    Layout *ftext = &(fontobj->_internals->active_text);
+    FontCache *cache = &fontobj->_internals->glyph_cache;
 
     if (ftext->buffer_size > 0) {
         _PGFT_free(ftext->glyphs);
-        _PGFT_free(ftext->posns);
+        ftext->glyphs = 0;
     }
-    _PGFT_Cache_Destroy(&ftext->glyph_cache);
+    _PGFT_Cache_Destroy(cache);
 }
 
-FontText *
-_PGFT_LoadFontText(FreeTypeInstance *ft, PgFontObject *fontobj,
-                   const FontRenderMode *mode, PGFT_String *text)
+Layout *
+_PGFT_LoadLayout(FreeTypeInstance *ft, PgFontObject *fontobj,
+                 const FontRenderMode *mode, PGFT_String *text)
 {
-    Py_ssize_t  string_length = PGFT_String_GET_LENGTH(text);
-
-    PGFT_char * buffer = PGFT_String_GET_DATA(text);
-    PGFT_char * buffer_end;
-    PGFT_char * ch;
-
-    FontText    *ftext = &(PGFT_INTERNALS(fontobj)->active_text);
-    FontGlyph   *glyph = 0;
-    FontGlyph   **glyph_array = 0;
-    FontMetrics *metrics;
+    Layout *ftext = &fontobj->_internals->active_text;
+    FontCache *cache = &fontobj->_internals->glyph_cache;
+    UpdateLevel_t level = (text ?
+                           UPDATE_GLYPHS : mode_compare(&ftext->mode, mode));
+    FT_Face font = 0;
     TextContext context;
 
-    FT_Face     font;
-    FT_Size_Metrics *sz_metrics;
+    if (level != UPDATE_NONE) {
+        copy_mode(&ftext->mode, mode);
+        font = _PGFT_GetFontSized(ft, fontobj, mode->face_size);
+        if (!font) {
+            PyErr_SetString(PyExc_SDLError, _PGFT_GetError(ft));
+            return 0;
+        }
+    }
+
+    switch (level) {
+
+    case UPDATE_GLYPHS:
+        _PGFT_Cache_Cleanup(cache);
+        fill_context(&context, ft, fontobj, mode, font);
+        if (text) {
+            if (size_text(ftext, ft, &context, text)) {
+                return 0;
+            }
+        }
+        if (load_glyphs(ftext, &context, cache)) {
+            return 0;
+        }
+        /* fall through */
+
+    case UPDATE_LAYOUT:
+        position_glyphs(ftext);
+        break;
+
+    default:
+        assert(level == UPDATE_NONE);
+        break;
+    }
+
+    return ftext;
+}
+
+static int
+size_text(Layout *ftext,
+          FreeTypeInstance *ft,
+          TextContext *context,
+          const PGFT_String *text)
+{
+    FT_Face font = context->font;
+    const FT_Size_Metrics *sz_metrics = &font->size->metrics;
+    Py_ssize_t string_length = PGFT_String_GET_LENGTH(text);
+    const PGFT_char *chars = PGFT_String_GET_DATA(text);
+    FT_Fixed y_scale = sz_metrics->y_scale;
+    int have_kerning = FT_HAS_KERNING(font);
+    Py_ssize_t length = 0;
+    GlyphSlot *slots;
+    GlyphIndex_t id;
+    GlyphIndex_t prev_id = 0;
+    FT_UInt32 ch;
+    Py_ssize_t i;
+    FT_Error error = 0;
+
+    assert(!(ftext->mode.render_flags & FT_RFLAG_KERNING) || have_kerning);
+
+    /* create the text struct */
+    if (string_length > ftext->buffer_size) {
+        _PGFT_free(ftext->glyphs);
+        ftext->glyphs = (GlyphSlot *)
+            _PGFT_malloc((size_t)string_length * sizeof(GlyphSlot));
+        if (!ftext->glyphs) {
+            PyErr_NoMemory();
+            return -1;
+        }
+        ftext->buffer_size = string_length;
+    }
+
+    /* Retrieve the glyph indices of recognized text characters */
+    slots = ftext->glyphs;
+    for (i = 0; i < string_length; ++i) {
+        ch = chars[i];
+        id = FTC_CMapCache_Lookup(context->charmap, context->id, -1, ch);
+        slots[length].id = id;
+        if (have_kerning) {
+            error = FT_Get_Kerning(font, prev_id, id, FT_KERNING_UNFITTED,
+                                   &slots[length].kerning);
+            if (error) {
+                _PGFT_SetError(ft, "Loading glyphs", error);
+                PyErr_SetString(PyExc_SDLError, _PGFT_GetError(ft));
+                return -1;
+            }
+        }
+        prev_id = id;
+        ++length;
+    }
+    ftext->length = length;
+
+    /* Fill in generate font parameters */
+    ftext->ascender = sz_metrics->ascender;
+    ftext->descender = sz_metrics->descender;
+    ftext->height = sz_metrics->height;
+    ftext->max_advance = sz_metrics->max_advance;
+    ftext->underline_pos = -FT_MulFix(font->underline_position, y_scale);
+    ftext->underline_size = FT_MulFix(font->underline_thickness, y_scale);
+    if (ftext->mode.style & FT_STYLE_STRONG) {
+        FT_Fixed bold_str = ftext->mode.strength * sz_metrics->x_ppem;
+
+        ftext->underline_size = FT_MulFix(ftext->underline_size,
+                                          FX16_ONE + bold_str / 4);
+    }
+    return 0;
+}
+
+static int
+load_glyphs(Layout *ftext, TextContext *context, FontCache *cache)
+{
+    GlyphSlot *slot = ftext->glyphs;
+    Py_ssize_t length = ftext->length;
+    FontRenderMode *mode = &ftext->mode;
+    FontGlyph *glyph;
+    Py_ssize_t i;
+
+    for (i = 0; i < length; ++i) {
+        glyph = _PGFT_Cache_FindGlyph(slot[i].id, mode, cache, context);
+        if (!glyph) {
+            PyErr_Format(PyExc_SDLError, "Unable to load glyph for id %lu",
+                         (unsigned long)slot[i].id);
+            return -1;
+        }
+        slot[i].glyph = glyph;
+    }
+    return 0;
+}
+
+static void
+position_glyphs(Layout *ftext)
+{
+    GlyphSlot *glyph_array = ftext->glyphs;
+    GlyphSlot *slot;
+    FontGlyph *glyph = 0;
+    Py_ssize_t n_glyphs = ftext->length;
+
+    FontMetrics *metrics;
 
     FT_Vector   pen = {0, 0};                /* untransformed origin  */
     FT_Vector   pen1 = {0, 0};
     FT_Vector   pen2;
 
-    FT_Vector   *next_pos;
-
-    int         vertical = mode->render_flags & FT_RFLAG_VERTICAL;
-    int         use_kerning = mode->render_flags & FT_RFLAG_KERNING;
-    int         pad = mode->render_flags & FT_RFLAG_PAD;
-    FT_UInt     prev_glyph_index = 0;
+    int         vertical = ftext->mode.render_flags & FT_RFLAG_VERTICAL;
+    int         use_kerning = ftext->mode.render_flags & FT_RFLAG_KERNING;
 
     /* All these are 16.16 precision */
-    FT_Angle    rotation_angle = mode->rotation_angle;
+    FT_Angle    rotation_angle = ftext->mode.rotation_angle;
 
     /* All these are 26.6 precision */
     FT_Vector   kerning;
     FT_Pos      glyph_width;
     FT_Pos      glyph_height;
     FT_Pos      top = FX6_MIN;
-    FT_Fixed    y_scale;
 
-    FT_Error    error = 0;
+    Py_ssize_t i;
 
-    /* load our sized font */
-    font = _PGFT_GetFontSized(ft, fontobj, mode->face_size);
-    if (!font) {
-        PyErr_SetString(PyExc_SDLError, _PGFT_GetError(ft));
-        return 0;
-    }
-    sz_metrics = &font->size->metrics;
-    y_scale = sz_metrics->y_scale;
+    assert(n_glyphs == 0 || glyph_array);
 
-    /* cleanup the cache */
-    _PGFT_Cache_Cleanup(&ftext->glyph_cache);
+    for (i = 0; i != n_glyphs; ++i) {
+        slot = &glyph_array[i];
+        glyph = slot->glyph;
 
-    /* create the text struct */
-    if (string_length > ftext->buffer_size) {
-        _PGFT_free(ftext->glyphs);
-        ftext->glyphs = (FontGlyph **)
-            _PGFT_malloc((size_t)string_length * sizeof(FontGlyph *));
-        if (!ftext->glyphs) {
-            PyErr_NoMemory();
-            return 0;
-        }
-
-        _PGFT_free(ftext->posns);
-        ftext->posns = (FT_Vector *)
-        _PGFT_malloc((size_t)string_length * sizeof(FT_Vector));
-        if (!ftext->posns) {
-            PyErr_NoMemory();
-            return 0;
-        }
-        ftext->buffer_size = string_length;
-    }
-    ftext->length = string_length;
-    ftext->ascender = sz_metrics->ascender;
-    ftext->underline_pos = -FT_MulFix(font->underline_position, y_scale);
-    ftext->underline_size = FT_MulFix(font->underline_thickness, y_scale);
-    if (mode->style & FT_STYLE_STRONG) {
-        FT_Fixed bold_str = mode->strength * sz_metrics->x_ppem;
-
-        ftext->underline_size = FT_MulFix(ftext->underline_size,
-                                          FX16_ONE + bold_str / 4);
-    }
-
-    /* fill it with the glyphs */
-    fill_context(&context, ft, fontobj, mode, font);
-    glyph_array = ftext->glyphs;
-    next_pos = ftext->posns;
-
-    for (ch = buffer, buffer_end = ch + string_length; ch < buffer_end; ++ch) {
         pen2.x = pen1.x;
         pen2.y = pen1.y;
         pen1.x = pen.x;
         pen1.y = pen.y;
-        /*
-         * Load the corresponding glyph from the cache
-         */
-        glyph = _PGFT_Cache_FindGlyph(*((FT_UInt32 *)ch), mode,
-                                     &ftext->glyph_cache, &context);
-
-        if (!glyph) {
-            --ftext->length;
-            continue;
-        }
         glyph_width = glyph->width;
         glyph_height = glyph->height;
 
         /*
-         * Do size calculations for all the glyphs in the text
+         * Do size calculations for the glyph
          */
-        if (use_kerning && prev_glyph_index) {
-            error = FT_Get_Kerning(font, prev_glyph_index,
-                                   glyph->glyph_index,
-                                   FT_KERNING_UNFITTED, &kerning);
-            if (error) {
-                _PGFT_SetError(ft, "Loading glyphs", error);
-                PyErr_SetString(PyExc_SDLError, _PGFT_GetError(ft));
-                return 0;
-            }
+        if (use_kerning) {
+            kerning.x = slot->kerning.x;
+            kerning.y = slot->kerning.y;
             if (rotation_angle != 0) {
                 FT_Vector_Rotate(&kerning, rotation_angle);
             }
             }
         }
 
-        prev_glyph_index = glyph->glyph_index;
         metrics = vertical ? &glyph->v_metrics : &glyph->h_metrics;
         if (metrics->bearing_rotated.y > top) {
             top = metrics->bearing_rotated.y;
         if (pen.x + metrics->bearing_rotated.x + glyph_width > max_x) {
             max_x = pen.x + metrics->bearing_rotated.x + glyph_width;
         }
-        next_pos->x = pen.x + metrics->bearing_rotated.x;
+        slot->posn.x = pen.x + metrics->bearing_rotated.x;
         pen.x += metrics->advance_rotated.x;
         if (vertical) {
             if (pen.y + metrics->bearing_rotated.y < min_y) {
             if (pen.y + metrics->bearing_rotated.y + glyph_height > max_y) {
                 max_y = pen.y + metrics->bearing_rotated.y + glyph_height;
             }
-            next_pos->y = pen.y + metrics->bearing_rotated.y;
+            slot->posn.y = pen.y + metrics->bearing_rotated.y;
             pen.y += metrics->advance_rotated.y;
         }
         else {
             if (pen.y - metrics->bearing_rotated.y + glyph_height > max_y) {
                 max_y = pen.y - metrics->bearing_rotated.y + glyph_height;
             }
-            next_pos->y = pen.y - metrics->bearing_rotated.y;
+            slot->posn.y = pen.y - metrics->bearing_rotated.y;
             pen.y -= metrics->advance_rotated.y;
         }
-        *glyph_array++ = glyph;
-        ++next_pos;
+
     }
 
+    fill_text_bounding_box(ftext, pen, min_x, max_x, min_y, max_y, top);
+}
+
+static void
+fill_text_bounding_box(Layout *ftext,
+                       FT_Vector pen,
+                       FT_Pos min_x, FT_Pos max_x,
+                       FT_Pos min_y, FT_Pos max_y,
+                       FT_Pos top)
+{
+    const FT_Fixed BASELINE = FX6_ONE;
+    FT_Fixed right = ftext->max_advance / 2;
+    FT_Fixed ascender = ftext->ascender;
+    FT_Fixed descender = ftext->descender;
+    FT_Fixed height = ftext->height;
+    int vertical = ftext->mode.render_flags & FT_RFLAG_VERTICAL;
+    int pad = ftext->mode.render_flags & FT_RFLAG_PAD;
+
     if (ftext->length == 0) {
         min_x = 0;
         max_x = 0;
-        if (vertical) {
-            ftext->min_y = 0;
-            max_y = sz_metrics->height;
-        }
-        else {
-            FT_Size_Metrics *sz_metrics = &font->size->metrics;
-
-            min_y = -sz_metrics->ascender;
-            max_y = -sz_metrics->descender;
-        }
+        min_y = vertical ? 0 : -ascender;
+        max_y = vertical ? height : -descender;
     }
 
+    ftext->left = FX6_TRUNC(FX6_FLOOR(min_x));
+    ftext->top = FX6_TRUNC(FX6_CEIL(top));
     if (pad) {
-        FT_Size_Metrics *sz_metrics = &font->size->metrics;
-
         if (pen.x > max_x) {
             max_x = pen.x;
         }
             min_y = pen.y;
         }
         if (vertical) {
-            FT_Fixed right = sz_metrics->max_advance / 2;
-
             if (max_x < right) {
                 max_x = right;
             }
             if (min_y > 0) {
                 min_y = 0;
             }
-            else if (max_y < pen.y) {
-                max_y = pen.y;
-            }
         }
         else {
-            FT_Fixed ascender = sz_metrics->ascender;
-            FT_Fixed descender = sz_metrics->descender;
-
             if (min_x > 0) {
                 min_x = 0;
             }
-            if (max_x < pen.x) {
-                max_x = pen.x;
-            }
             if (min_y > -ascender) {
                 min_y = -ascender;
             }
             if (max_y <= -descender) {
-                max_y = -descender + /* baseline */ FX6_ONE;
+                max_y = -descender + BASELINE;
             }
         }
     }
-
-    ftext->left = FX6_TRUNC(FX6_FLOOR(min_x));
-    ftext->top = FX6_TRUNC(FX6_CEIL(top));
     ftext->min_x = min_x;
     ftext->max_x = max_x;
     ftext->min_y = min_y;
     ftext->max_y = max_y;
     ftext->advance.x = pen.x;
     ftext->advance.y = pen.y;
-
-    return ftext;
 }
 
 int _PGFT_GetMetrics(FreeTypeInstance *ft, PgFontObject *fontobj,
                     long *miny, long *maxy,
                     double *advance_x, double *advance_y)
 {
-    FontText    *ftext = &(PGFT_INTERNALS(fontobj)->active_text);
+    FontCache *cache = &(fontobj->_internals->glyph_cache);
+    FT_UInt32 ch = (FT_UInt32)character;
+    GlyphIndex_t id;
     FontGlyph *glyph = 0;
     TextContext context;
     FT_Face     font;
     }
 
     /* cleanup the cache */
-    _PGFT_Cache_Cleanup(&ftext->glyph_cache);
+    _PGFT_Cache_Cleanup(cache);
 
     fill_context(&context, ft, fontobj, mode, font);
-    glyph = _PGFT_Cache_FindGlyph(character, mode,
-                                 &PGFT_INTERNALS(fontobj)->active_text.glyph_cache,
-                                 &context);
-
+    id = FTC_CMapCache_Lookup(context.charmap, context.id, -1, ch);
+    if (!id) {
+        return -1;
+    }
+    glyph = _PGFT_Cache_FindGlyph(id, mode, cache, &context);
     if (!glyph) {
         return -1;
     }
 
-    *gindex = glyph->glyph_index;
+    *gindex = id;
     *minx = (long)glyph->image->left;
     *maxx = (long)(glyph->image->left + glyph->image->bitmap.width);
     *maxy = (long)glyph->image->top;
 }
 
 int
-_PGFT_LoadGlyph(FontGlyph *glyph, PGFT_char character,
+_PGFT_LoadGlyph(FontGlyph *glyph, GlyphIndex_t id,
                 const FontRenderMode *mode, void *internal)
 {
     static FT_Vector delta = {0, 0};
     TextContext *context = (TextContext *)internal;
 
     FT_UInt32 load_flags;
-    FT_UInt gindex;
 
     FT_Fixed rotation_angle = mode->rotation_angle;
     /* FT_Matrix transform; */
     FT_Error error = 0;
 
     /*
-     * Calculate the corresponding glyph index for the char
-     */
-    gindex = FTC_CMapCache_Lookup(context->charmap, context->id,
-                                  -1, (FT_UInt32)character);
-
-    glyph->glyph_index = gindex;
-
-    /*
      * Get loading information
      */
     load_flags = get_load_flags(mode);
     /*
      * Load the glyph into the glyph slot
      */
-    if (FT_Load_Glyph(context->font, glyph->glyph_index, (FT_Int)load_flags) ||
+    if (FT_Load_Glyph(context->font, id, (FT_Int)load_flags) ||
         FT_Get_Glyph(context->font->glyph, &image))
         goto cleanup;
 
 
     return load_flags;
 }
+
+static UpdateLevel_t
+mode_compare(const FontRenderMode *a, const FontRenderMode *b)
+{
+    FT_UInt16 a_sflags = a->style;
+    FT_UInt16 b_sflags = b->style;
+    FT_UInt16 a_rflags = a->render_flags;
+    FT_UInt16 b_rflags = b->render_flags;
+
+    if (!same_sizes(&a->face_size, &b->face_size) ||
+        a->rotation_angle != b->rotation_angle ||
+        (a_sflags & GLYPH_STYLE_FLAGS) != (b_sflags & GLYPH_STYLE_FLAGS) ||
+        (a_rflags & GLYPH_RENDER_FLAGS) != (b_rflags & GLYPH_RENDER_FLAGS) ||
+        ((a_rflags & FT_RFLAG_TRANSFORM) &&
+         !same_transforms(&a->transform, &b->transform))) {
+        return UPDATE_GLYPHS;
+    }
+    if ((a_rflags & LAYOUT_RENDER_FLAGS) != (b_rflags & LAYOUT_RENDER_FLAGS)) {
+        return UPDATE_LAYOUT;
+    }
+    return UPDATE_NONE;
+}
+
+static int
+same_sizes(const Scale_t *a, const Scale_t *b)
+{
+    return a->x == b->x && a->y == b->y;
+}
+
+static int
+same_transforms(const FT_Matrix *a, const FT_Matrix *b)
+{
+    return a->xx == b->xx && a->xy == b->xy && a->yx == b->yx && a->yy == b->yy;
+}
+
+static void
+copy_mode(FontRenderMode *d, const FontRenderMode *s)
+{
+    memcpy(d, s, sizeof(FontRenderMode));
+}

File src/freetype/ft_render.c

 static const FontColor mono_opaque = {0, 0, 0, SDL_ALPHA_OPAQUE};
 static const FontColor mono_transparent = {0, 0, 0, SDL_ALPHA_TRANSPARENT};
 
-static void render(FreeTypeInstance *, FontText *, const FontRenderMode *,
+static void render(FreeTypeInstance *, Layout *, const FontRenderMode *,
                    const FontColor *, FontSurface *, unsigned, unsigned,
                    FT_Vector *, FT_Pos, FT_Fixed);
 
                       PgFontObject *fontobj, FontRenderMode *mode,
                       Scale_t face_size, int style, Angle_t rotation)
 {
+    FT_Face font = 0;
+
     if (face_size.x == 0) {
         if (fontobj->face_size.x == 0) {
             PyErr_SetString(PyExc_ValueError,
         }
     }
 
+    if (mode->render_flags & FT_RFLAG_KERNING) {
+        font = _PGFT_GetFontSized(ft, fontobj, mode->face_size);
+            PyErr_SetString(PyExc_SDLError, _PGFT_GetError(ft));
+            return -1;
+            if (!FT_HAS_KERNING(font)) {
+                mode->render_flags &= ~FT_RFLAG_KERNING;
+            }
+    }
     return 0;
 }
 
 void
-_PGFT_GetRenderMetrics(const FontRenderMode *mode, FontText *text,
+_PGFT_GetRenderMetrics(const FontRenderMode *mode, Layout *text,
                        unsigned *w, unsigned *h, FT_Vector *offset,
                        FT_Pos *underline_top, FT_Fixed *underline_size)
 {
     FT_Fixed underline_size;
 
     FontSurface font_surf;
-    FontText *font_text;
-
-    if (PGFT_String_GET_LENGTH(text) == 0) {
-        /* No rendering */
-        r->x = 0;
-        r->y = 0;
-        r->w = 0;
-        r->h = _PGFT_Font_GetHeightSized(ft, fontobj, mode->face_size);
-        return 0;
-    }
+    Layout *font_text;
 
     if (SDL_MUSTLOCK(surface)) {
         if (SDL_LockSurface(surface) == -1) {
     }
 
     /* build font text */
-    font_text = _PGFT_LoadFontText(ft, fontobj, mode, text);
+    font_text = _PGFT_LoadLayout(ft, fontobj, mode, text);
     if (!font_text) {
         if (locked) {
             SDL_UnlockSurface(surface);
         }
         return -1;
     }
+    if (font_text->length == 0) {
+        /* Nothing to rendering */
+        r->x = 0;
+        r->y = 0;
+        r->w = 0;
+        r->h = _PGFT_Font_GetHeightSized(ft, fontobj, mode->face_size);
+        return 0;
+    }
 
     _PGFT_GetRenderMetrics(mode, font_text, &width, &height, &offset,
                            &underline_top, &underline_size);
     FT_UInt32 surface_flags = SDL_SWSURFACE;
 
     FontSurface font_surf;
-    FontText *font_text;
+    Layout *font_text;
     unsigned width;
     unsigned height;
     FT_Vector offset;
     FontColor mono_bgcolor = {0, 0, 0, 0};
 
     /* build font text */
-    font_text = _PGFT_LoadFontText(ft, fontobj, mode, text);
+    font_text = _PGFT_LoadLayout(ft, fontobj, mode, text);
     if (!font_text) {
         return 0;
     }
     PyObject *array = 0;
     FontSurface surf;
 
-    FontText *font_text;
+    Layout *font_text;
     unsigned width;
     unsigned height;
     FT_Vector offset;
     FT_Fixed underline_size;
     int array_size;
 
-    if (PGFT_String_GET_LENGTH(text) == 0) {
-        /* Empty array */
+    /* build font text */
+    font_text = _PGFT_LoadLayout(ft, fontobj, mode, text);
+    if (!font_text) {
+        return 0;
+    }
+
+    if (font_text->length == 0) {
+        /* Nothing to render */
         *_width = 0;
         *_height = _PGFT_Font_GetHeight(ft, fontobj);
         return Bytes_FromStringAndSize("", 0);
     }
 
-    /* build font text */
-    font_text = _PGFT_LoadFontText(ft, fontobj, mode, text);
-
-    if (!font_text) {
-        return 0;
-    }
-
     _PGFT_GetRenderMetrics(mode, font_text, &width, &height, &offset,
                            &underline_top, &underline_size);
 
 
     FontSurface font_surf;
     SDL_PixelFormat format;
-    FontText *font_text;
+    Layout *font_text;
 
     /* Get target buffer */
     if (!view_init) {
     height = (unsigned)view_p->shape[1];
     itemsize = (unsigned)view_p->itemsize;
 
+    /* build font text */
+    font_text = _PGFT_LoadLayout(ft, fontobj, mode, text);
+    if (!font_text) {
+        PgBuffer_Release(&pg_view);
+        return -1;
+    }
+
     /* if empty string, then nothing more to do */
-    if (PGFT_String_GET_LENGTH(text) == 0) {
+    if (font_text->length == 0) {
         PgBuffer_Release(&pg_view);
         r->x = 0;
         r->y = 0;
         return 0;
     }
 
-    /* build font text */
-    font_text = _PGFT_LoadFontText(ft, fontobj, mode, text);
-    if (!font_text) {
-        PgBuffer_Release(&pg_view);
-        return -1;
-    }
-
     _PGFT_GetRenderMetrics(mode, font_text, &width, &height, &offset,
                            &underline_top, &underline_size);
     if (width == 0 || height == 0) {
  *
  *********************************************************/
 static void
-render(FreeTypeInstance *ft, FontText *text, const FontRenderMode *mode,
+render(FreeTypeInstance *ft, Layout *text, const FontRenderMode *mode,
        const FontColor *fg_color, FontSurface *surface,
        unsigned width, unsigned height, FT_Vector *offset,
        FT_Pos underline_top, FT_Fixed underline_size)
     int y;
     int n;
     int length = text->length;
-    FontGlyph **glyphs = text->glyphs;
+    GlyphSlot *slots = text->glyphs;
     FT_BitmapGlyph image;
-    FT_Vector *posns = text->posns;
     FontRenderPtr render_gray = surface->render_gray;
     FontRenderPtr render_mono = surface->render_mono;
     int is_underline_gray = 0;
     left = offset->x;
     top = offset->y;
     for (n = 0; n < length; ++n) {
-        image = glyphs[n]->image;
-        x = FX6_TRUNC(FX6_CEIL(left + posns[n].x));
-        y = FX6_TRUNC(FX6_CEIL(top + posns[n].y));
+        image = slots[n].glyph->image;
+        x = FX6_TRUNC(FX6_CEIL(left + slots[n].posn.x));
+        y = FX6_TRUNC(FX6_CEIL(top + slots[n].posn.y));
         if (image->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
             render_gray(x, y, surface, &(image->bitmap), fg_color);
             is_underline_gray = 1;

File src/freetype/ft_wrap.c

 _PGFT_GetTextRect(FreeTypeInstance *ft, PgFontObject *fontobj,
                   const FontRenderMode *mode, PGFT_String *text, SDL_Rect *r)
 {
-    FontText *font_text;
+    Layout *font_text;
     unsigned width;
     unsigned height;
     FT_Vector offset;
     FT_Pos underline_size;
     FT_Pos underline_top;
 
-    font_text = _PGFT_LoadFontText(ft, fontobj, mode, text);
-    if (!font_text) {
-        return -1;
-    }
+    font_text = _PGFT_LoadLayout(ft, fontobj, mode, text);
+    if (!font_text) goto error;
     _PGFT_GetRenderMetrics(mode, font_text, &width, &height, &offset,
                            &underline_size, &underline_top);
     r->x = -(Sint16)FX6_TRUNC(FX6_FLOOR(offset.x));
     r->w = (Uint16)width;
     r->h = (Uint16)height;
     return 0;
+
+  error:
+    return -1;
 }
 
 
     }
     memset(fontobj->_internals, 0x0, sizeof(FontInternals));
 
-    if (_PGFT_FontTextInit(ft, fontobj)) {
+    if (_PGFT_LayoutInit(ft, fontobj)) {
         _PGFT_free(fontobj->_internals);
         fontobj->_internals = 0;
         return -1;
 quit(PgFontObject *fontobj)
 {
     if (fontobj->_internals) {
-        _PGFT_FontTextFree(fontobj);
+        _PGFT_LayoutFree(fontobj);
         _PGFT_free(fontobj->_internals);
         fontobj->_internals = 0;
     }

File src/freetype/ft_wrap.h

  **********************************************************/
 
 typedef FT_UInt32 PGFT_char;
+typedef FT_UInt GlyphIndex_t;
 
 
 /**********************************************************
 } FontMetrics;
 
 typedef struct fontglyph_ {
-    FT_UInt glyph_index;
     FT_BitmapGlyph image;
 
     FT_Pos width;         /* 26.6 */
     FontMetrics v_metrics;
 } FontGlyph;
 
-typedef struct fonttext_ {
+typedef struct glyphslot_ {
+    GlyphIndex_t id;
+    FontGlyph *glyph;
+    FT_Vector posn;
+    FT_Vector kerning;
+} GlyphSlot;
+
+typedef struct layout_ {
+    FontRenderMode mode;
+
     int length;
 
     int top;       /* In pixels */
     FT_Vector offset;
     FT_Vector advance;
     FT_Pos ascender;
+    FT_Pos descender;
+    FT_Pos height;
+    FT_Pos max_advance;
     FT_Fixed underline_size;
     FT_Pos underline_pos;
 
     int buffer_size;
-    FontGlyph **glyphs;
-    FT_Vector *posns;
-
-    FontCache glyph_cache;
-} FontText;
+    GlyphSlot *glyphs;
+} Layout;
 
 struct fontsurface_;
 
 
 } FontSurface;
 
-#define PGFT_INTERNALS(f) ((FontInternals *)((f)->_internals))
-typedef struct FontInternals_ {
-    FontText active_text;
+typedef struct fontinternals_ {
+    Layout active_text;
+    FontCache glyph_cache;
 } FontInternals;
 
 typedef struct PGFT_String_ {
 } PGFT_String;
 
 #if defined(PGFT_DEBUG_CACHE)
-#define PGFT_FONT_CACHE(f) (PGFT_INTERNALS(f)->active_text.glyph_cache)
+#define PGFT_FONT_CACHE(f) ((f)->_internals->glyph_cache)
 #endif
 
 /**********************************************************
                      PGFT_char, const FontRenderMode *,
                      FT_UInt *, long *, long *, long *, long *,
                      double *, double *);
-void _PGFT_GetRenderMetrics(const FontRenderMode *, FontText *,
+void _PGFT_GetRenderMetrics(const FontRenderMode *, Layout *,
                             unsigned *, unsigned *, FT_Vector *,
                             FT_Pos *, FT_Fixed *);
 
                                 const FontColor *);
 
 
-/**************************************** Font text management ***************/
-int _PGFT_FontTextInit(FreeTypeInstance *, PgFontObject *);
-void _PGFT_FontTextFree(PgFontObject *);
-FontText *_PGFT_LoadFontText(FreeTypeInstance *, PgFontObject *,
-                            const FontRenderMode *, PGFT_String *);
-int _PGFT_LoadGlyph(FontGlyph *, PGFT_char, const FontRenderMode *, void *);
+/**************************************** Layout management ******************/
+int _PGFT_LayoutInit(FreeTypeInstance *, PgFontObject *);
+void _PGFT_LayoutFree(PgFontObject *);
+Layout *_PGFT_LoadLayout(FreeTypeInstance *, PgFontObject *,
+                         const FontRenderMode *, PGFT_String *);
+int _PGFT_LoadGlyph(FontGlyph *, GlyphIndex_t, const FontRenderMode *, void *);
 
 
 /**************************************** Glyph cache management *************/

File test/freetype_test.py

 import sys
 import os
 import ctypes
+import weakref
+import gc
 if __name__ == '__main__':
     pkg_dir = os.path.split(os.path.abspath(__file__))[0]
     parent_dir, pkg_name = os.path.split(pkg_dir)
 
 if is_pygame_pkg:
     from pygame.tests.test_utils import test_not_implemented, unittest, \
-                                        geterror
+                                        geterror, arrinter
 else:
-    from test.test_utils import test_not_implemented, unittest, geterror
+    from test.test_utils import test_not_implemented, unittest, geterror, \
+                                arrinter
 
 import pygame
 try:
 max_point_size = max_point_size_FX6 >> 6
 max_point_size_f = max_point_size_FX6 * 0.015625
 
+def surf_same_image(a, b):
+    """Return True if a's pixel buffer is identical to b's"""
+
+    a_sz = a.get_height() * a.get_pitch()
+    b_sz = b.get_height() * b.get_pitch()
+    if a_sz != b_sz:
+        return False
+    a_bytes = ctypes.string_at(a._pixels_address, a_sz)
+    b_bytes = ctypes.string_at(b._pixels_address, b_sz)
+    return a_bytes == b_bytes
+
 class FreeTypeFontTest(unittest.TestCase):
 
     _fixed_path = os.path.join(FONTDIR, 'test_fixed.otf')
         self.assertRaises(ValueError, font.render_to, surf, (0, 0),
                           'foobar', color, None, style=97, size=24)
 
-
     def test_freetype_Font_render(self):
 
         font = self._TEST_FONTS['sans']
         finally:
             font.antialiased = True
 
+    def test_freetype_Font_text_is_None(self):
+        f = ft.Font(self._sans_path, 36)
+        f.style = ft.STYLE_NORMAL
+        f.rotation = 0
+        text = 'ABCD'
+
+        # reference values
+        get_rect = f.get_rect(text)
+        f.vertical = True
+        get_rect_vert = f.get_rect(text)
+        self.assertTrue(get_rect_vert.width < get_rect.width)
+        self.assertTrue(get_rect_vert.height > get_rect.height)
+        f.vertical = False
+        render_to_surf = pygame.Surface(get_rect.size, pygame.SRCALPHA, 32)
+        arr = arrinter.Array(get_rect.size, 'u', 1)
+        render = f.render(text, (0, 0, 0))
+        render_to = f.render_to(render_to_surf, (0, 0), text, (0, 0, 0))
+        render_raw = f.render_raw(text)
+        render_raw_to = f.render_raw_to(arr, text)
+
+        # comparisons
+        surf = pygame.Surface(get_rect.size, pygame.SRCALPHA, 32)
+        self.assertEqual(f.get_rect(None), get_rect)
+        s, r = f.render(None, (0, 0, 0))
+        self.assertEqual(r, render[1])
+        self.assertTrue(surf_same_image(s, render[0]))
+        r = f.render_to(surf, (0, 0), None, (0, 0, 0))
+        self.assertEqual(r, render_to)
+        self.assertTrue(surf_same_image(surf, render_to_surf))
+        px, sz = f.render_raw(None)
+        self.assertEqual(sz, render_raw[1])
+        self.assertEqual(px, render_raw[0])
+        sz = f.render_raw_to(arr, None)
+        self.assertEqual(sz, render_raw_to)
+
+        # vertical: trigger glyph positioning.
+        f.vertical = True
+        r = f.get_rect(None)
+        self.assertEqual(r, get_rect_vert)
+        f.vertical = False
+
+        # wide style: trigger glyph reload
+        r = f.get_rect(None, style=ft.STYLE_WIDE)
+        self.assertEqual(r.height, get_rect.height)
+        self.assertTrue(r.width > get_rect.width)
+        r = f.get_rect(None)
+        self.assertEqual(r, get_rect)
+
+        # rotated: trigger glyph reload
+        r = f.get_rect(None, rotation=90)
+        self.assertEqual(r.width, get_rect.height)
+        self.assertEqual(r.height, get_rect.width)
+
+        # this method will not support None text
+        self.assertRaises(TypeError, f.get_metrics, None)
+
     if pygame.HAVE_NEWBUF:
         def test_newbuf(self):
             self.NEWBUF_test_newbuf()
         f.get_metrics(many_glyphs, size=8)
         f.get_metrics(many_glyphs, size=10)
         ccount, cdelete_count, caccess, chit, cmiss = f._debug_cache_stats
-        print (ccount, cdelete_count, caccess, chit, cmiss)
         self.assertTrue(ccount < count)
         self.assertEqual((ccount + cdelete_count, caccess, chit, cmiss),
                          (count, access, hit, miss))
         s = 'M' * 100000  # Way too long for an SDL surface
         self.assertRaises(pygame.error, font.render, s, (0, 0, 0))
 
+    def test_garbage_collection(self):
+        """Check reference counting on returned new references"""
+        def ref_items(seq):
+            return [weakref.ref(o) for o in seq]
+
+        font = self._TEST_FONTS['bmp-8-75dpi']
+        font.size = font.get_sizes()[0][0]
+        text = 'A'
+        rect = font.get_rect(text)
+        surf = pygame.Surface(rect.size, pygame.SRCALPHA, 32)
+        refs = []
+        refs.extend(ref_items(font.render(text, (0, 0, 0))))
+        refs.append(weakref.ref(font.render_to(surf, (0, 0), text, (0, 0, 0))))
+        refs.append(weakref.ref(font.get_rect(text)))
+
+        n = len(refs)
+        self.assertTrue(n > 0)
+        gc.collect()
+        for i in range(n):
+            self.assertTrue(refs[i]() is None, "ref %d not collected" % i)
+
+        try:
+            from sys import getrefcount
+        except ImportError:
+            pass
+        else:
+            array = arrinter.Array(rect.size, 'u', 1)
+            o = font.render_raw(text)
+            self.assertEqual(getrefcount(o), 2)
+            self.assertEqual(getrefcount(o[0]), 2)
+            self.assertEqual(getrefcount(o[1]), 2)
+            self.assertEqual(getrefcount(font.render_raw_to(array, text)), 1)
+            o = font.get_metrics('AB')
+            self.assertEqual(getrefcount(o), 2)
+            for i in range(len(o)):
+                self.assertEqual(getrefcount(o[i]), 2,
+                                 "refcount fail for item %d" % i)
+            o = font.get_sizes()
+            self.assertEqual(getrefcount(o), 2)
+            for i in range(len(o)):
+                self.assertEqual(getrefcount(o[i]), 2,
+                                 "refcount fail for item %d" % i)
+
 class FreeTypeTest(unittest.TestCase):
 
     def test_resolution(self):