Merge remote-tracking branch 'upstream/master' into run-black

This commit is contained in:
Hugo 2019-06-19 09:29:28 +03:00
commit 1b99362f3d
21 changed files with 248 additions and 127 deletions

View File

@ -1,7 +1,6 @@
NotoNastaliqUrdu-Regular.ttf: NotoNastaliqUrdu-Regular.ttf, from https://github.com/googlei18n/noto-fonts
NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/
(from https://github.com/googlei18n/noto-fonts)
All Noto fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to. All Noto fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -60,11 +60,10 @@ class TestFileIcns(PillowTestCase):
for w, h, r in im.info["sizes"]: for w, h, r in im.info["sizes"]:
wr = w * r wr = w * r
hr = h * r hr = h * r
im2 = Image.open(TEST_FILE) im.size = (w, h, r)
im2.size = (w, h, r) im.load()
im2.load() self.assertEqual(im.mode, "RGBA")
self.assertEqual(im2.mode, "RGBA") self.assertEqual(im.size, (wr, hr))
self.assertEqual(im2.size, (wr, hr))
# Check that we cannot load an incorrect size # Check that we cannot load an incorrect size
with self.assertRaises(ValueError): with self.assertRaises(ValueError):

View File

@ -1,7 +1,7 @@
from .helper import PillowTestCase, hopper from .helper import PillowTestCase, hopper
import io import io
from PIL import Image, IcoImagePlugin from PIL import Image, ImageDraw, IcoImagePlugin
TEST_ICO_FILE = "Tests/images/hopper.ico" TEST_ICO_FILE = "Tests/images/hopper.ico"
@ -87,3 +87,16 @@ class TestFileIco(PillowTestCase):
UserWarning, Image.open, "Tests/images/hopper_unexpected.ico" UserWarning, Image.open, "Tests/images/hopper_unexpected.ico"
) )
self.assertEqual(im.size, (16, 16)) self.assertEqual(im.size, (16, 16))
def test_draw_reloaded(self):
im = Image.open(TEST_ICO_FILE)
outfile = self.tempfile("temp_saved_hopper_draw.ico")
draw = ImageDraw.Draw(im)
draw.line((0, 0) + im.size, '#f00')
im.save(outfile)
im = Image.open(outfile)
im.save("Tests/images/hopper_draw.ico")
reloaded = Image.open("Tests/images/hopper_draw.ico")
self.assert_image_equal(im, reloaded)

View File

@ -16,6 +16,12 @@ class TestImagePsd(PillowTestCase):
im2 = hopper() im2 = hopper()
self.assert_image_similar(im, im2, 4.8) self.assert_image_similar(im, im2, 4.8)
def test_unclosed_file(self):
def open():
im = Image.open(test_file)
im.load()
self.assert_warning(None, open)
def test_invalid_file(self): def test_invalid_file(self):
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
@ -63,6 +69,12 @@ class TestImagePsd(PillowTestCase):
self.assertRaises(EOFError, im.seek, -1) self.assertRaises(EOFError, im.seek, -1)
def test_open_after_exclusive_load(self):
im = Image.open(test_file)
im.load()
im.seek(im.tell()+1)
im.load()
def test_icc_profile(self): def test_icc_profile(self):
im = Image.open(test_file) im = Image.open(test_file)
self.assertIn("icc_profile", im.info) self.assertIn("icc_profile", im.info)

View File

@ -25,6 +25,17 @@ class TestImageMode(PillowTestCase):
self.assertEqual(m.basemode, "L") self.assertEqual(m.basemode, "L")
self.assertEqual(m.basetype, "L") self.assertEqual(m.basetype, "L")
for mode in ("I;16", "I;16S",
"I;16L", "I;16LS",
"I;16B", "I;16BS",
"I;16N", "I;16NS"):
m = ImageMode.getmode(mode)
self.assertEqual(m.mode, mode)
self.assertEqual(str(m), mode)
self.assertEqual(m.bands, ("I",))
self.assertEqual(m.basemode, "L")
self.assertEqual(m.basetype, "L")
m = ImageMode.getmode("RGB") m = ImageMode.getmode("RGB")
self.assertEqual(m.mode, "RGB") self.assertEqual(m.mode, "RGB")
self.assertEqual(str(m), "RGB") self.assertEqual(str(m), "RGB")

View File

@ -99,6 +99,22 @@ class TestImagecomplextext(PillowTestCase):
self.assert_image_similar(im, target_img, 0.5) self.assert_image_similar(im, target_img, 0.5)
def test_text_direction_ttb(self):
ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", FONT_SIZE)
im = Image.new(mode='RGB', size=(100, 300))
draw = ImageDraw.Draw(im)
try:
draw.text((0, 0), 'English あい', font=ttf, fill=500, direction='ttb')
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.png'
target_img = Image.open(target)
self.assert_image_similar(im, target_img, 1.15)
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

@ -31,6 +31,12 @@ class TestImageSequence(PillowTestCase):
self.assertRaises(IndexError, lambda: i[index + 1]) self.assertRaises(IndexError, lambda: i[index + 1])
self.assertRaises(StopIteration, next, i) self.assertRaises(StopIteration, next, i)
def test_iterator_min_frame(self):
im = Image.open('Tests/images/hopper.psd')
i = ImageSequence.Iterator(im)
for index in range(1, im.n_frames):
self.assertEqual(i[index], next(i))
def _test_multipage_tiff(self): def _test_multipage_tiff(self):
im = Image.open("Tests/images/multipage.tiff") im = Image.open("Tests/images/multipage.tiff")
for index, frame in enumerate(ImageSequence.Iterator(im)): for index, frame in enumerate(ImageSequence.Iterator(im)):

View File

@ -17,17 +17,18 @@ class TestImageShow(PillowTestCase):
ImageShow._viewers.pop() ImageShow._viewers.pop()
def test_show(self): def test_show(self):
class TestViewer: class TestViewer(ImageShow.Viewer):
methodCalled = False methodCalled = False
def show(self, image, title=None, **options): def show_image(self, image, **options):
self.methodCalled = True self.methodCalled = True
return True return True
viewer = TestViewer() viewer = TestViewer()
ImageShow.register(viewer, -1) ImageShow.register(viewer, -1)
im = hopper() for mode in ("1", "I;16", "LA", "RGB", "RGBA"):
im = hopper(mode)
self.assertTrue(ImageShow.show(im)) self.assertTrue(ImageShow.show(im))
self.assertTrue(viewer.methodCalled) self.assertTrue(viewer.methodCalled)

View File

@ -410,7 +410,7 @@ These platforms are built and tested for every change.
| | PyPy, 3.7/MinGW |x86 | | | PyPy, 3.7/MinGW |x86 |
+----------------------------------+-------------------------------+-----------------------+ +----------------------------------+-------------------------------+-----------------------+
\* Mac OS X CI is not run for every commit, but is run for every release. \* macOS CI is not run for every commit, but is run for every release.
Other Platforms Other Platforms
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^

View File

@ -66,7 +66,8 @@ class BmpImageFile(ImageFile.ImageFile):
# -------------------------------------------------- BMP Compression values # -------------------------------------------------- BMP Compression values
COMPRESSIONS = {"RAW": 0, "RLE8": 1, "RLE4": 2, "BITFIELDS": 3, "JPEG": 4, "PNG": 5} COMPRESSIONS = {"RAW": 0, "RLE8": 1, "RLE4": 2, "BITFIELDS": 3, "JPEG": 4, "PNG": 5}
RAW, RLE8, RLE4, BITFIELDS, JPEG, PNG = 0, 1, 2, 3, 4, 5 for k, v in COMPRESSIONS.items():
vars()[k] = v
def _bitmap(self, header=0, offset=0): def _bitmap(self, header=0, offset=0):
""" Read relevant info about the BMP """ """ Read relevant info about the BMP """

View File

@ -251,8 +251,6 @@ class IcnsImageFile(ImageFile.ImageFile):
self.best_size[0] * self.best_size[2], self.best_size[0] * self.best_size[2],
self.best_size[1] * self.best_size[2], self.best_size[1] * self.best_size[2],
) )
# Just use this to see if it's loaded or not yet.
self.tile = ("",)
@property @property
def size(self): def size(self):
@ -286,7 +284,8 @@ class IcnsImageFile(ImageFile.ImageFile):
) )
Image.Image.load(self) Image.Image.load(self)
if not self.tile: if self.im and self.im.size == self.size:
# Already loaded
return return
self.load_prepare() self.load_prepare()
# This is likely NOT the best way to do it, but whatever. # This is likely NOT the best way to do it, but whatever.
@ -298,11 +297,6 @@ class IcnsImageFile(ImageFile.ImageFile):
self.im = im.im self.im = im.im
self.mode = im.mode self.mode = im.mode
self.size = im.size self.size = im.size
if self._exclusive_fp:
self.fp.close()
self.fp = None
self.icns = None
self.tile = ()
self.load_end() self.load_end()

View File

@ -288,6 +288,9 @@ class IcoImageFile(ImageFile.ImageFile):
self._size = value self._size = value
def load(self): def load(self):
if self.im and self.im.size == self.size:
# Already loaded
return
im = self.ico.getimage(self.size) im = self.ico.getimage(self.size)
# if tile is PNG, it won't really be loaded yet # if tile is PNG, it won't really be loaded yet
im.load() im.load()

View File

@ -48,9 +48,17 @@ def getmode(mode):
modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L") modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L")
modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L") modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L")
# mapping modes # mapping modes
modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L") for i16mode in (
modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L") "I;16",
modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L") "I;16S",
"I;16L",
"I;16LS",
"I;16B",
"I;16BS",
"I;16N",
"I;16NS",
):
modes[i16mode] = ModeDescriptor(i16mode, ("I",), "L", "L")
# set global mode cache atomically # set global mode cache atomically
_modes = modes _modes = modes
return _modes[mode] return _modes[mode]

View File

@ -32,7 +32,7 @@ class Iterator(object):
if not hasattr(im, "seek"): if not hasattr(im, "seek"):
raise AttributeError("im must have seek method") raise AttributeError("im must have seek method")
self.im = im self.im = im
self.position = 0 self.position = getattr(self.im, "_min_frame", 0)
def __getitem__(self, ix): def __getitem__(self, ix):
try: try:

View File

@ -63,15 +63,11 @@ class Viewer(object):
def show(self, image, **options): def show(self, image, **options):
# save temporary image to disk # save temporary image to disk
if image.mode[:4] == "I;16": if not (
# @PIL88 @PIL101 image.mode in ("1", "RGBA") or (self.format == "PNG" and image.mode == "LA")
# "I;16" isn't an 'official' mode, but we still want to ):
# provide a simple way to show 16-bit images.
base = "L"
# FIXME: auto-contrast if max() > 255?
else:
base = Image.getmodebase(image.mode) base = Image.getmodebase(image.mode)
if base != image.mode and image.mode != "1" and image.mode != "RGBA": if image.mode != base:
image = image.convert(base) image = image.convert(base)
return self.show_image(image, **options) return self.show_image(image, **options)
@ -128,7 +124,7 @@ elif sys.platform == "darwin":
def get_command(self, file, **options): def get_command(self, file, **options):
# on darwin open returns immediately resulting in the temp # on darwin open returns immediately resulting in the temp
# file removal while app is opening # file removal while app is opening
command = "open -a /Applications/Preview.app" command = "open -a Preview.app"
command = "(%s %s; sleep 20; rm -f %s)&" % ( command = "(%s %s; sleep 20; rm -f %s)&" % (
command, command,
quote(file), quote(file),
@ -143,12 +139,7 @@ elif sys.platform == "darwin":
f.write(file) f.write(file)
with open(path, "r") as f: with open(path, "r") as f:
subprocess.Popen( subprocess.Popen(
[ ["im=$(cat); open -a Preview.app $im; sleep 20; rm -f $im"],
"im=$(cat);"
"open -a /Applications/Preview.app $im;"
"sleep 20;"
"rm -f $im"
],
shell=True, shell=True,
stdin=f, stdin=f,
) )

View File

@ -54,6 +54,7 @@ class PsdImageFile(ImageFile.ImageFile):
format = "PSD" format = "PSD"
format_description = "Adobe Photoshop" format_description = "Adobe Photoshop"
_close_exclusive_fp_after_loading = False
def _open(self): def _open(self):
@ -128,7 +129,7 @@ class PsdImageFile(ImageFile.ImageFile):
self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels) self.tile = _maketile(self.fp, mode, (0, 0) + self.size, channels)
# keep the file open # keep the file open
self._fp = self.fp self.__fp = self.fp
self.frame = 1 self.frame = 1
self._min_frame = 1 self._min_frame = 1
@ -150,7 +151,7 @@ class PsdImageFile(ImageFile.ImageFile):
self.mode = mode self.mode = mode
self.tile = tile self.tile = tile
self.frame = layer self.frame = layer
self.fp = self._fp self.fp = self.__fp
return name, bbox return name, bbox
except IndexError: except IndexError:
raise EOFError("no such layer") raise EOFError("no such layer")
@ -167,6 +168,15 @@ class PsdImageFile(ImageFile.ImageFile):
if self.mode == "P": if self.mode == "P":
Image.Image.load(self) Image.Image.load(self)
def _close__fp(self):
try:
if self.__fp != self.fp:
self.__fp.close()
except AttributeError:
pass
finally:
self.__fp = None
def _layerinfo(file): def _layerinfo(file):
# read layerinfo block # read layerinfo block

View File

@ -54,7 +54,7 @@
typedef struct typedef struct
{ {
int index, x_offset, x_advance, y_offset; int index, x_offset, x_advance, y_offset, y_advance;
unsigned int cluster; unsigned int cluster;
} GlyphInfo; } GlyphInfo;
@ -79,6 +79,9 @@ typedef struct {
static PyTypeObject Font_Type; static PyTypeObject Font_Type;
typedef bool (*t_raqm_version_atleast)(unsigned int major,
unsigned int minor,
unsigned int micro);
typedef raqm_t* (*t_raqm_create)(void); typedef raqm_t* (*t_raqm_create)(void);
typedef int (*t_raqm_set_text)(raqm_t *rq, typedef int (*t_raqm_set_text)(raqm_t *rq,
const uint32_t *text, const uint32_t *text,
@ -107,6 +110,7 @@ typedef void (*t_raqm_destroy) (raqm_t *rq);
typedef struct { typedef struct {
void* raqm; void* raqm;
int version; int version;
t_raqm_version_atleast version_atleast;
t_raqm_create create; t_raqm_create create;
t_raqm_set_text set_text; t_raqm_set_text set_text;
t_raqm_set_text_utf8 set_text_utf8; t_raqm_set_text_utf8 set_text_utf8;
@ -162,6 +166,7 @@ setraqm(void)
} }
#if !defined(_MSC_VER) #if !defined(_MSC_VER)
p_raqm.version_atleast = (t_raqm_version_atleast)dlsym(p_raqm.raqm, "raqm_version_atleast");
p_raqm.create = (t_raqm_create)dlsym(p_raqm.raqm, "raqm_create"); p_raqm.create = (t_raqm_create)dlsym(p_raqm.raqm, "raqm_create");
p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text"); p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text");
p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)dlsym(p_raqm.raqm, "raqm_set_text_utf8"); p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)dlsym(p_raqm.raqm, "raqm_set_text_utf8");
@ -194,6 +199,7 @@ setraqm(void)
return 2; return 2;
} }
#else #else
p_raqm.version_atleast = (t_raqm_version_atleast)GetProcAddress(p_raqm.raqm, "raqm_version_atleast");
p_raqm.create = (t_raqm_create)GetProcAddress(p_raqm.raqm, "raqm_create"); p_raqm.create = (t_raqm_create)GetProcAddress(p_raqm.raqm, "raqm_create");
p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text"); p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text");
p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)GetProcAddress(p_raqm.raqm, "raqm_set_text_utf8"); p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)GetProcAddress(p_raqm.raqm, "raqm_set_text_utf8");
@ -407,9 +413,13 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *
direction = RAQM_DIRECTION_RTL; direction = RAQM_DIRECTION_RTL;
else if (strcmp(dir, "ltr") == 0) else if (strcmp(dir, "ltr") == 0)
direction = RAQM_DIRECTION_LTR; direction = RAQM_DIRECTION_LTR;
else if (strcmp(dir, "ttb") == 0) else if (strcmp(dir, "ttb") == 0) {
direction = RAQM_DIRECTION_TTB; direction = RAQM_DIRECTION_TTB;
else { if (p_raqm.version_atleast == NULL || !(*p_raqm.version_atleast)(0, 7, 0)) {
PyErr_SetString(PyExc_ValueError, "libraqm 0.7 or greater required for 'ttb' direction");
goto failed;
}
} else {
PyErr_SetString(PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"); PyErr_SetString(PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'");
goto failed; goto failed;
} }
@ -502,6 +512,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *
(*glyph_info)[i].x_offset = glyphs_01[i].x_offset; (*glyph_info)[i].x_offset = glyphs_01[i].x_offset;
(*glyph_info)[i].x_advance = glyphs_01[i].x_advance; (*glyph_info)[i].x_advance = glyphs_01[i].x_advance;
(*glyph_info)[i].y_offset = glyphs_01[i].y_offset; (*glyph_info)[i].y_offset = glyphs_01[i].y_offset;
(*glyph_info)[i].y_advance = glyphs_01[i].y_advance;
(*glyph_info)[i].cluster = glyphs_01[i].cluster; (*glyph_info)[i].cluster = glyphs_01[i].cluster;
} }
} else { } else {
@ -510,6 +521,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *
(*glyph_info)[i].x_offset = glyphs[i].x_offset; (*glyph_info)[i].x_offset = glyphs[i].x_offset;
(*glyph_info)[i].x_advance = glyphs[i].x_advance; (*glyph_info)[i].x_advance = glyphs[i].x_advance;
(*glyph_info)[i].y_offset = glyphs[i].y_offset; (*glyph_info)[i].y_offset = glyphs[i].y_offset;
(*glyph_info)[i].y_advance = glyphs[i].y_advance;
(*glyph_info)[i].cluster = glyphs[i].cluster; (*glyph_info)[i].cluster = glyphs[i].cluster;
} }
} }
@ -576,9 +588,11 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje
if (FT_Get_Kerning(self->face, last_index, (*glyph_info)[i].index, if (FT_Get_Kerning(self->face, last_index, (*glyph_info)[i].index,
ft_kerning_default,&delta) == 0) ft_kerning_default,&delta) == 0)
(*glyph_info)[i-1].x_advance += PIXEL(delta.x); (*glyph_info)[i-1].x_advance += PIXEL(delta.x);
(*glyph_info)[i-1].y_advance += PIXEL(delta.y);
} }
(*glyph_info)[i].x_advance = glyph->metrics.horiAdvance; (*glyph_info)[i].x_advance = glyph->metrics.horiAdvance;
(*glyph_info)[i].y_advance = glyph->metrics.vertAdvance;
last_index = (*glyph_info)[i].index; last_index = (*glyph_info)[i].index;
(*glyph_info)[i].cluster = ch; (*glyph_info)[i].cluster = ch;
} }
@ -602,9 +616,10 @@ 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, y_max, y_min; int x_max, x_min, y_max, y_min;
FT_Face face; FT_Face face;
int xoffset, yoffset; int xoffset, yoffset;
int horizontal_dir;
const char *dir = NULL; const char *dir = NULL;
const char *lang = NULL; const char *lang = NULL;
size_t i, count; size_t i, count;
@ -619,15 +634,15 @@ font_getsize(FontObject* self, PyObject* args)
face = NULL; face = NULL;
xoffset = yoffset = 0; xoffset = yoffset = 0;
y_max = y_min = 0; x_max = x_min = y_max = y_min = 0;
count = text_layout(string, self, dir, features, lang, &glyph_info, 0); count = text_layout(string, self, dir, features, lang, &glyph_info, 0);
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
return NULL; return NULL;
} }
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
for (x = i = 0; i < count; i++) { for (i = 0; i < count; i++) {
int index, error; int index, error;
FT_BBox bbox; FT_BBox bbox;
FT_Glyph glyph; FT_Glyph glyph;
@ -640,25 +655,35 @@ font_getsize(FontObject* self, PyObject* args)
if (error) if (error)
return geterror(error); return geterror(error);
if (i == 0 && face->glyph->metrics.horiBearingX < 0) { if (i == 0) {
if (horizontal_dir) {
if (face->glyph->metrics.horiBearingX < 0) {
xoffset = face->glyph->metrics.horiBearingX; xoffset = face->glyph->metrics.horiBearingX;
x -= xoffset; x_max -= xoffset;
}
} else {
if (face->glyph->metrics.vertBearingY < 0) {
yoffset = face->glyph->metrics.vertBearingY;
y_max -= yoffset;
}
}
} }
x += glyph_info[i].x_advance; FT_Get_Glyph(face->glyph, &glyph);
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_SUBPIXELS, &bbox);
if (horizontal_dir) {
x_max += glyph_info[i].x_advance;
if (i == count - 1) if (i == count - 1) {
{ // trim end gap from final glyph
int offset; int offset;
offset = glyph_info[i].x_advance - offset = glyph_info[i].x_advance -
face->glyph->metrics.width - face->glyph->metrics.width -
face->glyph->metrics.horiBearingX; face->glyph->metrics.horiBearingX;
if (offset < 0) if (offset < 0)
x -= offset; x_max -= offset;
} }
FT_Get_Glyph(face->glyph, &glyph);
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_SUBPIXELS, &bbox);
bbox.yMax -= glyph_info[i].y_offset; bbox.yMax -= glyph_info[i].y_offset;
bbox.yMin -= glyph_info[i].y_offset; bbox.yMin -= glyph_info[i].y_offset;
if (bbox.yMax > y_max) if (bbox.yMax > y_max)
@ -666,9 +691,27 @@ font_getsize(FontObject* self, PyObject* args)
if (bbox.yMin < y_min) if (bbox.yMin < y_min)
y_min = bbox.yMin; y_min = bbox.yMin;
/* find max distance of baseline from top */ // find max distance of baseline from top
if (face->glyph->metrics.horiBearingY > yoffset) if (face->glyph->metrics.horiBearingY > yoffset)
yoffset = face->glyph->metrics.horiBearingY; 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;
}
if (bbox.xMax > x_max)
x_max = bbox.xMax;
if (i == 0 || bbox.xMin < x_min)
x_min = bbox.xMin;
}
FT_Done_Glyph(glyph); FT_Done_Glyph(glyph);
} }
@ -679,20 +722,28 @@ font_getsize(FontObject* self, PyObject* args)
} }
if (face) { if (face) {
if (horizontal_dir) {
/* left bearing */ // left bearing
if (xoffset < 0) if (xoffset < 0)
x -= xoffset; x_max -= xoffset;
else else
xoffset = 0; xoffset = 0;
/* difference between the font ascender and the distance of /* difference between the font ascender and the distance of
* the baseline from the top */ * the baseline from the top */
yoffset = PIXEL(self->face->size->metrics.ascender - yoffset); 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), PIXEL(y_max - y_min), PIXEL(x_max - x_min), PIXEL(y_max - y_min),
PIXEL(xoffset), yoffset PIXEL(xoffset), yoffset
); );
} }
@ -703,7 +754,7 @@ font_render(FontObject* self, PyObject* args)
int x; int x;
unsigned int y; unsigned int y;
Imaging im; Imaging im;
int index, error, ascender; int index, error, ascender, horizontal_dir;
int load_flags; int load_flags;
unsigned char *source; unsigned char *source;
FT_GlyphSlot glyph; FT_GlyphSlot glyph;
@ -714,6 +765,8 @@ font_render(FontObject* self, PyObject* args)
int mask = 0; int mask = 0;
int temp; int temp;
int xx, x0, x1; int xx, x0, x1;
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; size_t i, count;
@ -747,27 +800,34 @@ font_render(FontObject* self, PyObject* args)
return geterror(error); return geterror(error);
glyph = self->face->glyph; glyph = self->face->glyph;
temp = (glyph->bitmap.rows - glyph->bitmap_top); temp = glyph->bitmap.rows - glyph->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;
} }
for (x = i = 0; i < count; i++) { x = y = 0;
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
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;
if (horizontal_dir) {
if (i == 0 && self->face->glyph->metrics.horiBearingX < 0) { if (i == 0 && self->face->glyph->metrics.horiBearingX < 0) {
x = -self->face->glyph->metrics.horiBearingX; x = -self->face->glyph->metrics.horiBearingX;
} }
glyph = self->face->glyph;
source = (unsigned char*) glyph->bitmap.buffer;
xx = PIXEL(x) + glyph->bitmap_left; xx = PIXEL(x) + glyph->bitmap_left;
xx += PIXEL(glyph_info[i].x_offset); xx += PIXEL(glyph_info[i].x_offset);
} else {
if (self->face->glyph->metrics.vertBearingX < 0) {
x = -self->face->glyph->metrics.vertBearingX;
}
xx = im->xsize / 2 - glyph->bitmap.width / 2;
}
x0 = 0; x0 = 0;
x1 = glyph->bitmap.width; x1 = glyph->bitmap.width;
if (xx < 0) if (xx < 0)
@ -775,45 +835,42 @@ font_render(FontObject* self, PyObject* args)
if (xx + x1 > im->xsize) if (xx + x1 > im->xsize)
x1 = im->xsize - xx; x1 = im->xsize - xx;
if (mask) { source = (unsigned char*) glyph->bitmap.buffer;
/* use monochrome mask (on palette images, etc) */ for (bitmap_y = 0; bitmap_y < glyph->bitmap.rows; bitmap_y++) {
for (y = 0; y < glyph->bitmap.rows; y++) { if (horizontal_dir) {
int yy = y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender); yy = bitmap_y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender);
yy -= PIXEL(glyph_info[i].y_offset); yy -= PIXEL(glyph_info[i].y_offset);
} else {
yy = bitmap_y + PIXEL(y + glyph->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;
int i, j, m = 128; if (mask) {
for (i = j = 0; j < x1; j++) { // use monochrome mask (on palette images, etc)
if (j >= x0 && (source[i] & m)) int j, k, m = 128;
for (j = k = 0; j < x1; j++) {
if (j >= x0 && (source[k] & m))
target[j] = 255; target[j] = 255;
if (!(m >>= 1)) { if (!(m >>= 1)) {
m = 128; m = 128;
i++; k++;
} }
} }
}
source += glyph->bitmap.pitch;
}
} else { } else {
/* use antialiased rendering */ // use antialiased rendering
for (y = 0; y < glyph->bitmap.rows; y++) { int k;
int yy = y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender); for (k = x0; k < x1; k++) {
yy -= PIXEL(glyph_info[i].y_offset); if (target[k] < source[k])
if (yy >= 0 && yy < im->ysize) { target[k] = source[k];
/* blend this glyph into the buffer */ }
int i;
unsigned char *target = im->image8[yy] + xx;
for (i = x0; i < x1; i++) {
if (target[i] < source[i])
target[i] = source[i];
} }
} }
source += glyph->bitmap.pitch; source += glyph->bitmap.pitch;
} }
}
x += glyph_info[i].x_advance; x += glyph_info[i].x_advance;
y -= glyph_info[i].y_advance;
} }
PyMem_Del(glyph_info); PyMem_Del(glyph_info);

View File

@ -405,7 +405,7 @@ ImagingAllocateArray(Imaging im, int dirty, int block_size)
// printf("NEW size: %dx%d, ls: %d, lpb: %d, blocks: %d\n", // printf("NEW size: %dx%d, ls: %d, lpb: %d, blocks: %d\n",
// im->xsize, im->ysize, aligned_linesize, lines_per_block, blocks_count); // im->xsize, im->ysize, aligned_linesize, lines_per_block, blocks_count);
/* One extra ponter is always NULL */ /* One extra pointer is always NULL */
im->blocks = calloc(sizeof(*im->blocks), blocks_count + 1); im->blocks = calloc(sizeof(*im->blocks), blocks_count + 1);
if ( ! im->blocks) { if ( ! im->blocks) {
return (Imaging) ImagingError_MemoryError(); return (Imaging) ImagingError_MemoryError();