CONTEXT
I have a working python tkinter code in which I use a font I have previously downloaded to my computer (it is 'Trajan Pro Regular.ttf'). I want the font to be seen by anyone running the .exe program on any computer.
I have tried using pyglet, but it didn't work. I've looked at other answers but they didn't work for me either. Using pyglet, I got this error :
Traceback (most recent call last):
File "ModifierTool.py", line 66, in <module>
File "pyglet\font\__init__.py", line 156, in add_file
FileNotFoundError: [Errno 2] No such file or directory: 'Trajan Pro Regular.ttf'
It was fixed by placing the .ttf file in the same folder as the .exe app. This is of course not what I want, I want the font to be packed within the .exe. Even when placing the .ttf file inside the same folder as the .exe, despite clearing the error, the font is still not displayed on computer who don't have it downloaded.
I'm using auto-py-to-exe for packing.
Is there not an easy (or not) way to load embedded font files similarly to images or other files, using ressource_path ?
Sorry if I don't provide much information, this is kind of a general issue and I don't have much to add.
I tried using pyglet but it failed.
I previously tried this way (resource_path is also used to load .png and .pak files anyway). Here is a simplified snippet of code :
import tkinter as tk
import tkinter.font as tkfont
def resource_path(relative_path):
try:
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
font_file = resource_path('Trajan Pro Regular.ttf')
trajan_pro = tkfont.Font(family='Trajan Pro', file=font_file, size=10, weight='bold')
def create_widgets(self):
button_text = tk.Label(frame, text=option, font=trajan_pro, bg='#222222', fg='white')``
That didn't work either.
I also tried using tkextrafont but I couldn't get it to work, the font didn't even load when testing. See :
import tkinter as tk
import pathlib
import tkextrafont
from tkextrafont import Font
def create_widgets(self):
fontpath = pathlib.Path(__file__).parent/"Trajan Pro Regular.ttf"
font = tkextrafont.Font(file=str(fontpath), family= "Trajan Pro Regular", size= 12, weight= 'bold')
...
all_button_label = tk.Label(frame, text='All', font=font, bg='#222222', fg='white')
...
if __name__ == '__main__':
root = tk.Tk()
EDIT 1
I now tried using the AddFontResourceEx function as per @relent95 's recommandation. I actually had a look at that prior to writing this post but scrapped the idea because I couldn't understand how to use it.
With this code (simplified):
import tkinter as tk
import tkinter.font as tkfont
import os
FR_PRIVATE = 0x10
FR_NOT_ENUM = 0x20
def loadfont(fontpath, private=True, enumerable=False):
if isinstance(fontpath, bytes):
pathbuf = create_string_buffer(fontpath)
AddFontResourceEx = windll.gdi32.AddFontResourceExA
elif isinstance(fontpath, str):
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)
class App(tk.Frame):
...
def create_widgets(self):
font_path = ("Trajan Pro Regular.ttf")
loadfont(font_path)
And then simply mentionning the font like this :
all_button_label = tk.Label(frame, text='All', font=tkfont.Font(family='Trajan Pro', size=12, weight='bold'), bg='#222222', fg='white')
I have managed to load the font correctly without having it downloaded onto my computer (when running the .py AND when running the .exe). Problem is, the file still needs to be in the same folder as the program to be loaded, which is something I don't want. I still don't know how to include the font within the executable.
I also tried replacing font_path = ("Trajan Pro Regular.ttf")
with font_path = os.path.abspath("Trajan Pro Regular.ttf")
and got the same result.
I'm using auto-py-to-exe to pack my python code and I do add
--add-data "D:/Desktop/Modifier Tool/Trajan Pro Regular.ttf;."
in the command. I tried just running the PyInstaller command in python but I got the same result as when I used auto-py-to-exe.
Reproducing the problem (AddFontResourceEx function)
Here is a simple code that highlights the problem. The font won't load unless it is in the same directory (or already downloaded), even after using --add-data with PyInstaller.
import tkinter as tk
import tkinter.font as tkfont
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):
if isinstance(fontpath, bytes):
pathbuf = create_string_buffer(fontpath)
AddFontResourceEx = windll.gdi32.AddFontResourceExA
elif isinstance(fontpath, str):
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)
class App(tk.Frame):
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
self.master = master
self.create_widgets()
def create_widgets(self):
font_path = ("MyFont.ttf")
loadfont(font_path)
label = tk.Label(text='Hello', font=tkfont.Font(family='My Font'))
label.pack()
root = tk.Tk()
app = App(root)
root.mainloop()
I hope it is clear.
EDIT 2
Reproducing the problem (pyglet)
With the following code, I get an error unless the .ttf file is in the same folder as the executable. The font does load correctly if it is placed in the same folder as the .exe app, unlike before.
Simplified code:
import tkinter as tk
import tkinter.font as tkfont
import pyglet
pyglet.options['win32_gdi_font'] = True
pyglet.font.add_file('MyFont.ttf')
class App(tk.Frame):
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
self.master = master
self.create_widgets()
def create_widgets(self):
label = tk.Label(text='Hello', font=tkfont.Font(family='My Font'))
label.pack()
if __name__ == '__main__':
root = tk.Tk()
app = App(root)
root.mainloop()
Error displayed when running the program without the .ttf file in the same directory:
Traceback (most recent call last):
File "Test.py", line 7, in <module>
File "pyglet\font\__init__.py", line 156, in add_file
FileNotFoundError: [Errno 2] No such file or directory: 'MyFont.ttf'
I'm really hoping to fix that issue that has been bugging me for the past few days.