bezierTools.py :  » GUI » TTX-FontTools » fonttools-2.3 » Lib » fontTools » misc » 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 » misc » bezierTools.py
"""fontTools.misc.bezierTools.py -- tools for working with bezier path segments."""


__all__ = [
  "calcQuadraticBounds",
  "calcCubicBounds",
  "splitLine",
  "splitQuadratic",
  "splitCubic",
  "splitQuadraticAtT",
  "splitCubicAtT",
  "solveQuadratic",
  "solveCubic",
]

from fontTools.misc.arrayTools import calcBounds
import numpy

epsilon = 1e-12


def calcQuadraticBounds(pt1, pt2, pt3):
  """Return the bounding rectangle for a qudratic bezier segment.
  pt1 and pt3 are the "anchor" points, pt2 is the "handle".

    >>> calcQuadraticBounds((0, 0), (50, 100), (100, 0))
    (0.0, 0.0, 100.0, 50.0)
    >>> calcQuadraticBounds((0, 0), (100, 0), (100, 100))
    (0.0, 0.0, 100.0, 100.0)
  """
  a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
  # calc first derivative
  ax, ay = a * 2
  bx, by = b
  roots = []
  if ax != 0:
    roots.append(-bx/ax)
  if ay != 0:
    roots.append(-by/ay)
  points = [a*t*t + b*t + c for t in roots if 0 <= t < 1] + [pt1, pt3]
  return calcBounds(points)


def calcCubicBounds(pt1, pt2, pt3, pt4):
  """Return the bounding rectangle for a cubic bezier segment.
  pt1 and pt4 are the "anchor" points, pt2 and pt3 are the "handles".

    >>> calcCubicBounds((0, 0), (25, 100), (75, 100), (100, 0))
    (0.0, 0.0, 100.0, 75.0)
    >>> calcCubicBounds((0, 0), (50, 0), (100, 50), (100, 100))
    (0.0, 0.0, 100.0, 100.0)
    >>> calcCubicBounds((50, 0), (0, 100), (100, 100), (50, 0))
    (35.5662432703, 0.0, 64.4337567297, 75.0)
  """
  a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
  # calc first derivative
  ax, ay = a * 3.0
  bx, by = b * 2.0
  cx, cy = c
  xRoots = [t for t in solveQuadratic(ax, bx, cx) if 0 <= t < 1]
  yRoots = [t for t in solveQuadratic(ay, by, cy) if 0 <= t < 1]
  roots = xRoots + yRoots
  
  points = [(a*t*t*t + b*t*t + c * t + d) for t in roots] + [pt1, pt4]
  return calcBounds(points)


def splitLine(pt1, pt2, where, isHorizontal):
  """Split the line between pt1 and pt2 at position 'where', which
  is an x coordinate if isHorizontal is False, a y coordinate if
  isHorizontal is True. Return a list of two line segments if the
  line was successfully split, or a list containing the original
  line.

    >>> printSegments(splitLine((0, 0), (100, 100), 50, True))
    ((0, 0), (50.0, 50.0))
    ((50.0, 50.0), (100, 100))
    >>> printSegments(splitLine((0, 0), (100, 100), 100, True))
    ((0, 0), (100, 100))
    >>> printSegments(splitLine((0, 0), (100, 100), 0, True))
    ((0, 0), (0.0, 0.0))
    ((0.0, 0.0), (100, 100))
    >>> printSegments(splitLine((0, 0), (100, 100), 0, False))
    ((0, 0), (0.0, 0.0))
    ((0.0, 0.0), (100, 100))
  """
  pt1, pt2 = numpy.array((pt1, pt2))
  a = (pt2 - pt1)
  b = pt1
  ax = a[isHorizontal]
  if ax == 0:
    return [(pt1, pt2)]
  t = float(where - b[isHorizontal]) / ax
  if 0 <= t < 1:
    midPt = a * t + b
    return [(pt1, midPt), (midPt, pt2)]
  else:
    return [(pt1, pt2)]


def splitQuadratic(pt1, pt2, pt3, where, isHorizontal):
  """Split the quadratic curve between pt1, pt2 and pt3 at position 'where',
  which is an x coordinate if isHorizontal is False, a y coordinate if
  isHorizontal is True. Return a list of curve segments.

    >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 150, False))
    ((0, 0), (50, 100), (100, 0))
    >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 50, False))
    ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
    ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0))
    >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 25, False))
    ((0.0, 0.0), (12.5, 25.0), (25.0, 37.5))
    ((25.0, 37.5), (62.5, 75.0), (100.0, 0.0))
    >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 25, True))
    ((0.0, 0.0), (7.32233047034, 14.6446609407), (14.6446609407, 25.0))
    ((14.6446609407, 25.0), (50.0, 75.0), (85.3553390593, 25.0))
    ((85.3553390593, 25.0), (92.6776695297, 14.6446609407), (100.0, -7.1054273576e-15))
    >>> # XXX I'm not at all sure if the following behavior is desirable:
    >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 50, True))
    ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
    ((50.0, 50.0), (50.0, 50.0), (50.0, 50.0))
    ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0))
  """
  a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
  solutions = solveQuadratic(a[isHorizontal], b[isHorizontal],
    c[isHorizontal] - where)
  solutions = [t for t in solutions if 0 <= t < 1]
  solutions.sort()
  if not solutions:
    return [(pt1, pt2, pt3)]
  return _splitQuadraticAtT(a, b, c, *solutions)


def splitCubic(pt1, pt2, pt3, pt4, where, isHorizontal):
  """Split the cubic curve between pt1, pt2, pt3 and pt4 at position 'where',
  which is an x coordinate if isHorizontal is False, a y coordinate if
  isHorizontal is True. Return a list of curve segments.

    >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 150, False))
    ((0, 0), (25, 100), (75, 100), (100, 0))
    >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 50, False))
    ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0))
    ((50.0, 75.0), (68.75, 75.0), (87.5, 50.0), (100.0, 0.0))
    >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 25, True))
    ((0.0, 0.0), (2.2937927384, 9.17517095361), (4.79804488188, 17.5085042869), (7.47413641001, 25.0))
    ((7.47413641001, 25.0), (31.2886200204, 91.6666666667), (68.7113799796, 91.6666666667), (92.52586359, 25.0))
    ((92.52586359, 25.0), (95.2019551181, 17.5085042869), (97.7062072616, 9.17517095361), (100.0, 1.7763568394e-15))
  """
  a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
  solutions = solveCubic(a[isHorizontal], b[isHorizontal], c[isHorizontal],
    d[isHorizontal] - where)
  solutions = [t for t in solutions if 0 <= t < 1]
  solutions.sort()
  if not solutions:
    return [(pt1, pt2, pt3, pt4)]
  return _splitCubicAtT(a, b, c, d, *solutions)


def splitQuadraticAtT(pt1, pt2, pt3, *ts):
  """Split the quadratic curve between pt1, pt2 and pt3 at one or more
  values of t. Return a list of curve segments.

    >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5))
    ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
    ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0))
    >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5, 0.75))
    ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
    ((50.0, 50.0), (62.5, 50.0), (75.0, 37.5))
    ((75.0, 37.5), (87.5, 25.0), (100.0, 0.0))
  """
  a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
  return _splitQuadraticAtT(a, b, c, *ts)


def splitCubicAtT(pt1, pt2, pt3, pt4, *ts):
  """Split the cubic curve between pt1, pt2, pt3 and pt4 at one or more
  values of t. Return a list of curve segments.

    >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5))
    ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0))
    ((50.0, 75.0), (68.75, 75.0), (87.5, 50.0), (100.0, 0.0))
    >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5, 0.75))
    ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0))
    ((50.0, 75.0), (59.375, 75.0), (68.75, 68.75), (77.34375, 56.25))
    ((77.34375, 56.25), (85.9375, 43.75), (93.75, 25.0), (100.0, 0.0))
  """
  a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
  return _splitCubicAtT(a, b, c, d, *ts)


def _splitQuadraticAtT(a, b, c, *ts):
  ts = list(ts)
  segments = []
  ts.insert(0, 0.0)
  ts.append(1.0)
  for i in range(len(ts) - 1):
    t1 = ts[i]
    t2 = ts[i+1]
    delta = (t2 - t1)
    # calc new a, b and c
    a1 = a * delta**2
    b1 = (2*a*t1 + b) * delta
    c1 = a*t1**2 + b*t1 + c
    pt1, pt2, pt3 = calcQuadraticPoints(a1, b1, c1)
    segments.append((pt1, pt2, pt3))
  return segments


def _splitCubicAtT(a, b, c, d, *ts):
  ts = list(ts)
  ts.insert(0, 0.0)
  ts.append(1.0)
  segments = []
  for i in range(len(ts) - 1):
    t1 = ts[i]
    t2 = ts[i+1]
    delta = (t2 - t1)
    # calc new a, b, c and d
    a1 = a * delta**3
    b1 = (3*a*t1 + b) * delta**2
    c1 = (2*b*t1 + c + 3*a*t1**2) * delta
    d1 = a*t1**3 + b*t1**2 + c*t1 + d
    pt1, pt2, pt3, pt4 = calcCubicPoints(a1, b1, c1, d1)
    segments.append((pt1, pt2, pt3, pt4))
  return segments


#
# Equation solvers.
#

from math import sqrt,acos,cos,pi


def solveQuadratic(a, b, c,
    sqrt=sqrt):
  """Solve a quadratic equation where a, b and c are real.
      a*x*x + b*x + c = 0
  This function returns a list of roots. Note that the returned list
  is neither guaranteed to be sorted nor to contain unique values!
  """
  if abs(a) < epsilon:
    if abs(b) < epsilon:
      # We have a non-equation; therefore, we have no valid solution
      roots = []
    else:
      # We have a linear equation with 1 root.
      roots = [-c/b]
  else:
    # We have a true quadratic equation.  Apply the quadratic formula to find two roots.
    DD = b*b - 4.0*a*c
    if DD >= 0.0:
      rDD = sqrt(DD)
      roots = [(-b+rDD)/2.0/a, (-b-rDD)/2.0/a]
    else:
      # complex roots, ignore
      roots = []
  return roots


def solveCubic(a, b, c, d,
    abs=abs, pow=pow, sqrt=sqrt, cos=cos, acos=acos, pi=pi):
  """Solve a cubic equation where a, b, c and d are real.
      a*x*x*x + b*x*x + c*x + d = 0
  This function returns a list of roots. Note that the returned list
  is neither guaranteed to be sorted nor to contain unique values!
  """
  #
  # adapted from:
  #   CUBIC.C - Solve a cubic polynomial
  #   public domain by Ross Cottrell
  # found at: http://www.strangecreations.com/library/snippets/Cubic.C
  #
  if abs(a) < epsilon:
    # don't just test for zero; for very small values of 'a' solveCubic()
    # returns unreliable results, so we fall back to quad.
    return solveQuadratic(b, c, d)
  a = float(a)
  a1 = b/a
  a2 = c/a
  a3 = d/a
  
  Q = (a1*a1 - 3.0*a2)/9.0
  R = (2.0*a1*a1*a1 - 9.0*a1*a2 + 27.0*a3)/54.0
  R2_Q3 = R*R - Q*Q*Q

  if R2_Q3 < 0:
    theta = acos(R/sqrt(Q*Q*Q))
    rQ2 = -2.0*sqrt(Q)
    x0 = rQ2*cos(theta/3.0) - a1/3.0
    x1 = rQ2*cos((theta+2.0*pi)/3.0) - a1/3.0
    x2 = rQ2*cos((theta+4.0*pi)/3.0) - a1/3.0
    return [x0, x1, x2]
  else:
    if Q == 0 and R == 0:
      x = 0
    else:
      x = pow(sqrt(R2_Q3)+abs(R), 1/3.0)
      x = x + Q/x
    if R >= 0.0:
      x = -x
    x = x - a1/3.0
    return [x]


#
# Conversion routines for points to parameters and vice versa
#

def calcQuadraticParameters(pt1, pt2, pt3):
  pt1, pt2, pt3 = numpy.array((pt1, pt2, pt3))
  c = pt1
  b = (pt2 - c) * 2.0
  a = pt3 - c - b
  return a, b, c


def calcCubicParameters(pt1, pt2, pt3, pt4):
  pt1, pt2, pt3, pt4 = numpy.array((pt1, pt2, pt3, pt4))
  d = pt1
  c = (pt2 - d) * 3.0
  b = (pt3 - pt2) * 3.0 - c
  a = pt4 - d - c - b
  return a, b, c, d


def calcQuadraticPoints(a, b, c):
  pt1 = c
  pt2 = (b * 0.5) + c
  pt3 = a + b + c
  return pt1, pt2, pt3


def calcCubicPoints(a, b, c, d):
  pt1 = d
  pt2 = (c / 3.0) + d
  pt3 = (b + c) / 3.0 + pt2
  pt4 = a + d + c + b
  return pt1, pt2, pt3, pt4


def _segmentrepr(obj):
  """
    >>> _segmentrepr([1, [2, 3], [], [[2, [3, 4], numpy.array([0.1, 2.2])]]])
    '(1, (2, 3), (), ((2, (3, 4), (0.1, 2.2))))'
  """
  try:
    it = iter(obj)
  except TypeError:
    return str(obj)
  else:
    return "(%s)" % ", ".join([_segmentrepr(x) for x in it])


def printSegments(segments):
  """Helper for the doctests, displaying each segment in a list of
  segments on a single line as a tuple.
  """
  for segment in segments:
    print _segmentrepr(segment)


if __name__ == "__main__":
  import doctest
  doctest.testmod()
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.