Files
CRM_Montzar/apps/web/src/lib/form-cover.ts
T

73 lines
3.0 KiB
TypeScript

/** اندازهٔ استاندارد کاور فرم (نسبت ۳:۱) */
export const COVER_WIDTH = 1200;
export const COVER_HEIGHT = 400;
const MAX_INPUT_BYTES = 15_000_000;
/** تصاویر عریض‌تر از این نسبت بنر حساب می‌شوند و برش می‌خورند؛ بقیه لوگو هستند و کامل جا می‌گیرند */
const BANNER_MIN_RATIO = 2;
function loadImage(file: File): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const url = URL.createObjectURL(file);
const img = new Image();
img.onload = () => {
URL.revokeObjectURL(url);
resolve(img);
};
img.onerror = () => {
URL.revokeObjectURL(url);
reject(new Error('تصویر قابل خواندن نیست.'));
};
img.src = url;
});
}
function toBlob(canvas: HTMLCanvasElement, type: string, quality?: number): Promise<Blob> {
return new Promise((resolve, reject) => {
canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('پردازش تصویر ناموفق بود.'))), type, quality);
});
}
/**
* تصویر را به کاور ۱۲۰۰×۴۰۰ تبدیل می‌کند:
* بنرهای عریض از وسط برش می‌خورند (JPEG)،
* لوگوها و تصاویر مربعی/عمودی بدون برش وسط کادر با پس‌زمینهٔ شفاف قرار می‌گیرند (PNG) تا رنگ فرم پشتشان دیده شود.
*/
export async function prepareFormCover(file: File): Promise<File> {
if (!file.type.startsWith('image/')) throw new Error('فقط فایل تصویری (PNG، JPG، WEBP، SVG) مجاز است.');
if (file.size > MAX_INPUT_BYTES) throw new Error('حجم تصویر بیش از ۱۵ مگابایت است.');
const img = await loadImage(file);
const w = img.naturalWidth || img.width;
const h = img.naturalHeight || img.height;
if (!w || !h) throw new Error('ابعاد تصویر نامعتبر است.');
const canvas = document.createElement('canvas');
canvas.width = COVER_WIDTH;
canvas.height = COVER_HEIGHT;
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('مرورگر از پردازش تصویر پشتیبانی نمی‌کند.');
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
const ratio = w / h;
if (ratio >= BANNER_MIN_RATIO) {
const target = COVER_WIDTH / COVER_HEIGHT;
let sw = w;
let sh = h;
if (ratio > target) sw = h * target;
else sh = w / target;
ctx.drawImage(img, (w - sw) / 2, (h - sh) / 2, sw, sh, 0, 0, COVER_WIDTH, COVER_HEIGHT);
const blob = await toBlob(canvas, 'image/jpeg', 0.88);
return new File([blob], 'cover.jpg', { type: 'image/jpeg' });
}
const boxW = COVER_WIDTH * 0.6;
const boxH = COVER_HEIGHT * 0.7;
const scale = Math.min(boxW / w, boxH / h, 3);
const dw = w * scale;
const dh = h * scale;
ctx.drawImage(img, (COVER_WIDTH - dw) / 2, (COVER_HEIGHT - dh) / 2, dw, dh);
const blob = await toBlob(canvas, 'image/png');
return new File([blob], 'cover.png', { type: 'image/png' });
}