├── .gitignore
├── CHANGELOG.md
├── DEFINITIONS.md
├── FilterListManager.glyphsPlugin
└── Contents
│ ├── Info.plist
│ ├── MacOS
│ └── plugin
│ └── Resources
│ ├── CustomFilter.plist
│ └── plugin.py
├── LICENSE
├── README.md
├── TOOLS.md
└── tools
├── exportfilters.py
├── glformatter.py
└── glinter.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
106 | # PyCharm files
107 | .idea
108 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Changelog
2 |
3 | ### 0.6.0
4 |
5 | - updated for new Glyphs application plugin format
6 | - Python 3 support
7 |
8 | ### 0.5.0
9 |
10 | - German menu item localization (thanks JM!)
11 |
12 | ### 0.4.1
13 |
14 | - removed undefined German string localizations for Edit menu items
15 |
16 | ### 0.4.0
17 |
18 | - initial release
19 | - added automated backup of user's CustomFilters.plist file at the time of plugin install to prevent loss of custom filters
20 | - added exception handling for local/remote definition file parsing
21 | - refactored directory and file paths to module level constants
22 | - eliminated unnecessary variable definitions
23 | - added url decoding for filter list name parsing from remote file names
24 | - added documentation
25 |
26 | See repository releases for pre-release changelog documentation prior to the v0.4.0 release
27 |
--------------------------------------------------------------------------------
/DEFINITIONS.md:
--------------------------------------------------------------------------------
1 | ## Filter List Definition File Spec
2 |
3 | The filter list definition file is a text file that is formatted with a newline-delimited list of glyph names to include in the new Glyphs filter list. Each definition file defines a single filter list. You can use one or more filter list definition files simultaneously during updates with the FLM plugin and these settings files can be stored on your local macOS system or on a remote web server that is accessible through HTTP GET requests.
4 |
5 | ### Filter list name
6 |
7 | Define the filter list name in the base name of the definition file. You may include spaces, hyphens, and parentheses in these names.
8 |
9 | The following are examples of how filenames are mapped to filter list names in the Glyphs application user interface:
10 |
11 | - `Esperanto.txt` = `Esperanto`
12 | - `Basic Latin.txt` = `Basic Latin`
13 | - `MES-1.txt` = `MES-1`
14 | - `Serbian (Latin).txt` = `Serbian (Latin)`
15 |
16 | The plugin provides support for empty lines in the body of the text file to improve readability and organization of the document. Use as many empty lines as you need in the definition file to format it however it is most helpful for sharing and maintenance of the definitions.
17 |
18 | ### Glyph names
19 |
20 | Format your filter list definition file with one glyph name per line. This is called newline-delimited format. Use either Unicode hexadecimal code point style names (e.g, `uni00F6`) or AGL-style nice names (e.g., `odieresis`) as supported by the Glyphs application.
21 |
22 | ### Comment line support
23 |
24 | Comment lines (i.e., lines in the definition file that include text that is not intended for use as part of the glyph name list) are indicated with either `#` or `//` at the beginning of the line. Include one of these two comment indicators at the beginning of each line that is intended to be a comment line in your definition file document. It is not mandatory to use comment indicators on empty lines that are used to format your document (e.g., empty lines between glyph name definitions, see example below).
25 |
26 | Note: Comment indicators can be added to the start of one or more lines that includes glyph names in order to exclude these lines from the list of glyphs used in a filter. This can be helpful if you need to make periodic modifications to sets of glyph names in a given list. Simply place a `#` or `//` at the start of the glyph name line to exclude it from the filter list definition.
27 |
28 | ### Example
29 |
30 | The following is an example of a valid filter list definition file that includes comment and empty line formatting:
31 |
32 | ##### Filename: `Esperanto.txt`
33 |
34 | ```
35 | // -------------------------------------------------
36 | // Esperanto Glyphs filter list definition file
37 | // Copyright 2018, Some Person
38 | // MIT License
39 | // -------------------------------------------------
40 |
41 | # U+0108
42 | Ccircumflex
43 |
44 | # U+0109
45 | ccircumflex
46 |
47 | # U+011C
48 | Gcircumflex
49 |
50 | # U+011D
51 | gcircumflex
52 |
53 | # U+0124
54 | Hcircumflex
55 |
56 | # U+0125
57 | hcircumflex
58 |
59 | # U+0134
60 | Jcircumflex
61 |
62 | # U+0135
63 | jcircumflex
64 |
65 | # U+015C
66 | Scircumflex
67 |
68 | # U+015D
69 | scircumflex
70 |
71 | # U+016C
72 | Ubreve
73 |
74 | # U+016D
75 | ubreve
76 |
77 | ```
78 |
79 | The same definition file could be written in a much more concise format like this:
80 |
81 | ##### Filename: `Esperanto.txt`
82 |
83 | ```
84 | Ccircumflex
85 | ccircumflex
86 | Gcircumflex
87 | gcircumflex
88 | Hcircumflex
89 | hcircumflex
90 | Jcircumflex
91 | jcircumflex
92 | Scircumflex
93 | scircumflex
94 | Ubreve
95 | ubreve
96 | ```
97 |
98 | The filter list that is created in the Glyphs application does not differ between the two definition file examples above. Use the approach that works best for you.
99 |
--------------------------------------------------------------------------------
/FilterListManager.glyphsPlugin/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | plugin
9 | CFBundleIdentifier
10 | org.sourcefoundry.FilterListManager
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | FilterListManager
15 | CFBundleShortVersionString
16 | 0.5.1
17 | CFBundleVersion
18 | 51
19 | UpdateFeedURL
20 | https://raw.githubusercontent.com/source-foundry/FilterListManager/master/FilterListManager.glyphsPlugin/Contents/Info.plist
21 | productPageURL
22 | https://github.com/source-foundry/FilterListManager
23 | productReleaseNotes
24 | Changelog: https://github.com/source-foundry/FilterListManager/blob/master/CHANGELOG.md
25 | NSHumanReadableCopyright
26 | Copyright 2018 Christopher Simpkins, Apache License 2.0
27 | NSPrincipalClass
28 | FilterListManager
29 |
30 |
31 |
--------------------------------------------------------------------------------
/FilterListManager.glyphsPlugin/Contents/MacOS/plugin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-foundry/FilterListManager/c3c50fd2c0da2b51140804bf3c5f523465049c26/FilterListManager.glyphsPlugin/Contents/MacOS/plugin
--------------------------------------------------------------------------------
/FilterListManager.glyphsPlugin/Contents/Resources/CustomFilter.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | name
7 | Incompatible Master
8 | predicate
9 | mastersCompatible == 0
10 |
11 |
12 | list
13 |
14 | Eth
15 | eth
16 | Lslash
17 | lslash
18 | Scaron
19 | scaron
20 | Yacute
21 | yacute
22 | Thorn
23 | thorn
24 | Zcaron
25 | zcaron
26 | onehalf
27 | onequarter
28 | onesuperior
29 | threequarters
30 | threesuperior
31 | twosuperior
32 | brokenbar
33 | minus
34 | multiply
35 | space
36 | exclam
37 | quotedbl
38 | numbersign
39 | dollar
40 | percent
41 | ampersand
42 | quotesingle
43 | parenleft
44 | parenright
45 | asterisk
46 | plus
47 | comma
48 | hyphen
49 | period
50 | slash
51 | zero
52 | one
53 | two
54 | three
55 | four
56 | five
57 | six
58 | seven
59 | eight
60 | nine
61 | colon
62 | semicolon
63 | less
64 | equal
65 | greater
66 | question
67 | at
68 | A
69 | B
70 | C
71 | D
72 | E
73 | F
74 | G
75 | H
76 | I
77 | J
78 | K
79 | L
80 | M
81 | N
82 | O
83 | P
84 | Q
85 | R
86 | S
87 | T
88 | U
89 | V
90 | W
91 | X
92 | Y
93 | Z
94 | bracketleft
95 | backslash
96 | bracketright
97 | asciicircum
98 | underscore
99 | grave
100 | a
101 | b
102 | c
103 | d
104 | e
105 | f
106 | g
107 | h
108 | i
109 | j
110 | k
111 | l
112 | m
113 | n
114 | o
115 | p
116 | q
117 | r
118 | s
119 | t
120 | u
121 | v
122 | w
123 | x
124 | y
125 | z
126 | braceleft
127 | bar
128 | braceright
129 | asciitilde
130 | Adieresis
131 | Aring
132 | Ccedilla
133 | Eacute
134 | Ntilde
135 | Odieresis
136 | Udieresis
137 | aacute
138 | agrave
139 | acircumflex
140 | adieresis
141 | atilde
142 | aring
143 | ccedilla
144 | eacute
145 | egrave
146 | ecircumflex
147 | edieresis
148 | iacute
149 | igrave
150 | icircumflex
151 | idieresis
152 | ntilde
153 | oacute
154 | ograve
155 | ocircumflex
156 | odieresis
157 | otilde
158 | uacute
159 | ugrave
160 | ucircumflex
161 | udieresis
162 | dagger
163 | degree
164 | cent
165 | sterling
166 | section
167 | bullet
168 | paragraph
169 | germandbls
170 | registered
171 | copyright
172 | trademark
173 | acute
174 | dieresis
175 | notequal
176 | AE
177 | Oslash
178 | infinity
179 | plusminus
180 | lessequal
181 | greaterequal
182 | yen
183 | micro
184 | partialdiff
185 | summation
186 | product
187 | pi
188 | integral
189 | ordfeminine
190 | ordmasculine
191 | Omega
192 | ae
193 | oslash
194 | questiondown
195 | exclamdown
196 | logicalnot
197 | radical
198 | florin
199 | approxequal
200 | Delta
201 | guillemetleft
202 | guillemetright
203 | ellipsis
204 | Agrave
205 | Atilde
206 | Otilde
207 | OE
208 | oe
209 | endash
210 | emdash
211 | quotedblleft
212 | quotedblright
213 | quoteleft
214 | quoteright
215 | divide
216 | lozenge
217 | ydieresis
218 | Ydieresis
219 | fraction
220 | euro
221 | guilsinglleft
222 | guilsinglright
223 | fi
224 | fl
225 | daggerdbl
226 | periodcentered
227 | quotesinglbase
228 | quotedblbase
229 | perthousand
230 | Acircumflex
231 | Ecircumflex
232 | Aacute
233 | Edieresis
234 | Egrave
235 | Iacute
236 | Icircumflex
237 | Idieresis
238 | Igrave
239 | Oacute
240 | Ocircumflex
241 | Ograve
242 | Uacute
243 | Ucircumflex
244 | Ugrave
245 | idotless
246 | circumflex
247 | tilde
248 | macron
249 | breve
250 | dotaccent
251 | ring
252 | cedilla
253 | hungarumlaut
254 | ogonek
255 | caron
256 |
257 | name
258 | Mac Roman
259 |
260 |
261 | list
262 |
263 | space
264 | exclam
265 | quotedbl
266 | numbersign
267 | dollar
268 | percent
269 | ampersand
270 | quotesingle
271 | parenleft
272 | parenright
273 | asterisk
274 | plus
275 | comma
276 | hyphen
277 | period
278 | slash
279 | zero
280 | one
281 | two
282 | three
283 | four
284 | five
285 | six
286 | seven
287 | eight
288 | nine
289 | colon
290 | semicolon
291 | less
292 | equal
293 | greater
294 | question
295 | at
296 | A
297 | B
298 | C
299 | D
300 | E
301 | F
302 | G
303 | H
304 | I
305 | J
306 | K
307 | L
308 | M
309 | N
310 | O
311 | P
312 | Q
313 | R
314 | S
315 | T
316 | U
317 | V
318 | W
319 | X
320 | Y
321 | Z
322 | bracketleft
323 | backslash
324 | bracketright
325 | asciicircum
326 | underscore
327 | grave
328 | a
329 | b
330 | c
331 | d
332 | e
333 | f
334 | g
335 | h
336 | i
337 | j
338 | k
339 | l
340 | m
341 | n
342 | o
343 | p
344 | q
345 | r
346 | s
347 | t
348 | u
349 | v
350 | w
351 | x
352 | y
353 | z
354 | braceleft
355 | bar
356 | braceright
357 | asciitilde
358 | euro
359 | quotesinglbase
360 | florin
361 | quotedblbase
362 | ellipsis
363 | dagger
364 | daggerdbl
365 | circumflex
366 | perthousand
367 | Scaron
368 | guilsinglleft
369 | OE
370 | Zcaron
371 | quoteleft
372 | quoteright
373 | quotedblleft
374 | quotedblright
375 | bullet
376 | endash
377 | emdash
378 | tilde
379 | trademark
380 | scaron
381 | guilsinglright
382 | oe
383 | zcaron
384 | Ydieresis
385 | exclamdown
386 | cent
387 | sterling
388 | currency
389 | yen
390 | brokenbar
391 | section
392 | dieresis
393 | copyright
394 | ordfeminine
395 | guillemetleft
396 | logicalnot
397 | softhyphen
398 | registered
399 | macron
400 | degree
401 | plusminus
402 | twosuperior
403 | threesuperior
404 | acute
405 | mu
406 | paragraph
407 | periodcentered
408 | cedilla
409 | onesuperior
410 | ordmasculine
411 | guillemetright
412 | onequarter
413 | onehalf
414 | threequarters
415 | questiondown
416 | Agrave
417 | Aacute
418 | Acircumflex
419 | Atilde
420 | Adieresis
421 | Aring
422 | AE
423 | Ccedilla
424 | Egrave
425 | Eacute
426 | Ecircumflex
427 | Edieresis
428 | Igrave
429 | Iacute
430 | Icircumflex
431 | Idieresis
432 | Eth
433 | Ntilde
434 | Ograve
435 | Oacute
436 | Ocircumflex
437 | Otilde
438 | Odieresis
439 | multiply
440 | Oslash
441 | Ugrave
442 | Uacute
443 | Ucircumflex
444 | Udieresis
445 | Yacute
446 | Thorn
447 | germandbls
448 | agrave
449 | aacute
450 | acircumflex
451 | atilde
452 | adieresis
453 | aring
454 | ae
455 | ccedilla
456 | egrave
457 | eacute
458 | ecircumflex
459 | edieresis
460 | igrave
461 | iacute
462 | icircumflex
463 | idieresis
464 | eth
465 | ntilde
466 | ograve
467 | oacute
468 | ocircumflex
469 | otilde
470 | odieresis
471 | divide
472 | oslash
473 | ugrave
474 | uacute
475 | ucircumflex
476 | udieresis
477 | yacute
478 | thorn
479 | ydieresis
480 |
481 | name
482 | Windows 1252
483 |
484 |
485 | list
486 |
487 | .notdef
488 | NULL
489 | CR
490 | space
491 | exclam
492 | quotedbl
493 | numbersign
494 | dollar
495 | percent
496 | ampersand
497 | quotesingle
498 | parenleft
499 | parenright
500 | asterisk
501 | plus
502 | comma
503 | hyphen
504 | period
505 | slash
506 | zero
507 | one
508 | two
509 | three
510 | four
511 | five
512 | six
513 | seven
514 | eight
515 | nine
516 | colon
517 | semicolon
518 | less
519 | equal
520 | greater
521 | question
522 | at
523 | A
524 | B
525 | C
526 | D
527 | E
528 | F
529 | G
530 | H
531 | I
532 | J
533 | K
534 | L
535 | M
536 | N
537 | O
538 | P
539 | Q
540 | R
541 | S
542 | T
543 | U
544 | V
545 | W
546 | X
547 | Y
548 | Z
549 | bracketleft
550 | backslash
551 | bracketright
552 | asciicircum
553 | underscore
554 | grave
555 | a
556 | b
557 | c
558 | d
559 | e
560 | f
561 | g
562 | h
563 | i
564 | j
565 | k
566 | l
567 | m
568 | n
569 | o
570 | p
571 | q
572 | r
573 | s
574 | t
575 | u
576 | v
577 | w
578 | x
579 | y
580 | z
581 | braceleft
582 | bar
583 | braceright
584 | asciitilde
585 |
586 | name
587 | ASCII
588 |
589 |
590 |
591 |
--------------------------------------------------------------------------------
/FilterListManager.glyphsPlugin/Contents/Resources/plugin.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | from __future__ import division, print_function, unicode_literals
3 |
4 | ###########################################################################################################
5 | #
6 | #
7 | # Filter List Manager
8 | # A plugin for the Glyphs font editor
9 | # Copyright 2018 Christopher Simpkins
10 | # Apache License 2.0
11 | #
12 | #
13 | ###########################################################################################################
14 |
15 | import logging
16 | import objc
17 | import os
18 | import plistlib
19 | import shutil
20 | import subprocess
21 | try:
22 | import urllib2 as urllibrequest
23 | import urlparse as urllibparse
24 | except:
25 | import urllib.request as urllibrequest
26 | import urllib.parse as urllibparse
27 |
28 | from GlyphsApp import *
29 | from GlyphsApp.plugins import *
30 |
31 | # -----------------
32 | # Path definitions
33 | # -----------------
34 | # Glyphs application paths
35 | GLYPHS_PLIST_FILE = os.path.join(
36 | os.path.expanduser("~"),
37 | "Library",
38 | "Application Support",
39 | "Glyphs",
40 | "CustomFilter.plist",
41 | )
42 |
43 |
44 | # FLM plugin paths
45 | FLM_GLYPHSFILTERS_DIR = os.path.join(os.path.expanduser("~"), "GlyphsFilters")
46 | FLM_BACKUP_DIR = os.path.join(os.path.expanduser("~"), "GlyphsFilters", "backup")
47 | FLM_BACKUP_ORIGINAL_FILE = os.path.join(FLM_BACKUP_DIR, "CustomFilters.plist.original")
48 | FLM_BACKUP_PREVIOUS_FILE = os.path.join(FLM_BACKUP_DIR, "CustomFilters.plist")
49 | FLM_DEFAULT_PLIST = os.path.join(
50 | os.path.expanduser("~"),
51 | "Library",
52 | "Application Support",
53 | "Glyphs",
54 | "Plugins",
55 | "FilterListManager.glyphsPlugin",
56 | "Contents",
57 | "Resources",
58 | "CustomFilter.plist",
59 | )
60 | FLM_LOG_DIR = os.path.join(os.path.expanduser("~"), "GlyphsFilters", "logs")
61 | FLM_LOG_FILE = os.path.join(FLM_LOG_DIR, "flm.log")
62 | FLM_REMOTE_DEF_FILE = os.path.join(
63 | os.path.expanduser("~"), "GlyphsFilters", "remote", "defs.txt"
64 | )
65 |
66 | # ---------------
67 | # Logging setup
68 | # ---------------
69 | if not os.path.isdir(FLM_LOG_DIR):
70 | os.makedirs(FLM_LOG_DIR)
71 | logging.basicConfig(
72 | filename=FLM_LOG_FILE,
73 | filemode="w",
74 | format="%(asctime)s %(levelname)s %(message)s",
75 | datefmt="%m/%d/%Y %I:%M:%S %p",
76 | level=logging.DEBUG,
77 | )
78 |
79 | # ----------------------------
80 | # CustomFilters.plist backup
81 | # ----------------------------
82 | # Backup CustomFilters.plist file present at the time of
83 | # plugin installation (to avoid permanent elimination of
84 | # custom filters that user created before plugin use)
85 | try:
86 | if not os.path.isfile(FLM_BACKUP_ORIGINAL_FILE) and os.path.isfile(GLYPHS_PLIST_FILE):
87 | if not os.path.isdir(FLM_BACKUP_DIR):
88 | os.makedirs(FLM_BACKUP_DIR)
89 | shutil.copy(GLYPHS_PLIST_FILE, FLM_BACKUP_ORIGINAL_FILE)
90 | except:
91 | pass
92 |
93 | class FilterListManager(GeneralPlugin):
94 | @objc.python_method
95 | def settings(self):
96 | self.update_name = Glyphs.localize(
97 | {
98 | "en": "Update Filter Lists",
99 | "de": "Filterlisten aktualisieren"
100 | }
101 | )
102 | self.restoredefault_name = Glyphs.localize(
103 | {
104 | "en": "Restore Default Filter Lists",
105 | "de": "Standard-Filterlisten wiederherstellen",
106 | }
107 | )
108 | self.opendir_name = Glyphs.localize(
109 | {
110 | "en": "Open GlyphsFilters Directory",
111 | "de": "Verzeichnis GlyphsFilters öffnen",
112 | }
113 | )
114 |
115 | @objc.python_method
116 | def start(self):
117 | try:
118 | # new API in Glyphs 2.3.1-910
119 | new_update_menu_item = NSMenuItem(self.update_name, self.updateFilters_)
120 | new_restore_menu_item = NSMenuItem(self.restoredefault_name, self.restoreFilters_)
121 | new_opendir_menu_item = NSMenuItem(self.opendir_name, self.openGlyphsfiltersDirectory_)
122 | Glyphs.menu[EDIT_MENU].append(new_update_menu_item)
123 | Glyphs.menu[EDIT_MENU].append(new_restore_menu_item)
124 | Glyphs.menu[EDIT_MENU].append(new_opendir_menu_item)
125 | except Exception:
126 | main_menu = Glyphs.mainMenu()
127 | update_selector = objc.selector(self.updateFilters_, signature="v@:@")
128 | restore_selector = objc.selector(self.restoreFilters_, signature="v@:@")
129 | open_selector = objc.selector(
130 | self.openGlyphsfiltersDirectory_, signature="v@:@"
131 | )
132 | new_update_menu_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
133 | self.update_name, update_selector, ""
134 | )
135 | new_restore_menu_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
136 | self.restore_name, restore_selector, ""
137 | )
138 | new_open_menu_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
139 | self.opendir_name, open_selector, ""
140 | )
141 | new_update_menu_item.setTarget_(self)
142 | main_menu.itemWithTag_(5).submenu().addItem_(new_update_menu_item)
143 | new_restore_menu_item.setTarget_(self)
144 | main_menu.itemWithTag_(5).submenu().addItem_(new_restore_menu_item)
145 | new_open_menu_item.setTarget_(self)
146 | main_menu.itemWithTag_(5).submenu().addItem_(new_open_menu_item)
147 |
148 | def updateFilters_(self, sender):
149 | """Perform the list filter update"""
150 | # Expected filter definitions directory test
151 | if not self.filter_directory_is_present():
152 | Glyphs.showNotification(
153 | "Filter List Manager",
154 | "ERROR: Unable to locate ~/GlyphsFilters directory! See log.",
155 | )
156 | logging.error(
157 | "Unable to locate the ~/GlyphsFilters directory. This directory is mandatory for execution. Please create it!"
158 | )
159 | return 0
160 |
161 | # --------------------------------------------
162 | #
163 | # Backup original CustomFilters.plist file
164 | #
165 | # --------------------------------------------
166 |
167 | # read previous CustomFilters.plist definition file
168 | # - used to prepare backup
169 | # - used to define new definition file
170 | try:
171 | previous_plist_data = plistlib.readPlist(GLYPHS_PLIST_FILE)
172 | except Exception as e:
173 | Glyphs.showNotification(
174 | "Filter List Manager",
175 | "ERROR: Unable to read CustomFilters.plist file. See log.",
176 | )
177 | logging.error(
178 | "Unable to read the original CustomFilters.plist file in order to create a backup. Error: "
179 | + str(e)
180 | )
181 | return 1
182 |
183 | new_plist_list = [] # storage data structure for new plist file definitions
184 |
185 | # backup the existing CustomFilters.plist file in the
186 | # ~/GlyphsFilters/backup directory
187 | try:
188 | if os.path.exists(FLM_BACKUP_DIR):
189 | shutil.move(GLYPHS_PLIST_FILE, FLM_BACKUP_PREVIOUS_FILE)
190 | else:
191 | os.makedirs(FLM_BACKUP_DIR)
192 | shutil.move(GLYPHS_PLIST_FILE, FLM_BACKUP_PREVIOUS_FILE)
193 | except Exception as e:
194 | Glyphs.showNotification(
195 | "Filter List Manager",
196 | "ERROR: Unable to backup your CustomFilters.plist file. See log.",
197 | )
198 | logging.error(
199 | "Unable to backup your CustomFilters.plist file. Error: " + str(e)
200 | )
201 | return 1
202 |
203 | # -----------------------------------------------------
204 | #
205 | # Update CustomFilters.plist file with new definitions
206 | #
207 | # -----------------------------------------------------
208 |
209 | # parse local filter list definition files
210 | try:
211 | local_filter_definitions_list = self.get_local_filter_definitions_list()
212 | except Exception as e:
213 | Glyphs.showNotification(
214 | "Filter List Manager",
215 | "Failed to parse local definition files. See log.",
216 | )
217 | logging.error("Failed to parse local definitions files. Error: " + str(e))
218 | return 1
219 |
220 | # parse remote filter list definition files
221 | try:
222 | remote_filter_definitions_list = self.get_remote_filter_definitions_list()
223 | except Exception as e:
224 | Glyphs.showNotification(
225 | "Filter List Manager",
226 | "Failed to parse remote definition files. See log.",
227 | )
228 | logging.error("Failed to parse remote definitions files. Error: " + str(e))
229 | return 1
230 |
231 | plist_filter_definitions_list = []
232 |
233 | # create list with data structure that is formatted
234 | # so that it can be translated to a plist file on write
235 | for local_filter in local_filter_definitions_list:
236 | filter_dict = {}
237 | filter_dict["name"] = local_filter.name
238 | filter_dict["list"] = local_filter.list
239 | plist_filter_definitions_list.append(filter_dict)
240 | logging.info(
241 | "Found local definition file for the filter '" + local_filter.name + "'"
242 | )
243 |
244 | for remote_filter in remote_filter_definitions_list:
245 | filter_dict = {}
246 | filter_dict["name"] = remote_filter.name
247 | filter_dict["list"] = remote_filter.list
248 | plist_filter_definitions_list.append(filter_dict)
249 | logging.info(
250 | "Found remote definition file for the filter '"
251 | + remote_filter.name
252 | + "'"
253 | )
254 |
255 | # confirm that at least one definition was parsed from the definition files
256 | # if not, abort
257 | if (
258 | len(local_filter_definitions_list) == 0
259 | and len(remote_filter_definitions_list) == 0
260 | ):
261 | Glyphs.showNotification(
262 | "Filter List Manager",
263 | "Unable to identify new filter list definition files. No changes were made.",
264 | )
265 | logging.info(
266 | "Unable to identify new filter list definition files. There were no changes made to the filter list definitions."
267 | )
268 | return 0
269 |
270 | # filter out previous filter list contents in the CustomFilter.plist
271 | # file and keep previous non-filter list contents
272 | for previous_definition in previous_plist_data:
273 | if "list" in previous_definition:
274 | if "name" in previous_definition:
275 | logging.info(
276 | "Removing previously defined filter list '"
277 | + previous_definition["name"]
278 | + "'"
279 | )
280 | else:
281 | new_plist_list.append(previous_definition)
282 | if "name" in previous_definition:
283 | logging.info(
284 | "Saving previously defined CustomFilters.plist data with name '"
285 | + previous_definition["name"]
286 | + "'"
287 | )
288 |
289 | # add new data that was read from local and remote
290 | # definition files to a data format that translates
291 | # to a plist file
292 | for new_definition in plist_filter_definitions_list:
293 | new_plist_list.append(new_definition)
294 | if "name" in new_definition:
295 | logging.info("Adding new filter list '" + new_definition["name"] + "'")
296 |
297 | # write new CustomFilters.plist definition file to disk
298 | try:
299 | plistlib.writePlist(new_plist_list, GLYPHS_PLIST_FILE)
300 | logging.info("The new CustomFilter.plist file write was successful.")
301 | except Exception as e:
302 | Glyphs.showNotification(
303 | "Filter List Manager",
304 | "ERROR: Unable to write CustomFilters.plist file. See log.",
305 | )
306 | logging.error(
307 | "Unable to write new CustomFilters.plist file to disk. Error: " + str(e)
308 | )
309 | return 1
310 |
311 | Glyphs.showNotification(
312 | "Filter List Manager",
313 | "The filter list updates were successful. Please quit and restart Glyphs.",
314 | )
315 | logging.info(
316 | "The filter list updates were successful. Please quit and restart the Glyphs application to view the new filter lists."
317 | )
318 |
319 | def restoreFilters_(self, sender):
320 | """Perform restore of default list filters"""
321 | # copy the default definitions to the Glyphs application
322 | try:
323 | default_filters = plistlib.readPlist(FLM_DEFAULT_PLIST)
324 | plistlib.writePlist(default_filters, GLYPHS_PLIST_FILE)
325 | except Exception as e:
326 | Glyphs.showNotification(
327 | "Filter List Manager",
328 | "ERROR: Unable to restore the default filter list definitions. See log.",
329 | )
330 | logging.error(
331 | "Unable to restore default filter list definitions. Error: " + str(e)
332 | )
333 | return 1
334 |
335 | Glyphs.showNotification(
336 | "Filter List Manager",
337 | "The default filter list restoration was successful. Please quit and restart Glyphs.",
338 | )
339 | logging.info(
340 | "The default filter list restoration was successful. Please quit and restart the Glyphs application to view the filter lists."
341 | )
342 |
343 | def openGlyphsfiltersDirectory_(self, sender):
344 | """Called from a plugin Edit menu item and opens the ~/GlyphsFilters directory in the macOS Finder"""
345 | if not os.path.isdir(FLM_GLYPHSFILTERS_DIR):
346 | Glyphs.showNotification(
347 | "Filter List Manager",
348 | "Unable to find ~/GlyphsFilters directory. Please create this path.",
349 | )
350 | logging.error(
351 | "Unable to find the ~/GlyphsFilters directory. Please create this directory path."
352 | )
353 | else:
354 | subprocess.call(["open", FLM_GLYPHSFILTERS_DIR])
355 | logging.info(
356 | "The ~/GlyphsFilters directory was opened with the Edit menu item."
357 | )
358 |
359 | @objc.python_method
360 | def filter_directory_is_present(self):
361 | """Tests for presence of the ~/GlyphsFilters directory"""
362 | if not os.path.isdir(FLM_GLYPHSFILTERS_DIR):
363 | return False
364 | else:
365 | return True
366 |
367 | @objc.python_method
368 | def get_local_filter_definitions_list(self):
369 | """Reads and launches parsing of local definition files, returns a Python list of
370 | Filter objects that are created from the parse"""
371 | local_definitions_list = []
372 |
373 | if not os.path.isdir(FLM_GLYPHSFILTERS_DIR):
374 | # return an empty list if the directory is not found
375 | return []
376 |
377 | raw_definitions_file_list = [
378 | f
379 | for f in os.listdir(FLM_GLYPHSFILTERS_DIR)
380 | if os.path.isfile(os.path.join(FLM_GLYPHSFILTERS_DIR, f))
381 | ]
382 | definitions_file_list = []
383 | # filter list for dotfiles. This eliminates macOS .DS_Store files that lead to errors during processing
384 | for definition_file in raw_definitions_file_list:
385 | if definition_file[0] == ".":
386 | pass
387 | else:
388 | definitions_file_list.append(definition_file)
389 | for definition_file in definitions_file_list:
390 | definition_path_list = definition_file.split(".")
391 | # define the filter list name as the file name
392 | new_filter = Filter(definition_path_list[0])
393 | with open(os.path.join(FLM_GLYPHSFILTERS_DIR, definition_file)) as f:
394 | # define the filter object with the definitions in the text data
395 | text = f.read()
396 | new_filter.define_list_with_newline_delimited_text(text)
397 |
398 | local_definitions_list.append(new_filter)
399 |
400 | return local_definitions_list
401 |
402 | @objc.python_method
403 | def get_remote_filter_definitions_list(self):
404 | """Pulls, reads, and launches parsing of remote definition files, returns a Python list of
405 | Filter objects that are created from the parse"""
406 | remote_definitions_list = []
407 |
408 | if not os.path.isfile(FLM_REMOTE_DEF_FILE):
409 | return []
410 |
411 | with open(FLM_REMOTE_DEF_FILE) as f:
412 | for line in f:
413 | url = line.strip()
414 | if len(url) == 0:
415 | pass
416 | elif url[0] in ("#", "/"):
417 | pass
418 | else:
419 | # unquote URL defined in list to define file name
420 | # (in case the user pasted a urlencoded string)
421 | decoded_url = urllibparse.unquote(url)
422 | parsed_url = urllibparse.urlparse(decoded_url).path
423 | parsed_path = os.path.split(parsed_url)
424 | filter_defintion_filename = parsed_path[1]
425 | new_filter = Filter(filter_defintion_filename)
426 |
427 | # quote URL defined in list to make HTTP GET request
428 | response = urllibrequest.urlopen(url)
429 | text = response.read()
430 | new_filter.define_list_with_newline_delimited_text(text)
431 | remote_definitions_list.append(new_filter)
432 |
433 | return remote_definitions_list
434 |
435 | @objc.python_method
436 | def __file__(self):
437 | """Glyphs plugin API specific method. Please leave this method unchanged"""
438 | return __file__
439 |
440 |
441 | class Filter(object):
442 | """Filter is an object that maintains data elements for Glyphs application filter lists.
443 | It is instantiated with a new filter list name and list elements are defined with
444 | a class method"""
445 |
446 | def __init__(self, name):
447 | self.comment_delimiters = ("#", "/")
448 | self.name = name
449 | self.list = []
450 |
451 | def define_list_with_newline_delimited_text(self, text):
452 | """Filter class method that defines a Filter object with parsed data from newline-
453 | delimited text files of list elements"""
454 | raw_code_point_list = text.splitlines()
455 | filtered_code_point_list = []
456 | for item in raw_code_point_list:
457 | test_item = item.strip()
458 | if len(test_item) == 0: # discard blank lines in definition file
459 | pass
460 | elif (
461 | test_item[0] in self.comment_delimiters
462 | ): # discard comment lines in definition file
463 | pass
464 | else:
465 | filtered_code_point_list.append(test_item)
466 | self.list = filtered_code_point_list
467 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Filter List Manager (FLM)
2 |
3 | ## About
4 |
5 | Filter List Manager (FLM) is a free, open source plugin for the [Glyphs font editor](https://glyphsapp.com) that automates the creation of glyph filter lists with simple newline-delimited glyph definition text files. Definition files can be stored locally on your computer or remotely (e.g., in a Github repository) and used with the plugin.
6 |
7 | ## Quickstart Guide
8 |
9 | - Download the FLM plugin from the repository releases and install in the Glyphs application
10 | - Export your previously defined filter list definitions to FLM formatted definition files with [the instructions below](#please-read-before-use)
11 | - Create newline-delimited local filter list definition files in `~/GlyphsFilters` using the base filename of the definition file to name your filter list
12 | - (*Optional*) Push one or more new filter list definition files to a remote server (or locate definition files that someone else has pushed to a remote server) and create a `~/GlyphsFilters/remote/defs.txt` settings file to [define the URL to the remote filter list definition files](https://github.com/source-foundry/FilterListManager/tree/dev#remote-definition-files)
13 | - Select the Edit > Update Filter Lists menu item in the Glyphs editor
14 | - Close and restart Glyphs to view your new filter lists!
15 |
16 | ## Table of Contents
17 |
18 | - [Please Read Before Use](#please-read-before-use) (Really, please read it!)
19 | - [Plugin Installation, Upgrades, Removal](#plugin-installation)
20 | - [How to Make Filter List Definition Files](DEFINITIONS.md)
21 | - [Filter List Definition File Storage Locations](#filter-list-definition-file-storage-location)
22 | - [Plugin Usage](#plugin-usage)
23 | - [Tools for Filter List Management](TOOLS.md)
24 | - [Contributing](#contributing)
25 | - [License](LICENSE)
26 |
27 | ## Please Read Before Use!
28 |
29 | The first run of the plugin replaces all existing Glyphs font editor filter lists that you have previously defined with the filter lists that are defined using the definition files specified for the FLM plugin. A handy Python 3 script is available to export your existing filter list definitions to the FLM definition file format and should be used before you run a FLM update. Download the `exportfilters.py` script located in the `tools` directory of this repository and run the script with a Python 3 interpreter using the following command from any directory on your system:
30 |
31 | ```
32 | python3 exportfilters.py
33 | ```
34 |
35 | Confirm that all of your existing filters are defined in new filter list definition files that are exported to the directory `~/GlyphsFilters`, then proceed with use of the plugin to define additional filter lists and manage your previous filter lists.
36 |
37 | For those who don't read documentation, there is a backup of the original `CustomFilter.plist` file that was present on FLM install. This file contains all of the filter list definitions that were present before you ran the FLM plugin. You can find this backup on the path `~/GlyphsFilters/backup/CustomFilter.plist.original`. Rename the file to `CustomFilter.plist` and move it to the path `~/Library/Application Support/Glyphs/CustomFilter.plist` to restore your original filter list state, then use the documentation above to export the filters for use with this plugin.
38 |
39 | ## Plugin Installation
40 |
41 | 1. Download the [latest release archive](https://github.com/source-foundry/FilterListManager/releases) from the FLM repository Releases in either a *.zip or *.tar.gz compressed format
42 | 2. Unpack the downloaded archive file
43 | 3. Open the unpacked directory
44 | 4. Double-click the plugin `FilterListManager.glyphsPlugin` in the top level of the directory to open it with the Glyphs application
45 | 5. Acknowledge that you want to install FLM in the Glyphs Install Plugin dialog that appears
46 |
47 | ### Upgrade Plugin
48 |
49 | The `Preferences > Addons > Plugins` window in Glyphs will indicate when an update is available for the FLM plugin. To upgrade your installed plugin, locate the [latest release of the plugin](https://github.com/source-foundry/FilterListManager/releases/latest) and follow the same instructions that you followed for the initial installation above.
50 |
51 | Changes that were included in releases since your last upgrade are indicated in the repository [CHANGELOG.md](CHANGELOG.md).
52 |
53 | ### Uninstall Plugin
54 |
55 | Uninstall the FLM plugin by deleting the directory on the path `~/Library/Application Support/Glyphs/Plugins/FilterListManager.glyphsPlugin`. Next, delete the `~/GlyphsFilters` directory with the command:
56 |
57 | ```
58 | $ rm -rf ~/GlyphsFilters
59 | ```
60 |
61 | All filter lists that were previously defined with FLM will remain in your editor. If you wish to remove any previously defined filter lists, use the builtin functionality for filter list management in the Glyphs GUI. Removal of the FLM plugin does not limit use of previously defined filter lists. You can remove all components of the plugin and continue to use filter lists normally in the absence of the plugin and all settings files. There is no need to modify the builtin functionality for filter list management in Glyphs following an uninstall of the FLM plugin.
62 |
63 | ## How to Make a Filter List Definition File
64 |
65 | The filter list definition file is a newline-delimited text file that lists your desired glyph names. See [DEFINITIONS.md](DEFINITIONS.md) for detailed documentation of how you format the file and define both glyph list names and the glyph names that are included in the filter lists.
66 |
67 | ## Filter List Definition File Storage Location
68 |
69 | The filter list definition files that you use to define new filter lists can be stored on your local macOS system or on a remote server that is accessible through HTTP GET requests. One or more local and remote filter list definition files can be used simultaneously to define all desired Glyphs font editor filter lists with the FLM plugin.
70 |
71 | ### Local definition files
72 |
73 | Place the filter list definition files that you make in the top level of the directory `~/GlyphsFilters` on your macOS system. If this directory does not exist after you install the FLM plugin, open your terminal and enter the following command:
74 |
75 | ```
76 | $ mkdir ~/GlyphsFilters
77 | ```
78 |
79 | The GlyphsFilters directory can be opened by selecting the Edit > Open GlyphsFilters Directory menu item in the Glyphs application after you install the FLM plugin.
80 |
81 | Note: FLM does not search sub-directories of `~/GlyphsFilters` for local definition files so you can create one or more sub-directories to store definition files that are not in active use. Sub-directory names `logs`, `backup`, and `remote` are reserved for the plugin. Please do not overwrite these directory paths.
82 |
83 | ### Remote definition files
84 |
85 | You can upload filter list definition files to any publicly accessible web server or use files that have been shared by others with this approach. You must create one additional settings file in the `~/GlyphsFilters` directory to define one or more URL where the FLM plugin can locate the remotely stored definition files.
86 |
87 | Save a text file on the path `~/GlyphsFilters/remote/defs.txt` with a newline-delimited list of URL that point to one or more filter list definition files. Each URL represents a new filter list definition for the Glyphs application. You may use the comment line indicators `#` and `//` in the `~/GlyphsFilters/remote/defs.txt` file. Empty lines are also permitted (and ignored).
88 |
89 | An example of a valid `~/GlyphsFilters/remote/defs.txt` file that points to two filter list definition files located on a remote Github server follows:
90 |
91 | ```
92 | // Remote filter list definitions
93 |
94 | # Mac Roman filter list definitions
95 | https://raw.githubusercontent.com/source-foundry/charset-filters/master/Mac-Roman.txt
96 |
97 | # MES-1 filter list definitions
98 | https://raw.githubusercontent.com/source-foundry/charset-filters/master/MES-1.txt
99 | ```
100 |
101 | Please note in the above example that you must use the URL for the "Raw" text file if you push your definition files to Github. This is formatted as `https://raw.githubusercontent.com/[account]/[project name]/[branch]/[filename]`. When you enter this URL in your browser you should see only the text file with no Github website UI around it. If you see the Github UI in the browser window, the URL that you are viewing points to HTML text and this will lead to errors during the FLM filter list update attempt.
102 |
103 | ## Plugin Usage
104 |
105 | Following installation, you will find three new menu items under the Glyphs application Edit menu:
106 |
107 | - Update Filter Lists
108 | - Restore Default Filter Lists
109 | - Open GlyphsFilters Directory
110 |
111 | ### Update Filter Lists Menu Item
112 |
113 | Select the Update Filter Lists menu item to perform an update of your Glyphs filter list definitions using all local and remote filter list definition files that you define in your `~/GlyphsFilters` directory.
114 |
115 |
116 | ### Restore Default Filter Lists Menu Item
117 |
118 | Select the Restore Default Filter Lists menu item to restore a default set of filter lists that include the ASCII, Mac Roman, and Windows 1252 filters.
119 |
120 | ### Open GlyphsFilters Directory Menu Item
121 |
122 | Select the Open GlyphsFilters Directory menu item to open the `~/GlyphsFilters` directory in the macOS Finder. The `~/GlyphsFilters` directory is the location where you store local filter list definition files and remote definition files. This directory also includes application logs that can be used to explore what happened during processing or evaluate errors. Lastly, and importantly, the directory includes backups of the `CustomFilters.plist` definition file at the time of plugin install and just prior to the last FLM plugin filter list update run so that you can recover any lost data.
123 |
124 |
125 | ## Tools for Filter List Management
126 |
127 | The `tools` directory in the FLM source repository contains Python 3 scripts that assist with the creation and management of filter list definition files. Please see the [TOOLS.md](TOOLS.md) documentation for details.
128 |
129 | ## Contributing
130 |
131 | Contributions to the FLM plugin project are warmly welcomed. Fork the repository and submit a Github pull request to suggest modifications to the source. Source must be contributed under the Apache License 2.0. File new issue reports for problems that you identify during use or for plugin enhancement suggestions.
132 |
133 | ## License
134 |
135 | [Apache License 2.0](LICENSE)
--------------------------------------------------------------------------------
/TOOLS.md:
--------------------------------------------------------------------------------
1 | ## Filter List Management Tools
2 |
3 | The filter list management tools include Python 3 scripts that are located in the [`tools` directory](https://github.com/source-foundry/FilterListManager/tree/master/tools) of the FLM repository.
4 |
5 | If a Python 3 interpreter is not available on your system, please see the Python documentation at https://www.python.org/downloads/ for installation instructions.
6 |
7 | The tools include:
8 |
9 | ### `exportfilters.py`
10 |
11 | The `exportfilters.py` script exports existing Glyphs application filter lists to the FLM definition file format and saves these definition files in the GlyphsFilters directory.
12 |
13 | #### Usage
14 |
15 | Download the script file from the FLM repository and execute it from any directory on your macOS system with the following command in your terminal:
16 |
17 | ```
18 | $ python3 exportfilters.py
19 | ```
20 |
21 | Confirm that the files were exported to your GlyphsFilters directory and that the files include the expected glyph names.
22 |
23 |
24 | ### `glformatter.py`
25 |
26 | The `glformatter.py` script formats lists of glyph names with additional Unicode data in a comment line above the glyph name definition.
27 |
28 | The data that are added depend upon how you specify your glyph name in the definition file. The possible data include:
29 |
30 | - Unicode code point in hexadecimal format
31 | - AGL-like nice name (if production name is specified in the file)
32 | - Unicode code point hexadecimal format production name (if nice name is specified in the file)
33 | - Unicode description of the code point
34 |
35 | #### Usage
36 |
37 | Download the script file from the FLM repository and execute it from any directory on your macOS system. Include one or more file paths to local definition files as arguments to the script:
38 |
39 | ```
40 | $ python3 glformatter.py [filepath 1] [filepath ...]
41 | ```
42 |
43 | The script reformats the file in place and saves a backup of the in-file on the path `[filepath].pre`.
44 |
45 |
46 | ### `glinter.py`
47 |
48 | The `glinter.py` script tests each glyph name that is specified in one or more filter list definition files against those that are defined in the Glyphs application GlyphData.xml file and the guidelines provided in the [Adobe OpenType Feature File Specification](https://github.com/adobe-type-tools/afdko/blob/develop/docs/OpenTypeFeatureFileSpecification.html) for *development* glyph names.
49 |
50 | The Glyphs application specific GlyphData.xml tests include any modifications that you've made to the GlyphData.xml data to define new glyph names. The intent is to permit you to confirm that the glyph names that you specified in your definition list will be properly recognized and imported into the Glyphs application for use.
51 |
52 | The GlyphData.xml tests that are performed include:
53 |
54 | 1) specified glyph name is a Glyphs application nice name
55 | 2) specified glyph name is a Glyphs application production name
56 | 3) specified glyph name is a Glyphs application alternate name
57 |
58 | The glyph name passes if criteria for (1) or (2) are met. The script suggests conversion to the nice name or production name (with those values!) if criteria for (3) are met. If there is no match for (1), (2), and (3), the script raises an error and indicates the glyph name and file path to the definition file.
59 |
60 | The Adobe OpenType Feature File Specification tests that are performed include:
61 |
62 | 1) glyph name is 63 characters or less in length
63 | 2) glyph name does not start with a period (except ".null" and ".notdef")
64 | 3) glyph name only includes characters in the set:
65 | - `A-Z`
66 | - `a-z`
67 | - `0-9`
68 | - `.` (period)
69 | - `_` (underscore)
70 | - `*` (asterisk)
71 | - `+` (plus sign)
72 | - `-` (hyphen)
73 | - `:` (colon)
74 | - `^` (circumflex accent)
75 | - `|` (vertical bar)
76 | - `~` (tilde)
77 |
78 | #### Usage
79 |
80 | Download the script file from the FLM repository and execute it from any directory on your macOS system. Include one or more file paths to local definition files as arguments to the script:
81 |
82 | ```
83 | $ python3 glinter.py [filepath 1] [filepath ...]
84 | ```
85 |
86 | Please note that the script does not modify the filter list definition file during execution. It indicates potential errors for your review. You must edit the file to address valid errors.
--------------------------------------------------------------------------------
/tools/exportfilters.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | # ===========================================================
5 | #
6 | #
7 | # exportfilters.py
8 | # A Glyphs font editor filter list to FLM plugin
9 | # definition file exporter script
10 | # Copyright 2018 Christopher Simpkins
11 | # Apache License 2.0
12 | #
13 | # Version 0.1.1
14 | #
15 | # ===========================================================
16 |
17 | # USAGE
18 | #
19 | # This script is dependent upon the Python 3 interpreter. If a Python 3 interpreter
20 | # is not available on your system, please see the Python documentation at
21 | # https://www.python.org/downloads/ for instructions on how to install it.
22 | #
23 | #
24 | # Execute the script from any directory on your system with the following command
25 | # in your terminal window:
26 | #
27 | # python3 exportfilters.py
28 | #
29 | #
30 | # Following execution your existing Glyphs application filter lists will be
31 | # exported as newline delimited definition files that are formatted so that
32 | # they work with the Filter List Manager plugin. The filter list name is
33 | # mapped to the base file name of the definition file and the glyph definitions
34 | # are listed with newline separators. You can find all definition files on
35 | # the path ~/GlyphsFilters.
36 |
37 | import os
38 | import plistlib
39 | import sys
40 |
41 |
42 | def main():
43 | try:
44 | # default paths
45 | glyphs_plist_path = os.path.join(
46 | os.path.expanduser("~"),
47 | "Library",
48 | "Application Support",
49 | "Glyphs",
50 | "CustomFilter.plist",
51 | )
52 |
53 | flm_definitions_directory_path = os.path.join(
54 | os.path.expanduser("~"), "GlyphsFilters"
55 | )
56 |
57 | if not os.path.isdir(flm_definitions_directory_path):
58 | os.makedirs(flm_definitions_directory_path)
59 |
60 | with open(glyphs_plist_path, "rb") as f:
61 | filters_data = plistlib.load(f)
62 | for filter_dict in filters_data:
63 | if "list" in filter_dict:
64 | # define filter file name and filter list contents
65 | filter_name = filter_dict["name"]
66 | filter_list = filter_dict["list"]
67 |
68 | base_filepath = filter_name + ".txt"
69 | outfile_path = os.path.join(
70 | flm_definitions_directory_path, base_filepath
71 | )
72 |
73 | # create newline-delimited file format
74 | outfile_text = (
75 | "// " + filter_name + " filter" + os.linesep + os.linesep
76 | )
77 | for glyph_name in filter_list:
78 | outfile_text += glyph_name + os.linesep
79 |
80 | with open(outfile_path, "w") as w:
81 | w.write(outfile_text)
82 | print(filter_name + " filter list exported...")
83 |
84 | print("---")
85 | print("Export complete!")
86 | print("You can find the definition files in the directory ~/GlyphsFilters")
87 | except Exception as e:
88 | sys.stderr.write(
89 | "[ERROR] There was an error during the export attempt. "
90 | + str(e)
91 | + os.linesep
92 | )
93 | sys.exit(1)
94 |
95 |
96 | if __name__ == "__main__":
97 | main()
98 |
--------------------------------------------------------------------------------
/tools/glformatter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | # ==============================================================
5 | #
6 | #
7 | # glformatter.py
8 | # Glyph name filter list definition file formatter
9 | # Copyright 2018 Christopher Simpkins
10 | # Apache License 2.0
11 | #
12 | # Version 0.1.1
13 | #
14 | # ==============================================================
15 |
16 | # USAGE
17 | #
18 | # This script is dependent upon the Python 3 interpreter. If a Python 3 interpreter
19 | # is not available on your system, please see the Python documentation at
20 | # https://www.python.org/downloads/ for instructions on how to install it.
21 | #
22 | #
23 | # Execute the script from any directory on your system by passing one or more
24 | # filepath arguments to local filter list definition files on your system:
25 | #
26 | # $ python3 glformatter.py [filepath 1] [filepath ...]
27 | #
28 | # The script reformats the text in the filter list definition file with the
29 | # following:
30 | #
31 | # - adds a comment line above each glyph name definition in the file
32 | # that includes:
33 | # - Unicode code point (in hexadecimal name format)
34 | # - AGL-style nice name (if not used for glyph name definition)
35 | # - production name (if not used for glyph name definition)
36 | # - Unicode description
37 | #
38 | # The file write takes place on the original file path. The original file
39 | # is backed up on the path:
40 | #
41 | # [original file path].pre
42 |
43 |
44 | import os
45 | import shutil
46 | import sys
47 | import xml.etree.ElementTree as ET
48 |
49 | COMMENT_DELIMITERS = ("#", "/")
50 |
51 |
52 | class Glyph(object):
53 | def __init__(self):
54 | self.unicode = ""
55 | self.name = ""
56 | self.description = ""
57 | self.production = ""
58 | self.alt_names = []
59 |
60 |
61 | def main(argv):
62 | glyphlist = []
63 | unicode_set = set()
64 | name_set = set()
65 | production_set = set()
66 | altname_set = set()
67 |
68 | tree = ET.parse(
69 | "/Applications/Glyphs.app/Contents/Frameworks/GlyphsCore.framework/Versions/A/Resources/GlyphData.xml"
70 | )
71 | root = tree.getroot()
72 | for child in root:
73 | glyph = Glyph()
74 | if "unicode" in child.attrib:
75 | glyph.unicode = child.attrib["unicode"]
76 | unicode_set.add(glyph.unicode)
77 | if "name" in child.attrib:
78 | glyph.name = child.attrib["name"]
79 | name_set.add(glyph.name)
80 | if "description" in child.attrib:
81 | glyph.description = child.attrib["description"]
82 | if "production" in child.attrib:
83 | glyph.production = child.attrib["production"]
84 | production_set.add(glyph.production)
85 | if "altNames" in child.attrib:
86 | namestring = child.attrib["altNames"]
87 | if len(namestring) > 0:
88 | name_list = namestring.split(",")
89 | for altname in name_list:
90 | save_name = altname.strip()
91 | glyph.alt_names.append(save_name)
92 | altname_set.add(save_name)
93 |
94 | glyphlist.append(glyph)
95 |
96 | for filepath in argv:
97 | if not os.path.isfile(filepath):
98 | sys.stderr.write(
99 | "[ERROR] " + filepath + " does not appear to be a valid filepath!"
100 | )
101 | sys.exit(1)
102 |
103 | outfile_list = []
104 |
105 | with open(filepath, "r") as f:
106 | text = f.read()
107 | text_line_list = text.splitlines()
108 | for line in text_line_list:
109 | if line[0] in COMMENT_DELIMITERS:
110 | outfile_list.append(line)
111 | elif len(line) == 0:
112 | outfile_list.append("")
113 | elif line is None:
114 | outfile_list.append("")
115 | else:
116 | glyph_name = line
117 | if glyph_name in name_set:
118 | outfile_list.append(
119 | get_comment_string(glyphlist, name=glyph_name)
120 | )
121 | outfile_list.append(glyph_name + os.linesep)
122 | elif glyph_name in production_set:
123 | outfile_list.append(
124 | get_comment_string(glyphlist, production=glyph_name)
125 | )
126 | outfile_list.append(glyph_name + os.linesep)
127 | else:
128 | outfile_list.append(glyph_name + os.linesep)
129 |
130 | # backup file to originalpath with ".pre" suffix
131 | backup_filepath = filepath + ".pre"
132 | shutil.copyfile(filepath, backup_filepath)
133 |
134 | # write out formatted file
135 | out_text = "\n".join(outfile_list)
136 | with open(filepath, "w") as f:
137 | f.write(out_text)
138 |
139 |
140 | def get_comment_string(glyphlist, name=None, production=None):
141 | if name is not None:
142 | for glyph in glyphlist:
143 | if glyph.name == name:
144 | comment_string = "# "
145 | if len(glyph.unicode) > 0:
146 | comment_string += "U+" + glyph.unicode
147 | if len(glyph.production) > 0:
148 | comment_string += " | " + glyph.production
149 | if len(glyph.description) > 0:
150 | comment_string += " | " + glyph.description
151 | return comment_string
152 | elif production is not None:
153 | for glyph in glyphlist:
154 | if glyph.production == production:
155 | comment_string = "# "
156 | if len(glyph.unicode) > 0:
157 | comment_string += "U+" + glyph.unicode
158 | if len(glyph.name) > 0:
159 | comment_string += " | " + glyph.name
160 | if len(glyph.description) > 0:
161 | comment_string += " | " + glyph.description
162 | return comment_string
163 | else:
164 | return ""
165 |
166 |
167 | if __name__ == "__main__":
168 | main(sys.argv[1:])
169 |
--------------------------------------------------------------------------------
/tools/glinter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | # ==============================================================
5 | #
6 | #
7 | # glinter.py
8 | # Glyph name validity testing script
9 | # Copyright 2018 Christopher Simpkins
10 | # Apache License 2.0
11 | #
12 | # Version 0.3.1
13 | #
14 | # ==============================================================
15 |
16 | # USAGE
17 | #
18 | # This script is dependent upon the Python 3 interpreter. If a Python 3 interpreter
19 | # is not available on your system, please see the Python documentation at
20 | # https://www.python.org/downloads/ for instructions on how to install it.
21 | #
22 | #
23 | # Execute the script from any directory on your system by passing one or more
24 | # filepath arguments to local filter list definition files on your system:
25 | #
26 | # $ python3 glinter.py [filepath 1] [filepath ...]
27 | #
28 | # The script tests each glyph name listed in the definition file vs.
29 | # the Glyphs application GlyphData.xml file and Adobe OpenType
30 | # Feature File specification for development glyph names
31 | #
32 | # Glyph names are tested against the Glyphs GlyphData.xml file to determine
33 | # if they are:
34 | # - Glyphs-defined nice names (AGL style names)
35 | # - Glyphs-defined Unicode hexadecimal style names (e.g, uniXXXX, uXXXXX)
36 | # - common alternate names -- if found, the above two glyph names are suggested
37 | #
38 | # If the glyph name is not found in the above set of lists, the script will raise
39 | # an error and identify the definition file path where the error was found.
40 | #
41 | # For glyph names that pass the above tests, the following additional tests
42 | # that are based upon the Adobe OT Feature File spec are performed:
43 | # - glyph name < 64 characters in length
44 | # - glyph name does not contain a leading period (except .notdef and .null)
45 | # - glyph name contains valid characters for development glyph names
46 | #
47 | # Adobe reference:
48 | # https://github.com/adobe-type-tools/afdko/blob/develop/docs/OpenTypeFeatureFileSpecification.html
49 |
50 | import os
51 | import re
52 | import sys
53 | import xml.etree.ElementTree as ET
54 |
55 |
56 | class Glyph(object):
57 | def __init__(self):
58 | self.unicode = ""
59 | self.name = ""
60 | self.description = ""
61 | self.production = ""
62 | self.alt_names = []
63 |
64 |
65 | class Filter(object):
66 | """Filter is an object that maintains data elements for Glyphs application filter lists.
67 | It is instantiated with a new filter list name and list elements are defined with
68 | a class method"""
69 |
70 | def __init__(self, name):
71 | self.comment_delimiters = ("#", "/")
72 | self.name = name
73 | self.list = []
74 |
75 | def define_list_with_newline_delimited_text(self, text):
76 | """Filter class method that defines a Filter object with parsed data from newline-
77 | delimited text files of list elements"""
78 | raw_code_point_list = text.splitlines()
79 | filtered_code_point_list = []
80 | for item in raw_code_point_list:
81 | test_item = item.strip()
82 | if len(test_item) == 0: # discard blank lines in definition file
83 | pass
84 | elif (
85 | test_item[0] in self.comment_delimiters
86 | ): # discard comment lines in definition file
87 | pass
88 | else:
89 | filtered_code_point_list.append(test_item)
90 | self.list = filtered_code_point_list
91 |
92 |
93 | def main(argv):
94 | # parse GlyphData.xml file for Glyphs application supported glyph name values
95 | glyphlist = []
96 | unicode_set = set()
97 | name_set = set()
98 | production_set = set()
99 | altname_set = set()
100 |
101 | tree = ET.parse(
102 | "/Applications/Glyphs.app/Contents/Frameworks/GlyphsCore.framework/Versions/A/Resources/GlyphData.xml"
103 | )
104 | root = tree.getroot()
105 | for child in root:
106 | glyph = Glyph()
107 | if "unicode" in child.attrib:
108 | glyph.unicode = child.attrib["unicode"]
109 | unicode_set.add(glyph.unicode)
110 | if "name" in child.attrib:
111 | glyph.name = child.attrib["name"]
112 | name_set.add(glyph.name)
113 | if "description" in child.attrib:
114 | glyph.description = child.attrib["description"]
115 | if "production" in child.attrib:
116 | glyph.production = child.attrib["production"]
117 | production_set.add(glyph.production)
118 | if "altNames" in child.attrib:
119 | namestring = child.attrib["altNames"]
120 | if len(namestring) > 0:
121 | name_list = namestring.split(",")
122 | for altname in name_list:
123 | save_name = altname.strip()
124 | glyph.alt_names.append(save_name)
125 | altname_set.add(save_name)
126 |
127 | glyphlist.append(glyph)
128 |
129 | # parse filter list definition files that were passed as command line args
130 | filter_list = []
131 |
132 | for filepath in argv:
133 | if os.path.isfile(filepath):
134 | new_filter = Filter(filepath)
135 | with open(filepath) as f:
136 | text = f.read()
137 | new_filter.define_list_with_newline_delimited_text(text)
138 | filter_list.append(new_filter)
139 | else:
140 | sys.stderr.write(
141 | "[ERROR]: Unable to identify a file on the path '"
142 | + filepath
143 | + "'"
144 | + os.linesep
145 | )
146 |
147 | if len(filter_list) == 0:
148 | sys.stderr.write(
149 | "[ERROR]: Unable to identify any definitions in the requested files!"
150 | + os.linesep
151 | )
152 | sys.exit(1)
153 |
154 | # BEGIN TESTS
155 | ANY_ERROR_DETECTED = False
156 | for a_filter in filter_list:
157 | FILTER_ERROR_DETECTED = False
158 | for glyph_name in a_filter.list:
159 | if glyph_name in name_set:
160 | pass
161 | elif glyph_name in production_set:
162 | pass
163 | elif glyph_name in altname_set:
164 | ANY_ERROR_DETECTED = True
165 | FILTER_ERROR_DETECTED = True
166 | sys.stderr.write(
167 | "'"
168 | + glyph_name
169 | + "' in definition file '"
170 | + a_filter.name
171 | + "' appears to be an alternate name. Consider replacement with one of the following names:"
172 | + os.linesep
173 | )
174 | for a_glyph in glyphlist:
175 | if glyph_name in a_glyph.alt_names:
176 | if len(a_glyph.name) > 0:
177 | sys.stderr.write(" --> " + a_glyph.name + os.linesep)
178 | if len(a_glyph.production) > 0:
179 | sys.stderr.write(" --> " + a_glyph.production + os.linesep)
180 | if len(a_glyph.unicode) == 4:
181 | uni_name = "uni" + a_glyph.unicode
182 | if not uni_name == a_glyph.production:
183 | sys.stderr.write(" --> " + uni_name + os.linesep)
184 | elif len(a_glyph.unicode) == 5:
185 | uni_name = "u" + a_glyph.unicode
186 | if not uni_name == a_glyph.production:
187 | sys.stderr.write(" --> " + uni_name + os.linesep)
188 | else:
189 | sys.stderr.write(
190 | "'"
191 | + glyph_name
192 | + "' does not appear to be a valid glyph name!"
193 | + os.linesep
194 | )
195 | ANY_ERROR_DETECTED = True
196 | FILTER_ERROR_DETECTED = True
197 |
198 | # BEGIN non-GlyphData.xml tests
199 | if FILTER_ERROR_DETECTED is False:
200 | # https://github.com/adobe-type-tools/afdko/blob/develop/docs/OpenTypeFeatureFileSpecification.html
201 | # glyph name length
202 | if len(glyph_name) > 63:
203 | sys.stderr.write(
204 | "'" + glyph_name + "' is too long! (> 63 characters)"
205 | )
206 | FILTER_ERROR_DETECTED = True
207 | ANY_ERROR_DETECTED = True
208 | # glyph name starts with period outside of defined positions (including .null defined in GlyphData.xml)
209 | if glyph_name[0] == "." and (glyph_name not in (".notdef", ".null")):
210 | sys.stderr.write(
211 | "'" + glyph_name + "' includes an invalid leading period!"
212 | )
213 | FILTER_ERROR_DETECTED = True
214 | ANY_ERROR_DETECTED = True
215 |
216 | # glyph name contains valid characters
217 | if not has_valid_characters(glyph_name):
218 | sys.stderr.write(
219 | "'" + glyph_name + "' contains invalid characters!" + os.linesep
220 | )
221 | FILTER_ERROR_DETECTED = True
222 | ANY_ERROR_DETECTED = True
223 |
224 | if FILTER_ERROR_DETECTED is True:
225 | sys.stderr.write("[ERROR] --> " + a_filter.name + os.linesep)
226 | else:
227 | print("[OK] " + a_filter.name)
228 |
229 | if ANY_ERROR_DETECTED is True:
230 | sys.exit(1)
231 | else:
232 | print("All tests passed!")
233 | sys.exit(0)
234 |
235 |
236 | def has_valid_characters(glyph_name):
237 | """Tests for presence of valid characters in a glyph name as specified by the Adobe
238 | OpenType Feature File specification. The test here includes characters
239 | specified for 'development' names, a broader set than the production name
240 | definition
241 |
242 | https://github.com/adobe-type-tools/afdko/blob/develop/docs/OpenTypeFeatureFileSpecification.html"""
243 | valid_characters = r"^[A-Za-z0-9\._\*\+\-\:\^\|\~]{1,63}$"
244 | regex = re.compile(valid_characters)
245 | return re.match(regex, glyph_name)
246 |
247 |
248 | if __name__ == "__main__":
249 | main(sys.argv[1:])
250 |
--------------------------------------------------------------------------------