The Tunni lines concept was devised by Eduardo Tunni and Fontlab Ltd., and is used in the FontLab font editor.
Many thanks go to Adam Twardoch and the people at FontLab who kindly gave permission to publish this repository.
https://oliverleenders.github.io/Tunni-Lines/
I have been amazed by the ingenuity and ease of use of FontLab's Tunni Line feature. Using either a single control point or dragging a line, the user is allowed to symmetrically and asymetrically change the shape of a cubic bezier curve. While the start and endpoints stay the same and the direction of the control handles remains fixed, the handles are linearly scaled in order to modify the curve.
Using the visualizations on the FontLab website, I have reverse engineered the math behind the
feature and implemented a simple example using D3.js
(probably overkill for such a simple thing
but hey).
Given a starting point
The Tunni Line however is simply a line between the points
Given the position of the tunni point
-
Find the halfway point
$h(a)$ between$t$ and$a$ , or respectively$h(b)$ with$t$ and$b$ .$$h_x(a) = \frac{a_x + t_x}{2}, \qquad h_y(a) = \frac{a_y + t_y}{2}$$ $$h_x(b) = \frac{b_x + t_x}{2}, \qquad h_y(b) = \frac{b_y + t_y}{2}$$ -
Add to
$h(a)$ (or${h(b)}$ ) the vector of the second (or first) control point$c^{(2)}$ (or$c^{(1)}$ ).$$h_x'(a) = h_x(a) + c_x^{(2)} - b_x, \qquad h_y'(a) = h_y(a) + c_y^{(2)} - b_y$$ $$h_x'(b) = h_x(b) + c_x^{(1)} - a_x, \qquad h_y'(b) = h_y(b) + c_y^{(1)} - a_y$$ -
Compute the intersection between the line
$\overline{h(a)h'(a)}$ and the line$\overline{ac^{(1)}}$ ($\overline{h(b)h'(b)}$ and$\overline{bc^{(2)}}$ respectively) and use the resulting points as new coordinates for$c^{(1)}$ (or$c^{(2)}$ ).
Let
In FontLab, it is possible to double click the Tunni Point to balance out the bezier curve. This can be achieved as follows:
- Compute the scale factors for both the control handles:
$$\lambda^{(1)} = \frac{||\overline{ca}||_2}{||\overline{sa}||_2} = \frac{\sqrt{\left(c_x^{(1)} - a_x\right)^2 + \left(c_y^{(1)} - a_y\right)^2}}{\sqrt{\left(s_x - a_x\right)^2 + \left(s_y - a_y\right)^2}}$$ $$\lambda^{(2)} = \frac{||\overline{cb}||_2}{||\overline{sb}||_2} = \frac{\sqrt{\left(c_x^{(2)} - b_x\right)^2 + \left(c_y^{(2)} - b_y\right)^2}}{\sqrt{\left(s_x - b_x\right)^2 + \left(s_y - b_y\right)^2}}$$ - Take the average of both scale factors.
$$\lambda^* = \frac{\lambda^{(1)} + \lambda^{(2)}}{2}$$ - Rescale the handles using
$\lambda^{\ast}$ .$$c_x^{(1)} = a_x + \lambda^* \cdot \left(s_x - a_x\right) \qquad c_y^{(1)} = a_y + \lambda^* \cdot \left(s_y - a_y\right)$$ $$c_x^{(2)} = b_x + \lambda^* \cdot \left(s_x - b_x\right) \qquad c_y^{(2)} = b_y + \lambda^* \cdot \left(s_y - b_y\right)$$
This "balance"-operation is different from other balancing operations beause it does not necessarily
ensure a smooth curve (especially at the joints of two incident bezier curves) but just states that
the tunni Line should be parralel to the line
For the Tunni Point to be "well" defined, both handles must lie on the same side of the line
The Tunni Line can remain visible at all times – well, almost. When both control points lie on the
line
Additional checks are required when moving the tunni line such that no control points passes through its corresponding anchor during scaling. When this happens, the dragging should end immediately.
Undo/Redo(done)- Download SVG
- (if I have a lot of time: snapping)