mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-11-13 05:06:49 +03:00
Merge remote-tracking branch 'upstream/master' into run-black
This commit is contained in:
commit
1b99362f3d
|
@ -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.
|
||||||
|
|
||||||
|
|
BIN
Tests/fonts/NotoSansJP-Regular.otf
Normal file
BIN
Tests/fonts/NotoSansJP-Regular.otf
Normal file
Binary file not shown.
BIN
Tests/images/hopper_draw.ico
Normal file
BIN
Tests/images/hopper_draw.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 846 B |
BIN
Tests/images/test_direction_ttb.png
Normal file
BIN
Tests/images/test_direction_ttb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)):
|
||||||
|
|
|
@ -17,19 +17,20 @@ 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"):
|
||||||
self.assertTrue(ImageShow.show(im))
|
im = hopper(mode)
|
||||||
self.assertTrue(viewer.methodCalled)
|
self.assertTrue(ImageShow.show(im))
|
||||||
|
self.assertTrue(viewer.methodCalled)
|
||||||
|
|
||||||
# Restore original state
|
# Restore original state
|
||||||
ImageShow._viewers.pop(0)
|
ImageShow._viewers.pop(0)
|
||||||
|
|
|
@ -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
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
|
@ -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 """
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -63,16 +63,12 @@ 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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
217
src/_imagingft.c
217
src/_imagingft.c
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -473,7 +483,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p_raqm.version == 1){
|
if (p_raqm.version == 1) {
|
||||||
glyphs_01 = (*p_raqm.get_glyphs_01)(rq, &count);
|
glyphs_01 = (*p_raqm.get_glyphs_01)(rq, &count);
|
||||||
if (glyphs_01 == NULL) {
|
if (glyphs_01 == NULL) {
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed.");
|
PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed.");
|
||||||
|
@ -496,12 +506,13 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p_raqm.version == 1){
|
if (p_raqm.version == 1) {
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
(*glyph_info)[i].index = glyphs_01[i].index;
|
(*glyph_info)[i].index = glyphs_01[i].index;
|
||||||
(*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,35 +655,63 @@ 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) {
|
||||||
xoffset = face->glyph->metrics.horiBearingX;
|
if (horizontal_dir) {
|
||||||
x -= xoffset;
|
if (face->glyph->metrics.horiBearingX < 0) {
|
||||||
}
|
xoffset = face->glyph->metrics.horiBearingX;
|
||||||
|
x_max -= xoffset;
|
||||||
x += glyph_info[i].x_advance;
|
}
|
||||||
|
} else {
|
||||||
if (i == count - 1)
|
if (face->glyph->metrics.vertBearingY < 0) {
|
||||||
{
|
yoffset = face->glyph->metrics.vertBearingY;
|
||||||
int offset;
|
y_max -= yoffset;
|
||||||
offset = glyph_info[i].x_advance -
|
}
|
||||||
face->glyph->metrics.width -
|
}
|
||||||
face->glyph->metrics.horiBearingX;
|
|
||||||
if (offset < 0)
|
|
||||||
x -= offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FT_Get_Glyph(face->glyph, &glyph);
|
FT_Get_Glyph(face->glyph, &glyph);
|
||||||
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_SUBPIXELS, &bbox);
|
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_SUBPIXELS, &bbox);
|
||||||
bbox.yMax -= glyph_info[i].y_offset;
|
if (horizontal_dir) {
|
||||||
bbox.yMin -= glyph_info[i].y_offset;
|
x_max += glyph_info[i].x_advance;
|
||||||
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 (i == count - 1) {
|
||||||
if (face->glyph->metrics.horiBearingY > yoffset)
|
// trim end gap from final glyph
|
||||||
yoffset = face->glyph->metrics.horiBearingY;
|
int offset;
|
||||||
|
offset = glyph_info[i].x_advance -
|
||||||
|
face->glyph->metrics.width -
|
||||||
|
face->glyph->metrics.horiBearingX;
|
||||||
|
if (offset < 0)
|
||||||
|
x_max -= offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
if (xoffset < 0)
|
||||||
|
x_max -= xoffset;
|
||||||
|
else
|
||||||
|
xoffset = 0;
|
||||||
|
|
||||||
/* left bearing */
|
/* difference between the font ascender and the distance of
|
||||||
if (xoffset < 0)
|
* the baseline from the top */
|
||||||
x -= xoffset;
|
yoffset = PIXEL(self->face->size->metrics.ascender - yoffset);
|
||||||
else
|
} else {
|
||||||
xoffset = 0;
|
// top bearing
|
||||||
/* difference between the font ascender and the distance of
|
if (yoffset < 0)
|
||||||
* the baseline from the top */
|
y_max -= yoffset;
|
||||||
yoffset = PIXEL(self->face->size->metrics.ascender - 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);
|
||||||
|
|
||||||
if (i == 0 && self->face->glyph->metrics.horiBearingX < 0) {
|
|
||||||
x = -self->face->glyph->metrics.horiBearingX;
|
|
||||||
}
|
|
||||||
|
|
||||||
glyph = self->face->glyph;
|
glyph = self->face->glyph;
|
||||||
|
if (horizontal_dir) {
|
||||||
|
if (i == 0 && self->face->glyph->metrics.horiBearingX < 0) {
|
||||||
|
x = -self->face->glyph->metrics.horiBearingX;
|
||||||
|
}
|
||||||
|
xx = PIXEL(x) + glyph->bitmap_left;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
source = (unsigned char*) glyph->bitmap.buffer;
|
|
||||||
xx = PIXEL(x) + glyph->bitmap_left;
|
|
||||||
xx += PIXEL(glyph_info[i].x_offset);
|
|
||||||
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);
|
||||||
if (yy >= 0 && yy < im->ysize) {
|
} else {
|
||||||
/* blend this glyph into the buffer */
|
yy = bitmap_y + PIXEL(y + glyph->metrics.vertBearingY) + ascender;
|
||||||
unsigned char *target = im->image8[yy] + xx;
|
yy += PIXEL(glyph_info[i].y_offset);
|
||||||
int i, j, m = 128;
|
}
|
||||||
for (i = j = 0; j < x1; j++) {
|
if (yy >= 0 && yy < im->ysize) {
|
||||||
if (j >= x0 && (source[i] & m))
|
// blend this glyph into the buffer
|
||||||
|
unsigned char *target = im->image8[yy] + xx;
|
||||||
|
if (mask) {
|
||||||
|
// use monochrome mask (on palette images, etc)
|
||||||
|
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++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
source += glyph->bitmap.pitch;
|
// use antialiased rendering
|
||||||
}
|
int k;
|
||||||
} else {
|
for (k = x0; k < x1; k++) {
|
||||||
/* use antialiased rendering */
|
if (target[k] < source[k])
|
||||||
for (y = 0; y < glyph->bitmap.rows; y++) {
|
target[k] = source[k];
|
||||||
int yy = y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender);
|
|
||||||
yy -= PIXEL(glyph_info[i].y_offset);
|
|
||||||
if (yy >= 0 && yy < im->ysize) {
|
|
||||||
/* 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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user