You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The first section of the code below will patch old versions of Pillow (3.0.0 at least) with the newer ImageDraw.text .textsize .multiline_text and .multiline_textsize from the repository. The second section of the code below patches in a new trio of APIs: ImageFont.getBB to get a bounding box for a single line of text, ImageDraw.textInfo to return a bounding box and analysis results for single or multiline text, and ImageDraw.textAtPos to actually draw single or multiline text at a specific position, or at various positions relative to the image on which you are drawing.
I have no experience with anything except Issues on GitHub. I barely know what a Pull request is. I'd be happy to go through any specific incantations that are required if someone tells me how. Meantime, here's the code, which I store in a file named PillowPatch.py (for now), and import after Pillow, so that I can use this functionality.
Markdown compatible version of the code in the next comment.
Markdown interprets Python code in interesting manners.
fromPILimportImage, ImageDraw, ImageFontdeftext(self, xy, text, fill=None, font=None, anchor=None):
ifself._multiline_check(text):
returnself.multiline_text(xy, text, fill, font, anchor)
ink, fill=self._getink(fill)
iffontisNone:
font=self.getfont()
ifinkisNone:
ink=fillifinkisnotNone:
try:
mask, offset=font.getmask2(text, self.fontmode)
xy=xy[0] +offset[0], xy[1] +offset[1]
exceptAttributeError:
try:
mask=font.getmask(text, self.fontmode)
exceptTypeError:
mask=font.getmask(text)
self.draw.draw_bitmap(xy, mask, ink)
defmultiline_text(self, xy, text, fill=None, font=None, anchor=None,
spacing=4, align="left"):
widths= []
max_width=0lines=self._multiline_split(text)
line_spacing=self.textsize('A', font=font)[1] +spacingforlineinlines:
line_width, line_height=self.textsize(line, font)
widths.append(line_width)
max_width=max(max_width, line_width)
left, top=xyforidx, lineinenumerate(lines):
ifalign=="left":
pass# left = xelifalign=="center":
left+= (max_width-widths[idx]) /2.0elifalign=="right":
left+= (max_width-widths[idx])
else:
assertFalse, 'align must be "left", "center" or "right"'self.text((left, top), line, fill, font, anchor)
top+=line_spacingleft=xy[0]
### Get the size of a given string, in pixels.deftextsize(self, text, font=None):
ifself._multiline_check(text):
returnself.multiline_textsize(text, font)
iffontisNone:
font=self.getfont()
returnfont.getsize(text)
defmultiline_textsize(self, text, font=None, spacing=4):
max_width=0lines=self._multiline_split(text)
line_spacing=self.textsize('A', font=font)[1] +spacingforlineinlines:
line_width, line_height=self.textsize(line, font)
max_width=max(max_width, line_width)
returnmax_width, len(lines)*line_spacingImageDraw.ImageDraw.text=textImageDraw.ImageDraw.textsize=textsizeImageDraw.ImageDraw.multiline_text=multiline_textImageDraw.ImageDraw.multiline_textsize=multiline_textsizedefgetBB( self, text ):
size, offset=self.font.getsize( text )
ascent, descent=self.getmetrics()
yMax=ascent-offset[ 1 ] # distance from baseline to max horiBearingYyMin=yMax-size[ 1 ]
xMin=offset[ 0 ]
xMax=size[ 0 ] +xMinreturn ( xMin, yMin, xMax, yMax )
# Note that font metrics assume an origin on a baseline. So xMin is negative# pixels to the left of the origin, xMax is positive pixels to the right of the# origin, yMin is negative pixels below the baseline, yMax is positive pixels# above the baseline.# if text contains "\n" it is treated as multiple lines of text.# yMin becomes really negative.# lineHeight and lineHeightPercent become relevant.# lineHeightPercent is only used if LineHeight is None, and defaults to 100%.# Use of a lineHeight < sum( font.getmetrics ) may result in text overlap.deftextInfo( self, text, font=None, lineHeight=None, lineHeightPercent=None ):
iffontisNone:
font=self.getfont()
lines=text.split('\n')
txMax=0txMin=0txWid=0tyMax=NonetyMin=Noneiflen( lines ) >1:
iflineHeightisNone:
iflineHeightPercentisNone:
lineHeightPercent=100lineHeight=int( sum( font.getmetrics())
*lineHeightPercent/100 )
lineBBs= []
forlineinlines:
lxMin, lyMin, lxMax, lyMax=font.getBB( line )
lineBBs.append(( lxMin, lyMin, lxMax, lyMax, line ))
lxWid=lxMax-lxMiniftxWid<lxWid:
txWid=lxWidiftxMax<lxMax:
txMax=lxMaxiftxMin>lxMin:
txMin=lxMiniftyMaxisNone:
tyMax=lyMax# from first line onlyiftyMinisNone:
tyMin=0# skips first line (unless it is also last)else:
tyMin-=lineHeighttyMin+=lyMin# from last linereturn ( txMin, tyMin, txMax, tyMax, txWid, lineHeight, lineBBs )
# Text is drawn as close as possible to the specified alignment edges of the# image, without truncation on those edges, then adjusted by the origin value.# alignX or alignY of 'exact' means to use the specified origin point exactly# in that direction. Otherwise origin is used as an offset from the calculated# alignment position. alignX can also be 'left', 'center', or 'right'; alignY# can also be 'top', 'middle', or 'bottom'. justifyX can be 'left', 'center',# or 'right'. Other parameters like textInfo.deftextAtPos( self, text, font=None, lineHeight=None, lineHeightPercent=None,
origin=( 0, 0 ), alignX='exact', alignY='exact', justifyX='left',
fill=None ):
iffontisNone:
font=self.getfont()
ink, fill=self._getink( fill )
ifinkisNone:
inx=fillifinkisNone:
returnifjustifyXnotin ('left', 'center', 'right'):
raiseValueError('Unknown justifyX value "%s".'%justifyX )
txMin, tyMin, txMax, tyMax, txWid, lineHeight, lineBBs=self.textInfo(
text, font, lineHeight, lineHeightPercent )
ifalignX=='exact':
ox=0elifalignX=='left':
ox=-txMinelifalignX=='right':
ox=self.im.size[ 0 ] -txMaxelifalignX=='center':
ox=self.im.size[ 0 ] //2-txMax//2else:
raiseValueError('Unknown alignX value "%s".'%alignX )
ifalignY=='exact':
oy=0elifalignY=='top':
oy=tyMaxelifalignY=='bottom':
oy=self.im.size[ 1 ] +tyMinelifalignY=='middle':
oy=self.im.size[ 1 ] //2+ ( tyMax+tyMin ) //2else:
raiseValueError('Unknown alignY value "%s".'%alignY )
ox+=origin[ 0 ]
oy+=origin[ 1 ]
ascent, descent=font.getmetrics()
whilelineBBs:
lxMin, lyMin, lxMax, lyMax, line=lineBBs.pop( 0 )
ifjustifyX=='left':
lox=oxelifjustifyX=='right':
lox=ox+txMax-lxMaxelse:
lox=ox+txMax//2-lxMax//2# finally, draw some textlox=lox+lxMinloy=oy-lyMaxim=Image.core.fill("L", ( lxMax-lxMin, lyMax-lyMin ), 0 )
font.font.render( line, im.id, self.fontmode=="1" )
self.draw.draw_bitmap(( lox, loy ), im, ink )
ifnotlineBBs:
breakoy+=lineHeightImageFont.FreeTypeFont.getBB=getBBImageDraw.ImageDraw.textInfo=textInfoImageDraw.ImageDraw.textAtPos=textAtPos
I think you've done a lot of great work looking into issue #1646@v-python. I was experiencing issues with rendering text, particularly with "stylish" fonts. I want to understand your new APIs because I think they might solve the problems that I am having.
Essentially the crux would be, does textAtPos render text like web browser would (which I believe is correct)? Such as dealing with line-height properly, and correctly determining the height of a line when it comes to rendering?
Is the correct way to get the height of a line using getmetrics()? Actually it isn't clear what this height should be - baseline to cap height, or descender to ascender?
@QasimK Thanks for the kind words. I had some false starts and misunderstandings due to limited documentation for some parts of Pillow and FontType, but I think I've worked through them all. At this point, and I think I do understand the processes, parameters, etc., and have solved the problems I was having, some of which were also in #1646. My comments there ranged from misconceived, incorrect, to partially correct, and finally correct (I believe), so it is good to have discussion here which eliminates the misconceptions.
textAtPos has 4 positioning modes in each dimension and 3 horizontal justification modes, but the block of text it renders, which must be all of one size and font, uses the same line height algorithms in all those modes.
A web browser is prepared to deal with interlinear font, font-style, and line-height changes embedded in the markup at any point in a block of text. Neither does a web browser expose pixel-level controls for positioning the block of text. However, within a block of text without such changes, I believe textAtPos can be made to produce text that would very nearly match that of a web browser, by giving both the equivalent parameters for font, fontsize, and lineHeight.
The proper lineheight of a horizontal font, according to its metrics, is yAscender - yDescender + yLineGap. The former two are available in Pillow as font.getmetrics(), except that yDescender is negated, and both are scaled to pixels for the selected pixelsize of the font, rather than being in font units, as inside the font file.
sum( font.getmetrics()) is as close as can be had in Pillow 3.0.0 to proper line height. The font metrics actually include another parameter, yLineGap, which is zero in several fonts I examined with FontTools, which gets included by FontType in its "height" metric, which is mentioned? exposed? in #1540. That wasn't yet available to me in Pillow 3.0, so I used sum( font.getmetrics()), which is numerically the same for fonts which have have yLineGap set to zero. So "correctly determining the height of a line" should use "height", or "sum( font.getmetrics()) + yLineGap", but neither of these were available to me in Pillow 3.0.0, so I approximated it by using sum( font.getmetrics()). However, an external program that can somehow obtain yLineGap from other sources (such as FontTools, or versions of Pillow that include #1540), could pass in to TextAtPos the proper lineheight value, and it would properly work with that. Merging my patch with a Pillow that includes #1540, I would recommend that its font.height would be used instead of summing font.getmetrics(). It should be more accurate in scaling, due to performing the summation in fontunits before the scaling, and would include the yLineGap. As #1540 shows, the sum after scaling is not always the same as the sum before scaling, due to rounding.
An updated PillowPatch.py that uses font.height in Pillow 3.1.0 and greater (well, any version that implements font.height). With a fallback to using sum( font.getmetrics()) if font.height raises.
fromPILimportImage, ImageDraw, ImageFontdeftext(self, xy, text, fill=None, font=None, anchor=None):
ifself._multiline_check(text):
returnself.multiline_text(xy, text, fill, font, anchor)
ink, fill=self._getink(fill)
iffontisNone:
font=self.getfont()
ifinkisNone:
ink=fillifinkisnotNone:
try:
mask, offset=font.getmask2(text, self.fontmode)
xy=xy[0] +offset[0], xy[1] +offset[1]
exceptAttributeError:
try:
mask=font.getmask(text, self.fontmode)
exceptTypeError:
mask=font.getmask(text)
self.draw.draw_bitmap(xy, mask, ink)
defmultiline_text(self, xy, text, fill=None, font=None, anchor=None,
spacing=4, align="left"):
widths= []
max_width=0lines=self._multiline_split(text)
line_spacing=self.textsize('A', font=font)[1] +spacingforlineinlines:
line_width, line_height=self.textsize(line, font)
widths.append(line_width)
max_width=max(max_width, line_width)
left, top=xyforidx, lineinenumerate(lines):
ifalign=="left":
pass# left = xelifalign=="center":
left+= (max_width-widths[idx]) /2.0elifalign=="right":
left+= (max_width-widths[idx])
else:
assertFalse, 'align must be "left", "center" or "right"'self.text((left, top), line, fill, font, anchor)
top+=line_spacingleft=xy[0]
### Get the size of a given string, in pixels.deftextsize(self, text, font=None):
ifself._multiline_check(text):
returnself.multiline_textsize(text, font)
iffontisNone:
font=self.getfont()
returnfont.getsize(text)
defmultiline_textsize(self, text, font=None, spacing=4):
max_width=0lines=self._multiline_split(text)
line_spacing=self.textsize('A', font=font)[1] +spacingforlineinlines:
line_width, line_height=self.textsize(line, font)
max_width=max(max_width, line_width)
returnmax_width, len(lines)*line_spacingImageDraw.ImageDraw.text=textImageDraw.ImageDraw.textsize=textsizeImageDraw.ImageDraw.multiline_text=multiline_textImageDraw.ImageDraw.multiline_textsize=multiline_textsizedefgetBB( self, text ):
size, offset=self.font.getsize( text )
ascent, descent=self.getmetrics()
yMax=ascent-offset[ 1 ] # distance from baseline to max horiBearingYyMin=yMax-size[ 1 ]
xMin=offset[ 0 ]
xMax=size[ 0 ] +xMinreturn ( xMin, yMin, xMax, yMax )
# Note that font metrics assume an origin on a baseline. So xMin is negative# pixels to the left of the origin, xMax is positive pixels to the right of the# origin, yMin is negative pixels below the baseline, yMax is positive pixels# above the baseline.# if text contains "\n" it is treated as multiple lines of text.# yMin becomes really negative.# lineHeight and lineHeightPercent become relevant.# lineHeightPercent is only used if LineHeight is None, and defaults to 100%.# Use of a lineHeight < sum( font.getmetrics ) may result in text overlap.# The best lineHeight would be that returned from the font.font.height# attribute, but in versions of PIL in which that isn't accessible,# sum( font.getmetrics()) is used instead.deftextInfo( self, text, font=None, lineHeight=None, lineHeightPercent=None ):
iffontisNone:
font=self.getfont()
lines=text.split('\n')
txMax=0txMin=0txWid=0tyMax=NonetyMin=Noneiflen( lines ) >1:
iflineHeightisNone:
iflineHeightPercentisNone:
lineHeightPercent=100try:
lineHeight=font.font.heightexceptExceptionasexc:
lineHeight=sum( font.getmetrics())
lineHeight=int( lineHeight*lineHeightPercent/100 )
lineBBs= []
forlineinlines:
lxMin, lyMin, lxMax, lyMax=font.getBB( line )
lineBBs.append(( lxMin, lyMin, lxMax, lyMax, line ))
lxWid=lxMax-lxMiniftxWid<lxWid:
txWid=lxWidiftxMax<lxMax:
txMax=lxMaxiftxMin>lxMin:
txMin=lxMiniftyMaxisNone:
tyMax=lyMax# from first line onlyiftyMinisNone:
tyMin=0# skips first line (unless it is also last)else:
tyMin-=lineHeighttyMin+=lyMin# from last linereturn ( txMin, tyMin, txMax, tyMax, txWid, lineHeight, lineBBs )
# Text is drawn as close as possible to the specified alignment edges of the# image, without truncation on those edges, then adjusted by the origin value.# alignX or alignY of 'exact' means to use the specified origin point exactly# in that direction. Otherwise origin is used as an offset from the calculated# alignment position. alignX can also be 'left', 'center', or 'right'; alignY# can also be 'top', 'middle', or 'bottom'. justifyX can be 'left', 'center',# or 'right'. Other parameters like textInfo.deftextAtPos( self, text, font=None, lineHeight=None, lineHeightPercent=None,
origin=( 0, 0 ), alignX='exact', alignY='exact', justifyX='left',
fill=None ):
iffontisNone:
font=self.getfont()
ink, fill=self._getink( fill )
ifinkisNone:
inx=fillifinkisNone:
returnifjustifyXnotin ('left', 'center', 'right'):
raiseValueError('Unknown justifyX value "%s".'%justifyX )
txMin, tyMin, txMax, tyMax, txWid, lineHeight, lineBBs=self.textInfo(
text, font, lineHeight, lineHeightPercent )
ifalignX=='exact':
ox=0elifalignX=='left':
ox=-txMinelifalignX=='right':
ox=self.im.size[ 0 ] -txMaxelifalignX=='center':
ox=self.im.size[ 0 ] //2-txMax//2else:
raiseValueError('Unknown alignX value "%s".'%alignX )
ifalignY=='exact':
oy=0elifalignY=='top':
oy=tyMaxelifalignY=='bottom':
oy=self.im.size[ 1 ] +tyMinelifalignY=='middle':
oy=self.im.size[ 1 ] //2+ ( tyMax+tyMin ) //2else:
raiseValueError('Unknown alignY value "%s".'%alignY )
ox+=origin[ 0 ]
oy+=origin[ 1 ]
ascent, descent=font.getmetrics()
whilelineBBs:
lxMin, lyMin, lxMax, lyMax, line=lineBBs.pop( 0 )
ifjustifyX=='left':
lox=oxelifjustifyX=='right':
lox=ox+txMax-lxMaxelse:
lox=ox+txMax//2-lxMax//2# finally, draw some textlox=lox+lxMinloy=oy-lyMaxim=Image.core.fill("L", ( lxMax-lxMin, lyMax-lyMin ), 0 )
font.font.render( line, im.id, self.fontmode=="1" )
self.draw.draw_bitmap(( lox, loy ), im, ink )
ifnotlineBBs:
breakoy+=lineHeightImageFont.FreeTypeFont.getBB=getBBImageDraw.ImageDraw.textInfo=textInfoImageDraw.ImageDraw.textAtPos=textAtPos
So I'm trying to make my first PR, following the instructions @QasimK supplied above. I got as far as the middle of step 2, have edited the new code into a branch in my local clone of the forked repository.
Is there a way to convince my Python install to use the code in my branch (...GitHub\Pillow...), so I can test it? Or a way to "build" (which I think, with Python, is mostly copying files around) and then install locally? Or should I just copy files around?
Maybe I can figure that out by reading more code or documentation, but I'm sure a novice at GitHub.
To install Pillow from it's directory, it should just be a matter of moving to the directory on the command line and then running python setup.py install. If you just wanted to build Pillow in the directory, without installing, then that would be python setup.py build.
If you're like to run tests on TravisCI, like Pillow will do once you have created the PR, you should find it simple to sign up for http://travis-ci.org/. Once you push the new branch to GitHub, Travis will start testing it.
"jpeg is required unless explicitly disabled..." older PIL (and, presumably, dependencies) already installed. Do I need a C compiler? Or what am I missing?
Not my operating system, so I might not be as much help as I could have been. I don't think there is a way to easily get the development requirements like on Mac/Linux, so I think you'll have to manually install the various dependencies. For JPEG, it would be either http://www.ijg.org/ or http://www.openjpeg.org/.
If you don't need to make changes to the C layer, and only the Python layer, you generally don't need a C compiler and can skip python setup.py install/python setup.py build run the tests from your dev directory.
Thanks for helping out. Seems that only experts can contribute to Windows. If you can integrate it and test it on Linux, that'd be great. No Linux here at the moment. I added some line comments to hugovk's review, answering some of his questions.
Activity
v-python commentedon Jan 13, 2016
Markdown interprets Python code in interesting manners.
QasimK commentedon Jan 14, 2016
I think you've done a lot of great work looking into issue #1646 @v-python. I was experiencing issues with rendering text, particularly with "stylish" fonts. I want to understand your new APIs because I think they might solve the problems that I am having.
Essentially the crux would be, does
textAtPosrender text like web browser would (which I believe is correct)? Such as dealing with line-height properly, and correctly determining the height of a line when it comes to rendering?Is the correct way to get the height of a line using
getmetrics()? Actually it isn't clear what this height should be - baseline to cap height, or descender to ascender?In order to create a pull request:
Feel free to ask me for any more specific question.
v-python commentedon Jan 14, 2016
@QasimK Thanks for the kind words. I had some false starts and misunderstandings due to limited documentation for some parts of Pillow and FontType, but I think I've worked through them all. At this point, and I think I do understand the processes, parameters, etc., and have solved the problems I was having, some of which were also in #1646. My comments there ranged from misconceived, incorrect, to partially correct, and finally correct (I believe), so it is good to have discussion here which eliminates the misconceptions.
textAtPos has 4 positioning modes in each dimension and 3 horizontal justification modes, but the block of text it renders, which must be all of one size and font, uses the same line height algorithms in all those modes.
A web browser is prepared to deal with interlinear font, font-style, and line-height changes embedded in the markup at any point in a block of text. Neither does a web browser expose pixel-level controls for positioning the block of text. However, within a block of text without such changes, I believe textAtPos can be made to produce text that would very nearly match that of a web browser, by giving both the equivalent parameters for font, fontsize, and lineHeight.
The proper lineheight of a horizontal font, according to its metrics, is yAscender - yDescender + yLineGap. The former two are available in Pillow as font.getmetrics(), except that yDescender is negated, and both are scaled to pixels for the selected pixelsize of the font, rather than being in font units, as inside the font file.
sum( font.getmetrics()) is as close as can be had in Pillow 3.0.0 to proper line height. The font metrics actually include another parameter, yLineGap, which is zero in several fonts I examined with FontTools, which gets included by FontType in its "height" metric, which is mentioned? exposed? in #1540. That wasn't yet available to me in Pillow 3.0, so I used sum( font.getmetrics()), which is numerically the same for fonts which have have yLineGap set to zero. So "correctly determining the height of a line" should use "height", or "sum( font.getmetrics()) + yLineGap", but neither of these were available to me in Pillow 3.0.0, so I approximated it by using sum( font.getmetrics()). However, an external program that can somehow obtain yLineGap from other sources (such as FontTools, or versions of Pillow that include #1540), could pass in to TextAtPos the proper lineheight value, and it would properly work with that. Merging my patch with a Pillow that includes #1540, I would recommend that its font.height would be used instead of summing font.getmetrics(). It should be more accurate in scaling, due to performing the summation in fontunits before the scaling, and would include the yLineGap. As #1540 shows, the sum after scaling is not always the same as the sum before scaling, due to rounding.
v-python commentedon Jan 23, 2016
An updated PillowPatch.py that uses font.height in Pillow 3.1.0 and greater (well, any version that implements font.height). With a fallback to using sum( font.getmetrics()) if font.height raises.
aclark4life commentedon Apr 1, 2016
@v-python Send this in a PR?
v-python commentedon Apr 2, 2016
So I'm trying to make my first PR, following the instructions @QasimK supplied above. I got as far as the middle of step 2, have edited the new code into a branch in my local clone of the forked repository.
Is there a way to convince my Python install to use the code in my branch (...GitHub\Pillow...), so I can test it? Or a way to "build" (which I think, with Python, is mostly copying files around) and then install locally? Or should I just copy files around?
Maybe I can figure that out by reading more code or documentation, but I'm sure a novice at GitHub.
radarhere commentedon Apr 2, 2016
To install Pillow from it's directory, it should just be a matter of moving to the directory on the command line and then running
python setup.py install. If you just wanted to build Pillow in the directory, without installing, then that would bepython setup.py build.If you're like to run tests on TravisCI, like Pillow will do once you have created the PR, you should find it simple to sign up for http://travis-ci.org/. Once you push the new branch to GitHub, Travis will start testing it.
v-python commentedon Apr 2, 2016
"jpeg is required unless explicitly disabled..." older PIL (and, presumably, dependencies) already installed. Do I need a C compiler? Or what am I missing?
Happens with build as well as install.
radarhere commentedon Apr 2, 2016
What operating system are you using?
v-python commentedon Apr 2, 2016
Windows 10
radarhere commentedon Apr 2, 2016
Not my operating system, so I might not be as much help as I could have been. I don't think there is a way to easily get the development requirements like on Mac/Linux, so I think you'll have to manually install the various dependencies. For JPEG, it would be either http://www.ijg.org/ or http://www.openjpeg.org/.
aclark4life commentedon Apr 2, 2016
We may have some Windows notes somewhere and/or maybe @cgohlke can comment.
hugovk commentedon Apr 3, 2016
If you don't need to make changes to the C layer, and only the Python layer, you generally don't need a C compiler and can skip
python setup.py install/python setup.py buildrun the tests from your dev directory.A good idea is to enable https://ci.appveyor.com/ for your fork and it'll run the tests on a clean Windows env for your own branches and pushes (and enable Travis CI for Linux builds; see https://github.com/python-pillow/Pillow/blob/master/.github/CONTRIBUTING.md#bug-fixes-feature-additions-etc )
(Side note: we should update CONTRIBUTING.md to mention AppVeyor for Win builds.)
wiredfool commentedon Apr 4, 2016
@radarhere OpenJpeg is a Jpeg2000 support library, which is completely different than LibJpeg, which is from Ilg.
The Windows build stuff that we have is in
winbuild/, but It's rough and I wouldn't want to point non-experts at it.micahcc commentedon May 19, 2016
@v-python thanks for the work. I've gone ahead and created a PR. I'll try to add tests etc. Actually kind of needed this for my own stuff. #1915
v-python commentedon May 23, 2016
Thanks for helping out. Seems that only experts can contribute to Windows. If you can integrate it and test it on Linux, that'd be great. No Linux here at the moment. I added some line comments to hugovk's review, answering some of his questions.
PanderMusubi commentedon Sep 25, 2016
In which release will this patch be included?
aclark4life commentedon Sep 25, 2016
@PanderMusubi It could go in the next release, after someone fixes #1915

Fixed line-spacing bug by swapping to PIL.ImageDraw.Draw.multiline_text
scsmla commentedon Oct 4, 2017
When i run a program using the PIL i got the following errors:
"'ImageFont' object has no attribute 'getmask2'.
Please show me the way to fix it
wiredfool commentedon Oct 4, 2017
@scsmla please don't repeat questions on unrelated threads
nulano commentedon Apr 13, 2020
#1915 was closed. Should this be closed as well?
aclark4life commentedon Apr 13, 2020
Sure, can always be re-opened if need be