Added text stroking

This commit is contained in:
Andrew Murray 2019-07-29 06:40:03 +10:00
parent f3f45cfec5
commit f93a5d0972
11 changed files with 355 additions and 55 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -1,8 +1,8 @@
import os.path import os.path
from PIL import Image, ImageColor, ImageDraw from PIL import Image, ImageColor, ImageDraw, ImageFont, features
from .helper import PillowTestCase, hopper from .helper import PillowTestCase, hopper, unittest
BLACK = (0, 0, 0) BLACK = (0, 0, 0)
WHITE = (255, 255, 255) WHITE = (255, 255, 255)
@ -29,6 +29,8 @@ POINTS2 = [10, 10, 20, 40, 30, 30]
KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)] KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)]
HAS_FREETYPE = features.check("freetype2")
class TestImageDraw(PillowTestCase): class TestImageDraw(PillowTestCase):
def test_sanity(self): def test_sanity(self):
@ -771,6 +773,54 @@ class TestImageDraw(PillowTestCase):
draw.textsize("\n") draw.textsize("\n")
draw.textsize("test\n") draw.textsize("test\n")
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available")
def test_textsize_stroke(self):
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20)
# Act / Assert
self.assertEqual(draw.textsize("A", font, stroke_width=2), (16, 20))
self.assertEqual(
draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2), (52, 44)
)
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available")
def test_stroke(self):
for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items():
# Arrange
im = Image.new("RGB", (120, 130))
draw = ImageDraw.Draw(im)
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120)
# Act
draw.text(
(10, 10), "A", "#f00", font, stroke_width=2, stroke_fill=stroke_fill
)
# Assert
self.assert_image_similar(
im, Image.open("Tests/images/imagedraw_stroke_" + suffix + ".png"), 2.8
)
@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available")
def test_stroke_multiline(self):
# Arrange
im = Image.new("RGB", (100, 250))
draw = ImageDraw.Draw(im)
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120)
# Act
draw.multiline_text(
(10, 10), "A\nB", "#f00", font, stroke_width=2, stroke_fill="#0f0"
)
# Assert
self.assert_image_similar(
im, Image.open("Tests/images/imagedraw_stroke_multiline.png"), 3.3
)
def test_same_color_outline(self): def test_same_color_outline(self):
# Prepare shape # Prepare shape
x0, y0 = 5, 5 x0, y0 = 5, 5

View File

@ -605,6 +605,21 @@ class TestImageFont(PillowTestCase):
self.assertEqual(t.getsize_multiline("ABC\nA"), (36, 36)) self.assertEqual(t.getsize_multiline("ABC\nA"), (36, 36))
self.assertEqual(t.getsize_multiline("ABC\nAaaa"), (48, 36)) self.assertEqual(t.getsize_multiline("ABC\nAaaa"), (48, 36))
def test_getsize_stroke(self):
# Arrange
t = self.get_font()
# Act / Assert
for stroke_width in [0, 2]:
self.assertEqual(
t.getsize("A", stroke_width=stroke_width),
(12 + stroke_width * 2, 16 + stroke_width * 2),
)
self.assertEqual(
t.getsize_multiline("ABC\nAaaa", stroke_width=stroke_width),
(48 + stroke_width * 2, 36 + stroke_width * 4),
)
def test_complex_font_settings(self): def test_complex_font_settings(self):
# Arrange # Arrange
t = self.get_font() t = self.get_font()

View File

@ -115,6 +115,30 @@ class TestImagecomplextext(PillowTestCase):
self.assert_image_similar(im, target_img, 1.15) self.assert_image_similar(im, target_img, 1.15)
def test_text_direction_ttb_stroke(self):
ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", 50)
im = Image.new(mode="RGB", size=(100, 300))
draw = ImageDraw.Draw(im)
try:
draw.text(
(25, 25),
"あい",
font=ttf,
fill=500,
direction="ttb",
stroke_width=2,
stroke_fill="#0f0",
)
except ValueError as ex:
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
self.skipTest("libraqm 0.7 or greater not available")
target = "Tests/images/test_direction_ttb_stroke.png"
target_img = Image.open(target)
self.assert_image_similar(im, target_img, 12.4)
def test_ligature_features(self): def test_ligature_features(self):
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)

View File

@ -255,7 +255,7 @@ Methods
Draw a shape. Draw a shape.
.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None) .. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None)
Draws the string at the given position. Draws the string at the given position.
@ -297,6 +297,15 @@ Methods
.. versionadded:: 6.0.0 .. versionadded:: 6.0.0
:param stroke_width: The width of the text stroke.
.. versionadded:: 6.2.0
:param stroke_fill: Color to use for the text stroke. If not given, will default to
the ``fill`` parameter.
.. versionadded:: 6.2.0
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None) .. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=0, align="left", direction=None, features=None, language=None)
Draws the string at the given position. Draws the string at the given position.
@ -336,7 +345,7 @@ Methods
.. versionadded:: 6.0.0 .. versionadded:: 6.0.0
.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None) .. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0)
Return the size of the given string, in pixels. Return the size of the given string, in pixels.
@ -372,7 +381,11 @@ Methods
.. versionadded:: 6.0.0 .. versionadded:: 6.0.0
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None) :param stroke_width: The width of the text stroke.
.. versionadded:: 6.2.0
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0)
Return the size of the given string, in pixels. Return the size of the given string, in pixels.
@ -408,6 +421,10 @@ Methods
.. versionadded:: 6.0.0 .. versionadded:: 6.0.0
:param stroke_width: The width of the text stroke.
.. versionadded:: 6.2.0
.. py:method:: PIL.ImageDraw.getdraw(im=None, hints=None) .. py:method:: PIL.ImageDraw.getdraw(im=None, hints=None)
.. warning:: This method is experimental. .. warning:: This method is experimental.

View File

@ -261,24 +261,95 @@ class ImageDraw(object):
return text.split(split_character) return text.split(split_character)
def text(self, xy, text, fill=None, font=None, anchor=None, *args, **kwargs): def text(
self,
xy,
text,
fill=None,
font=None,
anchor=None,
spacing=4,
align="left",
direction=None,
features=None,
language=None,
stroke_width=0,
stroke_fill=None,
*args,
**kwargs
):
if self._multiline_check(text): if self._multiline_check(text):
return self.multiline_text(xy, text, fill, font, anchor, *args, **kwargs) return self.multiline_text(
ink, fill = self._getink(fill) xy,
text,
fill,
font,
anchor,
spacing,
align,
direction,
features,
language,
stroke_width,
stroke_fill,
)
if font is None: if font is None:
font = self.getfont() font = self.getfont()
if ink is None:
ink = fill def getink(fill):
if ink is not None: ink, fill = self._getink(fill)
if ink is None:
return fill
return ink
def drawText(ink, stroke_width=0, stroke_offset=None):
coord = xy
try: try:
mask, offset = font.getmask2(text, self.fontmode, *args, **kwargs) mask, offset = font.getmask2(
xy = xy[0] + offset[0], xy[1] + offset[1] text,
self.fontmode,
direction=direction,
features=features,
language=language,
stroke_width=stroke_width,
*args,
**kwargs
)
coord = coord[0] + offset[0], coord[1] + offset[1]
except AttributeError: except AttributeError:
try: try:
mask = font.getmask(text, self.fontmode, *args, **kwargs) mask = font.getmask(
text,
self.fontmode,
direction,
features,
language,
stroke_width,
*args,
**kwargs
)
except TypeError: except TypeError:
mask = font.getmask(text) mask = font.getmask(text)
self.draw.draw_bitmap(xy, mask, ink) if stroke_offset:
coord = coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]
self.draw.draw_bitmap(coord, mask, ink)
ink = getink(fill)
if ink is not None:
stroke_ink = None
if stroke_width:
stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink
if stroke_ink is not None:
# Draw stroked text
drawText(stroke_ink, stroke_width)
# Draw normal text
drawText(ink, 0, (stroke_width, stroke_width))
else:
# Only draw normal text
drawText(ink)
def multiline_text( def multiline_text(
self, self,
@ -292,14 +363,23 @@ class ImageDraw(object):
direction=None, direction=None,
features=None, features=None,
language=None, language=None,
stroke_width=0,
stroke_fill=None,
): ):
widths = [] widths = []
max_width = 0 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = self.textsize("A", font=font)[1] + spacing line_spacing = (
self.textsize("A", font=font, stroke_width=stroke_width)[1] + spacing
)
for line in lines: for line in lines:
line_width, line_height = self.textsize( line_width, line_height = self.textsize(
line, font, direction=direction, features=features, language=language line,
font,
direction=direction,
features=features,
language=language,
stroke_width=stroke_width,
) )
widths.append(line_width) widths.append(line_width)
max_width = max(max_width, line_width) max_width = max(max_width, line_width)
@ -322,32 +402,50 @@ class ImageDraw(object):
direction=direction, direction=direction,
features=features, features=features,
language=language, language=language,
stroke_width=stroke_width,
stroke_fill=stroke_fill,
) )
top += line_spacing top += line_spacing
left = xy[0] left = xy[0]
def textsize( def textsize(
self, text, font=None, spacing=4, direction=None, features=None, language=None self,
text,
font=None,
spacing=4,
direction=None,
features=None,
language=None,
stroke_width=0,
): ):
"""Get the size of a given string, in pixels.""" """Get the size of a given string, in pixels."""
if self._multiline_check(text): if self._multiline_check(text):
return self.multiline_textsize( return self.multiline_textsize(
text, font, spacing, direction, features, language text, font, spacing, direction, features, language, stroke_width
) )
if font is None: if font is None:
font = self.getfont() font = self.getfont()
return font.getsize(text, direction, features, language) return font.getsize(text, direction, features, language, stroke_width)
def multiline_textsize( def multiline_textsize(
self, text, font=None, spacing=4, direction=None, features=None, language=None self,
text,
font=None,
spacing=4,
direction=None,
features=None,
language=None,
stroke_width=0,
): ):
max_width = 0 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = self.textsize("A", font=font)[1] + spacing line_spacing = (
self.textsize("A", font=font, stroke_width=stroke_width)[1] + spacing
)
for line in lines: for line in lines:
line_width, line_height = self.textsize( line_width, line_height = self.textsize(
line, font, spacing, direction, features, language line, font, spacing, direction, features, language, stroke_width
) )
max_width = max(max_width, line_width) max_width = max(max_width, line_width)
return max_width, len(lines) * line_spacing - spacing return max_width, len(lines) * line_spacing - spacing

View File

@ -207,7 +207,9 @@ class FreeTypeFont(object):
""" """
return self.font.ascent, self.font.descent return self.font.ascent, self.font.descent
def getsize(self, text, direction=None, features=None, language=None): def getsize(
self, text, direction=None, features=None, language=None, stroke_width=0
):
""" """
Returns width and height (in pixels) of given text if rendered in font with Returns width and height (in pixels) of given text if rendered in font with
provided direction, features, and language. provided direction, features, and language.
@ -243,13 +245,26 @@ class FreeTypeFont(object):
.. versionadded:: 6.0.0 .. versionadded:: 6.0.0
:param stroke_width: The width of the text stroke.
.. versionadded:: 6.2.0
:return: (width, height) :return: (width, height)
""" """
size, offset = self.font.getsize(text, direction, features, language) size, offset = self.font.getsize(text, direction, features, language)
return (size[0] + offset[0], size[1] + offset[1]) return (
size[0] + stroke_width * 2 + offset[0],
size[1] + stroke_width * 2 + offset[1],
)
def getsize_multiline( def getsize_multiline(
self, text, direction=None, spacing=4, features=None, language=None self,
text,
direction=None,
spacing=4,
features=None,
language=None,
stroke_width=0,
): ):
""" """
Returns width and height (in pixels) of given text if rendered in font Returns width and height (in pixels) of given text if rendered in font
@ -285,13 +300,19 @@ class FreeTypeFont(object):
.. versionadded:: 6.0.0 .. versionadded:: 6.0.0
:param stroke_width: The width of the text stroke.
.. versionadded:: 6.2.0
:return: (width, height) :return: (width, height)
""" """
max_width = 0 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = self.getsize("A")[1] + spacing line_spacing = self.getsize("A", stroke_width=stroke_width)[1] + spacing
for line in lines: for line in lines:
line_width, line_height = self.getsize(line, direction, features, language) line_width, line_height = self.getsize(
line, direction, features, language, stroke_width
)
max_width = max(max_width, line_width) max_width = max(max_width, line_width)
return max_width, len(lines) * line_spacing - spacing return max_width, len(lines) * line_spacing - spacing
@ -308,7 +329,15 @@ class FreeTypeFont(object):
""" """
return self.font.getsize(text)[1] return self.font.getsize(text)[1]
def getmask(self, text, mode="", direction=None, features=None, language=None): def getmask(
self,
text,
mode="",
direction=None,
features=None,
language=None,
stroke_width=0,
):
""" """
Create a bitmap for the text. Create a bitmap for the text.
@ -352,11 +381,20 @@ class FreeTypeFont(object):
.. versionadded:: 6.0.0 .. versionadded:: 6.0.0
:param stroke_width: The width of the text stroke.
.. versionadded:: 6.2.0
:return: An internal PIL storage memory instance as defined by the :return: An internal PIL storage memory instance as defined by the
:py:mod:`PIL.Image.core` interface module. :py:mod:`PIL.Image.core` interface module.
""" """
return self.getmask2( return self.getmask2(
text, mode, direction=direction, features=features, language=language text,
mode,
direction=direction,
features=features,
language=language,
stroke_width=stroke_width,
)[0] )[0]
def getmask2( def getmask2(
@ -367,6 +405,7 @@ class FreeTypeFont(object):
direction=None, direction=None,
features=None, features=None,
language=None, language=None,
stroke_width=0,
*args, *args,
**kwargs **kwargs
): ):
@ -413,13 +452,20 @@ class FreeTypeFont(object):
.. versionadded:: 6.0.0 .. versionadded:: 6.0.0
:param stroke_width: The width of the text stroke.
.. versionadded:: 6.2.0
:return: A tuple of an internal PIL storage memory instance as defined by the :return: A tuple of an internal PIL storage memory instance as defined by the
:py:mod:`PIL.Image.core` interface module, and the text offset, the :py:mod:`PIL.Image.core` interface module, and the text offset, the
gap between the starting coordinate and the first marking gap between the starting coordinate and the first marking
""" """
size, offset = self.font.getsize(text, direction, features, language) size, offset = self.font.getsize(text, direction, features, language)
size = size[0] + stroke_width * 2, size[1] + stroke_width * 2
im = fill("L", size, 0) im = fill("L", size, 0)
self.font.render(text, im.id, mode == "1", direction, features, language) self.font.render(
text, im.id, mode == "1", direction, features, language, stroke_width
)
return im, offset return im, offset
def font_variant( def font_variant(

View File

@ -25,6 +25,7 @@
#include <ft2build.h> #include <ft2build.h>
#include FT_FREETYPE_H #include FT_FREETYPE_H
#include FT_GLYPH_H #include FT_GLYPH_H
#include FT_STROKER_H
#include FT_MULTIPLE_MASTERS_H #include FT_MULTIPLE_MASTERS_H
#include FT_SFNT_NAMES_H #include FT_SFNT_NAMES_H
@ -790,7 +791,13 @@ font_render(FontObject* self, PyObject* args)
int index, error, ascender, horizontal_dir; int index, error, ascender, horizontal_dir;
int load_flags; int load_flags;
unsigned char *source; unsigned char *source;
FT_GlyphSlot glyph; FT_Glyph glyph;
FT_GlyphSlot glyph_slot;
FT_Bitmap bitmap;
FT_BitmapGlyph bitmap_glyph;
int stroke_width = 0;
FT_Stroker stroker = NULL;
FT_Int left;
/* render string into given buffer (the buffer *must* have /* render string into given buffer (the buffer *must* have
the right size, or this will crash) */ the right size, or this will crash) */
PyObject* string; PyObject* string;
@ -806,7 +813,8 @@ font_render(FontObject* self, PyObject* args)
GlyphInfo *glyph_info; GlyphInfo *glyph_info;
PyObject *features = NULL; PyObject *features = NULL;
if (!PyArg_ParseTuple(args, "On|izOz:render", &string, &id, &mask, &dir, &features, &lang)) { if (!PyArg_ParseTuple(args, "On|izOzi:render", &string, &id, &mask, &dir, &features, &lang,
&stroke_width)) {
return NULL; return NULL;
} }
@ -819,21 +827,37 @@ font_render(FontObject* self, PyObject* args)
Py_RETURN_NONE; Py_RETURN_NONE;
} }
if (stroke_width) {
error = FT_Stroker_New(library, &stroker);
if (error) {
return geterror(error);
}
FT_Stroker_Set(stroker, (FT_Fixed)stroke_width*64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
}
im = (Imaging) id; im = (Imaging) id;
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */ /* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */
load_flags = FT_LOAD_RENDER|FT_LOAD_NO_BITMAP; load_flags = FT_LOAD_NO_BITMAP;
if (mask) if (stroker == NULL) {
load_flags |= FT_LOAD_RENDER;
}
if (mask) {
load_flags |= FT_LOAD_TARGET_MONO; load_flags |= FT_LOAD_TARGET_MONO;
}
ascender = 0; ascender = 0;
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
index = glyph_info[i].index; index = glyph_info[i].index;
error = FT_Load_Glyph(self->face, index, load_flags); error = FT_Load_Glyph(self->face, index, load_flags);
if (error) if (error) {
return geterror(error); return geterror(error);
}
glyph = self->face->glyph; glyph_slot = self->face->glyph;
temp = glyph->bitmap.rows - glyph->bitmap_top; bitmap = glyph_slot->bitmap;
temp = bitmap.rows - glyph_slot->bitmap_top;
temp -= PIXEL(glyph_info[i].y_offset); temp -= PIXEL(glyph_info[i].y_offset);
if (temp > ascender) if (temp > ascender)
ascender = temp; ascender = temp;
@ -844,37 +868,62 @@ font_render(FontObject* self, PyObject* args)
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
index = glyph_info[i].index; index = glyph_info[i].index;
error = FT_Load_Glyph(self->face, index, load_flags); error = FT_Load_Glyph(self->face, index, load_flags);
if (error) if (error) {
return geterror(error); return geterror(error);
}
glyph = self->face->glyph; glyph_slot = self->face->glyph;
if (horizontal_dir) { if (stroker != NULL) {
if (i == 0 && self->face->glyph->metrics.horiBearingX < 0) { error = FT_Get_Glyph(glyph_slot, &glyph);
x = -self->face->glyph->metrics.horiBearingX; if (!error) {
error = FT_Glyph_Stroke(&glyph, stroker, 1);
} }
xx = PIXEL(x) + glyph->bitmap_left; if (!error) {
xx += PIXEL(glyph_info[i].x_offset); FT_Vector origin = {0, 0};
error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, &origin, 1);
}
if (error) {
return geterror(error);
}
bitmap_glyph = (FT_BitmapGlyph)glyph;
bitmap = bitmap_glyph->bitmap;
left = bitmap_glyph->left;
FT_Done_Glyph(glyph);
} else { } else {
if (self->face->glyph->metrics.vertBearingX < 0) { bitmap = glyph_slot->bitmap;
x = -self->face->glyph->metrics.vertBearingX; left = glyph_slot->bitmap_left;
}
if (horizontal_dir) {
if (i == 0 && glyph_slot->metrics.horiBearingX < 0) {
x = -glyph_slot->metrics.horiBearingX;
} }
xx = im->xsize / 2 - glyph->bitmap.width / 2; xx = PIXEL(x) + left;
xx += PIXEL(glyph_info[i].x_offset) + stroke_width;
} else {
if (glyph_slot->metrics.vertBearingX < 0) {
x = -glyph_slot->metrics.vertBearingX;
}
xx = im->xsize / 2 - bitmap.width / 2;
} }
x0 = 0; x0 = 0;
x1 = glyph->bitmap.width; x1 = bitmap.width;
if (xx < 0) if (xx < 0)
x0 = -xx; x0 = -xx;
if (xx + x1 > im->xsize) if (xx + x1 > im->xsize)
x1 = im->xsize - xx; x1 = im->xsize - xx;
source = (unsigned char*) glyph->bitmap.buffer; source = (unsigned char*) bitmap.buffer;
for (bitmap_y = 0; bitmap_y < glyph->bitmap.rows; bitmap_y++) { for (bitmap_y = 0; bitmap_y < bitmap.rows; bitmap_y++) {
if (horizontal_dir) { if (horizontal_dir) {
yy = bitmap_y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender); yy = bitmap_y + im->ysize - (PIXEL(glyph_slot->metrics.horiBearingY) + ascender);
yy -= PIXEL(glyph_info[i].y_offset); yy -= PIXEL(glyph_info[i].y_offset) + stroke_width * 2;
} else { } else {
yy = bitmap_y + PIXEL(y + glyph->metrics.vertBearingY) + ascender; yy = bitmap_y + PIXEL(y + glyph_slot->metrics.vertBearingY) + ascender;
yy += PIXEL(glyph_info[i].y_offset); yy += PIXEL(glyph_info[i].y_offset);
} }
if (yy >= 0 && yy < im->ysize) { if (yy >= 0 && yy < im->ysize) {
@ -900,12 +949,13 @@ font_render(FontObject* self, PyObject* args)
} }
} }
} }
source += glyph->bitmap.pitch; source += bitmap.pitch;
} }
x += glyph_info[i].x_advance; x += glyph_info[i].x_advance;
y -= glyph_info[i].y_advance; y -= glyph_info[i].y_advance;
} }
FT_Stroker_Done(stroker);
PyMem_Del(glyph_info); PyMem_Del(glyph_info);
Py_RETURN_NONE; Py_RETURN_NONE;
} }