Last active
January 9, 2024 02:03
-
-
Save ryanbugden/8e1f9d3ea82ea7ff6b53a2c93df400ed to your computer and use it in GitHub Desktop.
A simple window to manipulate unicode stuff across your font(s).
This file contains 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
# menuTitle: Unicorn | |
import ezui | |
from mojo.extensions import setExtensionDefault, getExtensionDefault | |
from glyphNameFormatter.reader import u2n, u2U, U2u, n2u | |
EXTENSION_KEY = "com.ryanbugden.unicorn.settings" | |
class Unicorn(ezui.WindowController): | |
def build(self): | |
content = """ | |
* TwoColumnForm @form1 | |
> : Span: | |
> ( ) Selected Glyphs @radioButtons | |
> (X) Current Font | |
> ( ) All Fonts | |
--- | |
* TwoColumnForm @form2 | |
> : Auto: | |
> (Unicodes) @autoUniButton | |
> (Glyph Names) @autoNameButton | |
> --- | |
> : Double-Map: | |
> (All-Uppercase) @allUpperButton | |
> (All-Lowercase) @allLowerButton | |
> (Miscellaneous) @miscDblMapButton | |
> --- | |
> : Remove: | |
> (Misplaced Unicodes) @removeMisplacedButton | |
> (Secondary Unicodes) @removeSecondaryButton | |
> (All Unicodes) @removeAllButton | |
> --- | |
> : Mark Glyphs: | |
> * HorizontalStack @markHorizStack | |
>> * ColorWell @markColorWell | |
>> ({xmark}) @removeMarkButton | |
> (With Unicodes) @markUnicodedButton | |
> (Without Unicodes) @markEmptyButton | |
""" | |
image_button_width = 20 | |
title_column_width = 90 | |
item_column_width = 150 | |
button_width = item_column_width | |
symbol_config = { | |
'scale' : 'medium', | |
'weight' : 'regular', | |
} | |
descriptionData = dict( | |
form1=dict( | |
titleColumnWidth=title_column_width, | |
itemColumnWidth=item_column_width | |
), | |
form2=dict( | |
titleColumnWidth=title_column_width, | |
itemColumnWidth=item_column_width | |
), | |
autoUniButton=dict( | |
width=button_width | |
), | |
autoNameButton=dict( | |
width=button_width | |
), | |
removeMisplacedButton=dict( | |
width=button_width | |
), | |
removeSecondaryButton=dict( | |
width=button_width | |
), | |
removeAllButton=dict( | |
width=button_width | |
), | |
allUpperButton=dict( | |
width=button_width | |
), | |
allLowerButton=dict( | |
width=button_width | |
), | |
miscDblMapButton=dict( | |
width=button_width | |
), | |
markHorizStack=dict( | |
distribution="equalSpacing", | |
width=button_width, | |
height=22, | |
), | |
removeMarkButton=dict( | |
symbolConfiguration=symbol_config, | |
drawBorder=True, | |
width=image_button_width | |
), | |
markEmptyButton=dict( | |
width=button_width, | |
), | |
markUnicodedButton=dict( | |
width = button_width, | |
), | |
) | |
self.w = ezui.EZPanel( | |
title="Unicorn", | |
content=content, | |
descriptionData=descriptionData, | |
controller=self | |
) | |
self.w.getNSWindow().setTitlebarAppearsTransparent_(True) | |
self.w.setItemValues(getExtensionDefault(EXTENSION_KEY, self.w.getItemValues())) | |
def started(self): | |
self.w.open() | |
def destroy(self): | |
setExtensionDefault(EXTENSION_KEY, self.w.getItemValues()) | |
def report_changes(self, unicodes_before, g): | |
unicodes_after = g.unicodes | |
if unicodes_after != unicodes_before: | |
change_text = f"\t{g.font.info.familyName} {g.font.info.styleName}: {g.name} ({g.layer.name}):" | |
spaces = " " * (40 - len(change_text)) | |
change_text += spaces | |
change_text += f"{unicodes_before} → {unicodes_after}" | |
return change_text | |
return None | |
def get_glyph_span(self): | |
a, f = AllFonts(), CurrentFont() | |
options = { | |
0: ([l[g_name] for g_name in f.selectedGlyphNames for l in f.layers if g_name in l.keys()]), | |
1: ([l[g_name] for l in f.layers for g_name in l.keys()]), | |
2: ([l[g_name] for f in a for l in f.layers for g_name in l.keys()]) | |
} | |
return options[self.w.getItem('radioButtons').get()] | |
def autoUniButtonCallback(self, sender): | |
self.auto_unicodes(self.get_glyph_span()) | |
def autoNameButtonCallback(self, sender): | |
self.auto_glyph_names(self.get_glyph_span()) | |
def removeMisplacedButtonCallback(self, sender): | |
self.remove_misplaced(self.get_glyph_span()) | |
def removeSecondaryButtonCallback(self, sender): | |
self.remove_secondary_unicodes(self.get_glyph_span()) | |
def removeAllButtonCallback(self, sender): | |
self.remove_all_unicodes(self.get_glyph_span()) | |
def allUpperButtonCallback(self, sender): | |
self.make_all_caps_or_lower(self.get_glyph_span(), method='upper') | |
def allLowerButtonCallback(self, sender): | |
self.make_all_caps_or_lower(self.get_glyph_span(), method='lower') | |
def miscDblMapButtonCallback(self, sender): | |
self.misc_double_map(self.get_glyph_span()) | |
def markUnicodedButtonCallback(self, sender): | |
self.mark_color = tuple([round(val, 5) for val in self.w.getItem("markColorWell").get()]) | |
self.mark_unicoded(self.get_glyph_span()) | |
def markEmptyButtonCallback(self, sender): | |
self.mark_color = tuple([round(val, 5) for val in self.w.getItem("markColorWell").get()]) | |
self.mark_empty(self.get_glyph_span()) | |
def removeMarkButtonCallback(self, sender): | |
self.mark_color = tuple([round(val, 5) for val in self.w.getItem("markColorWell").get()]) | |
self.remove_mark(self.get_glyph_span()) | |
def auto_unicodes(self, list_of_glyphs): | |
changed = [] | |
for g in list_of_glyphs: | |
unicodes_before = g.unicodes | |
g.autoUnicodes() | |
changes = self.report_changes(unicodes_before, g) | |
if changes: | |
changed.append(changes) | |
print(f"Unicorn: Automatically assigned the following unicodes:") | |
for change in sorted(changed): | |
print(change) | |
def auto_glyph_names(self, list_of_glyphs): | |
for g in list_of_glyphs: | |
try: | |
g.name = u2n(g.unicode) | |
except ValueError: | |
continue | |
except TypeError: | |
continue | |
def remove_misplaced(self, list_of_glyphs): | |
removed = [] | |
for g in list_of_glyphs: | |
if n2u(g.name) == None and g.unicodes != (): | |
g.unicodes = () | |
removed.append(g.name) | |
if removed: | |
print(f"Unicorn: Removed unicodes from {removed}") | |
else: | |
print(f"Unicorn: There are no extraneously assigned unicodes.") | |
def remove_secondary_unicodes(self, list_of_glyphs): | |
removed = [] | |
for g in list_of_glyphs: | |
if len(g.unicodes) > 1: | |
single_unicode = g.unicodes[0] | |
g.unicodes = () | |
g.unicode = single_unicode | |
removed.append(g.name) | |
if removed: | |
print(f"Unicorn: Removed secondary unicodes from {removed}") | |
else: | |
print(f"Unicorn: Did not have to remove any secondary unicodes from the selected set of glyphs.") | |
def remove_all_unicodes(self, list_of_glyphs): | |
removed = [] | |
for g in list_of_glyphs: | |
if g.unicodes != (): | |
g.unicodes = () | |
removed.append(g.name) | |
if removed: | |
print(f"Unicorn: Removed unicodes from {removed}") | |
else: | |
print(f"Unicorn: Did not have to remove any unicodes from the selected set of glyphs.") | |
def mark_unicoded(self, list_of_glyphs): | |
self.remove_mark(list_of_glyphs) | |
for g in list_of_glyphs: | |
if g.unicodes != (): | |
g.markColor = self.mark_color | |
def mark_empty(self, list_of_glyphs): | |
self.remove_mark(list_of_glyphs) | |
for g in list_of_glyphs: | |
if g.unicodes == (): | |
g.markColor = self.mark_color | |
def remove_mark(self, list_of_glyphs): | |
for g in list_of_glyphs: | |
if g.markColor == self.mark_color: | |
g.markColor = None | |
def make_all_caps_or_lower(self, list_of_glyphs, method='upper'): | |
exceptions = [ | |
(("Germandbls", (7838, 223, 7838)), ("germandbls", (223, 223, 7838))) | |
] | |
changed = [] | |
for g in list_of_glyphs: | |
unicodes_before = g.unicodes | |
g.unicodes = () | |
base_uni = n2u(g.name) | |
lower_uni = U2u(base_uni) | |
upper_uni = u2U(base_uni) | |
# Manually adding exceptions | |
for (upper, lower) in exceptions: | |
if g.name == upper[0]: | |
base_uni = upper[1][0] | |
lower_uni = upper[1][1] | |
upper_uni = upper[1][2] | |
break | |
elif g.name == lower[0]: | |
base_uni = lower[1][0] | |
lower_uni = lower[1][1] | |
upper_uni = lower[1][2] | |
break | |
# Is neither lowercase nor uppercase | |
if base_uni == upper_uni and base_uni == lower_uni: | |
g.unicode = base_uni | |
# Is lowercase | |
elif base_uni == lower_uni: | |
if method == 'lower': | |
# Remove unicode from respective uppercase | |
upper_name = u2n(upper_uni) | |
if upper_name in g.layer.keys(): | |
g.layer[upper_name].unicodes = () | |
# Set unicodes | |
g.unicodes = (lower_uni, upper_uni) | |
# Is uppercase (more specifically, definitely not lowercase) | |
elif base_uni == upper_uni: | |
if method == 'upper': | |
# Remove unicode from respective lowercase | |
lower_name = u2n(lower_uni) | |
if lower_name in g.layer.keys(): | |
g.layer[lower_name].unicodes = () | |
# Set unicodes | |
g.unicodes = (upper_uni, lower_uni) | |
changes = self.report_changes(unicodes_before, g) | |
if changes: changed.append(changes) | |
print(f"Unicorn: Made font all-{method}case with the following unicode changes:") | |
for change in sorted(changed): | |
print(change) | |
def misc_double_map(self, list_of_glyphs): | |
misc_dbl = { | |
# From AFDKO: https://github.com/adobe-type-tools/afdko/blob/b955b771aacfac517b0f12a761073d4b1a2f9a88/python/afdko/makeotf.py#L208-L225 | |
'space': (32, 2560), | |
'fraction': (8260, 8725), | |
# 'hyphen': (45, 173), # See: https://typedrawers.com/discussion/2046/special-dash-things-softhyphen-horizontalbar | |
'macron': (175, 713), | |
'mu': (181, 956), | |
'periodcentered': (183, 8729), | |
'Delta': (8710, 916), | |
'Omega': (8486, 937), | |
'Scedilla': (350, 63169), | |
'scedilla': (351, 63170), | |
'Tcommaaccent': (354, 538), | |
'tcommaaccent': (355, 539) | |
} | |
changed = [] | |
for g in list_of_glyphs: | |
unicodes_before = g.unicodes | |
if g.name in misc_dbl.keys(): | |
g.unicodes = misc_dbl[g.name] | |
# Remove unicodes from other glyphs that might have those unicodes. | |
for uni in misc_dbl[g.name]: | |
for other_gn in g.layer.keys(): | |
if other_gn != g.name: | |
other_g = g.layer[other_gn] | |
other_unicodes_before = other_g.unicodes | |
if uni in other_g.unicodes: | |
other_g.unicodes = () | |
changes = self.report_changes(other_unicodes_before, other_g) | |
if changes: changed.append(changes) | |
changes = self.report_changes(unicodes_before, g) | |
if changes: changed.append(changes) | |
print(f"Unicorn: Double-mapped unicodes with the following unicode changes:") | |
for change in sorted(changed): | |
print(change) | |
Unicorn() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment