如何将.ttf字体文件添加到Python代码中,以便它可以在任何计算机上可见?

huangapple go评论72阅读模式
英文:

How to add .ttf font file to python code so that it can be seen on any computer?

问题

**背景**

我有一个使用Python tkinter编写的工作中的代码,其中我使用了我之前下载到计算机上的字体(它是'Trajan Pro Regular.ttf')。我希望任何在任何计算机上运行.exe程序的人都能看到这个字体。

我尝试过使用pyglet,但它没有起作用。我看过其他答案,但对我也没有用。
使用pyglet时,我得到了这个错误:

Traceback (most recent call last):
File "ModifierTool.py", line 66, in
File "pyglet\font_init_.py", line 156, in add_file
FileNotFoundError: [Errno 2] No such file or directory: 'Trajan Pro Regular.ttf'


将.ttf文件放在与.exe应用程序相同的文件夹中就解决了这个问题。这当然不是我想要的,我希望将字体打包到.exe中。即使将.ttf文件放在与.exe相同的文件夹中,尽管清除了错误,但是在没有下载该字体的计算机上仍然无法显示该字体。

我使用auto-py-to-exe进行打包。

是否没有一种简单(或者不简单)的方法,类似于加载嵌入式图像或其他文件,使用`resource_path`?

抱歉如果我没有提供太多信息,这是一个普遍的问题,我没有太多可补充的内容。

我尝试使用了pyglet,但是失败了。

我之前尝试过这种方法(`resource_path`也用于加载.png和.pak文件)。这是代码的简化版本:

```python
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')

这也没有起作用。

我还尝试过使用tkextrafont,但是无法使其工作,即使在测试时字体也没有加载。见:

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()

编辑1

我现在尝试使用AddFontResourceEx函数,按照@relent95的建议。我在写这篇文章之前实际上已经看过它,但放弃了这个想法,因为我不知道如何使用它。

使用这段代码(简化版):

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):
    # ...
    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)
        all_button_label = tk.Label(frame, text='All', font=tkfont.Font(family='Trajan Pro', size=12, weight='bold'), bg='#222222', fg='white')
        
root = tk.Tk()

我成功加载了字体,而不需要将其下载到我的计算机上(当运行.py和.exe文件时)。问题是,文件仍然需要在与程序相同的文件夹中才能加载,这是我不想要的。我仍然不知道如何将字体包含在可执行文件中。

我还尝试用font_path = os.path.abspath("Trajan Pro Regular.ttf")替换了font_path = ("Trajan Pro Regular.ttf"),但是得到了相同的结果。

我使用auto-py-to-exe来打包我的Python代码,并且在命令中添加了--add-data "D:/Desktop/Modifier Tool/Trajan Pro Regular.ttf;."。我尝试只是在Python中运行PyInstaller命令,但是得到了与我使用auto-py-to-exe时相同的结果。

重现问题(AddFontResourceEx函数)

以下是突显问题的简单代码。除非文件与可执行文件在同一目录中(或者已经下载),否则字体不会加载,即使在PyInstaller中使用了--add-data。

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):
    # ...
    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()

我希望问题已经清晰了。

编辑2

重现问题(pyglet)

使用以下代码,如果.tff文件不在可执行文件的相同文件夹中,就会出现错误。如果将它放在.exe应用程序的文件夹中,字体就会加载正确,不同于之前。

简化的代码:

import tkinter as tk
import tkinter.font as tkfont
import pyglet

pyglet.options

<details>
<summary>英文:</summary>

**CONTEXT**

I have a working python tkinter code in which I use a font I have previously downloaded to my computer (it is &#39;Trajan Pro Regular.ttf&#39;). I want the font to be seen by anyone running the .exe program on any computer. 

I have tried using pyglet, but it didn&#39;t work. I&#39;ve looked at other answers but they didn&#39;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&#39;t have it downloaded.

I&#39;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&#39;t provide much information, this is kind of a general issue and I don&#39;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(&quot;.&quot;)

return os.path.join(base_path, relative_path)

font_file = resource_path(&#39;Trajan Pro Regular.ttf&#39;)
trajan_pro = tkfont.Font(family=&#39;Trajan Pro&#39;, file=font_file, size=10, weight=&#39;bold&#39;)

def create_widgets(self):

button_text = tk.Label(frame, text=option, font=trajan_pro, bg=&#39;#222222&#39;, fg=&#39;white&#39;)``
That didn&#39;t work either.

I also tried using tkextrafont but I couldn&#39;t get it to work, the font didn&#39;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/&quot;Trajan Pro Regular.ttf&quot;
            font = tkextrafont.Font(file=str(fontpath), family= &quot;Trajan Pro Regular&quot;, size= 12, weight= &#39;bold&#39;)
    ...
        all_button_label = tk.Label(frame, text=&#39;All&#39;, font=font, bg=&#39;#222222&#39;, fg=&#39;white&#39;)
    ...
    if __name__ == &#39;__main__&#39;:
        root = tk.Tk()


**EDIT 1**

I now tried using the AddFontResourceEx function as per @relent95 &#39;s recommandation. I actually had a look at that prior to writing this post but scrapped the idea because I couldn&#39;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(&#39;fontpath must be of type str or unicode&#39;)
        
        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 = (&quot;Trajan Pro Regular.ttf&quot;)
    loadfont(font_path)

And then simply mentionning the font like this :

    all_button_label = tk.Label(frame, text=&#39;All&#39;, font=tkfont.Font(family=&#39;Trajan Pro&#39;, size=12, weight=&#39;bold&#39;), bg=&#39;#222222&#39;, fg=&#39;white&#39;)

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&#39;t want. I still don&#39;t know how to include the font within the executable.

I also tried replacing `font_path = (&quot;Trajan Pro Regular.ttf&quot;)` with `font_path = os.path.abspath(&quot;Trajan Pro Regular.ttf&quot;)` and got the same result.

I&#39;m using auto-py-to-exe to pack my python code and I do add 
`--add-data &quot;D:/Desktop/Modifier Tool/Trajan Pro Regular.ttf;.&quot;` 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&#39;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(&#39;fontpath must be of type str or unicode&#39;)
        
        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 = (&quot;MyFont.ttf&quot;)
            loadfont(font_path)
    
            label = tk.Label(text=&#39;Hello&#39;, font=tkfont.Font(family=&#39;My Font&#39;))
            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[&#39;win32_gdi_font&#39;] = True
    
    pyglet.font.add_file(&#39;MyFont.ttf&#39;)
    
    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=&#39;Hello&#39;, font=tkfont.Font(family=&#39;My Font&#39;))
            label.pack()
    
    if __name__ == &#39;__main__&#39;:
        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 &quot;Test.py&quot;, line 7, in &lt;module&gt;
      File &quot;pyglet\font\__init__.py&quot;, line 156, in add_file
    FileNotFoundError: [Errno 2] No such file or directory: &#39;MyFont.ttf&#39;

I&#39;m really hoping to fix that issue that has been bugging me for the past few days.

</details>


# 答案1
**得分**: 3

Your code使用`pyglet`时,在脚本转换为可执行文件时未使用正确的字体路径。

以下是修改后的代码,使用了正确的字体路径:

```python
from pathlib import Path
import tkinter as tk
import tkinter.font as tkfont
import pyglet

pyglet.options['win32_gdi_font'] = True
fontpath = Path(__file__).parent / 'Trajan Pro.ttf'
pyglet.font.add_file(str(fontpath))

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='Trajan Pro', size=64))
        label.pack()

if __name__ == '__main__':
    root = tk.Tk()
    app = App(root)
    root.mainloop()
英文:

Your code using pyglet does not use the correct path of the font when the script is converted to executable.

Below is the modified code which uses the correct path of the font:

from pathlib import Path
import tkinter as tk
import tkinter.font as tkfont
import pyglet

pyglet.options[&#39;win32_gdi_font&#39;] = True
fontpath = Path(__file__).parent / &#39;Trajan Pro.ttf&#39;
pyglet.font.add_file(str(fontpath))

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=&#39;Hello&#39;, font=tkfont.Font(family=&#39;Trajan Pro&#39;, size=64))
        label.pack()

if __name__ == &#39;__main__&#39;:
    root = tk.Tk()
    app = App(root)
    root.mainloop()

The result of the executable generated by PyInstaller without the font file alongside with the executable:

如何将.ttf字体文件添加到Python代码中,以便它可以在任何计算机上可见?

huangapple
  • 本文由 发表于 2023年5月11日 02:45:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76221689.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定