Example #1
0
def arrange_by_language(self):
    from fontFeatures import Routine

    if not self.languages:
        return
    languages = {}

    def add_lang(p, r):
        nonlocal languages
        if not p in languages:
            languages[p] = []
        languages[p].extend(r)

    for s, l in self.languages:
        if l == "*":
            add_lang((s, "dflt"), self.rules)
        else:
            add_lang((s, l), self.rules)

    if len(languages.keys()) < 2:
        return
    routines = []
    for k, v in languages.items():
        r = Routine(rules=v, languages=[k], flags=self.flags)
        if self.name:
            r.name = self.name + "_" + k[0] + "_" + k[1]
        routines.append(r)
    return routines
Example #2
0
def test_add():
    r1 = Routine(name="One")
    r2 = Routine(name="Two")
    f1 = FontFeatures()
    f2 = FontFeatures()
    f1.namedClasses["One"] = ["A", "B", "C"]
    f1.glyphclasses["a"] = "base"
    f1.addFeature("onex", [r1])
    f2.namedClasses["Two"] = ["d", "e", "f"]
    f2.addFeature("twox", [r2])
    f2.glyphclasses["d"] = "mark"
    f2.anchors["a"] = {"top": (300, 200)}

    combined = f1 + f2
    assert combined.namedClasses["One"] == ["A", "B", "C"]
    assert combined.namedClasses["Two"] == ["d", "e", "f"]
    assert "onex" in combined.features
    assert combined.features["onex"][0].routine == r1
    assert "twox" in combined.features
    assert combined.features["twox"][0].routine == r2
    assert combined.glyphclasses["a"] == "base"
    assert combined.glyphclasses["d"] == "mark"

    el = combined.toXML()
    assert etree.tostring(el).decode() == expected
Example #3
0
def arrange_by_language(self):
    from fontFeatures import Routine

    languages = {}

    def add_lang(p, r):
        nonlocal languages
        if not p in languages:
            languages[p] = []
        languages[p].append(r)

    for r in self.rules:
        if not r.languages:
            add_lang(("DFLT", "dflt"), r)
        else:
            for lang in r.languages:
                newrule = copy.deepcopy(r)
                newrule.languages = [lang]
                add_lang(lang, newrule)

    if len(languages.keys()) < 2:
        return
    routines = []
    for k, v in languages.items():
        r = Routine(rules=v, languages=[k], flags=self.flags)
        if self.name:
            r.name = self.name + "_" + k[0] + "_" + k[1]
        routines.append(r)
    return routines
 def test_MergeMultipleSingleSubstitutions_1(self):
     r1 = Routine(rules=[
         Substitution([["a", "y"]], [["b", "z"]]),
         Substitution([["b", "d", "a"]], [["c", "e", "f"]]),
     ])
     Optimizer(FontFeatures()).optimize_routine(r1, level=1)
     self.assertEqual(r1.asFea(), "    sub [a y b d] by [c z c e];\n")
Example #5
0
    def makeBuffer(self, before_after="before"):
        buf = VariationAwareBuffer(
            self.project.font,
            direction=self.buffer_direction,
        )
        if self.project.variations:
            buf.location = self.location
            buf.vf = self.project.variations
        buf.items = [
            VariationAwareBufferItem.new_glyph(g, self.project.font, buf)
            for g in self.representative_string
        ]
        shaper = Shaper(self.project.fontfeatures, self.project.font)

        shaper.execute(buf, features=self.makeShaperFeatureArray())
        routine = Routine(rules=[self.rule])
        # print("Before shaping: ", buf.serialize())
        if before_after == "after" and self.rule:
            print("Before application: ", buf.serialize())
            print(self.rule.asFea())
            buf.clear_mask()  # XXX
            try:
                routine.apply_to_buffer(buf)
            except Exception as e:
                print("Couldn't shape: " + str(e))
            print("After application: ", buf.serialize())
        return buf
 def test_MergeMultipleSingleSubstitutions_2(self):
     r1 = Routine(rules=[
         Substitution([["a"]], [["b"]]),
         Substitution([["b"]], [["c"]]),
         Substitution([["d"]], [["e"]]),
         Substitution([["y"]], [["z"]]),
     ])
     Optimizer(FontFeatures()).optimize_routine(r1, level=1)
     self.assertEqual(r1.asFea(), "    sub [a b d y] by [c c e z];\n")
 def test_GlyphClasses(self):
     r1 = Routine(rules=[
         Substitution([["a", "b", "c", "d", "e", "f", "g", "h"]], [["z"]]),
     ])
     ff = FontFeatures()
     Optimizer(ff).optimize_routine(r1, level=1)
     self.assertEqual(r1.asFea(), "    sub @class1 by z;\n")
     self.assertEqual(ff.namedClasses["class1"],
                      ("a", "b", "c", "d", "e", "f", "g", "h"))
    def test_complex(self):
        pos1 = Substitution(["a"], ["b"])
        pos2 = Substitution(["b"], ["c"])
        r1 = Routine(rules=[pos1], name="dummy1")
        r2 = Routine(rules=[pos2], name="dummy2")
        rr1 = RoutineReference(routine=r1)
        rr2 = RoutineReference(routine=r2)

        c = Chaining([["a"], ["b"]], lookups=[[rr1, rr2], None])
        self.assertEqual(c.asFea(), "sub a' lookup dummy1 lookup dummy2 b';")
Example #9
0
def test_routine_partition_not_needed():
    f = FontFeatures()

    s1 = Substitution([["A"]], [["A.grk"]], languages=["grek/*"])
    s2 = Substitution([["A"]], [["A.esp"]], languages=["grek/*"])
    r = Routine(rules=[s1, s2], flags=0x2)

    f.routines.append(r)
    f.partitionRoutine(r, lambda rule: tuple(rule.languages or []))

    assert len(f.routines) == 1

    r.rules = []
    f.partitionRoutine(r, lambda rule: tuple(rule.languages or []))
    assert len(f.routines) == 1
Example #10
0
 def roundTrip(self, thing, dependencies):
     f = FontFeatures()
     f.routines.extend(dependencies)
     rt = thing.__class__.fromXML(thing.toXML())
     f.routines.append(Routine(rules=[rt]))
     f.resolveAllRoutines()
     self.assertEqual(rt.asFea(), thing.asFea())
Example #11
0
 def xmlToFontFeatures(self):
     routines = {}
     warnings = []
     for xmlroutine in self.xml.find("routines"):
         if "computed" in xmlroutine.attrib:
             r = ComputedRoutine.fromXML(xmlroutine)
             r.project = self
         elif "divider" in xmlroutine.attrib:
             r = DividerRoutine.fromXML(xmlroutine)
         else:
             r = Routine.fromXML(xmlroutine)
         routines[r.name] = r
         self.fontfeatures.routines.append(r)
     for xmlfeature in self.xml.find("features"):
         # Temporary until we refactor fontfeatures
         featurename = xmlfeature.get("name")
         self.fontfeatures.features[featurename] = []
         for r in xmlfeature:
             routinename = r.get("name")
             if routinename in routines:
                 self.fontfeatures.addFeature(featurename,
                                              [routines[routinename]])
             else:
                 warnings.append(
                     "Lost routine %s referenced in feature %s" %
                     (routinename, featurename))
     return warnings  # We don't do anything with them yet
Example #12
0
def fromXML(klass, el):
    """Creates a FontFeatures object from a lxml Element object."""
    f = klass()
    from fontFeatures import RoutineReference, Routine

    for part in el:
        if part.tag == "namedclasses":
            for cl_el in part:
                f.namedClasses[cl_el.get("name")] = [g.text for g in cl_el]
        elif part.tag == "routines":
            f.routines = [Routine.fromXML(r) for r in part]
        elif part.tag == "features":
            for feat in part:
                f.features[feat.get("name")] = [
                    RoutineReference.fromXML(x) for x in feat
                ]
        elif part.tag == "anchors":
            for glyph in part:
                f.anchors[glyph.get("name")] = {
                    el.get("name"): (int(el.get("x")), int(el.get("y"))) for el in glyph
                }
        elif part.tag == "glyphclasses":
            for cl_el in part:
                f.glyphclasses[cl_el.get("name")] = cl_el.get("class")

    for refs in f.features.values():
        for ref in refs:
            ref.resolve(f)
    return f
Example #13
0
    def test_simple_sub(self):
        pos = Substitution(["a"], ["b"])
        r = Routine(rules=[pos], name="dummy")
        rr = RoutineReference(routine=r)

        c = Chaining([["a"], ["b"]], lookups=[[rr], None])
        self.assertEqual(c.asFea(), "sub a' lookup dummy b';")
def test_multiple_languages_routine():
    f = FontFeatures()
    s1 = Substitution([["a"]], ["b"])
    expected = """languagesystem arab URD;
languagesystem arab FAR;

lookup Routine_1 {
    lookupflag 0;
    ;
    sub a by b;
} Routine_1;

feature locl {
    script arab;
    language URD;
            lookup Routine_1;

} locl;

feature locl {
    script arab;
    language FAR;
            lookup Routine_1;

} locl;
"""
    f = FontFeatures()
    r1 = Routine(rules=[s1], languages=[("arab", "URD "), ("arab", "FAR ")])
    f.addFeature("locl", [r1])
    assert f.asFea(do_gdef=False) == expected
Example #15
0
    def insertRows(self, position, rows=1, parent=QModelIndex()):
        """ Insert a row into the model. """
        assert(rows == 1)
        self.beginInsertRows(QModelIndex(), position, position + rows - 1)

        self.project.fontfeatures.routines.insert(position,Routine(name="", rules=[]))
        self.endInsertRows()
        return True
Example #16
0
def arrange_by_type(self):
    from fontFeatures import Routine

    # Arrange into rules of similar type (Substitution/Positioning)
    ruleTypes = {}
    for r in self.rules:
        if not type(r) in ruleTypes:
            ruleTypes[type(r)] = []
        ruleTypes[type(r)].append(r)
    if len(ruleTypes.keys()) == 1:
        return
    routines = []
    for k, v in ruleTypes.items():
        r = Routine(rules=v, flags=self.flags)
        if self.name:
            r.name = self.name + "_" + str(k)
    return routines
Example #17
0
    def test_complex_pos(self):
        v = ValueRecord(xAdvance=120)
        pos1 = Positioning(["a"], [v])
        r1 = Routine(rules=[pos1])
        rr1 = RoutineReference(routine=r1)

        c = Chaining([["a"], ["b"]], lookups=[[rr1], None])
        c.feaPreamble(FontFeatures())
        self.assertEqual(c.asFea(), "pos a' lookup ChainedRoutine1 b';")
Example #18
0
        def wrap_and_flush():
            nonlocal oddStatements

            if len(oddStatements) > 0:
                self.parser.fontfeatures.addFeature(featurename, [
                    Routine(rules=[
                        r for r in oddStatements if isinstance(r, Rule)
                    ])
                ])
            oddStatements = []
Example #19
0
def test_featurelist(qtmodeltester):
    proj = FluxProject()
    proj.fontfeatures = FontFeatures()
    r1 = Routine(name="routine1")
    r2 = Routine(name="routine2")
    proj.fontfeatures.features["test"] = [r1, r2]

    proj = FluxProject("Hind.fluxml")

    model = FeatureListModel(proj)

    root = model.index(-1, -1)
    assert (model.describeIndex(root) == "root of tree")
    feature1 = model.index(0, 0)
    assert (model.describeIndex(feature1) == "feature at row 0")
    child1 = model.index(0, 0, feature1)
    assert (child1.parent() == feature1)
    assert (model.index(0, 0, feature1) == model.index(0, 0, feature1))
    # import code; code.interact(local=locals())
    qtmodeltester.check(model, force_py=True)
    qtmodeltester.check(model)
Example #20
0
    def test_simple_pos(self):
        v = ValueRecord(xAdvance=120)
        pos = Positioning(["a"], [v])
        r = Routine(rules=[pos], name="dummy")
        rr = RoutineReference(routine=r)

        c = Chaining([["a"], ["b"]], lookups=[[rr], None])
        self.assertEqual(c.asFea(), "pos a' lookup dummy b';")
        self.assertEqual(
            etree.tostring(c.toXML()),
            '<chaining><lookups><slot><routinereference name="dummy"/></slot><slot><lookup/></slot></lookups><input><slot><glyph>a</glyph></slot><slot><glyph>b</glyph></slot></input></chaining>'
            .encode("utf-8"))
Example #21
0
def test_namedclass_in_slot():
    font = Babelfont.load("tests/data/LibertinusSans-Regular.otf")
    buf = Buffer(font, glyphs=["A", "B", "C"])
    r = Routine()
    r.addRule(Substitution([["G", "@AB"]], [["X"]]))
    r.apply_to_buffer(buf, namedclasses={"AB": ["A", "B"]})
    assert buf.serialize(position=False) == "X|X|C"
Example #22
0
def arrange_by_flags(self):
    from fontFeatures import Routine

    flagTypes = {}
    for r in self.rules:
        if not r.flags:
            r.flags = 0
        if not r.flags in flagTypes:
            flagTypes[r.flags] = []
        flagTypes[r.flags].append(r)
    if len(flagTypes.keys()) == 1:
        if not self.flags:
            self.flags = 0
        self.flags = self.flags | list(flagTypes.keys())[0]
        return
    routines = []
    for k, v in flagTypes.items():
        r = Routine(rules=v, flags=k)
        if self.name:
            r.name = self.name + "_" + str(k)
        routines.append(r)
    return routines
Example #23
0
def arrange_by_lookup_type(self):
    from fontFeatures import Routine

    ruleTypes = {}
    for r in self.rules:
        if not lookup_type(r) in ruleTypes:
            ruleTypes[lookup_type(r)] = []
        ruleTypes[lookup_type(r)].append(r)
    if len(ruleTypes.keys()) == 1:
        return
    # Special case the fact that a single sub can be expressed as part of a
    # multiple sub if needed
    if tuple(sorted(ruleTypes.keys())) == (1, 2) or tuple(
            sorted(ruleTypes.keys())) == (1, 8):
        return
    routines = []
    for k, v in ruleTypes.items():
        r = Routine(rules=v, flags=self.flags)
        if self.name:
            r.name = self.name + "_" + str(k)
        routines.append(r)
    return routines
Example #24
0
def test_multiple_applications():
    font = Babelfont.load("tests/data/LibertinusSans-Regular.otf")
    buf = Buffer(font, glyphs=["A", "B", "C"])
    r = Routine()
    r.addRule(Substitution([["A"]], [["X"]]))
    r.addRule(Substitution([["B"]], [["Y"]]))
    r.apply_to_buffer(buf)
    assert buf.serialize(position=False) == "X|Y|C"
Example #25
0
def test_routine_partition():
    f = FontFeatures()

    s1 = Substitution([["A"]], [["A.grk"]], languages=["grek/*"])
    s2 = Substitution([["A"]], [["A.esp"]], languages=["latn/ESP "])
    r = Routine(rules=[s1, s2], flags=0x2)

    f.routines.append(r)

    dummy = Routine(rules=[Substitution([["G"]], [["G"]])])
    f.routines.append(dummy)

    c = Chaining(
        [["A"], ["V"]],
        lookups=[
            [RoutineReference(routine=dummy),
             RoutineReference(routine=r)],
            [RoutineReference(routine=r),
             RoutineReference(routine=dummy)],
        ])
    r2 = Routine(rules=[c])
    f.routines.append(r2)

    f.addFeature("locl", [r])

    f.partitionRoutine(r, lambda rule: tuple(rule.languages or []))

    assert len(f.routines) == 4
    assert f.routines[0].flags == f.routines[1].flags
    assert len(f.routines[0].rules) == 1
    assert len(f.routines[1].rules) == 1
    assert f.routines[0].rules[0].replacement[0][0] == "A.grk"
    assert f.routines[1].rules[0].replacement[0][0] == "A.esp"

    assert len(c.lookups[0]) == 3
    assert len(f.features["locl"]) == 2
Example #26
0
def test_script_language_split():
    f = FontFeatures()

    s1 = Substitution([["question"]], [["questiongreek"]],
                      languages=[("grek", "*")])
    s2 = Substitution([["A"]], [["alpha"]], languages=[("grek", "ELL ")])
    s3 = Substitution([["question"]], [["questiondown"]],
                      languages=[("latn", "ESP ")])
    s4 = Substitution([["question"]], [["B"]], languages=[("latn", "POL ")])
    s5 = Substitution([["X"]], [["Y"]])
    r = Routine(rules=[s1, s2, s3, s4, s5])

    f.addFeature("locl", [r])
    f.buildBinaryFeatures(font)
    gsub = "\n".join(getXML(font["GSUB"].toXML))
    assert gsub == """<Version value="0x00010000"/>
Example #27
0
    def compatibleRules(self, l, r):
        from fontFeatures.feaLib.Routine import (
            arrange_by_type,
            arrange_by_lookup_type,
            arrange_by_flags,
        )

        testRoutine = Routine()
        testRoutine.rules.extend(l.rules)
        testRoutine.rules.extend(r.rules)
        if arrange_by_type(testRoutine):
            return False
        if arrange_by_lookup_type(testRoutine):
            return False
        if arrange_by_flags(testRoutine):
            return False
        return True
Example #28
0
def fromXML(klass, el):
    from fontFeatures import Routine, RoutineReference
    rule = klass(klass._slotArray(klass, el.find("input")),
                 precontext=klass._slotArray(klass, el.find("precontext")),
                 postcontext=klass._slotArray(klass, el.find("postcontext")),
                 address=el.get("address"),
                 languages=el.get("languages"),
                 flags=int(el.get("flags") or 0))
    lookupsxml = el.find("lookups")
    rule.lookups = []
    for slot in lookupsxml:
        routines = []
        for lu in slot:
            if lu.tag == "routinereference":
                routines.append(RoutineReference.fromXML(lu))
            elif lu.tag == "routine":
                routines.append(Routine.fromXML(lu))
        rule.lookups.append(routines)
    return rule
Example #29
0
    def apply(self, ff):
        logger = logging.getLogger("fontFeatures")
        deadRoutine = Routine(name="dead")

        def haschains(routine):
            for r in routine.rules:
                if isinstance(r, Chaining):
                    return True

        routinelist = ff.routines
        ff.markRoutineUseInChains()
        for lix, l in enumerate(routinelist):
            if haschains(l) or l == deadRoutine:
                continue
            for rix in range(lix + 1, len(routinelist)):
                r = routinelist[rix]
                if haschains(r) or r == deadRoutine:
                    continue
                if self.nonOverlapping(ff, l, r):
                    logger.info("Merging nonoverlapping routines %s , %s" %
                                (l.name, r.name))
                    self.merge(l, r)
                    routinelist[rix] = deadRoutine
        ff.routines = list(filter(lambda r: r != deadRoutine, routinelist))
Example #30
0
def test_routine_named():
    r1 = Routine(name="One")
    f1 = FontFeatures()
    f1.addFeature("onex", [r1])
    assert f1.routineNamed("One") == r1