Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Fix #253: Use advance width to calculate text bounding box
  • Loading branch information
mdboom committed Jan 27, 2015
commit c6d01835816ace3204212d072da1ac3af2b54997
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
2015-01-23 Text bounding boxes are now computed with advance width rather than
ink area. This may result in slightly different placement of text.

2014-10-27 Allowed selection of the backend using the `MPLBACKEND` environment
variable. Added documentation on backend selection methods.

Expand Down
5 changes: 4 additions & 1 deletion lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,15 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
font.draw_glyphs_to_bitmap(antialiased=rcParams['text.antialiased'])
d = font.get_descent() / 64.0
# The descent needs to be adjusted for the angle
xo, yo = font.get_bitmap_offset()
xo /= 64.0
yo /= 64.0
xd = -d * np.sin(np.deg2rad(angle))
yd = d * np.cos(np.deg2rad(angle))

#print x, y, int(x), int(y), s
self._renderer.draw_text_image(
font, np.round(x - xd), np.round(y + yd) + 1, angle, gc)
font, np.round(x - xd + xo), np.round(y + yd + yo) + 1, angle, gc)

def get_text_width_height_descent(self, s, prop, ismath):
"""
Expand Down
101 changes: 36 additions & 65 deletions src/ft2font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -569,46 +569,6 @@ void FT2Font::select_charmap(unsigned long i)
}
}

FT_BBox FT2Font::compute_string_bbox()
{
FT_BBox bbox;
/* initialize string bbox to "empty" values */
bbox.xMin = bbox.yMin = 32000;
bbox.xMax = bbox.yMax = -32000;

int right_side = 0;
for (size_t n = 0; n < glyphs.size(); n++) {
FT_BBox glyph_bbox;
FT_Glyph_Get_CBox(glyphs[n], ft_glyph_bbox_subpixels, &glyph_bbox);
if (glyph_bbox.xMin < bbox.xMin) {
bbox.xMin = glyph_bbox.xMin;
}
if (glyph_bbox.yMin < bbox.yMin) {
bbox.yMin = glyph_bbox.yMin;
}
if (glyph_bbox.xMin == glyph_bbox.xMax) {
right_side += glyphs[n]->advance.x >> 10;
if (right_side > bbox.xMax) {
bbox.xMax = right_side;
}
} else {
if (glyph_bbox.xMax > bbox.xMax) {
bbox.xMax = glyph_bbox.xMax;
}
}
if (glyph_bbox.yMax > bbox.yMax)
bbox.yMax = glyph_bbox.yMax;
}
/* check that we really grew the string bbox */
if (bbox.xMin > bbox.xMax) {
bbox.xMin = 0;
bbox.yMin = 0;
bbox.xMax = 0;
bbox.yMax = 0;
}
return bbox;
}

int FT2Font::get_kerning(int left, int right, int mode)
{
if (!FT_HAS_KERNING(face)) {
Expand All @@ -617,7 +577,7 @@ int FT2Font::get_kerning(int left, int right, int mode)
FT_Vector delta;

if (!FT_Get_Kerning(face, left, right, mode, &delta)) {
return (int)(delta.x / hinting_factor);
return (int)(delta.x) / (hinting_factor << 6);
} else {
return 0;
}
Expand All @@ -641,17 +601,22 @@ void FT2Font::set_text(
pen.x = 0;
pen.y = 0;

bbox.xMin = bbox.yMin = 32000;
bbox.xMax = bbox.yMax = -32000;

for (unsigned int n = 0; n < N; n++) {
std::string thischar("?");
FT_UInt glyph_index;
FT_BBox glyph_bbox;
FT_Pos last_advance;

glyph_index = FT_Get_Char_Index(face, codepoints[n]);

// retrieve kerning distance and move pen position
if (use_kerning && previous && glyph_index) {
FT_Vector delta;
FT_Get_Kerning(face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
pen.x += delta.x / hinting_factor;
pen.x += (delta.x << 10) / (hinting_factor << 16);
}
error = FT_Load_Glyph(face, glyph_index, flags);
if (error) {
Expand All @@ -669,18 +634,30 @@ void FT2Font::set_text(
}
// ignore errors, jump to next glyph

last_advance = face->glyph->advance.x;
FT_Glyph_Transform(thisGlyph, 0, &pen);
FT_Glyph_Transform(thisGlyph, &matrix, 0);
xys.push_back(pen.x);
xys.push_back(pen.y);
pen.x += face->glyph->advance.x;

FT_Glyph_Get_CBox(thisGlyph, ft_glyph_bbox_subpixels, &glyph_bbox);

bbox.xMin = std::min(bbox.xMin, glyph_bbox.xMin);
bbox.xMax = std::max(bbox.xMax, glyph_bbox.xMax);
bbox.yMin = std::min(bbox.yMin, glyph_bbox.yMin);
bbox.yMax = std::max(bbox.yMax, glyph_bbox.yMax);

pen.x += last_advance;

previous = glyph_index;
glyphs.push_back(thisGlyph);
}

// now apply the rotation
for (unsigned int n = 0; n < glyphs.size(); n++) {
FT_Glyph_Transform(glyphs[n], &matrix, 0);
FT_Vector_Transform(&pen, &matrix);
advance = pen.x;

if (bbox.xMin > bbox.xMax) {
bbox.xMin = bbox.yMin = bbox.xMax = bbox.yMax = 0;
}
}

Expand Down Expand Up @@ -722,30 +699,29 @@ void FT2Font::load_glyph(FT_UInt glyph_index, FT_UInt32 flags)

void FT2Font::get_width_height(long *width, long *height)
{
FT_BBox bbox = compute_string_bbox();

*width = bbox.xMax - bbox.xMin;
*width = advance;
*height = bbox.yMax - bbox.yMin;
}

long FT2Font::get_descent()
{
FT_BBox bbox = compute_string_bbox();
return -bbox.yMin;
}

void FT2Font::get_bitmap_offset(long *x, long *y)
{
*x = bbox.xMin;
*y = 0;
}

void FT2Font::draw_glyphs_to_bitmap(bool antialiased)
{
FT_BBox string_bbox = compute_string_bbox();
size_t width = (string_bbox.xMax - string_bbox.xMin) / 64 + 2;
size_t height = (string_bbox.yMax - string_bbox.yMin) / 64 + 2;
size_t width = (bbox.xMax - bbox.xMin) / 64 + 2;
size_t height = (bbox.yMax - bbox.yMin) / 64 + 2;

image.resize(width, height);

for (size_t n = 0; n < glyphs.size(); n++) {
FT_BBox bbox;
FT_Glyph_Get_CBox(glyphs[n], ft_glyph_bbox_pixels, &bbox);

error = FT_Glyph_To_Bitmap(
&glyphs[n], antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1);
if (error) {
Expand All @@ -756,22 +732,17 @@ void FT2Font::draw_glyphs_to_bitmap(bool antialiased)
// now, draw to our target surface (convert position)

// bitmap left and top in pixel, string bbox in subpixel
FT_Int x = (FT_Int)(bitmap->left - (string_bbox.xMin / 64.));
FT_Int y = (FT_Int)((string_bbox.yMax / 64.) - bitmap->top + 1);
FT_Int x = (FT_Int)(bitmap->left - (bbox.xMin / 64.));
FT_Int y = (FT_Int)((bbox.yMax / 64.) - bitmap->top + 1);

image.draw_bitmap(&bitmap->bitmap, x, y);
}
}

void FT2Font::get_xys(bool antialiased, std::vector<double> &xys)
{
FT_BBox string_bbox = compute_string_bbox();

for (size_t n = 0; n < glyphs.size(); n++) {

FT_BBox bbox;
FT_Glyph_Get_CBox(glyphs[n], ft_glyph_bbox_pixels, &bbox);

error = FT_Glyph_To_Bitmap(
&glyphs[n], antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO, 0, 1);
if (error) {
Expand All @@ -781,8 +752,8 @@ void FT2Font::get_xys(bool antialiased, std::vector<double> &xys)
FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyphs[n];

// bitmap left and top in pixel, string bbox in subpixel
FT_Int x = (FT_Int)(bitmap->left - string_bbox.xMin / 64.);
FT_Int y = (FT_Int)(string_bbox.yMax / 64. - bitmap->top + 1);
FT_Int x = (FT_Int)(bitmap->left - bbox.xMin / 64.);
FT_Int y = (FT_Int)(bbox.yMax / 64. - bitmap->top + 1);
// make sure the index is non-neg
x = x < 0 ? 0 : x;
y = y < 0 ? 0 : y;
Expand Down
4 changes: 3 additions & 1 deletion src/ft2font.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class FT2Font
void load_char(long charcode, FT_UInt32 flags);
void load_glyph(FT_UInt glyph_index, FT_UInt32 flags);
void get_width_height(long *width, long *height);
void get_bitmap_offset(long *x, long *y);
long get_descent();
// TODO: Since we know the size of the array upfront, we probably don't
// need to dynamically allocate like this
Expand Down Expand Up @@ -121,12 +122,13 @@ class FT2Font
FT_Error error;
std::vector<FT_Glyph> glyphs;
std::vector<FT_Vector> pos;
FT_BBox bbox;
FT_Pos advance;
double angle;
double ptsize;
double dpi;
long hinting_factor;

FT_BBox compute_string_bbox();
void set_scalable_attributes();

// prevent copying
Expand Down
15 changes: 15 additions & 0 deletions src/ft2font_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,20 @@ static PyObject *PyFT2Font_get_width_height(PyFT2Font *self, PyObject *args, PyO
return Py_BuildValue("ll", width, height);
}

const char *PyFT2Font_get_bitmap_offset__doc__ =
"w, h = get_bitmap_offset()\n"
"\n"
"Get the offset in 26.6 subpixels for the bitmap if ink hangs left or below (0, 0)\n";

static PyObject *PyFT2Font_get_bitmap_offset(PyFT2Font *self, PyObject *args, PyObject *kwds)
{
long x, y;

CALL_CPP("get_bitmap_offset", (self->x->get_bitmap_offset(&x, &y)));

return Py_BuildValue("ll", x, y);
}

const char *PyFT2Font_get_descent__doc__ =
"d = get_descent()\n"
"\n"
Expand Down Expand Up @@ -1572,6 +1586,7 @@ static PyTypeObject *PyFT2Font_init_type(PyObject *m, PyTypeObject *type)
{"load_char", (PyCFunction)PyFT2Font_load_char, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_char__doc__},
{"load_glyph", (PyCFunction)PyFT2Font_load_glyph, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_glyph__doc__},
{"get_width_height", (PyCFunction)PyFT2Font_get_width_height, METH_NOARGS, PyFT2Font_get_width_height__doc__},
{"get_bitmap_offset", (PyCFunction)PyFT2Font_get_bitmap_offset, METH_NOARGS, PyFT2Font_get_bitmap_offset__doc__},
{"get_descent", (PyCFunction)PyFT2Font_get_descent, METH_NOARGS, PyFT2Font_get_descent__doc__},
{"draw_glyphs_to_bitmap", (PyCFunction)PyFT2Font_draw_glyphs_to_bitmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_draw_glyphs_to_bitmap__doc__},
{"get_xys", (PyCFunction)PyFT2Font_get_xys, METH_VARARGS|METH_KEYWORDS, PyFT2Font_get_xys__doc__},
Expand Down