'Python PIL decrease letter spacing

How can I decrease the letter spacing of this text? I want to make the text more squished together by a few pixels.

I'm trying to make a transparent image, with text on it, that I want pushed together. Like this, but transparent:

image

from PIL import Image, ImageDraw, ImageFont

(W, H) = (140, 40)

#create transparent image
image = Image.new("RGBA", (140, 40), (0,0,0,0))

#load font
font = ImageFont.truetype("Arial.ttf", 30)
draw = ImageDraw.Draw(image)

text = "kpy7n"
w,h = font.getsize(text)

draw.text(((W-w)/2,(H-h)/2), text, font=font, fill=0)

image.save("transparent-image.png")


Solution 1:[1]

This function will automate all the pain for you. It was written to emulate Photoshop values and can render leading (the space between lines) as well as tracking (the space between characters).

def draw_text_psd_style(draw, xy, text, font, tracking=0, leading=None, **kwargs):
    """
    usage: draw_text_psd_style(draw, (0, 0), "Test", 
                tracking=-0.1, leading=32, fill="Blue")

    Leading is measured from the baseline of one line of text to the
    baseline of the line above it. Baseline is the invisible line on which most
    letters—that is, those without descenders—sit. The default auto-leading
    option sets the leading at 120% of the type size (for example, 12?point
    leading for 10?point type).

    Tracking is measured in 1/1000 em, a unit of measure that is relative to 
    the current type size. In a 6 point font, 1 em equals 6 points; 
    in a 10 point font, 1 em equals 10 points. Tracking
    is strictly proportional to the current type size.
    """
    def stutter_chunk(lst, size, overlap=0, default=None):
        for i in range(0, len(lst), size - overlap):
            r = list(lst[i:i + size])
            while len(r) < size:
                r.append(default)
            yield r
    x, y = xy
    font_size = font.size
    lines = text.splitlines()
    if leading is None:
        leading = font.size * 1.2
    for line in lines:
        for a, b in stutter_chunk(line, 2, 1, ' '):
            w = font.getlength(a + b) - font.getlength(b)
            # dprint("[debug] kwargs")
            print("[debug] kwargs:{}".format(kwargs))
                
            draw.text((x, y), a, font=font, **kwargs)
            x += w + (tracking / 1000) * font_size
        y += leading
        x = xy[0]

It takes a font and a draw object, which can be obtained via:

font = ImageFont.truetype("Arial.ttf", 30)
draw = ImageDraw.Draw(image)

Solution 2:[2]

You have to draw the text character by character and then change the x coordinate when drawing the next

Example of code:

w,h = font.getsize("k")
draw.text(((W,H),"K", font=font, fill=0)
draw.text(((W+w)*0.7,H),"p", font=font, fill=0)
draw.text(((W+w*2)*0.7,H),"y", font=font, fill=0)
draw.text(((W+w*3)*1,H),"7", font=font, fill=0)
draw.text(((W+w*4)*0.8,H),"n", font=font, fill=0)

Solution 3:[3]

You can do this by changing the kerning - I am not sure how to do that with PIL at the moment, but it is possible with ImageMagick in the Terminal and with Python using wand which is a Python binding to ImageMagick.

First, in the Terminal - look at the parameter -kerning which is first minus three then plus three:

magick -size 200x80 xc:black -gravity center -font "Arial Bold.ttf" -pointsize 50 -kerning -3 -fill white -draw "text 0,0 'kpy7n'" k-3.png

enter image description here

magick -size 200x80 xc:black -gravity center -font "Arial Bold.ttf" -pointsize 50 -kerning 3 -fill white -draw "text 0,0 'kpy7n'" k+3.png

enter image description here

And, somewhat similarly in Python:

#!/usr/bin/env python3

# Needed this on macOS Monterey:
# export WAND_MAGICK_LIBRARY_SUFFIX="-7.Q16HDRI"
# export MAGICK_HOME=/opt/homebrew

from wand.image import Image
from wand.drawing import Drawing
from wand.font import Font

text = "kpy7n"

# Create a black canvas 400x120
with Image(width=400, height=120, pseudo='xc:black') as image:
    with Drawing() as draw:
        # Draw once in yellow with positive kerning
        draw.font_size = 50
        draw.font = 'Arial Bold.ttf'
        draw.fill_color = 'yellow'
        draw.text_kerning = 3.0
        draw.text(10, 80, text)
        draw(image)
        # Draw again in magenta with negative kerning
        draw.fill_color = 'magenta'
        draw.text_kerning = -3.0
        draw.text(200, 80, text)
        draw(image)
    image.save(filename='result.png')

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Mark Setchell