How can I find the full path to a font from its display name on a Mac?
Question:
I am using the Photoshop’s javascript API to find the fonts in a given PSD.
Given a font name returned by the API, I want to find the actual physical font file that font name corresponds to on the disc.
This is all happening in a python program running on OSX so I guess I’m looking for one of:
- Some Photoshop javascript
- A Python function
- An OSX API that I can call from python
Answers:
open up a terminal (Applications->Utilities->Terminal) and type this in:
locate InsertFontHere
This will spit out every file that has the name you want.
Warning: there may be alot to wade through.
I haven’t been able to find anything that does this directly. I think you’ll have to iterate through the various font folders on the system: /System/Library/Fonts
, /Library/Fonts
, and there can probably be a user-level directory as well ~/Library/Fonts
.
Unfortunately the only API that isn’t deprecated is located in the ApplicationServices framework, which doesn’t have a bridge support file, and thus isn’t available in the bridge. If you’re wanting to use ctypes, you can use ATSFontGetFileReference after looking up the ATSFontRef.
Cocoa doesn’t have any native support, at least as of 10.5, for getting the location of a font.
There must be a method in Cocoa to get a list of fonts, then you would have to use the PyObjC bindings to call it..
Depending on what you need them for, you could probably just use something like the following..
import os
def get_font_list():
fonts = []
for font_path in ["/Library/Fonts", os.path.expanduser("~/Library/Fonts")]:
if os.path.isdir(font_path):
fonts.extend(
[os.path.join(font_path, cur_font)
for cur_font in os.listdir(font_path)
]
)
return fonts
With matplotlib
(pip3 install -U matplotlib
):
from matplotlib import font_manager
fontmap = {font.name: font for font in font_manager.fontManager.ttflist}
fontmap.update({font.name: font for font in font_manager.fontManager.afmlist})
print(f'Total fonts: {len(fontmap.keys())}')
for family in sorted(fontmap.keys()):
font = fontmap[family]
print(f'{family:<30}: {font.fname}')
Sample output
Total fonts: 312
.Aqua Kana : /System/Library/Fonts/AquaKana.ttc
Academy Engraved LET : /System/Library/Fonts/Supplemental/Academy Engraved LET Fonts.ttf
Al Bayan : /System/Library/Fonts/Supplemental/AlBayan.ttc
American Typewriter : /System/Library/Fonts/Supplemental/AmericanTypewriter.ttc
...
Zapf Dingbats : /System/Library/Fonts/ZapfDingbats.ttf
ZapfDingbats : /usr/local/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/ZapfDingbats.afm
Zapfino : /System/Library/Fonts/Supplemental/Zapfino.ttf
NOTE: The font families from matplotlib do not seem to include all system fonts that are available for example to the PyQt5 library:
from PyQt5.QtGui import QFontDatabase
from PyQt5.QtWidgets import QApplication
app = QApplication([])
print('n'.join(QFontDatabase().families()))
I had encountered similar requirements and I ended up by this method:
def get_font_path(font):
ttf_filename = os.path.basename(font)
dirs = []
if sys.platform == "win32":
# check the windows font repository
# NOTE: must use uppercase WINDIR, to work around bugs in
# 1.5.2's os.environ.get()
windir = os.environ.get("WINDIR")
if windir:
dirs.append(os.path.join(windir, "fonts"))
elif sys.platform in ("linux", "linux2"):
lindirs = os.environ.get("XDG_DATA_DIRS", "")
if not lindirs:
# According to the freedesktop spec, XDG_DATA_DIRS should
# default to /usr/share
lindirs = "/usr/share"
dirs += [
os.path.join(lindir, "fonts") for lindir in lindirs.split(":")
]
elif sys.platform == "darwin":
dirs += [
"/Library/Fonts",
"/System/Library/Fonts",
os.path.expanduser("~/Library/Fonts"),
]
ext = os.path.splitext(ttf_filename)[1]
first_font_with_a_different_extension = None
for directory in dirs:
for walkroot, walkdir, walkfilenames in os.walk(directory):
for walkfilename in walkfilenames:
if ext and walkfilename == ttf_filename:
return os.path.join(walkroot, walkfilename)
elif (
not ext
and os.path.splitext(walkfilename)[0] == ttf_filename
):
fontpath = os.path.join(walkroot, walkfilename)
if os.path.splitext(fontpath)[1] == ".ttf":
return fontpath
if (
not ext
and first_font_with_a_different_extension is None
):
first_font_with_a_different_extension = fontpath
if first_font_with_a_different_extension:
return first_font_with_a_different_extension
Note that the original code is from PIL
I am using the Photoshop’s javascript API to find the fonts in a given PSD.
Given a font name returned by the API, I want to find the actual physical font file that font name corresponds to on the disc.
This is all happening in a python program running on OSX so I guess I’m looking for one of:
- Some Photoshop javascript
- A Python function
- An OSX API that I can call from python
open up a terminal (Applications->Utilities->Terminal) and type this in:
locate InsertFontHere
This will spit out every file that has the name you want.
Warning: there may be alot to wade through.
I haven’t been able to find anything that does this directly. I think you’ll have to iterate through the various font folders on the system: /System/Library/Fonts
, /Library/Fonts
, and there can probably be a user-level directory as well ~/Library/Fonts
.
Unfortunately the only API that isn’t deprecated is located in the ApplicationServices framework, which doesn’t have a bridge support file, and thus isn’t available in the bridge. If you’re wanting to use ctypes, you can use ATSFontGetFileReference after looking up the ATSFontRef.
Cocoa doesn’t have any native support, at least as of 10.5, for getting the location of a font.
There must be a method in Cocoa to get a list of fonts, then you would have to use the PyObjC bindings to call it..
Depending on what you need them for, you could probably just use something like the following..
import os
def get_font_list():
fonts = []
for font_path in ["/Library/Fonts", os.path.expanduser("~/Library/Fonts")]:
if os.path.isdir(font_path):
fonts.extend(
[os.path.join(font_path, cur_font)
for cur_font in os.listdir(font_path)
]
)
return fonts
With matplotlib
(pip3 install -U matplotlib
):
from matplotlib import font_manager
fontmap = {font.name: font for font in font_manager.fontManager.ttflist}
fontmap.update({font.name: font for font in font_manager.fontManager.afmlist})
print(f'Total fonts: {len(fontmap.keys())}')
for family in sorted(fontmap.keys()):
font = fontmap[family]
print(f'{family:<30}: {font.fname}')
Sample output
Total fonts: 312
.Aqua Kana : /System/Library/Fonts/AquaKana.ttc
Academy Engraved LET : /System/Library/Fonts/Supplemental/Academy Engraved LET Fonts.ttf
Al Bayan : /System/Library/Fonts/Supplemental/AlBayan.ttc
American Typewriter : /System/Library/Fonts/Supplemental/AmericanTypewriter.ttc
...
Zapf Dingbats : /System/Library/Fonts/ZapfDingbats.ttf
ZapfDingbats : /usr/local/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts/ZapfDingbats.afm
Zapfino : /System/Library/Fonts/Supplemental/Zapfino.ttf
NOTE: The font families from matplotlib do not seem to include all system fonts that are available for example to the PyQt5 library:
from PyQt5.QtGui import QFontDatabase from PyQt5.QtWidgets import QApplication app = QApplication([]) print('n'.join(QFontDatabase().families()))
I had encountered similar requirements and I ended up by this method:
def get_font_path(font):
ttf_filename = os.path.basename(font)
dirs = []
if sys.platform == "win32":
# check the windows font repository
# NOTE: must use uppercase WINDIR, to work around bugs in
# 1.5.2's os.environ.get()
windir = os.environ.get("WINDIR")
if windir:
dirs.append(os.path.join(windir, "fonts"))
elif sys.platform in ("linux", "linux2"):
lindirs = os.environ.get("XDG_DATA_DIRS", "")
if not lindirs:
# According to the freedesktop spec, XDG_DATA_DIRS should
# default to /usr/share
lindirs = "/usr/share"
dirs += [
os.path.join(lindir, "fonts") for lindir in lindirs.split(":")
]
elif sys.platform == "darwin":
dirs += [
"/Library/Fonts",
"/System/Library/Fonts",
os.path.expanduser("~/Library/Fonts"),
]
ext = os.path.splitext(ttf_filename)[1]
first_font_with_a_different_extension = None
for directory in dirs:
for walkroot, walkdir, walkfilenames in os.walk(directory):
for walkfilename in walkfilenames:
if ext and walkfilename == ttf_filename:
return os.path.join(walkroot, walkfilename)
elif (
not ext
and os.path.splitext(walkfilename)[0] == ttf_filename
):
fontpath = os.path.join(walkroot, walkfilename)
if os.path.splitext(fontpath)[1] == ".ttf":
return fontpath
if (
not ext
and first_font_with_a_different_extension is None
):
first_font_with_a_different_extension = fontpath
if first_font_with_a_different_extension:
return first_font_with_a_different_extension
Note that the original code is from PIL