Reputation: 287
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.
Upvotes: 24
Views: 41623
Reputation: 3149
There is a way of getting external fonts into Tkinter on Windows.
The key piece of code to make this work is the following function:
from ctypes import windll, byref, create_unicode_buffer, create_string_buffer
FR_PRIVATE = 0x10
FR_NOT_ENUM = 0x20
def loadfont(fontpath, private=True, enumerable=False):
'''
Makes fonts located in file `fontpath` available to the font system.
`private` if True, other processes cannot see this font, and this
font will be unloaded when the process dies
`enumerable` if True, this font will appear when enumerating fonts
See https://msdn.microsoft.com/en-us/library/dd183327(VS.85).aspx
'''
# This function was taken from
# https://github.com/ifwe/digsby/blob/f5fe00244744aa131e07f09348d10563f3d8fa99/digsby/src/gui/native/win/winfonts.py#L15
# This function is written for Python 2.x. For 3.x, you
# have to convert the isinstance checks to bytes and str
if isinstance(fontpath, str):
pathbuf = create_string_buffer(fontpath)
AddFontResourceEx = windll.gdi32.AddFontResourceExA
elif isinstance(fontpath, unicode):
pathbuf = create_unicode_buffer(fontpath)
AddFontResourceEx = windll.gdi32.AddFontResourceExW
else:
raise TypeError('fontpath must be of type str or unicode')
flags = (FR_PRIVATE if private else 0) | (FR_NOT_ENUM if not enumerable else 0)
numFontsAdded = AddFontResourceEx(byref(pathbuf), flags, 0)
return bool(numFontsAdded)
After you call loadfont
with the path to your font file (which can be any of .fon
, .fnt
, .ttf
, .ttc
, .fot
, .otf
, .mmm
, .pfb
, or .pfm
), you can load the font like any other installed font tkFont.Font(family=XXX, ...)
. and use it anywhere you like. [See MSDN for more info]
The biggest caveat here is that the family name of the font won't necessarily be the name of the file; it's embedded in the font data. Instead of trying to parse out the name, it would probably be easier to just look it up in a font browser GUI and hardcode into your application. edit: or, per patthoyt's comment below, look it up in tkFont.families()
(as the last item, or, more robustly, by comparing the list of families before and after loading the font).
I found this function in digsby (license); there's an unloadfont
function defined there if you want to remove the font before your program finishes executing. (You can also just rely on the private
setting to unload the font when your program ends.)
For anyone interested, here is a discussion on this topic on [TCLCORE] from a few years ago. Some more background: fonts on MSDN
Upvotes: 19
Reputation: 93
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
Upvotes: 1
Reputation: 419
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()
Upvotes: 4
Reputation: 425
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()
Upvotes: 11
Reputation: 2180
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))
Upvotes: 1
Reputation: 41
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
Upvotes: 3
Reputation: 386362
There is no way to load an external font file into Tkinter without resorting to platform-specific hacks. There's nothing built-in to Tkinter to support it.
Upvotes: 13
Reputation: 4728
I found this discussion where they cover how to use a line of text as an image and use PIL to place it into the window. That might be a solution.
I could not find a way to use tkFont to import a bundled font in the tkFont man page.
Upvotes: 6