Example #1
0
    def renameClasses(self, oldToNew):
        """
        Renames all class names (in each StateRow, and in the class table) as
        specified by the oldToNew dict, which maps old strings to new ones. If
        an old name is not present as a key in oldToNew, that class name will
        not be changed.
        
        >>> oldToNew = {"acute": "acuteaccent", "grave": "graveaccent"}
        >>> obj = _makePointExample()
        >>> obj.renameClasses(oldToNew)
        >>> obj.pprint(onlySignificant=True)
        State 'Start of text':
          Class 'x':
            Mark this glyph, then go to state 'Saw x'
        State 'Start of line':
          Class 'x':
            Mark this glyph, then go to state 'Saw x'
        State 'Saw x':
          Class 'acuteaccent':
            Go to state 'Start of text' after doing this alignment:
              Marked glyph's point: 19
              Current glyph's point: 12
          Class 'graveaccent':
            Go to state 'Start of text' after doing this alignment:
              Marked glyph's point: 19
              Current glyph's point: 4
          Class 'x':
            Mark this glyph, then go to state 'Saw x'
        Class table:
          30: x
          96: acuteaccent
          97: graveaccent
        Header information:
          Horizontal
          With-stream
          No variation kerning
          Process forward
        """

        ct = classtable.ClassTable()

        for glyph, className in self.classTable.items():
            ct[glyph] = oldToNew.get(className, className)

        self.classTable = ct

        for stateName in self:
            stateRow = self[stateName]
            sr = staterow.StateRow()

            for className, entryObj in stateRow.items():
                sr[oldToNew.get(className, className)] = entryObj

            self[stateName] = sr
    def makeInitialClassTable(self, gVec):
        """
        Creates and returns a ClassTable from the specified vector of tuples.
        """

        ct = classtable.ClassTable()

        for t in gVec:
            for g in t:
                if g is not None and g not in ct:
                    ct[g] = self.nmbf(g)

        return ct
Example #3
0
def analyze_shift_makeClasses(glyphTuples, effectTuples, nmbf):
    """
    Creates a ClassTable based on an analysis of the specified glyphs and
    effects.
    
    ### E = effect.Effect
    ### e0 = E()
    ### e1 = E(xShift=-40)
    ### e2 = E(xShift=25)
    ### gt = ((15, 39), (28, 50), (56, 84))
    ### et = ((e0, e1), (e0, e2), (e0, e1))
    ### analyze_shift_makeClasses(gt, et, str).pprint()
    15: 15
    28: 28
    39: like 39
    50: 50
    56: 56
    84: like 39
    """

    ct = classtable.ClassTable()
    d = collections.defaultdict(set)

    for tG, tE in zip(glyphTuples, effectTuples):
        for g, e in zip(tG, tE):
            if e:
                d[g].add((e.xShift, e.yShift))
            elif g not in ct:
                ct[g] = nmbf(g)

    d = {g: frozenset(s) for g, s in d.items()}
    dInv = utilities.invertDictFull(d, asSets=True)

    for eGroup, gSet in dInv.items():
        gv = sorted(gSet - {None})

        if not gv:
            continue

        if len(gv) == 1:
            ct[gv[0]] = nmbf(gv[0])

        else:
            s = "like %s" % (nmbf(gv[0]), )

            for g in gv:
                ct[g] = s

    return ct
Example #4
0
    def _makeCoordExample():
        cv = coverage.Coverage(crossStream=False)
        ct = classtable.ClassTable({30: "x", 96: "acute", 97: "grave"})

        pe1 = coordentry.CoordEntry(markedX=200,
                                    markedY=1600,
                                    currentX=450,
                                    currentY=1100)

        pe2 = coordentry.CoordEntry(markedX=200,
                                    markedY=1600,
                                    currentX=300,
                                    currentY=1000)

        e1 = entry4.Entry(newState="Start of text")
        e2 = entry4.Entry(newState="Saw x", mark=True)
        e3 = entry4.Entry(newState="Start of text", action=pe1)
        e4 = entry4.Entry(newState="Start of text", action=pe2)

        row1 = staterow.StateRow({
            "End of text": e1,
            "Out of bounds": e1,
            "Deleted glyph": e1,
            "End of line": e1,
            "x": e2,
            "acute": e1,
            "grave": e1
        })

        row2 = staterow.StateRow({
            "End of text": e1,
            "Out of bounds": e1,
            "Deleted glyph": e1,
            "End of line": e1,
            "x": e2,
            "acute": e3,
            "grave": e4
        })

        return Format4(
            {
                "Start of text": row1,
                "Start of line": row1,
                "Saw x": row2
            },
            coverage=cv,
            classTable=ct)
Example #5
0
    def fromkern_format1(cls, k1, **kwArgs):
        """
        Creates and returns a new Format1 object using the specified 'kern'
        Format1 object.
        """

        cv = coverage.Coverage.fromkern_coverage(k1.coverage)
        ct = classtable.ClassTable(k1.classTable)
        r = cls({}, coverage=cv, tupleIndex=k1.tupleIndex, classTable=ct)

        for stateName, stateRowObj in k1.items():
            newRow = staterow.StateRow()

            for className, entryObj in stateRowObj.items():
                # The main difference that needs to be taken account of is the
                # different position of the "reset cross-stream kerning" flag.
                # In the 'kern' world, that's all the way down in the Value
                # object, while in the 'kerx' world it's up at the Entry level.

                reset = False

                if entryObj.values is not None:
                    v = []

                    for value in entryObj.values:
                        v.append(int(value))

                        if value.resetCrossStream:
                            reset = True

                    values = valuetuple.ValueTuple(v)

                else:
                    values = None

                newRow[className] = entry.Entry(newState=entryObj.newState,
                                                noAdvance=entryObj.noAdvance,
                                                push=entryObj.push,
                                                reset=reset,
                                                values=values)

            r[stateName] = newRow

        return r
Example #6
0
    def _makePointExample():
        cv = coverage.Coverage(crossStream=False)
        ct = classtable.ClassTable({30: "x", 96: "acute", 97: "grave"})
        pe1 = pointentry.PointEntry(markedPoint=19, currentPoint=12)
        pe2 = pointentry.PointEntry(markedPoint=19, currentPoint=4)
        e1 = entry4.Entry(newState="Start of text")
        e2 = entry4.Entry(newState="Saw x", mark=True)
        e3 = entry4.Entry(newState="Start of text", action=pe1)
        e4 = entry4.Entry(newState="Start of text", action=pe2)

        row1 = staterow.StateRow({
            "End of text": e1,
            "Out of bounds": e1,
            "Deleted glyph": e1,
            "End of line": e1,
            "x": e2,
            "acute": e1,
            "grave": e1
        })

        row2 = staterow.StateRow({
            "End of text": e1,
            "Out of bounds": e1,
            "Deleted glyph": e1,
            "End of line": e1,
            "x": e2,
            "acute": e3,
            "grave": e4
        })

        return Format4(
            {
                "Start of text": row1,
                "Start of line": row1,
                "Saw x": row2
            },
            coverage=cv,
            classTable=ct)
Example #7
0
    def renameClasses(self, oldToNew):
        """
        Renames all class names (in each StateRow, and in the class table) as
        specified by the oldToNew dict, which maps old strings to new ones. If
        an old name is not present as a key in oldToNew, that class name will
        not be changed.
        
        >>> obj = _testingValues[1].__deepcopy__()
        >>> oldToNew = {"Space": "Whitespace"}
        >>> obj.renameClasses(oldToNew)
        >>> obj.pprint(namer=namer.testingNamer(), onlySignificant=True)
        State 'Start of text':
          Class 'Letter':
            Go to state 'In word'
        State 'Start of line':
          Class 'Letter':
            Go to state 'In word'
        State 'In word':
          Class 'Out of bounds':
            Go to state 'In word'
          Class 'Deleted glyph':
            Go to state 'In word'
          Class 'Letter':
            Push this glyph, then go to state 'In word' after applying these kerning shifts to the popped glyphs:
              Pop #1: 682
          Class 'Punctuation':
            Go to state 'In word'
          Class 'Whitespace':
            Reset kerning, then go to state 'Start of text'
        Class table:
          afii60001: Letter
          afii60002: Letter
          xyz13: Punctuation
          xyz14: Punctuation
          xyz31: Letter
          xyz32: Letter
          xyz4: Whitespace
          xyz5: Punctuation
          xyz52: Letter
          xyz6: Punctuation
          xyz7: Punctuation
        Header information:
          Horizontal
          Cross-stream
          No variation kerning
          Process forward
        """

        ct = classtable.ClassTable()

        for glyph, className in self.classTable.items():
            ct[glyph] = oldToNew.get(className, className)

        self.classTable = ct

        for stateName in self:
            stateRow = self[stateName]
            sr = staterow.StateRow()

            for className, entryObj in stateRow.items():
                sr[oldToNew.get(className, className)] = entryObj

            self[stateName] = sr
Example #8
0
    def _combinedClasses(leftCD, rightCD, leftMap=None):
        """
        Given a pair of classDefs (or similar dict-like objects) which might
        have some glyphs in common, return a new ClassTable object. This is
        necessary in AAT because kerning state tables don't have separate left
        and right class maps.

        If a leftMap is provided, it will be filled with keys of leftCD class
        indices pointing to sets of class names involving those indices.
        
        >>> L = {
        ...   6: 1,
        ...   8: 1,
        ...   10: 2,
        ...   14: 2,
        ...   16: 2,
        ...   23: 3,
        ...   29: 3,
        ...   31: 4,
        ...   32: 4,
        ...   34: 4,
        ...   35: 4}
        >>> R = {
        ...   7: 1,
        ...   9: 1,
        ...   10: 2,
        ...   11: 2,
        ...   12: 2,
        ...   23: 5,
        ...   24: 4,
        ...   25: 3,
        ...   29: 5,
        ...   32: 5}
        >>> M = {}
        >>> Format1._combinedClasses(L, R, M).pprint()
        6: class_L_1
        7: class_R_1
        8: class_L_1
        9: class_R_1
        10: class_LR_2_2
        11: class_R_2
        12: class_R_2
        14: class_L_2
        16: class_L_2
        23: class_LR_3_5
        24: class_R_4
        25: class_R_3
        29: class_LR_3_5
        31: class_L_4
        32: class_LR_4_5
        34: class_L_4
        35: class_L_4
        
        >>> for lc, s in sorted(M.items()): print(lc, sorted(s))
        1 ['class_L_1']
        2 ['class_LR_2_2', 'class_L_2']
        3 ['class_LR_3_5']
        4 ['class_LR_4_5', 'class_L_4']
        """

        leftCD = {k: v for k, v in leftCD.items() if v}
        rightCD = {k: v for k, v in rightCD.items() if v}
        allLeftGlyphs = set(leftCD)
        allRightGlyphs = set(rightCD)
        allGlyphs = allLeftGlyphs | allRightGlyphs
        d = {}  # combinedName -> glyph set

        if leftMap is None:
            leftMap = {}

        for glyph in allGlyphs:
            if glyph in allLeftGlyphs and glyph in allRightGlyphs:
                leftClass = leftCD[glyph]
                rightClass = rightCD[glyph]
                name = "class_LR_%d_%d" % (leftClass, rightClass)

            elif glyph in allLeftGlyphs:
                leftClass = leftCD[glyph]
                name = "class_L_%d" % (leftClass, )

            else:
                leftClass = None
                name = "class_R_%d" % (rightCD[glyph], )

            d.setdefault(name, set()).add(glyph)

            if leftClass is not None:
                leftMap.setdefault(leftClass, set()).add(name)

        return classtable.ClassTable((g, cn) for cn, s in d.items() for g in s)
Example #9
0

if __debug__:
    from fontio3.kerx import coverage
    from fontio3.utilities import namer

    _srtv = staterow._testingValues
    _cv = coverage.Coverage(crossStream=True)

    _ct = classtable.ClassTable({
        3: "Space",
        4: "Punctuation",
        5: "Punctuation",
        6: "Punctuation",
        12: "Punctuation",
        13: "Punctuation",
        30: "Letter",
        31: "Letter",
        51: "Letter",
        96: "Letter",
        97: "Letter"
    })

    _testingValues = (Format1(),
                      Format1(
                          {
                              "Start of text": _srtv[1],
                              "Start of line": _srtv[1],
                              "In word": _srtv[2]
                          },
                          classTable=_ct,
Example #10
0
def analyze_attach_makeClasses(glyphTuples, effectTuples, nmbf, hmtxObj):
    """
    Creates and returns the ClassTable for the specified glyphs and effects.
    
    ### ed = _fakeEditor()
    ### E = effect.Effect
    ### nmbf = (lambda n: "class %d" % (n,))
    ### e0 = E()
    ### e1 = E(xAttach=-34)
    ### e2 = E(xAttach=-74)
    ### gt = ((20, 10),)
    ### et = ((e0, e1),)
    ### analyze_attach_makeClasses(gt, et, nmbf, ed.hmtx).pprint()
    10: class 10
    20: class 20
    
    ### gt = ((20, 10), (20, 11))
    ### et = ((e0, e1), (e0, e1))
    ### analyze_attach_makeClasses(gt, et, nmbf, ed.hmtx).pprint()
    10: mark group class 10
    11: mark group class 10
    20: class 20
    
    ### gt = ((20, 10), (20, 11))
    ### et = ((e0, e1), (e0, e2))
    ### analyze_attach_makeClasses(gt, et, nmbf, ed.hmtx).pprint()
    10: class 10
    11: class 11
    20: class 20
    
    ### gt = ((20, 10), (21, 11))
    ### et = ((e0, e1), (e0, e2))
    ### analyze_attach_makeClasses(gt, et, nmbf, ed.hmtx).pprint()
    10: mark group class 10
    11: mark group class 10
    20: base group class 20
    21: base group class 20
    """

    # The coordObjPool is a dict mapping the base glyph's (x, y) to a
    # corresponding CoordEntry. Note that the attaching glyph's coordinates
    # will always be (0, 0) in this model!

    coordObjPool = {}

    # The commonEffects variable is a dict mapping a base glyph's (x, y) to a
    # set of mark glyph indices that use that particular effect.
    #
    # The baseCollection variable is a dict mapping base glyph indices to a set
    # of (x, y) values. Two base glyphs can be combined into the same class
    # only if their sets are equal.

    commonEffects = collections.defaultdict(set)
    baseCollection = collections.defaultdict(set)
    CE = coordentry.CoordEntry
    allBases, allMarks = set(), set()

    for gt, et in zip(glyphTuples, effectTuples):
        if len(gt) > 2:
            vGT, vET = [], []

            for g, e in zip(gt, et):
                if g is None:
                    if e:
                        raise ValueError("Bad mark-to-ligature data!")

                else:
                    vGT.append(g)
                    vET.append(e)

            gt = tuple(vGT)
            et = tuple(vET)

        gBase, gMark = gt
        eBase, eMark = et
        assert (not eBase)
        aw = hmtxObj[gBase].advance
        t = (aw + eMark.xAttach, eMark.yAttach)
        baseCollection[gBase].add(t)

        if t not in coordObjPool:
            co = CE(t[0], t[1], 0, 0)
            coordObjPool[t] = co

        co = coordObjPool[t]
        commonEffects[t].add(gMark)
        allBases.add(gBase)
        allMarks.add(gMark)

    # The inWhich variable is a dict mapping glyphs to sets of all the keys in
    # commonEffects in whose corresponding sets the glyph appears.

    inWhich = collections.defaultdict(set)

    for k, s in commonEffects.items():
        for g in s:
            inWhich[g].add(k)

    # The inWhichRev variable is a dict with the inverse map of inWhich: that
    # is to say, mapping frozenset versions of inWhich's values to sets of
    # glyphs that share those values.

    inWhichRev = collections.defaultdict(set)

    for g, keySet in inWhich.items():
        inWhichRev[frozenset(keySet)].add(g)

    ct = classtable.ClassTable()

    for glyphSet in inWhichRev.values():
        # Remove any baseforms (probably won't be any)
        glyphSet -= allBases

        if not glyphSet:
            continue

        avatar = nmbf(min(glyphSet))

        if len(glyphSet) == 1:
            groupName = avatar
        else:
            groupName = "mark group %s" % (avatar, )

        for glyph in glyphSet:
            ct[glyph] = groupName

    # Now combine (where possible) bases, using the baseCollection dict created
    # above in the main walker loop.

    bcRev = collections.defaultdict(set)

    for g, xyGroup in baseCollection.items():
        bcRev[frozenset(xyGroup)].add(g)

    for xyGroup, glyphSet in bcRev.items():
        if len(xyGroup) > 1 or len(glyphSet) < 2:
            continue

        avatar = nmbf(min(glyphSet))
        groupName = "base group %s" % (avatar, )

        for glyph in glyphSet:
            ct[glyph] = groupName

    # Finally, any glyphs not yet in the class table at this point are just
    # given a class to themselves.

    leftToDo = (allBases | allMarks) - set(ct)

    for glyph in leftToDo:
        ct[glyph] = nmbf(glyph)

    return ct