box size text
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { type FC, useEffect, useState } from 'react'
|
||||
import Konva from 'konva'
|
||||
import { clx } from '@/helpers/utils'
|
||||
import EditorSidebar from './components/EditorSidebar'
|
||||
import EditorCanvas from './components/EditorCanvas'
|
||||
@@ -10,6 +11,16 @@ import { useEditorStore } from './store/editorStore'
|
||||
const Editor: FC = () => {
|
||||
|
||||
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 { loadPages, pages } = useEditorStore()
|
||||
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 Konva from "konva";
|
||||
import type { TextShapeProps } from "./types";
|
||||
@@ -29,6 +29,15 @@ const getFontWeight = (fontWeight?: string): string => {
|
||||
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 => {
|
||||
if (!fill) return "#000000";
|
||||
if (opacity === undefined || opacity === 100) return fill;
|
||||
@@ -61,26 +70,90 @@ const TextShape = ({
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Update actual text dimensions after render
|
||||
// Only update if width/height are not set (initial render)
|
||||
useEffect(() => {
|
||||
if (shapeRef.current && (!obj.width || !obj.height)) {
|
||||
const node = shapeRef.current;
|
||||
const clientRect = node.getClientRect({ skipTransform: true });
|
||||
// همتراز کردن width/height با Konva؛ بعد از لود وبفونت باید `_setTextData` دوباره اجرا شود
|
||||
// وگرنه Konva هنوز عرض خطوط را با متریک فونت جایگزین نگه میدارد و باکس از متن کوچک میماند.
|
||||
useLayoutEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
// Update store with actual dimensions
|
||||
if (clientRect.width && clientRect.height) {
|
||||
onUpdate(obj.id, {
|
||||
width: Math.ceil(clientRect.width),
|
||||
height: Math.ceil(clientRect.height),
|
||||
});
|
||||
const syncFromNode = () => {
|
||||
if (cancelled) return;
|
||||
const textNode = shapeRef.current;
|
||||
const groupNode = groupRef.current;
|
||||
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 });
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [obj.text, obj.fontSize, obj.fontFamily, obj.fontWeight, obj.letterSpacing]);
|
||||
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;
|
||||
}
|
||||
|
||||
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 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 handleDblClick = () => {
|
||||
@@ -125,7 +198,7 @@ const TextShape = ({
|
||||
minHeight: `${box.height}px`,
|
||||
fontSize: `${(obj.fontSize || 24) * stageScale}px`,
|
||||
fontFamily: fontFamily,
|
||||
fontWeight: fontWeight,
|
||||
fontWeight: getFontWeight(obj.fontWeight),
|
||||
color: obj.fill || "#000000",
|
||||
letterSpacing: `${(obj.letterSpacing || 0) * stageScale}px`,
|
||||
lineHeight: textNode.lineHeight().toString(),
|
||||
@@ -214,8 +287,6 @@ const TextShape = ({
|
||||
name="text-object"
|
||||
x={obj.x}
|
||||
y={obj.y}
|
||||
width={obj.width}
|
||||
height={obj.height}
|
||||
rotation={obj.rotation || 0}
|
||||
draggable={draggable && !isEditing}
|
||||
onClick={(e) => {
|
||||
@@ -240,7 +311,7 @@ const TextShape = ({
|
||||
fontSize={obj.fontSize || 24}
|
||||
fill={fillColor}
|
||||
fontFamily={fontFamily}
|
||||
fontStyle={fontWeight}
|
||||
fontStyle={konvaFontStyle}
|
||||
letterSpacing={obj.letterSpacing ?? 0}
|
||||
align="right"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user