| 1 | #FLM: Automatic Text |
|---|
| 2 | # -*- coding: utf-8 -*- |
|---|
| 3 | |
|---|
| 4 | """Automatic Text |
|---|
| 5 | |
|---|
| 6 | A FontLab Studio port of a long forgotten but very helpful Letraset |
|---|
| 7 | FontStudio feature. Automatic Text generates mixed sequences of |
|---|
| 8 | glyphs, useful when checking for sanity your font metrics/kerning. |
|---|
| 9 | |
|---|
| 10 | It should work with any copy of FontLab Studio 5, nothing else is |
|---|
| 11 | required. |
|---|
| 12 | |
|---|
| 13 | To install, put this file in your FontLab Macros folder (on OS X it is |
|---|
| 14 | in ~/Library/Application Support/FontLab 5/Macros), then restart |
|---|
| 15 | FontLab. You’ll find Automatic Text in the Macro toolbar, which can be |
|---|
| 16 | toggled with the menu item View → Toolbars → Macro. |
|---|
| 17 | |
|---|
| 18 | You can then launch Automatic Text by clicking the Run button next to |
|---|
| 19 | the macro name in the Macro toolbar, and it will popup its dialog |
|---|
| 20 | box. If you select one glyph in the font window it will be selected as |
|---|
| 21 | the glyph to generate. If you select several glyphs Automatic Text |
|---|
| 22 | will default to the selection mode (which wasn’t there in FontStudio) |
|---|
| 23 | using the selected glyphs as a base to generate the glyph sequence. |
|---|
| 24 | |
|---|
| 25 | Thanks to Claudio Piccinini <http://www.tht.it/> for showing me his |
|---|
| 26 | (working!) copy of FontStudio with this neat feature. |
|---|
| 27 | """ |
|---|
| 28 | |
|---|
| 29 | import string |
|---|
| 30 | |
|---|
| 31 | from FL import * |
|---|
| 32 | |
|---|
| 33 | __author__ = "Antonio Cavedoni <http://cavedoni.com/>" |
|---|
| 34 | __version__ = "0.2" |
|---|
| 35 | __svnid__ = "$Id$" |
|---|
| 36 | __license__ = "Python" |
|---|
| 37 | __contributors__ = [ |
|---|
| 38 | "Claudio Piccinini <http://www.tht.it/>", |
|---|
| 39 | "Karsten Luecke <http://www.kltf.de/>" |
|---|
| 40 | ] |
|---|
| 41 | |
|---|
| 42 | UPPERCASE = [x for x in string.uppercase] |
|---|
| 43 | LOWERCASE = [x for x in string.lowercase] |
|---|
| 44 | DIGITS = "zero one two three four five six seven eight nine".split() |
|---|
| 45 | |
|---|
| 46 | class AutomaticTextDialog: |
|---|
| 47 | def __init__(self): |
|---|
| 48 | self.d = Dialog(self) |
|---|
| 49 | self.d.size = Point(320, 280) |
|---|
| 50 | self.d.Center() |
|---|
| 51 | self.d.title = 'Automatic Text...' |
|---|
| 52 | self.d.AddControl(CHECKBOXCONTROL, Rect(15, 10, 255, 40), |
|---|
| 53 | 'generateall', STYLE_CHECKBOX, |
|---|
| 54 | 'Generate all characters') |
|---|
| 55 | self.d.AddControl(CHECKBOXCONTROL, Rect(15, 60, 100, 80), |
|---|
| 56 | 'generatebeside', STYLE_CHECKBOX, 'Generate') |
|---|
| 57 | self.d.AddControl(EDITCONTROL, Rect(100, 58, 170, 84), |
|---|
| 58 | 'letter', STYLE_EDIT, '') |
|---|
| 59 | self.d.AddControl(STATICCONTROL, Rect(180, 62, 280, 86), |
|---|
| 60 | 'label0', STYLE_LABEL, 'beside:') |
|---|
| 61 | self.d.AddControl(CHECKBOXCONTROL, Rect(50, 90, 160, 120), |
|---|
| 62 | 'lowercase', STYLE_CHECKBOX, |
|---|
| 63 | 'a through z') |
|---|
| 64 | self.d.AddControl(CHECKBOXCONTROL, Rect(180, 90, 310, 120), |
|---|
| 65 | 'all_characters', STYLE_CHECKBOX, 'All characters') |
|---|
| 66 | self.d.AddControl(CHECKBOXCONTROL, Rect(50, 120, 160, 150), |
|---|
| 67 | 'uppercase', STYLE_CHECKBOX, 'A through Z') |
|---|
| 68 | self.d.AddControl(CHECKBOXCONTROL, Rect(180, 120, 310, 150), |
|---|
| 69 | 'numbers', STYLE_CHECKBOX, '0 through 9') |
|---|
| 70 | self.d.AddControl(CHECKBOXCONTROL, Rect(50, 150, 80, 180), |
|---|
| 71 | 'other', STYLE_CHECKBOX, '') |
|---|
| 72 | self.d.AddControl(EDITCONTROL, Rect(70, 152, 140, 178), |
|---|
| 73 | 'other_from', STYLE_EDIT, '') |
|---|
| 74 | self.d.AddControl(STATICCONTROL, Rect(150, 158, 210, 178), |
|---|
| 75 | 'label1', STYLE_LABEL, 'through') |
|---|
| 76 | self.d.AddControl(EDITCONTROL, Rect(210, 152, 285, 178), |
|---|
| 77 | 'other_to', STYLE_EDIT, '') |
|---|
| 78 | self.d.AddControl(CHECKBOXCONTROL, Rect(50, 185, 285, 210), |
|---|
| 79 | 'selection', STYLE_CHECKBOX, |
|---|
| 80 | 'Selected glyphs in font window') |
|---|
| 81 | |
|---|
| 82 | self.generateall = 0 |
|---|
| 83 | self.generatebeside = 1 |
|---|
| 84 | self.letter = '' |
|---|
| 85 | self.lowercase = 1 |
|---|
| 86 | self.all_characters = 0 |
|---|
| 87 | self.uppercase = 0 |
|---|
| 88 | self.numbers = 0 |
|---|
| 89 | self.other = 0 |
|---|
| 90 | self.other_from = '' |
|---|
| 91 | self.other_to = '' |
|---|
| 92 | self.selection = 0 |
|---|
| 93 | |
|---|
| 94 | if fl.count_selected == 1: |
|---|
| 95 | try: |
|---|
| 96 | self.letter = "/" + fl.glyph.name |
|---|
| 97 | except AttributeError: |
|---|
| 98 | pass |
|---|
| 99 | elif fl.count_selected > 1: |
|---|
| 100 | self.lowercase = 0 |
|---|
| 101 | self.other = 0 |
|---|
| 102 | self.selection = 1 |
|---|
| 103 | |
|---|
| 104 | self.generates = ['generateall', 'generatebeside'] |
|---|
| 105 | self.besides = ['all_characters', 'lowercase', 'uppercase', |
|---|
| 106 | 'numbers', 'other', 'selection'] |
|---|
| 107 | |
|---|
| 108 | def check_control_in_list(self, control, controls): |
|---|
| 109 | for c in controls: |
|---|
| 110 | if c == control: |
|---|
| 111 | setattr(self, c, 1) |
|---|
| 112 | else: |
|---|
| 113 | setattr(self, c, 0) |
|---|
| 114 | self.d.PutValue(c) |
|---|
| 115 | return control |
|---|
| 116 | |
|---|
| 117 | def on_generateall(self, code): |
|---|
| 118 | self.d.GetValue('generateall') |
|---|
| 119 | self.check_control_in_list('generateall', self.generates + self.besides) |
|---|
| 120 | |
|---|
| 121 | def on_generatebeside(self, code): |
|---|
| 122 | self.d.GetValue('generatebeside') |
|---|
| 123 | self.check_control_in_list('generatebeside', self.generates) |
|---|
| 124 | |
|---|
| 125 | def on_letter(self, code): |
|---|
| 126 | self.d.GetValue('letter') |
|---|
| 127 | |
|---|
| 128 | def on_other_from(self, code): |
|---|
| 129 | self.d.GetValue('other_from') |
|---|
| 130 | |
|---|
| 131 | def on_other_to(self, code): |
|---|
| 132 | self.d.GetValue('other_to') |
|---|
| 133 | |
|---|
| 134 | def on_selection(self, code): |
|---|
| 135 | self.d.GetValue('selection') |
|---|
| 136 | self.check_control_in_list('generatebeside', self.generates) |
|---|
| 137 | self.check_control_in_list('selection', self.besides) |
|---|
| 138 | |
|---|
| 139 | def on_all_characters(self, code): |
|---|
| 140 | self.d.GetValue('all_characters') |
|---|
| 141 | self.check_control_in_list('generatebeside', self.generates) |
|---|
| 142 | self.check_control_in_list('all_characters', self.besides) |
|---|
| 143 | |
|---|
| 144 | def on_numbers(self, code): |
|---|
| 145 | self.d.GetValue('numbers') |
|---|
| 146 | self.check_control_in_list('generatebeside', self.generates) |
|---|
| 147 | self.check_control_in_list('numbers', self.besides) |
|---|
| 148 | |
|---|
| 149 | def on_uppercase(self, code): |
|---|
| 150 | self.d.GetValue('uppercase') |
|---|
| 151 | self.check_control_in_list('generatebeside', self.generates) |
|---|
| 152 | self.check_control_in_list('uppercase', self.besides) |
|---|
| 153 | |
|---|
| 154 | def on_lowercase(self, code): |
|---|
| 155 | self.d.GetValue('lowercase') |
|---|
| 156 | self.check_control_in_list('generatebeside', self.generates) |
|---|
| 157 | self.check_control_in_list('lowercase', self.besides) |
|---|
| 158 | |
|---|
| 159 | def on_other(self, code): |
|---|
| 160 | self.d.GetValue('other') |
|---|
| 161 | self.check_control_in_list('generatebeside', self.generates) |
|---|
| 162 | self.check_control_in_list('other', self.besides) |
|---|
| 163 | |
|---|
| 164 | def on_ok(self, code): |
|---|
| 165 | return 1 |
|---|
| 166 | |
|---|
| 167 | def Run(self): |
|---|
| 168 | return self.d.Run() |
|---|
| 169 | |
|---|
| 170 | def get_all_glyphs(): |
|---|
| 171 | # generate a temp font so we can sort its glyphs |
|---|
| 172 | # by Unicode name without damaging the original |
|---|
| 173 | tempfont = Font(fl.font) |
|---|
| 174 | fl.Add(tempfont) |
|---|
| 175 | tempfontid = fl.ifont |
|---|
| 176 | fl.CallCommand(fl_cmd.FontSortByUnicode) |
|---|
| 177 | |
|---|
| 178 | # build a string with all the characters |
|---|
| 179 | glyphs = [] |
|---|
| 180 | for i in range(len(tempfont)): |
|---|
| 181 | n = fl.font[i].name |
|---|
| 182 | if n not in ['.notdef', '.null', 'CR']: |
|---|
| 183 | glyphs.append(get_glyph_name(fl.font[i].name)) |
|---|
| 184 | |
|---|
| 185 | # close the temp font |
|---|
| 186 | tempfont.modified = 0 |
|---|
| 187 | fl.Close(tempfontid) |
|---|
| 188 | |
|---|
| 189 | return glyphs |
|---|
| 190 | |
|---|
| 191 | def map_digit_to_name(digit): |
|---|
| 192 | if digit in [str(x) for x in string.digits]: |
|---|
| 193 | return DIGITS[int(digit)] |
|---|
| 194 | else: |
|---|
| 195 | return digit |
|---|
| 196 | |
|---|
| 197 | def get_glyph_name(glyph): |
|---|
| 198 | glyph = map_digit_to_name(glyph) |
|---|
| 199 | if not glyph.startswith('/'): |
|---|
| 200 | return '/%s' % glyph |
|---|
| 201 | else: |
|---|
| 202 | return glyph |
|---|
| 203 | |
|---|
| 204 | def get_selection(): |
|---|
| 205 | selected = [] |
|---|
| 206 | for i in range(len(fl.font)): |
|---|
| 207 | if fl.Selected(fl.font[i].index): |
|---|
| 208 | try: |
|---|
| 209 | selected.append(get_glyph_name(fl.font[i].name)) |
|---|
| 210 | except AttributeError: |
|---|
| 211 | pass |
|---|
| 212 | return selected |
|---|
| 213 | |
|---|
| 214 | def generate(glyph_set, glyph_name=None): |
|---|
| 215 | # open metrics window |
|---|
| 216 | fl.CallCommand(fl_cmd.WindowNewMetrics) |
|---|
| 217 | |
|---|
| 218 | # normalise the glyph names |
|---|
| 219 | glyph_set = [get_glyph_name(x) for x in glyph_set] |
|---|
| 220 | |
|---|
| 221 | # write to the metrics preview text |
|---|
| 222 | if not glyph_name: |
|---|
| 223 | fl.metricspreview = "".join(glyph_set) |
|---|
| 224 | else: |
|---|
| 225 | fl.metricspreview = \ |
|---|
| 226 | glyph_name + glyph_name.join(glyph_set) + glyph_name |
|---|
| 227 | |
|---|
| 228 | def generate_beside_two(glyph_name, glyph_from, glyph_to): |
|---|
| 229 | glyphset = [] |
|---|
| 230 | in_sequence = False |
|---|
| 231 | for i in range(len(fl.font)): |
|---|
| 232 | if not in_sequence: |
|---|
| 233 | if fl.font[i].name == map_digit_to_name(glyph_from): |
|---|
| 234 | glyphset.append(get_glyph_name(fl.font[i].name)) |
|---|
| 235 | in_sequence = True |
|---|
| 236 | else: |
|---|
| 237 | glyphset.append(get_glyph_name(fl.font[i].name)) |
|---|
| 238 | if fl.font[i].name == map_digit_to_name(glyph_to): |
|---|
| 239 | break |
|---|
| 240 | generate(glyphset, get_glyph_name(glyph_name)) |
|---|
| 241 | |
|---|
| 242 | d = AutomaticTextDialog() |
|---|
| 243 | |
|---|
| 244 | if d.Run() == 1: |
|---|
| 245 | if d.generateall: |
|---|
| 246 | generate(get_all_glyphs()) |
|---|
| 247 | |
|---|
| 248 | elif d.generatebeside: |
|---|
| 249 | char = get_glyph_name(d.letter) |
|---|
| 250 | |
|---|
| 251 | if d.all_characters: |
|---|
| 252 | generate(get_all_glyphs(), char) |
|---|
| 253 | elif d.uppercase: |
|---|
| 254 | generate(UPPERCASE, char) |
|---|
| 255 | elif d.lowercase: |
|---|
| 256 | generate(LOWERCASE, char) |
|---|
| 257 | elif d.numbers: |
|---|
| 258 | generate(DIGITS, char) |
|---|
| 259 | elif d.other: |
|---|
| 260 | generate_beside_two(d.letter, d.other_from, d.other_to) |
|---|
| 261 | elif d.selection: |
|---|
| 262 | generate(get_selection(), char) |
|---|