Fredrik Lundh avatar Fredrik Lundh committed a9eab83

Use replace semantics for Unicode characters in 8-bit bitmap fonts.

Comments (0)

Files changed (3)

 
   http://bitbucket.org/effbot/pil-2009-raclette/changesets/
 
++ Don't choke on Unicode strings when using bitmap fonts; render
+  replace characters instead.
+
 + Added Oliver Tonnhofer's PNG and Quantization work:
 
   - Added support for PNG8 images with transparency

Tests/test_imagedraw.py

 from tester import *
 
+# FIXME: this module needs a lot more tests!
+
 from PIL import Image
 from PIL import ImageDraw
+from PIL import ImageFont
 
 def test_sanity():
 
     assert_exception(AttributeError, lambda: draw.setink(0))
     assert_exception(AttributeError, lambda: draw.setfill(0))
 
+def test_unicode_text():
+
+    # use replace semantics when using unicode strings with bitmap
+    # fonts
+
+    im1 = Image.new("L", (512, 512), "white")
+    im2 = Image.new("L", (512, 512), "white")
+
+    font = ImageFont.load_default()
+
+    text = u"-\u263a-"
+
+    ImageDraw.Draw(im1).text((40, 40), text, font=font)
+    ImageDraw.Draw(im2).text((40, 40), text.encode("iso-8859-1", "replace"), font=font)
+
+    assert_image_equal(im1, im2)
 #define PyObject_Del PyMem_DEL
 #endif
 
+#if PY_VERSION_HEX >= 0x01060000
+#if PY_VERSION_HEX  < 0x02020000 || defined(Py_USING_UNICODE)
+/* defining this enables unicode support (default under 1.6a1 and later) */
+#define HAVE_UNICODE
+#endif
+#endif
+
 #if PY_VERSION_HEX < 0x02030000
 #define PyMODINIT_FUNC DL_EXPORT(void)
 #define PyLong_AsUnsignedLongMask PyLong_AsUnsignedLong
 
 #ifdef WITH_IMAGEDRAW
 
+#define REPLACEMENT_CHAR '?'
+
 static PyObject*
 _font_new(PyObject* self_, PyObject* args)
 {
     PyObject_Del(self);
 }
 
+static int
+textchar(PyObject* string, int index, int* char_out)
+{
+#if defined(HAVE_UNICODE)
+    if (PyUnicode_Check(string)) {
+        Py_UNICODE* p = PyUnicode_AS_UNICODE(string);
+        int size = PyUnicode_GET_SIZE(string);
+        if (index >= size)
+            return 0;
+        *char_out = p[index];
+        return 1;
+    }
+#endif
+    if (PyString_Check(string)) {
+        unsigned char* p = (unsigned char*) PyString_AS_STRING(string);
+        int size = PyString_GET_SIZE(string);
+        if (index >= size)
+            return 0;
+        *char_out = (unsigned char) p[index];
+        return 1;
+    }
+    return 0;
+}
+
 static inline int
-textwidth(ImagingFontObject* self, const unsigned char* text)
+textwidth(ImagingFontObject* self, PyObject* text)
 {
-    int xsize;
-
-    for (xsize = 0; *text; text++)
-        xsize += self->glyphs[*text].dx;
+    int xsize, i, ch;
+
+    xsize = 0;
+
+    for (i = 0; textchar(text, i, &ch); i++) {
+        if (ch >= 256)
+	    ch = REPLACEMENT_CHAR;
+        xsize += self->glyphs[ch].dx;
+    }
 
     return xsize;
 }
 {
     Imaging im;
     Imaging bitmap;
-    int x, b;
+    int x, i, b, ch;
     int status;
     Glyph* glyph;
 
-    unsigned char* text;
+    PyObject* text;
     char* mode = "";
-    if (!PyArg_ParseTuple(args, "s|s:getmask", &text, &mode))
+    if (!PyArg_ParseTuple(args, "O|s:getmask", &text, &mode))
         return NULL;
 
+#if defined(HAVE_UNICODE)
+    if (!PyUnicode_Check(text) && !PyString_Check(text)) {
+#else
+    if (!PyString_Check(text)) {
+#endif
+        PyErr_SetString(PyExc_TypeError, "expected string");
+        return NULL;
+    }
+
     im = ImagingNew(self->bitmap->mode, textwidth(self, text), self->ysize);
     if (!im)
         return NULL;
 
-    b = 0;
+    x = b = 0;
     (void) ImagingFill(im, &b);
 
     b = self->baseline;
-    for (x = 0; *text; text++) {
-        glyph = &self->glyphs[*text];
+
+    for (i = 0; textchar(text, i, &ch); i++) {
+        if (ch >= 256)
+	    ch = REPLACEMENT_CHAR;
+        glyph = &self->glyphs[ch];
         bitmap = ImagingCrop(
             self->bitmap,
             glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1
 static PyObject*
 _font_getsize(ImagingFontObject* self, PyObject* args)
 {
-    unsigned char* text;
-    if (!PyArg_ParseTuple(args, "s:getsize", &text))
+    PyObject* text;
+    if (!PyArg_ParseTuple(args, "O:getsize", &text))
         return NULL;
 
+#if defined(HAVE_UNICODE)
+    if (!PyUnicode_Check(text) && !PyString_Check(text)) {
+#else
+    if (!PyString_Check(text)) {
+#endif
+        PyErr_SetString(PyExc_TypeError, "expected string");
+        return NULL;
+    }
+
     return Py_BuildValue("ii", textwidth(self, text), self->ysize);
 }
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.