From c742ff3829c672a8c0f4f025ffeb981274943df9 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Wed, 29 Apr 2026 12:17:19 +0330 Subject: [PATCH] border radius --- .../components/canvas/ObjectRenderer.tsx | 1 + .../sidebar/settings/ShapeSettings.tsx | 16 ++++++++++ .../components/tools/RectangleShape.tsx | 2 ++ src/pages/editor/store/editorStore.types.ts | 1 + src/pages/editor/utils/exportUtils.ts | 32 ++++++++++++++++--- src/pages/viewer/components/BookPage.tsx | 13 +++++--- src/pages/viewer/utils/dataTransformer.ts | 4 +++ 7 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/pages/editor/components/canvas/ObjectRenderer.tsx b/src/pages/editor/components/canvas/ObjectRenderer.tsx index 59d0731..367786a 100644 --- a/src/pages/editor/components/canvas/ObjectRenderer.tsx +++ b/src/pages/editor/components/canvas/ObjectRenderer.tsx @@ -177,6 +177,7 @@ const ObjectRenderer = ({ y={relativeY} width={mask.width || 100} height={mask.height || 100} + cornerRadius={Math.max(0, mask.borderRadius ?? 0)} rotation={mask.rotation || 0} /> ); diff --git a/src/pages/editor/components/sidebar/settings/ShapeSettings.tsx b/src/pages/editor/components/sidebar/settings/ShapeSettings.tsx index ef11d1a..613f6a1 100644 --- a/src/pages/editor/components/sidebar/settings/ShapeSettings.tsx +++ b/src/pages/editor/components/sidebar/settings/ShapeSettings.tsx @@ -13,6 +13,9 @@ const ShapeSettings = ({ selectedObject, onUpdate }: ShapeSettingsProps) => { const baseFill = selectedObject.fill ?? "#3b82f6"; const baseStroke = selectedObject.stroke ?? "#1e40af"; const baseStrokeWidth = selectedObject.strokeWidth ?? 0; + const baseBorderRadius = selectedObject.borderRadius ?? 0; + const isSquareShape = + selectedObject.shapeType === "square" || selectedObject.shapeType === undefined; return (
@@ -65,6 +68,19 @@ const ShapeSettings = ({ selectedObject, onUpdate }: ShapeSettingsProps) => { } min={0} /> + {isSquareShape && ( + + onUpdate(selectedObject.id, { + borderRadius: Math.max(0, parseInt(e.target.value) || 0), + }) + } + min={0} + /> + )}
); }; diff --git a/src/pages/editor/components/tools/RectangleShape.tsx b/src/pages/editor/components/tools/RectangleShape.tsx index c1d7eee..f9d7030 100644 --- a/src/pages/editor/components/tools/RectangleShape.tsx +++ b/src/pages/editor/components/tools/RectangleShape.tsx @@ -13,6 +13,7 @@ const RectangleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: Shap const actualStrokeWidth = obj.strokeWidth ?? 0; const hasStroke = actualStrokeWidth > 0; + const cornerRadius = Math.max(0, obj.borderRadius ?? 0); // برای نمایش انتخاب: اگر strokeWidth واقعی > 0 است، از آن استفاده کن، وگرنه stroke را برای انتخاب نمایش بده const displayStroke = isSelected @@ -36,6 +37,7 @@ const RectangleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: Shap y={obj.y} width={obj.width || 100} height={obj.height || 100} + cornerRadius={cornerRadius} fill={isMask ? "transparent" : obj.fill} stroke={displayStroke} strokeWidth={displayStrokeWidth} diff --git a/src/pages/editor/store/editorStore.types.ts b/src/pages/editor/store/editorStore.types.ts index db4bb17..54015b5 100644 --- a/src/pages/editor/store/editorStore.types.ts +++ b/src/pages/editor/store/editorStore.types.ts @@ -57,6 +57,7 @@ export type EditorObject = { scaleX?: number; scaleY?: number; shapeType?: ShapeType; + borderRadius?: number; tableData?: TableData; visible?: boolean; isMask?: boolean; diff --git a/src/pages/editor/utils/exportUtils.ts b/src/pages/editor/utils/exportUtils.ts index 788a956..7360418 100644 --- a/src/pages/editor/utils/exportUtils.ts +++ b/src/pages/editor/utils/exportUtils.ts @@ -75,12 +75,32 @@ const drawMaskShape = ( // Default: Rectangle const width = maskShape.width || 100; const height = maskShape.height || 100; - ctx.fillRect(0, 0, width, height); + const radius = Math.max(0, maskShape.borderRadius || 0); + drawRoundedRectPath(ctx, 0, 0, width, height, radius); + ctx.fill(); } ctx.restore(); }; +const drawRoundedRectPath = ( + ctx: CanvasRenderingContext2D, + x: number, + y: number, + width: number, + height: number, + radius: number +): void => { + const normalizedRadius = Math.max(0, Math.min(radius, width / 2, height / 2)); + ctx.beginPath(); + if (normalizedRadius === 0) { + ctx.rect(x, y, width, height); + } else { + ctx.roundRect(x, y, width, height, normalizedRadius); + } + ctx.closePath(); +}; + /** * رسم یک object روی canvas context * این تابع یک نسخه ساده است - در پیاده‌سازی واقعی باید @@ -122,10 +142,12 @@ const drawObject = async ( ctx.fill(); if (ctx.lineWidth > 0) ctx.stroke(); } else { - ctx.fillRect(0, 0, object.width || 100, object.height || 100); - if (ctx.lineWidth > 0) { - ctx.strokeRect(0, 0, object.width || 100, object.height || 100); - } + const width = object.width || 100; + const height = object.height || 100; + const radius = Math.max(0, object.borderRadius || 0); + drawRoundedRectPath(ctx, 0, 0, width, height, radius); + ctx.fill(); + if (ctx.lineWidth > 0) ctx.stroke(); } } // برای text objects diff --git a/src/pages/viewer/components/BookPage.tsx b/src/pages/viewer/components/BookPage.tsx index 5646731..af32069 100644 --- a/src/pages/viewer/components/BookPage.tsx +++ b/src/pages/viewer/components/BookPage.tsx @@ -49,6 +49,8 @@ const BookPage = forwardRef( }; const renderObject = (obj: EditorObject, index: number) => { + const actualStrokeWidth = obj.strokeWidth ?? 0; + const hasStroke = actualStrokeWidth > 0; const baseStyle: React.CSSProperties = { position: 'absolute', left: `${(obj.x || 0) * scale}px`, @@ -220,8 +222,8 @@ const BookPage = forwardRef( height: `${radius * 2}px`, borderRadius: '50%', backgroundColor: obj.fill || 'transparent', - border: obj.stroke - ? `${(obj.strokeWidth || 1) * scale}px solid ${obj.stroke}` + border: hasStroke && obj.stroke + ? `${actualStrokeWidth * scale}px solid ${obj.stroke}` : 'none', opacity: obj.opacity ?? 1, transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined, @@ -261,7 +263,7 @@ const BookPage = forwardRef( const bottomLeftX = halfSize - halfWidth; const bottomRightX = halfSize + halfWidth; - const strokeWidth = obj.stroke ? (obj.strokeWidth || 1) * scale : 0; + const strokeWidth = hasStroke && obj.stroke ? actualStrokeWidth * scale : 0; const fillColor = obj.fill || '#000000'; const strokeColor = obj.stroke || 'transparent'; @@ -330,8 +332,9 @@ const BookPage = forwardRef( width: `${(obj.width || 100) * scale}px`, height: `${(obj.height || 100) * scale}px`, backgroundColor: obj.fill || 'transparent', - border: obj.stroke - ? `${(obj.strokeWidth || 1) * scale}px solid ${obj.stroke}` + borderRadius: `${Math.max(0, (obj.borderRadius || 0) * scale)}px`, + border: hasStroke && obj.stroke + ? `${actualStrokeWidth * scale}px solid ${obj.stroke}` : 'none', zIndex: index, // برای حفظ ترتیب رندر }; diff --git a/src/pages/viewer/utils/dataTransformer.ts b/src/pages/viewer/utils/dataTransformer.ts index fd8c0ca..d406edb 100644 --- a/src/pages/viewer/utils/dataTransformer.ts +++ b/src/pages/viewer/utils/dataTransformer.ts @@ -19,6 +19,7 @@ type ViewerDataPage = { stroke?: string; strokeWidth?: number; shapeType?: string; + borderRadius?: number; imageUrl?: string; videoUrl?: string; linkUrl?: string; @@ -93,6 +94,9 @@ export function transformViewerDataToPages(data: ViewerData): PageData[] { if (obj.type === "rectangle" && obj.shapeType) { baseObject.shapeType = obj.shapeType as EditorObject["shapeType"]; } + if (obj.type === "rectangle" && obj.borderRadius !== undefined) { + baseObject.borderRadius = obj.borderRadius; + } if (obj.type === "image" && obj.imageUrl) { baseObject.imageUrl = obj.imageUrl;