This commit is contained in:
aeroaks 2014-06-07 12:55:46 +00:00
commit 9d3425e620
2 changed files with 53 additions and 27 deletions

View File

@ -29,13 +29,15 @@ from __future__ import print_function
from PIL import Image from PIL import Image
from PIL._util import isDirectory, isPath from PIL._util import isDirectory, isPath
import os, sys import os
import sys
try: try:
import warnings import warnings
except ImportError: except ImportError:
warnings = None warnings = None
class _imagingft_not_installed: class _imagingft_not_installed:
# module placeholder # module placeholder
def __getattr__(self, id): def __getattr__(self, id):
@ -91,7 +93,7 @@ class ImageFont:
if file.readline() != b"PILfont\n": if file.readline() != b"PILfont\n":
raise SyntaxError("Not a PILfont file") raise SyntaxError("Not a PILfont file")
d = file.readline().split(b";") d = file.readline().split(b";")
self.info = [] # FIXME: should be a dictionary self.info = [] # FIXME: should be a dictionary
while True: while True:
s = file.readline() s = file.readline()
if not s or s == b"DATA\n": if not s or s == b"DATA\n":
@ -113,6 +115,7 @@ class ImageFont:
self.getsize = self.font.getsize self.getsize = self.font.getsize
self.getmask = self.font.getmask self.getmask = self.font.getmask
## ##
# Wrapper for FreeType fonts. Application code should use the # Wrapper for FreeType fonts. Application code should use the
# <b>truetype</b> factory function to create font objects. # <b>truetype</b> factory function to create font objects.
@ -131,7 +134,8 @@ class FreeTypeFont:
self.font = core.getfont(font, size, index, encoding) self.font = core.getfont(font, size, index, encoding)
else: else:
self.font_bytes = font.read() self.font_bytes = font.read()
self.font = core.getfont("", size, index, encoding, self.font_bytes) self.font = core.getfont(
"", size, index, encoding, self.font_bytes)
def getname(self): def getname(self):
return self.font.family, self.font.style return self.font.family, self.font.style
@ -151,9 +155,10 @@ class FreeTypeFont:
def getmask2(self, text, mode="", fill=Image.core.fill): def getmask2(self, text, mode="", fill=Image.core.fill):
size, offset = self.font.getsize(text) size, offset = self.font.getsize(text)
im = fill("L", size, 0) im = fill("L", size, 0)
self.font.render(text, im.id, mode=="1") self.font.render(text, im.id, mode == "1")
return im, offset return im, offset
## ##
# Wrapper that creates a transposed font from any existing font # Wrapper that creates a transposed font from any existing font
# object. # object.
@ -168,7 +173,7 @@ class TransposedFont:
def __init__(self, font, orientation=None): def __init__(self, font, orientation=None):
self.font = font self.font = font
self.orientation = orientation # any 'transpose' argument, or None self.orientation = orientation # any 'transpose' argument, or None
def getsize(self, text): def getsize(self, text):
w, h = self.font.getsize(text) w, h = self.font.getsize(text)
@ -207,7 +212,8 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
:param filename: A truetype font file. Under Windows, if the file :param filename: A truetype font file. Under Windows, if the file
is not found in this filename, the loader also looks in is not found in this filename, the loader also looks in
Windows :file:`fonts/` directory. Windows :file:`fonts/` and Linux :file:`XDG_DATA_DIRS`
directories.
:param size: The requested size, in points. :param size: The requested size, in points.
:param index: Which font face to load (default is first available face). :param index: Which font face to load (default is first available face).
:param encoding: Which font encoding to use (default is Unicode). Common :param encoding: Which font encoding to use (default is Unicode). Common
@ -227,14 +233,18 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None):
try: try:
return FreeTypeFont(font, size, index, encoding) return FreeTypeFont(font, size, index, encoding)
except IOError: except IOError:
dir = None
if sys.platform == "win32": if sys.platform == "win32":
# check the windows font repository # check the windows font repository
# NOTE: must use uppercase WINDIR, to work around bugs in # NOTE: must use uppercase WINDIR, to work around bugs in
# 1.5.2's os.environ.get() # 1.5.2's os.environ.get()
windir = os.environ.get("WINDIR") dir = os.environ.get("WINDIR")
if windir: elif sys.platform == "linux":
filename = os.path.join(windir, "fonts", font) # check the Linux font repository
return FreeTypeFont(filename, size, index, encoding) dir = os.environ.get("XDG_DATA_DIRS")
if dir:
filename = os.path.join(dir, "fonts", font)
return FreeTypeFont(filename, size, index, encoding)
raise raise
@ -272,8 +282,8 @@ def load_default():
import base64 import base64
f = ImageFont() f = ImageFont()
f._load_pilfont_data( f._load_pilfont_data(
# courB08 # courB08
BytesIO(base64.decodestring(b''' BytesIO(base64.decodestring(b'''
UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
@ -395,7 +405,7 @@ w7IkEbzhVQAAAABJRU5ErkJggg==
if __name__ == "__main__": if __name__ == "__main__":
# create font data chunk for embedding # create font data chunk for embedding
import base64, os, sys import base64
font = "../Images/courB08" font = "../Images/courB08"
print(" f._load_pilfont_data(") print(" f._load_pilfont_data(")
print(" # %s" % os.path.basename(font)) print(" # %s" % os.path.basename(font))

View File

@ -6,44 +6,54 @@ import os
try: try:
from PIL import ImageFont from PIL import ImageFont
ImageFont.core.getfont # check if freetype is available ImageFont.core.getfont # check if freetype is available
except ImportError: except ImportError:
skip() skip()
from PIL import ImageDraw from PIL import ImageDraw
font_path = "Tests/fonts/FreeMono.ttf" font_path = "Tests/fonts/FreeMono.ttf"
font_size=20 font_size = 20
def test_sanity(): def test_sanity():
assert_match(ImageFont.core.freetype2_version, "\d+\.\d+\.\d+$") assert_match(ImageFont.core.freetype2_version, "\d+\.\d+\.\d+$")
def test_font_with_name(): def test_font_with_name():
assert_no_exception(lambda: ImageFont.truetype(font_path, font_size)) assert_no_exception(lambda: ImageFont.truetype(font_path, font_size))
assert_no_exception(lambda: _render(font_path)) assert_no_exception(lambda: _render(font_path))
_clean() _clean()
def _font_as_bytes(): def _font_as_bytes():
with open(font_path, 'rb') as f: with open(font_path, 'rb') as f:
font_bytes = BytesIO(f.read()) font_bytes = BytesIO(f.read())
return font_bytes return font_bytes
def test_font_with_filelike(): def test_font_with_filelike():
assert_no_exception(lambda: ImageFont.truetype(_font_as_bytes(), font_size)) assert_no_exception(
lambda: ImageFont.truetype(_font_as_bytes(), font_size))
assert_no_exception(lambda: _render(_font_as_bytes())) assert_no_exception(lambda: _render(_font_as_bytes()))
# Usage note: making two fonts from the same buffer fails. # Usage note: making two fonts from the same buffer fails.
#shared_bytes = _font_as_bytes() # shared_bytes = _font_as_bytes()
#assert_no_exception(lambda: _render(shared_bytes)) # assert_no_exception(lambda: _render(shared_bytes))
#assert_exception(Exception, lambda: _render(shared_bytes)) # assert_exception(Exception, lambda: _render(shared_bytes))
_clean() _clean()
def test_font_with_open_file(): def test_font_with_open_file():
with open(font_path, 'rb') as f: with open(font_path, 'rb') as f:
assert_no_exception(lambda: _render(f)) assert_no_exception(lambda: _render(f))
_clean() _clean()
def test_font_old_parameters(): def test_font_old_parameters():
assert_warning(DeprecationWarning, lambda: ImageFont.truetype(filename=font_path, size=font_size)) assert_warning(
DeprecationWarning,
lambda: ImageFont.truetype(filename=font_path, size=font_size))
def _render(font): def _render(font):
txt = "Hello World!" txt = "Hello World!"
@ -56,9 +66,11 @@ def _render(font):
img.save('font.png') img.save('font.png')
return img return img
def _clean(): def _clean():
os.unlink('font.png') os.unlink('font.png')
def test_render_equal(): def test_render_equal():
img_path = _render(font_path) img_path = _render(font_path)
with open(font_path, 'rb') as f: with open(font_path, 'rb') as f:
@ -70,7 +82,7 @@ def test_render_equal():
def test_render_multiline(): def test_render_multiline():
im = Image.new(mode='RGB', size=(300,100)) im = Image.new(mode='RGB', size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
ttf = ImageFont.truetype(font_path, font_size) ttf = ImageFont.truetype(font_path, font_size)
line_spacing = draw.textsize('A', font=ttf)[1] + 8 line_spacing = draw.textsize('A', font=ttf)[1] + 8
@ -80,14 +92,13 @@ def test_render_multiline():
draw.text((0, y), line, font=ttf) draw.text((0, y), line, font=ttf)
y += line_spacing y += line_spacing
target = 'Tests/images/multiline_text.png' target = 'Tests/images/multiline_text.png'
target_img = Image.open(target) target_img = Image.open(target)
# some versions of freetype have different horizontal spacing. # some versions of freetype have different horizontal spacing.
# setting a tight epsilon, I'm showing the original test failure # setting a tight epsilon, I'm showing the original test failure
# at epsilon = ~38. # at epsilon = ~38.
assert_image_similar(im, target_img,.5) assert_image_similar(im, target_img, .5)
def test_rotated_transposed_font(): def test_rotated_transposed_font():
@ -133,3 +144,8 @@ def test_unrotated_transposed_font():
assert_equal(box_size_a, box_size_b) assert_equal(box_size_a, box_size_b)
def test_font_not_found():
assert_exception(IOError, lambda: ImageFont.truetype("/fake/font.ttf"))
# End of file