first commit
This commit is contained in:
103
src/utils/cropUtils.js
Normal file
103
src/utils/cropUtils.js
Normal file
@@ -0,0 +1,103 @@
|
||||
const loadImage = (file) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const image = new Image();
|
||||
image.onload = () => {
|
||||
URL.revokeObjectURL(image.src);
|
||||
resolve(image);
|
||||
};
|
||||
image.onerror = (error) => {
|
||||
URL.revokeObjectURL(image.src);
|
||||
reject(error);
|
||||
};
|
||||
image.src = URL.createObjectURL(file);
|
||||
});
|
||||
|
||||
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
||||
|
||||
const normalizeCropConfig = (config) => {
|
||||
if (!config?.imageWidth || !config?.imageHeight) {
|
||||
throw new Error('Geçerli bir crop referansı bulunamadı.');
|
||||
}
|
||||
const safeWidth = Math.max(
|
||||
1,
|
||||
config.width - (config.left ?? 0) - (config.right ?? 0),
|
||||
);
|
||||
const safeHeight = Math.max(
|
||||
1,
|
||||
config.height - (config.top ?? 0) - (config.bottom ?? 0),
|
||||
);
|
||||
const xStart = Math.max(0, config.cropAreaX + (config.left ?? 0));
|
||||
const yStart = Math.max(0, config.cropAreaY + (config.top ?? 0));
|
||||
return {
|
||||
xRatio: xStart / config.imageWidth,
|
||||
yRatio: yStart / config.imageHeight,
|
||||
widthRatio: safeWidth / config.imageWidth,
|
||||
heightRatio: safeHeight / config.imageHeight,
|
||||
};
|
||||
};
|
||||
|
||||
const cropImage = async (file, normalizedConfig) => {
|
||||
const image = await loadImage(file);
|
||||
const { width: imgWidth, height: imgHeight } = image;
|
||||
const cropWidth = clamp(
|
||||
Math.round(normalizedConfig.widthRatio * imgWidth),
|
||||
1,
|
||||
imgWidth,
|
||||
);
|
||||
const cropHeight = clamp(
|
||||
Math.round(normalizedConfig.heightRatio * imgHeight),
|
||||
1,
|
||||
imgHeight,
|
||||
);
|
||||
const startX = clamp(
|
||||
Math.round(normalizedConfig.xRatio * imgWidth),
|
||||
0,
|
||||
imgWidth - cropWidth,
|
||||
);
|
||||
const startY = clamp(
|
||||
Math.round(normalizedConfig.yRatio * imgHeight),
|
||||
0,
|
||||
imgHeight - cropHeight,
|
||||
);
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = cropWidth;
|
||||
canvas.height = cropHeight;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(image, startX, startY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
||||
|
||||
const blob = await new Promise((resolve, reject) => {
|
||||
canvas.toBlob((result) => {
|
||||
if (result) {
|
||||
resolve(result);
|
||||
} else {
|
||||
reject(new Error('Canvas blob oluşturulamadı.'));
|
||||
}
|
||||
}, 'image/png');
|
||||
});
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
return { blob, url };
|
||||
};
|
||||
|
||||
export const applyCropToImages = async (images, config) => {
|
||||
if (!images?.length) {
|
||||
throw new Error('Önce görsel yüklemelisin.');
|
||||
}
|
||||
if (!config || !config.imageWidth) {
|
||||
throw new Error('Crop ayarı bulunamadı.');
|
||||
}
|
||||
const normalized = normalizeCropConfig(config);
|
||||
const results = [];
|
||||
for (const image of images) {
|
||||
const { blob, url } = await cropImage(image.file, normalized);
|
||||
results.push({
|
||||
id: image.id,
|
||||
filename: image.file.name,
|
||||
blob,
|
||||
url,
|
||||
order: image.order,
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
46
src/utils/epubUtils.js
Normal file
46
src/utils/epubUtils.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const base64ToBlob = (base64, mimeType) => {
|
||||
const binary = atob(base64);
|
||||
const len = binary.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i += 1) {
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
}
|
||||
return new Blob([bytes], { type: mimeType });
|
||||
};
|
||||
|
||||
const API_BASE = import.meta.env.VITE_API_BASE_URL || 'http://localhost:4000';
|
||||
|
||||
export const createEpubFromOcr = async (text) => {
|
||||
if (!text?.trim()) {
|
||||
throw new Error('Önce OCR adımını tamamlamalısın.');
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE}/generate-epub`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
text,
|
||||
meta: {
|
||||
title: 'imgPub OCR Export',
|
||||
author: 'imgPub',
|
||||
filename: `imgpub${Date.now()}.epub`,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = await response.text();
|
||||
throw new Error(message || 'EPUB oluşturma isteği başarısız.');
|
||||
}
|
||||
|
||||
const payload = await response.json();
|
||||
const blob = base64ToBlob(payload.data, 'application/epub+zip');
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
return {
|
||||
filename: payload.filename,
|
||||
blob,
|
||||
url,
|
||||
generatedAt: Date.now(),
|
||||
};
|
||||
};
|
||||
9
src/utils/fileUtils.js
Normal file
9
src/utils/fileUtils.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const downloadBlob = (blobUrl, filename) => {
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = blobUrl;
|
||||
anchor.download = filename;
|
||||
anchor.style.display = 'none';
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
};
|
||||
6
src/utils/ocrUtils.js
Normal file
6
src/utils/ocrUtils.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export const correctTurkishCharacters = (text = '') =>
|
||||
text
|
||||
.replace(/İ/g, 'İ')
|
||||
.replace(/i̇/g, 'i')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
Reference in New Issue
Block a user