Commits

Lenard Lindstrom committed 2aa48db

add bitmap size introspection to pygame.freetype.Font (toward Issue #75)

To the freetype.Font type, add the fixed_sizes property and the get_sizes()
method. For unit tests on the new features, add two new fonts to
pygame/tests/fixtures/fonts: PyGameMono.otf and PyGameMono-8.bdf.
PyGameMono.otf is a proper fixed-width outline font. It also contains
two embedded bitmaps. PyGameMono-8.bdf is generated from one of those
bitmaps.

This changeset leaves pygame.freetype.Font bitmap incomplete. No bitmap specific
rendering is implemented. Bitmap fonts don't transform properly. An embedding
bitmap strike in an outline file is chosen preferentially over a generated
bitmap. Bitmap strike size selection is poorly handled (a returned get_sizes()
size tuple should include the point size used for selection with a ptsize
argument.)

Comments (0)

Files changed (10)

docs/reST/ref/freetype.rst

 
 | :sl:`Enhanced Pygame module for loading and rendering computer fonts`
 
---- Note that some features may change before a formal release
-
 The :mod:`pygame.freetype` module allows for the rendering of all font file formats
 supported by FreeType, namely ``TTF``, Type1, ``CFF``, OpenType, ``SFNT``, ``PCF``,
 ``FNT``, ``BDF``, ``PFR`` and Type42 fonts. It can render any UTF-32 character in a
 .. function:: init
 
    | :sl:`Initialize the underlying FreeType 2 library.`
-   | :sg:`init(cache_size=64, resolution=72) -> None`
+   | :sg:`init(cache_size=64, resolution=72)`
 
    This function initializes the underlying FreeType 2 library and must be
    called before trying to use any of the functionality of the 'freetype'
 .. function:: quit
 
    | :sl:`Shut down the underlying FreeType 2 library.`
-   | :sg:`quit() -> None`
+   | :sg:`quit()`
 
    This function de-initializes the ``freetype`` module. After calling this
    function, you should not invoke any class, method or function related to the
 .. function:: set_default_resolution
 
    | :sl:`Set the default pixel size in dots per inch for the module`
-   | :sg:`set_default_resolution([resolution]) -> None`
+   | :sg:`set_default_resolution([resolution])`
 
    Set the default pixel size, in dots per inch, for the module. If the
    optional argument is omitted or zero the resolution is reset to 72.
       This is the average value of all glyphs in the font.
       It is not adjusted for strong or rotation.
 
+   .. method:: get_sizes
+
+      | :sl:`return the available sizes of embedded bitmaps`
+      | :sg:`get_sizes() -> [(int, int, float, float, float), ...]`
+      | :sg:`get_sizes() -> []`
+
+      This returns a list of tuple records, one for each point size
+      supported. Each tuple containing the height in pixels, width in pixels,
+      nominal size of the strike in fractional points, horizontal ppem
+      (nominal width) in fractional pixels, and vertical ppem (nominal height)
+      in fractional pixels. The point size is equivalent to the rounded
+      vertical ppem ― index 3 ― of the tuple. Use it for the ptsize argument
+      of the render methods.
+
    .. method:: render
 
       | :sl:`Return rendered text as a surface`
    .. attribute:: style
 
       | :sl:`The font's style flags`
-      | :sg:`style <-> int`
+      | :sg:`style -> int`
 
       Gets or sets the default style of the Font. This default style will be
       used for all text rendering and size calculations unless overridden
    .. attribute:: underline
 
       | :sl:`The state of the font's underline style flag`
-      | :sg:`underline <-> bool`
+      | :sg:`underline -> bool`
 
       Gets or sets whether the font will be underlined when drawing text. This
       default style value will be used for all text rendering and size
    .. attribute:: strong
 
       | :sl:`The state of the font's strong style flag`
-      | :sg:`strong <-> bool`
+      | :sg:`strong -> bool`
 
       Gets or sets whether the font will be bold when drawing text. This
       default style value will be used for all text rendering and size
    .. attribute:: oblique
 
       | :sl:`The state of the font's oblique style flag`
-      | :sg:`oblique <-> bool`
+      | :sg:`oblique -> bool`
 
       Gets or sets whether the font will be rendered as oblique. This
       default style value will be used for all text rendering and size
    .. attribute:: wide
 
       | :sl:`The state of the font's wide style flag`
-      | :sg:`wide <-> bool`
+      | :sg:`wide -> bool`
 
       Gets or sets whether the font will be stretched horizontally
       when drawing text. It produces a result similar to font.Font's
    .. attribute:: strength
 
       | :sl:`The strength associated with the strong or wide font styles`
-      | :sg:`strength <-> float`
+      | :sg:`strength -> float`
 
       The amount by which a font glyph's size is enlarged for the
       strong or wide transformations, as a fraction of the untransformed
    .. attribute:: underline_adjustment
 
       | :sl:`Adjustment factor for the underline position`
-      | :sg:`underline_adjustment <-> float`
+      | :sg:`underline_adjustment -> float`
 
       Gets or sets a factor which, when positive, is multiplied with the
       font's underline offset to adjust the underline position. A negative
       | :sl:`Gets whether the font is fixed-width`
       | :sg:`fixed_width -> bool`
 
-      Read only. Returns whether this Font is a fixed-width (bitmap) font.
+      Read only. Return True if the font contains fixed-width characters
+      (for example Courier, Bitstream Vera Sans Mono, Andale Mono).
 
-      Note that scalable fonts whose glyphs are all the same width (i.e.
-      monospace ``TTF`` fonts used for programming) are not considered fixed
-      width.
+   .. attribute:: fixed_sizes
+
+      | :sl:`the number of embedded bitmap sizes the font`
+      | :sg:`fixed_sizes -> int`
+
+      Read only. Return the number of point sizes for which the font contains
+      bitmap character images. If zero then the font is not a bitmap font. 
+      A scalable font may contain pre-rendered point sizes.
+
+   .. attribute:: scalable
+
+      | :sl:`Gets whether the font is scalable`
+      | :sg:`scalable -> bool`
+
+      Read only. Return True if the font contains outline glyphs. If so,
+      the point size is not limited to available bitmap sizes.
 
    .. attribute:: antialiased
 
       | :sl:`Font anti-aliasing mode`
-      | :sg:`antialiased <-> bool`
+      | :sg:`antialiased -> bool`
 
       Gets or sets the font's anti-aliasing mode. This defaults to ``True`` on
       all fonts, which are rendered with full 8 bit blending.
    .. attribute:: origin
 
       | :sl:`Font render to text origin mode`
-      | :sg:`vertical -> bool`
+      | :sg:`origin -> bool`
 
       If set True, then when rendering to an existing surface, the position
       is taken to be that of the text origin. Otherwise the render position is
    .. attribute:: ucs4
 
       | :sl:`Enable UCS-4 mode`
-      | :sg:`ucs4 <-> bool`
+      | :sg:`ucs4 -> bool`
 
       Gets or sets the decoding of Unicode text. By default, the
       freetype module performs UTF-16 surrogate pair decoding on Unicode text.
                       [['xbm_cursors',
                           ['*.xbm']],
                        ['fonts',
-                          ['*.ttf', '*.otf']]]]]])
+                          ['*.ttf', '*.otf', '*.bdf']]]]]])
 
 #examples
 add_datafiles(data_files, 'pygame/examples',
 static PyObject *_ftfont_getsizeddescender(PgFontObject *, PyObject *);
 static PyObject *_ftfont_getsizedheight(PgFontObject *, PyObject *);
 static PyObject *_ftfont_getsizedglyphheight(PgFontObject *, PyObject *);
+static PyObject *_ftfont_getsizes(PgFontObject *);
 
 /* static PyObject *_ftfont_copy(PgFontObject *); */
 
 static int _ftfont_setstyle(PgFontObject *, PyObject *, void *);
 static PyObject *_ftfont_getname(PgFontObject *, void *);
 static PyObject *_ftfont_getpath(PgFontObject *, void *);
+static PyObject *_ftfont_getscalable(PgFontObject *, void *);
 static PyObject *_ftfont_getfixedwidth(PgFontObject *, void *);
+static PyObject *_ftfont_getfixedsizes(PgFontObject *, void *);
 static PyObject *_ftfont_getstrength(PgFontObject *, void *);
 static int _ftfont_setstrength(PgFontObject *, PyObject *, void *);
 static PyObject *_ftfont_getunderlineadjustment(PgFontObject *, void *);
         DOC_FONTGETMETRICS
     },
     {
+        "get_sizes",
+        (PyCFunction) _ftfont_getsizes,
+        METH_NOARGS,
+        DOC_FONTGETSIZES
+    },
+    {
         "render",
         (PyCFunction)_ftfont_render,
         METH_VARARGS | METH_KEYWORDS,
         0
     },
     {
+        "scalable",
+        (getter)_ftfont_getscalable,
+        0,
+        DOC_FONTSCALABLE,
+        0
+    },
+    {
         "fixed_width",
         (getter)_ftfont_getfixedwidth,
         0,
         0
     },
     {
+        "fixed_sizes",
+        (getter)_ftfont_getfixedsizes,
+        0,
+        DOC_FONTFIXEDSIZES,
+        0
+    },
+    {
         "antialiased",
         (getter)_ftfont_getrender_flag,
         (setter)_ftfont_setrender_flag,
 }
 
 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;
+}
+
+static PyObject *
 _ftfont_getfixedwidth(PgFontObject *self, void *closure)
 {
     FreeTypeInstance *ft;
     return fixed_width >= 0 ? PyBool_FromLong(fixed_width) : 0;
 }
 
+static PyObject *
+_ftfont_getfixedsizes(PgFontObject *self, void *closure)
+{
+    FreeTypeInstance *ft;
+    long num_fixed_sizes;
+    ASSERT_GRAB_FREETYPE(ft, 0);
+
+    ASSERT_SELF_IS_ALIVE(self);
+    num_fixed_sizes = _PGFT_Font_NumFixedSizes(ft, (PgFontObject *)self);
+    return num_fixed_sizes >= 0 ? PyInt_FromLong(num_fixed_sizes) : 0;
+}
+
 
 /** Generic render flag attributes */
 static PyObject *
 }
 
 static PyObject *
+_ftfont_getsizes(PgFontObject *self)
+{
+    int nsizes;
+    unsigned i;
+    int rc;
+    long height = 0, width = 0;
+    double size = 0.0;
+    double x_ppem = 0.0, y_ppem = 0.0;
+    PyObject *size_list = 0;
+    PyObject *size_item;
+    FreeTypeInstance *ft;
+    ASSERT_GRAB_FREETYPE(ft, 0);
+
+    nsizes = _PGFT_Font_NumFixedSizes(ft, self);
+    if (nsizes < 0) goto error;
+    size_list = PyList_New(nsizes);
+    if (!size_list) goto error;
+    for (i = 0; i < nsizes; ++i) {
+        rc = _PGFT_Font_GetAvailableSize(ft, self, i,
+                                         &height, &width,
+                                         &size, &x_ppem, &y_ppem);
+        if (rc < 0) goto error;
+        assert(rc > 0);
+        size_item = Py_BuildValue("llddd", height, width, size, x_ppem, y_ppem);
+        if (!size_item) goto error;
+        PyList_SET_ITEM(size_list, i, size_item);
+    }
+    return size_list;
+
+  error:
+    Py_XDECREF(size_list);
+    return 0;
+}
+
+static PyObject *
 _ftfont_render_raw(PgFontObject *self, PyObject *args, PyObject *kwds)
 {
     /* keyword list */

src/doc/freetype_doc.h

 
 #define DOC_PYGAMEFREETYPEGETVERSION "get_version() -> (int, int, int)\nReturn the FreeType 2 version"
 
-#define DOC_PYGAMEFREETYPEINIT "init(cache_size=64, resolution=72) -> None\nInitialize the underlying FreeType 2 library."
+#define DOC_PYGAMEFREETYPEINIT "init(cache_size=64, resolution=72)\nInitialize the underlying FreeType 2 library."
 
-#define DOC_PYGAMEFREETYPEQUIT "quit() -> None\nShut down the underlying FreeType 2 library."
+#define DOC_PYGAMEFREETYPEQUIT "quit()\nShut down the underlying FreeType 2 library."
 
 #define DOC_PYGAMEFREETYPEWASINIT "was_init() -> bool\nReturn whether the the FreeType 2 library is initialized."
 
 #define DOC_PYGAMEFREETYPEGETDEFAULTRESOLUTION "get_default_resolution() -> long\nReturn the default pixel size in dots per inch"
 
-#define DOC_PYGAMEFREETYPESETDEFAULTRESOLUTION "set_default_resolution([resolution]) -> None\nSet the default pixel size in dots per inch for the module"
+#define DOC_PYGAMEFREETYPESETDEFAULTRESOLUTION "set_default_resolution([resolution])\nSet the default pixel size in dots per inch for the module"
 
 #define DOC_PYGAMEFREETYPEGETDEFAULTFONT "get_default_font() -> string\nGet the filename of the default font"
 
 
 #define DOC_FONTGETSIZEDGLYPHHEIGHT "get_sized_glyph_height() -> int\nThe scaled bounding box height of the font in pixels"
 
+#define DOC_FONTGETSIZES "get_sizes() -> [(int, int, float, float, float), ...]\nget_sizes() -> []\nreturn the available sizes of embedded bitmaps"
+
 #define DOC_FONTRENDER "render(text, fgcolor, bgcolor=None, style=STYLE_DEFAULT, rotation=0, ptsize=default) -> (Surface, Rect)\nReturn rendered text as a surface"
 
 #define DOC_FONTRENDERTO "render(surf, dest, text, fgcolor, bgcolor=None, style=STYLE_DEFAULT, rotation=0, ptsize=default) -> Rect\nRender text onto an existing surface"
 
 #define DOC_FONTRENDERRAWTO "render_raw_to(array, text, dest=None, style=STYLE_DEFAULT, rotation=0, ptsize=default, invert=False) -> (int, int)\nRender text into an array of ints"
 
-#define DOC_FONTSTYLE "style <-> int\nThe font's style flags"
+#define DOC_FONTSTYLE "style -> int\nThe font's style flags"
 
-#define DOC_FONTUNDERLINE "underline <-> bool\nThe state of the font's underline style flag"
+#define DOC_FONTUNDERLINE "underline -> bool\nThe state of the font's underline style flag"
 
-#define DOC_FONTSTRONG "strong <-> bool\nThe state of the font's strong style flag"
+#define DOC_FONTSTRONG "strong -> bool\nThe state of the font's strong style flag"
 
-#define DOC_FONTOBLIQUE "oblique <-> bool\nThe state of the font's oblique style flag"
+#define DOC_FONTOBLIQUE "oblique -> bool\nThe state of the font's oblique style flag"
 
-#define DOC_FONTWIDE "wide <-> bool\nThe state of the font's wide style flag"
+#define DOC_FONTWIDE "wide -> bool\nThe state of the font's wide style flag"
 
-#define DOC_FONTSTRENGTH "strength <-> float\nThe strength associated with the strong or wide font styles"
+#define DOC_FONTSTRENGTH "strength -> float\nThe strength associated with the strong or wide font styles"
 
-#define DOC_FONTUNDERLINEADJUSTMENT "underline_adjustment <-> float\nAdjustment factor for the underline position"
+#define DOC_FONTUNDERLINEADJUSTMENT "underline_adjustment -> float\nAdjustment factor for the underline position"
 
 #define DOC_FONTFIXEDWIDTH "fixed_width -> bool\nGets whether the font is fixed-width"
 
-#define DOC_FONTANTIALIASED "antialiased <-> bool\nFont anti-aliasing mode"
+#define DOC_FONTFIXEDSIZES "fixed_sizes -> int\nthe number of embedded bitmap sizes the font"
+
+#define DOC_FONTSCALABLE "scalable -> bool\nGets whether the font is scalable"
+
+#define DOC_FONTANTIALIASED "antialiased -> bool\nFont anti-aliasing mode"
 
 #define DOC_FONTKERNING "kerning -> bool\nCharacter kerning mode"
 
 #define DOC_FONTVERTICAL "vertical -> bool\nFont vertical mode"
 
-#define DOC_FONTORIGIN "vertical -> bool\nFont render to text origin mode"
+#define DOC_FONTORIGIN "origin -> bool\nFont render to text origin mode"
 
 #define DOC_FONTPAD "pad -> bool\npadded boundary mode"
 
-#define DOC_FONTUCS4 "ucs4 <-> bool\nEnable UCS-4 mode"
+#define DOC_FONTUCS4 "ucs4 -> bool\nEnable UCS-4 mode"
 
 #define DOC_FONTRESOLUTION "resolution -> int\nPixel resolution in dots per inch"
 
 Return the FreeType 2 version
 
 pygame.freetype.init
- init(cache_size=64, resolution=72) -> None
+ init(cache_size=64, resolution=72)
 Initialize the underlying FreeType 2 library.
 
 pygame.freetype.quit
- quit() -> None
+ quit()
 Shut down the underlying FreeType 2 library.
 
 pygame.freetype.was_init
 Return the default pixel size in dots per inch
 
 pygame.freetype.set_default_resolution
- set_default_resolution([resolution]) -> None
+ set_default_resolution([resolution])
 Set the default pixel size in dots per inch for the module
 
 pygame.freetype.get_default_font
  get_sized_glyph_height() -> int
 The scaled bounding box height of the font in pixels
 
+pygame.freetype.Font.get_sizes
+ get_sizes() -> [(int, int, float, float, float), ...]
+ get_sizes() -> []
+return the available sizes of embedded bitmaps
+
 pygame.freetype.Font.render
  render(text, fgcolor, bgcolor=None, style=STYLE_DEFAULT, rotation=0, ptsize=default) -> (Surface, Rect)
 Return rendered text as a surface
 Render text into an array of ints
 
 pygame.freetype.Font.style
- style <-> int
+ style -> int
 The font's style flags
 
 pygame.freetype.Font.underline
- underline <-> bool
+ underline -> bool
 The state of the font's underline style flag
 
 pygame.freetype.Font.strong
- strong <-> bool
+ strong -> bool
 The state of the font's strong style flag
 
 pygame.freetype.Font.oblique
- oblique <-> bool
+ oblique -> bool
 The state of the font's oblique style flag
 
 pygame.freetype.Font.wide
- wide <-> bool
+ wide -> bool
 The state of the font's wide style flag
 
 pygame.freetype.Font.strength
- strength <-> float
+ strength -> float
 The strength associated with the strong or wide font styles
 
 pygame.freetype.Font.underline_adjustment
- underline_adjustment <-> float
+ underline_adjustment -> float
 Adjustment factor for the underline position
 
 pygame.freetype.Font.fixed_width
  fixed_width -> bool
 Gets whether the font is fixed-width
 
+pygame.freetype.Font.fixed_sizes
+ fixed_sizes -> int
+the number of embedded bitmap sizes the font
+
+pygame.freetype.Font.scalable
+ scalable -> bool
+Gets whether the font is scalable
+
 pygame.freetype.Font.antialiased
- antialiased <-> bool
+ antialiased -> bool
 Font anti-aliasing mode
 
 pygame.freetype.Font.kerning
 Font vertical mode
 
 pygame.freetype.Font.origin
- vertical -> bool
+ origin -> bool
 Font render to text origin mode
 
 pygame.freetype.Font.pad
 padded boundary mode
 
 pygame.freetype.Font.ucs4
- ucs4 <-> bool
+ ucs4 -> bool
 Enable UCS-4 mode
 
 pygame.freetype.Font.resolution

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);
         RAISE(PyExc_RuntimeError, _PGFT_GetError(ft));
         return -1;
     }
-    return FT_IS_FIXED_WIDTH(font);
+    return FT_IS_FIXED_WIDTH(font) ? 1 : 0;
+}
+
+int
+_PGFT_Font_NumFixedSizes(FreeTypeInstance *ft, PgFontObject *fontobj)
+{
+    FT_Face font = _PGFT_GetFont(ft, fontobj);
+
+    if (!font) {
+        RAISE(PyExc_RuntimeError, _PGFT_GetError(ft));
+        return -1;
+    }
+    return FT_HAS_FIXED_SIZES(font) ? font->num_fixed_sizes : 0;
+}
+
+int
+_PGFT_Font_GetAvailableSize(FreeTypeInstance *ft, PgFontObject *fontobj,
+                            unsigned n, long *height_p, long *width_p,
+                            double *size_p, double *x_ppem_p, double *y_ppem_p)
+{
+    FT_Face font = _PGFT_GetFont(ft, fontobj);
+    FT_Bitmap_Size *bitmap_size_p;
+
+    if (!font) {
+        RAISE(PyExc_RuntimeError, _PGFT_GetError(ft));
+        return -1;
+    }
+    if (!FT_HAS_FIXED_SIZES(font) || n > font->num_fixed_sizes) /* cond. or */ {
+        return 0;
+    }
+    bitmap_size_p = font->available_sizes + n;
+    *height_p = (long)bitmap_size_p->height;
+    *width_p = (long)bitmap_size_p->width;
+    *size_p = FX6_TO_DBL(bitmap_size_p->size);
+    *x_ppem_p = FX6_TO_DBL(bitmap_size_p->x_ppem);
+    *y_ppem_p = FX6_TO_DBL(bitmap_size_p->y_ppem);
+    return 1;
 }
 
 const char *
         RAISE(PyExc_RuntimeError, _PGFT_GetError(ft));
         return 0;
     }
-    return font->family_name;
+    return font->family_name ? font->family_name : "";
 }
 
 /* All the font metric functions raise an exception and return 0 on an error.

src/freetype/ft_wrap.h

 #define FX16_CEIL_TO_FX6(x) (((x) + 1023L) >> 10)
 #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.5259e-5 /* 65536.0^-1 */)
+#define FX16_TO_DBL(x) ((x) * 1.52587890625e-5 /* 2.0^-16 */)
 #define DBL_TO_FX16(d) ((FT_Fixed)((d) * 65536.0))
+#define FX6_TO_DBL(x) ((x) * 1.5625e-2 /* 2.0^-6 */)
 
 /* Internal configuration variables */
 #define PGFT_DEFAULT_CACHE_SIZE 64
                                FT_UInt16);
 long _PGFT_Font_GetGlyphHeightSized(FreeTypeInstance *, PgFontObject *,
                                     FT_UInt16);
+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,
+                                long *, long *, double *, double *, double *);
 const char *_PGFT_Font_GetName(FreeTypeInstance *, PgFontObject *);
 int _PGFT_TryLoadFont_Filename(FreeTypeInstance *,
                                PgFontObject *, const char *, long);

test/fixtures/fonts/PyGameMono-8.bdf

+STARTFONT 2.1
+FONT -FontForge-PyGameMono-Medium-R-Normal--8-80-75-75-C-80-ISO10646-1
+SIZE 8 75 75
+FONTBOUNDINGBOX 6 7 0 0
+COMMENT "Generated by fontforge, http://fontforge.sourceforge.net"
+COMMENT "Created by Lenard Lindstrom,,, with FontForge 2.0 (http://fontforge.sf.net)"
+STARTPROPERTIES 29
+FOUNDRY "FontForge"
+FAMILY_NAME "PyGameMono"
+WEIGHT_NAME "Medium"
+SLANT "R"
+SETWIDTH_NAME "Normal"
+ADD_STYLE_NAME ""
+PIXEL_SIZE 8
+POINT_SIZE 80
+RESOLUTION_X 75
+RESOLUTION_Y 75
+SPACING "C"
+AVERAGE_WIDTH 80
+CHARSET_REGISTRY "ISO10646"
+CHARSET_ENCODING "1"
+FONTNAME_REGISTRY ""
+CHARSET_COLLECTIONS "ISO10646-1"
+FONT_NAME "PyGameMono"
+FACE_NAME "PyGame Mono"
+FONT_VERSION "001.000"
+FONT_ASCENT 6
+FONT_DESCENT 2
+UNDERLINE_POSITION -1
+UNDERLINE_THICKNESS 1
+RAW_ASCENT 800
+RAW_DESCENT 200
+RELATIVE_WEIGHT 50
+RELATIVE_SETWIDTH 50
+FIGURE_WIDTH -1
+AVG_UPPERCASE_WIDTH 80
+ENDPROPERTIES
+CHARS 5
+STARTCHAR .notdef
+ENCODING 0
+SWIDTH 1000 0
+DWIDTH 8 0
+BBX 6 6 0 0
+BITMAP
+FC
+84
+84
+84
+84
+FC
+ENDCHAR
+STARTCHAR A
+ENCODING 65
+SWIDTH 1000 0
+DWIDTH 8 0
+BBX 6 7 0 0
+BITMAP
+78
+84
+84
+FC
+84
+84
+84
+ENDCHAR
+STARTCHAR B
+ENCODING 66
+SWIDTH 1000 0
+DWIDTH 8 0
+BBX 6 6 0 0
+BITMAP
+FC
+44
+78
+4C
+44
+FC
+ENDCHAR
+STARTCHAR C
+ENCODING 67
+SWIDTH 1000 0
+DWIDTH 8 0
+BBX 6 6 0 0
+BITMAP
+78
+C4
+C0
+C0
+C4
+78
+ENDCHAR
+STARTCHAR u13079
+ENCODING 77945
+SWIDTH 1000 0
+DWIDTH 8 0
+BBX 6 4 0 1
+BITMAP
+78
+B4
+B4
+78
+ENDCHAR
+ENDFONT

test/fixtures/fonts/PyGameMono.otf

Binary file added.

test/fixtures/fonts/PyGameMono.sfd

+SplineFontDB: 3.0
+FontName: PyGameMono
+FullName: PyGame Mono
+FamilyName: PyGameMono
+Weight: Medium
+Copyright: Created by Lenard Lindstrom,,, with FontForge 2.0 (http://fontforge.sf.net)
+UComments: "2013-10-11: Created.+AAoACgAA-pygame - Python Game Library+AAoA-Test font PyGameMono, Copyright (C) 2013  Lenard Lindstrom+AAoACgAA-This library is free software; you can redistribute it and/or+AAoA-modify it under the terms of the GNU Library General Public+AAoA-License as published by the Free Software Foundation; either+AAoA-version 2 of the License, or (at your option) any later version.+AAoACgAA-This library is distributed in the hope that it will be useful,+AAoA-but WITHOUT ANY WARRANTY; without even the implied warranty of+AAoA-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+AAoA-Library General Public License for more details.+AAoACgAA-You should have received a copy of the GNU Library General Public+AAoA-License along with this library; if not, write to the Free+AAoA-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA+AAoACgAA" 
+Version: 001.000
+ItalicAngle: 0
+UnderlinePosition: -100
+UnderlineWidth: 50
+Ascent: 800
+Descent: 200
+LayerCount: 2
+Layer: 0 0 "Back"  1
+Layer: 1 0 "Fore"  0
+XUID: [1021 912 78401767 10725709]
+FSType: 8
+OS2Version: 0
+OS2_WeightWidthSlopeOnly: 0
+OS2_UseTypoMetrics: 1
+CreationTime: 1381544181
+ModificationTime: 1381781368
+PfmFamily: 49
+TTFWeight: 500
+TTFWidth: 5
+LineGap: 90
+VLineGap: 0
+OS2TypoAscent: 0
+OS2TypoAOffset: 1
+OS2TypoDescent: 0
+OS2TypoDOffset: 1
+OS2TypoLinegap: 90
+OS2WinAscent: 0
+OS2WinAOffset: 1
+OS2WinDescent: 0
+OS2WinDOffset: 1
+HheadAscent: 0
+HheadAOffset: 1
+HheadDescent: 0
+HheadDOffset: 1
+OS2FamilyClass: 2319
+OS2Vendor: 'PfEd'
+MarkAttachClasses: 1
+DEI: 91125
+LangName: 1033 
+Encoding: UnicodeFull
+UnicodeInterp: none
+NameList: Adobe Glyph List
+DisplaySize: -24
+AntiAlias: 1
+FitToEm: 1
+WinInfo: 77904 24 18
+BeginPrivate: 0
+EndPrivate
+TeXData: 1 0 0 346030 173015 115343 0 1048576 115343 783286 444596 497025 792723 393216 433062 380633 303038 157286 324010 404750 52429 2506097 1059062 262144
+BeginChars: 1114112 5
+
+StartChar: A
+Encoding: 65 65 0
+Width: 1000
+VWidth: 0
+HStem: 0 21G<72.5 119.695 682.5 726.5> 384 111<312.148 495.257> 777 123<264.353 535.647>
+VStem: 0 139<585.021 689.951> 661 139<585.002 688.082>
+LayerCount: 2
+Fore
+SplineSet
+139 636 m 0
+ 139 558.168 255.928 495 400 495 c 0
+ 544.072 495 661 558.168 661 636 c 0
+ 661 713.832 544.072 777 400 777 c 0
+ 255.928 777 139 713.832 139 636 c 0
+100 0 m 1
+ 45 234 0 361.682 0 600 c 0
+ 0 834 205 900 400 900 c 24
+ 595 900 800 822 800 600 c 0
+ 800 360.834 753 234 700 0 c 1
+ 665 180 590.045 384 400 384 c 24
+ 209.955 384 139.39 149.76 100 0 c 1
+EndSplineSet
+Validated: 1
+EndChar
+
+StartChar: B
+Encoding: 66 66 1
+Width: 1000
+VWidth: 0
+HStem: 0 91<307.884 570.044> 355 97<317.65 572.008> 724 76.4445<315.347 574.736>
+VStem: 120 80<174.135 277.365 535.109 572> 694 106<173.306 274.441 532.71 643.215>
+LayerCount: 2
+Fore
+SplineSet
+200 223 m 0
+ 200 150 303 91 437 91 c 0
+ 571 91 694 150 694 223 c 0
+ 694 296 571 355 437 355 c 0
+ 303 355 200 296 200 223 c 0
+202 588 m 0
+ 202 513 312 452 448 452 c 0
+ 584 452 694 513 694 588 c 0
+ 694 663 584 724 448 724 c 0
+ 312 724 202 663 202 588 c 0
+800 200 m 0
+ 800 92 749 1 552 0 c 0
+ 430 -0 253 -0 0 0 c 1
+ 85 41 120 216 120 400 c 0
+ 120 572 73 756 0 800 c 1
+ 150 800 273.778 800.444 372.519 800.444 c 0
+ 421.889 800.444 465 800.333 502 800 c 0
+ 785 799 800 665 800 600 c 0
+ 800 540 781 510 760 490 c 0
+ 730 461 694 453 694 400 c 0
+ 694 358 723 339 760 310 c 0
+ 781 294 800 259 800 200 c 0
+EndSplineSet
+Validated: 1
+EndChar
+
+StartChar: .notdef
+Encoding: 0 0 2
+Width: 1000
+VWidth: 0
+HStem: 0 88<283.168 514.832> 712 88<283.168 514.832>
+VStem: 0 83<285.634 514.366> 715 85<285.634 514.366>
+LayerCount: 2
+Fore
+SplineSet
+83 400 m 0
+ 83 227.776 224.568 88 399 88 c 0
+ 573.432 88 715 227.776 715 400 c 0
+ 715 572.224 573.432 712 399 712 c 0
+ 224.568 712 83 572.224 83 400 c 0
+0 800 m 1
+ 800 800 l 1
+ 800 0 l 1
+ 0 0 l 1
+ 0 800 l 1
+EndSplineSet
+Validated: 1
+Comment: "Default character" 
+EndChar
+
+StartChar: C
+Encoding: 67 67 3
+Width: 1000
+VWidth: 0
+HStem: 0 110<323.65 572.264> 690 110<326.99 550.385>
+VStem: 0 147<291.893 504.85>
+LayerCount: 2
+Fore
+SplineSet
+425 110 m 0
+ 564 110 687 167 788 230 c 1
+ 796 230 796 230 800 230 c 1
+ 800 222 800 224 800 216 c 1
+ 753 98 621 -0 450 0 c 0
+ 227 0 0 165 0 400 c 24
+ 0 635 235 800 450 800 c 1
+ 580 800 745 668 800 584 c 1
+ 800 575 799 576 800 570 c 1
+ 796 570 796 570 791 570 c 1
+ 684 628 535 690 425 690 c 0
+ 277 690 147 557 147 400 c 0
+ 147 243 268 110 425 110 c 0
+EndSplineSet
+Validated: 1
+EndChar
+
+StartChar: u13079
+Encoding: 77945 77945 4
+Width: 1000
+VWidth: 0
+HStem: 200 66<262.666 534.533> 302 196<339.508 460.492> 534 66<262.278 535.629>
+VStem: 302 196<339.508 460.492>
+CounterMasks: 1 e0
+LayerCount: 2
+Fore
+SplineSet
+302 400 m 0
+ 302 454.096 345.904 498 400 498 c 0
+ 454.096 498 498 454.096 498 400 c 0
+ 498 345.904 454.096 302 400 302 c 0
+ 345.904 302 302 345.904 302 400 c 0
+146 400 m 0
+ 146 326.032 259.792 266 400 266 c 0
+ 540.208 266 654 326.032 654 400 c 0
+ 654 473.968 540.208 534 400 534 c 0
+ 259.792 534 146 473.968 146 400 c 0
+0 400 m 1
+ 124 550 225.587 600 400 600 c 24
+ 574.413 600 690 532 800 400 c 1
+ 720 254 574.413 200 400 200 c 24
+ 225.587 200 90 234 0 400 c 1
+EndSplineSet
+Validated: 1
+EndChar
+EndChars
+BitmapFont: 8 6 6 2 1 
+BDFChar: 0 65 8 0 5 0 6
+G_CbJKS5!Y
+BDFChar: 1 66 8 0 5 0 5
+r'Yd'7/R#b
+BDFChar: 2 0 8 0 5 0 5
+r.K`VK_tfM
+BDFChar: 3 67 8 0 5 0 5
+Gf7D5`-;7^
+BDFChar: 4 77945 8 0 5 1 4
+GdOi&
+EndBitmapFont
+BitmapFont: 19 6 15 4 1 
+BDFChar: 0 65 19 0 14 0 16
+&)]\IGWbI:i""ZTn/h?gs8N&tr-iT)E#`6o?jo:'5Qh&e
+BDFChar: 1 66 19 0 14 0 14
+s7h*AGRcD30EsKH5PRJ<5PR2J0Es38Hlde8s7cQo
+BDFChar: 2 0 19 0 14 0 14
+s8N&tr-n,Vi"!O,^]qRY^]qRYi"#5tr-n\Fs8Duu
+BDFChar: 3 67 19 0 14 0 14
+"5j^b4</SMDueerhuM[8huM[8Dub+e4<+n,"5j.Z
+BDFChar: 4 77945 19 0 14 4 10
+&)]E,@uTH.@uNB,&)[Ef
+BDFChar: -1 68 19 0 0 0 0
+z
+EndBitmapFont
+EndSplineFont

test/freetype_test.py

 
     _fixed_path = os.path.join(FONTDIR, 'test_fixed.otf')
     _sans_path = os.path.join(FONTDIR, 'test_sans.ttf')
+    _mono_path = os.path.join(FONTDIR, 'PyGameMono.otf')
+    _bmp_path = os.path.join(FONTDIR, 'PyGameMono-8.bdf')
     _TEST_FONTS = {}
 
     def setUp(self):
             # https://fedorahosted.org/liberation-fonts/
             self._TEST_FONTS['sans'] = ft.Font(self._sans_path)
 
+        if 'mono' not in self._TEST_FONTS:
+            # A scalable mono test font made for Pygame. It contains only
+            # a few glyphs: '\0', 'A', 'B', 'C', and U+13079.
+            # It also contains two bitmap sizes: 8.0 X 8.0 and 19.0 X 19.0.
+            self._TEST_FONTS['mono'] = ft.Font(self._mono_path)
+
+        if 'bmp' not in self._TEST_FONTS:
+            # A fixed size bitmap mono test font made for Pygame.
+            # It contains only a few glyphs: '\0', 'A', 'B', 'C', and U+13079.
+            # The size is 8.0 X 8.0.
+            self._TEST_FONTS['bmp'] = ft.Font(self._bmp_path)
+
     def test_freetype_defaultfont(self):
         font = ft.Font(None)
         self.assertEqual(font.name, "FreeSans")
         # Test attribute preservation during reinitalization
         f = ft.Font(self._sans_path, ptsize=24, ucs4=True)
         self.assertEqual(f.name, 'Liberation Sans')
+        self.assertTrue(f.scalable)
         self.assertFalse(f.fixed_width)
         self.assertTrue(f.antialiased)
         self.assertFalse(f.oblique)
         f.oblique = True
         f.__init__(self._fixed_path)
         self.assertEqual(f.name, 'Inconsolata')
+        self.assertTrue(f.scalable)
         ##self.assertTrue(f.fixed_width)
         self.assertFalse(f.fixed_width)  # need a properly marked Mono font
         self.assertFalse(f.antialiased)
         self.assertTrue(f.oblique)
         self.assertTrue(f.ucs4)
 
+    def test_freetype_Font_scalable(self):
+
+        f = self._TEST_FONTS['sans']
+        self.assertTrue(f.scalable)
+
+        self.assertRaises(RuntimeError, lambda : nullfont().scalable)
+
     def test_freetype_Font_fixed_width(self):
 
         f = self._TEST_FONTS['sans']
         self.assertFalse(f.fixed_width)
 
-        f = self._TEST_FONTS['fixed']
-        ##self.assertTrue(f.fixed_width)
-        self.assertFalse(f.fixed_width)  # need a properly marked Mone font
+        f = self._TEST_FONTS['mono']
+        self.assertTrue(f.fixed_width)
 
         self.assertRaises(RuntimeError, lambda : nullfont().fixed_width)
 
+    def test_freetype_Font_fixed_sizes(self):
+        
+        f = self._TEST_FONTS['sans']
+        self.assertEqual(f.fixed_sizes, 0)
+        f = self._TEST_FONTS['bmp']
+        self.assertEqual(f.fixed_sizes, 1)
+        f = self._TEST_FONTS['mono']
+        self.assertEqual(f.fixed_sizes, 2)
+
+    def test_freetype_Font_get_sizes(self):
+        f = self._TEST_FONTS['sans']
+        szlist = f.get_sizes()
+        self.assertTrue(isinstance(szlist, list))
+        self.assertEqual(len(szlist), 0)
+        f = self._TEST_FONTS['bmp']
+        szlist = f.get_sizes()
+        self.assertTrue(isinstance(szlist, list))
+        self.assertEqual(len(szlist), 1)
+        size8 = szlist[0]
+        self.assertEqual(int(size8[3] * 64.0 + 0.5), 8 * 64)
+        self.assertEqual(int(size8[4] * 64.0 + 0.5), 8 * 64)
+        f = self._TEST_FONTS['mono']
+        szlist = f.get_sizes()
+        self.assertTrue(isinstance(szlist, list))
+        self.assertEqual(len(szlist), 2)
+        size8 = szlist[0]
+        self.assertEqual(int(size8[3] * 64.0 + 0.5), 8 * 64)
+        self.assertEqual(int(size8[4] * 64.0 + 0.5), 8 * 64)
+        size19 = szlist[1]
+        self.assertEqual(int(size19[3] * 64.0 + 0.5), 19 * 64)
+        self.assertEqual(int(size19[4] * 64.0 + 0.5), 19 * 64)
+
     def test_freetype_Font_get_metrics(self):
 
         font = self._TEST_FONTS['sans']
                           style=97, ptsize=24)
 
         # valid surrogate pairs
-#        rend1 = font.render(None, as_unicode(r'\uD800\uDC00'), color, ptsize=24)
-#        rend1 = font.render(None, as_unicode(r'\uDBFF\uDFFF'), color, ptsize=24)
-#        rend1 = font.render(None, as_unicode(r'\uD80C\uDCA7'), color, ptsize=24)
-#        rend2 = font.render(None, as_unicode(r'\U000130A7'), color, ptsize=24)
-#        self.assertEqual(rend1[1], rend2[1])
-#        font.utf16_surrogates = False
-#        try:
-#            rend1 = font.render(None, as_unicode(r'\uD80C\uDCA7'),
-#                                color, ptsize=24)
-#        finally:
-#            font.utf16_surrogates = True
-#        self.assertNotEqual(rend1[1], rend2[1])
+        font2 = self._TEST_FONTS['mono']
+        ucs4 = font2.ucs4
+        try:
+            font2.ucs4 = False
+            rend1 = font2.render(as_unicode(r'\uD80C\uDC79'), color, ptsize=24)
+            rend2 = font2.render(as_unicode(r'\U00013079'), color, ptsize=24)
+            self.assertEqual(rend1[1], rend2[1])
+            font2.ucs4 = True
+            rend1 = font2.render(as_unicode(r'\uD80C\uDC79'), color, ptsize=24)
+            self.assertNotEqual(rend1[1], rend2[1])
+        finally:
+            font2.ucs4 = ucs4
             
         # malformed surrogate pairs
         self.assertRaises(UnicodeEncodeError, font.render,