Truly custom font in Tkinter

Viewed 22470

I am making an interface in Tkinter and I need to have custom fonts. Not just, say, Helvetica at a certain size or whatever, but fonts other than what would normally be available on any given platform. This would be something that would be kept with the program as an image file or (preferably) Truetype font file or similar. I don't want to have to install the desired fonts on every machine that is going to use the program, I just want to carry them around with the program in the same directory.

The tkFont module looks like it ought to do something like this, but I can't see where it would take a filename for a font not normally accessible to the system running the program. Thanks in advance for your help.

8 Answers

this worked for me on windows but doesn't seem to work on linux:

import pyglet,tkinter
pyglet.font.add_file('file.ttf')

root = tkinter.Tk()
MyLabel = tkinter.Label(root,text="test",font=('font name',25))
MyLabel.pack()
root.mainloop()

This was a simple solution for me:

import pyglet, tkinter
pyglet.font.add_file("your font path here")
#then you can use the font as you would normally

tkextrafont seems to me to be the most lightweight and simple, with prebuilt wheels for Windows and Linux on PyPI. Example:

import tkinter as tk
from tkextrafont import Font

window = tk.Tk()
font = Font(file="tests/overhaul.ttf", family="Overhaul")
tk.Label(window, text="Hello", font=font).pack()
window.mainloop()

for linux, I was able to install the otf font file I had into the system fonts directory:

mkdir /usr/share/fonts/opentype/my_fonts_name
cp ~/Downloads/my_fonts_name.otf /usr/share/fonts/opentype/my_fonts_name/

I found this home directory worked, and ended up using it instead:

mkdir ~/.fonts/
cp ~/Downloads/my_fonts_name.otf ~/.fonts/

in either case, then I could load it using a string of the font-name (as all the tkinter docs show):

# unshown code
self.canvas = tk.Canvas(self.data_frame, background="black")
self.canvas.create_text(event.x, event.y, text=t, tags='clicks', 
                        fill='firebrick1',
                        font=("My Fonts Name", 22))

For future people that come across this question, that want a very simple solution and implementation that works cross-platform. This answer is probably an answer discussed in the invalid link on August 16, 2012. You can use PIL's ImageFont.truetype() to render the font, then use Image.new() and ImageDraw.Draw. I have put it into a class.

class RenderFont:
    def __init__(self, filename, fill=(0, 0, 0):
        """
        constructor for RenderFont
        filename: the filename to the ttf font file
        fill: the color of the text
        """
        self._file = filename
        self._fill = fill
        self._image = None
        
    def get_render(self, font_size, txt, type_="normal"):
        """
        returns a transparent PIL image that contains the text
        font_size: the size of text
        txt: the actual text
        type_: the type of the text, "normal" or "bold"
        """
        if type(txt) is not str:
            raise TypeError("text must be a string")

        if type(font_size) is not int:
            raise TypeError("font_size must be a int")

        width = len(txt)*font_size
        height = font_size+5

        font = ImageFont.truetype(font=self._file, size=font_size)
        self._image = Image.new(mode='RGBA', size=(width, height), color=(255, 255, 255))

        rgba_data = self._image.getdata()
        newdata = []

        for item in rgba_data:
            if item[0] == 255 and item[1] == 255 and item[2] == 255:
                newdata.append((255, 255, 255, 0))

            else:
                newdata.append(item)

        self._image.putdata(newdata)

        draw = ImageDraw.Draw(im=self._image)

        if type_ == "normal":
            draw.text(xy=(width/2, height/2), text=txt, font=font, fill=self._fill, anchor='mm')
        elif type_ == "bold":
            draw.text(xy=(width/2, height/2), text=txt, font=font, fill=self._fill, anchor='mm', 
            stroke_width=1, stroke_fill=self._fill)

        return self._image
Related