Ejemplo n.º 1
0
 def pieceIterator(self, **kwArgs):
     """
     Returns a generator over (TTContours, Matrix) pairs, where the matrix
     is fully resolved, even for deeply nested composite glyphs. There are
     two keyword arguments:
     
         currMatrix      The matrix on entry to this level. If not
                         specified, the identity matrix will be used.
         
         editor          The Editor object, from which the Glyf object is
                         obtained. This is required.
         
         runCheck        A Boolean controlling whether the pre-flight check
                         for bad glyphs or circular references will be run.
                         Default is True.
     
     This call may raise one of these exceptions:
     
         BadEditorOrGlyf
         BadOrCircularComponents
     """
     
     if kwArgs.get('runCheck', True):
         arg, b1, b2 = set(), set(), set()
         kwArgs.pop('badCycles', None)
         kwArgs.pop('badGlyphs', None)
         self.allReferencedGlyphs(arg, badCycles=b1, badGlyphs=b2, **kwArgs)
         
         if not arg:
             raise BadEditorOrGlyf()
         
         if b1 or b2:
             raise BadOrCircularComponents()
     
     currMatrix = kwArgs.get('currMatrix', None)
     e = kwArgs['editor']
     glyfTable = e.glyf
     
     if currMatrix is None:
         currMatrix = matrix.Matrix()
     
     for piece in self.components:
         thisMatrix = currMatrix.multiply(piece.transformationMatrix)
         obj = glyfTable[piece.glyphIndex]
         
         if obj.isComposite:
             for t in obj.pieceIterator(
               currMatrix = thisMatrix,
               editor = e,
               runCheck = False):
                 
                 yield t
         
         else:
             yield (obj.contours, thisMatrix)
Ejemplo n.º 2
0
    def fromcffdata(cls, components, offset, advance, **kwArgs):
        """
        Constructs and returns a CFFCompositeGlyph from the specified component
        list and offset pair.

        >>> c = CFFCompositeGlyph.fromcffdata( [5,10], [100,100], 500 )

        NOTE: In the following, the bounds are incomplete; see the note in the
        source code.

        >>> c.pprint(namer = namer.testingNamer())
        Accent: xyz11
        Accent Offset: Shift X by 100.0 and Y by 100.0
        Base: xyz6
        Bounds:
          Minimum X: 0
          Minimum Y: 0
          Maximum X: 0
          Maximum Y: 0
        CFF Advance: 500
        """

        b = components[0]
        a = components[1]
        aOff = matrix.Matrix(([1, 0, 0], [0, 1, 0], [offset[0], offset[1], 1]))

        # *** NOTE *** This needs to be updated to calculate or otherwise
        # obtain a correct bounding box for the resulting composite glyph.
        # Unlike TrueType, CFF does not store a BBX for composites; it must be
        # calculated from the components.
        #
        # With the current setup, we don't have access to the component glyph
        # data. For now, we just create an empty rectangle.

        glyphbounds = rectangle.Rectangle()

        #        aTrans = a.transformed(aOff)
        #        B = rectangle.Rectangle
        #         glyphbounds = functools.reduce(
        #           B.unioned,
        #           (b, aTrans),
        #           B())

        return cls(
            baseGlyph=b,
            accentGlyph=a,
            accentOffset=aOff,
            bounds=glyphbounds,
            cffAdvance=advance,
        )
Ejemplo n.º 3
0
def _recalc(d, **kwArgs):
    editor = kwArgs['editor']

    if editor is None or (not editor.reallyHas(b'maxp')):
        return False, d

    fontGlyphCount = editor.maxp.numGlyphs
    scaler = kwArgs.get('scaler', None)
    r = d

    if (scaler is not None) and editor.reallyHas(b'glyf'):
        r = d.__deepcopy__()
        ratio = d.ratio

        if ratio.xRatio > 1 or ratio.yEndRatio > 1:
            xs = (ratio.xRatio * 1.0) / (ratio.yEndRatio * 1.0)
        else:
            xs = 1.0

        for p in d.group.keys():
            mat = matrix.Matrix(
                ((xs * p, 0.0, 0.0), (0.0, p * 1.0, 0.0), (0.0, 0.0, 1.0)))

            try:
                scaler.setTransform(mat)
            except:
                raise ScalerError()

            r_ymax = -32768
            r_ymin = 32767

            for g in range(fontGlyphCount):
                try:
                    m = scaler.getOutlineMetrics(g)
                except:
                    raise ScalerError()

                r_ymax = max(r_ymax, int(oldRound(m.hi_y)))
                r_ymin = min(r_ymin, int(oldRound(m.lo_y)))

            r.group[p] = record.Record(yMin=r_ymin, yMax=r_ymax)

    return (r != d), r
Ejemplo n.º 4
0
def _validate(obj, **kwArgs):
    logger = kwArgs['logger']
    m = obj.transformationMatrix
    ZE = mathutilities.zeroEnough
    CE = mathutilities.closeEnough
    r = True

    if ZE(m[0][0]) and ZE(m[0][1]):
        logger.error(('E1116', (), "Component matrix collapses to zero in x."))

        r = False

    if ZE(m[1][0]) and ZE(m[1][1]):
        logger.error(('E1116', (), "Component matrix collapses to zero in y."))

        r = False

    if (obj.compoundAnchor is None) != (obj.componentAnchor is None):
        logger.error(('E1117', (),
                      "Either both anchors or neither should be specified."))

        r = False

    if ((obj.compoundAnchor is not None)
            and (obj.transformationMatrix != matrix.Matrix())):

        logger.error(
            ('E1117', (), "Matrix must be identity if anchors are used."))

        r = False

    x = abs(m[0][0])
    y = abs(m[1][1])

    if ((x != 1 and CE(x, 1, delta=1.0e-3))
            or (y != 1 and CE(y, 1, delta=1.0e-3))):

        logger.warning(
            ('V0816', (),
             "The x-scale and/or y-scale for this component is close to +1 "
             "or -1, but not exactly equal to +1 or -1."))

    return r
Ejemplo n.º 5
0
    def fromvalidatedcffdata(cls, components, offset, advance, **kwArgs):
        """
        Like fromcffdata(), this method constructs and returns a CFFGlyph by
        parsing and interpreting the specified charstring, privatedict
        (containing localsubrs), and globalsubrs, *ALL* of which are required
        in order to fully parse a glyph into a usable form. However, it also
        does extensive validation via the logging module (the client should
        have done a logging.basicConfig call prior to calling this method,
        unless a logger is passed in via the 'logger' keyword argument).
        """

        logger = kwArgs.pop('logger', None)

        if logger is None:
            logger = logging.getLogger().getChild('cffcompositeglyph')
        else:
            logger = logger.getChild('cffcompositeglyph')

        b = components[0]
        a = components[1]
        aOff = matrix.Matrix(([1, 0, 0], [0, 1, 0], [offset[0], offset[1], 1]))

        # *** NOTE *** This needs to be updated to calculate or otherwise
        # obtain a correct bounding box for the resulting composite glyph.
        # Unlike TrueType, CFF does not store a BBX for composites; it must be
        # calculated from the components.
        #
        # With the current setup, we don't have access to the component glyph
        # data. For now, we just create an empty rectangle.

        glyphbounds = rectangle.Rectangle()

        return cls(
            baseGlyph=b,
            accentGlyph=a,
            accentOffset=aOff,
            bounds=glyphbounds,
            cffAdvance=advance,
        )
Ejemplo n.º 6
0
#

if 0:

    def __________________():
        pass


if __debug__:
    from fontio3 import utilities
    from fontio3.utilities import namer
    from fontio3.CFF import cffbounds

    _testingValues = [
        CFFCompositeGlyph(cffAdvance=667,
                          accentOffset=matrix.Matrix(
                              ([1, 0, 0], [0, 1, 0], [177, 170, 1])),
                          bounds=cffbounds.CFFBounds(xMin=0,
                                                     yMin=0,
                                                     yMax=0,
                                                     xMax=0),
                          baseGlyph=38,
                          accentGlyph=124)
    ]


def _test():
    import doctest
    doctest.testmod()


if __name__ == "__main__":
Ejemplo n.º 7
0
    def intersection(self, other, delta=1.0e-5):
        """
        Determines if self and other intersect, and returns an object
        representing that intersection. This could be None if they do not
        intersect, a Point object if they intersect in a single point, or a
        Line object if they are fully or partly coincident.
        
        >>> P = point.Point
        
        Simple point intersection cases:
        
        >>> print(Line(P(0, 0), P(0, 8)).intersection(Line(P(0, 4), P(8, 4))))
        (0.0, 4.0)
        >>> print(Line(P(2, 1), P(5, 4)).intersection(Line(P(3, 3), P(11, 3))))
        (4.0, 3.0)
        
        Non-parallel, out-of-bounds intersection case:
        
        >>> L1 = Line(P(2, 2), P(5, 5))
        >>> L2 = Line(P(20000, 2), P(20004, 5))
        >>> print(L1.intersection(L2))
        None
        
        Parallel line cases, disjoint and otherwise:
        
        >>> print(Line(P(1, 1), P(5, 5)).intersection(Line(P(1, 2), P(5, 6))))
        None
        >>> print(Line(P(1, 1), P(5, 5)).intersection(Line(P(2, 2), P(6, 6))))
        Line from (2.0, 2.0) to (5, 5)
        >>> print(Line(P(0, 0), P(1000000, 1000000)).intersection(Line(P(999999, 999999), P(2000000, 2000000))))
        (999999, 999999)
        >>> print(Line(P(1, 1), P(5, 5)).intersection(Line(P(6, 6), P(8, 8))))
        None
        
        Degenerate line cases:
        >>> print(Line(P(4, 4), P(4, 4)).intersection(Line(P(0, 0), P(5, 5))))
        (4, 4)
        >>> print(Line(P(4, 4), P(4, 4)).intersection(Line(P(1, 0), P(5, 5))))
        None
        >>> print(Line(P(4, 4), P(4, 4)).intersection(Line(P(4, 4), P(4, 4.0000001))))
        (4, 4)
        >>> print(Line(P(4, 4), P(4, 4)).intersection(Line(P(70000, 70000), P(70000, 70000))))
        None
        >>> print(Line(P(1, 1), P(5, 5)).intersection(Line(P(3, 3), P(3, 3))))
        (3, 3)
        >>> print(Line(P(1, 1), P(5, 5)).intersection(Line(P(8, 8), P(8, 8))))
        None
        >>> print(Line(P(1, 1), P(5, 5)).intersection(Line(P(2, 3), P(2, 3))))
        None
        """

        CE = mathutilities.closeEnough
        CES = mathutilities.closeEnoughSpan
        IIP = mathutilities.intIfPossible

        if self.isDegenerate(delta):
            if other.isDegenerate(delta):
                if mathutilities.closeEnoughXY(self.p1, other.p1, delta):
                    return self.p1.__copy__()

                return None

            t = other.parametricValueFromPoint(self.p1)

            if t is None:
                return t

            return self.p1.__copy__()

        elif other.isDegenerate(delta):
            t = self.parametricValueFromPoint(other.p1)

            if (t is None) or (not CES(t)):
                return None

            return other.p1.__copy__()

        if CE(self.slope(), other.slope()):
            # lines are either parallel or coincident
            t1 = self.parametricValueFromPoint(other.p1)

            if t1 is None:
                return None

            # lines are coincident, although the segments may not overlap
            t2 = self.parametricValueFromPoint(other.p2)

            if max(t1, t2) < 0 or min(t1, t2) > 1:
                return None

            t1 = IIP(max(0, t1))
            t2 = IIP(min(1, t2))

            if CE(t1, t2):
                return other.p1

            return type(self)(self.pointFromParametricValue(t1),
                              self.pointFromParametricValue(t2))

        # the slopes differ, so solve the equations
        factorMatrix = matrix.Matrix(
            ((self.p2.x - self.p1.x, other.p1.x - other.p2.x, 0),
             (self.p2.y - self.p1.y, other.p1.y - other.p2.y, 0), (0, 0, 1)))

        fmInverse = factorMatrix.inverse()
        assert fmInverse is not None

        constMatrix = matrix.Matrix(
            ((other.p1.x - self.p1.x, 0, 0), (other.p1.y - self.p1.y, 0, 0),
             (0, 0, 1)))

        prod = fmInverse.multiply(constMatrix)
        t = IIP(prod[0][0])
        u = IIP(prod[1][0])

        if CES(t) and CES(u):
            return self.pointFromParametricValue(t)

        return None  # no intersection within the actual length
Ejemplo n.º 8
0
def _validate(d, **kwArgs):
    editor = kwArgs['editor']
    scaler = kwArgs.get('scaler', None)
    logger = kwArgs['logger']

    logger = logger.getChild('vdmxrecord %d:%d' %
                             (d.ratio.xRatio, d.ratio.yEndRatio))

    if editor is None or (not editor.reallyHas(b'maxp')):
        logger.warning(
            ('W2503', (),
             "Unable to validate contents because there was no Editor "
             "or no 'maxp' table was available."))

        return False

    try:
        r = kwArgs.get('recalculateditem', None)
        diff = r != d

        if r is None:
            diff, r = _recalc(d, **kwArgs)

    except:
        logger.error(('V0554', (),
                      "An error occured in the scaler during device metrics "
                      "calculation, preventing validation."))

        return False

    if diff:
        fontGlyphCount = editor.maxp.numGlyphs

        for ppem, recalcrec in r.group.items():
            currec = d.group[ppem]

            if currec.yMax > recalcrec.yMax or currec.yMin < recalcrec.yMin:

                # VDMX is "too big"...only a warning. This is just
                # "inefficient"; it won't cause clipping.

                logger.warning(('W2500', (ppem, currec.yMax, currec.yMin,
                                          recalcrec.yMax, recalcrec.yMin),
                                "Record for %d ppem [%d,%d] is larger than "
                                "the calculated value [%d,%d]."))

            if currec.yMax < recalcrec.yMax or currec.yMin > recalcrec.yMin:

                # VDMX is "too small"...this is an error, as it will cause
                # clipping under Windows. If we have a scaler, report on
                # individual glyphs.

                if scaler is not None:
                    try:
                        ratio = d.ratio

                        if ratio.xRatio > 1 or ratio.yEndRatio > 1:
                            xs = (ratio.xRatio * 1.0) / (ratio.yEndRatio * 1.0)
                        else:
                            xs = 1

                        mat = matrix.Matrix(([xs * ppem, 0.0,
                                              0.0], [0.0, ppem * 1.0,
                                                     0.0], [0.0, 0.0, 1.0]))

                        try:
                            scaler.setTransform(mat)
                        except:
                            raise ScalerError()

                        badYMax = span2.Span()
                        badYMin = span2.Span()

                        for g in range(fontGlyphCount):
                            try:
                                m = scaler.getOutlineMetrics(g)
                            except:
                                raise ScalerError()

                            if oldRound(m.hi_y) > currec.yMax:
                                badYMax.add(g)

                            if oldRound(m.lo_y) < currec.yMin:
                                badYMin.add(g)

                        if len(badYMax) > 0:
                            logger.error(
                                ('V0775', (currec.yMax, ppem, str(badYMax)),
                                 "The following glyphs' actual yMax values "
                                 "exceed the stored value of %d at %d ppem: "
                                 "%s. Clipping may occur."))

                        if len(badYMin) > 0:
                            logger.error(
                                ('V0775', (currec.yMin, ppem, str(badYMin)),
                                 "The following glyphs' actual yMin values "
                                 "exceed the stored value of %d at %d ppem: "
                                 "%s. Clipping may occur."))

                    except ScalerError:
                        logger.error(
                            ('V0554', (),
                             "An error occured in the scaler during device "
                             "metrics calculation, preventing validation."))

                        return False

                # Otherwise just report generically for the ppem (using MS
                # error code).

                else:
                    logger.error(
                        ('E2500', (ppem, currec.yMax, currec.yMin,
                                   recalcrec.yMax, recalcrec.yMin),
                         "Record for %d ppem [%d,%d] is smaller than the "
                         "calculated value [%d,%d]. Clipping may occur."))
        return False

    logger.info(('P2500', (), "The VDMX data matches the expected values."))

    return True
Ejemplo n.º 9
0
# Test code
#

if 0:

    def __________________():
        pass


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

    _m1 = matrix.Matrix.forShift(0, -40)

    _m2 = matrix.Matrix([[1.25, 0.75, 0.0], [0.0, 1.5, 0.0], [300.0, 0.0,
                                                              1.0]])

    _testingValues = (TTComponent(),
                      TTComponent(glyphIndex=80,
                                  transformationMatrix=_m1,
                                  roundToGrid=True),
                      TTComponent(glyphIndex=100,
                                  transformationMatrix=_m2,
                                  useMyMetrics=True),
                      TTComponent(glyphIndex=901), TTComponent(glyphIndex=902),
                      TTComponent(glyphIndex=903), TTComponent(glyphIndex=904),
                      TTComponent(glyphIndex=905))

    del _m1, _m2