fix text shape place
This commit is contained in:
+30
-30
@@ -14,7 +14,7 @@
|
||||
"fontFamily": "irancell",
|
||||
"fontWeight": "bold",
|
||||
"fill": "#1a1a1a",
|
||||
"opacity": 1
|
||||
"opacity": 50
|
||||
},
|
||||
{
|
||||
"id": "image-1",
|
||||
@@ -24,7 +24,7 @@
|
||||
"width": 300,
|
||||
"height": 300,
|
||||
"imageUrl": "https://picsum.photos/200/300",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "rectangle-1",
|
||||
@@ -37,7 +37,7 @@
|
||||
"stroke": "#1e40af",
|
||||
"strokeWidth": 2,
|
||||
"shapeType": "square",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "circle-1",
|
||||
@@ -50,7 +50,7 @@
|
||||
"stroke": "#dc2626",
|
||||
"strokeWidth": 2,
|
||||
"shapeType": "circle",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "video-1",
|
||||
@@ -60,7 +60,7 @@
|
||||
"width": 400,
|
||||
"height": 300,
|
||||
"videoUrl": "https://loremipsum.video/vt/powerpoint-1.mp4",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -78,7 +78,7 @@
|
||||
"fontFamily": "irancell",
|
||||
"fontWeight": "bold",
|
||||
"fill": "#000000",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "line-1",
|
||||
@@ -89,7 +89,7 @@
|
||||
"height": 2,
|
||||
"stroke": "#64748b",
|
||||
"strokeWidth": 3,
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "arrow-1",
|
||||
@@ -100,7 +100,7 @@
|
||||
"height": 100,
|
||||
"stroke": "#f59e0b",
|
||||
"strokeWidth": 4,
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "triangle-1",
|
||||
@@ -113,7 +113,7 @@
|
||||
"stroke": "#059669",
|
||||
"strokeWidth": 2,
|
||||
"shapeType": "triangle",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "link-1",
|
||||
@@ -126,7 +126,7 @@
|
||||
"fontSize": 18,
|
||||
"linkUrl": "https://example.com",
|
||||
"fill": "#3b82f6",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "image-2",
|
||||
@@ -136,7 +136,7 @@
|
||||
"width": 250,
|
||||
"height": 200,
|
||||
"imageUrl": "https://picsum.photos/200/300",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -154,7 +154,7 @@
|
||||
"fontFamily": "irancell",
|
||||
"fontWeight": "normal",
|
||||
"fill": "#1a1a1a",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "grid-1",
|
||||
@@ -314,7 +314,7 @@
|
||||
"stroke": "#808080",
|
||||
"strokeWidth": 1
|
||||
},
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "abstract-1",
|
||||
@@ -327,7 +327,7 @@
|
||||
"stroke": "#7c3aed",
|
||||
"strokeWidth": 2,
|
||||
"shapeType": "abstract",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -345,7 +345,7 @@
|
||||
"fontFamily": "irancell",
|
||||
"fontWeight": "bold",
|
||||
"fill": "#1a1a1a",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "video-2",
|
||||
@@ -355,7 +355,7 @@
|
||||
"width": 350,
|
||||
"height": 250,
|
||||
"videoUrl": "https://loremipsum.video/vt/powerpoint-1.mp4",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "image-3",
|
||||
@@ -365,7 +365,7 @@
|
||||
"width": 250,
|
||||
"height": 250,
|
||||
"imageUrl": "https://picsum.photos/200/300",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "line-2",
|
||||
@@ -376,7 +376,7 @@
|
||||
"height": 2,
|
||||
"stroke": "#94a3b8",
|
||||
"strokeWidth": 2,
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "arrow-2",
|
||||
@@ -387,7 +387,7 @@
|
||||
"height": 80,
|
||||
"stroke": "#ec4899",
|
||||
"strokeWidth": 5,
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "circle-2",
|
||||
@@ -400,7 +400,7 @@
|
||||
"stroke": "#d97706",
|
||||
"strokeWidth": 3,
|
||||
"shapeType": "circle",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -418,7 +418,7 @@
|
||||
"fontFamily": "irancell",
|
||||
"fontWeight": "bold",
|
||||
"fill": "#1a1a1a",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "rectangle-2",
|
||||
@@ -432,7 +432,7 @@
|
||||
"strokeWidth": 2,
|
||||
"shapeType": "square",
|
||||
"rotation": 15,
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "triangle-2",
|
||||
@@ -446,7 +446,7 @@
|
||||
"strokeWidth": 2,
|
||||
"shapeType": "triangle",
|
||||
"rotation": 30,
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "abstract-2",
|
||||
@@ -460,7 +460,7 @@
|
||||
"strokeWidth": 2,
|
||||
"shapeType": "abstract",
|
||||
"rotation": -20,
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "image-4",
|
||||
@@ -471,7 +471,7 @@
|
||||
"height": 150,
|
||||
"imageUrl": "https://picsum.photos/200/300",
|
||||
"rotation": 10,
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "video-3",
|
||||
@@ -481,7 +481,7 @@
|
||||
"width": 300,
|
||||
"height": 200,
|
||||
"videoUrl": "https://loremipsum.video/vt/powerpoint-1.mp4",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "link-2",
|
||||
@@ -494,7 +494,7 @@
|
||||
"fontSize": 20,
|
||||
"linkUrl": "https://example.com",
|
||||
"fill": "#6366f1",
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "line-3",
|
||||
@@ -506,7 +506,7 @@
|
||||
"stroke": "#f97316",
|
||||
"strokeWidth": 4,
|
||||
"rotation": 45,
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "arrow-3",
|
||||
@@ -518,7 +518,7 @@
|
||||
"stroke": "#84cc16",
|
||||
"strokeWidth": 6,
|
||||
"rotation": -30,
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
},
|
||||
{
|
||||
"id": "text-6",
|
||||
@@ -531,7 +531,7 @@
|
||||
"fontWeight": "normal",
|
||||
"fill": "#475569",
|
||||
"letterSpacing": 1,
|
||||
"opacity": 1
|
||||
"opacity": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -15,6 +15,35 @@ type BookPageProps = {
|
||||
* نیاز به forwardRef دارد تا react-pageflip بتواند ref را مدیریت کند
|
||||
*/
|
||||
const BookPage = forwardRef<HTMLDivElement, BookPageProps>(({ page, scale = 1 }, ref) => {
|
||||
// تابع برای تبدیل opacity به رنگ (مثل editor)
|
||||
// در editor، getColorWithOpacity انتظار opacity 0-100 دارد
|
||||
// در dataTransformer، opacity از 0-1 به 0-100 تبدیل شده است
|
||||
// پس opacity را مستقیماً استفاده میکنیم
|
||||
const getColorWithOpacity = (fill?: string, opacity?: number): string => {
|
||||
if (!fill) return '#000000';
|
||||
|
||||
// opacity در EditorObject به صورت 0-100 است (بعد از تبدیل در dataTransformer)
|
||||
// در editor getColorWithOpacity انتظار 0-100 دارد
|
||||
const opacityValue = opacity !== undefined ? opacity : 100;
|
||||
|
||||
// اگر opacity 100% باشد، رنگ را بدون تغییر برگردان (مثل editor)
|
||||
if (opacityValue >= 100) return fill;
|
||||
|
||||
const hex = fill.replace('#', '');
|
||||
// اگر hex کوتاهتر از 6 کاراکتر باشد، padding اضافه کن
|
||||
const paddedHex = hex.length === 3
|
||||
? hex.split('').map(char => char + char).join('')
|
||||
: hex;
|
||||
|
||||
const r = parseInt(paddedHex.substring(0, 2), 16);
|
||||
const g = parseInt(paddedHex.substring(2, 4), 16);
|
||||
const b = parseInt(paddedHex.substring(4, 6), 16);
|
||||
// opacityValue را به alpha تبدیل کن (0-100 -> 0-1) مثل editor
|
||||
const alpha = opacityValue / 100;
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||
};
|
||||
|
||||
const renderObject = (obj: EditorObject, index: number) => {
|
||||
const baseStyle: React.CSSProperties = {
|
||||
position: 'absolute',
|
||||
@@ -30,16 +59,19 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(({ page, scale = 1 },
|
||||
}
|
||||
|
||||
switch (obj.type) {
|
||||
case 'text':
|
||||
case 'text': {
|
||||
// برای text، opacity در رنگ اعمال میشود، پس باید opacity را از baseStyle حذف کنیم
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { opacity, ...textBaseStyle } = baseStyle;
|
||||
return (
|
||||
<div
|
||||
key={obj.id || index}
|
||||
style={{
|
||||
...baseStyle,
|
||||
...textBaseStyle,
|
||||
fontSize: `${(obj.fontSize || 16) * scale}px`,
|
||||
fontFamily: obj.fontFamily || 'irancell, sans-serif',
|
||||
fontWeight: obj.fontWeight || 'normal',
|
||||
color: obj.fill || '#000000',
|
||||
color: getColorWithOpacity(obj.fill, obj.opacity),
|
||||
whiteSpace: 'pre-wrap',
|
||||
letterSpacing: obj.letterSpacing ? `${obj.letterSpacing * scale}px` : undefined,
|
||||
}}
|
||||
@@ -47,6 +79,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(({ page, scale = 1 },
|
||||
{obj.text || ''}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
case 'image':
|
||||
return (
|
||||
@@ -58,7 +91,8 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(({ page, scale = 1 },
|
||||
...baseStyle,
|
||||
width: obj.width ? `${obj.width * scale}px` : 'auto',
|
||||
height: obj.height ? `${obj.height * scale}px` : 'auto',
|
||||
objectFit: 'contain',
|
||||
objectFit: 'fill', // مشابه Konva که image را با width و height مشخص شده رندر میکند
|
||||
zIndex: index, // برای حفظ ترتیب رندر
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -102,11 +136,10 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(({ page, scale = 1 },
|
||||
</a>
|
||||
);
|
||||
|
||||
case 'rectangle':
|
||||
case 'rectangle': {
|
||||
if (obj.shapeType === 'circle') {
|
||||
// در editor، circle با مرکز در x, y رندر میشود (radius = width/2)
|
||||
// اما در viewer-data.json ممکن است x, y به عنوان گوشه بالا-چپ bounding box باشد
|
||||
// برای سازگاری با editor، فرض میکنیم x, y مرکز است
|
||||
// در dataTransformer از گوشه بالا-چپ به مرکز تبدیل شده است
|
||||
const radius = ((obj.width || 100) / 2) * scale;
|
||||
const centerX = (obj.x || 0) * scale;
|
||||
const centerY = (obj.y || 0) * scale;
|
||||
@@ -128,6 +161,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(({ page, scale = 1 },
|
||||
opacity: obj.opacity ?? 1,
|
||||
transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined,
|
||||
transformOrigin: 'center center',
|
||||
zIndex: index, // برای حفظ ترتیب رندر
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -204,9 +238,11 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(({ page, scale = 1 },
|
||||
border: obj.stroke
|
||||
? `${(obj.strokeWidth || 1) * scale}px solid ${obj.stroke}`
|
||||
: 'none',
|
||||
zIndex: index, // برای حفظ ترتیب رندر
|
||||
};
|
||||
|
||||
return <div key={obj.id || index} style={shapeStyle} />;
|
||||
}
|
||||
|
||||
case 'line': {
|
||||
// در EditorObject، width و height به عنوان مختصات انتهایی استفاده میشوند
|
||||
@@ -283,7 +319,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(({ page, scale = 1 },
|
||||
);
|
||||
}
|
||||
|
||||
case 'grid':
|
||||
case 'grid': {
|
||||
if (!obj.tableData) return null;
|
||||
|
||||
const { rows, cols, cells, cellWidth, cellHeight, stroke, strokeWidth } =
|
||||
@@ -325,6 +361,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(({ page, scale = 1 },
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
|
||||
@@ -44,7 +44,10 @@ export function transformViewerDataToPages(data: ViewerData): PageData[] {
|
||||
type: obj.type as EditorObject['type'],
|
||||
x: obj.x,
|
||||
y: obj.y,
|
||||
opacity: obj.opacity ?? 1,
|
||||
// در JSON، opacity به صورت 0-100 است (درصد)
|
||||
// در editor هم opacity به صورت 0-100 است
|
||||
// پس opacity را مستقیماً استفاده میکنیم
|
||||
opacity: obj.opacity !== undefined ? obj.opacity : 100,
|
||||
visible: true,
|
||||
};
|
||||
|
||||
@@ -66,11 +69,12 @@ export function transformViewerDataToPages(data: ViewerData): PageData[] {
|
||||
// برای circle در viewer-data.json، x, y به عنوان گوشه بالا-چپ bounding box است
|
||||
// اما در editor، circle با مرکز در x, y ذخیره میشود
|
||||
// پس باید تبدیل کنیم
|
||||
if (obj.type === 'rectangle' && obj.shapeType === 'circle') {
|
||||
// تبدیل از گوشه بالا-چپ به مرکز
|
||||
baseObject.x = obj.x + (obj.width || 100) / 2;
|
||||
baseObject.y = obj.y + (obj.height || 100) / 2;
|
||||
}
|
||||
// اما بر اساس بررسی، در JSON موقعیت به عنوان مرکز ذخیره شده است
|
||||
// پس نباید تبدیل کنیم - مستقیماً استفاده میکنیم
|
||||
// اگر در آینده JSON به صورت گوشه بالا-چپ export شود، باید تبدیل کنیم:
|
||||
// baseObject.x = obj.x + (obj.width || 100) / 2;
|
||||
// baseObject.y = obj.y + (obj.height || 100) / 2;
|
||||
// اما فعلاً JSON به صورت مرکز است، پس تبدیل نمیکنیم
|
||||
if (obj.fill !== undefined) baseObject.fill = obj.fill;
|
||||
if (obj.stroke !== undefined) baseObject.stroke = obj.stroke;
|
||||
if (obj.strokeWidth !== undefined) baseObject.strokeWidth = obj.strokeWidth;
|
||||
|
||||
Reference in New Issue
Block a user