Skip to content

Fonts

FONT UTILITIES

check_app_fonts(folders)

Checks each font in a folder to see if it is installed or outdated. @param folders: Folder paths containing fonts to check. @return: A tuple containing a dict of missing fonts and a dict of outdated fonts.

Source code in src/utils/fonts.py
244
245
246
247
248
249
250
251
252
253
254
255
def check_app_fonts(folders: list[str]) -> tuple[dict[str, FontDetails], dict[str, FontDetails]]:
    """
    Checks each font in a folder to see if it is installed or outdated.
    @param folders: Folder paths containing fonts to check.
    @return: A tuple containing a dict of missing fonts and a dict of outdated fonts.
    """
    # Get a dictionary of fonts found in target folder and fonts installed
    fonts: dict[str, FontDetails] = {}
    for f in folders:
        fonts.update(get_fonts_from_folder(f))
    missing, found = get_missing_fonts(fonts)
    return missing, get_outdated_fonts(found, missing)

get_document_fonts(container=None, fonts=None, ps_fonts=None)

Get a list of all fonts used in a given Photoshop Document or LayerSet. @param container: Photoshop Document or LayerSet object. @param fonts: Existing fonts list to build onto. @param ps_fonts: Pre-computed Photoshop fonts list. @return: Unique list of font names.

Source code in src/utils/fonts.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def get_document_fonts(
    container: Optional[type[LayerContainer]] = None,
    fonts: Optional[dict] = None,
    ps_fonts: Optional[dict] = None
) -> dict:
    """
    Get a list of all fonts used in a given Photoshop Document or LayerSet.
    @param container: Photoshop Document or LayerSet object.
    @param fonts: Existing fonts list to build onto.
    @param ps_fonts: Pre-computed Photoshop fonts list.
    @return: Unique list of font names.
    """
    # Establish starting fonts and Photoshop fonts
    fonts = fonts or {}
    ps_fonts = ps_fonts or get_ps_font_dict()
    container = container or con.app.activeDocument

    # Check each layer for a TextItem with a font
    for layer in [n for n in container.artLayers if n.kind == LayerKind.TextLayer]:
        try:
            # Log a new font or update an existing one
            font = str(layer.textItem.font)
            if font in fonts:
                fonts[font]['count'] += 1
            else:
                fonts[font] = {
                    'name': ps_fonts.get(font, None),
                    'count': 1
                }
        except PS_EXCEPTIONS:
            # Font property couldn't be accessed
            print(f"Font unreadable for layer: {layer.name}")

    # Make additional calls for nested groups
    for group in container.layerSets:
        fonts = get_document_fonts(group, fonts, ps_fonts=ps_fonts)
    return fonts

get_font_details(path)

Gets the font name and postscript name for a given font file. @param path: Path to ttf or otf file. @return: Tuple containing name and postscript name.

Source code in src/utils/fonts.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def get_font_details(path: str) -> Optional[tuple[str, FontDetails]]:
    """
    Gets the font name and postscript name for a given font file.
    @param path: Path to ttf or otf file.
    @return: Tuple containing name and postscript name.
    """
    with suppress(PS_EXCEPTIONS, TTLibError):
        with TTFont(path) as font:
            font_name = font['name'].getName(4, 3, 1, 1033).toUnicode()
            font_postscript = font['name'].getDebugName(6)
            version_match = Reg.VERSION.search(font['name'].getDebugName(5))
            font_version = version_match.group(1).lstrip('0') if version_match else None
        return font_postscript, {'name': font_name, 'version': font_version}
    return

get_fonts_from_folder(folder)

Return a dictionary of font details for the fonts contained in a target directory. @param folder: Directory containing font files to read (supports TTF and OTF fonts). @return: Dictionary of FontDetails.

Source code in src/utils/fonts.py
158
159
160
161
162
163
164
165
166
167
168
169
def get_fonts_from_folder(folder: str) -> dict[str, FontDetails]:
    """
    Return a dictionary of font details for the fonts contained in a target directory.
    @param folder: Directory containing font files to read (supports TTF and OTF fonts).
    @return: Dictionary of FontDetails.
    """
    # Get a list of the font names in your `fonts` folder
    with suppress(Exception):
        ext = (".otf", ".ttf", ".OTF", ".TTF")
        local_fonts = [osp.join(folder, f) for f in os.listdir(folder) if f.endswith(ext)]
        return {n[0]: n[1] for n in [get_font_details(f) for f in local_fonts] if n}
    return {}

get_installed_fonts_dict()

Gets a dictionary of every font installed by the user. @return: Dictionary with postScriptName as key, and tuple of display name and version as value.

Source code in src/utils/fonts.py
172
173
174
175
176
177
178
179
180
181
182
183
184
def get_installed_fonts_dict() -> dict[str, FontDetails]:
    """
    Gets a dictionary of every font installed by the user.
    @return: Dictionary with postScriptName as key, and tuple of display name and version as value.
    """
    with suppress(Exception):
        installed_fonts_dir = os.path.expandvars(r'%userprofile%\AppData\Local\Microsoft\Windows\Fonts')
        system_fonts_dir = os.path.join(os.path.join(os.environ['WINDIR']), 'Fonts')
        return {
            **get_fonts_from_folder(installed_fonts_dir),
            **get_fonts_from_folder(system_fonts_dir)
        }
    return {}

get_missing_fonts(fonts)

Checks each font to see if it's present in the Photoshop font list. @param fonts: A dictionary of fonts to check for. @return: Tuple containing a dictionary of fonts missing and fonts found.

Source code in src/utils/fonts.py
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def get_missing_fonts(fonts: dict[str, FontDetails]) -> tuple[dict[str, FontDetails], dict[str, FontDetails]]:
    """
    Checks each font to see if it's present in the Photoshop font list.
    @param fonts: A dictionary of fonts to check for.
    @return: Tuple containing a dictionary of fonts missing and fonts found.
    """
    # Figure out which fonts are missing
    found: dict[str, FontDetails] = {}
    missing: dict[str, FontDetails] = {}
    for script_name, data in fonts.items():
        try:
            # Check if font exists in Photoshop
            _ = con.app.fonts.app[script_name]
            found[script_name] = data
        except PS_EXCEPTIONS:
            # Font not found in Photoshop
            missing[script_name] = data
    return missing, found

get_outdated_fonts(fonts, missing=None)

Compares the version of each font given against installed fonts. @param fonts: A dictionary of fonts to check against installed fonts. @param missing: An optional dictionary of fonts Photoshop couldn't locate, check in install dir. @return: A dict of fonts with outdated version number. Dict contains the newer version.

Source code in src/utils/fonts.py
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def get_outdated_fonts(
    fonts: dict[str, FontDetails],
    missing: Optional[dict[str, FontDetails]] = None
) -> dict[str, FontDetails]:
    """
    Compares the version of each font given against installed fonts.
    @param fonts: A dictionary of fonts to check against installed fonts.
    @param missing: An optional dictionary of fonts Photoshop couldn't locate, check in install dir.
    @return: A dict of fonts with outdated version number. Dict contains the newer version.
    """
    # Check each confirmed font for version changes
    outdated: dict[str, FontDetails] = {}
    installed: dict[str, FontDetails] = get_installed_fonts_dict()
    if not missing:
        missing = {}

    # Check fonts for any outdated
    for name, data in fonts.items():
        if name in installed and installed[name].get('version'):
            if parse(installed[name]['version']) < parse(data['version']):
                outdated[name] = data

    # Check missing fonts to see if found in installed dict, if so check for version change
    for k in list(missing.keys()):
        if k in installed and installed[k].get('version'):
            if parse(installed[k]['version']) < parse(missing[k]['version']):
                outdated[k] = missing[k]
            del missing[k]

    return outdated

get_ps_font_dict()

Gets a dictionary of every font accessible in Photoshop. @return: Dictionary with postScriptName as key, display name as value.

Source code in src/utils/fonts.py
86
87
88
89
90
91
92
93
94
95
def get_ps_font_dict() -> dict[str, str]:
    """
    Gets a dictionary of every font accessible in Photoshop.
    @return: Dictionary with postScriptName as key, display name as value.
    """
    fonts = {}
    for f in con.app.fonts:
        with suppress(PS_EXCEPTIONS):
            fonts[f.name] = f.postScriptName
    return fonts

register_font(font_path)

Add FontResource using given font file, refresh Photoshop fonts. @param font_path: Path to compatible font file. @return: True if succeeded, False if failed.

Source code in src/utils/fonts.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def register_font(font_path: str) -> bool:
    """
    Add FontResource using given font file, refresh Photoshop fonts.
    @param font_path: Path to compatible font file.
    @return: True if succeeded, False if failed.
    """
    result = ctypes.windll.gdi32.AddFontResourceW(osp.abspath(font_path))
    if result != 0:
        # Font Resource added successfully
        try:
            # Notify all programs
            print(f"{osp.basename(font_path)} added to font cache!")
            hwnd_broadcast = wintypes.HWND(-1)
            ctypes.windll.user32.SendMessageW(
                hwnd_broadcast, wintypes.UINT(0x001D), wintypes.WPARAM(0), wintypes.LPARAM(0)
            )
            con.app.refreshFonts()
        except Exception as e:
            print(e)
        return True
    return False

unregister_font(font_path)

Remove FontResource using given font file, refresh Photoshop fonts. @param font_path: Path to compatible font file. @return: True if succeeded, False if failed.

Source code in src/utils/fonts.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def unregister_font(font_path: str) -> bool:
    """
    Remove FontResource using given font file, refresh Photoshop fonts.
    @param font_path: Path to compatible font file.
    @return: True if succeeded, False if failed.
    """
    result = ctypes.windll.gdi32.RemoveFontResourceW(osp.abspath(font_path))
    if result != 0:
        # Font Resource removed successfully
        try:
            # Notify all programs
            print(f"{osp.basename(font_path)} removed from font cache!")
            hwnd_broadcast = wintypes.HWND(-1)
            ctypes.windll.user32.SendMessageW(
                hwnd_broadcast, wintypes.UINT(0x001D), wintypes.WPARAM(0), wintypes.LPARAM(0)
            )
            con.app.refreshFonts()
        except Exception as e:
            print(e)
        return True
    return False