Ejemplo 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)]
Ejemplo n.º 2
0
 def _get_metrics(self, glyph, metric=None):
     metrics = get_glyph_metrics(self.parser.font, glyph)
     if metric is not None:
         if metric not in TESTVALUE_METRICS:
             raise ValueError("Unknown metric '%s'" % metric)
         else:
             return metrics[metric]
     else:
         return metrics
Ejemplo n.º 3
0
    def meets_predicate(self, glyphname, predicate, parser):
        inverted = predicate["inverted"]
        metric = predicate["predicate"]
        if metric == "hasanchor":
            anchor = predicate["value"]
            truth = glyphname in parser.fontfeatures.anchors and anchor in parser.fontfeatures.anchors[
                glyphname]
        elif metric == "category":
            cat = predicate["value"]
            truth = parser.font[glyphname].category == cat
        elif metric == "hasglyph":
            truth = re.sub(predicate["value"]["replace"],
                           predicate["value"]["with"],
                           glyphname) in parser.font
        else:
            comp = predicate["comparator"]
            if isinstance(predicate["value"], dict):
                v = predicate["value"]
                testvalue_metrics = get_glyph_metrics(parser.font, v["glyph"])
                if v["metric"] not in testvalue_metrics:
                    raise ValueError("Unknown metric '%s'" % metric)
                testvalue = testvalue_metrics[v["metric"]]
            else:
                testvalue = int(predicate["value"])

            metrics = get_glyph_metrics(parser.font, glyphname)
            if metric not in metrics:
                raise ValueError("Unknown metric '%s'" % metric)
            value = metrics[metric]
            if comp == ">":
                truth = value > testvalue
            elif comp == "<":
                truth = value < testvalue
            elif comp == ">=":
                truth = value >= testvalue
            elif comp == "<=":
                truth = value <= testvalue
            else:
                raise ValueError("Bad comparator %s (can't happen?)" % comp)
        if inverted:
            truth = not truth
        return truth
Ejemplo n.º 4
0
    def compute_threshold(self, parser, below_dots):
        from fontFeatures.ttLib import unparse

        font = parser.font
        behforms = list(
            filter(
                lambda g: g.startswith("BEm") or g.startswith("BEi"),
                parser.font.keys(),
            ))
        bottomOfDot = statistics.mean(
            [get_glyph_metrics(font, x)["yMin"] for x in below_dots])

        if hasattr(parser.fontfeatures, "anchors"):
            anchor1_y = statistics.mean([
                parser.fontfeatures.anchors[x]["_bottom"][1]
                for x in below_dots if x in parser.fontfeatures.anchors
            ])
            anchor2_y = statistics.mean([
                parser.fontfeatures.anchors[x]["bottom"][1] for x in behforms
                if x in parser.fontfeatures.anchors
                and "bottom" in parser.fontfeatures.anchors[x]
            ])
        else:
            # Find the anchors
            ff2 = unparse(font)
            if "mark" not in ff2.features:
                raise ValueError("No mark positioning for font!")
            rules = list(
                filter(
                    lambda r: below_dots[0] in r.marks and any(
                        [m in r.bases for m in behforms]),
                    [x for y in ff2.features["mark"] for x in y.rules],
                ))
            if len(rules) < 1:
                raise ValueError("No nukta positioning?")
            r = rules[0]
            anchor1_y = r.marks[below_dots[0]][1]
            anchor2_y = statistics.mean(
                [r.bases[x][1] for x in behforms if x in r.bases])
        displacement = anchor2_y - anchor1_y
        return -(bottomOfDot + displacement)
Ejemplo n.º 5
0
    def test(self, glyphset, font, infocache):
        matches = []
        if self.type == "Name":
            # print(self.comparator, self.value)
            if self.comparator == "begins":
                matches = [x for x in glyphset if x.startswith(self.value)]
            elif self.comparator == "ends":
                matches = [x for x in glyphset if x.endswith(self.value)]
            elif self.comparator == "matches":
                try:
                    matches = [x for x in glyphset if re.search(self.value, x)]
                except Exception as e:
                    matches = []

        # XXX HasAnchor
        # XXX Is member of
        # XXX Is Category

        if self.metric:
            matches = []
            try:
                for g in glyphset:
                    if g not in infocache:
                        infocache[g] = {"metrics": get_glyph_metrics(font, g)}

                    got = infocache[g]["metrics"][self.type]
                    expected, comp = int(self.value), self.comparator
                    if (comp == ">" and got > expected) or (
                            comp == "<" and got < expected) or (
                                comp == "=" and got == expected) or (
                                    comp == "<=" and got <= expected) or (
                                        comp == ">=" and got >= expected):
                        matches.append(g)
            except Exception as e:
                print(e)
                pass

        return matches
Ejemplo 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
Ejemplo n.º 7
0
    if args.prev:
        preview = ""
        for i in range(num_chars):
            preview += str(chr(i + start_char))
        image = Image.new("RGB", (1000, 1000))
        draw = ImageDraw.Draw(image)
        draw.text((0, 0), preview, font=font)
        image = image.crop(image.getbbox())
        image.save(args.tf + "_prev" + str(args.pt[pt]) + ".png")
        pixels = image.load()

    # Find the max and min values
    ymax = 0
    ymin = 0
    for z in range(num_chars):
        m = glyphtools.get_glyph_metrics(font_info,
                                         font_info.getGlyphName(start_id + z))
        new_ymax = (font_size * m['yMax'] / scale)
        new_ymin = (font_size * m['yMin'] / scale)
        ymin = round(min(ymin, new_ymin))
        ymax = round(max(ymax, new_ymax))
    maxmax = round(abs(ymin) + abs(ymax))

    # We are adding a new font size. Allign the start address
    if curr_offset & 3:
        curr_offset = (curr_offset + 3) & ~3

    # Add the glyph header indicating the global size of the font
    font_data += abs(ymin).to_bytes(1, byteorder="little", signed=False)
    font_data += abs(ymax).to_bytes(1, byteorder="little", signed=False)
    font_data.append(0)
    font_data.append(0)