Skip to content

Instantly share code, notes, and snippets.

@typoman
Last active November 13, 2024 13:07
Show Gist options
  • Select an option

  • Save typoman/40073f939b58f8f04cb5d1254aefa1d1 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown
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