Edited 06-29: updated how to compute the length of the direction vector. See the end of this post.
Intuitively, the Chinese stroke “㇐” has a horizontal direction, while “㇓” has a mostly top-right to bottom-left direction. How do we determine the “direction” of a stroke?
First we need to figure out what is a stroke and what is a “direction”. Since we are working in a 2D plane, we can instead use a method for any closed loop without self-intersection. Most frequently, these shapes are represented in piecewise parametric curves[1], which is also the case for font-shapped characters from free-type fonts. One possible definition of “direction” is the main axis of the smallest bounding ellipsis. Computing the smallest bounding ellipsis is hard to do analytically, but we can deal with another form of ellipsis much more easily: statistics has given us a tool for dealing with fitted ellipsis: principal component analysis (PCA).
In practice, PCA is always used with samples, but instead we can abuse it to compute the ellipsis fitted to a multivariate distribution with known PDF. In our case, we can define the “direction” of a shape to be the main axis of the ellipsis fitted to the uniform distribution over the internal of .
What do we need
First, the PDF of the distribution over the internal of some shape closed with internal and area is:
So first, we need to compute the area of . Another subtle thing that needs to be determine is the orientation of , because it’s not guaranteed that it has conter-clockwise orientation. Both of these can be computed in a single step by first assuming has the correct orientation, compute the area as-is, and then judge the orientation by the sign of .
Now with the PDF fully defined, we need to compute the covariance matrix of this distribution. This in turn requires the following values:
- and
Since and , so we need the first and second central moment of x and y, which are:
And for the covariance:
Finally, compute the largest eigenvector & eigenvalue for the covariance matrix:
Since this is a symmetric matrix, a real eigenvalue & eigenvector is guaranteed.
Computation
In practice, all curves are SVG paths. We first preprocess the paths to split them into non-overlapping components that do not self-intersect.
Then we can use the lyon_algorithms
crate for sampling along a SVG path. Extra caution should be taken for the left-over segment at the end of the integration.
For computing the eigenvalue and eigenvector, we used two tricks for 2x2 matrices, one from 3b1b (YouTube), and another one from math stackexchange. For the following matrix:
Recall that the sum of eigenvalues , and the product of eigenvalues . So we can directly solve for the larger eigenvalue .
Then, we can compute the (unnormalized) eigenvector corresponding to to be:
BTW, since we are dealing with a symmetric real matrix (), it’s guaranteed that:
Finally, we scale the eigenvector to have the length , because now it repersent the “standard derivation” along that axis.
Result

The above figure shows the computed direction vector scaled by 10. Because the length of the original vector is the standard derivation along the principal axis, it’s almost always being covered by the stroke itself.
Also because of this, the length of the vector does not directly reflect the “outer dimension” of the stroke. The distribution of thickness along the axis will affect the length of the vector. One may argue this is not a good indicator of the dimension of a shape. In that case, a distribution along the boundary of the shape may be used instead of the internal of the shape.
But for me, this is good enough™, and does reflect the optical heaviness of the various part of a stroke, so I’m settling with this.
Edit 06/29: We noticed a problem with the aforementioned approach to compute the length of direction vectors. When the primary and secondary component has similar derivation, the direction of the primary component alone may not actually suggest the shape of the original distribution. An example is the dot in the figure above.
Instead, we choose to scale the direction vector based on the ratio between the derivations along primary and secondary component (square root of the two eigenvalues):
This mapping has the property that if , then , effectively suggests that the shape has no direction. The more eccentric the fitted ellipsis is, the closer is to 1. One can also just the use ellipsis eccentricity.
Numerically, indicates a small vector shape. A good fallback is to just return .