Ejemplo n.º 1
0
def asFeaAST(self, inFeature=False):
    if self.name and not inFeature:
        f = feaast.LookupBlock(name=self.name)
    elif self.name:
        f = feaast.LookupBlock(name=self.name)
    else:
        f = feaast.Block()
    arranged = arrange(self)

    if arranged and inFeature:
        f = feaast.Block()
        for a in arranged:
            f.statements.append(asFeaAST(a, inFeature))
        return f

    if hasattr(self, "flags"):
        flags = feaast.LookupFlagStatement(self.flags)
        if self.flags & 0x10 and hasattr(self,
                                         "markFilteringSetAsClass"):  # XXX
            # We only need the name, not the contents
            mfs = feaast.GlyphClassDefinition(self.markFilteringSetAsClass,
                                              feaast.GlyphClass([]))
            flags.markFilteringSet = feaast.GlyphClassName(mfs)
        if self.flags & 0xFF00 and hasattr(self,
                                           "markAttachmentSetAsClass"):  # XXX
            mfs = feaast.GlyphClassDefinition(self.markAttachmentSetAsClass,
                                              feaast.GlyphClass([]))
            flags.markAttachment = feaast.GlyphClassName(mfs)

        f.statements.append(flags)

    for x in self.comments:
        f.statements.append(feaast.Comment(x))

    f.statements.append(feaast.Comment(";"))
    lastaddress = self.address
    if lastaddress:
        f.statements.append(
            feaast.Comment("# Original source: %s " %
                           (" ".join([str(x) for x in lastaddress]))))
    for x in self.rules:
        if x.address and x.address != lastaddress:
            f.statements.append(
                feaast.Comment("# Original source: %s " % x.address))
            lastaddress = x.address
        f.statements.append(x.asFeaAST())
    return f
Ejemplo n.º 2
0
def asFeaAST(self):
    lut = lookup_type(self)
    if not lut:
        return feaast.Comment("")

    if lut == 1:  # GSUB 1 Single Substitution
        return feaast.SingleSubstStatement(
            [glyphref(x) for x in self.input],
            [glyphref(x) for x in self.replacement],
            [glyphref(x) for x in self.precontext],
            [glyphref(x) for x in self.postcontext],
            False,
        )
    elif lut == 2:  # GSUB 2 Multiple Substitution
        # Paired rules need to become a set of statements
        if is_paired(self):
            return paired_mult(self)

        return feaast.MultipleSubstStatement(
            [glyphref(x) for x in self.precontext],
            glyphref(self.input[0]),
            [glyphref(x) for x in self.postcontext],
            [glyphref(x) for x in self.replacement],
        )
    elif lut == 3:  # GSUB 3 Alternate Substitution
        return feaast.AlternateSubstStatement(
            [glyphref(x) for x in self.precontext],
            glyphref(self.input[0]),
            [glyphref(x) for x in self.postcontext],
            feaast.GlyphClass(
                [feaast.GlyphName(x) for x in self.replacement[0]]),
        )
    elif lut == 4:  # GSUB 4 Ligature Substitution
        # Paired rules need to become a set of statements
        if is_paired(self):
            return paired_ligature(self)

        return feaast.LigatureSubstStatement(
            [glyphref(x) for x in self.precontext],
            [glyphref(x) for x in self.input],
            [glyphref(x) for x in self.postcontext],
            glyphref(self.replacement[0]),
            False,
        )
    elif lut in [5, 6, 7
                 ]:  # GSUB 5, 6, 7 Different types of contextual substitutions
        raise NotImplementedError("Use the Chain verb for this")
    elif lut == 8:  # GSUB 8 Reverse Chaining Single Substitution
        return feaast.ReverseChainSingleSubstStatement(
            [glyphref(x) for x in self.precontext],
            [glyphref(x) for x in self.postcontext],
            [glyphref(x) for x in self.input],
            [glyphref(self.replacement[0])],
        )
    elif lut >= 9:
        raise NotImplementedError(
            "Invalid GSUB lookup type requested: {}".format(lut))

    raise ValueError("LookupType must be a single positive integer")
Ejemplo n.º 3
0
def asFeaAST(self):
    if self.name:
        f = feaast.LookupBlock(name=self.name)
    else:
        f = feaast.Block()

    if hasattr(self, "flags"):
        flags = feaast.LookupFlagStatement(self.flags)
        if self.flags & 0x10 and hasattr(self,
                                         "markFilteringSetAsClass"):  # XXX
            # We only need the name, not the contents
            mfs = feaast.GlyphClassDefinition(self.markFilteringSetAsClass,
                                              feaast.GlyphClass([]))
            flags.markFilteringSet = feaast.GlyphClassName(mfs)
        if self.flags & 0xFF00 and hasattr(self,
                                           "markAttachmentSetAsClass"):  # XXX
            mfs = feaast.GlyphClassDefinition(self.markAttachmentSetAsClass,
                                              feaast.GlyphClass([]))
            flags.markAttachment = feaast.GlyphClassName(mfs)

        f.statements.append(flags)

    for x in self.comments:
        f.statements.append(feaast.Comment(x))

    f.statements.append(feaast.Comment(";"))
    lastaddress = self.address
    if lastaddress:
        f.statements.append(
            feaast.Comment("# Original source: %s " %
                           (" ".join([str(x) for x in lastaddress]))))
    for x in self.rules:
        if x.address and x.address != lastaddress:
            f.statements.append(
                feaast.Comment("# Original source: %s " % x.address))
            lastaddress = x.address
        if hasattr(x, "note"):
            f.statements.append(
                feaast.Comment("\n".join(
                    [f"# {n}" for n in x.note.split("\n")])))
        f.statements.append(x.asFeaAST())
    return f
Ejemplo n.º 4
0
 def _build_end_locations(self):
     # The statements in the ast only have their start location, but we also
     # need the end location to find the text in between.
     # FIXME: (jany) maybe feaLib could provide that?
     # Add a fake statement at the end, it's the only one that won't get
     # a proper end_location, but its presence will help compute the
     # end_location of the real last statement(s).
     self._lines.append('#')  # Line corresponding to the fake statement
     fake_location = (None, len(self._lines), 1)
     self._doc.statements.append(ast.Comment(text="Sentinel", location=fake_location))
     self._build_end_locations_rec(self._doc)
     # Remove the fake last statement
     self._lines.pop()
     self._doc.statements.pop()
Ejemplo n.º 5
0
def asFeaAST(self):
    """Returns this font's features as a feaLib AST object, for later
    translation to AFDKO code."""
    from fontFeatures import Routine, Chaining

    ff = feaast.FeatureFile()

    add_language_system_statements(self, ff)

    add_gdef(self, ff)

    newRoutines = [self.routines[i] for i in reorderAndResolve(self)]

    # Preamble
    for k in newRoutines:
        assert isinstance(k, Routine)
        if not k.name:
            k.name = self.gensym("Routine_")
        pre = k.feaPreamble(self)
        if k.rules:
            ff.statements.extend(pre)

    for k, v in self.namedClasses.items():
        asclass = _to_inline_class(v)
        ff.statements.append(feaast.GlyphClassDefinition(k, asclass))

    ff.statements.append(feaast.Comment(""))

    for k in newRoutines:
        if k.rules and k.usecount != 1:
            ff.statements.append(k.asFeaAST())

    expandedLanguages = []
    for s, ls in self.scripts_and_languages.items():
        for l in ls:
            expandedLanguages.append((s, l))
    for k, v in self.features.items():
        f = feaast.FeatureBlock(k)
        for n in v:
            f.statements.append(n.asFeaAST(allLanguages=expandedLanguages))
        ff.statements.append(f)
    return ff
Ejemplo n.º 6
0
    def _lookupDefinition(self, lookup):
        mark_attachement = None
        mark_filtering = None

        flags = 0
        if lookup.direction == "RTL":
            flags |= 1
        if not lookup.process_base:
            flags |= 2
        # FIXME: Does VOLT support this?
        # if not lookup.process_ligatures:
        #     flags |= 4
        if not lookup.process_marks:
            flags |= 8
        elif isinstance(lookup.process_marks, str):
            mark_attachement = self._groupName(lookup.process_marks)
        elif lookup.mark_glyph_set is not None:
            mark_filtering = self._groupName(lookup.mark_glyph_set)

        lookupflags = None
        if flags or mark_attachement is not None or mark_filtering is not None:
            lookupflags = ast.LookupFlagStatement(flags, mark_attachement,
                                                  mark_filtering)
        if "\\" in lookup.name:
            # Merge sub lookups as subtables (lookups named “base\sub”),
            # makeotf/feaLib will issue a warning and ignore the subtable
            # statement if it is not a pairpos lookup, though.
            name = lookup.name.split("\\")[0]
            if name.lower() not in self._lookups:
                fealookup = ast.LookupBlock(self._lookupName(name))
                if lookupflags is not None:
                    fealookup.statements.append(lookupflags)
                fealookup.statements.append(ast.Comment("# " + lookup.name))
            else:
                fealookup = self._lookups[name.lower()]
                fealookup.statements.append(ast.SubtableStatement())
                fealookup.statements.append(ast.Comment("# " + lookup.name))
            self._lookups[name.lower()] = fealookup
        else:
            fealookup = ast.LookupBlock(self._lookupName(lookup.name))
            if lookupflags is not None:
                fealookup.statements.append(lookupflags)
            self._lookups[lookup.name.lower()] = fealookup

        if lookup.comments is not None:
            fealookup.statements.append(ast.Comment("# " + lookup.comments))

        contexts = []
        if lookup.context:
            for context in lookup.context:
                prefix = self._context(context.left)
                suffix = self._context(context.right)
                ignore = context.ex_or_in == "EXCEPT_CONTEXT"
                contexts.append([prefix, suffix, ignore, False])
                # It seems that VOLT will create contextual substitution using
                # only the input if there is no other contexts in this lookup.
                if ignore and len(lookup.context) == 1:
                    contexts.append([[], [], False, True])
        else:
            contexts.append([[], [], False, False])

        targetlookup = None
        for prefix, suffix, ignore, chain in contexts:
            if lookup.sub is not None:
                self._gsubLookup(lookup, prefix, suffix, ignore, chain,
                                 fealookup)

            if lookup.pos is not None:
                if self._settings.get("COMPILER_USEEXTENSIONLOOKUPS"):
                    fealookup.use_extension = True
                if prefix or suffix or chain or ignore:
                    if not ignore and targetlookup is None:
                        targetname = self._lookupName(lookup.name + " target")
                        targetlookup = ast.LookupBlock(targetname)
                        fealookup.targets = getattr(fealookup, "targets", [])
                        fealookup.targets.append(targetlookup)
                        self._gposLookup(lookup, targetlookup)
                    self._gposContextLookup(lookup, prefix, suffix, ignore,
                                            fealookup, targetlookup)
                else:
                    self._gposLookup(lookup, fealookup)
Ejemplo n.º 7
0
    def _buildFeatureFile(self, tables):
        doc = ast.FeatureFile()
        statements = doc.statements

        if self._glyphclasses:
            statements.append(ast.Comment("# Glyph classes"))
            statements.extend(self._glyphclasses.values())

        if self._markclasses:
            statements.append(ast.Comment("\n# Mark classes"))
            statements.extend(c[1] for c in sorted(self._markclasses.items()))

        if self._lookups:
            statements.append(ast.Comment("\n# Lookups"))
            for lookup in self._lookups.values():
                statements.extend(getattr(lookup, "targets", []))
                statements.append(lookup)

        # Prune features
        features = self._features.copy()
        for ftag in features:
            scripts = features[ftag]
            for stag in scripts:
                langs = scripts[stag]
                for ltag in langs:
                    langs[ltag] = [
                        l for l in langs[ltag] if l.lower() in self._lookups
                    ]
                scripts[stag] = {t: l for t, l in langs.items() if l}
            features[ftag] = {t: s for t, s in scripts.items() if s}
        features = {t: f for t, f in features.items() if f}

        if features:
            statements.append(ast.Comment("# Features"))
            for ftag, scripts in features.items():
                feature = ast.FeatureBlock(ftag)
                stags = sorted(scripts, key=lambda k: 0 if k == "DFLT" else 1)
                for stag in stags:
                    feature.statements.append(ast.ScriptStatement(stag))
                    ltags = sorted(scripts[stag],
                                   key=lambda k: 0 if k == "dflt" else 1)
                    for ltag in ltags:
                        include_default = True if ltag == "dflt" else False
                        feature.statements.append(
                            ast.LanguageStatement(
                                ltag, include_default=include_default))
                        for name in scripts[stag][ltag]:
                            lookup = self._lookups[name.lower()]
                            lookupref = ast.LookupReferenceStatement(lookup)
                            feature.statements.append(lookupref)
                statements.append(feature)

        if self._gdef and "GDEF" in tables:
            classes = []
            for name in ("BASE", "MARK", "LIGATURE", "COMPONENT"):
                if name in self._gdef:
                    classname = "GDEF_" + name.lower()
                    glyphclass = ast.GlyphClassDefinition(
                        classname, self._gdef[name])
                    statements.append(glyphclass)
                    classes.append(ast.GlyphClassName(glyphclass))
                else:
                    classes.append(None)

            gdef = ast.TableBlock("GDEF")
            gdef.statements.append(ast.GlyphClassDefStatement(*classes))
            statements.append(gdef)

        return doc
Ejemplo n.º 8
0
def asFeaAST(self, do_gdef=True):
    """Returns this font's features as a feaLib AST object, for later
    translation to AFDKO code."""
    from fontFeatures import Routine, Chaining
    ff = feaast.FeatureFile()

    add_language_system_statements(self, ff)

    if do_gdef:
        add_gdef(self, ff)

    # In OpenType, we need to rearrange the routines such that all rules for
    # a given languagesystem, lookup type, and lookup flags, appear in the
    # same lookup. Also, lookups with the same languagesystem need to appear next
    # to one another, because FEA syntax is stupid.

    # Now arrange them by type/etc.
    for k, v in self.features.items():
        for reference in v:
            routine = reference.routine
            # If a rule has >1 language it must first be split
            newrules = []
            for r in routine.rules:
                if len(r.languages or []) > 1:
                    for language in r.languages:
                        newrule = copy.copy(r)
                        newrule.languages = [language]
                        newrules.append(newrule)
                else:
                    newrules.append(r)
            routine.rules = newrules
            partitioned = self.partitionRoutine(
                routine, lambda rule: tuple([
                    tuple(rule.languages or []),
                    type(rule),
                    lookup_type(rule)
                ]))
            if routine.name and partitioned and len(partitioned) > 1:
                for p in partitioned:
                    rule = p.rules[0]
                    language = (rule.languages or [("DFLT", "dflt")])[0]
                    p.name = p.name + "%s_%s_%s_%i" % (
                        language[0].strip(), language[1].strip(),
                        type(rule).__name__, lookup_type(rule))

    for r in self.routines:
        r.usecount = 0
        # Bubble up flags and languages
        if r.rules and not r.flags:
            r.flags = r.rules[0].flags
        if r.rules and not r.languages:
            r.languages = r.rules[0].languages

    for k, v in self.features.items():
        # Similarly split routines with multiple languages
        references = []
        for reference in v:
            routine = reference.routine
            if len(routine.languages or []) > 1:
                splitroutines = []
                for language in routine.languages:
                    # This is wrong. It should really be a new reference to the
                    # same routine. But this will do for now.
                    newreference = copy.copy(reference)
                    newreference.languages = [language]
                    references.append(newreference)
            else:
                references.append(reference)
                reference.languages = routine.languages
        self.features[k] = references
        # Order the arranged routines by language
        # new_references = list(sorted(v, key=lambda x: tuple(x.routine.languages or [])))

    # Next, we'll ensure that all chaining lookups are resolved and in the right order
    newRoutines = [self.routines[i] for i in reorderAndResolve(self)]

    # Preamble
    for k in newRoutines:
        assert isinstance(k, Routine)
        if not k.name and k.usecount != 1:
            k.name = self.gensym("Routine_")
        pre = k.feaPreamble(self)
        if k.rules:
            ff.statements.extend(pre)

    for k, v in self.namedClasses.items():
        asclass = _to_inline_class(v)
        ff.statements.append(feaast.GlyphClassDefinition(k, asclass))

    ff.statements.append(feaast.Comment(""))

    for k in newRoutines:
        if k.rules:
            ff.statements.append(k.asFeaAST())

    for k, v in self.features.items():
        for routine in v:
            # Putting each routine in its own feature saves problems...
            lang = routine.languages
            f = feaast.FeatureBlock(k)
            if lang:
                f.statements.append(feaast.ScriptStatement(lang[0][0]))
                f.statements.append(
                    feaast.LanguageStatement("%4s" % lang[0][1]))
            f.statements.append(routine.asFeaAST(expand=k == "aalt"))
            ff.statements.append(f)
    return ff