Векторизация шрифтов и textToPath
MixSlide поддерживает преобразование текста в SVG-пути прямо в runtime виджетов. Это позволяет строить эффектные текстовые фигуры (logo-like, обводка, текст по кривой, маски).
Доступные функции
textToPath(text, fontRefId, _fontSize, layoutOptions)
(_fontSizeв текущей реализации runtime передаётся как legacy-параметр и не влияет на расчёт. Размер шрифта задаётся в параметрах рендера/масштабировании viewBox.)textToPathAlongSVGPath(text, fontRefId, fontSize, pathData, layoutOptions)
Обе функции возвращают объект:
{
path: string;
bbox: { x, y, width, height };
ready: boolean;
topToBaseline?: number;
unitsPerEm?: number;
xHeight?: number;
}
Подготовка параметров
Для работы с шрифтом нужен параметр типа font:
const { text, font, fontSize } = useParams({
text: { type: "text", default: "Sample" },
font: { type: "font" },
fontSize: { type: "integer", min: 1, default: 48, step: 1 },
});
Объект font обычно содержит:
refId— ссылка на шрифт,features— OpenType features,variant— оси вариативности.
Рекомендуемый паттерн рендера
ready может быть false, пока шрифт не загрузился. Поэтому лучше считать путь и bbox через useBlockMemo, а затем безопасно строить viewBox.
function Widget() {
const { text, font, fontSize, width, height, fitWidth, fitHeight, fill, stroke, strokeWidth, letterSpacing, wordSpacing } = useParams({
text: { type: "text", default: "Привет" },
font: { type: "font" },
fontSize: { type: "integer", min: 6, max: 300, default: 48, step: 1 },
width: { type: "float" },
height: { type: "float" },
fitWidth: { type: "boolean", default: true },
fitHeight: { type: "boolean", default: true },
fill: { type: "colorGradient", default: "#000000" },
stroke: { type: "colorGradient", default: "#000000" },
strokeWidth: { type: "integer", min: 0, step: 1, default: 0 },
letterSpacing: { type: "float", default: 0 },
wordSpacing: { type: "float", default: 0 },
});
const {_fill, _stroke} = useBlockMemo("resolvedPaint", () => ({
_fill: isGradient(fill) ? undefined : fill,
_stroke: isGradient(stroke) ? undefined : stroke,
}), [fill, stroke]);
const { path, viewBox } = useBlockMemo("textPath", () => {
const result = textToPath(
text,
font?.refId,
fontSize,
{ letterSpacing, wordSpacing, features: font?.features, variant: font?.variant },
);
const bbox = result.ready && result.path ? getSvgPathBbox(result.path) : { x: 0, y: 0, width, height };
const vWidth = fitWidth ? bbox.width : width;
const vHeight = fitHeight ? bbox.height : height;
return {
path: result.path,
viewBox: `${fitWidth ? bbox.x : 0} ${fitHeight ? bbox.y : 0} ${vWidth} ${vHeight}`,
};
}, [text, font?.refId, fontSize, width, height, fitWidth, fitHeight, letterSpacing, wordSpacing, font?.features, font?.variant]);
return (
<svg width="100%" height="100%" viewBox={viewBox} preserveAspectRatio="none">
<path
d={path}
fill={_fill}
stroke={_stroke}
strokeWidth={strokeWidth}
vectorEffect="non-scaling-stroke"
/>
</svg>
);
}
Важные моменты
textToPathможет вернуться сready: falseдо подгрузки шрифта.- До того момента используйте fallback bbox/плейсхолдер, чтобы не получить нулевой
viewBox. - Для текста по кривой используйте
textToPathAlongSVGPath; для него обязателенpathData. font-объект лучше не кэшировать самосозданными глобальными переменными — храните зависимости черезuseBlockMemo.
![Пример текстового SVG с автоматическим пересчётом viewBox] []
Практические рекомендации по качеству результата
- Для сложных шрифтов включайте корректные
features/variantтолько в том случае, если они действительно нужны. - Ограничивайте максимальную длину текста на одном пути, чтобы не словить
maxSteps. - Если нужен стабильный рендер по умолчанию, используйте плоский текстовый fallback и переключайте в path только когда
ready=true. - Избегайте многократного пересчёта пути в каждом render — мемоизируйте через
useBlockMemo.