basePen.py :  » GUI » TTX-FontTools » fonttools-2.3 » Lib » fontTools » pens » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » GUI » TTX FontTools 
TTX FontTools » fonttools 2.3 » Lib » fontTools » pens » basePen.py
"""fontTools.pens.basePen.py -- Tools and base classes to build pen objects.

The Pen Protocol

A Pen is a kind of object that standardizes the way how to "draw" outlines:
it is a middle man between an outline and a drawing. In other words:
it is an abstraction for drawing outlines, making sure that outline objects
don't need to know the details about how and where they're being drawn, and
that drawings don't need to know the details of how outlines are stored.

The most basic pattern is this:

    outline.draw(pen)  # 'outline' draws itself onto 'pen'

Pens can be used to render outlines to the screen, but also to construct
new outlines. Eg. an outline object can be both a drawable object (it has a
draw() method) as well as a pen itself: you *build* an outline using pen
methods.

The AbstractPen class defines the Pen protocol. It implements almost
nothing (only no-op closePath() and endPath() methods), but is useful
for documentation purposes. Subclassing it basically tells the reader:
"this class implements the Pen protocol.". An examples of an AbstractPen
subclass is fontTools.pens.transformPen.TransformPen.

The BasePen class is a base implementation useful for pens that actually
draw (for example a pen renders outlines using a native graphics engine).
BasePen contains a lot of base functionality, making it very easy to build
a pen that fully conforms to the pen protocol. Note that if you subclass
BasePen, you _don't_ override moveTo(), lineTo(), etc., but _moveTo(),
_lineTo(), etc. See the BasePen doc string for details. Examples of
BasePen subclasses are fontTools.pens.boundsPen.BoundsPen and
fontTools.pens.cocoaPen.CocoaPen.

Coordinates are usually expressed as (x, y) tuples, but generally any
sequence of length 2 will do.
"""


__all__ = ["AbstractPen", "BasePen",
           "decomposeSuperBezierSegment", "decomposeQuadraticSegment"]


class AbstractPen:

  def moveTo(self, pt):
    """Begin a new sub path, set the current point to 'pt'. You must
    end each sub path with a call to pen.closePath() or pen.endPath().
    """
    raise NotImplementedError

  def lineTo(self, pt):
    """Draw a straight line from the current point to 'pt'."""
    raise NotImplementedError

  def curveTo(self, *points):
    """Draw a cubic bezier with an arbitrary number of control points.

    The last point specified is on-curve, all others are off-curve
    (control) points. If the number of control points is > 2, the
    segment is split into multiple bezier segments. This works
    like this:

    Let n be the number of control points (which is the number of
    arguments to this call minus 1). If n==2, a plain vanilla cubic
    bezier is drawn. If n==1, we fall back to a quadratic segment and
    if n==0 we draw a straight line. It gets interesting when n>2:
    n-1 PostScript-style cubic segments will be drawn as if it were
    one curve. See decomposeSuperBezierSegment().

    The conversion algorithm used for n>2 is inspired by NURB
    splines, and is conceptually equivalent to the TrueType "implied
    points" principle. See also decomposeQuadraticSegment().
    """
    raise NotImplementedError

  def qCurveTo(self, *points):
    """Draw a whole string of quadratic curve segments.

    The last point specified is on-curve, all others are off-curve
    points.

    This method implements TrueType-style curves, breaking up curves
    using 'implied points': between each two consequtive off-curve points,
    there is one implied point exactly in the middle between them. See
    also decomposeQuadraticSegment().

    The last argument (normally the on-curve point) may be None.
    This is to support contours that have NO on-curve points (a rarely
    seen feature of TrueType outlines).
    """
    raise NotImplementedError

  def closePath(self):
    """Close the current sub path. You must call either pen.closePath()
    or pen.endPath() after each sub path.
    """
    pass

  def endPath(self):
    """End the current sub path, but don't close it. You must call
    either pen.closePath() or pen.endPath() after each sub path.
    """
    pass

  def addComponent(self, glyphName, transformation):
    """Add a sub glyph. The 'transformation' argument must be a 6-tuple
    containing an affine transformation, or a Transform object from the
    fontTools.misc.transform module. More precisely: it should be a
    sequence containing 6 numbers.
    """
    raise NotImplementedError


class BasePen(AbstractPen):

  """Base class for drawing pens. You must override _moveTo, _lineTo and
  _curveToOne. You may additionally override _closePath, _endPath,
  addComponent and/or _qCurveToOne. You should not override any other
  methods.
  """

  def __init__(self, glyphSet):
    self.glyphSet = glyphSet
    self.__currentPoint = None

  # must override

  def _moveTo(self, pt):
    raise NotImplementedError

  def _lineTo(self, pt):
    raise NotImplementedError

  def _curveToOne(self, pt1, pt2, pt3):
    raise NotImplementedError

  # may override

  def _closePath(self):
    pass

  def _endPath(self):
    pass

  def _qCurveToOne(self, pt1, pt2):
    """This method implements the basic quadratic curve type. The
    default implementation delegates the work to the cubic curve
    function. Optionally override with a native implementation.
    """
    pt0x, pt0y = self.__currentPoint
    pt1x, pt1y = pt1
    pt2x, pt2y = pt2
    mid1x = pt0x + 0.66666666666666667 * (pt1x - pt0x)
    mid1y = pt0y + 0.66666666666666667 * (pt1y - pt0y)
    mid2x = pt2x + 0.66666666666666667 * (pt1x - pt2x)
    mid2y = pt2y + 0.66666666666666667 * (pt1y - pt2y)
    self._curveToOne((mid1x, mid1y), (mid2x, mid2y), pt2)

  def addComponent(self, glyphName, transformation):
    """This default implementation simply transforms the points
    of the base glyph and draws it onto self.
    """
    from fontTools.pens.transformPen import TransformPen
    try:
      glyph = self.glyphSet[glyphName]
    except KeyError:
      pass
    else:
      tPen = TransformPen(self, transformation)
      glyph.draw(tPen)

  # don't override

  def _getCurrentPoint(self):
    """Return the current point. This is not part of the public
    interface, yet is useful for subclasses.
    """
    return self.__currentPoint

  def closePath(self):
    self._closePath()
    self.__currentPoint = None

  def endPath(self):
    self._endPath()
    self.__currentPoint = None

  def moveTo(self, pt):
    self._moveTo(pt)
    self.__currentPoint = pt

  def lineTo(self, pt):
    self._lineTo(pt)
    self.__currentPoint = pt

  def curveTo(self, *points):
    n = len(points) - 1  # 'n' is the number of control points
    assert n >= 0
    if n == 2:
      # The common case, we have exactly two BCP's, so this is a standard
      # cubic bezier. Even though decomposeSuperBezierSegment() handles
      # this case just fine, we special-case it anyway since it's so
      # common.
      self._curveToOne(*points)
      self.__currentPoint = points[-1]
    elif n > 2:
      # n is the number of control points; split curve into n-1 cubic
      # bezier segments. The algorithm used here is inspired by NURB
      # splines and the TrueType "implied point" principle, and ensures
      # the smoothest possible connection between two curve segments,
      # with no disruption in the curvature. It is practical since it
      # allows one to construct multiple bezier segments with a much
      # smaller amount of points.
      _curveToOne = self._curveToOne
      for pt1, pt2, pt3 in decomposeSuperBezierSegment(points):
        _curveToOne(pt1, pt2, pt3)
        self.__currentPoint = pt3
    elif n == 1:
      self.qCurveTo(*points)
    elif n == 0:
      self.lineTo(points[0])
    else:
      raise AssertionError, "can't get there from here"

  def qCurveTo(self, *points):
    n = len(points) - 1  # 'n' is the number of control points
    assert n >= 0
    if points[-1] is None:
      # Special case for TrueType quadratics: it is possible to
      # define a contour with NO on-curve points. BasePen supports
      # this by allowing the final argument (the expected on-curve
      # point) to be None. We simulate the feature by making the implied
      # on-curve point between the last and the first off-curve points
      # explicit.
      x, y = points[-2]  # last off-curve point
      nx, ny = points[0] # first off-curve point
      impliedStartPoint = (0.5 * (x + nx), 0.5 * (y + ny))
      self.__currentPoint = impliedStartPoint
      self._moveTo(impliedStartPoint)
      points = points[:-1] + (impliedStartPoint,)
    if n > 0:
      # Split the string of points into discrete quadratic curve
      # segments. Between any two consecutive off-curve points
      # there's an implied on-curve point exactly in the middle.
      # This is where the segment splits.
      _qCurveToOne = self._qCurveToOne
      for pt1, pt2 in decomposeQuadraticSegment(points):
        _qCurveToOne(pt1, pt2)
        self.__currentPoint = pt2
    else:
      self.lineTo(points[0])


def decomposeSuperBezierSegment(points):
  """Split the SuperBezier described by 'points' into a list of regular
  bezier segments. The 'points' argument must be a sequence with length
  3 or greater, containing (x, y) coordinates. The last point is the
  destination on-curve point, the rest of the points are off-curve points.
  The start point should not be supplied.

  This function returns a list of (pt1, pt2, pt3) tuples, which each
  specify a regular curveto-style bezier segment.
  """
  n = len(points) - 1
  assert n > 1
  bezierSegments = []
  pt1, pt2, pt3 = points[0], None, None
  for i in range(2, n+1):
    # calculate points in between control points.
    nDivisions = min(i, 3, n-i+2)
    d = float(nDivisions)
    for j in range(1, nDivisions):
      factor = j / d
      temp1 = points[i-1]
      temp2 = points[i-2]
      temp = (temp2[0] + factor * (temp1[0] - temp2[0]),
          temp2[1] + factor * (temp1[1] - temp2[1]))
      if pt2 is None:
        pt2 = temp
      else:
        pt3 = (0.5 * (pt2[0] + temp[0]),
             0.5 * (pt2[1] + temp[1]))
        bezierSegments.append((pt1, pt2, pt3))
        pt1, pt2, pt3 = temp, None, None
  bezierSegments.append((pt1, points[-2], points[-1]))
  return bezierSegments


def decomposeQuadraticSegment(points):
  """Split the quadratic curve segment described by 'points' into a list
  of "atomic" quadratic segments. The 'points' argument must be a sequence
  with length 2 or greater, containing (x, y) coordinates. The last point
  is the destination on-curve point, the rest of the points are off-curve
  points. The start point should not be supplied.

  This function returns a list of (pt1, pt2) tuples, which each specify a
  plain quadratic bezier segment.
  """
  n = len(points) - 1
  assert n > 0
  quadSegments = []
  for i in range(n - 1):
    x, y = points[i]
    nx, ny = points[i+1]
    impliedPt = (0.5 * (x + nx), 0.5 * (y + ny))
    quadSegments.append((points[i], impliedPt))
  quadSegments.append((points[-2], points[-1]))
  return quadSegments


class _TestPen(BasePen):
  """Test class that prints PostScript to stdout."""
  def _moveTo(self, pt):
    print "%s %s moveto" % (pt[0], pt[1])
  def _lineTo(self, pt):
    print "%s %s lineto" % (pt[0], pt[1])
  def _curveToOne(self, bcp1, bcp2, pt):
    print "%s %s %s %s %s %s curveto" % (bcp1[0], bcp1[1],
        bcp2[0], bcp2[1], pt[0], pt[1])
  def _closePath(self):
    print "closepath"


if __name__ == "__main__":
  pen = _TestPen(None)
  pen.moveTo((0, 0))
  pen.lineTo((0, 100))
  pen.curveTo((50, 75), (60, 50), (50, 25), (0, 0))
  pen.closePath()

  pen = _TestPen(None)
  # testing the "no on-curve point" scenario
  pen.qCurveTo((0, 0), (0, 100), (100, 100), (100, 0), None)
  pen.closePath()
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.