Esempio n. 1
0
    def _gposContextLookup(self, lookup, prefix, suffix, ignore, fealookup,
                           targetlookup):
        statements = fealookup.statements

        assert not lookup.reversal

        pos = lookup.pos
        if isinstance(pos, VAst.PositionAdjustPairDefinition):
            for (idx1, idx2), (pos1, pos2) in pos.adjust_pair.items():
                glyphs1 = self._coverage(pos.coverages_1[idx1 - 1])
                glyphs2 = self._coverage(pos.coverages_2[idx2 - 1])
                assert len(glyphs1) == 1
                assert len(glyphs2) == 1
                glyphs = (glyphs1[0], glyphs2[0])

                if ignore:
                    statement = ast.IgnorePosStatement([(prefix, glyphs,
                                                         suffix)])
                else:
                    lookups = (targetlookup, targetlookup)
                    statement = ast.ChainContextPosStatement(
                        prefix, glyphs, suffix, lookups)
                statements.append(statement)
        elif isinstance(pos, VAst.PositionAdjustSingleDefinition):
            glyphs = [ast.GlyphClass()]
            for a, b in pos.adjust_single:
                glyph = self._coverage(a)
                glyphs[0].extend(glyph)

            if ignore:
                statement = ast.IgnorePosStatement([(prefix, glyphs, suffix)])
            else:
                statement = ast.ChainContextPosStatement(
                    prefix, glyphs, suffix, [targetlookup])
            statements.append(statement)
        elif isinstance(pos, VAst.PositionAttachDefinition):
            glyphs = [ast.GlyphClass()]
            for coverage, _ in pos.coverage_to:
                glyphs[0].extend(self._coverage(coverage))

            if ignore:
                statement = ast.IgnorePosStatement([(prefix, glyphs, suffix)])
            else:
                statement = ast.ChainContextPosStatement(
                    prefix, glyphs, suffix, [targetlookup])
            statements.append(statement)
        else:
            raise NotImplementedError(pos)
Esempio n. 2
0
    def parse(self):
        """Parse the feature code.

        Returns:
            A ``FontFeatures`` object containing the rules of this file.
        """

        # Borrow glyph classes
        for name, members in self.ff.namedClasses.items():
            glyphclass = ast.GlyphClassDefinition(
                name, ast.GlyphClass([m for m in members]))
            self.parser.glyphclasses_.define(name, glyphclass)

        parsetree = self.parser.parse()

        # Return glyph classes
        if len(self.parser.glyphclasses_.scopes_):
            for name, definition in self.parser.glyphclasses_.scopes_[
                    -1].items():
                if isinstance(definition, ast.MarkClass):
                    pass
                    # self.ff.namedClasses[name] = list(definition.glyphs.keys())
                else:
                    self.ff.namedClasses[name] = definition.glyphs.glyphs

        self.features_ = {}
        parsetree.build(self)
        return self.ff
Esempio n. 3
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
Esempio n. 4
0
    def parse_glyphclass_(self, accept_glyphname):
        if (accept_glyphname and
                self.next_token_type_ in (Lexer.NAME, Lexer.CID)):
            glyph = self.expect_glyph_()
            return ast.GlyphName(self.cur_token_location_, glyph)
        if self.next_token_type_ is Lexer.GLYPHCLASS:
            self.advance_lexer_()
            gc = self.glyphclasses_.resolve(self.cur_token_)
            if gc is None:
                raise FeatureLibError(
                    "Unknown glyph class @%s" % self.cur_token_,
                    self.cur_token_location_)
            if isinstance(gc, ast.MarkClass):
                return ast.MarkClassName(self.cur_token_location_, gc)
            else:
                return ast.GlyphClassName(self.cur_token_location_, gc)

        self.expect_symbol_("[")
        glyphs = set()
        location = self.cur_token_location_
        while self.next_token_ != "]":
            if self.next_token_type_ is Lexer.NAME:
                glyph = self.expect_glyph_()
                if self.next_token_ == "-":
                    range_location = self.cur_token_location_
                    range_start = glyph
                    self.expect_symbol_("-")
                    range_end = self.expect_glyph_()
                    glyphs.update(self.make_glyph_range_(range_location,
                                                         range_start,
                                                         range_end))
                else:
                    glyphs.add(glyph)
            elif self.next_token_type_ is Lexer.CID:
                glyph = self.expect_glyph_()
                if self.next_token_ == "-":
                    range_location = self.cur_token_location_
                    range_start = self.cur_token_
                    self.expect_symbol_("-")
                    range_end = self.expect_cid_()
                    glyphs.update(self.make_cid_range_(range_location,
                                                       range_start, range_end))
                else:
                    glyphs.add("cid%05d" % self.cur_token_)
            elif self.next_token_type_ is Lexer.GLYPHCLASS:
                self.advance_lexer_()
                gc = self.glyphclasses_.resolve(self.cur_token_)
                if gc is None:
                    raise FeatureLibError(
                        "Unknown glyph class @%s" % self.cur_token_,
                        self.cur_token_location_)
                glyphs.update(gc.glyphSet())
            else:
                raise FeatureLibError(
                    "Expected glyph name, glyph range, "
                    "or glyph class reference",
                    self.next_token_location_)
        self.expect_symbol_("]")
        return ast.GlyphClass(location, glyphs)
Esempio n. 5
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")
Esempio n. 6
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
Esempio n. 7
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)
Esempio n. 8
0
    def _glyphDefinition(self, glyph):
        try:
            self._glyph_map[glyph.name] = self._glyph_order[glyph.id]
        except TypeError:
            pass

        if glyph.type in ("BASE", "MARK", "LIGATURE", "COMPONENT"):
            if glyph.type not in self._gdef:
                self._gdef[glyph.type] = ast.GlyphClass()
            self._gdef[glyph.type].glyphs.append(self._glyphName(glyph.name))

        if glyph.type == "MARK":
            self._marks.add(glyph.name)
        elif glyph.type == "LIGATURE":
            self._ligatures[glyph.name] = glyph.components
Esempio n. 9
0
 def asFea(self, indent=""):
     res = ""
     l = len(self.glyphs.glyphSet())
     for i, glyph in enumerate(self.glyphs.glyphSet()):
         if i > 0:
             res += "\n" + indent
         res += "sub "
         if len(self.prefix) or len(self.suffix):
             if len(self.prefix):
                 res += " ".join(map(asFea, self.prefix)) + " "
             res += asFea(glyph) + "'"    # even though we really only use 1
             if len(self.suffix):
                 res += " " + " ".join(map(asFea, self.suffix))
         else:
             res += asFea(glyph)
         res += " from "
         replacements = ast.GlyphClass(glyphs=self.replacements.glyphSet()[i::l], location=self.location)
         res += asFea(replacements)
         res += ";"
     return res
Esempio n. 10
0
def makeGlyphClassDefinition(className, members):
    glyphNames = [ast.GlyphName(g) for g in members]
    glyphClass = ast.GlyphClass(glyphNames)
    classDef = ast.GlyphClassDefinition(className, glyphClass)
    return classDef
Esempio n. 11
0
def _to_inline_class(glyphs):
    return feaast.GlyphClass([feaast.GlyphName(x) for x in glyphs])
Esempio n. 12
0
def glyphref(g):
    if len(g) == 1:
        return feaast.GlyphName(g[0])
    return feaast.GlyphClass([feaast.GlyphName(x) for x in g])
Esempio n. 13
0
 def _enum(self, enum):
     return ast.GlyphClass(self._coverage(enum.enum))
Esempio n. 14
0
 def test_glyphname_escape(self):
     statement = ast.GlyphClass()
     for name in ("BASE", "NULL", "foo", "a"):
         statement.append(ast.GlyphName(name))
     self.assertEqual(statement.asFea(), r"[\BASE \NULL foo a]")