Математика трансформаций очень простая.
Отрендерить текст на изображение можно используя
https://opentype.js.org/
Вы можете отрендерить каждую букву используя библиотеку, а далее уже отрендеренные буквы использовать на своем сайте без библиотеки.
Либо использовать эту же библиотеку для того, чтобы получить path data для каждой буквы.
Если ваша трансформация сводится к аффинным преобразованиям, то можно рассчитывать координаты не каждой точки изображения буквы, а только точки, которые участвуют в path data. Таким образом преобразуя все точки path data вы получите такую же path data, используя которую вы начертаете шрифты в любом месте на любом изображении. Это может дать очень выгодный прирост к производительности (потенциально до 1000х раз). Кроме того, линии path data "рисуется" с правильным расчетом межпиксельного пространства, и вам не надо будет делать ресамплинг, как при работе с изображениями.
Но рисовать шрифты по безье - это не всегда можно свести к аффинным преобразованиям.
На верхней иллюстрации у вас используется искажение по оси X: skewX()
Получить координаты каждой точки можно умножив матрицу с исходными точками на матрицу CTM.
https://www.w3.org/TR/SVG11/coords.html#TransformM...
По ссылке выше вы найдёте матрицы с формулами аффинных преобразований в двухмерном пространстве. Матрицы имеют вид (3х3) из которых 6 чисел значимые, 3 числа - статичные (всегда [0, 0, 1]).
Для 3D-преобразований используются матрицы 4х4 из которых 12 чисел значимые, 4 числа - статичные (всегда [0, 0, 0, 1]):
https://drafts.csswg.org/css-transforms-2/#mathema...
Для расчета 3d-трансформаций с учетом перспективы используются матрицы 8х8.
Рассчитать координаты безье можно функциями (соответственно, квадратичная и кубическая безьё):
// [p0x, p0x] - are coordinates of origin point
// [p1x, p1y] - are coordinates of single control point
// [p2x, p2y] - are coordinates of destination point
// T - is number of points that needs to draw the curve
const quadratic = ([p0x,p0y], [p1x,p1y], [p2x,p2y], T = 60) => {
const x = t => (1 - t)**2 * p0x + 2 * (1 - t) * t * p1x + t**2 * p2x;
const y = t => (1 - t)**2 * p0y + 2 * (1 - t) * t * p1y + t**2 * p2y;
return Array.from({ length: T+1 }).map((_, t) => [x(t / T), y(t / T)]);
};
// [p0x, p0x] - are coordinates of origin point
// [p1x, p1y] - are coordinates of first control point
// [p2x, p2y] - are coordinates of second control point
// [p3x, p3y] - are coordinates of destination point
// T - is number of points that needs to draw the curve
const cubic = ([p0x, p0y], [p1x, p1y], [p2x, p2y], [p3x, p3y], T = 60) => {
const y = t => (1 - t)**3 * p0y + 3 * (1 - t)**2 * t * p1y + 3 * (1 - t) * t**2 * p2y + t**3 * p3y;
const x = t => (1 - t)**3 * p0x + 3 * (1 - t)**2 * t * p1x + 3 * (1 - t) * t**2 * p2x + t**3 * p3x;
return Array.from({ length: T+1 }).map((_, t) => [x(t / T), y(t / T)]);
};
На нижней иллюстрации вы используются трехмерные искажения.
Про математику 3D искажении с перспективой можно прочитать тут
https://www.cs.cmu.edu/~ph/texfund/texfund.pdf
Там рассматриваются и формулы получения координат на плоскости и ресамплинг (чтобы, например, не было пикселизации шрифтов после трансформации).
Для перемножений матриц можно использовать функцию
const multiply = (a, b) => a.map((_, r) => b[0].map((_, c) => a[r].reduce((s,_,i) => s + a[r][i] * b[i][c], 0)));