Skip to content

Instantly share code, notes, and snippets.

@typoman
Last active November 13, 2024 13:07
Show Gist options
  • Save typoman/40073f939b58f8f04cb5d1254aefa1d1 to your computer and use it in GitHub Desktop.
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.
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())
@typoman
Copy link
Author

typoman commented Nov 13, 2024

Usage

Running the Script

To run the script, navigate to the directory containing the script and execute the following command:

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

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment