PIL Custom Font Alternate Glyphs Not Utilized - python

I made a custom handwriting font and exported it to a .ttf file. The service I used, Caligriphr, allows for alternate glyphs for each character. When I type with the font in notepad, alternate glyphs display correctly. However, when I write text onto an image using PIL with the custom font, only one glyph is used
for each character. Below is my code to write on the image:
body = ""
with open('body.txt') as fin:
ls = fin.readlines()
for l in ls:
words = l.split(' ')
for word in words:
double = random.randint(1,2) == 2
if(double):
body += ' ' + word
else:
body += ' ' + word
image = Image.open('graph.jpg').convert("RGBA")
text = Image.new('RGBA', image.size, (255,255,255,0))
font = ImageFont.FreeTypeFont('Graphite.ttf', 100)
d = ImageDraw.Draw(text)
d.text(xy=offset, text=body, fill = (26,29,32, 230), font=font)
tilt = random.random() * 2
slt = text.rotate(tilt, expand=1)
sx, sy = slt.size
image.paste(slt, (0,0, sx, sy), slt)
image.save('sample.png')
Edit: showing code for how body string is constructed
Any help would be appreciated.
Alternate Glyphs Displayed in Notepad
PIL output not utilizing alternate glyphs

This may depend on whether you're using the original PIL library, or Pillow.
Getting contextual alternate glyphs in the drawn output requires that the rendering engine draw the string as an entire string (not character by character) and, while doing so, process certain data in the font that performs glyph substitutions from the default glyphs.
This data could be OpenType Layout tables in OpenType fonts, AAT tables in Apple TrueType fonts, or Graphite tables in Graphite fonts, depending on what the platform/library supports. Since you mention you got alternates in Notepad, that indicates that the font has OpenType Layout data.
Reading the Pillow ImageFont documentation, it doesn't give any indication as to whether it supports any of these font formats. However, looking at the Pillow project in Github, I see that [winbuild\config.py] pulls in Harfbuzz, and that would provide support for OpenType Layout data. So, it seems that Pillow ought to draw using the contextual alternate glyphs, though I don't know if anything is needed to trigger it. (In Notepad, it happens by default, but that's not true everywhere.)

If using Pillow, you might need to explicitly enable the Raqm layout engine when loading the font: "Raqm - A library for complex text layout" http://host-oman.github.io/libraqm/raqm-Raqm.html
This engine can be specified using the ImageFont.truetype() function:
font = ImageFont.truetype('Graphite.ttf', 100, layout_engine=ImageFont.LAYOUT_RAQM)
Docs: https://pillow.readthedocs.io/en/stable/reference/ImageFont.html#PIL.ImageFont.truetype
Testing if Raqm is availble on your system:
>>> from PIL import features
>>> features.check('raqm')
True
If not available, on my Ubuntu 20 it installs via
$ sudo apt install libraqm0
Edit: It looks like you don't even have to specify the layout_engine parameter at all. On my system alternate glyphs are used as soon as I install the Raqm library, even without changing the code.

Related

Python, PyQt: Find font file name from postscript name

I am handing off font data from one application to another, and unfortunately the first application is only able to give me "postscript" font names and nothing else. What I need is to find the filename on disk of the postscript font name. For example I have the postscript name "ZoomlaYingXing-A024" which is called "ZhuLang Semi-Cursive Script Chinese Font.otf" on my hard drive.
The second application is written in PyQt4 and ideally I could use some of the tools in Qt to handle this. For example I can construct a QFont from the font name, and query QFont.exactMatch() to make sure that I get THAT font - and from there I could create a QFontInfo object, however the only useful data I seem to be able to get out of a QFontInfo object is rawName() which just gives me the postscript name again...
From reading around it looks like I could search the registry but I have never worked with the registry and the examples I found were written in C++ which I'm not sure how to translate.
For info this only needs to work on Windows.
You could use fontTools and matplotlib.
If you need any more information about how to decode the font postscript, see the answer I posted here: How to get the font name (title) from a font file in python
from matplotlib import font_manager
from fontTools import ttLib
def isFontMatchPostscriptName(fontPath: str, font: ttLib.TTFont):
postScriptName = font['name'].getDebugName(6)
if postScriptName == "Cambria":
print("The font is located in: ", fontPath)
fontsPath = font_manager.findSystemFonts()
for fontPath in fontsPath:
with open(fontPath, 'rb') as fontFile:
fontType = fontFile.read(4)
if fontType == (b'ttcf'):
# TTC File.
ttCollection = ttLib.ttCollection.TTCollection(fontPath)
for font in ttCollection.fonts:
isFontMatchPostscriptName(fontPath, font)
else:
# It will be any TrueType or OpenType file except TTC file
isFontMatchPostscriptName(fontPath, ttLib.TTFont(fontPath))

PIL cuts off top of letters

I've spent a lot of time making my first web application using Python, and I'm using pil for generating images. After reading a lot, I've managed to implement proper text aligning, wrapping, generating files with many extensions etc.
However, all the text generated by PIL is cut off at the top. Here's a sample.
It should say ŻÓĆjygpq in a variety of fonts (the font names are on the left).
I've found few posts here: fonts clipping with PIL,
but I'd like to avoid using another module (aggdraw); since I've figured out so many things in PIL already I'd like to stick to that.
I've tried many fonts in different sizes, but text is still cut off. I even tried to use PIL fonts, but it still doesn't work. [Also converting OTF to BDF, and to PIL].
This is on Ubuntu. What should I try next?
I hope to be wrong on this one, but the only correct fix relies on patching how _imagingft.c
renders the text. PIL depends on FreeType for this task, but PIL seems to be miscalculating the positioning. Also, the height in getsize is overestimated (although that doesn't cause problem). For the moment, I've put a patch to handle these issues at: http://pastebin.com/jP2iLkDN (there seems to be a better way to patch the render code).
Here are some examples of the output I get without the patch and with the patch, respectively:
Results using the code present in the linked discussion. On OSX:
On Ubuntu:
Here is the code to generate the top figures:
# -*- encoding: utf8 -*-
import sys
import Image, ImageDraw, ImageFont
im = Image.new("RGBA", (1000, 1000), 'white')
draw = ImageDraw.Draw(im)
start_y = 7
text = u'\u00d1\u00d3yŻ\u00d4Ćgp\u010c\u0137'
for i in xrange(28, 46, 2):
font = ImageFont.truetype('Junicode-Bold.ttf', i)
width, height = font.getsize(text)
draw.rectangle((0, start_y, width, height + start_y), outline='blue')
draw.text((0, start_y), text, font=font, fill='black')
start_y += height + 7
im.crop((0, 0, width + 1, start_y + 2)).save(sys.argv[1])
The bottom figures were generated according to code present in the linked topic about PIL cutting off parts of the text.
Not the best solution but I see people have solved this by adding a leading a trailing space to their text.

get svg text size in python

I am generating SVG image in python (pure, no external libs yet). I want to know what will be a text element size, before I place it properly. Any good idea how to make it? I checked pysvg library but I saw nothing like getTextSize()
This can be be pretty complicated. To start with, you'll have to familiarize yourself with chapter on text of the SVG specification. Assuming you want to get the width of plain text elements, and not textpath elements, at a minimum you'd have to:
Parse the font selection properties, spacing properties and read the xml:space attibute, as well as the writing-mode property (can also be top-bottom instead of just left-to-right and right-to-left).
Based on the above, open the correct font, and read the glyph data and extract the widths and heights of the glyphs in your text string. Alone finding the font can be a big task, seeing the multiple places where font files can hide.
(optionally) Look through the string for possible ligatures (depending on the language), and replace them with the correct glyph if it exists in the font.
Add the widths for all the characters and spaces, the latter depending on the spacing properties and (optionally) possible kerning pairs.
A possible solution would be to use the pango library. You can find python bindings for it in py-gtk. Unfortunately, except from some examples, the documentation for the python bindings is pretty scarce. But it would take care of the details of font loading and determining the extents of a Layout.
Another way is to study the SVG renderer in your browser. But e.g. the support for SVG text in Firefox is limited.
Also instructive is to study how TeX does it, especially the concept (pdf) of boxes (for letters) and glue (for spacing).
I had this exact same problem, but I had a variable width font. I solved it by taking the text element (correct font and content) I wanted, wrote it to a svg file, and I used Inkscape installed on my PC to render the drawing to a temporary png file. I then read back the dimensions of the png file (extracted from the header), removed the temp svg and png files and used the result to place the text where I wanted and elements around it.
I found that rendering to a drawing, using a DPI of 90 seemed to give me the exact numbers I needed, or the native numbers used in svgwrite as a whole. -D is the flag to use so that only the drawable element, i.e. the text, is rendered.
os.cmd(/cygdrive/c/Program\ Files\ \(x86\)/Inkscape/inkscape.exe -f work_temp.svg -e work_temp.png -d 90 -D)
I used these functions to extract the png numbers, found at this link, note mine is corrected slightly for python3 (still working in python2)
def is_png(data):
return (data[:8] == b'\x89PNG\r\n\x1a\n'and (data[12:16] == b'IHDR'))
def get_image_info(data):
if is_png(data):
w, h = struct.unpack('>LL', data[16:24])
width = int(w)
height = int(h)
else:
raise Exception('not a png image')
return width, height
if __name__ == '__main__':
with open('foo.png', 'rb') as f:
data = f.read()
print is_png(data)
print get_image_info(data)
It's clunky, but it worked

Python Imaging Library - Text rendering

I'm trying to render some text using PIL, but the result that comes out is, frankly, crap.
For example, here's some text I wrote in Photoshop:
and what comes out of PIL:
As you can see, the results from PIL is less than satisfactory. Maybe I'm just being picky, but is there any way to draw text using PIL that gets results more close to my reference image?
Here's the code I'm using on Python 2.7 with PIL 1.1.7
image = Image.new("RGBA", (288,432), (255,255,255))
usr_font = ImageFont.truetype("resources/HelveticaNeueLight.ttf", 25)
d_usr = ImageDraw.Draw(image)
d_usr = d_usr.text((105,280), "Travis L.",(0,0,0), font=usr_font)
I came up with my own solution that I find acceptable.
What I did was render the text large, like 3x the size it needs to be then scale it resize it down with antialiasing, it's not 100% perfect, but it's a hell of a lot better than default, and doesn't require cairo or pango.
for example,
image = Image.new("RGBA", (600,150), (255,255,255))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype("resources/HelveticaNeueLight.ttf", fontsize)
draw.text((10, 0), txt, (0,0,0), font=font)
img_resized = image.resize((188,45), Image.ANTIALIAS)
and you endup with this result,
which is a lot better than what I was getting before with the same font.
Try using pycairo - the python bindings for the Cairo drawing library -- it is usefull for more refined drawing, with antialiased lines,
and such - and you can generate vector based images as well
Correctly handling fonts, and layout is complicated, and requires the use of
the "pango" and "pangocairo" libraries as well. Although they are made
for serious font work (all GTK+ widgets do use pango for font rendering),
the available docuemtnation and examples are extremely poor.
The sample bellow shows the prints available in the system and renders the
sample text in a font family passed as parameter on the command line.
# -*- coding: utf-8 -*-
import cairo
import pango
import pangocairo
import sys
surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, 320, 120)
context = cairo.Context(surf)
#draw a background rectangle:
context.rectangle(0,0,320,120)
context.set_source_rgb(1, 1, 1)
context.fill()
#get font families:
font_map = pangocairo.cairo_font_map_get_default()
families = font_map.list_families()
# to see family names:
print [f.get_name() for f in font_map.list_families()]
#context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
# Positions drawing origin so that the text desired top-let corner is at 0,0
context.translate(50,25)
pangocairo_context = pangocairo.CairoContext(context)
pangocairo_context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
layout = pangocairo_context.create_layout()
fontname = sys.argv[1] if len(sys.argv) >= 2 else "Sans"
font = pango.FontDescription(fontname + " 25")
layout.set_font_description(font)
layout.set_text(u"Travis L.")
context.set_source_rgb(0, 0, 0)
pangocairo_context.update_layout(layout)
pangocairo_context.show_layout(layout)
with open("cairo_text.png", "wb") as image_file:
surf.write_to_png(image_file)
I've never used PIL, but a quick review of the documentation for the Draw method indicates that PIL provides a way to render simple graphics. Photoshop provides a way to render complex graphics. To get anywhere close to Photoshop-like results requires, at a minimum, font hinting and anti-aliasing. PIL's documentation doesn't even hint at having such capabilities. You may want to look at using an external tool that might do a better job of rendering text on images. For example, ImageMagick (you'll want to use the 8-bit version, which handles standard 24-bit RGB). You can find some text drawing samples here: http://www.imagemagick.org/Usage/draw/
Suggestion: use Wand or a different Imaging library
Here's an example with wand -
from wand.color import Color
from wand.image import Image
from wand.drawing import Drawing
from wand.compat import nested
with Drawing() as draw:
with Image(width=1000, height=100, background=Color('lightblue')) as img:
draw.font_family = 'Indie Flower'
draw.font_size = 40.0
draw.push()
draw.fill_color = Color('hsl(0%, 0%, 0%)')
draw.text(0,int(img.height/2 + 20), 'Hello, world!')
draw.pop()
draw(img)
img.save(filename='image.png')
In python3 there is an option for aliased fonts. I couldn't find this answer anywhere, hopefully it helps someone like me who found this question on google and had to dig a long time to find the answer.
draw = ImageDraw.Draw(img)
draw.fontmode = "L"
Mentioned in the docs here
You can also try to write the font two times it increases the quality immense.
image = Image.new("RGBA", (288,432), (255,255,255))
usr_font = ImageFont.truetype("resources/HelveticaNeueLight.ttf", 25)
d_usr = ImageDraw.Draw(image)
d_usr = d_usr.text((105,280), "Travis L.",(0,0,0), font=usr_font)
d_usr = d_usr.text((105,280), "Travis L.",(0,0,0), font=usr_font)

Printing Graphics in Python

I need to print "Wheel Tags" from python. Wheel tags will have images, lines, and text.
The Python tutorial has two paragraphs about creating postscript files with the image lib. After reading it I still do not know how to lay out the data. I was hoping some one might have samples of how to layout the images, text and lines?
Thanks for any help.
See http://effbot.org/imagingbook/psdraw.htm
Note that:
the PSDraw module does not appear to have been actively maintained since 2005; I would guess that most of the effort has been redirected into supporting the PDF format instead. You might be happier using pypdf instead;
it has comments like '# FIXME: incomplete' and 'NOT YET IMPLEMENTED' in the source
it does not appear to have any method of setting the page size - which as I recall means it defaults to A4 (8.26 x 11.69 inches)
all measurements are in points, at 72 points per inch.
You will need to do something like:
import Image
import PSDraw
# fns for measurement conversion
PTS = lambda x: 1.00 * x # points
INS = lambda x: 72.00 * x # inches-to-points
CMS = lambda x: 28.35 * x # centimeters-to-points
outputFile = 'myfilename.ps'
outputFileTitle = 'Wheel Tag 36147'
myf = open(outputFile,'w')
ps = PSDraw.PSDraw(myf)
ps.begin_document(outputFileTitle)
ps is now a PSDraw object which will write PostScript to the specified file, and the document header has been written - you are ready to start drawing stuff.
To add an image:
im = Image.open("myimage.jpg")
box = ( # bounding-box for positioning on page
INS(1), # left
INS(1), # top
INS(3), # right
INS(3) # bottom
)
dpi = 300 # desired on-page resolution
ps.image(box, im, dpi)
To add text:
ps.setfont("Helvetica", PTS(12)) # PostScript fonts only -
# must be one which your printer has available
loc = ( # where to put the text?
INS(1), # horizontal value - I do not know whether it is left- or middle-aligned
INS(3.25) # vertical value - I do not know whether it is top- or bottom-aligned
)
ps.text(loc, "Here is some text")
To add a line:
lineFrom = ( INS(4), INS(1) )
lineTo = ( INS(4), INS(9) )
ps.line( lineFrom, lineTo )
... and I don't see any options for changing stroke weight.
When you are finished, you have to close the file off like:
ps.end_document()
myf.close()
Edit: I was doing a bit of reading on setting stroke weights, and I ran across a different module, psfile: http://seehuhn.de/pages/psfile#sec:2.0.0 The module itself looks pretty minimal - he's writing a lot of raw postscript - but it should give you a better idea of what's going on behind the scenes.
I would recommend the open source library Reportlab for this sort of task.
It is very simple to use and outputs directly to PDF format.
A very simple example from the official documentation:
from reportlab.pdfgen import canvas
def hello(c):
c.drawString(100,100,"Hello World")
c = canvas.Canvas("hello.pdf")
hello(c)
c.showPage()
c.save()
As long as PIL is installed, it is also very easy to add images to your page:
canvas.drawImage(self, image, x,y, width=None,height=None,mask=None)
where "image" is either a PIL Image object, or the filename of the image you wish to use.
Plenty of examples in the documentation also.

Categories