box size text
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { type FC, useEffect, useState } from 'react'
|
import { type FC, useEffect, useState } from 'react'
|
||||||
|
import Konva from 'konva'
|
||||||
import { clx } from '@/helpers/utils'
|
import { clx } from '@/helpers/utils'
|
||||||
import EditorSidebar from './components/EditorSidebar'
|
import EditorSidebar from './components/EditorSidebar'
|
||||||
import EditorCanvas from './components/EditorCanvas'
|
import EditorCanvas from './components/EditorCanvas'
|
||||||
@@ -10,6 +11,16 @@ import { useEditorStore } from './store/editorStore'
|
|||||||
const Editor: FC = () => {
|
const Editor: FC = () => {
|
||||||
|
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
|
|
||||||
|
// Konva 10 با legacyTextRendering=false متن را با baseline جدید میکشد ولی getWidth/getHeight
|
||||||
|
// و کادر ترنسفورمر هنوز بر اساس جعبهٔ قدیمیاند؛ نتیجه: متن از کادر آبی بیرون میزند.
|
||||||
|
useEffect(() => {
|
||||||
|
const prev = Konva.legacyTextRendering
|
||||||
|
Konva.legacyTextRendering = true
|
||||||
|
return () => {
|
||||||
|
Konva.legacyTextRendering = prev
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
const [isLayersPanelOpen, setIsLayersPanelOpen] = useState(false)
|
const [isLayersPanelOpen, setIsLayersPanelOpen] = useState(false)
|
||||||
const { loadPages, pages } = useEditorStore()
|
const { loadPages, pages } = useEditorStore()
|
||||||
const { data } = useGetCatalogById(id!)
|
const { data } = useGetCatalogById(id!)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef, useMemo, useState } from "react";
|
import { useEffect, useLayoutEffect, useRef, useMemo, useState } from "react";
|
||||||
import { Text, Group } from "react-konva";
|
import { Text, Group } from "react-konva";
|
||||||
import Konva from "konva";
|
import Konva from "konva";
|
||||||
import type { TextShapeProps } from "./types";
|
import type { TextShapeProps } from "./types";
|
||||||
@@ -29,6 +29,15 @@ const getFontWeight = (fontWeight?: string): string => {
|
|||||||
return weightMap[fontWeight] || "normal";
|
return weightMap[fontWeight] || "normal";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Konva.Text فقط `fontStyle` دارد (نه fontWeight جداگانه)؛ مقدار باید normal|bold|italic باشد. */
|
||||||
|
const getKonvaFontStyle = (fontWeight?: string): string => {
|
||||||
|
const w = getFontWeight(fontWeight);
|
||||||
|
if (w === "bold" || w === "bolder") return "bold";
|
||||||
|
const n = parseInt(w, 10);
|
||||||
|
if (!Number.isNaN(n) && n >= 600) return "bold";
|
||||||
|
return "normal";
|
||||||
|
};
|
||||||
|
|
||||||
const getColorWithOpacity = (fill?: string, opacity?: number): string => {
|
const getColorWithOpacity = (fill?: string, opacity?: number): string => {
|
||||||
if (!fill) return "#000000";
|
if (!fill) return "#000000";
|
||||||
if (opacity === undefined || opacity === 100) return fill;
|
if (opacity === undefined || opacity === 100) return fill;
|
||||||
@@ -61,26 +70,90 @@ const TextShape = ({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Update actual text dimensions after render
|
// همتراز کردن width/height با Konva؛ بعد از لود وبفونت باید `_setTextData` دوباره اجرا شود
|
||||||
// Only update if width/height are not set (initial render)
|
// وگرنه Konva هنوز عرض خطوط را با متریک فونت جایگزین نگه میدارد و باکس از متن کوچک میماند.
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (shapeRef.current && (!obj.width || !obj.height)) {
|
let cancelled = false;
|
||||||
const node = shapeRef.current;
|
|
||||||
const clientRect = node.getClientRect({ skipTransform: true });
|
|
||||||
|
|
||||||
// Update store with actual dimensions
|
const syncFromNode = () => {
|
||||||
if (clientRect.width && clientRect.height) {
|
if (cancelled) return;
|
||||||
onUpdate(obj.id, {
|
const textNode = shapeRef.current;
|
||||||
width: Math.ceil(clientRect.width),
|
const groupNode = groupRef.current;
|
||||||
height: Math.ceil(clientRect.height),
|
if (!textNode || !groupNode) return;
|
||||||
|
if (groupNode.scaleX() !== 1 || groupNode.scaleY() !== 1) return;
|
||||||
|
|
||||||
|
textNode._setTextData();
|
||||||
|
|
||||||
|
const { width: rw, height: rh } = textNode.getClientRect({
|
||||||
|
skipTransform: true,
|
||||||
});
|
});
|
||||||
|
const w = Math.ceil(rw);
|
||||||
|
const h = Math.ceil(rh);
|
||||||
|
if (w > 0 && h > 0 && (w !== obj.width || h !== obj.height)) {
|
||||||
|
onUpdate(obj.id, { width: w, height: h });
|
||||||
}
|
}
|
||||||
|
textNode.getLayer()?.batchDraw();
|
||||||
|
};
|
||||||
|
|
||||||
|
const scheduleSync = () => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (!cancelled) syncFromNode();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolvedFamily = getFontFamily(obj.fontFamily).split(",")[0].trim();
|
||||||
|
const fontSize = obj.fontSize || 24;
|
||||||
|
const konvaStyle = getKonvaFontStyle(obj.fontWeight);
|
||||||
|
|
||||||
|
const fontsApi = document.fonts;
|
||||||
|
|
||||||
|
const waitFontsThenSync = () => {
|
||||||
|
if (!fontsApi || typeof fontsApi.load !== "function") {
|
||||||
|
scheduleSync();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [obj.text, obj.fontSize, obj.fontFamily, obj.fontWeight, obj.letterSpacing]);
|
const specs = [
|
||||||
|
`${fontSize}px ${resolvedFamily}`,
|
||||||
|
`${konvaStyle} normal ${fontSize}px ${resolvedFamily}`,
|
||||||
|
`bold ${fontSize}px ${resolvedFamily}`,
|
||||||
|
`300 ${fontSize}px ${resolvedFamily}`,
|
||||||
|
`200 ${fontSize}px ${resolvedFamily}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
void Promise.all(
|
||||||
|
specs.map((s) => fontsApi.load(s).catch(() => [])),
|
||||||
|
).finally(() => {
|
||||||
|
if (!cancelled) scheduleSync();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
scheduleSync();
|
||||||
|
waitFontsThenSync();
|
||||||
|
|
||||||
|
const onLoadingDone = () => {
|
||||||
|
if (!cancelled) scheduleSync();
|
||||||
|
};
|
||||||
|
fontsApi?.addEventListener?.("loadingdone", onLoadingDone);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
fontsApi?.removeEventListener?.("loadingdone", onLoadingDone);
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
obj.id,
|
||||||
|
obj.text,
|
||||||
|
obj.fontSize,
|
||||||
|
obj.fontFamily,
|
||||||
|
obj.fontWeight,
|
||||||
|
obj.letterSpacing,
|
||||||
|
obj.width,
|
||||||
|
obj.height,
|
||||||
|
onUpdate,
|
||||||
|
]);
|
||||||
|
|
||||||
const fontFamily = useMemo(() => getFontFamily(obj.fontFamily), [obj.fontFamily]);
|
const fontFamily = useMemo(() => getFontFamily(obj.fontFamily), [obj.fontFamily]);
|
||||||
const fontWeight = useMemo(() => getFontWeight(obj.fontWeight), [obj.fontWeight]);
|
const konvaFontStyle = useMemo(() => getKonvaFontStyle(obj.fontWeight), [obj.fontWeight]);
|
||||||
const fillColor = useMemo(() => getColorWithOpacity(obj.fill, obj.opacity), [obj.fill, obj.opacity]);
|
const fillColor = useMemo(() => getColorWithOpacity(obj.fill, obj.opacity), [obj.fill, obj.opacity]);
|
||||||
|
|
||||||
const handleDblClick = () => {
|
const handleDblClick = () => {
|
||||||
@@ -125,7 +198,7 @@ const TextShape = ({
|
|||||||
minHeight: `${box.height}px`,
|
minHeight: `${box.height}px`,
|
||||||
fontSize: `${(obj.fontSize || 24) * stageScale}px`,
|
fontSize: `${(obj.fontSize || 24) * stageScale}px`,
|
||||||
fontFamily: fontFamily,
|
fontFamily: fontFamily,
|
||||||
fontWeight: fontWeight,
|
fontWeight: getFontWeight(obj.fontWeight),
|
||||||
color: obj.fill || "#000000",
|
color: obj.fill || "#000000",
|
||||||
letterSpacing: `${(obj.letterSpacing || 0) * stageScale}px`,
|
letterSpacing: `${(obj.letterSpacing || 0) * stageScale}px`,
|
||||||
lineHeight: textNode.lineHeight().toString(),
|
lineHeight: textNode.lineHeight().toString(),
|
||||||
@@ -214,8 +287,6 @@ const TextShape = ({
|
|||||||
name="text-object"
|
name="text-object"
|
||||||
x={obj.x}
|
x={obj.x}
|
||||||
y={obj.y}
|
y={obj.y}
|
||||||
width={obj.width}
|
|
||||||
height={obj.height}
|
|
||||||
rotation={obj.rotation || 0}
|
rotation={obj.rotation || 0}
|
||||||
draggable={draggable && !isEditing}
|
draggable={draggable && !isEditing}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -240,7 +311,7 @@ const TextShape = ({
|
|||||||
fontSize={obj.fontSize || 24}
|
fontSize={obj.fontSize || 24}
|
||||||
fill={fillColor}
|
fill={fillColor}
|
||||||
fontFamily={fontFamily}
|
fontFamily={fontFamily}
|
||||||
fontStyle={fontWeight}
|
fontStyle={konvaFontStyle}
|
||||||
letterSpacing={obj.letterSpacing ?? 0}
|
letterSpacing={obj.letterSpacing ?? 0}
|
||||||
align="right"
|
align="right"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user