Exemplo n.º 1
0
    def action(self, args):
        overhang_padding, glyphs = args
        parser = self.parser
        for c in ["inits", "medis"]:
            if c not in parser.fontfeatures.namedClasses:
                raise ValueError("Please define @%s class before calling")

        medis = parser.fontfeatures.namedClasses["medis"]
        inits = parser.fontfeatures.namedClasses["inits"]
        overhangers = glyphs.resolve(parser.fontfeatures, parser.font)

        binned_medis = bin_glyphs_by_metric(parser.font,
                                            medis,
                                            "run",
                                            bincount=8)
        binned_inits = bin_glyphs_by_metric(parser.font,
                                            inits,
                                            "run",
                                            bincount=8)
        rules = []
        maxchainlength = 0
        longeststring = []
        for yb in overhangers:
            entry_anchor = parser.fontfeatures.anchors[yb]["entry"]
            overhang = max(
                -get_glyph_metrics(parser.font, yb)["rsb"],
                get_glyph_metrics(parser.font, yb)["xMax"] - entry_anchor[0],
            )

            workqueue = [[x] for x in binned_inits]
            while workqueue:
                string = workqueue.pop(0)
                totalwidth = sum([max(x[1], failsafe_min_run) for x in string])
                if totalwidth > overhang or len(string) > failsafe_max_length:
                    continue

                adjustment = overhang - totalwidth + int(overhang_padding)
                postcontext = [x[0] for x in string[:-1]] + [[yb]]
                input_ = string[-1]
                example = [input_[0][0]] + [x[0] for x in postcontext]
                warnings.warn(
                    "For glyphs in %s, overhang=%i totalwidth=%i adjustment=%i"
                    % (example, overhang, totalwidth, adjustment))
                maxchainlength = max(maxchainlength, len(string))

                rules.append(
                    fontFeatures.Positioning(
                        [input_[0]],
                        [fontFeatures.ValueRecord(xAdvance=int(adjustment))],
                        postcontext=postcontext,
                    ))
                for medi in binned_medis:
                    workqueue.append([medi] + string)
        warnings.warn("Bari Ye collision maximum chain length was %i glyphs" %
                      maxchainlength)
        return [fontFeatures.Routine(rules=rules, flags=8)]
Exemplo n.º 2
0
    def action(self, args):
        # glyphs is already resolved, because this class has functions of DefineClass, which resolves `primary`
        classname, (metric, bincount), glyphs = args[0], (args[1].value, args[2].value), args[3]
        binned = bin_glyphs_by_metric(self.parser.font, glyphs, metric, bincount=int(bincount))
        for i in range(1, int(bincount) + 1):
            self.parser.fontfeatures.namedClasses["%s_%s%i" % (classname, metric, i)] = tuple(binned[i - 1][0])

        return classname, (metric, bincount), glyphs
Exemplo n.º 3
0
    def store(self, parser, tokens):
        from glyphtools import determine_kern, bin_glyphs_by_metric
        import fontFeatures
        import itertools

        units = int(tokens[-1].token)
        kerns = []

        bincount = 5
        lefts = parser.expandGlyphOrClassName(tokens[0].token)
        rights = parser.expandGlyphOrClassName(tokens[1].token)

        def make_kerns(rise=0, context=[], direction="LTR"):
            kerns = []
            for l in lefts:
                for r in rights:
                    if direction == "LTR":
                        kern = determine_kern(parser.font,
                                              l,
                                              r,
                                              units,
                                              offset1=(0, rise))
                    else:
                        kern = determine_kern(parser.font,
                                              r,
                                              l,
                                              units,
                                              offset1=(0, rise))
                    if abs(kern) < 5:
                        continue
                    v = fontFeatures.ValueRecord(xAdvance=kern)
                    kerns.append(
                        fontFeatures.Positioning([[r]], [v],
                                                 precontext=[[l]],
                                                 postcontext=context))
            return kerns

        context = [
            parser.expandGlyphOrClassName(x.token) for x in tokens[2:-1]
        ]
        if len(context) == 0:
            return [fontFeatures.Routine(rules=make_kerns())]

        kerns = []
        binned_contexts = [
            bin_glyphs_by_metric(parser.font,
                                 glyphs,
                                 "rise",
                                 bincount=bincount) for glyphs in context
        ]
        for c in itertools.product(*binned_contexts):
            totalrise = sum([x[1] for x in c])
            precontext = [x[0] for x in c]
            kerns.extend(
                make_kerns(totalrise, context=precontext, direction="RTL"))

        return [fontFeatures.Routine(rules=kerns)]
Exemplo n.º 4
0
    def action(self, args):
        parser = self.parser
        bincount = 5
        lefts, rights, units, pre = args[0]
        lefts = lefts.resolve(parser.fontfeatures, parser.font)
        rights = rights.resolve(parser.fontfeatures, parser.font)
        pre = [g.resolve(parser.fontfeatures, parser.font) for g in pre]

        def make_kerns(rise=0, context=[], direction="LTR"):
            kerns = []
            for l in lefts:
                for r in rights:
                    if direction == "LTR":
                        kern = determine_kern(parser.font,
                                              l,
                                              r,
                                              units,
                                              offset1=(0, rise))
                    else:
                        kern = determine_kern(parser.font,
                                              r,
                                              l,
                                              units,
                                              offset1=(0, rise))
                    if abs(kern) < 5:
                        continue
                    v = fontFeatures.ValueRecord(xAdvance=kern)
                    kerns.append(
                        fontFeatures.Positioning([[r]], [v],
                                                 precontext=[[l]],
                                                 postcontext=context))
            return kerns

        if not pre:
            return [fontFeatures.Routine(rules=make_kerns())]

        kerns = []
        binned_contexts = [
            bin_glyphs_by_metric(parser.font,
                                 glyphs,
                                 "rise",
                                 bincount=bincount) for glyphs in pre
        ]
        for c in itertools.product(*binned_contexts):
            totalrise = sum([x[1] for x in c])
            precontext = [x[0] for x in c]
            kerns.extend(
                make_kerns(totalrise, context=precontext, direction="RTL"))

        return [fontFeatures.Routine(rules=kerns)]
Exemplo n.º 5
0
    def action(self, parser, metric, bincount, classname, definition):
        glyphs = self.resolve_definition(parser, definition[0])
        predicates = definition[1]
        for p in predicates:
            glyphs = list(
                filter(lambda x: self.meets_predicate(x, p, parser), glyphs))

        binned = bin_glyphs_by_metric(parser.font,
                                      glyphs,
                                      metric,
                                      bincount=int(bincount))
        for i in range(1, int(bincount) + 1):
            parser.fontfeatures.namedClasses["%s_%s%i" %
                                             (classname["classname"], metric,
                                              i)] = tuple(binned[i - 1][0])
Exemplo n.º 6
0
    def action(self, args):
        strategy, below_dots = args
        parser = self.parser
        for c in ["inits", "medis", "bariye", "behs"]:
            if c not in parser.fontfeatures.namedClasses:
                raise ValueError("Please define @%s class before calling" % c)

        alwaysDrop = strategy == "AlwaysDrop"

        # OK, here's the plan of attack for bari ye.
        # First, we find the length of the longest possible sequence.
        # Smallest medi * n + smallest beh + smallest init -> length of bari yeh right tail
        medis = parser.fontfeatures.namedClasses["medis"]
        inits = parser.fontfeatures.namedClasses["inits"]
        behs = parser.fontfeatures.namedClasses["behs"]
        below_dots = below_dots.resolve(parser.fontfeatures, parser.font)
        smallest_medi_width = max(
            failsafe_min_run, min([get_run(parser.font, g) for g in medis]))
        smallest_init_beh_width = min(
            [get_run(parser.font, g) for g in (set(behs) & set(inits))])
        smallest_init_beh = min(
            (set(behs) & set(inits)),
            key=lambda g: get_run(parser.font, g),
        )
        warnings.warn("Smallest medi width is %i" % smallest_medi_width)
        smallest_medi = min(medis, key=lambda g: get_run(parser.font, g))

        routines = []
        for bariye in parser.fontfeatures.namedClasses["bariye"]:
            warnings.warn("BariYe computation for %s" % bariye)
            entry_anchor = parser.fontfeatures.anchors[bariye]["entry"]
            bariye_tail = max(
                -get_glyph_metrics(parser.font, bariye)["rsb"],
                get_glyph_metrics(parser.font, bariye)["xMax"] -
                entry_anchor[0],
            )
            # bariye_tail += max(get_glyph_metrics(parser.font, dot)["width"] for dot in below_dots) / 2
            # # Increase tail by half the width of the widest nukta
            # bariye_tail += (
            #     max(
            #         [
            #             get_glyph_metrics(parser.font, g)["xMax"]
            #             - get_glyph_metrics(parser.font, g)["xMin"]
            #             for g in below_dots
            #         ]
            #     )
            #     / 2
            # )

            # Consider two cases.
            # First, the case where the beh is init and full of short medis
            maximum_sequence_length_1 = 1 + math.ceil(
                (bariye_tail - smallest_init_beh_width) / smallest_medi_width)
            warnings.warn("Longest init beh sequence: %s" %
                          " ".join([smallest_medi] *
                                   (maximum_sequence_length_1 - 1) +
                                   [smallest_init_beh]))
            # Second, the case where the init is not beh, but there are a bunch of medis,
            # one (or more) of which is a medi beh
            smallest_init_nonbeh_width = min([
                max(get_rise(parser.font, g), failsafe_min_run)
                for g in (set(inits) - set(behs))
            ])
            smallest_medi_beh_width = min([
                max(get_rise(parser.font, g), failsafe_min_run)
                for g in (set(medis) & set(behs))
            ])

            maximum_sequence_length_2 = 2 + math.ceil(
                (bariye_tail - smallest_init_nonbeh_width -
                 smallest_medi_beh_width) / smallest_medi_width)

            maximum_sequence_length = min(
                failsafe_max_length,
                max(maximum_sequence_length_1, maximum_sequence_length_2))
            warnings.warn("Max sequence width is %i" % maximum_sequence_length)

            # Next, let's create a chain rule for all nukta sequences
            dropBYsRoutine = fontFeatures.Routine(flags=0x0010)
            dropBYsRoutine.markFilteringSet = below_dots

            dropADotRoutine = fontFeatures.Routine()
            # Substitute those not ending .yb with those ending .yb
            below_dots_non_yb = list(
                sorted(filter(lambda x: not x.endswith(".yb"), below_dots)))
            below_dots_yb = list(
                sorted(filter(lambda x: x.endswith(".yb"), below_dots)))
            if len(below_dots_non_yb) != len(below_dots_yb):
                raise ValueError(
                    "Mismatch in @below_dots: %s has .yb suffix, %s does not" %
                    (below_dots_yb, below_dots_non_yb))

            dropADotRoutine.addRule(
                fontFeatures.Substitution([below_dots_non_yb],
                                          [below_dots_yb]))

            maybeDropDotRoutine = fontFeatures.Routine(flags=0x0010)
            maybeDropDotRoutine.markFilteringSet = below_dots

            binned_medis = bin_glyphs_by_metric(parser.font,
                                                medis,
                                                "run",
                                                bincount=accuracy1)
            binned_inits = bin_glyphs_by_metric(parser.font,
                                                inits,
                                                "run",
                                                bincount=accuracy1)

            queue = [[[[bariye]]]]
            bin2mk = {"0": None, "1": below_dots}
            while len(queue) > 0:
                consideration = queue.pop(0)
                # import code; code.interact(local=locals())
                seq_length = sum([s[1] for s in consideration[:-1]])
                repsequence = [(s[0][0], s[1]) for s in consideration[:-1]]
                sequence = [s[0] for s in consideration]
                warnings.warn("Sequence %s total %i bariye_tail %i" %
                              (repsequence, seq_length, bariye_tail))

                if seq_length > bariye_tail or len(
                        consideration) > maximum_sequence_length:
                    continue

                lu = [None] * len(sequence)
                if alwaysDrop:
                    lu[0] = [dropADotRoutine]
                else:
                    lu[0] = [maybeDropDotRoutine]
                for j in range(0, 2**(len(sequence) - 1)):
                    binary = "{0:0%ib}" % (len(sequence) - 1)
                    marksequence = [bin2mk[x] for x in list(binary.format(j))]
                    # import code; code.interact(local=locals())
                    newsequence = dropnone(interleave(sequence, marksequence))
                    chainrule = fontFeatures.Chaining([below_dots],
                                                      postcontext=newsequence,
                                                      lookups=lu)
                    # We don't combine the bins here precisely because they're
                    # disjoint sets and that means they can be expressed as a
                    # format 2 class-based rule! Wonderful!
                    dropBYsRoutine.addRule(chainrule)

                for m in binned_medis:
                    queue.append([list(m)] + consideration)

            if not alwaysDrop:
                # Check to see if it can fit in the gap, and only move it if it can't
                medis_by_rise = bin_glyphs_by_metric(parser.font,
                                                     medis,
                                                     "rise",
                                                     bincount=accuracy2)
                queue = [[[[bariye],
                           get_glyph_metrics(parser.font, bariye)["rise"]]]]
                ybClearance = self.get_yb_clearance(parser, bariye)
                gapRequired = self.compute_threshold(parser,
                                                     below_dots) - ybClearance
                warnings.warn(
                    "%i units of rise are required to fit a nukta in the gap" %
                    gapRequired)
                while len(queue) > 0:
                    consideration = queue.pop(0)
                    total_rise = sum([s[1] for s in consideration])
                    repsequence = [(s[0][0], s[1]) for s in consideration]
                    # warnings.warn("Sequence %s total rise %i required %i" % (repsequence, total_rise, gapRequired))
                    if (total_rise > gapRequired
                            or len(consideration) > maximum_sequence_length):
                        # warnings.warn("Does not drop")
                        continue

                    sequence = [s[0] for s in consideration]
                    lu = [None] * len(sequence)
                    lu[0] = [dropADotRoutine]
                    chainrule = fontFeatures.Chaining([below_dots],
                                                      postcontext=sequence,
                                                      lookups=lu)
                    maybeDropDotRoutine.addRule(chainrule)
                    # print("Drops %s"  % chainrule.asFea())
                    for m in medis_by_rise:
                        if total_rise + m[1] < gapRequired:
                            queue.append([list(m)] + consideration)

            # Add all the routines to the parser
            parser.fontfeatures.routines.append(dropADotRoutine)
            if not alwaysDrop:
                parser.fontfeatures.routines.append(maybeDropDotRoutine)
            routines.append(dropBYsRoutine)
        return routines