103 lines
2.8 KiB
TypeScript
103 lines
2.8 KiB
TypeScript
export type FillType = "solid" | "gradient";
|
|
|
|
export type LinearGradient = {
|
|
from: string;
|
|
to: string;
|
|
angle: number;
|
|
};
|
|
|
|
type Point = { x: number; y: number };
|
|
|
|
type GradientPointMode = "rect" | "centered";
|
|
|
|
const toRadians = (angle: number) => (angle * Math.PI) / 180;
|
|
|
|
const normalizeAngle = (angle: number) => {
|
|
if (!Number.isFinite(angle)) return 0;
|
|
return ((angle % 360) + 360) % 360;
|
|
};
|
|
|
|
const getGradientPoints = (
|
|
width: number,
|
|
height: number,
|
|
angle: number,
|
|
mode: GradientPointMode,
|
|
): { start: Point; end: Point } => {
|
|
const safeWidth = Math.max(1, width);
|
|
const safeHeight = Math.max(1, height);
|
|
// App/UI angle: 0° = top→bottom, 90° = left→right (clockwise).
|
|
const rad = toRadians(normalizeAngle(angle));
|
|
const half = Math.sqrt(safeWidth * safeWidth + safeHeight * safeHeight) / 2;
|
|
const halfX = Math.sin(rad) * half;
|
|
const halfY = Math.cos(rad) * half;
|
|
|
|
if (mode === "centered") {
|
|
return {
|
|
start: { x: -halfX, y: -halfY },
|
|
end: { x: halfX, y: halfY },
|
|
};
|
|
}
|
|
|
|
const cx = safeWidth / 2;
|
|
const cy = safeHeight / 2;
|
|
return {
|
|
start: { x: cx - halfX, y: cy - halfY },
|
|
end: { x: cx + halfX, y: cy + halfY },
|
|
};
|
|
};
|
|
|
|
export const getKonvaGradientProps = (
|
|
fillType: FillType | undefined,
|
|
gradient: LinearGradient | undefined,
|
|
width: number,
|
|
height: number,
|
|
mode: GradientPointMode,
|
|
offset: Point = { x: 0, y: 0 },
|
|
) => {
|
|
if (fillType !== "gradient" || !gradient) return {};
|
|
const points = getGradientPoints(width, height, gradient.angle, mode);
|
|
return {
|
|
fillPriority: "linear-gradient" as const,
|
|
fillLinearGradientStartPoint: {
|
|
x: points.start.x + offset.x,
|
|
y: points.start.y + offset.y,
|
|
},
|
|
fillLinearGradientEndPoint: {
|
|
x: points.end.x + offset.x,
|
|
y: points.end.y + offset.y,
|
|
},
|
|
fillLinearGradientColorStops: [0, gradient.from, 1, gradient.to] as [
|
|
number,
|
|
string,
|
|
number,
|
|
string,
|
|
],
|
|
};
|
|
};
|
|
|
|
/** SVG linearGradient endpoints matching the Konva gradient math. */
|
|
export const getSvgGradientEndpoints = (
|
|
gradient: LinearGradient,
|
|
width: number,
|
|
height: number,
|
|
mode: GradientPointMode = "rect",
|
|
offset: Point = { x: 0, y: 0 },
|
|
) => {
|
|
const points = getGradientPoints(width, height, gradient.angle, mode);
|
|
return {
|
|
x1: points.start.x + offset.x,
|
|
y1: points.start.y + offset.y,
|
|
x2: points.end.x + offset.x,
|
|
y2: points.end.y + offset.y,
|
|
};
|
|
};
|
|
|
|
/** Convert app/UI angle to CSS linear-gradient degrees (0deg = upward in CSS). */
|
|
export const toCssGradientAngle = (angle: number) =>
|
|
normalizeAngle(180 - normalizeAngle(angle));
|
|
|
|
export const toCssLinearGradient = (gradient: LinearGradient | undefined) => {
|
|
if (!gradient) return undefined;
|
|
return `linear-gradient(${toCssGradientAngle(gradient.angle)}deg, ${gradient.from} 0%, ${gradient.to} 100%)`;
|
|
};
|