Skip to content

Commit 389ad11

Browse files
committed
Only call text_layout once in getmask2
1 parent 1fc8d82 commit 389ad11

2 files changed

Lines changed: 144 additions & 105 deletions

File tree

src/PIL/ImageFont.py

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
#
2727

2828
import base64
29-
import math
3029
import os
3130
import sys
3231
import warnings
@@ -551,28 +550,23 @@ def getmask2(
551550
:py:mod:`PIL.Image.core` interface module, and the text offset, the
552551
gap between the starting coordinate and the first marking
553552
"""
554-
size, offset = self.font.getsize(
555-
text, mode, direction, features, language, anchor
556-
)
557553
if start is None:
558554
start = (0, 0)
559-
size = tuple(math.ceil(size[i] + stroke_width * 2 + start[i]) for i in range(2))
560-
offset = offset[0] - stroke_width, offset[1] - stroke_width
555+
im, size, offset = self.font.render(
556+
text,
557+
Image.core.fill,
558+
mode,
559+
direction,
560+
features,
561+
language,
562+
stroke_width,
563+
anchor,
564+
ink,
565+
start[0],
566+
start[1],
567+
Image.MAX_IMAGE_PIXELS,
568+
)
561569
Image._decompression_bomb_check(size)
562-
im = Image.core.fill("RGBA" if mode == "RGBA" else "L", size, 0)
563-
if min(size):
564-
self.font.render(
565-
text,
566-
im.id,
567-
mode,
568-
direction,
569-
features,
570-
language,
571-
stroke_width,
572-
ink,
573-
start[0],
574-
start[1],
575-
)
576570
return im, offset
577571

578572
def font_variant(

src/_imagingft.c

Lines changed: 130 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -551,73 +551,25 @@ font_getlength(FontObject *self, PyObject *args) {
551551
return PyLong_FromLong(length);
552552
}
553553

554-
static PyObject *
555-
font_getsize(FontObject *self, PyObject *args) {
554+
static int
555+
bounding_box_and_anchors(FT_Face face, const char *anchor, int horizontal_dir, GlyphInfo *glyph_info, size_t count, int load_flags, int *width, int *height, int *x_offset, int *y_offset) {
556556
int position; /* pen position along primary axis, in 26.6 precision */
557557
int advanced; /* pen position along primary axis, in pixels */
558558
int px, py; /* position of current glyph, in pixels */
559559
int x_min, x_max, y_min, y_max; /* text bounding box, in pixels */
560560
int x_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */
561-
int load_flags; /* FreeType load_flags parameter */
562561
int error;
563-
FT_Face face;
564562
FT_Glyph glyph;
565-
FT_BBox bbox; /* glyph bounding box */
566-
GlyphInfo *glyph_info = NULL; /* computed text layout */
567-
size_t i, count; /* glyph_info index and length */
568-
int horizontal_dir; /* is primary axis horizontal? */
569-
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
570-
int color = 0; /* is FT_LOAD_COLOR enabled? */
571-
const char *mode = NULL;
572-
const char *dir = NULL;
573-
const char *lang = NULL;
574-
const char *anchor = NULL;
575-
PyObject *features = Py_None;
576-
PyObject *string;
577-
578-
/* calculate size and bearing for a given string */
579-
580-
if (!PyArg_ParseTuple(
581-
args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor)) {
582-
return NULL;
583-
}
584-
585-
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
586-
587-
mask = mode && strcmp(mode, "1") == 0;
588-
color = mode && strcmp(mode, "RGBA") == 0;
589-
590-
if (anchor == NULL) {
591-
anchor = horizontal_dir ? "la" : "lt";
592-
}
593-
if (strlen(anchor) != 2) {
594-
goto bad_anchor;
595-
}
596-
597-
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
598-
if (PyErr_Occurred()) {
599-
return NULL;
600-
}
601-
602-
load_flags = FT_LOAD_DEFAULT;
603-
if (mask) {
604-
load_flags |= FT_LOAD_TARGET_MONO;
605-
}
606-
if (color) {
607-
load_flags |= FT_LOAD_COLOR;
608-
}
609-
563+
FT_BBox bbox; /* glyph bounding box */
564+
size_t i; /* glyph_info index */
610565
/*
611566
* text bounds are given by:
612567
* - bounding boxes of individual glyphs
613568
* - pen line, i.e. 0 to `advanced` along primary axis
614569
* this means point (0, 0) is part of the text bounding box
615570
*/
616-
face = NULL;
617571
position = x_min = x_max = y_min = y_max = 0;
618572
for (i = 0; i < count; i++) {
619-
face = self->face;
620-
621573
if (horizontal_dir) {
622574
px = PIXEL(position + glyph_info[i].x_offset);
623575
py = PIXEL(glyph_info[i].y_offset);
@@ -640,12 +592,14 @@ font_getsize(FontObject *self, PyObject *args) {
640592

641593
error = FT_Load_Glyph(face, glyph_info[i].index, load_flags);
642594
if (error) {
643-
return geterror(error);
595+
geterror(error);
596+
return 1;
644597
}
645598

646599
error = FT_Get_Glyph(face->glyph, &glyph);
647600
if (error) {
648-
return geterror(error);
601+
geterror(error);
602+
return 1;
649603
}
650604

651605
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox);
@@ -669,13 +623,15 @@ font_getsize(FontObject *self, PyObject *args) {
669623
FT_Done_Glyph(glyph);
670624
}
671625

672-
if (glyph_info) {
673-
PyMem_Free(glyph_info);
674-
glyph_info = NULL;
626+
if (anchor == NULL) {
627+
anchor = horizontal_dir ? "la" : "lt";
628+
}
629+
if (strlen(anchor) != 2) {
630+
goto bad_anchor;
675631
}
676632

677633
x_anchor = y_anchor = 0;
678-
if (face) {
634+
if (count) {
679635
if (horizontal_dir) {
680636
switch (anchor[0]) {
681637
case 'l': // left
@@ -693,15 +649,15 @@ font_getsize(FontObject *self, PyObject *args) {
693649
}
694650
switch (anchor[1]) {
695651
case 'a': // ascender
696-
y_anchor = PIXEL(self->face->size->metrics.ascender);
652+
y_anchor = PIXEL(face->size->metrics.ascender);
697653
break;
698654
case 't': // top
699655
y_anchor = y_max;
700656
break;
701657
case 'm': // middle (ascender + descender) / 2
702658
y_anchor = PIXEL(
703-
(self->face->size->metrics.ascender +
704-
self->face->size->metrics.descender) /
659+
(face->size->metrics.ascender +
660+
face->size->metrics.descender) /
705661
2);
706662
break;
707663
case 's': // horizontal baseline
@@ -711,7 +667,7 @@ font_getsize(FontObject *self, PyObject *args) {
711667
y_anchor = y_min;
712668
break;
713669
case 'd': // descender
714-
y_anchor = PIXEL(self->face->size->metrics.descender);
670+
y_anchor = PIXEL(face->size->metrics.descender);
715671
break;
716672
default:
717673
goto bad_anchor;
@@ -751,17 +707,74 @@ font_getsize(FontObject *self, PyObject *args) {
751707
}
752708
}
753709
}
754-
755-
return Py_BuildValue(
756-
"(ii)(ii)",
757-
(x_max - x_min),
758-
(y_max - y_min),
759-
(-x_anchor + x_min),
760-
-(-y_anchor + y_max));
710+
*width = x_max - x_min;
711+
*height = y_max - y_min;
712+
*x_offset = -x_anchor + x_min;
713+
*y_offset = -(-y_anchor + y_max);
714+
return 0;
761715

762716
bad_anchor:
763717
PyErr_Format(PyExc_ValueError, "bad anchor specified: %s", anchor);
764-
return NULL;
718+
return 1;
719+
}
720+
721+
static PyObject *
722+
font_getsize(FontObject *self, PyObject *args) {
723+
int width, height, x_offset, y_offset;
724+
int load_flags; /* FreeType load_flags parameter */
725+
int error;
726+
GlyphInfo *glyph_info = NULL; /* computed text layout */
727+
size_t count; /* glyph_info length */
728+
int horizontal_dir; /* is primary axis horizontal? */
729+
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
730+
int color = 0; /* is FT_LOAD_COLOR enabled? */
731+
const char *mode = NULL;
732+
const char *dir = NULL;
733+
const char *lang = NULL;
734+
const char *anchor = NULL;
735+
PyObject *features = Py_None;
736+
PyObject *string;
737+
738+
/* calculate size and bearing for a given string */
739+
740+
if (!PyArg_ParseTuple(
741+
args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor)) {
742+
return NULL;
743+
}
744+
745+
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
746+
747+
mask = mode && strcmp(mode, "1") == 0;
748+
color = mode && strcmp(mode, "RGBA") == 0;
749+
750+
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
751+
if (PyErr_Occurred()) {
752+
return NULL;
753+
}
754+
755+
load_flags = FT_LOAD_DEFAULT;
756+
if (mask) {
757+
load_flags |= FT_LOAD_TARGET_MONO;
758+
}
759+
if (color) {
760+
load_flags |= FT_LOAD_COLOR;
761+
}
762+
763+
error = bounding_box_and_anchors(self->face, anchor, horizontal_dir, glyph_info, count, load_flags, &width, &height, &x_offset, &y_offset);
764+
if (glyph_info) {
765+
PyMem_Free(glyph_info);
766+
glyph_info = NULL;
767+
}
768+
if (error) {
769+
return NULL;
770+
}
771+
772+
return Py_BuildValue(
773+
"(ii)(ii)",
774+
width,
775+
height,
776+
x_offset,
777+
y_offset);
765778
}
766779

767780
static PyObject *
@@ -785,6 +798,7 @@ font_render(FontObject *self, PyObject *args) {
785798
unsigned int bitmap_y; /* glyph bitmap y index */
786799
unsigned char *source; /* glyph bitmap source buffer */
787800
unsigned char convert_scale; /* scale factor for non-8bpp bitmaps */
801+
PyObject *image;
788802
Imaging im;
789803
Py_ssize_t id;
790804
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
@@ -795,27 +809,34 @@ font_render(FontObject *self, PyObject *args) {
795809
const char *mode = NULL;
796810
const char *dir = NULL;
797811
const char *lang = NULL;
812+
const char *anchor = NULL;
798813
PyObject *features = Py_None;
799814
PyObject *string;
815+
PyObject *fill;
800816
float x_start = 0;
801817
float y_start = 0;
818+
int width, height, x_offset, y_offset;
819+
int horizontal_dir; /* is primary axis horizontal? */
820+
PyObject *max_image_pixels = Py_None;
802821

803822
/* render string into given buffer (the buffer *must* have
804823
the right size, or this will crash) */
805824

806825
if (!PyArg_ParseTuple(
807826
args,
808-
"On|zzOziLff:render",
827+
"OO|zzOzizLffO:render",
809828
&string,
810-
&id,
829+
&fill,
811830
&mode,
812831
&dir,
813832
&features,
814833
&lang,
815834
&stroke_width,
835+
&anchor,
816836
&foreground_ink_long,
817837
&x_start,
818-
&y_start)) {
838+
&y_start,
839+
&max_image_pixels)) {
819840
return NULL;
820841
}
821842

@@ -841,8 +862,41 @@ font_render(FontObject *self, PyObject *args) {
841862
if (PyErr_Occurred()) {
842863
return NULL;
843864
}
844-
if (count == 0) {
845-
Py_RETURN_NONE;
865+
866+
load_flags = stroke_width ? FT_LOAD_NO_BITMAP : FT_LOAD_DEFAULT;
867+
if (mask) {
868+
load_flags |= FT_LOAD_TARGET_MONO;
869+
}
870+
if (color) {
871+
load_flags |= FT_LOAD_COLOR;
872+
}
873+
874+
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
875+
876+
error = bounding_box_and_anchors(self->face, anchor, horizontal_dir, glyph_info, count, load_flags, &width, &height, &x_offset, &y_offset);
877+
if (error) {
878+
PyMem_Del(glyph_info);
879+
return NULL;
880+
}
881+
882+
width += stroke_width * 2 + ceil(x_start);
883+
height += stroke_width * 2 + ceil(y_start);
884+
if (max_image_pixels != Py_None) {
885+
if (width * height > PyLong_AsLong(max_image_pixels) * 2) {
886+
PyMem_Del(glyph_info);
887+
return Py_BuildValue("O(ii)(ii)", Py_None, width, height, 0, 0);
888+
}
889+
}
890+
891+
image = PyObject_CallFunction(fill, "s(ii)", strcmp(mode, "RGBA") == 0 ? "RGBA" : "L", width, height);
892+
id = PyLong_AsSsize_t(PyObject_GetAttrString(image, "id"));
893+
im = (Imaging)id;
894+
895+
x_offset -= stroke_width;
896+
y_offset -= stroke_width;
897+
if (count == 0 || width == 0 || height == 0) {
898+
PyMem_Del(glyph_info);
899+
return Py_BuildValue("O(ii)(ii)", image, width, height, x_offset, y_offset);
846900
}
847901

848902
if (stroke_width) {
@@ -859,15 +913,6 @@ font_render(FontObject *self, PyObject *args) {
859913
0);
860914
}
861915

862-
im = (Imaging)id;
863-
load_flags = stroke_width ? FT_LOAD_NO_BITMAP : FT_LOAD_DEFAULT;
864-
if (mask) {
865-
load_flags |= FT_LOAD_TARGET_MONO;
866-
}
867-
if (color) {
868-
load_flags |= FT_LOAD_COLOR;
869-
}
870-
871916
/*
872917
* calculate x_min and y_max
873918
* must match font_getsize or there may be clipping!
@@ -1064,7 +1109,7 @@ font_render(FontObject *self, PyObject *args) {
10641109
}
10651110
FT_Stroker_Done(stroker);
10661111
PyMem_Del(glyph_info);
1067-
Py_RETURN_NONE;
1112+
return Py_BuildValue("O(ii)(ii)", image, width, height, x_offset, y_offset);
10681113

10691114
glyph_error:
10701115
if (stroker != NULL) {

0 commit comments

Comments
 (0)