Merge pull request #4910 from nulano/anchor-part1
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 963 B After Width: | Height: | Size: 992 B |
Before Width: | Height: | Size: 777 B After Width: | Height: | Size: 785 B |
Before Width: | Height: | Size: 605 B After Width: | Height: | Size: 714 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 810 B After Width: | Height: | Size: 802 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 937 B After Width: | Height: | Size: 918 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
@ -35,9 +35,24 @@ class TestImageFont:
|
||||||
# Freetype has different metrics depending on the version.
|
# Freetype has different metrics depending on the version.
|
||||||
# (and, other things, but first things first)
|
# (and, other things, but first things first)
|
||||||
METRICS = {
|
METRICS = {
|
||||||
(">=2.3", "<2.4"): {"multiline": 30, "textsize": 12, "getters": (13, 16)},
|
(">=2.3", "<2.4"): {
|
||||||
(">=2.7",): {"multiline": 6.2, "textsize": 2.5, "getters": (12, 16)},
|
"multiline": 30,
|
||||||
"Default": {"multiline": 0.5, "textsize": 0.5, "getters": (12, 16)},
|
"textsize": 12,
|
||||||
|
"getters": (13, 16),
|
||||||
|
"mask": (107, 13),
|
||||||
|
},
|
||||||
|
(">=2.7",): {
|
||||||
|
"multiline": 6.2,
|
||||||
|
"textsize": 2.5,
|
||||||
|
"getters": (12, 16),
|
||||||
|
"mask": (108, 13),
|
||||||
|
},
|
||||||
|
"Default": {
|
||||||
|
"multiline": 0.5,
|
||||||
|
"textsize": 0.5,
|
||||||
|
"getters": (12, 16),
|
||||||
|
"mask": (108, 13),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -343,7 +358,7 @@ class TestImageFont:
|
||||||
mask = transposed_font.getmask(text)
|
mask = transposed_font.getmask(text)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert mask.size == (13, 108)
|
assert mask.size == self.metrics["mask"][::-1]
|
||||||
|
|
||||||
def test_unrotated_transposed_font_get_mask(self):
|
def test_unrotated_transposed_font_get_mask(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -356,7 +371,7 @@ class TestImageFont:
|
||||||
mask = transposed_font.getmask(text)
|
mask = transposed_font.getmask(text)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert mask.size == (108, 13)
|
assert mask.size == self.metrics["mask"]
|
||||||
|
|
||||||
def test_free_type_font_get_name(self):
|
def test_free_type_font_get_name(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -400,7 +415,7 @@ class TestImageFont:
|
||||||
mask = font.getmask(text)
|
mask = font.getmask(text)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert mask.size == (108, 13)
|
assert mask.size == self.metrics["mask"]
|
||||||
|
|
||||||
def test_load_path_not_found(self):
|
def test_load_path_not_found(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -471,7 +486,8 @@ class TestImageFont:
|
||||||
d = ImageDraw.Draw(img)
|
d = ImageDraw.Draw(img)
|
||||||
d.text((10, 10), text, font=ttf)
|
d.text((10, 10), text, font=ttf)
|
||||||
|
|
||||||
assert_image_similar_tofile(img, target, self.metrics["multiline"])
|
# fails with 14.7
|
||||||
|
assert_image_similar_tofile(img, target, 6.2)
|
||||||
|
|
||||||
def _test_fake_loading_font(self, monkeypatch, path_to_fake, fontname):
|
def _test_fake_loading_font(self, monkeypatch, path_to_fake, fontname):
|
||||||
# Make a copy of FreeTypeFont so we can patch the original
|
# Make a copy of FreeTypeFont so we can patch the original
|
||||||
|
@ -728,7 +744,7 @@ class TestImageFont:
|
||||||
|
|
||||||
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
|
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
|
||||||
font.set_variation_by_axes([500, 50])
|
font.set_variation_by_axes([500, 50])
|
||||||
self._check_text(font, "Tests/images/variation_adobe_axes.png", 5.1)
|
self._check_text(font, "Tests/images/variation_adobe_axes.png", 11.05)
|
||||||
|
|
||||||
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
|
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
|
||||||
font.set_variation_by_axes([100])
|
font.set_variation_by_axes([100])
|
||||||
|
|
|
@ -259,9 +259,11 @@ class FreeTypeFont:
|
||||||
|
|
||||||
:return: (width, height)
|
:return: (width, height)
|
||||||
"""
|
"""
|
||||||
|
# vertical offset is added for historical reasons
|
||||||
|
# see https://github.com/python-pillow/Pillow/pull/4910#discussion_r486682929
|
||||||
size, offset = self.font.getsize(text, False, direction, features, language)
|
size, offset = self.font.getsize(text, False, direction, features, language)
|
||||||
return (
|
return (
|
||||||
size[0] + stroke_width * 2 + offset[0],
|
size[0] + stroke_width * 2,
|
||||||
size[1] + stroke_width * 2 + offset[1],
|
size[1] + stroke_width * 2 + offset[1],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
282
src/_imagingft.c
|
@ -131,8 +131,8 @@ typedef struct {
|
||||||
static p_raqm_func p_raqm;
|
static p_raqm_func p_raqm;
|
||||||
|
|
||||||
|
|
||||||
/* round a 26.6 pixel coordinate to the nearest larger integer */
|
/* round a 26.6 pixel coordinate to the nearest integer */
|
||||||
#define PIXEL(x) ((((x)+63) & -64)>>6)
|
#define PIXEL(x) ((((x)+32) & -64)>>6)
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
geterror(int code)
|
geterror(int code)
|
||||||
|
@ -585,7 +585,8 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje
|
||||||
}
|
}
|
||||||
|
|
||||||
(*glyph_info)[i].x_advance = glyph->metrics.horiAdvance;
|
(*glyph_info)[i].x_advance = glyph->metrics.horiAdvance;
|
||||||
(*glyph_info)[i].y_advance = glyph->metrics.vertAdvance;
|
// y_advance is only used in ttb, which is not supported by basic layout
|
||||||
|
(*glyph_info)[i].y_advance = 0;
|
||||||
last_index = (*glyph_info)[i].index;
|
last_index = (*glyph_info)[i].index;
|
||||||
(*glyph_info)[i].cluster = ch;
|
(*glyph_info)[i].cluster = ch;
|
||||||
}
|
}
|
||||||
|
@ -609,41 +610,38 @@ text_layout(PyObject* string, FontObject* self, const char* dir, PyObject *featu
|
||||||
static PyObject*
|
static PyObject*
|
||||||
font_getsize(FontObject* self, PyObject* args)
|
font_getsize(FontObject* self, PyObject* args)
|
||||||
{
|
{
|
||||||
int x_position, x_max, x_min, y_max, y_min;
|
int position; /* pen position along primary axis, in 26.6 precision */
|
||||||
|
int advanced; /* pen position along primary axis, in pixels */
|
||||||
|
int px, py; /* position of current glyph, in pixels */
|
||||||
|
int x_min, x_max, y_min, y_max; /* text bounding box, in pixels */
|
||||||
|
int x_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */
|
||||||
|
int load_flags; /* FreeType load_flags parameter */
|
||||||
|
int error;
|
||||||
FT_Face face;
|
FT_Face face;
|
||||||
int xoffset, yoffset;
|
FT_Glyph glyph;
|
||||||
int horizontal_dir;
|
FT_BBox bbox; /* glyph bounding box */
|
||||||
int mask = 0;
|
GlyphInfo *glyph_info = NULL; /* computed text layout */
|
||||||
int load_flags;
|
size_t i, count; /* glyph_info index and length */
|
||||||
|
int horizontal_dir; /* is primary axis horizontal? */
|
||||||
|
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
||||||
const char *dir = NULL;
|
const char *dir = NULL;
|
||||||
const char *lang = NULL;
|
const char *lang = NULL;
|
||||||
size_t i, count;
|
|
||||||
GlyphInfo *glyph_info = NULL;
|
|
||||||
PyObject *features = Py_None;
|
PyObject *features = Py_None;
|
||||||
|
PyObject *string;
|
||||||
|
|
||||||
/* calculate size and bearing for a given string */
|
/* calculate size and bearing for a given string */
|
||||||
|
|
||||||
PyObject* string;
|
|
||||||
if (!PyArg_ParseTuple(args, "O|izOz:getsize", &string, &mask, &dir, &features, &lang)) {
|
if (!PyArg_ParseTuple(args, "O|izOz:getsize", &string, &mask, &dir, &features, &lang)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||||
|
|
||||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask);
|
count = text_layout(string, self, dir, features, lang, &glyph_info, mask);
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
face = NULL;
|
|
||||||
xoffset = yoffset = 0;
|
|
||||||
x_position = x_max = x_min = y_max = y_min = 0;
|
|
||||||
|
|
||||||
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
|
||||||
for (i = 0; i < count; i++) {
|
|
||||||
int index, error, offset, x_advanced;
|
|
||||||
FT_BBox bbox;
|
|
||||||
FT_Glyph glyph;
|
|
||||||
face = self->face;
|
|
||||||
index = glyph_info[i].index;
|
|
||||||
/* 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
|
||||||
* Yifu Yu<root@jackyyf.com>, 2014-10-15
|
* Yifu Yu<root@jackyyf.com>, 2014-10-15
|
||||||
*/
|
*/
|
||||||
|
@ -651,74 +649,64 @@ font_getsize(FontObject* self, PyObject* args)
|
||||||
if (mask) {
|
if (mask) {
|
||||||
load_flags |= FT_LOAD_TARGET_MONO;
|
load_flags |= FT_LOAD_TARGET_MONO;
|
||||||
}
|
}
|
||||||
error = FT_Load_Glyph(face, index, load_flags);
|
|
||||||
|
/*
|
||||||
|
* text bounds are given by:
|
||||||
|
* - bounding boxes of individual glyphs
|
||||||
|
* - pen line, i.e. 0 to `advanced` along primary axis
|
||||||
|
* this means point (0, 0) is part of the text bounding box
|
||||||
|
*/
|
||||||
|
face = NULL;
|
||||||
|
position = x_min = x_max = y_min = y_max = 0;
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
face = self->face;
|
||||||
|
|
||||||
|
if (horizontal_dir) {
|
||||||
|
px = PIXEL(position + glyph_info[i].x_offset);
|
||||||
|
py = PIXEL(glyph_info[i].y_offset);
|
||||||
|
|
||||||
|
position += glyph_info[i].x_advance;
|
||||||
|
advanced = PIXEL(position);
|
||||||
|
if (advanced > x_max) {
|
||||||
|
x_max = advanced;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
px = PIXEL(glyph_info[i].x_offset);
|
||||||
|
py = PIXEL(position + glyph_info[i].y_offset);
|
||||||
|
|
||||||
|
position += glyph_info[i].y_advance;
|
||||||
|
advanced = PIXEL(position);
|
||||||
|
if (advanced < y_min) {
|
||||||
|
y_min = advanced;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error = FT_Load_Glyph(face, glyph_info[i].index, load_flags);
|
||||||
if (error) {
|
if (error) {
|
||||||
return geterror(error);
|
return geterror(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i == 0) {
|
error = FT_Get_Glyph(face->glyph, &glyph);
|
||||||
if (horizontal_dir) {
|
if (error) {
|
||||||
if (face->glyph->metrics.horiBearingX < 0) {
|
return geterror(error);
|
||||||
xoffset = face->glyph->metrics.horiBearingX;
|
|
||||||
x_position -= xoffset;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (face->glyph->metrics.vertBearingY < 0) {
|
|
||||||
yoffset = face->glyph->metrics.vertBearingY;
|
|
||||||
y_max -= yoffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FT_Get_Glyph(face->glyph, &glyph);
|
|
||||||
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_SUBPIXELS, &bbox);
|
|
||||||
if (horizontal_dir) {
|
|
||||||
x_position += glyph_info[i].x_advance;
|
|
||||||
|
|
||||||
x_advanced = x_position;
|
|
||||||
offset = glyph_info[i].x_advance -
|
|
||||||
face->glyph->metrics.width -
|
|
||||||
face->glyph->metrics.horiBearingX;
|
|
||||||
if (offset < 0) {
|
|
||||||
x_advanced -= offset;
|
|
||||||
}
|
|
||||||
if (x_advanced > x_max) {
|
|
||||||
x_max = x_advanced;
|
|
||||||
}
|
|
||||||
|
|
||||||
bbox.yMax += glyph_info[i].y_offset;
|
|
||||||
bbox.yMin += glyph_info[i].y_offset;
|
|
||||||
if (bbox.yMax > y_max) {
|
|
||||||
y_max = bbox.yMax;
|
|
||||||
}
|
|
||||||
if (bbox.yMin < y_min) {
|
|
||||||
y_min = bbox.yMin;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find max distance of baseline from top
|
|
||||||
if (face->glyph->metrics.horiBearingY > yoffset) {
|
|
||||||
yoffset = face->glyph->metrics.horiBearingY;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
y_max -= glyph_info[i].y_advance;
|
|
||||||
|
|
||||||
if (i == count - 1) {
|
|
||||||
// trim end gap from final glyph
|
|
||||||
int offset;
|
|
||||||
offset = -glyph_info[i].y_advance -
|
|
||||||
face->glyph->metrics.height -
|
|
||||||
face->glyph->metrics.vertBearingY;
|
|
||||||
if (offset < 0) {
|
|
||||||
y_max -= offset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox);
|
||||||
|
bbox.xMax += px;
|
||||||
if (bbox.xMax > x_max) {
|
if (bbox.xMax > x_max) {
|
||||||
x_max = bbox.xMax;
|
x_max = bbox.xMax;
|
||||||
}
|
}
|
||||||
if (i == 0 || bbox.xMin < x_min) {
|
bbox.xMin += px;
|
||||||
|
if (bbox.xMin < x_min) {
|
||||||
x_min = bbox.xMin;
|
x_min = bbox.xMin;
|
||||||
}
|
}
|
||||||
|
bbox.yMax += py;
|
||||||
|
if (bbox.yMax > y_max) {
|
||||||
|
y_max = bbox.yMax;
|
||||||
|
}
|
||||||
|
bbox.yMin += py;
|
||||||
|
if (bbox.yMin < y_min) {
|
||||||
|
y_min = bbox.yMin;
|
||||||
}
|
}
|
||||||
|
|
||||||
FT_Done_Glyph(glyph);
|
FT_Done_Glyph(glyph);
|
||||||
|
@ -729,72 +717,63 @@ font_getsize(FontObject* self, PyObject* args)
|
||||||
glyph_info = NULL;
|
glyph_info = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
x_anchor = y_anchor = 0;
|
||||||
if (face) {
|
if (face) {
|
||||||
if (horizontal_dir) {
|
if (horizontal_dir) {
|
||||||
// left bearing
|
x_anchor = 0;
|
||||||
if (xoffset < 0) {
|
y_anchor = PIXEL(self->face->size->metrics.ascender);
|
||||||
x_max -= xoffset;
|
|
||||||
} else {
|
} else {
|
||||||
xoffset = 0;
|
x_anchor = x_min;
|
||||||
}
|
y_anchor = 0;
|
||||||
|
|
||||||
/* difference between the font ascender and the distance of
|
|
||||||
* the baseline from the top */
|
|
||||||
yoffset = PIXEL(self->face->size->metrics.ascender - yoffset);
|
|
||||||
} else {
|
|
||||||
// top bearing
|
|
||||||
if (yoffset < 0) {
|
|
||||||
y_max -= yoffset;
|
|
||||||
} else {
|
|
||||||
yoffset = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Py_BuildValue(
|
return Py_BuildValue(
|
||||||
"(ii)(ii)",
|
"(ii)(ii)",
|
||||||
PIXEL(x_max - x_min), PIXEL(y_max - y_min),
|
(x_max - x_min), (y_max - y_min),
|
||||||
PIXEL(xoffset), yoffset
|
(-x_anchor + x_min), -(-y_anchor + y_max)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
font_render(FontObject* self, PyObject* args)
|
font_render(FontObject* self, PyObject* args)
|
||||||
{
|
{
|
||||||
int x;
|
int x, y; /* pen position, in 26.6 precision */
|
||||||
unsigned int y;
|
int px, py; /* position of current glyph, in pixels */
|
||||||
Imaging im;
|
int x_min, y_max; /* text offset in 26.6 precision */
|
||||||
int index, error, ascender, horizontal_dir;
|
int load_flags; /* FreeType load_flags parameter */
|
||||||
int load_flags;
|
int error;
|
||||||
unsigned char *source;
|
|
||||||
FT_Glyph glyph;
|
FT_Glyph glyph;
|
||||||
FT_GlyphSlot glyph_slot;
|
FT_GlyphSlot glyph_slot;
|
||||||
FT_Bitmap bitmap;
|
FT_Bitmap bitmap;
|
||||||
FT_BitmapGlyph bitmap_glyph;
|
FT_BitmapGlyph bitmap_glyph;
|
||||||
int stroke_width = 0;
|
|
||||||
FT_Stroker stroker = NULL;
|
FT_Stroker stroker = NULL;
|
||||||
FT_Int left;
|
GlyphInfo *glyph_info = NULL; /* computed text layout */
|
||||||
/* render string into given buffer (the buffer *must* have
|
size_t i, count; /* glyph_info index and length */
|
||||||
the right size, or this will crash) */
|
int xx, yy; /* pixel offset of current glyph bitmap */
|
||||||
PyObject* string;
|
int x0, x1; /* horizontal bounds of glyph bitmap to copy */
|
||||||
|
unsigned int bitmap_y; /* glyph bitmap y index */
|
||||||
|
unsigned char *source; /* glyph bitmap source buffer */
|
||||||
|
Imaging im;
|
||||||
Py_ssize_t id;
|
Py_ssize_t id;
|
||||||
int mask = 0;
|
int horizontal_dir; /* is primary axis horizontal? */
|
||||||
int temp;
|
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
||||||
int xx, x0, x1;
|
int stroke_width = 0;
|
||||||
int yy;
|
|
||||||
unsigned int bitmap_y;
|
|
||||||
const char *dir = NULL;
|
const char *dir = NULL;
|
||||||
const char *lang = NULL;
|
const char *lang = NULL;
|
||||||
size_t i, count;
|
PyObject *features = Py_None;
|
||||||
GlyphInfo *glyph_info;
|
PyObject* string;
|
||||||
PyObject *features = NULL;
|
|
||||||
|
/* render string into given buffer (the buffer *must* have
|
||||||
|
the right size, or this will crash) */
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "On|izOzi:render", &string, &id, &mask, &dir, &features, &lang,
|
if (!PyArg_ParseTuple(args, "On|izOzi:render", &string, &id, &mask, &dir, &features, &lang,
|
||||||
&stroke_width)) {
|
&stroke_width)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
glyph_info = NULL;
|
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||||
|
|
||||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask);
|
count = text_layout(string, self, dir, features, lang, &glyph_info, mask);
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -813,16 +792,23 @@ font_render(FontObject* self, PyObject* args)
|
||||||
}
|
}
|
||||||
|
|
||||||
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_NO_BITMAP;
|
load_flags = FT_LOAD_NO_BITMAP;
|
||||||
if (mask) {
|
if (mask) {
|
||||||
load_flags |= FT_LOAD_TARGET_MONO;
|
load_flags |= FT_LOAD_TARGET_MONO;
|
||||||
}
|
}
|
||||||
|
|
||||||
ascender = 0;
|
/*
|
||||||
|
* calculate x_min and y_max
|
||||||
|
* must match font_getsize or there may be clipping!
|
||||||
|
*/
|
||||||
|
x = y = x_min = y_max = 0;
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
index = glyph_info[i].index;
|
px = PIXEL(x + glyph_info[i].x_offset);
|
||||||
error = FT_Load_Glyph(self->face, index, load_flags | FT_LOAD_RENDER);
|
py = PIXEL(y + glyph_info[i].y_offset);
|
||||||
|
|
||||||
|
error = FT_Load_Glyph(self->face, glyph_info[i].index, load_flags | FT_LOAD_RENDER);
|
||||||
if (error) {
|
if (error) {
|
||||||
return geterror(error);
|
return geterror(error);
|
||||||
}
|
}
|
||||||
|
@ -830,22 +816,30 @@ font_render(FontObject* self, PyObject* args)
|
||||||
glyph_slot = self->face->glyph;
|
glyph_slot = self->face->glyph;
|
||||||
bitmap = glyph_slot->bitmap;
|
bitmap = glyph_slot->bitmap;
|
||||||
|
|
||||||
temp = bitmap.rows - glyph_slot->bitmap_top;
|
if (glyph_slot->bitmap_top + py > y_max) {
|
||||||
temp -= PIXEL(glyph_info[i].y_offset);
|
y_max = glyph_slot->bitmap_top + py;
|
||||||
if (temp > ascender) {
|
|
||||||
ascender = temp;
|
|
||||||
}
|
}
|
||||||
|
if (glyph_slot->bitmap_left + px < x_min) {
|
||||||
|
x_min = glyph_slot->bitmap_left + px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
x += glyph_info[i].x_advance;
|
||||||
|
y += glyph_info[i].y_advance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set pen position to text origin */
|
||||||
|
x = (-x_min + stroke_width) << 6;
|
||||||
|
y = (-y_max + (-stroke_width)) << 6;
|
||||||
|
|
||||||
if (stroker == NULL) {
|
if (stroker == NULL) {
|
||||||
load_flags |= FT_LOAD_RENDER;
|
load_flags |= FT_LOAD_RENDER;
|
||||||
}
|
}
|
||||||
|
|
||||||
x = y = 0;
|
|
||||||
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
index = glyph_info[i].index;
|
px = PIXEL(x + glyph_info[i].x_offset);
|
||||||
error = FT_Load_Glyph(self->face, index, load_flags);
|
py = PIXEL(y + glyph_info[i].y_offset);
|
||||||
|
|
||||||
|
error = FT_Load_Glyph(self->face, glyph_info[i].index, load_flags);
|
||||||
if (error) {
|
if (error) {
|
||||||
return geterror(error);
|
return geterror(error);
|
||||||
}
|
}
|
||||||
|
@ -867,25 +861,15 @@ font_render(FontObject* self, PyObject* args)
|
||||||
bitmap_glyph = (FT_BitmapGlyph)glyph;
|
bitmap_glyph = (FT_BitmapGlyph)glyph;
|
||||||
|
|
||||||
bitmap = bitmap_glyph->bitmap;
|
bitmap = bitmap_glyph->bitmap;
|
||||||
left = bitmap_glyph->left;
|
xx = px + bitmap_glyph->left;
|
||||||
|
yy = -(py + bitmap_glyph->top);
|
||||||
} else {
|
} else {
|
||||||
bitmap = glyph_slot->bitmap;
|
bitmap = glyph_slot->bitmap;
|
||||||
left = glyph_slot->bitmap_left;
|
xx = px + glyph_slot->bitmap_left;
|
||||||
}
|
yy = -(py + glyph_slot->bitmap_top);
|
||||||
|
|
||||||
if (horizontal_dir) {
|
|
||||||
if (i == 0 && glyph_slot->metrics.horiBearingX < 0) {
|
|
||||||
x = -glyph_slot->metrics.horiBearingX;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* clip glyph bitmap width to target image bounds */
|
||||||
x0 = 0;
|
x0 = 0;
|
||||||
x1 = bitmap.width;
|
x1 = bitmap.width;
|
||||||
if (xx < 0) {
|
if (xx < 0) {
|
||||||
|
@ -896,14 +880,8 @@ font_render(FontObject* self, PyObject* args)
|
||||||
}
|
}
|
||||||
|
|
||||||
source = (unsigned char*) bitmap.buffer;
|
source = (unsigned char*) bitmap.buffer;
|
||||||
for (bitmap_y = 0; bitmap_y < bitmap.rows; bitmap_y++) {
|
for (bitmap_y = 0; bitmap_y < bitmap.rows; bitmap_y++, yy++) {
|
||||||
if (horizontal_dir) {
|
/* clip glyph bitmap height to target image bounds */
|
||||||
yy = bitmap_y + im->ysize - (PIXEL(glyph_slot->metrics.horiBearingY) + ascender);
|
|
||||||
yy -= PIXEL(glyph_info[i].y_offset) + stroke_width * 2;
|
|
||||||
} else {
|
|
||||||
yy = bitmap_y + PIXEL(y + glyph_slot->metrics.vertBearingY) + ascender;
|
|
||||||
yy += PIXEL(glyph_info[i].y_offset);
|
|
||||||
}
|
|
||||||
if (yy >= 0 && yy < im->ysize) {
|
if (yy >= 0 && yy < im->ysize) {
|
||||||
// blend this glyph into the buffer
|
// blend this glyph into the buffer
|
||||||
unsigned char *target = im->image8[yy] + xx;
|
unsigned char *target = im->image8[yy] + xx;
|
||||||
|
@ -932,7 +910,7 @@ font_render(FontObject* self, PyObject* args)
|
||||||
source += 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;
|
||||||
if (stroker != NULL) {
|
if (stroker != NULL) {
|
||||||
FT_Done_Glyph(glyph);
|
FT_Done_Glyph(glyph);
|
||||||
}
|
}
|
||||||
|
|