Last active
November 13, 2024 13:07
-
-
Save typoman/40073f939b58f8f04cb5d1254aefa1d1 to your computer and use it in GitHub Desktop.
Auto set the OpenType OS/2 Weight Class value for a UFO font based on its styleName.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
from fontParts.world import * | |
import argparse | |
""" | |
Command Line Python Script | |
- Type: Mastering | |
- Purpose: To determine the OpenType OS/2 Weight Class value for a font based on its styleName. | |
- Specifications: | |
- Parse the styleName of the font | |
- Use a predefined mapping of style names to weight classes | |
- Calculate the Levenshtein distance between the styleName and the predefined style names | |
- Set the OpenType OS/2 Weight Class value based on the closest matching style name | |
- Publish Date: 2024-11-13 | |
- Author: Bahman Eslami | |
- License: MIT | |
### Usage | |
#### Running the Script | |
To run the script, navigate to the directory containing the script and execute the following command: | |
```bash | |
python font-auto-weight-class.py /path/to/fonts | |
``` | |
Replace `/path/to/fonts` with the actual path to the folder containing the UFO fonts. | |
#### Command Line Arguments | |
* `Fonts Root`: The path to the folder containing the UFO fonts. | |
#### Example | |
```bash | |
python font-auto-weight-class.py ~/Documents/Fonts/Source-Sans/ | |
``` | |
This will run the script on the UFO fonts located in the `~/Documents/Fonts/Source-Sans/` directory. | |
""" | |
# The predefined mapping of style names to weight classes are taken from | |
# Microsoft's documentation but it has been expanded to include more style names. | |
# You can add your names to the following mapping. | |
# https://docs.microsoft.com/en-us/typography/opentype/spec/os2#weight-class | |
weightClassMapping = { | |
"Hairline": 20, | |
"Ultra-thin": 40, | |
"Extra-thin": 60, | |
"Thin": 100, | |
"Ultra-light": 150, "Ultralight": 150, | |
"Extra-light": 200, "Extralight": 200, "Extra light": 200, | |
"Light": 300, | |
"Normal": 400, "Regular": 400, "Roman": 400, "": 400, | |
"Roman No2": 420, | |
"Book": 440, | |
"Medium": 500, | |
"Semi-bold": 600, "Demi-bold": 600, "Semibold": 600, "Demi": 600, "Demibold": 600, | |
"Bold": 700, | |
"Extra-bold": 800, "Extrabold": 800, | |
"Ultra-bold": 850, "Ultrabold": 850, | |
"Black": 900, | |
"Heavy": 930, | |
"Fat": 960 | |
} | |
def levenshtein_distance(base_string, string_list): | |
""" | |
Calculate the Levenshtein distance between two strings. | |
This is used to find the closest word to the target words. | |
Args: | |
base_string (str): The target word | |
string_list (list): A list of words to compare to | |
Returns: | |
int: The Levenshtein distance between the two strings | |
""" | |
base_string = base_string.lower() | |
if len(base_string) < len(string_list): | |
return levenshtein_distance(string_list, base_string) | |
# If one of the strings is empty | |
if len(string_list) == 0: | |
return len(base_string) | |
previous_row = range(len(string_list) + 1) | |
for i, c1 in enumerate(base_string): | |
current_row = [i + 1] | |
for j, c2 in enumerate(string_list): | |
insertions = previous_row[j + 1] + 1 | |
deletions = current_row[j] + 1 | |
substitutions = previous_row[j] + (c1 != c2) | |
current_row.append(min(insertions, deletions, substitutions)) | |
previous_row = current_row | |
return previous_row[-1] | |
def sort_by_levenshtein(target, words): | |
""" | |
Sort a list of words by their Levenshtein distance to a target word. | |
This function uses the levenshtein_distance function to calculate the distance between the target word and each word in the list, | |
and then sorts the list based on these distances. | |
Args: | |
target (str): The target word | |
words (list): A list of words to compare to | |
Returns: | |
list: A list of words sorted by their Levenshtein distance to the target word | |
""" | |
return sorted(words, key=lambda word: levenshtein_distance(target, word.lower())) | |
def main(): | |
parser = argparse.ArgumentParser(description="Set OpenType OS/2 weight class for UFO fonts based on style name") | |
parser.add_argument("Fonts Root", help="Path to the folder containing the UFO fonts") | |
parsed_args = parser.parse_args() | |
folder_path = parsed_args["Fonts Root"] | |
for filename in os.listdir(folder_path): | |
if filename.endswith(".ufo"): | |
font_path = os.path.join(folder_path, filename) | |
try: | |
font = Font(file_path) | |
except Exception as e: | |
print(f"Error loading UFO file: {file_path}, {e}") | |
font = OpenFont(font_path, showInterface=False) | |
styleName = font.info.styleName | |
closest_weight_name = sort_by_levenshtein(styleName, weightClassMapping.keys())[0] | |
weightClass = weightClassMapping.get(closest_weight_name, None) | |
if weightClass is not None: | |
font.info.openTypeOS2WeightClass = weightClass | |
print(f"Set {filename} to weight class {weightClass}") | |
ask = input("Save changes? (Y/N): ") | |
if ask.lower() == "y": | |
font.save() | |
else: | |
print(f"Unmapped style {styleName}.") | |
font.close() | |
else: | |
print(f"Could not determine weight class for {filename}") | |
font.close() | |
if __name__ == '__main__': | |
import sys | |
sys.exit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage
Running the Script
To run the script, navigate to the directory containing the script and execute the following command:
Replace
/path/to/fonts
with the actual path to the folder containing the UFO fonts.Command Line Arguments
Fonts Root
: The path to the folder containing the UFO fonts.Example
python font-auto-weight-class.py ~/Documents/Fonts/Source-Sans/
This will run the script on the UFO fonts located in the
~/Documents/Fonts/Source-Sans/
directory.