mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-23 03:25:07 +03:00
Backend moved to a separate repository
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
/** Material expressive shape SVG paths — generated from Compose MaterialShapes. */
|
||||
|
||||
export { MATERIAL_SHAPE_PATHS } from "./materialShapes.generated";
|
||||
|
||||
import { MATERIAL_SHAPE_PATHS } from "./materialShapes.generated";
|
||||
|
||||
export type MaterialShapeName = keyof typeof MATERIAL_SHAPE_PATHS;
|
||||
|
||||
export function getMaterialShapePath(name: string): string {
|
||||
return MATERIAL_SHAPE_PATHS[name] ?? MATERIAL_SHAPE_PATHS.Circle;
|
||||
}
|
||||
|
||||
interface PathBounds {
|
||||
minX: number;
|
||||
minY: number;
|
||||
maxX: number;
|
||||
maxY: number;
|
||||
}
|
||||
|
||||
export interface PathUnitSquareFit {
|
||||
transform: string;
|
||||
}
|
||||
|
||||
const pathFitCache = new Map<string, PathUnitSquareFit>();
|
||||
|
||||
/**
|
||||
* Parses M/L/Z path commands and returns axis-aligned bounds of all vertices.
|
||||
* Generated Material shape paths use only these commands.
|
||||
*/
|
||||
function computePathBounds(pathD: string): PathBounds {
|
||||
const tokens = pathD.trim().match(/[MLZmlz]|[-+]?(?:\d*\.\d+|\d+)(?:[eE][-+]?\d+)?/g);
|
||||
if (!tokens?.length) {
|
||||
return { minX: 0, minY: 0, maxX: 1, maxY: 1 };
|
||||
}
|
||||
|
||||
let minX = Infinity;
|
||||
let minY = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let maxY = -Infinity;
|
||||
let curX = 0;
|
||||
let curY = 0;
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let cmd = "";
|
||||
let i = 0;
|
||||
|
||||
const extend = (x: number, y: number) => {
|
||||
minX = Math.min(minX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxX = Math.max(maxX, x);
|
||||
maxY = Math.max(maxY, y);
|
||||
curX = x;
|
||||
curY = y;
|
||||
};
|
||||
|
||||
while (i < tokens.length) {
|
||||
const token = tokens[i];
|
||||
if (/^[A-Za-z]$/.test(token)) {
|
||||
cmd = token;
|
||||
i++;
|
||||
if (cmd === "Z" || cmd === "z") {
|
||||
extend(startX, startY);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const x = Number(tokens[i++]);
|
||||
const y = Number(tokens[i++]);
|
||||
|
||||
let absX = x;
|
||||
let absY = y;
|
||||
switch (cmd) {
|
||||
case "M":
|
||||
absX = x;
|
||||
absY = y;
|
||||
startX = absX;
|
||||
startY = absY;
|
||||
cmd = "L";
|
||||
break;
|
||||
case "m":
|
||||
absX = curX + x;
|
||||
absY = curY + y;
|
||||
startX = absX;
|
||||
startY = absY;
|
||||
cmd = "l";
|
||||
break;
|
||||
case "L":
|
||||
absX = x;
|
||||
absY = y;
|
||||
break;
|
||||
case "l":
|
||||
absX = curX + x;
|
||||
absY = curY + y;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
extend(absX, absY);
|
||||
}
|
||||
|
||||
return { minX, minY, maxX, maxY };
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a normalized Material shape path to fill viewBox `0 0 1 1` edge-to-edge.
|
||||
*/
|
||||
export function fitPathToUnitSquare(pathD: string): PathUnitSquareFit {
|
||||
const cached = pathFitCache.get(pathD);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const { minX, minY, maxX, maxY } = computePathBounds(pathD);
|
||||
const width = maxX - minX;
|
||||
const height = maxY - minY;
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
const fallback = { transform: "" };
|
||||
pathFitCache.set(pathD, fallback);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const sx = 1 / width;
|
||||
const sy = 1 / height;
|
||||
const fit: PathUnitSquareFit = {
|
||||
transform: `scale(${sx}, ${sy}) translate(${-minX}, ${-minY})`,
|
||||
};
|
||||
pathFitCache.set(pathD, fit);
|
||||
return fit;
|
||||
}
|
||||
Reference in New Issue
Block a user