diff --git a/docs/font-metadata.md b/docs/font-metadata.md index c4d908f..9fb9b65 100644 --- a/docs/font-metadata.md +++ b/docs/font-metadata.md @@ -141,9 +141,25 @@ This section contains the advance widths of all the glyphs. This section lists contextual substitutions. If OpenType contextual substitution support is unavailable, this section can be used to select replacement glyphs and their metadata. Each item in `inputGlyphs`, `backtrackGlyphs`, and `lookaheadGlyphs` is a class of matching glyphs. The `index` in a substitution identifies the item in `inputGlyphs` that should be replaced. +When a contextual lookup uses an OpenType `MarkAttachmentType`, `markAttachmentClasses` lists the glyphs in each attachment class and the rule's `markAttachmentClass` identifies the applicable class. Marks outside that class are ignored while matching the rule. + ```js { ... + "markAttachmentClasses": { + "gorgon": [ + "digorgon", + "digorgonDottedLeftAbove", + "digorgonDottedLeftBelow", + "digorgonDottedRight", + "gorgonAbove", + "gorgonDottedLeft", + "gorgonDottedRight", + "trigorgon", + "trigorgonDottedLeftAbove", + "trigorgonDottedRight" + ] + }, "contextualSubstitutions": [ { "inputGlyphs": [ @@ -165,7 +181,8 @@ This section lists contextual substitutions. If OpenType contextual substitution "from": "yporroi", "to": "yporroi.gorgon" } - ] + ], + "markAttachmentClass": "gorgon" } ], ... diff --git a/fonts/neanes.metadata.json b/fonts/neanes.metadata.json index 0f48088..2497cbf 100644 --- a/fonts/neanes.metadata.json +++ b/fonts/neanes.metadata.json @@ -7898,6 +7898,20 @@ ] } }, + "markAttachmentClasses": { + "gorgon": [ + "digorgon", + "digorgonDottedLeftAbove", + "digorgonDottedLeftBelow", + "digorgonDottedRight", + "gorgonAbove", + "gorgonDottedLeft", + "gorgonDottedRight", + "trigorgon", + "trigorgonDottedLeftAbove", + "trigorgonDottedRight" + ] + }, "contextualSubstitutions": [ { "inputGlyphs": [ @@ -7964,7 +7978,8 @@ "from": "yporroi", "to": "yporroi.gorgon" } - ] + ], + "markAttachmentClass": "gorgon" }, { "inputGlyphs": [ @@ -7987,7 +8002,8 @@ "from": "yporroi", "to": "yporroi.digorgon" } - ] + ], + "markAttachmentClass": "gorgon" }, { "inputGlyphs": [ @@ -8009,7 +8025,8 @@ "from": "yporroi", "to": "yporroi.trigorgon" } - ] + ], + "markAttachmentClass": "gorgon" }, { "inputGlyphs": [ diff --git a/fonts/neanesengraving.metadata.json b/fonts/neanesengraving.metadata.json index dee25a0..21e851f 100644 --- a/fonts/neanesengraving.metadata.json +++ b/fonts/neanesengraving.metadata.json @@ -7898,6 +7898,20 @@ ] } }, + "markAttachmentClasses": { + "gorgon": [ + "digorgon", + "digorgonDottedLeftAbove", + "digorgonDottedLeftBelow", + "digorgonDottedRight", + "gorgonAbove", + "gorgonDottedLeft", + "gorgonDottedRight", + "trigorgon", + "trigorgonDottedLeftAbove", + "trigorgonDottedRight" + ] + }, "contextualSubstitutions": [ { "inputGlyphs": [ @@ -7964,7 +7978,8 @@ "from": "yporroi", "to": "yporroi.gorgon" } - ] + ], + "markAttachmentClass": "gorgon" }, { "inputGlyphs": [ @@ -7987,7 +8002,8 @@ "from": "yporroi", "to": "yporroi.digorgon" } - ] + ], + "markAttachmentClass": "gorgon" }, { "inputGlyphs": [ @@ -8009,7 +8025,8 @@ "from": "yporroi", "to": "yporroi.trigorgon" } - ] + ], + "markAttachmentClass": "gorgon" }, { "inputGlyphs": [ diff --git a/fonts/neanesstathisseries.metadata.json b/fonts/neanesstathisseries.metadata.json index 0309ce2..3fab4af 100644 --- a/fonts/neanesstathisseries.metadata.json +++ b/fonts/neanesstathisseries.metadata.json @@ -7930,6 +7930,20 @@ ] } }, + "markAttachmentClasses": { + "gorgon": [ + "digorgon", + "digorgonDottedLeftAbove", + "digorgonDottedLeftBelow", + "digorgonDottedRight", + "gorgonAbove", + "gorgonDottedLeft", + "gorgonDottedRight", + "trigorgon", + "trigorgonDottedLeftAbove", + "trigorgonDottedRight" + ] + }, "contextualSubstitutions": [ { "inputGlyphs": [ @@ -7996,7 +8010,8 @@ "from": "yporroi", "to": "yporroi.gorgon" } - ] + ], + "markAttachmentClass": "gorgon" }, { "inputGlyphs": [ @@ -8019,7 +8034,8 @@ "from": "yporroi", "to": "yporroi.digorgon" } - ] + ], + "markAttachmentClass": "gorgon" }, { "inputGlyphs": [ @@ -8041,7 +8057,8 @@ "from": "yporroi", "to": "yporroi.trigorgon" } - ] + ], + "markAttachmentClass": "gorgon" }, { "inputGlyphs": [ diff --git a/fonts/neanesstathisseriesengraving.metadata.json b/fonts/neanesstathisseriesengraving.metadata.json index 89e5560..2701d97 100644 --- a/fonts/neanesstathisseriesengraving.metadata.json +++ b/fonts/neanesstathisseriesengraving.metadata.json @@ -7930,6 +7930,20 @@ ] } }, + "markAttachmentClasses": { + "gorgon": [ + "digorgon", + "digorgonDottedLeftAbove", + "digorgonDottedLeftBelow", + "digorgonDottedRight", + "gorgonAbove", + "gorgonDottedLeft", + "gorgonDottedRight", + "trigorgon", + "trigorgonDottedLeftAbove", + "trigorgonDottedRight" + ] + }, "contextualSubstitutions": [ { "inputGlyphs": [ @@ -7996,7 +8010,8 @@ "from": "yporroi", "to": "yporroi.gorgon" } - ] + ], + "markAttachmentClass": "gorgon" }, { "inputGlyphs": [ @@ -8019,7 +8034,8 @@ "from": "yporroi", "to": "yporroi.digorgon" } - ] + ], + "markAttachmentClass": "gorgon" }, { "inputGlyphs": [ @@ -8041,7 +8057,8 @@ "from": "yporroi", "to": "yporroi.trigorgon" } - ] + ], + "markAttachmentClass": "gorgon" }, { "inputGlyphs": [ diff --git a/scripts/generate-font-metadata.py b/scripts/generate-font-metadata.py index afd170d..e5e782a 100644 --- a/scripts/generate-font-metadata.py +++ b/scripts/generate-font-metadata.py @@ -365,7 +365,12 @@ def asdict(self): if alternates: d["glyphsWithAlternates"] = alternates - contextual_substitutions = self.contextual_substitutions() + contextual_substitutions, mark_attachment_classes = ( + self.contextual_substitutions() + ) + if mark_attachment_classes: + d["markAttachmentClasses"] = mark_attachment_classes + if contextual_substitutions: d["contextualSubstitutions"] = contextual_substitutions @@ -450,8 +455,11 @@ def contextual_substitutions(self): substitutions_by_lookup = self._substitutions_by_lookup(feature_file) contextual_substitutions = [] + mark_attachment_classes = {} - for statement in self._walk_statements(feature_file): + for statement, lookup_flag in self._walk_statements_with_lookup_flags( + feature_file + ): if isinstance(statement, ast.ChainContextSubstStatement): input_glyphs = [self._glyph_set(g) for g in statement.glyphs] backtrack_glyphs = [self._glyph_set(g) for g in statement.prefix] @@ -498,16 +506,25 @@ def contextual_substitutions(self): continue if substitutions: - contextual_substitutions.append( - { - "inputGlyphs": input_glyphs, - "backtrackGlyphs": backtrack_glyphs, - "lookaheadGlyphs": lookahead_glyphs, - "substitutions": substitutions, - } - ) + contextual_substitution = { + "inputGlyphs": input_glyphs, + "backtrackGlyphs": backtrack_glyphs, + "lookaheadGlyphs": lookahead_glyphs, + "substitutions": substitutions, + } + + if lookup_flag is not None and lookup_flag.markAttachment is not None: + mark_attachment_class = lookup_flag.markAttachment.glyphclass.name + mark_attachment_classes[mark_attachment_class] = self._glyph_set( + lookup_flag.markAttachment + ) + contextual_substitution["markAttachmentClass"] = ( + mark_attachment_class + ) - return contextual_substitutions + contextual_substitutions.append(contextual_substitution) + + return contextual_substitutions, mark_attachment_classes def _substitutions_by_lookup(self, node): substitutions_by_lookup = {} @@ -549,6 +566,19 @@ def _walk_statements(self, node): yield statement yield from self._walk_statements(statement) + def _walk_statements_with_lookup_flags(self, node, lookup_flag=None): + current_lookup_flag = lookup_flag + + for statement in getattr(node, "statements", []): + if isinstance(statement, ast.LookupFlagStatement): + current_lookup_flag = statement + continue + + yield statement, current_lookup_flag + yield from self._walk_statements_with_lookup_flags( + statement, current_lookup_flag + ) + @staticmethod def _glyph_set(glyph_expr): return sorted(glyph_expr.glyphSet())