Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update of Pr #2284, Complex Text Support #2576

Merged
merged 41 commits into from
Jul 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0b178ed
Add complex text support.
ShamsaHamed Jan 13, 2016
3e5007c
Fix failure in test_imagefont.py
ShamsaHamed Jan 25, 2016
349ac49
Fix build with python 3.2
ShamsaHamed Jan 25, 2016
326e23d
fallback code
ShamsaHamed Jan 27, 2016
fe871bb
Document complex text layout features.
ShamsaHamed Feb 3, 2016
fcd20da
Testing complex text layout.
ShamsaHamed Feb 3, 2016
02d0bcb
Solve merge conflict
ShamsaHamed Feb 4, 2016
a778505
Fix gitsize function.
ShamsaHamed Feb 16, 2016
312c87e
fix testing
ShamsaHamed Feb 18, 2016
1992979
fix windows build
Fahad-Alsaidi Dec 13, 2016
de8ba93
fix windows build
Fahad-Alsaidi Dec 13, 2016
538cec3
fix setup.py
Fahad-Alsaidi Dec 14, 2016
6dc4c7e
improve docs for CTL
Fahad-Alsaidi Dec 14, 2016
629f832
break up text_layout into two implementations
Fahad-Alsaidi Dec 14, 2016
697db86
update depends/* to install raqm
Fahad-Alsaidi Dec 14, 2016
4ed6962
only catch ImportError same as test_imagefont.py
Fahad-Alsaidi Dec 14, 2016
74e4ccc
declare text_layout_raqm only when we HAVE_RAQM
Fahad-Alsaidi Dec 14, 2016
fd7a675
adding KeyError exception
Fahad-Alsaidi Dec 14, 2016
e07a254
update
Fahad-Alsaidi Dec 15, 2016
eb75a94
fix build
Fahad-Alsaidi Dec 15, 2016
e8584f3
fix ubuntu build
Fahad-Alsaidi Dec 17, 2016
2528743
fedora has raqm package
Fahad-Alsaidi Dec 19, 2016
4283109
Fix RAQM feature detection
wiredfool Jun 13, 2017
f371ca0
hoist tests out of try/except, use feature detection
wiredfool Jun 13, 2017
b8c04de
added layout engine switch
wiredfool Jun 13, 2017
3932733
test both layout engines, if available
wiredfool Jun 13, 2017
9bdda3d
update install script
wiredfool Jun 13, 2017
53e247c
test tweak for ubuntu 16.04
wiredfool Jun 13, 2017
183e0ec
Added Noto font license [ci skip]
wiredfool Jun 13, 2017
8d9f602
remove non-tempfile usage from tests
wiredfool Jun 21, 2017
db2359a
y offset on trusty/x86 is 1.63
wiredfool Jun 21, 2017
f737f00
Freebsd's bash is elsewhere, wget necessary, setuptools not found
wiredfool Jun 22, 2017
d417ba5
remove ppa
wiredfool Jun 22, 2017
57632d5
Use the cmake version of raqm
wiredfool Jun 28, 2017
109e10d
cmake dependency
wiredfool Jun 28, 2017
14293ea
Tests pass on osx
wiredfool Jun 29, 2017
90a9913
Font.getsize needs direction and features
wiredfool Jun 29, 2017
9f7aae3
Doc changes/additions
wiredfool Jun 29, 2017
2c6cf03
test features in get size
wiredfool Jun 30, 2017
d2e8da1
release notes
wiredfool Jun 30, 2017
1eedfe5
Install updates for raqm
wiredfool Jul 1, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 4 additions & 2 deletions .travis/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
set -e

sudo apt-get update
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-tk \
python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-tk\
python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick\
libharfbuzz-dev libfribidi-dev

pip install cffi
pip install nose
pip install check-manifest
Expand Down
24 changes: 14 additions & 10 deletions PIL/ImageDraw.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,25 +207,24 @@ def text(self, xy, text, fill=None, font=None, anchor=None,
if self._multiline_check(text):
return self.multiline_text(xy, text, fill, font, anchor,
*args, **kwargs)

ink, fill = self._getink(fill)
if font is None:
font = self.getfont()
if ink is None:
ink = fill
if ink is not None:
try:
mask, offset = font.getmask2(text, self.fontmode)
mask, offset = font.getmask2(text, self.fontmode, *args, **kwargs)
xy = xy[0] + offset[0], xy[1] + offset[1]
except AttributeError:
try:
mask = font.getmask(text, self.fontmode)
mask = font.getmask(text, self.fontmode, *args, **kwargs)
except TypeError:
mask = font.getmask(text)
self.draw.draw_bitmap(xy, mask, ink)

def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
spacing=4, align="left"):
spacing=4, align="left", direction=None, features=None):
widths = []
max_width = 0
lines = self._multiline_split(text)
Expand All @@ -244,25 +243,30 @@ def multiline_text(self, xy, text, fill=None, font=None, anchor=None,
left += (max_width - widths[idx])
else:
assert False, 'align must be "left", "center" or "right"'
self.text((left, top), line, fill, font, anchor)
self.text((left, top), line, fill, font, anchor,
direction=direction, features=features)
top += line_spacing
left = xy[0]

def textsize(self, text, font=None, *args, **kwargs):
def textsize(self, text, font=None, spacing=4, direction=None,
features=None):
"""Get the size of a given string, in pixels."""
if self._multiline_check(text):
return self.multiline_textsize(text, font, *args, **kwargs)
return self.multiline_textsize(text, font, spacing,
direction, features)

if font is None:
font = self.getfont()
return font.getsize(text)
return font.getsize(text, direction, features)

def multiline_textsize(self, text, font=None, spacing=4):
def multiline_textsize(self, text, font=None, spacing=4, direction=None,
features=None):
max_width = 0
lines = self._multiline_split(text)
line_spacing = self.textsize('A', font=font)[1] + spacing
for line in lines:
line_width, line_height = self.textsize(line, font)
line_width, line_height = self.textsize(line, font, spacing,
direction, features)
max_width = max(max_width, line_width)
return max_width, len(lines)*line_spacing

Expand Down
69 changes: 45 additions & 24 deletions PIL/ImageFont.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ def __getattr__(self, id):
except ImportError:
core = _imagingft_not_installed()

LAYOUT_BASIC = 0
LAYOUT_RAQM = 1

# FIXME: add support for pilfont2 format (see FontFile.py)

# --------------------------------------------------------------------
Expand Down Expand Up @@ -103,9 +106,12 @@ def _load_pilfont_data(self, file, image):

self.font = Image.core.font(image.im, data)

# delegate critical operations to internal type
self.getsize = self.font.getsize
self.getmask = self.font.getmask
def getsize(self, text, *args, **kwargs):
return self.font.getsize(text)

def getmask(self, text, mode="", *args, **kwargs):
return self.font.getmask(text, mode)



##
Expand All @@ -115,44 +121,55 @@ def _load_pilfont_data(self, file, image):
class FreeTypeFont(object):
"FreeType font wrapper (requires _imagingft service)"

def __init__(self, font=None, size=10, index=0, encoding=""):
def __init__(self, font=None, size=10, index=0, encoding="",
layout_engine=None):
# FIXME: use service provider instead

self.path = font
self.size = size
self.index = index
self.encoding = encoding

if layout_engine not in (LAYOUT_BASIC, LAYOUT_RAQM):
layout_engine = LAYOUT_BASIC
if core.HAVE_RAQM:
layout_engine = LAYOUT_RAQM
if layout_engine == LAYOUT_RAQM and not core.HAVE_RAQM:
layout_engine = LAYOUT_BASIC

self.layout_engine = layout_engine

if isPath(font):
self.font = core.getfont(font, size, index, encoding)
self.font = core.getfont(font, size, index, encoding, layout_engine=layout_engine)
else:
self.font_bytes = font.read()
self.font = core.getfont(
"", size, index, encoding, self.font_bytes)
"", size, index, encoding, self.font_bytes, layout_engine)

def getname(self):
return self.font.family, self.font.style

def getmetrics(self):
return self.font.ascent, self.font.descent

def getsize(self, text):
size, offset = self.font.getsize(text)
def getsize(self, text, direction=None, features=None):
size, offset = self.font.getsize(text, direction, features)
return (size[0] + offset[0], size[1] + offset[1])

def getoffset(self, text):
return self.font.getsize(text)[1]

def getmask(self, text, mode=""):
return self.getmask2(text, mode)[0]
def getmask(self, text, mode="", direction=None, features=None):
return self.getmask2(text, mode, direction=direction, features=features)[0]

def getmask2(self, text, mode="", fill=Image.core.fill):
size, offset = self.font.getsize(text)
def getmask2(self, text, mode="", fill=Image.core.fill, direction=None, features=None):
size, offset = self.font.getsize(text, direction, features)
im = fill("L", size, 0)
self.font.render(text, im.id, mode == "1")
self.font.render(text, im.id, mode == "1", direction, features)
return im, offset

def font_variant(self, font=None, size=None, index=None, encoding=None):
def font_variant(self, font=None, size=None, index=None, encoding=None,
layout_engine=None):
"""
Create a copy of this FreeTypeFont object,
using any specified arguments to override the settings.
Expand All @@ -165,8 +182,9 @@ def font_variant(self, font=None, size=None, index=None, encoding=None):
return FreeTypeFont(font=self.path if font is None else font,
size=self.size if size is None else size,
index=self.index if index is None else index,
encoding=self.encoding if encoding is None else
encoding)
encoding=self.encoding if encoding is None else encoding,
layout_engine=self.layout_engine if layout_engine is None else layout_engine
)


class TransposedFont(object):
Expand All @@ -185,14 +203,14 @@ def __init__(self, font, orientation=None):
self.font = font
self.orientation = orientation # any 'transpose' argument, or None

def getsize(self, text):
def getsize(self, text, *args, **kwargs):
w, h = self.font.getsize(text)
if self.orientation in (Image.ROTATE_90, Image.ROTATE_270):
return h, w
return w, h

def getmask(self, text, mode=""):
im = self.font.getmask(text, mode)
def getmask(self, text, mode="", *args, **kwargs):
im = self.font.getmask(text, mode, *args, **kwargs)
if self.orientation is not None:
return im.transpose(self.orientation)
return im
Expand All @@ -212,7 +230,8 @@ def load(filename):
return f


def truetype(font=None, size=10, index=0, encoding=""):
def truetype(font=None, size=10, index=0, encoding="",
layout_engine=None):
"""
Load a TrueType or OpenType font file, and create a font object.
This function loads a font object from the given file, and creates
Expand All @@ -230,12 +249,14 @@ def truetype(font=None, size=10, index=0, encoding=""):
Symbol), "ADOB" (Adobe Standard), "ADBE" (Adobe Expert),
and "armn" (Apple Roman). See the FreeType documentation
for more information.
:param layout_engine: Which layout engine to use, if available:
`ImageFont.LAYOUT_BASIC` or `ImageFont.LAYOUT_RAQM`.
:return: A font object.
:exception IOError: If the file could not be read.
"""

try:
return FreeTypeFont(font, size, index, encoding)
return FreeTypeFont(font, size, index, encoding, layout_engine)
except IOError:
ttf_filename = os.path.basename(font)

Expand Down Expand Up @@ -266,16 +287,16 @@ def truetype(font=None, size=10, index=0, encoding=""):
for walkfilename in walkfilenames:
if ext and walkfilename == ttf_filename:
fontpath = os.path.join(walkroot, walkfilename)
return FreeTypeFont(fontpath, size, index, encoding)
return FreeTypeFont(fontpath, size, index, encoding, layout_engine)
elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
fontpath = os.path.join(walkroot, walkfilename)
if os.path.splitext(fontpath)[1] == '.ttf':
return FreeTypeFont(fontpath, size, index, encoding)
return FreeTypeFont(fontpath, size, index, encoding, layout_engine)
if not ext and first_font_with_a_different_extension is None:
first_font_with_a_different_extension = fontpath
if first_font_with_a_different_extension:
return FreeTypeFont(first_font_with_a_different_extension, size,
index, encoding)
index, encoding, layout_engine)
raise


Expand Down
1 change: 1 addition & 0 deletions PIL/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def get_supported_codecs():
features = {
"webp_mux": ("PIL._webp", 'HAVE_WEBPMUX'),
"transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"),
"raqm": ("PIL._imagingft", "HAVE_RAQM")
}

def check_feature(feature):
Expand Down
6 changes: 6 additions & 0 deletions Tests/fonts/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

NotoNastaliqUrdu-Regular.ttf:

(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.
Binary file added Tests/fonts/NotoNastaliqUrdu-Regular.ttf
Binary file not shown.
Binary file added Tests/images/test_Nastalifont_text.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_arabictext_features.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_complex_unicode_text.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_direction_ltr.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_direction_rtl.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_kerning_features.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_ligature_features.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_text.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/test_y_offset.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.