From 3064de4499ae7e493b2d09705f757fc8c8e5285b Mon Sep 17 00:00:00 2001 From: adbac <126618591+adbac@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:31:17 +0200 Subject: [PATCH 01/13] add support for character variant names and update documentation --- Lib/feaPyFoFum/feaPyFoFum.py | 50 ++++++++++++++++++++++++++++++++++++ readme.md | 19 +++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/Lib/feaPyFoFum/feaPyFoFum.py b/Lib/feaPyFoFum/feaPyFoFum.py index 1c4fb31..36c002b 100644 --- a/Lib/feaPyFoFum/feaPyFoFum.py +++ b/Lib/feaPyFoFum/feaPyFoFum.py @@ -1057,3 +1057,53 @@ def _stylisticSetNames(self, names): self._indentText(text) self._identifierStack.append("stylisticSetNames") return text + + # character variant + + def formatCharacterVariantNames(self, *names): + lines = ["cvParameters {"] + orderedNames = dict( + FeatUILabelNameID=[], + FeatUITooltipTextNameID=[], + SampleTextNameID=[], + ParamUILabelNameID=[], + ) + for name in names: + if (nameType := name["type"]) in orderedNames: + orderedNames[nameType].append(name) + # XXX silently fail here? + for nameType in orderedNames: + block = [f"{self._whitespace}{nameType} {{"] + for name in orderedNames[nameType]: + text = name["text"] + platform = name.get("platform") + script = name.get("script") + language = name.get("language") + line = ["name"] + if platform is not None: + line.append(str(platform)) + if script is not None: + line.append(str(script)) + line.append(str(language)) + line.append(u'\"%s\"' % text) + line = (self._whitespace * 2) + " ".join(line) + ";" + block.append(line) + block.append((self._whitespace + "};")) + lines.extend(block) + lines.append("};") + text = "\n".join(lines) + return text + + def characterVariantNames(self, *names): + d = dict( + identifier="characterVariantNames", + names=names + ) + self._content.append(d) + + def _characterVariantNames(self, names): + text = self._handleBreakBefore("characterVariantNames") + text.extend(self.formatCharacterVariantNames(*names).splitlines()) + self._indentText(text) + self._identifierStack.append("characterVariantNames") + return text diff --git a/readme.md b/readme.md index f6b5b01..a4a8878 100644 --- a/readme.md +++ b/readme.md @@ -103,7 +103,7 @@ If `choice` is `True` the rule will be written as a `from` rule (GSUB LookupType The same contextual marking defined in the `substitution` method will be run for `ignoreSubstitution`. -##### writer.stylisticSetFeatureNames(name1, name2, name3, ...) +##### writer.stylisticSetNames(name1, name2, name3, ...) This will write the given names as `featureNames` in the current feature. Names must be dicts of this form: @@ -116,6 +116,21 @@ name = { } ``` +##### writer.characterVariantNames(name1, name2, name3, ...) + +This will write the given names as `cvParameters` in the current feature. Names must be dicts of this form: + +```python +name = { + "type": "type for this name", + # 'type' must be one of [FeatUILabelNameID, FeatUITooltipTextNameID, SampleTextNameID, ParamUILabelNameID] + "text" : "name for string", + "platform" : int, # optional + "script" : int, # optional + "language" : int, # optional +} +``` + ##### writer.write() Return a string containing everything stored in the writer properly formatted for .fea. @@ -149,6 +164,8 @@ This will only assume that the substitution rule should contain contextual marki ##### writer.formatStylisticSetNames(name1, name2, name3, ...) +##### writer.formatCharacterVariantNames(name1, name2, name3, ...) + # FeaPyFoFum From 7ef84a042173c47c7d6b446f47c3e461601e14eb Mon Sep 17 00:00:00 2001 From: adbac <126618591+adbac@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:50:37 +0200 Subject: [PATCH 02/13] skip empty cv names categories --- Lib/feaPyFoFum/feaPyFoFum.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/feaPyFoFum/feaPyFoFum.py b/Lib/feaPyFoFum/feaPyFoFum.py index 36c002b..33de275 100644 --- a/Lib/feaPyFoFum/feaPyFoFum.py +++ b/Lib/feaPyFoFum/feaPyFoFum.py @@ -1072,9 +1072,11 @@ def formatCharacterVariantNames(self, *names): if (nameType := name["type"]) in orderedNames: orderedNames[nameType].append(name) # XXX silently fail here? - for nameType in orderedNames: + for nameType, nameDicts in orderedNames.items(): + if len(nameDicts) == 0: + continue block = [f"{self._whitespace}{nameType} {{"] - for name in orderedNames[nameType]: + for name in nameDicts: text = name["text"] platform = name.get("platform") script = name.get("script") From 174ea8c1c942cd7f71b18d714143b1c1359f31e3 Mon Sep 17 00:00:00 2001 From: adbac <126618591+adbac@users.noreply.github.com> Date: Wed, 16 Jul 2025 00:09:29 +0200 Subject: [PATCH 03/13] support namespace additions --- Lib/feaPyFoFum/feaPyFoFum.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Lib/feaPyFoFum/feaPyFoFum.py b/Lib/feaPyFoFum/feaPyFoFum.py index 33de275..233d2a8 100644 --- a/Lib/feaPyFoFum/feaPyFoFum.py +++ b/Lib/feaPyFoFum/feaPyFoFum.py @@ -15,7 +15,7 @@ class FeaPyFoFumError(Exception): # External API # ------------ -def compileFeatures(text, font, verbose=False, compileReferencedFiles=False): +def compileFeatures(text, font, verbose=False, compileReferencedFiles=False, namespaceAdditions={}): """ Compile the dynamic features in the given text. @@ -31,11 +31,14 @@ def compileFeatures(text, font, verbose=False, compileReferencedFiles=False): files will be compiled and the references will be updated. The locations of the referenced files are assumed to be relative to the directory containing the font. + + Additions to the execution namespace can be made through namespaceAdditions. """ if not compileReferencedFiles: text = _compileFeatureText( text, font, + namespace=namespaceAdditions, verbose=verbose )[0] else: @@ -45,6 +48,7 @@ def compileFeatures(text, font, verbose=False, compileReferencedFiles=False): text, referencedFiles = _compileFeatureText( text, font, + namespace=namespaceAdditions, relativePath=relativePath, verbose=verbose ) @@ -54,6 +58,7 @@ def compileFeatures(text, font, verbose=False, compileReferencedFiles=False): outPath, relativePath, font, + namespace=namespaceAdditions, verbose=False ) return text @@ -63,7 +68,7 @@ def compileFeatures(text, font, verbose=False, compileReferencedFiles=False): # .fea File Creation # ------------------ -def _compileFeatureText(text, font, relativePath=None, verbose=False, recursionDepth=0): +def _compileFeatureText(text, font, relativePath=None, verbose=False, namespace={}, recursionDepth=0): """ Compile the completed feature text. If the relativePath is given files referenced @@ -84,12 +89,11 @@ def _compileFeatureText(text, font, relativePath=None, verbose=False, recursionD else: raise FeaPyFoFumError("Maximum reference file recursion depth exceeded.") # compile - namespace = {} text = _executeFeatureText(text, font, namespace, verbose=verbose) return text, referencedFiles -def _compileReferencedFeatureFile(inPath, outPath, relativePath, font, verbose=False, recursionDepth=0): +def _compileReferencedFeatureFile(inPath, outPath, relativePath, font, verbose=False, namespace={}, recursionDepth=0): """ Compile the file given in inPath and write it to outPath. """ @@ -103,6 +107,7 @@ def _compileReferencedFeatureFile(inPath, outPath, relativePath, font, verbose=F text, font, relativePath, + namespace=namespace, verbose=verbose, recursionDepth=recursionDepth ) @@ -115,6 +120,7 @@ def _compileReferencedFeatureFile(inPath, outPath, relativePath, font, verbose=F referenceOutPath, relativePath, font, + namespace=namespace, verbose=verbose, recursionDepth=recursionDepth + 1 ) From cfc0f7bf2a56cbfa9c173ba20fac336622967fa1 Mon Sep 17 00:00:00 2001 From: adbac <126618591+adbac@users.noreply.github.com> Date: Sat, 19 Jul 2025 14:59:47 +0200 Subject: [PATCH 04/13] add option to parse include statements --- Lib/feaPyFoFum/feaPyFoFum.py | 67 +++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/Lib/feaPyFoFum/feaPyFoFum.py b/Lib/feaPyFoFum/feaPyFoFum.py index 233d2a8..d733f93 100644 --- a/Lib/feaPyFoFum/feaPyFoFum.py +++ b/Lib/feaPyFoFum/feaPyFoFum.py @@ -15,7 +15,7 @@ class FeaPyFoFumError(Exception): # External API # ------------ -def compileFeatures(text, font, verbose=False, compileReferencedFiles=False, namespaceAdditions={}): +def compileFeatures(text, font, verbose=False, compileReferencedFiles=False, namespaceAdditions={}, parseIncludes=False): """ Compile the dynamic features in the given text. @@ -33,7 +33,12 @@ def compileFeatures(text, font, verbose=False, compileReferencedFiles=False, nam relative to the directory containing the font. Additions to the execution namespace can be made through namespaceAdditions. + + If parseIncludes is set to True, all include statements will be replaced by the compiled contents of the referenced files recursively. """ + if parseIncludes: + basePath = os.path.dirname(getattr(font, 'path', '') or os.getcwd()) + text = _parseIncludes(text, basePath, set(), font=font, namespace=namespaceAdditions, verbose=verbose) if not compileReferencedFiles: text = _compileFeatureText( text, @@ -64,6 +69,66 @@ def compileFeatures(text, font, verbose=False, compileReferencedFiles=False, nam return text +def _parseIncludes(text, basePath, processedFiles, font=None, namespace=None, verbose=False, recursionDepth=0): + """ + Recursively replace include(path); statements with the compiled contents of the referenced files. + Each include path is resolved relative to the directory of the file containing the include statement (not the entry file). + basePath: directory to resolve relative include paths for the current file + processedFiles: set of absolute paths to avoid infinite recursion + font: font object to pass to _compileFeatureText + namespace: namespace dict for code execution + verbose: verbose flag for compilation + recursionDepth: current recursion depth (must be <= 5) + The included text will be indented to match the include statement. + """ + if recursionDepth > 5: + raise FeaPyFoFumError("Maximum include recursion depth exceeded.") + if namespace is None: + namespace = {} + pattern = re.compile(r"^([ \t]*)include\s*\(([^)]+)\)\s*;", re.MULTILINE) + def _read_file(path): + with open(path, 'r', encoding='utf-8') as f: + return f.read() + while True: + match = pattern.search(text) + if not match: + break + indent = match.group(1) + relPath = match.group(2).strip() + absPath = os.path.normpath(os.path.join(basePath, relPath)) + if absPath in processedFiles: + raise FeaPyFoFumError(f"Recursive include detected: {absPath}") + processedFiles.add(absPath) + if not os.path.isfile(absPath): + raise FeaPyFoFumError(f"Included file not found: {absPath}") + includedText = _read_file(absPath) + # Recursively parse and compile includes in the included file + compiledText, _ = _compileFeatureText( + includedText, + font, + namespace=namespace, + verbose=verbose + ) + compiledText = _parseIncludes( + compiledText, + os.path.dirname(absPath), # base_path is updated for each file + processedFiles, + font=font, + namespace=namespace, + verbose=verbose, + recursionDepth=recursionDepth + 1 + ) + # Indent the compiled text to match the include statement + indentedCompiled = '\n'.join( + (indent + line if line.strip() != '' else line) + for line in compiledText.splitlines() + ) + # Replace the include statement with the indented compiled text + text = text[:match.start()] + indentedCompiled + text[match.end():] + processedFiles.remove(absPath) + return text + + # ------------------ # .fea File Creation # ------------------ From cc775eb346c5d5984a70920f5ea8ba83fd0c9eba Mon Sep 17 00:00:00 2001 From: adbac <126618591+adbac@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:23:57 +0200 Subject: [PATCH 05/13] add possibility to feed function with a file path instead of a text + fix _parseIncludes function to compile text before looking for include statements --- Lib/feaPyFoFum/feaPyFoFum.py | 124 +++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 48 deletions(-) diff --git a/Lib/feaPyFoFum/feaPyFoFum.py b/Lib/feaPyFoFum/feaPyFoFum.py index d733f93..19f1e1d 100644 --- a/Lib/feaPyFoFum/feaPyFoFum.py +++ b/Lib/feaPyFoFum/feaPyFoFum.py @@ -15,7 +15,7 @@ class FeaPyFoFumError(Exception): # External API # ------------ -def compileFeatures(text, font, verbose=False, compileReferencedFiles=False, namespaceAdditions={}, parseIncludes=False): +def compileFeatures(textOrPath, font, verbose=False, compileReferencedFiles=False, namespaceAdditions={}, parseIncludes=False): """ Compile the dynamic features in the given text. @@ -36,40 +36,68 @@ def compileFeatures(text, font, verbose=False, compileReferencedFiles=False, nam If parseIncludes is set to True, all include statements will be replaced by the compiled contents of the referenced files recursively. """ - if parseIncludes: - basePath = os.path.dirname(getattr(font, 'path', '') or os.getcwd()) - text = _parseIncludes(text, basePath, set(), font=font, namespace=namespaceAdditions, verbose=verbose) - if not compileReferencedFiles: - text = _compileFeatureText( - text, - font, - namespace=namespaceAdditions, - verbose=verbose - )[0] + # detect .fea path or text + try: + assert str(textOrPath).endswith(".fea") + assert os.path.exists(str(textOrPath)) + filePath = textOrPath + with open(filePath, "r") as f: + text = f.read() + except: + filePath = None + text = textOrPath + # create namespace with additions + namespace = dict( + FEA_PATH=filePath, + **namespaceAdditions, + ) + # determine the base directory for relative paths + if filePath is not None: + relativePath = os.path.dirname(filePath) + elif font.path: + relativePath = os.path.dirname(font.path) else: relativePath = None - if font.path: - relativePath = os.path.dirname(font.path) - text, referencedFiles = _compileFeatureText( - text, - font, - namespace=namespaceAdditions, - relativePath=relativePath, - verbose=verbose - ) - for inPath, outPath in referencedFiles: - _compileReferencedFeatureFile( - inPath, - outPath, - relativePath, + # compile + if parseIncludes: + text = _parseIncludes(text, relativePath, set(), font=font, namespace=namespace, verbose=verbose) + else: + if not compileReferencedFiles: + text = _compileFeatureText( + text, + font, + updateIncludes=False, + namespace=namespace, + verbose=verbose + )[0] + else: + relativePath = None + if font.path: + relativePath = os.path.dirname(font.path) + text, referencedFiles = _compileFeatureText( + text, font, - namespace=namespaceAdditions, - verbose=False + namespace=namespace, + relativePath=relativePath, + verbose=verbose ) + for inPath, outPath in referencedFiles: + _compileReferencedFeatureFile( + inPath, + outPath, + relativePath, + font, + namespace=namespace, + verbose=False + ) return text -def _parseIncludes(text, basePath, processedFiles, font=None, namespace=None, verbose=False, recursionDepth=0): +# ------------------ +# .fea File Creation +# ------------------ + +def _parseIncludes(text, filePath, processedFiles, font=None, namespace={}, verbose=False, recursionDepth=0): """ Recursively replace include(path); statements with the compiled contents of the referenced files. Each include path is resolved relative to the directory of the file containing the include statement (not the entry file). @@ -83,10 +111,18 @@ def _parseIncludes(text, basePath, processedFiles, font=None, namespace=None, ve """ if recursionDepth > 5: raise FeaPyFoFumError("Maximum include recursion depth exceeded.") - if namespace is None: - namespace = {} + # Compile the text before searching for include statements + namespace["FEA_PATH"] = filePath # update namespace + compiledText, _ = _compileFeatureText( + text, + font, + updateIncludes=False, + namespace=namespace, + verbose=verbose + ) + text = compiledText pattern = re.compile(r"^([ \t]*)include\s*\(([^)]+)\)\s*;", re.MULTILINE) - def _read_file(path): + def _readFile(path): with open(path, 'r', encoding='utf-8') as f: return f.read() while True: @@ -94,6 +130,7 @@ def _read_file(path): if not match: break indent = match.group(1) + basePath = os.path.dirname(filePath) relPath = match.group(2).strip() absPath = os.path.normpath(os.path.join(basePath, relPath)) if absPath in processedFiles: @@ -101,17 +138,12 @@ def _read_file(path): processedFiles.add(absPath) if not os.path.isfile(absPath): raise FeaPyFoFumError(f"Included file not found: {absPath}") - includedText = _read_file(absPath) + includedText = _readFile(absPath) # Recursively parse and compile includes in the included file - compiledText, _ = _compileFeatureText( + namespace["FEA_PATH"] = absPath # update namespace + includedText = _parseIncludes( includedText, - font, - namespace=namespace, - verbose=verbose - ) - compiledText = _parseIncludes( - compiledText, - os.path.dirname(absPath), # base_path is updated for each file + absPath, # absPath is updated for each file processedFiles, font=font, namespace=namespace, @@ -121,7 +153,7 @@ def _read_file(path): # Indent the compiled text to match the include statement indentedCompiled = '\n'.join( (indent + line if line.strip() != '' else line) - for line in compiledText.splitlines() + for line in includedText.splitlines() ) # Replace the include statement with the indented compiled text text = text[:match.start()] + indentedCompiled + text[match.end():] @@ -129,18 +161,14 @@ def _read_file(path): return text -# ------------------ -# .fea File Creation -# ------------------ - -def _compileFeatureText(text, font, relativePath=None, verbose=False, namespace={}, recursionDepth=0): +def _compileFeatureText(text, font, relativePath=None, updateIncludes=True, verbose=False, namespace={}, recursionDepth=0): """ Compile the completed feature text. - If the relativePath is given files referenced + If updateIncludes is True and the relativePath is given files referenced with include statements will be processed """ referencedFiles = [] - if relativePath is not None: + if updateIncludes and relativePath is not None: # find referenced files and update them to the new paths # XXX the relative path stuff here is potentially problematic. # XXX the .fea spec is vague about how paths should be resolved. From 675e51af76b014b715c4ec2952b33264d42eaf16 Mon Sep 17 00:00:00 2001 From: adbac <126618591+adbac@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:30:49 +0200 Subject: [PATCH 06/13] resolve file path --- Lib/feaPyFoFum/feaPyFoFum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/feaPyFoFum/feaPyFoFum.py b/Lib/feaPyFoFum/feaPyFoFum.py index 19f1e1d..a59936d 100644 --- a/Lib/feaPyFoFum/feaPyFoFum.py +++ b/Lib/feaPyFoFum/feaPyFoFum.py @@ -40,7 +40,7 @@ def compileFeatures(textOrPath, font, verbose=False, compileReferencedFiles=Fals try: assert str(textOrPath).endswith(".fea") assert os.path.exists(str(textOrPath)) - filePath = textOrPath + filePath = os.path.abspath(textOrPath) with open(filePath, "r") as f: text = f.read() except: From b25ac2ceb153ed4c2120981a563ca1dab4457daa Mon Sep 17 00:00:00 2001 From: adbac <126618591+adbac@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:34:42 +0200 Subject: [PATCH 07/13] fix wrong path --- Lib/feaPyFoFum/feaPyFoFum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/feaPyFoFum/feaPyFoFum.py b/Lib/feaPyFoFum/feaPyFoFum.py index a59936d..ff08cc3 100644 --- a/Lib/feaPyFoFum/feaPyFoFum.py +++ b/Lib/feaPyFoFum/feaPyFoFum.py @@ -60,7 +60,7 @@ def compileFeatures(textOrPath, font, verbose=False, compileReferencedFiles=Fals relativePath = None # compile if parseIncludes: - text = _parseIncludes(text, relativePath, set(), font=font, namespace=namespace, verbose=verbose) + text = _parseIncludes(text, filePath, set(), font=font, namespace=namespace, verbose=verbose) else: if not compileReferencedFiles: text = _compileFeatureText( From a2539be3708a8db330596fac087d115180224837 Mon Sep 17 00:00:00 2001 From: adbac <126618591+adbac@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:41:37 +0200 Subject: [PATCH 08/13] simplify file path handling --- Lib/feaPyFoFum/feaPyFoFum.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/feaPyFoFum/feaPyFoFum.py b/Lib/feaPyFoFum/feaPyFoFum.py index ff08cc3..1ac893b 100644 --- a/Lib/feaPyFoFum/feaPyFoFum.py +++ b/Lib/feaPyFoFum/feaPyFoFum.py @@ -38,8 +38,7 @@ def compileFeatures(textOrPath, font, verbose=False, compileReferencedFiles=Fals """ # detect .fea path or text try: - assert str(textOrPath).endswith(".fea") - assert os.path.exists(str(textOrPath)) + assert os.path.exists(textOrPath) and os.path.splitext(textOrPath)[1] == "fea" filePath = os.path.abspath(textOrPath) with open(filePath, "r") as f: text = f.read() From 06e90938c171792a088c36e02e47e6c093d9a2e4 Mon Sep 17 00:00:00 2001 From: adbac <126618591+adbac@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:06:42 +0200 Subject: [PATCH 09/13] avoid syntax warnings --- Lib/feaPyFoFum/feaPyFoFum.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/feaPyFoFum/feaPyFoFum.py b/Lib/feaPyFoFum/feaPyFoFum.py index 1ac893b..45c842a 100644 --- a/Lib/feaPyFoFum/feaPyFoFum.py +++ b/Lib/feaPyFoFum/feaPyFoFum.py @@ -256,10 +256,10 @@ def _findReferenceFiles(text): """ text = _stripComments(text) pattern = re.compile( - "include\s*\(" - "[^\)]+" - "\s*\)" - "\s*;" + r"include\s*\(" + r"[^\)]+" + r"\s*\)" + r"\s*;" ) return pattern.findall(text) From d4949be882c0347305770e918139ecda6861eaaf Mon Sep 17 00:00:00 2001 From: adbac <126618591+adbac@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:13:39 +0200 Subject: [PATCH 10/13] compile text in the right place --- Lib/feaPyFoFum/feaPyFoFum.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/feaPyFoFum/feaPyFoFum.py b/Lib/feaPyFoFum/feaPyFoFum.py index 45c842a..4f45b1c 100644 --- a/Lib/feaPyFoFum/feaPyFoFum.py +++ b/Lib/feaPyFoFum/feaPyFoFum.py @@ -166,6 +166,9 @@ def _compileFeatureText(text, font, relativePath=None, updateIncludes=True, verb If updateIncludes is True and the relativePath is given files referenced with include statements will be processed """ + # compile + text = _executeFeatureText(text, font, namespace, verbose=verbose) + # update include statements referencedFiles = [] if updateIncludes and relativePath is not None: # find referenced files and update them to the new paths @@ -180,8 +183,6 @@ def _compileFeatureText(text, font, relativePath=None, updateIncludes=True, verb text = text.replace(referencedData["target"], referencedData["replacement"]) else: raise FeaPyFoFumError("Maximum reference file recursion depth exceeded.") - # compile - text = _executeFeatureText(text, font, namespace, verbose=verbose) return text, referencedFiles From 758935f78447c679bee1025d690fc6ddcefb471b Mon Sep 17 00:00:00 2001 From: adbac <126618591+adbac@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:13:48 +0200 Subject: [PATCH 11/13] fix extension detection --- Lib/feaPyFoFum/feaPyFoFum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/feaPyFoFum/feaPyFoFum.py b/Lib/feaPyFoFum/feaPyFoFum.py index 4f45b1c..7f4b31c 100644 --- a/Lib/feaPyFoFum/feaPyFoFum.py +++ b/Lib/feaPyFoFum/feaPyFoFum.py @@ -38,7 +38,7 @@ def compileFeatures(textOrPath, font, verbose=False, compileReferencedFiles=Fals """ # detect .fea path or text try: - assert os.path.exists(textOrPath) and os.path.splitext(textOrPath)[1] == "fea" + assert os.path.exists(textOrPath) and os.path.splitext(textOrPath)[1] == ".fea" filePath = os.path.abspath(textOrPath) with open(filePath, "r") as f: text = f.read() From 80baf89d7fc0c4e02915f00f8eccdb33edd15be6 Mon Sep 17 00:00:00 2001 From: adbac <126618591+adbac@users.noreply.github.com> Date: Sun, 14 Sep 2025 11:35:30 +0200 Subject: [PATCH 12/13] update readme --- readme.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index a4a8878..03ccb22 100644 --- a/readme.md +++ b/readme.md @@ -184,7 +184,8 @@ originalFeatures = font.features.text font.features.text = compileFeatures( originalFeatures, font, - compileReferencedFiles=True + compileReferencedFiles=True, + namespaceAdditions=dict(someVariable=123), ) # generate the binary @@ -207,7 +208,7 @@ This snippet will compile the features, put them in the font, generate an OTF-CF - probably other stuff * In the namespace, insert all `writer.format*` methods as `format*` function lookalikes to make calling them less cumbersome. * Clean up the output from the writer. - - http://opentypecookbook.com/style-guide.html + - http://opentypecookbook.com/style-guide/ - The identifier system seems to be going haywire and inserting unnecessary blank lines. * Test cases. * Add commandline tool. From d845e9f04beb144f72250fed559d0f391c0ff9e2 Mon Sep 17 00:00:00 2001 From: adbac <126618591+adbac@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:54:35 +0200 Subject: [PATCH 13/13] fix: wrong init path in setup file --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index d8c5ded..1cc1621 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,13 @@ import re -from setuptools import setup +from setuptools import setup _versionRE = re.compile(r'__version__\s*=\s*\"([^\"]+)\"') # read the version number for the settings file -with open('lib/feaPyFoFum/__init__.py', "r") as settings: +with open('Lib/feaPyFoFum/__init__.py', "r") as settings: code = settings.read() found = _versionRE.search(code) - assert found is not None, "glyphConstruction __version__ not found" + assert found is not None, "feaPyFoFum __version__ not found" __version__ = found.group(1) setup(