Example #1
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")
Example #2
0
def makeQuranSajdaLine(font):
    pos = font["uni06D7"].getBounds(font).yMax
    thickness = font.info.postscriptUnderlineThickness
    minwidth = 100

    _, gdefclasses = findGDEF(font)
    # collect glyphs grouped by their widths rounded by 100 units, we will use
    # them to decide the widths of over/underline glyphs we will draw
    widths = {}
    for glyph in font:
        u = glyph.unicode
        if ((u is None ) or (0x0600 <= u <= 0x06FF) or u == ord(" ")) \
        and glyph.width > 0:
            width = round(glyph.width / minwidth) * minwidth
            width = width > minwidth and width or minwidth
            if not width in widths:
                widths[width] = []
            widths[width].append(glyph.name)

    base = 'uni0305'
    drawOverline(font, base, 0x0305, pos, thickness, 500)

    mark = ast.FeatureBlock("mark")
    overset = ast.GlyphClassDefinition("OverSet", ast.GlyphClass([base]))
    lookupflag = ast.LookupFlagStatement(
        markFilteringSet=ast.GlyphClassName(overset))
    mark.statements.extend([overset, lookupflag])

    for width in sorted(widths.keys()):
        # for each width group we create an over/underline glyph with the same
        # width, and add a contextual substitution lookup to use it when an
        # over/underline follows any glyph in this group
        replace = f"uni0305.{width}"
        drawOverline(font, replace, None, pos, thickness, width)
        sub = ast.SingleSubstStatement([ast.GlyphName(base)],
                                       [ast.GlyphName(replace)],
                                       [ast.GlyphClass(widths[width])], [],
                                       False)
        gdefclasses.markGlyphs.append(replace)
        mark.statements.append(sub)

    font.features.text.statements.append(mark)
Example #3
0
    def _gsubLookup(self, lookup, prefix, suffix, ignore, chain, fealookup):
        statements = fealookup.statements

        sub = lookup.sub
        for key, val in sub.mapping.items():
            if not key or not val:
                path, line, column = sub.location
                log.warning(
                    f"{path}:{line}:{column}: Ignoring empty substitution")
                continue
            statement = None
            glyphs = self._coverage(key)
            replacements = self._coverage(val)
            if ignore:
                chain_context = (prefix, glyphs, suffix)
                statement = ast.IgnoreSubstStatement([chain_context])
            elif isinstance(sub, VAst.SubstitutionSingleDefinition):
                assert len(glyphs) == 1
                assert len(replacements) == 1
                statement = ast.SingleSubstStatement(glyphs, replacements,
                                                     prefix, suffix, chain)
            elif isinstance(sub,
                            VAst.SubstitutionReverseChainingSingleDefinition):
                assert len(glyphs) == 1
                assert len(replacements) == 1
                statement = ast.ReverseChainSingleSubstStatement(
                    prefix, suffix, glyphs, replacements)
            elif isinstance(sub, VAst.SubstitutionMultipleDefinition):
                assert len(glyphs) == 1
                statement = ast.MultipleSubstStatement(prefix, glyphs[0],
                                                       suffix, replacements,
                                                       chain)
            elif isinstance(sub, VAst.SubstitutionLigatureDefinition):
                assert len(replacements) == 1
                statement = ast.LigatureSubstStatement(prefix, glyphs, suffix,
                                                       replacements[0], chain)
            else:
                raise NotImplementedError(sub)
            statements.append(statement)
Example #4
0
    def parse_substitute_(self):
        assert self.cur_token_ in {"substitute", "sub", "reversesub", "rsub"}
        location = self.cur_token_location_
        reverse = self.cur_token_ in {"reversesub", "rsub"}
        old_prefix, old, lookups, values, old_suffix, hasMarks = \
            self.parse_glyph_pattern_(vertical=False)
        if any(values):
            raise FeatureLibError(
                "Substitution statements cannot contain values", location)
        new = []
        if self.next_token_ == "by":
            keyword = self.expect_keyword_("by")
            while self.next_token_ != ";":
                gc = self.parse_glyphclass_(accept_glyphname=True)
                new.append(gc)
        elif self.next_token_ == "from":
            keyword = self.expect_keyword_("from")
            new = [self.parse_glyphclass_(accept_glyphname=False)]
        else:
            keyword = None
        self.expect_symbol_(";")
        if len(new) is 0 and not any(lookups):
            raise FeatureLibError(
                'Expected "by", "from" or explicit lookup references',
                self.cur_token_location_)

        # GSUB lookup type 3: Alternate substitution.
        # Format: "substitute a from [a.1 a.2 a.3];"
        if keyword == "from":
            if reverse:
                raise FeatureLibError(
                    'Reverse chaining substitutions do not support "from"',
                    location)
            if len(old) != 1 or len(old[0].glyphSet()) != 1:
                raise FeatureLibError(
                    'Expected a single glyph before "from"',
                    location)
            if len(new) != 1:
                raise FeatureLibError(
                    'Expected a single glyphclass after "from"',
                    location)
            return ast.AlternateSubstStatement(
                location, old_prefix, old[0], old_suffix, new[0])

        num_lookups = len([l for l in lookups if l is not None])

        # GSUB lookup type 1: Single substitution.
        # Format A: "substitute a by a.sc;"
        # Format B: "substitute [one.fitted one.oldstyle] by one;"
        # Format C: "substitute [a-d] by [A.sc-D.sc];"
        if (not reverse and len(old) == 1 and len(new) == 1 and
                num_lookups == 0):
            glyphs = sorted(list(old[0].glyphSet()))
            replacements = sorted(list(new[0].glyphSet()))
            if len(replacements) == 1:
                replacements = replacements * len(glyphs)
            if len(glyphs) != len(replacements):
                raise FeatureLibError(
                    'Expected a glyph class with %d elements after "by", '
                    'but found a glyph class with %d elements' %
                    (len(glyphs), len(replacements)), location)
            return ast.SingleSubstStatement(location,
                                            dict(zip(glyphs, replacements)),
                                            old_prefix, old_suffix,
                                            forceChain=hasMarks)

        # GSUB lookup type 2: Multiple substitution.
        # Format: "substitute f_f_i by f f i;"
        if (not reverse and
                len(old) == 1 and len(old[0].glyphSet()) == 1 and
                len(new) > 1 and max([len(n.glyphSet()) for n in new]) == 1 and
                num_lookups == 0):
            return ast.MultipleSubstStatement(
                location, old_prefix, tuple(old[0].glyphSet())[0], old_suffix,
                tuple([list(n.glyphSet())[0] for n in new]))

        # GSUB lookup type 4: Ligature substitution.
        # Format: "substitute f f i by f_f_i;"
        if (not reverse and
                len(old) > 1 and len(new) == 1 and
                len(new[0].glyphSet()) == 1 and
                num_lookups == 0):
            return ast.LigatureSubstStatement(
                location, old_prefix, old, old_suffix,
                list(new[0].glyphSet())[0], forceChain=hasMarks)

        # GSUB lookup type 8: Reverse chaining substitution.
        if reverse:
            if len(old) != 1:
                raise FeatureLibError(
                    "In reverse chaining single substitutions, "
                    "only a single glyph or glyph class can be replaced",
                    location)
            if len(new) != 1:
                raise FeatureLibError(
                    'In reverse chaining single substitutions, '
                    'the replacement (after "by") must be a single glyph '
                    'or glyph class', location)
            if num_lookups != 0:
                raise FeatureLibError(
                    "Reverse chaining substitutions cannot call named lookups",
                    location)
            glyphs = sorted(list(old[0].glyphSet()))
            replacements = sorted(list(new[0].glyphSet()))
            if len(replacements) == 1:
                replacements = replacements * len(glyphs)
            if len(glyphs) != len(replacements):
                raise FeatureLibError(
                    'Expected a glyph class with %d elements after "by", '
                    'but found a glyph class with %d elements' %
                    (len(glyphs), len(replacements)), location)
            return ast.ReverseChainSingleSubstStatement(
                location, old_prefix, old_suffix,
                dict(zip(glyphs, replacements)))

        # GSUB lookup type 6: Chaining contextual substitution.
        assert len(new) == 0, new
        rule = ast.ChainContextSubstStatement(
            location, old_prefix, old, old_suffix, lookups)
        return rule