cu2qu.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. # cython: language_level=3
  2. # distutils: define_macros=CYTHON_TRACE_NOGIL=1
  3. # Copyright 2015 Google Inc. All Rights Reserved.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. try:
  17. import cython
  18. COMPILED = cython.compiled
  19. except (AttributeError, ImportError):
  20. # if cython not installed, use mock module with no-op decorators and types
  21. from fontTools.misc import cython
  22. COMPILED = False
  23. import math
  24. from .errors import Error as Cu2QuError, ApproxNotFoundError
  25. __all__ = ["curve_to_quadratic", "curves_to_quadratic"]
  26. MAX_N = 100
  27. NAN = float("NaN")
  28. @cython.cfunc
  29. @cython.inline
  30. @cython.returns(cython.double)
  31. @cython.locals(v1=cython.complex, v2=cython.complex)
  32. def dot(v1, v2):
  33. """Return the dot product of two vectors.
  34. Args:
  35. v1 (complex): First vector.
  36. v2 (complex): Second vector.
  37. Returns:
  38. double: Dot product.
  39. """
  40. return (v1 * v2.conjugate()).real
  41. @cython.cfunc
  42. @cython.inline
  43. @cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
  44. @cython.locals(
  45. _1=cython.complex, _2=cython.complex, _3=cython.complex, _4=cython.complex
  46. )
  47. def calc_cubic_points(a, b, c, d):
  48. _1 = d
  49. _2 = (c / 3.0) + d
  50. _3 = (b + c) / 3.0 + _2
  51. _4 = a + d + c + b
  52. return _1, _2, _3, _4
  53. @cython.cfunc
  54. @cython.inline
  55. @cython.locals(
  56. p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex
  57. )
  58. @cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
  59. def calc_cubic_parameters(p0, p1, p2, p3):
  60. c = (p1 - p0) * 3.0
  61. b = (p2 - p1) * 3.0 - c
  62. d = p0
  63. a = p3 - d - c - b
  64. return a, b, c, d
  65. @cython.cfunc
  66. @cython.inline
  67. @cython.locals(
  68. p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex
  69. )
  70. def split_cubic_into_n_iter(p0, p1, p2, p3, n):
  71. """Split a cubic Bezier into n equal parts.
  72. Splits the curve into `n` equal parts by curve time.
  73. (t=0..1/n, t=1/n..2/n, ...)
  74. Args:
  75. p0 (complex): Start point of curve.
  76. p1 (complex): First handle of curve.
  77. p2 (complex): Second handle of curve.
  78. p3 (complex): End point of curve.
  79. Returns:
  80. An iterator yielding the control points (four complex values) of the
  81. subcurves.
  82. """
  83. # Hand-coded special-cases
  84. if n == 2:
  85. return iter(split_cubic_into_two(p0, p1, p2, p3))
  86. if n == 3:
  87. return iter(split_cubic_into_three(p0, p1, p2, p3))
  88. if n == 4:
  89. a, b = split_cubic_into_two(p0, p1, p2, p3)
  90. return iter(
  91. split_cubic_into_two(a[0], a[1], a[2], a[3])
  92. + split_cubic_into_two(b[0], b[1], b[2], b[3])
  93. )
  94. if n == 6:
  95. a, b = split_cubic_into_two(p0, p1, p2, p3)
  96. return iter(
  97. split_cubic_into_three(a[0], a[1], a[2], a[3])
  98. + split_cubic_into_three(b[0], b[1], b[2], b[3])
  99. )
  100. return _split_cubic_into_n_gen(p0, p1, p2, p3, n)
  101. @cython.locals(
  102. p0=cython.complex,
  103. p1=cython.complex,
  104. p2=cython.complex,
  105. p3=cython.complex,
  106. n=cython.int,
  107. )
  108. @cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
  109. @cython.locals(
  110. dt=cython.double, delta_2=cython.double, delta_3=cython.double, i=cython.int
  111. )
  112. @cython.locals(
  113. a1=cython.complex, b1=cython.complex, c1=cython.complex, d1=cython.complex
  114. )
  115. def _split_cubic_into_n_gen(p0, p1, p2, p3, n):
  116. a, b, c, d = calc_cubic_parameters(p0, p1, p2, p3)
  117. dt = 1 / n
  118. delta_2 = dt * dt
  119. delta_3 = dt * delta_2
  120. for i in range(n):
  121. t1 = i * dt
  122. t1_2 = t1 * t1
  123. # calc new a, b, c and d
  124. a1 = a * delta_3
  125. b1 = (3 * a * t1 + b) * delta_2
  126. c1 = (2 * b * t1 + c + 3 * a * t1_2) * dt
  127. d1 = a * t1 * t1_2 + b * t1_2 + c * t1 + d
  128. yield calc_cubic_points(a1, b1, c1, d1)
  129. @cython.cfunc
  130. @cython.inline
  131. @cython.locals(
  132. p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex
  133. )
  134. @cython.locals(mid=cython.complex, deriv3=cython.complex)
  135. def split_cubic_into_two(p0, p1, p2, p3):
  136. """Split a cubic Bezier into two equal parts.
  137. Splits the curve into two equal parts at t = 0.5
  138. Args:
  139. p0 (complex): Start point of curve.
  140. p1 (complex): First handle of curve.
  141. p2 (complex): Second handle of curve.
  142. p3 (complex): End point of curve.
  143. Returns:
  144. tuple: Two cubic Beziers (each expressed as a tuple of four complex
  145. values).
  146. """
  147. mid = (p0 + 3 * (p1 + p2) + p3) * 0.125
  148. deriv3 = (p3 + p2 - p1 - p0) * 0.125
  149. return (
  150. (p0, (p0 + p1) * 0.5, mid - deriv3, mid),
  151. (mid, mid + deriv3, (p2 + p3) * 0.5, p3),
  152. )
  153. @cython.cfunc
  154. @cython.inline
  155. @cython.locals(
  156. p0=cython.complex,
  157. p1=cython.complex,
  158. p2=cython.complex,
  159. p3=cython.complex,
  160. )
  161. @cython.locals(
  162. mid1=cython.complex,
  163. deriv1=cython.complex,
  164. mid2=cython.complex,
  165. deriv2=cython.complex,
  166. )
  167. def split_cubic_into_three(p0, p1, p2, p3):
  168. """Split a cubic Bezier into three equal parts.
  169. Splits the curve into three equal parts at t = 1/3 and t = 2/3
  170. Args:
  171. p0 (complex): Start point of curve.
  172. p1 (complex): First handle of curve.
  173. p2 (complex): Second handle of curve.
  174. p3 (complex): End point of curve.
  175. Returns:
  176. tuple: Three cubic Beziers (each expressed as a tuple of four complex
  177. values).
  178. """
  179. mid1 = (8 * p0 + 12 * p1 + 6 * p2 + p3) * (1 / 27)
  180. deriv1 = (p3 + 3 * p2 - 4 * p0) * (1 / 27)
  181. mid2 = (p0 + 6 * p1 + 12 * p2 + 8 * p3) * (1 / 27)
  182. deriv2 = (4 * p3 - 3 * p1 - p0) * (1 / 27)
  183. return (
  184. (p0, (2 * p0 + p1) / 3.0, mid1 - deriv1, mid1),
  185. (mid1, mid1 + deriv1, mid2 - deriv2, mid2),
  186. (mid2, mid2 + deriv2, (p2 + 2 * p3) / 3.0, p3),
  187. )
  188. @cython.cfunc
  189. @cython.inline
  190. @cython.returns(cython.complex)
  191. @cython.locals(
  192. t=cython.double,
  193. p0=cython.complex,
  194. p1=cython.complex,
  195. p2=cython.complex,
  196. p3=cython.complex,
  197. )
  198. @cython.locals(_p1=cython.complex, _p2=cython.complex)
  199. def cubic_approx_control(t, p0, p1, p2, p3):
  200. """Approximate a cubic Bezier using a quadratic one.
  201. Args:
  202. t (double): Position of control point.
  203. p0 (complex): Start point of curve.
  204. p1 (complex): First handle of curve.
  205. p2 (complex): Second handle of curve.
  206. p3 (complex): End point of curve.
  207. Returns:
  208. complex: Location of candidate control point on quadratic curve.
  209. """
  210. _p1 = p0 + (p1 - p0) * 1.5
  211. _p2 = p3 + (p2 - p3) * 1.5
  212. return _p1 + (_p2 - _p1) * t
  213. @cython.cfunc
  214. @cython.inline
  215. @cython.returns(cython.complex)
  216. @cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
  217. @cython.locals(ab=cython.complex, cd=cython.complex, p=cython.complex, h=cython.double)
  218. def calc_intersect(a, b, c, d):
  219. """Calculate the intersection of two lines.
  220. Args:
  221. a (complex): Start point of first line.
  222. b (complex): End point of first line.
  223. c (complex): Start point of second line.
  224. d (complex): End point of second line.
  225. Returns:
  226. complex: Location of intersection if one present, ``complex(NaN,NaN)``
  227. if no intersection was found.
  228. """
  229. ab = b - a
  230. cd = d - c
  231. p = ab * 1j
  232. try:
  233. h = dot(p, a - c) / dot(p, cd)
  234. except ZeroDivisionError:
  235. return complex(NAN, NAN)
  236. return c + cd * h
  237. @cython.cfunc
  238. @cython.returns(cython.int)
  239. @cython.locals(
  240. tolerance=cython.double,
  241. p0=cython.complex,
  242. p1=cython.complex,
  243. p2=cython.complex,
  244. p3=cython.complex,
  245. )
  246. @cython.locals(mid=cython.complex, deriv3=cython.complex)
  247. def cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance):
  248. """Check if a cubic Bezier lies within a given distance of the origin.
  249. "Origin" means *the* origin (0,0), not the start of the curve. Note that no
  250. checks are made on the start and end positions of the curve; this function
  251. only checks the inside of the curve.
  252. Args:
  253. p0 (complex): Start point of curve.
  254. p1 (complex): First handle of curve.
  255. p2 (complex): Second handle of curve.
  256. p3 (complex): End point of curve.
  257. tolerance (double): Distance from origin.
  258. Returns:
  259. bool: True if the cubic Bezier ``p`` entirely lies within a distance
  260. ``tolerance`` of the origin, False otherwise.
  261. """
  262. # First check p2 then p1, as p2 has higher error early on.
  263. if abs(p2) <= tolerance and abs(p1) <= tolerance:
  264. return True
  265. # Split.
  266. mid = (p0 + 3 * (p1 + p2) + p3) * 0.125
  267. if abs(mid) > tolerance:
  268. return False
  269. deriv3 = (p3 + p2 - p1 - p0) * 0.125
  270. return cubic_farthest_fit_inside(
  271. p0, (p0 + p1) * 0.5, mid - deriv3, mid, tolerance
  272. ) and cubic_farthest_fit_inside(mid, mid + deriv3, (p2 + p3) * 0.5, p3, tolerance)
  273. @cython.cfunc
  274. @cython.inline
  275. @cython.locals(tolerance=cython.double)
  276. @cython.locals(
  277. q1=cython.complex,
  278. c0=cython.complex,
  279. c1=cython.complex,
  280. c2=cython.complex,
  281. c3=cython.complex,
  282. )
  283. def cubic_approx_quadratic(cubic, tolerance):
  284. """Approximate a cubic Bezier with a single quadratic within a given tolerance.
  285. Args:
  286. cubic (sequence): Four complex numbers representing control points of
  287. the cubic Bezier curve.
  288. tolerance (double): Permitted deviation from the original curve.
  289. Returns:
  290. Three complex numbers representing control points of the quadratic
  291. curve if it fits within the given tolerance, or ``None`` if no suitable
  292. curve could be calculated.
  293. """
  294. q1 = calc_intersect(cubic[0], cubic[1], cubic[2], cubic[3])
  295. if math.isnan(q1.imag):
  296. return None
  297. c0 = cubic[0]
  298. c3 = cubic[3]
  299. c1 = c0 + (q1 - c0) * (2 / 3)
  300. c2 = c3 + (q1 - c3) * (2 / 3)
  301. if not cubic_farthest_fit_inside(0, c1 - cubic[1], c2 - cubic[2], 0, tolerance):
  302. return None
  303. return c0, q1, c3
  304. @cython.cfunc
  305. @cython.locals(n=cython.int, tolerance=cython.double)
  306. @cython.locals(i=cython.int)
  307. @cython.locals(all_quadratic=cython.int)
  308. @cython.locals(
  309. c0=cython.complex, c1=cython.complex, c2=cython.complex, c3=cython.complex
  310. )
  311. @cython.locals(
  312. q0=cython.complex,
  313. q1=cython.complex,
  314. next_q1=cython.complex,
  315. q2=cython.complex,
  316. d1=cython.complex,
  317. )
  318. def cubic_approx_spline(cubic, n, tolerance, all_quadratic):
  319. """Approximate a cubic Bezier curve with a spline of n quadratics.
  320. Args:
  321. cubic (sequence): Four complex numbers representing control points of
  322. the cubic Bezier curve.
  323. n (int): Number of quadratic Bezier curves in the spline.
  324. tolerance (double): Permitted deviation from the original curve.
  325. Returns:
  326. A list of ``n+2`` complex numbers, representing control points of the
  327. quadratic spline if it fits within the given tolerance, or ``None`` if
  328. no suitable spline could be calculated.
  329. """
  330. if n == 1:
  331. return cubic_approx_quadratic(cubic, tolerance)
  332. if n == 2 and all_quadratic == False:
  333. return cubic
  334. cubics = split_cubic_into_n_iter(cubic[0], cubic[1], cubic[2], cubic[3], n)
  335. # calculate the spline of quadratics and check errors at the same time.
  336. next_cubic = next(cubics)
  337. next_q1 = cubic_approx_control(
  338. 0, next_cubic[0], next_cubic[1], next_cubic[2], next_cubic[3]
  339. )
  340. q2 = cubic[0]
  341. d1 = 0j
  342. spline = [cubic[0], next_q1]
  343. for i in range(1, n + 1):
  344. # Current cubic to convert
  345. c0, c1, c2, c3 = next_cubic
  346. # Current quadratic approximation of current cubic
  347. q0 = q2
  348. q1 = next_q1
  349. if i < n:
  350. next_cubic = next(cubics)
  351. next_q1 = cubic_approx_control(
  352. i / (n - 1), next_cubic[0], next_cubic[1], next_cubic[2], next_cubic[3]
  353. )
  354. spline.append(next_q1)
  355. q2 = (q1 + next_q1) * 0.5
  356. else:
  357. q2 = c3
  358. # End-point deltas
  359. d0 = d1
  360. d1 = q2 - c3
  361. if abs(d1) > tolerance or not cubic_farthest_fit_inside(
  362. d0,
  363. q0 + (q1 - q0) * (2 / 3) - c1,
  364. q2 + (q1 - q2) * (2 / 3) - c2,
  365. d1,
  366. tolerance,
  367. ):
  368. return None
  369. spline.append(cubic[3])
  370. return spline
  371. @cython.locals(max_err=cython.double)
  372. @cython.locals(n=cython.int)
  373. @cython.locals(all_quadratic=cython.int)
  374. def curve_to_quadratic(curve, max_err, all_quadratic=True):
  375. """Approximate a cubic Bezier curve with a spline of n quadratics.
  376. Args:
  377. cubic (sequence): Four 2D tuples representing control points of
  378. the cubic Bezier curve.
  379. max_err (double): Permitted deviation from the original curve.
  380. all_quadratic (bool): If True (default) returned value is a
  381. quadratic spline. If False, it's either a single quadratic
  382. curve or a single cubic curve.
  383. Returns:
  384. If all_quadratic is True: A list of 2D tuples, representing
  385. control points of the quadratic spline if it fits within the
  386. given tolerance, or ``None`` if no suitable spline could be
  387. calculated.
  388. If all_quadratic is False: Either a quadratic curve (if length
  389. of output is 3), or a cubic curve (if length of output is 4).
  390. """
  391. curve = [complex(*p) for p in curve]
  392. for n in range(1, MAX_N + 1):
  393. spline = cubic_approx_spline(curve, n, max_err, all_quadratic)
  394. if spline is not None:
  395. # done. go home
  396. return [(s.real, s.imag) for s in spline]
  397. raise ApproxNotFoundError(curve)
  398. @cython.locals(l=cython.int, last_i=cython.int, i=cython.int)
  399. @cython.locals(all_quadratic=cython.int)
  400. def curves_to_quadratic(curves, max_errors, all_quadratic=True):
  401. """Return quadratic Bezier splines approximating the input cubic Beziers.
  402. Args:
  403. curves: A sequence of *n* curves, each curve being a sequence of four
  404. 2D tuples.
  405. max_errors: A sequence of *n* floats representing the maximum permissible
  406. deviation from each of the cubic Bezier curves.
  407. all_quadratic (bool): If True (default) returned values are a
  408. quadratic spline. If False, they are either a single quadratic
  409. curve or a single cubic curve.
  410. Example::
  411. >>> curves_to_quadratic( [
  412. ... [ (50,50), (100,100), (150,100), (200,50) ],
  413. ... [ (75,50), (120,100), (150,75), (200,60) ]
  414. ... ], [1,1] )
  415. [[(50.0, 50.0), (75.0, 75.0), (125.0, 91.66666666666666), (175.0, 75.0), (200.0, 50.0)], [(75.0, 50.0), (97.5, 75.0), (135.41666666666666, 82.08333333333333), (175.0, 67.5), (200.0, 60.0)]]
  416. The returned splines have "implied oncurve points" suitable for use in
  417. TrueType ``glif`` outlines - i.e. in the first spline returned above,
  418. the first quadratic segment runs from (50,50) to
  419. ( (75 + 125)/2 , (120 + 91.666..)/2 ) = (100, 83.333...).
  420. Returns:
  421. If all_quadratic is True, a list of splines, each spline being a list
  422. of 2D tuples.
  423. If all_quadratic is False, a list of curves, each curve being a quadratic
  424. (length 3), or cubic (length 4).
  425. Raises:
  426. fontTools.cu2qu.Errors.ApproxNotFoundError: if no suitable approximation
  427. can be found for all curves with the given parameters.
  428. """
  429. curves = [[complex(*p) for p in curve] for curve in curves]
  430. assert len(max_errors) == len(curves)
  431. l = len(curves)
  432. splines = [None] * l
  433. last_i = i = 0
  434. n = 1
  435. while True:
  436. spline = cubic_approx_spline(curves[i], n, max_errors[i], all_quadratic)
  437. if spline is None:
  438. if n == MAX_N:
  439. break
  440. n += 1
  441. last_i = i
  442. continue
  443. splines[i] = spline
  444. i = (i + 1) % l
  445. if i == last_i:
  446. # done. go home
  447. return [[(s.real, s.imag) for s in spline] for spline in splines]
  448. raise ApproxNotFoundError(curves)