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;