diff --git "a/indexproo.html" "b/indexproo.html" --- "a/indexproo.html" +++ "b/indexproo.html" @@ -12,6 +12,7 @@ @@ -1433,7 +1324,7 @@
-
+
شركة الريحان

المراجع الذكي

@@ -1441,131 +1332,6 @@ - -
-
-

- نبذة مختصرة -

- -
-
-
- تحديد الأرقام -
-

اكتشاف الأرقام المختلفة بين النص المصدر والهدف بجميع أشكالها (عربية، هندية، إنجليزية).

-
- -
-
- النصوص المفقودة -
-

تحديد النصوص الموجودة في المصدر والمفقودة في الترجمة، سواء كانت مفقودة كلياً أو جزئياً.

-
- -
-
- اختلافات المعنى -
-

اكتشاف التغييرات في المعنى بين النص الأصلي والترجمة التي تؤثر على المضمون.

-
-
- -
-

طرق العرض المتعددة:

-
-
- - العرض الكلاسيكي -
-
- - العرض المقسم -
-
- - العرض التفاعلي -
-
-
-
-
- - -
-
-

- أمثلة توضيحية -

- - -
-
-
- - مثال على اختلافات الأرقام -
- أرقام -
-
-
- في عام ٢٠٢٣، تم تنفيذ المشروع بتكلفة ٥٠٠٠٠٠ ريال، وشارك فيه ٢٥ موظفًا على مدار ١٢ شهرًا. وتم توفير ٣٠٪ من الميزانية المخصصة للمشروع. -
-
- In 2022, the project was implemented at a cost of 450,000 riyals, with 23 employees participating over 10 months. 35% of the project's allocated budget was saved. -
-
-
- -
-
- - -
-
-
- - مثال على النص المفقود -
- نص مفقود -
-
-
- يعتبر التعليم المستمر من أهم أدوات النجاح في عصرنا الحالي. ويشمل ذلك التعلم الذاتي والدورات التدريبية والقراءة المستمرة. كما أن التطبيق العملي للمعرفة المكتسبة يساعد على ترسيخها وتطويرها. -
-
- Continuous education is considered one of the most important tools for success in our current era. This includes self-learning and training courses. Practical application of acquired knowledge helps to consolidate and develop it. -
-
-
- -
-
- - -
-
-
- - مثال على اختلاف المعنى -
- اختلاف معنى -
-
-
- تتميز هذه السيارة بأنظمة أمان متطورة تشمل نظام منع انغلاق المكابح ونظام التحكم الإلكتروني بالثبات. وتعمل بمحرك اقتصادي يوفر في استهلاك الوقود بنسبة عالية. -
-
- This car features advanced entertainment systems including an anti-lock braking system and electronic stability control. It runs on a high-performance engine that provides exceptional speed and acceleration. -
-
-
- -
-
-
-
-
@@ -1573,7 +1339,7 @@

تحميل الملفات

- +
@@ -1583,7 +1349,7 @@ تاريخ آخر معالجة: -
- +
@@ -1594,13 +1360,8 @@ ملف السورس
-
- -
- +
@@ -1610,14 +1371,9 @@ ملف التارجت
-
- -
- + - + - + - + - +
@@ -1835,7 +1589,7 @@ العرض التفاعلي
- +
@@ -1854,27 +1608,35 @@
- - -
-
شرح الأخطاء
-
+ + +
+
+ ملخص التحليل +
+
+ +
- +
- - -
-
شرح الأخطاء في المقاطع
-
+ + +
+
+ شرح الأخطاء في المقاطع +
+
+ +
- +
@@ -1895,7 +1657,7 @@

اضغط "التالي" للبدء في استعراض الاختلافات

- + - + - +
@@ -1941,7 +1703,7 @@
- +
توصيات المعالجة @@ -1951,15 +1713,19 @@
- + -
-
التفاصيل والتوضيحات
-
+
+
+ التفاصيل والتوضيحات +
+
+ +
- +
@@ -1974,14 +1740,14 @@ - +
-
`; + pagePreview.innerHTML = ` +

الصفحة ${extractedPageNumbers[index]}

+
${text.substring(0, 100)}${text.length > 100 ? '...' : ''}
+ `; - // إضافة زر إغلاق للرسالة - const closeBtn = errorDiv.querySelector('button'); - closeBtn.addEventListener('click', () => { - errorDiv.style.opacity = '0'; - errorDiv.style.transform = 'translateY(-10px)'; - errorDiv.style.transition = 'opacity 0.3s, transform 0.3s'; - setTimeout(() => { - errorsList.removeChild(errorDiv); - }, 300); + resultPreview.appendChild(pagePreview); }); - - errorsList.appendChild(errorDiv); - - // تمرير تلقائي إلى أحدث رسالة - errorDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + + // عرض النص الكامل + document.getElementById('resultText').textContent = extractedTexts.join('\n\n'); } - + /* ===================================== - دالة محسنة للتمييز على مستوى الجملة + نسخ النص المستخرج ===================================== */ - function highlightSentenceErrors(text, errors) { - if (!errors || errors.length === 0) return text; + function copyText() { + const resultText = document.getElementById('resultText'); - // نسخ النص لتجنب تغيير النص الأصلي - let highlightedText = text; + // إنشاء عنصر textarea مؤقت + const textarea = document.createElement('textarea'); + textarea.value = resultText.textContent; + document.body.appendChild(textarea); - // فرز الأخطاء حسب طول النص المحدد (من الأطول للأقصر) لتجنب تداخل التحديدات - const sortedErrors = [...errors].sort((a, b) => - b.errorText.length - a.errorText.length - ); + // اختيار النص ونسخه + textarea.select(); + document.execCommand('copy'); - // تطبيق التحديد لكل خطأ - for (const error of sortedErrors) { - // تحديد نوع الخطأ وتطبيق التنسيق المناسب - let highlightClass = ''; - let icon = ''; - - if (error.type === 'number') { - highlightClass = 'highlight-number'; - icon = 'hashtag'; - } else if (error.type === 'missing') { - highlightClass = 'highlight-missing'; - icon = 'minus-circle'; - } else if (error.type === 'meaning') { - highlightClass = 'highlight-meaning'; - icon = 'exclamation-circle'; - } - - // تنفيذ الاستبدال مع الاحتفاظ بمعرف فريد للخطأ - const errorId = `error-${Math.random().toString(36).substring(2, 9)}`; - const explanation = encodeURIComponent(error.explanation || 'لا يوجد شرح متاح'); - const reference = encodeURIComponent(error.reference || 'لا يوجد مرجع متاح'); - - // إضافة أيقونة صغيرة داخل التمييز - const replacement = `${error.errorText}`; - - // البحث عن الجملة الكاملة وتحديدها - const sentenceIndex = highlightedText.indexOf(error.sentence); - if (sentenceIndex !== -1) { - // تحديد الجملة بكاملها بفئة خاصة - const beforeSentence = highlightedText.substring(0, sentenceIndex); - const afterSentence = highlightedText.substring(sentenceIndex + error.sentence.length); - - // استبدال النص المحدد داخل الجملة - let updatedSentence = error.sentence; - const errorTextIndex = updatedSentence.indexOf(error.errorText); - if (errorTextIndex !== -1) { - updatedSentence = - updatedSentence.substring(0, errorTextIndex) + - replacement + - updatedSentence.substring(errorTextIndex + error.errorText.length); - } - - // وضع الجملة المحدثة في سياقها مع تمييز الجملة بأكملها - highlightedText = - beforeSentence + - `${updatedSentence}` + - afterSentence; - } else { - // إذا تعذر العثور على الجملة الكاملة، قم بتحديد النص المحدد فقط - highlightedText = highlightedText.replace( - new RegExp(escapeRegExp(error.errorText), 'g'), - replacement - ); - } - } + // إزالة العنصر المؤقت + document.body.removeChild(textarea); - return highlightedText; + alert('تم نسخ النص'); } - + /* ===================================== - تمييز النص المفقود بالكامل والمفقود جزئيا + تنزيل النص المستخرج ===================================== */ - function highlightMissingText(sourceText, targetText) { - // تقسيم النصوص إلى فقرات - const sourceParagraphs = sourceText.split(/\n\s*\n/).filter(p => p.trim()); - const targetParagraphs = targetText.split(/\n\s*\n/).filter(p => p.trim()); - - // مصفوفة لتخزين النص المعدل - let modifiedSource = sourceText; - - // البحث عن الفقرات المفقودة تماما في الهدف - for (const paragraph of sourceParagraphs) { - if (paragraph.trim().length < 10) continue; // تجاهل الفقرات القصيرة جدا - - // فحص ما إذا كانت الفقرة مفقودة تماما - let isCompletelyMissing = true; - - // البحث عن أجزاء من الفقرة في النص الهدف - for (const targetPara of targetParagraphs) { - if (targetPara.includes(paragraph) || paragraph.includes(targetPara)) { - isCompletelyMissing = false; - break; - } - } - - // إذا كانت مفقودة تماما، نميزها بشكل مختلف - if (isCompletelyMissing) { - modifiedSource = modifiedSource.replace( - paragraph, - `${paragraph}` - ); - } - } - - // البحث عن الجمل المفقودة جزئيا - const sourceLines = sourceParagraphs.join('\n').split(/[.!?]\s+/); - - for (const line of sourceLines) { - if (line.trim().length < 5) continue; // تجاهل الجمل القصيرة جدا - - // البحث عن جمل موجودة في السورس ومفقودة في التارجت - let isPartiallyMissing = true; - - if (targetText.includes(line)) { - isPartiallyMissing = false; - } else { - // البحث عن كلمات رئيسية من الجملة في التارجت - const keywords = line.split(/\s+/).filter(word => word.length > 3); - let foundKeywords = 0; - - for (const keyword of keywords) { - if (targetText.includes(keyword)) { - foundKeywords++; - } - } - - // إذا وجدنا أكثر من 50% من الكلمات الرئيسية، فهي مفقودة جزئيا - if (keywords.length > 0 && foundKeywords / keywords.length > 0.5) { - isPartiallyMissing = false; - } - } - - // تمييز الجمل المفقودة جزئيا فقط إذا لم تكن مميزة بالفعل كمفقودة تماما - if (isPartiallyMissing && !modifiedSource.includes(`${line}` - ); - } - } - - return modifiedSource; + function downloadText() { + const text = document.getElementById('resultText').textContent; + const blob = new Blob([text], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'extracted_text.txt'; + a.click(); + + URL.revokeObjectURL(url); } - + /* ===================================== - دوال مساعدة للتعامل مع النصوص + استخدام النص المستخرج ===================================== */ - // حساب عدد الكلمات في النص - function countWords(text) { - return text.trim().split(/\s+/).filter(word => word !== "").length; - } + function useOcrText(target) { + const text = document.getElementById('resultText').textContent; - // هروب أحرف Regex الخاصة - function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - } + if (target === 'source') { + document.getElementById('sourceText').value = text; + } else { + document.getElementById('targetText').value = text; + } - // تقسيم النص إلى أسطر مع عرض رقم السطر - function splitIntoLines(text) { - return text.split('\n').map((line, i) => { - // تحسين شكل عرض الأسطر - return ` -
- ${i+1} - ${line || ' '} -
`; - }).join(''); + alert(`تم استخدام النص المستخرج كنص ${target === 'source' ? 'مصدر' : 'هدف'}`); } + + /* ===================================== + وظائف محرر الصور + ===================================== */ + function initializeImageEditor(index) { + currentPageIndex = index; - // الحصول على رقم السطر لظهور عبارة معينة - function getLineNumber(text, substring) { - const index = text.indexOf(substring); - if (index === -1) return "غير محدد"; - return text.substring(0, index).split("\n").length; - } + const imageCanvas = document.getElementById('imageCanvas'); + const imageEditor = document.getElementById('imageEditor'); - // اقتطاع النص إذا كان طويلا جدا - function truncateText(text, maxLength) { - if (text.length <= maxLength) return text; - return text.substring(0, maxLength) + '...'; - } + imageEditor.classList.remove('hidden'); - // تنسيق نص التحليل بتمييز الكلمات المهمة - function formatAnalysisText(text) { - // تمييز الكلمات المهمة مثل "الأرقام" و "المفقودة" و "المعنى" - text = text.replace(/الأرقام/g, 'الأرقام'); - text = text.replace(/المفقودة/g, 'المفقودة'); - text = text.replace(/المعنى/g, 'المعنى'); + // حفظ البيانات الأصلية للصورة + originalImageData = documentPages[index].imageData; + + // إنشاء كائن Fabric canvas + if (fabricCanvas) { + fabricCanvas.dispose(); + } + + fabricCanvas = new fabric.Canvas('imageCanvas'); + + // تحميل الصورة + fabric.Image.fromURL(originalImageData, function(img) { + // تحجيم الصورة لتناسب الكانفاس + const containerWidth = imageCanvas.parentElement.clientWidth; + const scale = containerWidth / img.width; + + img.scale(scale); + + fabricCanvas.setWidth(img.width * scale); + fabricCanvas.setHeight(img.height * scale); + fabricCanvas.setBackgroundImage(img, fabricCanvas.renderAll.bind(fabricCanvas)); + }); + } - // تحديد علامات التمييز - text = text.replace(/<([^<>]+)>/g, '<$1>'); - text = text.replace(/__(.*?)__/g, '__$1__'); - text = text.replace(/\[MEANING\](.*?)\[\/MEANING\]/g, '$1'); + function rotateImageLeft() { + if (!fabricCanvas) return; + + const img = fabricCanvas.backgroundImage; + img.rotate((img.angle || 0) - 90); + fabricCanvas.renderAll(); + + updateModifiedImage(); + } - return text; + function rotateImageRight() { + if (!fabricCanvas) return; + + const img = fabricCanvas.backgroundImage; + img.rotate((img.angle || 0) + 90); + fabricCanvas.renderAll(); + + updateModifiedImage(); } + + function flipImageHorizontal() { + if (!fabricCanvas) return; + + const img = fabricCanvas.backgroundImage; + img.set('flipX', !img.flipX); + fabricCanvas.renderAll(); - // تمييز الكلمة المتزامنة في النص - function highlightSyncWord(text, syncWord) { - if (!syncWord || !text.includes(syncWord)) return text; + updateModifiedImage(); + } - // تجنب تمييز الكلمة إذا كانت جزءًا من كلمة أكبر - const regex = new RegExp(`(\\b${escapeRegExp(syncWord)}\\b)`, 'g'); - // جعل الظهور الأخير للكلمة ممَيز - const lastIndex = text.lastIndexOf(syncWord); - if (lastIndex !== -1) { - const beforeSync = text.substring(0, lastIndex); - const afterSync = text.substring(lastIndex + syncWord.length); - return beforeSync + '' + syncWord + '' + afterSync; - } - - return text; - } - - /* ===================================== - دوال التمييز (Highlighting) للنتائج - دالة محسنة تدمج آلية الكشف عن النص المفقود - ===================================== */ - function applyHighlights(originalText, targetText, analysisOutput) { - // تطبيق تمييز للنص المفقود تماما والمفقود جزئيا - const enhancedSourceText = highlightMissingText(originalText, targetText); - - // استخراج النصوص التي بها مشاكل من التحليل - const numberMatches = Array.from(analysisOutput.matchAll(/<([^<>]+)>/g)).map(m => m[1].trim()); - const missingMatches = Array.from(analysisOutput.matchAll(/__(.*?)__/g)).map(m => m[1].trim()); - const meaningMatches = Array.from(analysisOutput.matchAll(/\[MEANING\](.*?)\[\/MEANING\]/g)).map(m => m[1].trim()); - - // تقسيم النص الأصلي إلى جمل باستخدام التعبير المنتظم - const sentences = splitIntoSentences(enhancedSourceText); - - // معالجة كل جملة على حدة وتطبيق التحديد إذا وُجدت مشكلة - const highlightedSentences = sentences.map(sentence => { - let hasError = false; - let highlightedSentence = sentence; - - // تحديد اختلافات الأرقام مع مراعاة أشكال الأرقام المختلفة - numberMatches.forEach(phrase => { - if (sentence.includes(phrase)) { - // استخدام المقارنة بعد توحيد الأرقام - const normalizedPhrase = normalizeNumbers(phrase); - const normalizedSentence = normalizeNumbers(sentence); - // تجنب تحديد النص إذا كان الاختلاف فقط في شكل الأرقام - if (normalizedSentence.includes(normalizedPhrase)) { - // لا تعامله كاختلاف جوهري - } else { - const regex = new RegExp(escapeRegExp(phrase), 'g'); - highlightedSentence = highlightedSentence.replace(regex, `${phrase}`); - hasError = true; - } - } - }); - - // تحديد النصوص المفقودة - missingMatches.forEach(phrase => { - if (sentence.includes(phrase)) { - const regex = new RegExp(escapeRegExp(phrase), 'g'); - highlightedSentence = highlightedSentence.replace(regex, `${phrase}`); - hasError = true; - } - }); - - // تحديد اختلافات المعنى - meaningMatches.forEach(phrase => { - if (sentence.includes(phrase)) { - const regex = new RegExp(escapeRegExp(phrase), 'g'); - highlightedSentence = highlightedSentence.replace(regex, `${phrase}`); - hasError = true; - } - }); - - // إذا كان هناك خطأ، قم بتمييز الجملة كاملة - if (hasError) { - return `${highlightedSentence}`; - } - - return highlightedSentence; - }); - - return highlightedSentences.join(' '); - } - - /* ===================================== - تطبيق فلتر الأخطاء على جميع طرق العرض - ===================================== */ - function applyErrorFilter(filterType) { - // تطبيق الفلتر على العرض الكلاسيكي - applyClassicViewFilter(filterType); + function flipImageVertical() { + if (!fabricCanvas) return; - // تطبيق الفلتر على العرض المقسم - applySegmentedViewFilter(filterType); + const img = fabricCanvas.backgroundImage; + img.set('flipY', !img.flipY); + fabricCanvas.renderAll(); - // تطبيق الفلتر على العرض التفاعلي - applyInteractiveViewFilter(filterType); + updateModifiedImage(); } - // تطبيق الفلتر على العرض الكلاسيكي - function applyClassicViewFilter(filterType) { - // إذا كان الفلتر "all"، نعرض كل الأخطاء - if (filterType === 'all') { - document.querySelectorAll('#sourceTextReview .highlight-number, #sourceTextReview .highlight-missing, #sourceTextReview .highlight-meaning, #targetTextReview .highlight-number, #targetTextReview .highlight-missing, #targetTextReview .highlight-meaning, #sourceTextReview .completely-missing, #sourceTextReview .partially-missing').forEach(el => { - el.style.display = ''; - }); - // إعادة تفعيل جميع الجمل - document.querySelectorAll('.sentence-with-error').forEach(el => { - el.classList.remove('opacity-50'); - }); + function activateCropMode() { + if (!fabricCanvas) return; + + if (isInCropMode) { return; } - // إخفاء كل الأخطاء أولاً - document.querySelectorAll('#sourceTextReview .highlight-number, #sourceTextReview .highlight-missing, #sourceTextReview .highlight-meaning, #targetTextReview .highlight-number, #targetTextReview .highlight-missing, #targetTextReview .highlight-meaning, #sourceTextReview .completely-missing, #sourceTextReview .partially-missing').forEach(el => { - el.style.display = 'none'; - }); + isInCropMode = true; + document.getElementById('cropImage').textContent = 'تطبيق القص'; - // إضفاء شفافية على جميع الجمل - document.querySelectorAll('.sentence-with-error').forEach(el => { - el.classList.add('opacity-50'); + // إنشاء مربع للقص + cropRect = new fabric.Rect({ + left: 50, + top: 50, + width: fabricCanvas.width - 100, + height: fabricCanvas.height - 100, + fill: 'rgba(0,0,0,0.2)', + stroke: 'black', + strokeDashArray: [5, 5], + strokeWidth: 2, + selectable: true }); - // إظهار الأخطاء المطلوبة فقط - let selector = ''; - if (filterType === 'number') { - selector = '.highlight-number'; - } else if (filterType === 'missing') { - selector = '.highlight-missing, .completely-missing, .partially-missing'; - } else if (filterType === 'meaning') { - selector = '.highlight-meaning'; - } - - document.querySelectorAll(`#sourceTextReview ${selector}, #targetTextReview ${selector}`).forEach(el => { - el.style.display = ''; - // إزالة الشفافية عن الجمل التي تحتوي على هذا النوع من الأخطاء - const errorId = el.getAttribute('data-error-id'); - if (errorId) { - document.querySelectorAll(`.sentence-with-error[data-error-id="${errorId}"]`).forEach(sentence => { - sentence.classList.remove('opacity-50'); - }); - } - }); + fabricCanvas.add(cropRect); + fabricCanvas.setActiveObject(cropRect); } - // تطبيق الفلتر على العرض المقسم - function applySegmentedViewFilter(filterType) { - // استرجاع جميع المقاطع - const segments = document.querySelectorAll('.segment-comparison'); + function applyCrop() { + if (!fabricCanvas || !cropRect) return; - if (filterType === 'all') { - // إظهار جميع المقاطع - segments.forEach(segment => { - segment.style.display = ''; - // إظهار جميع الأخطاء - segment.querySelectorAll('.highlight-number, .highlight-missing, .highlight-meaning, .completely-missing, .partially-missing').forEach(el => { - el.style.display = ''; - }); - // إعادة تفعيل جميع الجمل - segment.querySelectorAll('.sentence-with-error').forEach(el => { - el.classList.remove('opacity-50'); + isInCropMode = false; + document.getElementById('cropImage').textContent = 'قص الصورة'; + + // الحصول على معلومات مستطيل القص + const rect = cropRect; + const img = fabricCanvas.backgroundImage; + + // إزالة مستطيل القص + fabricCanvas.remove(rect); + + // إنشاء كانفاس جديد للقص + const cropCanvas = document.createElement('canvas'); + const cropContext = cropCanvas.getContext('2d'); + + cropCanvas.width = rect.getScaledWidth(); + cropCanvas.height = rect.getScaledHeight(); + + // إعداد المصدر والوجهة للقص + const sourceLeft = rect.left; + const sourceTop = rect.top; + const sourceWidth = rect.getScaledWidth(); + const sourceHeight = rect.getScaledHeight(); + + // إنشاء صورة جديدة من الصورة الأصلية + const tempImage = new Image(); + tempImage.onload = function() { + // رسم الجزء المقصوص من الصورة + cropContext.drawImage( + tempImage, + sourceLeft / img.scaleX, + sourceTop / img.scaleY, + sourceWidth / img.scaleX, + sourceHeight / img.scaleY, + 0, 0, + cropCanvas.width, + cropCanvas.height + ); + + // تحديث صورة الخلفية + fabric.Image.fromURL(cropCanvas.toDataURL(), function(newImg) { + fabricCanvas.setBackgroundImage(newImg, fabricCanvas.renderAll.bind(fabricCanvas)); + fabricCanvas.setDimensions({ + width: cropCanvas.width, + height: cropCanvas.height }); + + updateModifiedImage(); }); - return; - } + }; + tempImage.src = img._element.src; + } + + function resetImage() { + if (!fabricCanvas) return; - // إخفاء كل الأخطاء أولاً في جميع المقاطع - segments.forEach(segment => { - segment.querySelectorAll('.highlight-number, .highlight-missing, .highlight-meaning, .completely-missing, .partially-missing').forEach(el => { - el.style.display = 'none'; - }); + // إعادة الصورة إلى حالتها الأصلية + fabric.Image.fromURL(originalImageData, function(img) { + const containerWidth = document.getElementById('imageCanvas').parentElement.clientWidth; + const scale = containerWidth / img.width; - // إضفاء شفافية على جميع الجمل - segment.querySelectorAll('.sentence-with-error').forEach(el => { - el.classList.add('opacity-50'); - }); + img.scale(scale); - // تحديد السيليكتور حسب نوع الفلتر - let selector = ''; - if (filterType === 'number') { - selector = '.highlight-number'; - } else if (filterType === 'missing') { - selector = '.highlight-missing, .completely-missing, .partially-missing'; - } else if (filterType === 'meaning') { - selector = '.highlight-meaning'; - } - - // إظهار الأخطاء المطلوبة فقط - const highlightsToShow = segment.querySelectorAll(selector); - highlightsToShow.forEach(el => { - el.style.display = ''; - // إزالة الشفافية عن الجمل التي تحتوي على هذا النوع من الأخطاء - const errorId = el.getAttribute('data-error-id'); - if (errorId) { - segment.querySelectorAll(`.sentence-with-error[data-error-id="${errorId}"]`).forEach(sentence => { - sentence.classList.remove('opacity-50'); - }); - } - }); + fabricCanvas.setWidth(img.width * scale); + fabricCanvas.setHeight(img.height * scale); + fabricCanvas.setBackgroundImage(img, fabricCanvas.renderAll.bind(fabricCanvas)); - // إذا لم يكن هناك أخطاء من النوع المطلوب في هذا المقطع، نخفيه - if (highlightsToShow.length === 0) { - segment.style.display = 'none'; - } else { - segment.style.display = ''; - } + // إزالة أي عناصر إضافية + fabricCanvas.clear(); + + // إعادة تعيين حالة القص + isInCropMode = false; + cropRect = null; + document.getElementById('cropImage').textContent = 'قص الصورة'; + + // تحديث الصورة المعدلة + updateModifiedImage(); }); } - // تطبيق الفلتر على العرض التفاعلي - function applyInteractiveViewFilter(filterType) { - // فلترة الفروقات - if (filterType === 'all') { - // إعادة جميع الفروقات - currentDiffIndex = 0; - displayCurrentDifference(); - return; - } + function improveContrast() { + if (!fabricCanvas) return; - // فلترة الفروقات حسب النوع - const filteredDiffs = allDifferences.filter(diff => diff.type === filterType); + const img = fabricCanvas.backgroundImage; - // إذا لم نجد فروقات، نعرض رسالة - if (filteredDiffs.length === 0) { - document.getElementById('currentDiffDisplay').innerHTML = ` -
-
- -
-

لا توجد اختلافات من نوع ${filterType === 'number' ? 'الأرقام' : filterType === 'missing' ? 'النصوص المفقودة' : 'المعنى'}

-
`; - document.getElementById('diffCounter').textContent = "0/0"; - - // إخفاء عرض المصدر والهدف - document.getElementById('diffDetailedView').classList.add('hidden'); - document.getElementById('diffReference').classList.add('hidden'); - - // تعطيل أزرار التنقل - document.getElementById('prevDiff').disabled = true; - document.getElementById('nextDiff').disabled = true; - } else { - // تعيين المؤشر للفرق الأول من النوع المطلوب - currentDiffIndex = allDifferences.findIndex(diff => diff.type === filterType); - - // عرض الفرق الحالي - displayCurrentDifference(); + // إنشاء كانفاس مؤقت + const tempCanvas = document.createElement('canvas'); + const tempContext = tempCanvas.getContext('2d'); + + tempCanvas.width = img.width * img.scaleX; + tempCanvas.height = img.height * img.scaleY; + + // رسم الصورة على الكانفاس المؤقت + tempContext.drawImage(img._element, 0, 0, tempCanvas.width, tempCanvas.height); + + // الحصول على بيانات الصورة + const imageData = tempContext.getImageData(0, 0, tempCanvas.width, tempCanvas.height); + const data = imageData.data; + + // تحسين التباين + const factor = 1.5; // عامل التباين + + for (let i = 0; i < data.length; i += 4) { + // الحصول على القيم RGB + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; - // تفعيل أزرار التنقل - document.getElementById('prevDiff').disabled = false; - document.getElementById('nextDiff').disabled = false; + // حساب القيمة الجديدة + data[i] = Math.min(255, Math.max(0, factor * (r - 128) + 128)); + data[i + 1] = Math.min(255, Math.max(0, factor * (g - 128) + 128)); + data[i + 2] = Math.min(255, Math.max(0, factor * (b - 128) + 128)); } + + // وضع البيانات المعدلة في الكانفاس + tempContext.putImageData(imageData, 0, 0); + + // تحديث صورة الخلفية + fabric.Image.fromURL(tempCanvas.toDataURL(), function(newImg) { + fabricCanvas.setBackgroundImage(newImg, fabricCanvas.renderAll.bind(fabricCanvas)); + + updateModifiedImage(); + }); } - + + function updateModifiedImage() { + // تحديث الصورة المعدلة في مصفوفة الصفحات + if (fabricCanvas && currentPageIndex !== -1) { + documentPages[currentPageIndex].imageData = fabricCanvas.toDataURL(); + } + } + /* ===================================== - دالة تقسيم النصوص باستخدام DeepSeek + رندر أزرار اختيار أوراق Excel ===================================== */ - async function alignTextsWithModel(sourceText, targetText) { - try { - // إظهار حالة التقسيم - document.getElementById('processingStatus').classList.remove('hidden'); - document.getElementById('statusText').textContent = 'جاري تقسيم ومزامنة النصوص ...'; - document.getElementById('progressBar').style.width = '30%'; - - // استدعاء نموذج DeepSeek للتقسيم والمزامنة - const prompt = `أنت خبير في تقسيم النصوص ومزامنتها. مهمتك تجزئة النصين التاليين (المصدر والهدف) إلى فقرات متوازية - بحيث تتزامن كل فقرة في المصدر مع الفقرة المقابلة لها في الهدف من حيث المعنى والمحتوى. - - يجب أن تتبع القواعد التالية: - 1. حتى لو كان هناك نص مفقود في أحد النصين، ابحث عن أقرب مقابل له - 2. قسّم النصوص إلى أجزاء متزامنة لا تزيد عن 500 كلمة لكل جزء - 3. حاول مطابقة العناوين والأرقام والمصطلحات المشتركة بين النصين - - ��رجع النتائج بصيغة JSON: - { - "segments": [ - { - "source": "نص المصدر للفقرة الأولى", - "target": "نص الهدف المقابل للفقرة الأولى" - }, - { - "source": "نص المصدر للفقرة الثانية", - "target": "نص الهدف المقابل للفقرة الثانية" - }, - ... - ] - } - - النص المصدر: - ${sourceText} - - النص الهدف: - ${targetText}`; - - const payload = { - model: "deepseek-chat", - messages: [ - { role: "system", content: "أنت خبير في تقسيم النصوص ومزامنتها للمقارنة والتحليل." }, - { role: "user", content: prompt } - ], - temperature: 0.3, - max_tokens: 8000 + function renderSheetSelector(sheetNames, targetType) { + const container = document.getElementById('sheetSelectorContainer'); + container.innerHTML = ''; + + sheetNames.forEach(sheetName => { + const btn = document.createElement('div'); + btn.className = 'sheet-selector'; + btn.textContent = sheetName; + btn.onclick = function() { + document.querySelectorAll('.sheet-selector').forEach(b => b.classList.remove('active')); + this.classList.add('active'); + selectExcelSheet(sheetName, targetType); }; + container.appendChild(btn); + }); + + // تفعيل الورقة الأولى + const firstSheet = container.querySelector('.sheet-selector'); + if (firstSheet) { + firstSheet.classList.add('active'); + } + + // إظهار قسم معاينة Excel + document.getElementById('excelPreviewCard').classList.remove('hidden'); + } - document.getElementById('progressBar').style.width = '50%'; - - const response = await fetch(DEEPSEEK_API_URL, { - method: 'POST', - headers: { - 'Authorization': 'Bearer ' + DEEPSEEK_API_KEY, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(payload) - }); - - if (!response.ok) { - throw new Error('فشل استدعاء نموذج لتقسيم النصوص: ' + response.statusText); - } + /* ===================================== + اختيار ورقة Excel + ===================================== */ + function selectExcelSheet(sheetName, targetType) { + currentSheetName = sheetName; + + // تحويل الورقة إلى مصفوفة + const worksheet = excelWorkbook.Sheets[sheetName]; + const data = XLSX.utils.sheet_to_json(worksheet, {header: 1}); + + // تخزين البيانات للاستخدام لاحقًا + excelData = { + targetType: targetType, + sheetName: sheetName, + data: data + }; + + // عرض البيانات في جدول + renderExcelTable(data); + } - document.getElementById('progressBar').style.width = '80%'; - - const data = await response.json(); - const result = data.choices[0].message.content.trim(); - - // استخراج JSON من النتيجة - let segments = []; - try { - const jsonMatch = result.match(/\{[\s\S]*\}/); - if (jsonMatch) { - const parsedData = JSON.parse(jsonMatch[0]); - segments = parsedData.segments || []; - } - } catch (e) { - console.error('خطأ في تحليل نتيجة تقسيم النصوص:', e); - // استخدام طريقة بديلة إذا فشل التحليل - segments = createFallbackSegments(sourceText, targetText); + /* ===================================== + رندر جدول Excel + ===================================== */ + function renderExcelTable(data) { + const container = document.getElementById('excelContent'); + + if (!data || data.length === 0) { + container.innerHTML = '

لا توجد بيانات في هذه الورقة

'; + return; + } + + let html = ''; + + // إنشاء الترويسة باستخدام الصف الأول أو محتوى عمود + const headerRow = data[0]; + + for (let i = 0; i < headerRow.length; i++) { + html += ``; + } + + html += ''; + + // إضافة صفوف البيانات + for (let i = 1; i < data.length; i++) { + html += ''; + const row = data[i]; + + for (let j = 0; j < headerRow.length; j++) { + html += ``; } - - document.getElementById('progressBar').style.width = '100%'; - document.getElementById('statusText').textContent = 'تم تقسيم النصوص بنجاح!'; - - // إخفاء حالة المعالجة بعد فترة قصيرة - setTimeout(() => { - document.getElementById('processingStatus').classList.add('hidden'); - }, 1000); - - return segments; - } catch (error) { - console.error('خطأ في تقسيم النصوص:', error); - // إخفاء حالة المعالجة وإظهار رسالة الخطأ - document.getElementById('processingStatus').classList.add('hidden'); - addError('حدث خطأ أثناء تقسيم النصوص: ' + error.message); - - // إعادة تقسيم بسيط كحل احتياطي - return createFallbackSegments(sourceText, targetText); + + html += ''; } + + html += '
${headerRow[i] || 'عمود ' + (i+1)}
${row[j] !== undefined ? row[j] : ''}
'; + container.innerHTML = html; } - // إنشاء تقسيم بسيط كحل احتياطي مع تحسينات - function createFallbackSegments(sourceText, targetText) { - addError('تم الانتقال إلى آلية التقسيم الاحتياطية نظراً لحدوث خطأ في التقسيم المتقدم', 'warning'); - - // تقسيم النصوص إلى فقرات - const sourceParagraphs = sourceText.split(/\n\s*\n/).filter(p => p.trim()); - const targetParagraphs = targetText.split(/\n\s*\n/).filter(p => p.trim()); - - const segments = []; - const maxLength = 500; // الحد الأقصى للكلمات في كل قسم - - // تحسين: محاولة مطابقة الفقرات بناءً على المحتوى المشترك - let sourceIndex = 0; - let targetIndex = 0; - - while (sourceIndex < sourceParagraphs.length && targetIndex < targetParagraphs.length) { - let currentSource = ''; - let currentTarget = ''; - let currentWordCount = 0; - - const initialSourceIndex = sourceIndex; - const initialTargetIndex = targetIndex; - - // إضافة فقرات حتى نصل إلى الحد الأقصى - while (sourceIndex < sourceParagraphs.length && - targetIndex < targetParagraphs.length && - currentWordCount < maxLength) { - - const sourcePara = sourceParagraphs[sourceIndex]; - const targetPara = targetParagraphs[targetIndex]; - - const sourceWords = sourcePara.split(/\s+/).length; - const targetWords = targetPara.split(/\s+/).length; - - // إذا كانت إضافة هذه الفقرة ستتجاوز الحد الأقصى، نتوقف - if (currentWordCount + Math.max(sourceWords, targetWords) > maxLength && currentWordCount > 0) { - break; - } - - // إضافة الفقرات الحالية - currentSource += (currentSource ? '\n\n' : '') + sourcePara; - currentTarget += (currentTarget ? '\n\n' : '') + targetPara; - currentWordCount += Math.max(sourceWords, targetWords); - - sourceIndex++; - targetIndex++; - } - - // إذا لم نتمكن من إضافة أي فقرات، نتوقف لتجنب حلقة لانهائية - if (sourceIndex === initialSourceIndex && targetIndex === initialTargetIndex) { - break; - } - - // إضافة القسم الحالي - if (currentSource || currentTarget) { - segments.push({ source: currentSource, target: currentTarget }); - } + /* ===================================== + استخدام محتوى Excel كنص + ===================================== */ + function useExcelContent(target) { + if (!excelData || !excelData.data) { + alert('لم يتم تحميل بيانات Excel'); + return; } - - // إضافة الفقرات المتبقية في المصدر - if (sourceIndex < sourceParagraphs.length) { - const remainingSource = sourceParagraphs.slice(sourceIndex).join('\n\n'); - segments.push({ source: remainingSource, target: 'نص مفقود في الترجمة' }); + + // تحويل البيانات إلى نص + let textContent = ''; + + // تخطي صف الترويسة وبدء من الصف 1 + for (let i = 1; i < excelData.data.length; i++) { + const row = excelData.data[i]; + if (row && row.length > 0) { + textContent += row.join('\t') + '\n'; + } } - - // إضافة الفقرات المتبقية في الهدف - if (targetIndex < targetParagraphs.length) { - const remainingTarget = targetParagraphs.slice(targetIndex).join('\n\n'); - segments.push({ source: 'نص إضافي في الترجمة', target: remainingTarget }); + + // تحديث النص المصدر أو الهدف + if (target === 'source') { + document.getElementById('sourceText').value = textContent; + } else if (target === 'target') { + document.getElementById('targetText').value = textContent; + } else if (target === 'extra') { + document.getElementById('sourceExtraText').value = textContent; } - - return segments; + + alert('تم استخدام محتوى Excel بنجاح'); } /* ===================================== - دالة تحليل قسم واحد باستخدام DeepSeek Chat + دالة توحيد الأرقام بمختلف أشكالها ===================================== */ - async function analyzeAlignedPair(sourceText, targetText, pairNumber) { - try { - // إظهار حالة التحليل - document.getElementById('statusText').textContent = `جاري تحليل القسم ${pairNumber}...`; - - // برومبت محسن للتحليل - التركيز على اكتشاف النص المفقود تماما أو جزئيا - const prompt = `قارن النص المصدر والنص الهدف التاليين، وحدد بدقة: - 1. اختلافات الأرقام: ضع الرقم المختلف بين علامتي < > - 2. النصوص المفقودة: ضع النص المفقود في الترجمة بين علامتي __ __ - • تأكد من تحديد النص المفقود تماما بشكل منفصل - • إذا كان جزء من النص مفقود، حدده بدقة - 3. اختلافات المعنى: ضع النص الذي تغير معناه بين علامتي [MEANING] [/MEANING] + function normalizeNumbers(text) { + // تحويل الأرقام العربية والهندية إلى أرقام إنجليزية + return text.replace(/[٠١٢٣٤٥٦٧٨٩]/g, d => d.charCodeAt(0) - 1632) // أرقام عربية + .replace(/[۰۱۲۳۴۵۶۷۸۹]/g, d => d.charCodeAt(0) - 1776); // أرقام هندية + } - اعتبر الأرقام بمختلف أنظمتها (العربية والهندية والإنجليزية) متطابقة إذا كانت تمثل نفس الرقم. - قم بتحليل كل جملة على حدة وتحديد الاختلافات بدقة. - - لكل اختلاف، قدم شرحاً موجزاً للخطأ والتصحيح المقترح. - انتبه بشكل خاص للنصوص المفقودة تماما في الترجمة وحددها. + /* ===================================== + تقسيم النص إلى جمل - محسن + ===================================== */ + function splitIntoSentences(text) { + // تعبير منتظم محسن للتعامل مع نهايات الجمل بشكل أفضل + const sentences = text.split(/(?<=[.!?])\s+|(?<=\n\s*\n)/g); + + // تنظيف النتائج من الجمل الفارغة + return sentences.filter(s => s.trim()); + } - النص المصدر: - ${sourceText} + /* ===================================== + دالة معالجة عملية التحليل عند الضغط على الزر + ===================================== */ + document.getElementById('submitBtn').addEventListener('click', async () => { + const sourceText = document.getElementById('sourceText').value; + const targetText = document.getElementById('targetText').value; + + // مسح الرسائل السابقة وإظهار النتائج + document.getElementById('errorsList').innerHTML = ''; + document.getElementById('resultSection').classList.remove('hidden'); + + if (!sourceText || !targetText) { + addError('يرجى إدخال كلا النصين المصدر والهدف'); + return; + } + + try { + addError('جارٍ تقسيم النصوص ومزامنتها...', 'info'); + document.getElementById('processingStatus').classList.remove('hidden'); + + // تقسيم النصوص إلى أجزاء متزامنة بحد أقصى 6 أقسام + const segments = await alignTextsWithModel(sourceText, targetText); + + if (!segments || segments.length === 0) { + throw new Error('فشل في تقسيم النصوص - لم يتم العثور على أقسام'); + } + + // عرض الأقسام في المسودة مع التمييز المبدئي + displayDraftSegments(segments); + + // تخزين الأقسام في المتغير العام + analysisSegments = segments.map(segment => ({ + source: segment.source, + target: segment.target, + analysis: 'جارٍ التحليل...', + errors: { numbers: 0, missing: 0, meaning: 0 } + })); + + addError(`تم تقسيم النصوص إلى ${segments.length} قسم بنجاح`, 'info'); + addError('جارٍ تحليل الأقسام...', 'info'); + + let totalNumberErrors = 0; + let totalMissingErrors = 0; + let totalMeaningErrors = 0; + + // تحليل كل قسم على حدة + for (let i = 0; i < segments.length; i++) { + document.getElementById('progressBar').style.width = `${((i + 1) / segments.length) * 100}%`; + document.getElementById('statusText').textContent = `جاري تحليل القسم ${i+1} من ${segments.length}...`; + + // استدعاء دالة تحليل القسم + const analysisResult = await analyzeAlignedPair(segments[i].source, segments[i].target, i+1); + + // تحديث المسودة بنتائج التحليل + updateSegmentAnalysis(i, analysisResult); + + // تحديث متغير تخزين التحليل + analysisSegments[i].analysis = analysisResult.analysis; + analysisSegments[i].errors = analysisResult.errors; + + // تجميع إجمالي الأخطاء + totalNumberErrors += analysisResult.errors.numbers; + totalMissingErrors += analysisResult.errors.missing; + totalMeaningErrors += analysisResult.errors.meaning; + + // السماح بوقت للمعالجة بين الطلبات + await new Promise(resolve => setTimeout(resolve, 200)); + } + + // إعداد الملخص النهائي + const totalErrors = totalNumberErrors + totalMissingErrors + totalMeaningErrors; + if (totalErrors === 0) { + addError('لم يتم العثور على أخطاء - النصوص متطابقة', 'info'); + } else { + addError(`تم الانتهاء من التحليل. العثور على ${totalErrors} خطأ:`, 'info'); + if (totalNumberErrors > 0) addError(`- ${totalNumberErrors} اختلاف في الأرقام`, 'warning'); + if (totalMissingErrors > 0) addError(`- ${totalMissingErrors} نص مفقود`, 'warning'); + if (totalMeaningErrors > 0) addError(`- ${totalMeaningErrors} اختلاف في المعنى`, 'warning'); + } + + // تجهيز العرض الكلاسيكي + displayClassicView(sourceText, targetText); + + // تجهيز العرض المقسم + displaySegmentedView(); + + // تجهيز العرض التفاعلي + setupInteractiveView(); + + } catch (error) { + console.error('Error during analysis:', error); + addError('حدث خطأ أثناء التحليل: ' + error.message, 'error'); + } finally { + document.getElementById('processingStatus').classList.add('hidden'); + } + }); + + /* ===================================== + دالة إضافة التمييز المبدئي للنص + ===================================== */ + function addPreliminaryHighlights(sourceText, targetText) { + let highlightedText = sourceText; + + // 1. تحديد الأرقام المحتملة + const numberRegex = /(\d+|[٠١٢٣٤٥٦٧٨٩]+)/g; + highlightedText = highlightedText.replace(numberRegex, '$1'); + + // 2. تحديد النصوص المفقودة المحتملة + const paragraphs = sourceText.split(/\n\s*\n/).filter(p => p.trim().length > 20); + + for (const paragraph of paragraphs) { + if (!isTextSubstantiallyIncluded(paragraph, targetText)) { + highlightedText = highlightedText.replace( + paragraph, + `${paragraph}` + ); + } + } + + return highlightedText; + } + + /* ===================================== + التحقق من تضمين النص بشكل جوهري + ===================================== */ + function isTextSubstantiallyIncluded(text, targetText) { + // استخراج الكلمات المهمة (أطول من 3 أحرف) + const words = text.split(/\s+/).filter(w => w.length > 3); + if (words.length === 0) return true; + + // حساب عدد الكلمات المهمة الموجودة في النص الهدف + const foundCount = words.filter(word => targetText.includes(word)).length; + + // اعتبار النص مضمن إذا وجدنا أكثر من 50% من الكلمات المهمة + return foundCount / words.length > 0.5; + } + + /* ===================================== + دالة تقسيم النصوص مع عدد أقصى 6 أقسام + ===================================== */ + async function alignTextsWithModel(sourceText, targetText) { + try { + // إظهار حالة التقسيم + document.getElementById('processingStatus').classList.remove('hidden'); + document.getElementById('statusText').textContent = 'جاري تقسيم ومزامنة النصوص...'; + document.getElementById('progressBar').style.width = '30%'; + + // استدعاء نموذج DeepSeek للتقسيم والمزامنة + const prompt = `مهمتك هي تقسيم النصين التاليين (النص المصدر والنص الهدف) إلى أقسام متوازية بحيث يتطابق كل قسم في المصدر مع ما يقابله في الهدف. + + ملاحظات مهمة: + 1. عدد الأقسام يجب أن لا يتجاوز 6 أقسام بأي حال + 2. قسّم النص إلى أجزاء منطقية ومترابطة + 3. حاول الحفاظ على تكامل الفقرات والجمل + + أعِد النتائج بصيغة JSON بهذا الشكل: + { + "segments": [ + { + "source": "نص المصدر للقسم الأول", + "target": "نص الهدف المقابل للقسم الأول" + }, + ... + ] + } + + النص المصدر: + ${sourceText} + + النص الهدف: + ${targetText}`; + + const payload = { + model: "deepseek-chat", + messages: [ + { role: "system", content: "أنت خبير في تقسيم النصوص ومزامنتها للمقارنة والتحليل." }, + { role: "user", content: prompt } + ], + temperature: 0.3, + max_tokens: 8000 + }; + + document.getElementById('progressBar').style.width = '50%'; + + const response = await fetch(DEEPSEEK_API_URL, { + method: 'POST', + headers: { + 'Authorization': 'Bearer ' + DEEPSEEK_API_KEY, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(payload) + }); + + if (!response.ok) { + throw new Error('فشل استدعاء نموذج DeepSeek: ' + response.statusText); + } + + document.getElementById('progressBar').style.width = '80%'; + + const data = await response.json(); + const result = data.choices[0].message.content; + + // استخراج JSON من النتيجة + let segments = []; + try { + const jsonMatch = result.match(/\{[\s\S]*\}/); + if (jsonMatch) { + const parsedData = JSON.parse(jsonMatch[0]); + segments = parsedData.segments || []; + } + } catch (e) { + console.error('خطأ في تحليل نتيجة تقسيم النصوص:', e); + // استخدام طريقة بديلة إذا فشل التحليل + segments = createFallbackSegments(sourceText, targetText); + } + + // لضمان عدم تخطي الحد الأقصى 6 أقسام + if (segments.length > 6) { + segments = consolidateSegments(segments, 6); + } + + document.getElementById('progressBar').style.width = '100%'; + document.getElementById('statusText').textContent = 'تم تقسيم النصوص بنجاح!'; + + // إخفاء حالة المعالجة بعد فترة قصيرة + setTimeout(() => { + document.getElementById('processingStatus').classList.add('hidden'); + }, 1000); + + return segments; + } catch (error) { + console.error('خطأ في تقسيم النصوص:', error); + // إخفاء حالة المعالجة وإظهار رسالة الخطأ + document.getElementById('processingStatus').classList.add('hidden'); + addError('حدث خطأ أثناء تقسيم النصوص: ' + error.message); + + // إعادة تقسيم بسيط كحل احتياطي مع تحديد أقصى 6 أقسام + return createFallbackSegments(sourceText, targetText); + } + } + + /* ===================================== + دمج الأقسام لتقليل عددها إلى الحد الأقصى + ===================================== */ + function consolidateSegments(segments, maxSegments) { + if (segments.length <= maxSegments) { + return segments; + } + + // عدد الأقسام في كل مجموعة جديدة + const segmentsPerGroup = Math.ceil(segments.length / maxSegments); + const consolidatedSegments = []; + + for (let i = 0; i < segments.length; i += segmentsPerGroup) { + // دمج مجموعة من الأقسام معًا + const groupSegments = segments.slice(i, Math.min(i + segmentsPerGroup, segments.length)); + + const consolidatedSource = groupSegments.map(s => s.source).join('\n\n'); + const consolidatedTarget = groupSegments.map(s => s.target).join('\n\n'); + + consolidatedSegments.push({ + source: consolidatedSource, + target: consolidatedTarget + }); + } + + return consolidatedSegments; + } + + /* ===================================== + إنشاء تقسيمات احتياطية - محدودة بـ 6 أقسام + ===================================== */ + function createFallbackSegments(sourceText, targetText) { + addError('تم الانتقال إلى آلية التقسيم الاحتياطية', 'warning'); + + // تقسيم النصوص إلى فقرات + const sourceParagraphs = sourceText.split(/\n\s*\n/).filter(p => p.trim()); + const targetParagraphs = targetText.split(/\n\s*\n/).filter(p => p.trim()); + + // حساب عدد الأقسام المطلوبة (6 كحد أقصى) + const MAX_SEGMENTS = 6; + const sourceSegmentsCount = Math.min(MAX_SEGMENTS, sourceParagraphs.length); + const segmentsPerGroup = Math.ceil(sourceParagraphs.length / sourceSegmentsCount); + + const segments = []; + + // توزيع فقرات المصدر على الأقسام + for (let i = 0; i < sourceSegmentsCount; i++) { + const startIdx = i * segmentsPerGroup; + const endIdx = Math.min(startIdx + segmentsPerGroup, sourceParagraphs.length); + + const sourceSegment = sourceParagraphs.slice(startIdx, endIdx).join('\n\n'); + + // محاولة العثور على أقسام مقابلة في الهدف + let targetSegment = ''; + + // تخمين الأقسام المقابلة في الهدف بناءً على الطول النسبي + const targetStartIdx = Math.floor((startIdx / sourceParagraphs.length) * targetParagraphs.length); + const targetEndIdx = Math.min(Math.floor((endIdx / sourceParagraphs.length) * targetParagraphs.length), targetParagraphs.length); + + targetSegment = targetParagraphs.slice(targetStartIdx, targetEndIdx).join('\n\n'); + + // إذا لم نجد قسمًا مقابلًا، نضع رسالة توضيحية + if (!targetSegment) { + targetSegment = '(لا يوجد نص مقابل في الهدف)'; + } + + segments.push({ + source: sourceSegment, + target: targetSegment + }); + } + + return segments; + } + + /* ===================================== + تحليل قسم واحد باستخدام نموذج الذكاء الاصطناعي + ===================================== */ + async function analyzeAlignedPair(sourceText, targetText, pairNumber) { + try { + // إظهار حالة التحليل + document.getElementById('statusText').textContent = `جاري تحليل القسم ${pairNumber}...`; + + // برومبت محسن للتحليل + const prompt = `قارن النص المصدر والنص الهدف التاليين، وحدد بدقة: + 1. اختلافات الأرقام: ضع الرقم المختلف بين علامتي < > + 2. النصوص المفقودة: ضع النص المفقود في الترجمة بين علامتي __ __ + 3. اختلافات المعنى: ضع النص الذي تغير معناه بين علامتي [MEANING] [/MEANING] + + اعتبر الأرقام بمختلف أنظمتها (العربية والهندية والإنجليزية) متطابقة إذا كانت تمثل نفس الرقم. + قم بتحليل كل جملة على حدة وتحديد الاختلافات بدقة. + + لكل اختلاف، قدم شرحاً موجزاً للخطأ والتصحيح المقترح. + + النص المصدر: + ${sourceText} النص الهدف: ${targetText}`; @@ -3370,7 +3299,7 @@ max_tokens: 2048 }; - // استدعاء DeepSeek API + // استدعاء API const response = await fetch(DEEPSEEK_API_URL, { method: 'POST', headers: { @@ -3381,7 +3310,7 @@ }); if (!response.ok) { - throw new Error('فشل استدعاء للتحليل: ' + response.statusText); + throw new Error('فشل استدعاء API للتحليل: ' + response.statusText); } const data = await response.json(); @@ -3393,7 +3322,7 @@ const meaningDiffs = (analysisResult.match(/\[MEANING\](.*?)\[\/MEANING\]/g) || []).length; const totalDiffs = numberDiffs + missingTexts + meaningDiffs; - + // إنشاء ملخص للتحليل let summary; if (totalDiffs === 0) { @@ -3433,7 +3362,7 @@ } /* ===================================== - عرض الأقسام في المسودة - محسن + عرض الأقسام في المسودة - محسن مع تمييز مبدئي ===================================== */ function displayDraftSegments(segments) { const container = document.getElementById('paragraphDivisionsContainer'); @@ -3455,14 +3384,17 @@ `; + // إضافة تمييز مبدئي للنص المصدر + const preliminaryHighlightedSource = addPreliminaryHighlights(segment.source, segment.target); + // إنشاء محتوى القسم بطريقة محسنة const contentDiv = document.createElement('div'); contentDiv.className = 'section-content'; - - // استخدام تنسيق محسن للفقرات المتوازية + + // استخدام تنسيق محسن للفقرات المتوازية مع التمييز المبدئي contentDiv.innerHTML = `
-
${segment.source}
+
${preliminaryHighlightedSource}
${segment.target}
@@ -3532,111 +3464,7 @@ } /* ===================================== - دالة معالجة عملية التحليل عند الضغط على الزر - ===================================== */ - document.getElementById('submitBtn').addEventListener('click', async () => { - const sourceText = document.getElementById('sourceText').value; - const targetText = document.getElementById('targetText').value; - - // مسح الرسائل السابقة وإظهار النتائج - document.getElementById('errorsList').innerHTML = ''; - document.getElementById('resultSection').classList.remove('hidden'); - - if (!sourceText || !targetText) { - addError('يرجى إدخال كلا النصين المصدر والهدف'); - return; - } - - // مقارنة عدد الكلمات وتنبيه إن وجد اختلاف - const sourceWordCount = countWords(sourceText); - const targetWordCount = countWords(targetText); - if (Math.abs(sourceWordCount - targetWordCount) > sourceWordCount * 0.2) { - addError(`عدد كلمات النص المصدر (${sourceWordCount}) يختلف بشكل كبير عن النص الهدف (${targetWordCount})`, 'warning'); - } - - try { - addError('جارٍ تقسيم النصوص باستخدام المقسم...', 'info'); - document.getElementById('processingStatus').classList.remove('hidden'); - - // تقسيم النصوص إلى أجزاء متزامنة باستخدام DeepSeek - const segments = await alignTextsWithModel(sourceText, targetText); - - if (!segments || segments.length === 0) { - throw new Error('فشل في تقسيم النصوص - لم يتم العثور على أقسام'); - } - - // عرض الأقسام في المسودة - displayDraftSegments(segments); - - // تخزين الأقسام في المتغير العام - analysisSegments = segments.map(segment => ({ - source: segment.source, - target: segment.target, - analysis: 'جارٍ التحليل...', - errors: { numbers: 0, missing: 0, meaning: 0 } - })); - - addError(`تم تقسيم النصوص إلى ${segments.length} قسم متزامن بنجاح`, 'info'); - addError('جارٍ تحليل الأقسام ..', 'info'); - - let totalNumberErrors = 0; - let totalMissingErrors = 0; - let totalMeaningErrors = 0; - - // تحليل كل قسم على حدة - for (let i = 0; i < segments.length; i++) { - document.getElementById('progressBar').style.width = `${((i + 1) / segments.length) * 100}%`; - document.getElementById('statusText').textContent = `جاري تحليل القسم ${i+1} من ${segments.length}...`; - - // استدعاء دالة تحليل القسم - const analysisResult = await analyzeAlignedPair(segments[i].source, segments[i].target, i+1); - - // تحديث المسودة بنتائج التحليل - updateSegmentAnalysis(i, analysisResult); - - // تحديث متغير تخزين التحليل - analysisSegments[i].analysis = analysisResult.analysis; - analysisSegments[i].errors = analysisResult.errors; - - // تجميع إجمالي الأخطاء - totalNumberErrors += analysisResult.errors.numbers; - totalMissingErrors += analysisResult.errors.missing; - totalMeaningErrors += analysisResult.errors.meaning; - - // السماح بوقت للمعالجة بين الطلبات - await new Promise(resolve => setTimeout(resolve, 200)); - } - - // إعداد الملخص النهائي - const totalErrors = totalNumberErrors + totalMissingErrors + totalMeaningErrors; - if (totalErrors === 0) { - addError('لم يتم العثور على أخطاء - النصوص متطابقة', 'info'); - } else { - addError(`تم الانتهاء من التحليل. العثور على ${totalErrors} خطأ:`, 'info'); - if (totalNumberErrors > 0) addError(`- ${totalNumberErrors} اختلاف في الأرقام`, 'warning'); - if (totalMissingErrors > 0) addError(`- ${totalMissingErrors} نص مفقود`, 'warning'); - if (totalMeaningErrors > 0) addError(`- ${totalMeaningErrors} اختلاف في المعنى`, 'warning'); - } - - // تجهيز العرض الكلاسيكي - displayClassicView(sourceText, targetText); - - // تجهيز العرض المقسم - displaySegmentedView(); - - // تجهيز العرض التفاعلي - setupInteractiveView(); - - } catch (error) { - console.error('Error during analysis:', error); - addError('حدث خطأ أثناء التحليل: ' + error.message, 'error'); - } finally { - document.getElementById('processingStatus').classList.add('hidden'); - } - }); - - /* ===================================== - عرض النتائج في العرض الكلاسيكي - محسن + عرض النتائج في العرض الكلاسيكي - محسن ===================================== */ function displayClassicView(sourceText, targetText) { // دمج نتائج التحليل من جميع الأقسام @@ -3645,14 +3473,14 @@ if (index > 0) combinedAnalysis += '\n\n---\n\n'; combinedAnalysis += segment.analysis; }); - + // تطبيق التحديد على النصوص الكاملة مع المقارنة document.getElementById('sourceTextReview').innerHTML = applyHighlights(sourceText, targetText, combinedAnalysis); document.getElementById('targetTextReview').innerHTML = applyHighlights(targetText, sourceText, combinedAnalysis); - + // إعداد شرح الأخطاء بطريقة محسنة document.getElementById('classicViewExplanation').innerHTML = generateExplanation(sourceText, targetText, combinedAnalysis); - + // إضافة مستمعي الأحداث للنصوص المحددة setTimeout(() => { document.querySelectorAll('.highlight-number, .highlight-meaning, .highlight-missing, .completely-missing, .partially-missing').forEach(element => { @@ -3662,7 +3490,7 @@ this.classList.contains('partially-missing') ? 'missing' : 'number'); const errorText = this.textContent; let explanation = ''; - + if (this.classList.contains('completely-missing')) { explanation = 'هذا النص مفقود تماما في الترجمة. يجب إضافته للحفاظ على اكتمال المحتوى.'; } else if (this.classList.contains('partially-missing')) { @@ -3670,27 +3498,160 @@ } else { explanation = decodeURIComponent(this.getAttribute('data-explanation') || 'لا يوجد شرح متاح'); } - + showErrorExplanation(errorType, errorText, explanation); }); }); }, 100); } + /* ===================================== + دالة تمييز النص - محسنة + ===================================== */ + function applyHighlights(originalText, targetText, analysisOutput) { + // تطبيق تمييز للنص المفقود تماما والمفقود جزئيا + const enhancedSourceText = highlightMissingText(originalText, targetText); + + // استخراج النصوص التي بها مشاكل من التحليل + const numberMatches = Array.from(analysisOutput.matchAll(/<([^<>]+)>/g)).map(m => m[1].trim()); + const missingMatches = Array.from(analysisOutput.matchAll(/__(.*?)__/g)).map(m => m[1].trim()); + const meaningMatches = Array.from(analysisOutput.matchAll(/\[MEANING\](.*?)\[\/MEANING\]/g)).map(m => m[1].trim()); + + // تقسيم النص الأصلي إلى جمل + const sentences = splitIntoSentences(enhancedSourceText); + + // معالجة كل جملة على حدة وتطبيق التحديد + const highlightedSentences = sentences.map(sentence => { + let hasError = false; + let highlightedSentence = sentence; + + // تحديد اختلافات الأرقام + numberMatches.forEach(phrase => { + if (sentence.includes(phrase)) { + const regex = new RegExp(escapeRegExp(phrase), 'g'); + highlightedSentence = highlightedSentence.replace(regex, `${phrase}`); + hasError = true; + } + }); + + // تحديد النصوص المفقودة + missingMatches.forEach(phrase => { + if (sentence.includes(phrase)) { + const regex = new RegExp(escapeRegExp(phrase), 'g'); + highlightedSentence = highlightedSentence.replace(regex, `${phrase}`); + hasError = true; + } + }); + + // تحديد اختلافات المعنى + meaningMatches.forEach(phrase => { + if (sentence.includes(phrase)) { + const regex = new RegExp(escapeRegExp(phrase), 'g'); + highlightedSentence = highlightedSentence.replace(regex, `${phrase}`); + hasError = true; + } + }); + + // إذا كان هناك خطأ، قم بتمييز الجملة كاملة + if (hasError) { + return `${highlightedSentence}`; + } + + return highlightedSentence; + }); + + return highlightedSentences.join(' '); + } + + /* ===================================== + دالة تمييز النص المفقود + ===================================== */ + function highlightMissingText(sourceText, targetText) { + // تقسيم النصوص إلى فقرات + const sourceParagraphs = sourceText.split(/\n\s*\n/).filter(p => p.trim()); + const targetParagraphs = targetText.split(/\n\s*\n/).filter(p => p.trim()); + + // مصفوفة لتخزين النص المعدل + let modifiedSource = sourceText; + + // البحث عن الفقرات المفقودة تماما في الهدف + for (const paragraph of sourceParagraphs) { + if (paragraph.trim().length < 10) continue; // تجاهل الفقرات القصيرة جدا + + // فحص ما إذا كانت الفقرة مفقودة تماما + let isCompletelyMissing = true; + + // البحث عن أجزاء من الفقرة في النص الهدف + for (const targetPara of targetParagraphs) { + if (targetPara.includes(paragraph) || paragraph.includes(targetPara)) { + isCompletelyMissing = false; + break; + } + } + + // إذا كانت مفقودة تماما، نميزها بشكل مختلف + if (isCompletelyMissing) { + modifiedSource = modifiedSource.replace( + paragraph, + `${paragraph}` + ); + } + } + + // البحث عن الجمل المفقودة جزئيا + const sourceLines = sourceParagraphs.join('\n').split(/[.!?]\s+/); + + for (const line of sourceLines) { + if (line.trim().length < 5) continue; // تجاهل الجمل القصيرة جدا + + // البحث عن جمل موجودة في السورس ومفقودة في التارجت + let isPartiallyMissing = true; + + if (targetText.includes(line)) { + isPartiallyMissing = false; + } else { + // البحث عن كلمات رئيسية من الجملة في التارجت + const keywords = line.split(/\s+/).filter(word => word.length > 3); + let foundKeywords = 0; + + for (const keyword of keywords) { + if (targetText.includes(keyword)) { + foundKeywords++; + } + } + + // إذا وجدنا أكثر من 50% من الكلمات الرئيسية، فهي مفقودة جزئيا + if (keywords.length > 0 && foundKeywords / keywords.length > 0.5) { + isPartiallyMissing = false; + } + } + + // تمييز الجمل المفقودة جزئيا فقط إذا لم تكن مميزة بالفعل كمفقودة تماما + if (isPartiallyMissing && !modifiedSource.includes(`${line}` + ); + } + } + + return modifiedSource; + } + /* ===================================== عرض النتائج في العرض المقسم - محسن ===================================== */ function displaySegmentedView() { const container = document.getElementById('segmentedComparisonContainer'); container.innerHTML = ''; // مسح المحتوى الحالي - + // إضافة مقطع لكل قسم analysisSegments.forEach((segment, index) => { // تحديد أنواع الأخطاء في هذا القسم const hasNumbers = segment.errors.numbers > 0; const hasMissing = segment.errors.missing > 0; const hasMeaning = segment.errors.meaning > 0; - + // إنشاء علامات الأخطاء let tagHTML = ''; if (hasNumbers) { @@ -3705,16 +3666,16 @@ if (!hasNumbers && !hasMissing && !hasMeaning) { tagHTML = 'مطابق'; } - + // إنشاء عنصر المقطع const segmentDiv = document.createElement('div'); segmentDiv.className = 'segment-comparison'; segmentDiv.dataset.segmentIndex = index; - + // تطبيق التحديد على نصوص المقطع const sourceHighlighted = applyHighlights(segment.source, segment.target, segment.analysis); const targetHighlighted = applyHighlights(segment.target, segment.source, segment.analysis); - + // إعداد HTML للمقطع segmentDiv.innerHTML = `
@@ -3729,10 +3690,13 @@ ${formatAnalysisText(segment.analysis)}
`; - + container.appendChild(segmentDiv); }); - + + // إضافة شرح للعرض المقسم + document.getElementById('segmentViewExplanation').innerHTML = generateSegmentViewExplanation(); + // إضافة مستمعي الأحداث للتحديدات setTimeout(() => { document.querySelectorAll('.segment-source .highlight-number, .segment-source .highlight-meaning, .segment-target .highlight-missing, .segment-source .completely-missing, .segment-source .partially-missing').forEach(element => { @@ -3742,7 +3706,7 @@ this.classList.contains('partially-missing') ? 'missing' : 'number'); const errorText = this.textContent; let explanation = ''; - + if (this.classList.contains('completely-missing')) { explanation = 'هذا النص مفقود تماما في الترجمة. يجب إضافته للحفاظ على اكتمال المحتوى.'; } else if (this.classList.contains('partially-missing')) { @@ -3750,7 +3714,7 @@ } else { explanation = decodeURIComponent(this.getAttribute('data-explanation') || 'لا يوجد شرح متاح'); } - + showErrorExplanation(errorType, errorText, explanation); }); }); @@ -3766,7 +3730,7 @@ let numberDiffCount = 0; let missingTextCount = 0; let meaningDiffCount = 0; - + // فحص جميع المقاطع analysisSegments.forEach((segment, segmentIndex) => { // استخراج اختلافات الأرقام @@ -3780,7 +3744,7 @@ }); numberDiffCount++; }); - + // استخراج النصوص المفقودة const missingMatches = Array.from(segment.analysis.matchAll(/__(.*?)__/g)); missingMatches.forEach(match => { @@ -3792,7 +3756,7 @@ }); missingTextCount++; }); - + // استخراج اختلافات المعنى const meaningMatches = Array.from(segment.analysis.matchAll(/\[MEANING\](.*?)\[\/MEANING\]/g)); meaningMatches.forEach(match => { @@ -3805,16 +3769,16 @@ meaningDiffCount++; }); }); - + // تحديث عدادات الاختلافات document.getElementById('numberDiffCount').textContent = numberDiffCount; document.getElementById('missingTextCount').textContent = missingTextCount; document.getElementById('meaningDiffCount').textContent = meaningDiffCount; - + // إعداد توصيات المعالجة const recommendationsContainer = document.getElementById('diffRecommendations'); recommendationsContainer.innerHTML = ''; - + if (allDifferences.length === 0) { recommendationsContainer.innerHTML = `
@@ -3834,15 +3798,15 @@ if (meaningDiffCount > 0) { recommendationsContainer.innerHTML += `

صحح اختلافات المعنى لضمان دقة الترجمة.

`; } - + // توصية عامة recommendationsContainer.innerHTML += `

استخدم وضع العرض المقسم للتعديل الدقيق.

`; } - + // تعيين أحداث النقر لأزرار التنقل document.getElementById('prevDiff').addEventListener('click', showPreviousDifference); document.getElementById('nextDiff').addEventListener('click', showNextDifference); - + // إعداد الاختلاف الأول if (allDifferences.length > 0) { currentDiffIndex = 0; @@ -3862,23 +3826,23 @@ document.getElementById('prevDiff').disabled = true; document.getElementById('nextDiff').disabled = true; } - - // إضافة توضيحات إضافية - document.getElementById('interactiveViewExplanation').innerHTML = ` -

استخدم الأزرار "التالي" و"السابق" للتنقل بين الاختلافات المختلفة التي تم العثور عليها.

-

يمكنك تصفية الاختلافات حسب نوعها باستخدام خيارات التصفية الموجودة أعلاه.

- `; + + // إضافة توضيحات للعرض التفاعلي + document.getElementById('interactiveViewExplanation').innerHTML = generateInteractiveViewExplanation(); } - // الحصول على السياق المحيط بنص معين - محسن + /* ===================================== + دوال مساعدة للعرض التفاعلي + ===================================== */ + // الحصول على السياق المحيط بنص معين function getContextAroundMatch(text, match, contextSize) { const index = text.indexOf(match); if (index === -1) return ""; - + // الحصول على جملة كاملة تحتوي على النص المطابق const start = Math.max(0, text.lastIndexOf('.', index) + 1); const end = Math.min(text.length, text.indexOf('.', index + match.length) + 1); - + // إذا لم نتمكن من العثور على جملة كاملة، نستخدم عدد الأحرف let context; if (end - start < 10) { @@ -3888,33 +3852,33 @@ } else { context = text.substring(start, end); } - + // تمييز النص المطابق const highlightedContext = context.replace( new RegExp(`(${escapeRegExp(match)})`, 'g'), `$1` ); - + return highlightedContext; } - // عرض الاختلاف التالي - function showNextDifference() { + // عرض الاختلاف السابق + function showPreviousDifference() { if (allDifferences.length === 0) return; - - currentDiffIndex = (currentDiffIndex + 1) % allDifferences.length; + + currentDiffIndex = (currentDiffIndex - 1 + allDifferences.length) % allDifferences.length; displayCurrentDifference(); } - // عرض الاختلاف السابق - function showPreviousDifference() { + // عرض الاختلاف التالي + function showNextDifference() { if (allDifferences.length === 0) return; - - currentDiffIndex = (currentDiffIndex - 1 + allDifferences.length) % allDifferences.length; + + currentDiffIndex = (currentDiffIndex + 1) % allDifferences.length; displayCurrentDifference(); } - // عرض الاختلاف الحالي - محسن + // عرض الاختلاف الحالي function displayCurrentDifference() { if (allDifferences.length === 0 || currentDiffIndex < 0) { document.getElementById('currentDiffDisplay').innerHTML = @@ -3924,14 +3888,14 @@ document.getElementById('diffReference').classList.add('hidden'); return; } - + const diff = allDifferences[currentDiffIndex]; const segment = analysisSegments[diff.segmentIndex]; - + let typeLabel = ''; let typeClass = ''; let icon = ''; - + if (diff.type === 'number') { typeLabel = 'اختلاف رقمي'; typeClass = 'bg-yellow-100 text-yellow-800'; @@ -3945,9 +3909,9 @@ typeClass = 'bg-red-100 text-red-800'; icon = 'fas fa-exclamation-circle'; } - + let highlightedContext = diff.context; - + // إضافة رقم المقطع ونوع الخطأ وعرض السياق document.getElementById('currentDiffDisplay').innerHTML = `
@@ -3975,24 +3939,24 @@
`; - + document.getElementById('diffCounter').textContent = `${currentDiffIndex + 1}/${allDifferences.length}`; - + // عرض النص في المصدر والهدف document.getElementById('diffDetailedView').classList.remove('hidden'); - + // عرض النص المصدر document.getElementById('diffSourceText').innerHTML = `
${highlightSourceText(segment.source, diff.text)}
`; - + // عرض النص الهدف document.getElementById('diffTargetText').innerHTML = `
${highlightTargetText(segment.target, diff)}
`; - + // عرض قسم المراجع إذا كان متاحا if (diff.reference) { document.getElementById('diffReference').classList.remove('hidden'); @@ -4010,17 +3974,17 @@ ); } - // تمييز النص في الهدف بناء على نوع الاختلاف + // تمييز النص في الهدف function highlightTargetText(targetText, diff) { if (diff.type === 'missing') { // بالنسبة للنص المفقود، نحاول العثور على مكان يجب إدراجه const words = diff.text.split(/\s+/).filter(w => w.length > 3); let markedText = targetText; - + // البحث عن كلمات قبل وبعد النص المفقود في السياق const contextBefore = diff.context.split(diff.text)[0]?.trim(); const contextAfter = diff.context.split(diff.text)[1]?.trim(); - + if (contextBefore && targetText.includes(contextBefore)) { const index = targetText.indexOf(contextBefore) + contextBefore.length; markedText = @@ -4037,16 +4001,16 @@ // إذا لم نتمكن من تحديد المكان، نضيف علامة في النهاية markedText += ` [نص مفقود: ${diff.text}]`; } - + return markedText; } else if (diff.type === 'number') { // البحث عن الأرقام في النص الهدف وتحديد الرقم المختلف const numbers = targetText.match(/\d+/g) || []; if (numbers.length === 0) return targetText; - + let markedText = targetText; - + // تحديد الرقم الذي يختلف عن النص المصدر for (const num of numbers) { if (num !== diff.text) { @@ -4056,14 +4020,14 @@ ); } } - + return markedText; } else { // meaning // البحث عن عبارات مشابهة في النص الهدف const words = diff.text.split(/\s+/).filter(w => w.length > 3); let markedText = targetText; - + for (const word of words) { if (targetText.includes(word)) { const regex = new RegExp(`(.{0,10}${escapeRegExp(word)}.{0,10})`, 'g'); @@ -4073,7 +4037,7 @@ ); } } - + return markedText; } } @@ -4091,774 +4055,649 @@ } /* ===================================== - دالة توليد الشرح التفصيلي Organized Explanation - يتم تقسيم الشرح إلى خطوات منظمة + هروب أحرف Regex الخاصة + ===================================== */ + function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + /* ===================================== + تنسيق نص التحليل بتمييز الكلمات المهمة + ===================================== */ + function formatAnalysisText(text) { + // تمييز الكلمات المهمة + text = text.replace(/الأرقام/g, 'الأرقام'); + text = text.replace(/المفقودة/g, 'المفقودة'); + text = text.replace(/المعنى/g, 'المعنى'); + + // تحديد علامات التمييز + text = text.replace(/<([^<>]+)>/g, '<$1>'); + text = text.replace(/__(.*?)__/g, '__$1__'); + text = text.replace(/\[MEANING\](.*?)\[\/MEANING\]/g, '$1'); + + return text; + } + + /* ===================================== + توليد شرح منظم للعرض الكلاسيكي ===================================== */ function generateExplanation(sourceText, targetText, analysisOutput) { - // إضافة تحليل محسن للنصوص المفقودة تماما والمفقودة جزئيا - let steps = []; - + // إضافة تحليل محسن للنصوص المفقودة + const numberErrors = []; + const missingErrors = []; + const meaningErrors = []; + + // استخراج الأخطاء من تحليل النص + const numberMatches = Array.from(analysisOutput.matchAll(/<([^<>]+)>/g)); + numberMatches.forEach(match => { + numberErrors.push({ + text: match[1], + explanation: 'تأكد من تطابق هذا الرقم في النص الهدف.' + }); + }); + + const missingMatches = Array.from(analysisOutput.matchAll(/__(.*?)__/g)); + missingMatches.forEach(match => { + missingErrors.push({ + text: match[1], + explanation: 'تأكد من وجود هذا النص في الترجمة.' + }); + }); + + const meaningMatches = Array.from(analysisOutput.matchAll(/\[MEANING\](.*?)\[\/MEANING\]/g)); + meaningMatches.forEach(match => { + meaningErrors.push({ + text: match[1], + explanation: 'راجع ترجمة هذا النص للتأكد من نقل المعنى الصحيح.' + }); + }); + // البحث عن النصوص المفقودة تماما const completelyMissing = []; const sourceParagraphs = sourceText.split(/\n\s*\n/).filter(p => p.trim()); - + for (const paragraph of sourceParagraphs) { if (paragraph.trim().length < 10) continue; - + // فحص إذا كانت الفقرة مفقودة تماما if (!targetText.includes(paragraph) && !isPartiallyIncluded(paragraph, targetText)) { - completelyMissing.push({ - text: truncateText(paragraph, 100), - lineNum: getLineNumber(sourceText, paragraph) - }); + if (paragraph.length > 100) { + completelyMissing.push({ + text: paragraph.substring(0, 100) + '...', + explanation: 'هذا النص مفقود تماما في الترجمة' + }); + } else { + completelyMissing.push({ + text: paragraph, + explanation: 'هذا النص مفقود تماما في الترجمة' + }); + } } } + + // إضافة النصوص المفقودة إلى قائمة الأخطاء + completelyMissing.forEach(missing => { + missingErrors.push(missing); + }); + + // إنشاء المخرجات المحسنة + if (numberErrors.length === 0 && missingErrors.length === 0 && meaningErrors.length === 0) { + return ` +
+
+ +
+

النصوص متطابقة تماماً ولا توجد فروقات تحتاج للتنبيه.

+

تهانينا! الترجمة دقيقة ومكتملة.

+
`; + } + + let html = ` +
+
+
${numberErrors.length}
+
+ + اختلافات الأرقام +
+
+ +
+
${missingErrors.length}
+
+ + النصوص المفقودة +
+
+ +
+
${meaningErrors.length}
+
+ + اختلافات المعنى +
+
+
- // إضافة الخطوة 1: النصوص المفقودة تماما - if (completelyMissing.length > 0) { - completelyMissing.forEach(missing => { - steps.push(` -
  • -
    - - النص المفقود تماما: - السطر ${missing.lineNum} +
    `; + + // إضافة مجموعة اختلافات الأرقام + if (numberErrors.length > 0) { + html += ` +
    +
    + + اختلافات الأرقام (${numberErrors.length}) +
    +
    `; + + // إضافة 5 أخطاء كحد أقصى (أو جميعها إذا كانت أقل) + const displayedErrors = numberErrors.slice(0, 5); + + displayedErrors.forEach((error, index) => { + html += ` +
    +
    + اختلاف ${index + 1}
    -
    - ${missing.text} +
    + ${error.text}
    -
    - - هذا النص موجود في المصدر ومفقود تماما في الترجمة. + -
  • `); +
    `; }); - } - // الخطوة 2: اختلافات الأرقام - const numberRegex = /<([^<>]+)>/g; - let match; - while ((match = numberRegex.exec(analysisOutput)) !== null) { - const phrase = match[1].trim(); - if (phrase) { - const lineNum = getLineNumber(sourceText, phrase); - steps.push(` -
  • -
    - - اختلاف رقمي: - السطر ${lineNum} -
    -
    - ${phrase} -
    -
    - - تأكد من تطابق هذا الرقم في النص الهدف. -
    -
  • `); + // إضافة مؤشر "المزيد" إذا كان هناك أكثر من 5 أخطاء + if (numberErrors.length > 5) { + html += ` +
    + + و${numberErrors.length - 5} اختلافات أخرى +
    `; } + + html += ` +
    +
    `; } + + // إضافة مجموعة النصوص المفقودة + if (missingErrors.length > 0) { + html += ` +
    +
    + + النصوص المفقودة (${missingErrors.length}) +
    +
    `; + + // إضافة 5 أخطاء كحد أقصى (أو جميعها إذا كانت أقل) + const displayedErrors = missingErrors.slice(0, 5); - // الخطوة 3: النصوص المفقودة جزئيا - const missingRegex = /__(.*?)__/g; - while ((match = missingRegex.exec(analysisOutput)) !== null) { - const phrase = match[1].trim(); - if (phrase) { - const lineNum = getLineNumber(sourceText, phrase); - steps.push(` -
  • -
    - - نص مفقود: - السطر ${lineNum} + displayedErrors.forEach((error, index) => { + html += ` +
    +
    + نص مفقود ${index + 1}
    -
    - ${phrase} +
    + ${error.text}
    -
    - - تأكد من وجود هذا النص في الترجمة. + -
  • `); +
    `; + }); + + // إضافة مؤشر "المزيد" إذا كان هناك أكثر من 5 أخطاء + if (missingErrors.length > 5) { + html += ` +
    + + و${missingErrors.length - 5} نصوص مفقودة أخرى +
    `; } + + html += ` +
    + `; } + + // إضافة مجموعة اختلافات المعنى + if (meaningErrors.length > 0) { + html += ` +
    +
    + + اختلافات المعنى (${meaningErrors.length}) +
    +
    `; + + // إضافة 5 أخطاء كحد أقصى (أو جميعها إذا كانت أقل) + const displayedErrors = meaningErrors.slice(0, 5); - // الخطوة 4: اختلافات المعنى - const meaningRegex = /\[MEANING\](.*?)\[\/MEANING\]/g; - while ((match = meaningRegex.exec(analysisOutput)) !== null) { - const phrase = match[1].trim(); - if (phrase) { - const lineNum = getLineNumber(sourceText, phrase); - steps.push(` -
  • -
    - - اختلاف معنى: - السطر ${lineNum} + displayedErrors.forEach((error, index) => { + html += ` +
    +
    + اختلاف معنى ${index + 1}
    -
    - ${phrase} +
    + ${error.text}
    -
    - - راجع ترجمة هذا النص للتأكد من نقل المعنى الصحيح. + -
  • `); +
    `; + }); + + // إضافة مؤشر "المزيد" إذا كان هناك أكثر من 5 أخطاء + if (meaningErrors.length > 5) { + html += ` +
    + + و${meaningErrors.length - 5} اختلافات معنى أخرى +
    `; } - } - // إذا لم يكن هناك اختلافات - if (steps.length === 0) { - return ` -
    -
    - + html += `
    -

    النصوص متطابقة تماماً ولا توجد فروقات تحتاج للتنبيه.

    -

    تهانينا! الترجمة دقيقة ومكتملة.

    `; } + + html += ` +
    + +
    +

    + + توصيات للتحسين +

    + +
    `; + + return html; + } + + /* ===================================== + توليد شرح للعرض المقسم + ===================================== */ + function generateSegmentViewExplanation() { + return ` +
    +

    + + عن العرض المقسم +

    +

    + يقوم هذا العرض بتقسيم النص إلى مقاطع متوازية لتسهيل المقارنة بين النص المصدر والترجمة. + انقر على النص المميز للحصول على تفاصيل إضافية حول الاختلاف. +

    +
    +
    +

    + + نصائح للاستخدام +

    + +
    `; + } + + /* ===================================== + توليد شرح للعرض التفاعلي + ===================================== */ + function generateInteractiveViewExplanation() { return ` -
    -

    - - ملخص الاختلافات +
    +

    + + عن العرض التفاعلي

    -

    تم العثور على ${steps.length} اختلاف يحتاج إلى مراجعة.

    +

    + يقوم هذا العرض بتمكينك من استعراض الاختلافات واحدة تلو الأخرى بطريقة تفاعلية. + استخدم أزرار "التالي" و"السابق" للتنقل بين الاختلافات المختلفة. +

    -
      ${steps.join('')}
    `; + +
    +

    + + مزايا هذا العرض +

    +
      +
    • تركيز أفضل على كل اختلاف على حدة
    • +
    • رؤية النص المصدر والهدف معًا لكل اختلاف
    • +
    • الحصول على توصيات محددة لمعالجة كل مشكلة
    • +
    +
    `; } - - // دالة مساعدة للتحقق إذا كان النص مضمن جزئيا + + /* ===================================== + دالة للتحقق إذا كان النص مضمن جزئيا + ===================================== */ function isPartiallyIncluded(text, targetText) { // تقسيم النص إلى كلمات رئيسية const keywords = text.split(/\s+/).filter(word => word.length > 3); let foundKeywords = 0; - + // حساب عدد الكلمات الرئيسية الموجودة في النص الهدف for (const keyword of keywords) { if (targetText.includes(keyword)) { foundKeywords++; } } - + // اعتبار النص مضمن جزئيا إذا وجدنا أكثر من 30% من الكلمات return keywords.length > 0 && foundKeywords / keywords.length > 0.3; } - /* ===================================== - التبديل بين عرض وإخفاء المسودة - ===================================== */ - document.getElementById('toggleDraftBtn').addEventListener('click', function() { - const draftSection = document.getElementById('fullTextDraftSection'); - draftSection.classList.toggle('hidden'); - - // تغيير نص الزر بناء على حالة العرض - const isHidden = draftSection.classList.contains('hidden'); - this.innerHTML = isHidden ? - ' عرض مسودة التحليل' : - ' إخفاء مسودة التحليل'; - }); - - /* ===================================== - تنزيل التقرير بصيغة Excel - ===================================== */ - document.getElementById('downloadExcelBtn').addEventListener('click', function() { - // إنشاء تقرير Excel بناء على نتائج التحليل - if (!analysisSegments || analysisSegments.length === 0) { - alert('لا توجد نتائج تحليل للتنزيل'); - return; - } - - try { - // إنشاء مصفوفة البيانات للتقرير - const reportData = []; - - // إضافة رأس الجدول - reportData.push(['رقم المقطع', 'النص المصدر', 'النص الهدف', 'الأخطاء', 'التحليل']); - - // إضافة بيانات التحليل - analysisSegments.forEach((segment, index) => { - const errors = []; - if (segment.errors.numbers > 0) errors.push(`اختلافات أرقام: ${segment.errors.numbers}`); - if (segment.errors.missing > 0) errors.push(`نصوص مفقودة: ${segment.errors.missing}`); - if (segment.errors.meaning > 0) errors.push(`اختلافات معنى: ${segment.errors.meaning}`); - - reportData.push([ - index + 1, - segment.source, - segment.target, - errors.join('\n'), - segment.analysis - ]); - }); - - // إنشاء ورقة عمل - const ws = XLSX.utils.aoa_to_sheet(reportData); - - // إنشاء المصنف - const wb = XLSX.utils.book_new(); - XLSX.utils.book_append_sheet(wb, ws, "تقرير التحليل"); - - // تنزيل الملف - XLSX.writeFile(wb, "تقرير_تحليل_النصوص.xlsx"); - - } catch (error) { - console.error("خطأ في إنشاء ملف Excel:", error); - alert("حدث خطأ أثناء إنشاء التقرير"); - } - }); - /* ===================================== تنزيل التقرير بصيغة Word ===================================== */ - document.getElementById('downloadWordBtn').addEventListener('click', function() { - alert('سيتم تنفيذ تنزيل التقرير بصيغة Word قريبًا'); - }); - - /* ===================================== - معالجة OCR للملفات - ===================================== */ - function processFileForOCR(type) { - const fileInput = type === 'source' ? document.getElementById('sourceFile') : document.getElementById('targetFile'); - const file = fileInput.files[0]; - - if (!file) { - alert('الرجاء اختيار ملف أولاً'); - return; - } - - if (file.type === 'application/pdf') { - processPDF(file, type); - } else if (file.type.startsWith('image/')) { - processImage(file, type); - } else { - alert('يرجى اختيار ملف PDF أو صورة للمعالجة'); - } + function downloadWordReport() { + // إنشاء محتوى ملف Word + let wordContent = ` + + + + + تقرير تحليل النصوص - شركة الريحان + + + +
    +

    تقرير تحليل النصوص المترجمة

    +
    شركة الريحان للترجمة المعتمدة
    +
    تاريخ التقرير: ${new Date().toLocaleDateString('ar-EG')}
    +
    + +
    +
    ملخص التحليل
    + + + + + `; - function deselectAllPages() { - selectedPages = []; - documentPages.forEach(page => { - page.selected = false; - }); - - document.querySelectorAll('.pdf-page').forEach(pageDiv => { - pageDiv.classList.remove('selected'); + // حساب إجمالي الأخطاء + let totalNumbers = 0; + let totalMissing = 0; + let totalMeaning = 0; + + analysisSegments.forEach(segment => { + totalNumbers += segment.errors.numbers; + totalMissing += segment.errors.missing; + totalMeaning += segment.errors.meaning; }); - } - async function extractText() { - if (selectedPages.length === 0) { - alert('الرجاء اختيار صفحة واحدة على الأقل'); - return; - } - - document.getElementById('processingStatus').classList.remove('hidden'); - document.getElementById('statusText').textContent = 'جاري استخراج النص...'; - document.getElementById('progressBar').style.width = '0%'; - - extractedTexts = []; - extractedPageNumbers = []; - - try { - for (let i = 0; i < selectedPages.length; i++) { - const pageIndex = selectedPages[i]; - const pageData = documentPages[pageIndex]; - - document.getElementById('statusText').textContent = `جاري استخراج النص من الصفحة ${pageData.pageNum}...`; - document.getElementById('progressBar').style.width = `${(i / selectedPages.length) * 100}%`; - - // استخدام API الـ OCR - const base64Image = pageData.imageData.split(',')[1]; - - // إرسال الصورة إلى API الـ OCR - const formData = new FormData(); - formData.append('image', base64Image); - formData.append('language', 'ara'); // تحديد اللغة العربية - - const response = await fetch(OCR_API_URL, { - method: 'POST', - headers: { - 'x-rapidapi-key': RAPIDAPI_KEY, - 'x-rapidapi-host': 'ocr43.p.rapidapi.com' - }, - body: formData - }); - - if (!response.ok) { - throw new Error(`خطأ في استخراج النص: ${response.statusText}`); - } - - const data = await response.json(); - - if (data && data.text) { - extractedTexts.push(data.text); - extractedPageNumbers.push(pageData.pageNum); - ocrPagesCount++; - - // تحديث العداد في localStorage - localStorage.setItem('ocrPagesCount', ocrPagesCount); - document.getElementById('ocrCounter').textContent = ocrPagesCount; - - // تحديث تاريخ آخر معالجة - const now = new Date(); - const dateStr = now.toLocaleDateString('ar-EG'); - localStorage.setItem('lastOcrDate', dateStr); - document.getElementById('lastOcrDate').textContent = dateStr; - } - } - - document.getElementById('progressBar').style.width = '100%'; - - // عرض النصوص المستخرجة - displayExtractedTexts(); - - } catch (error) { - console.error('خطأ في استخراج النص:', error); - alert('حدث خطأ أثناء استخراج النص: ' + error.message); - document.getElementById('processingStatus').classList.add('hidden'); - } - } + wordContent += ` + + + + + + + + + + + + + + + + +
    نوع الاختلافالعدد
    اختلافات الأرقام${totalNumbers}
    النصوص المفقودة${totalMissing}
    اختلافات المعنى${totalMeaning}
    إجمالي الاختلافات${totalNumbers + totalMissing + totalMeaning}
    +
    `; - function displayExtractedTexts() { - document.getElementById('pdfPagesCard').classList.add('hidden'); - document.getElementById('resultsCard').classList.remove('hidden'); - document.getElementById('processingStatus').classList.add('hidden'); - - const resultPreview = document.getElementById('resultPreview'); - resultPreview.innerHTML = ''; - - extractedTexts.forEach((text, index) => { - const pagePreview = document.createElement('div'); - pagePreview.className = 'page-preview'; - - pagePreview.innerHTML = ` -

    الصفحة ${extractedPageNumbers[index]}

    -
    ${text.substring(0, 100)}${text.length > 100 ? '...' : ''}
    - `; - - resultPreview.appendChild(pagePreview); + // إضافة تفاصيل كل قسم + analysisSegments.forEach((segment, index) => { + wordContent += ` +
    +
    القسم ${index + 1}
    +
    + النص المصدر:
    + ${segment.source} +
    +
    + النص الهدف:
    + ${segment.target} +
    +
    + التحليل:
    + ${segment.analysis.replace(/<([^<>]+)>/g, '<$1>') + .replace(/__(.*?)__/g, '__$1__') + .replace(/\[MEANING\](.*?)\[\/MEANING\]/g, '$1')} +
    +
    `; }); - - // عرض النص الكامل - document.getElementById('resultText').textContent = extractedTexts.join('\n\n'); - } - function copyText() { - const resultText = document.getElementById('resultText'); - - // إنشاء عنصر textarea مؤقت - const textarea = document.createElement('textarea'); - textarea.value = resultText.textContent; - document.body.appendChild(textarea); - - // اختيار النص ونسخه - textarea.select(); - document.execCommand('copy'); - - // إزالة العنصر المؤقت - document.body.removeChild(textarea); - - alert('تم نسخ النص'); - } + // إضافة التذييل + wordContent += ` + + +`; - function downloadText() { - const text = document.getElementById('resultText').textContent; - const blob = new Blob([text], { type: 'text/plain' }); + // تنزيل الملف + const blob = new Blob(['\ufeff', wordContent], { type: 'application/msword' }); const url = URL.createObjectURL(blob); - const a = document.createElement('a'); a.href = url; - a.download = 'extracted_text.txt'; + a.download = 'تقرير_تحليل_النصوص.doc'; + document.body.appendChild(a); a.click(); - + document.body.removeChild(a); URL.revokeObjectURL(url); } + + /* ===================================== + عرض الأخطاء والرسائل - محسنة + ===================================== */ + function addError(message, type = 'error') { + const errorsList = document.getElementById('errorsList'); + if (!errorsList) return; + const errorDiv = document.createElement('div'); - function useOcrText(target) { - const text = document.getElementById('resultText').textContent; - - if (target === 'source') { - document.getElementById('sourceText').value = text; - } else { - document.getElementById('targetText').value = text; - } - - alert(`تم استخدام النص المستخرج كنص ${target === 'source' ? 'مصدر' : 'هدف'}`); - } + // تحسين مظهر رسائل الخطأ + let bgColor, borderColor, textColor, icon; - /* ===================================== - وظائف محرر الصور - ===================================== */ - function initializeImageEditor(index) { - currentPageIndex = index; - - const imageCanvas = document.getElementById('imageCanvas'); - const imageEditor = document.getElementById('imageEditor'); - - imageEditor.classList.remove('hidden'); - - // حفظ البيانات الأصلية للصورة - originalImageData = documentPages[index].imageData; - - // إنشاء كائن Fabric canvas - if (fabricCanvas) { - fabricCanvas.dispose(); + if (type === 'error') { + bgColor = 'bg-red-50'; + borderColor = 'border-red-200'; + textColor = 'text-red-700'; + icon = 'exclamation-circle text-red-500'; + } else if (type === 'info') { + bgColor = 'bg-blue-50'; + borderColor = 'border-blue-200'; + textColor = 'text-blue-700'; + icon = 'info-circle text-blue-500'; + } else { // warning + bgColor = 'bg-yellow-50'; + borderColor = 'border-yellow-200'; + textColor = 'text-yellow-700'; + icon = 'exclamation-triangle text-yellow-500'; } - - fabricCanvas = new fabric.Canvas('imageCanvas'); - - // تحميل الصورة - fabric.Image.fromURL(originalImageData, function(img) { - // تحجيم الصورة لتناسب الكانفاس - const containerWidth = imageCanvas.parentElement.clientWidth; - const scale = containerWidth / img.width; - - img.scale(scale); - - fabricCanvas.setWidth(img.width * scale); - fabricCanvas.setHeight(img.height * scale); - fabricCanvas.setBackgroundImage(img, fabricCanvas.renderAll.bind(fabricCanvas)); - }); - } - - function rotateImageLeft() { - if (!fabricCanvas) return; - - const img = fabricCanvas.backgroundImage; - img.rotate((img.angle || 0) - 90); - fabricCanvas.renderAll(); - - updateModifiedImage(); - } - function rotateImageRight() { - if (!fabricCanvas) return; - - const img = fabricCanvas.backgroundImage; - img.rotate((img.angle || 0) + 90); - fabricCanvas.renderAll(); - - updateModifiedImage(); - } + errorDiv.className = `p-4 rounded-xl ${bgColor} ${textColor} border ${borderColor} shadow-sm mb-3 animate-scale`; + errorDiv.innerHTML = ` +
    + + ${message} + +
    `; - function flipImageHorizontal() { - if (!fabricCanvas) return; - - const img = fabricCanvas.backgroundImage; - img.set('flipX', !img.flipX); - fabricCanvas.renderAll(); - - updateModifiedImage(); - } + // إضافة زر إغلاق للرسالة + const closeBtn = errorDiv.querySelector('button'); + closeBtn.addEventListener('click', () => { + errorDiv.style.opacity = '0'; + errorDiv.style.transform = 'translateY(-10px)'; + errorDiv.style.transition = 'opacity 0.3s, transform 0.3s'; + setTimeout(() => { + errorsList.removeChild(errorDiv); + }, 300); + }); - function flipImageVertical() { - if (!fabricCanvas) return; - - const img = fabricCanvas.backgroundImage; - img.set('flipY', !img.flipY); - fabricCanvas.renderAll(); - - updateModifiedImage(); - } + errorsList.appendChild(errorDiv); - function activateCropMode() { - if (!fabricCanvas) return; - - if (isInCropMode) { - return; - } - - isInCropMode = true; - document.getElementById('cropImage').textContent = 'تطبيق القص'; - - // إنشاء مربع للقص - cropRect = new fabric.Rect({ - left: 50, - top: 50, - width: fabricCanvas.width - 100, - height: fabricCanvas.height - 100, - fill: 'rgba(0,0,0,0.2)', - stroke: 'black', - strokeDashArray: [5, 5], - strokeWidth: 2, - selectable: true - }); - - fabricCanvas.add(cropRect); - fabricCanvas.setActiveObject(cropRect); + // تمرير تلقائي إلى أحدث رسالة + errorDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } - - function applyCrop() { - if (!fabricCanvas || !cropRect) return; - - isInCropMode = false; - document.getElementById('cropImage').textContent = 'قص الصورة'; - - // الحصول على معلومات مستطيل القص - const rect = cropRect; - const img = fabricCanvas.backgroundImage; - - // إزالة مستطيل القص - fabricCanvas.remove(rect); - - // إنشاء كانفاس جديد للقص - const cropCanvas = document.createElement('canvas'); - const cropContext = cropCanvas.getContext('2d'); - - cropCanvas.width = rect.getScaledWidth(); - cropCanvas.height = rect.getScaledHeight(); - - // إعداد المصدر والوجهة للقص - const sourceLeft = rect.left; - const sourceTop = rect.top; - const sourceWidth = rect.getScaledWidth(); - const sourceHeight = rect.getScaledHeight(); - - // إنشاء صورة جديدة من الصورة الأصلية - const tempImage = new Image(); - tempImage.onload = function() { - // رسم الجزء المقصوص من الصورة - cropContext.drawImage( - tempImage, - sourceLeft / img.scaleX, - sourceTop / img.scaleY, - sourceWidth / img.scaleX, - sourceHeight / img.scaleY, - 0, 0, - cropCanvas.width, - cropCanvas.height - ); - - // تحديث صورة الخلفية - fabric.Image.fromURL(cropCanvas.toDataURL(), function(newImg) { - fabricCanvas.setBackgroundImage(newImg, fabricCanvas.renderAll.bind(fabricCanvas)); - fabricCanvas.setDimensions({ - width: cropCanvas.width, - height: cropCanvas.height - }); - - updateModifiedImage(); + + /* ===================================== + تطبيق فلتر الأخطاء على جميع طرق العرض + ===================================== */ + function applyErrorFilter(filterType) { + // تطبيق الفلتر على العرض الكلاسيكي + if (filterType === 'all') { + // إظهار كل الأخطاء + document.querySelectorAll('.highlight-number, .highlight-missing, .highlight-meaning, .completely-missing, .partially-missing').forEach(el => { + el.style.display = ''; + }); + // إعادة تفعيل جميع الجمل + document.querySelectorAll('.sentence-with-error').forEach(el => { + el.classList.remove('opacity-50'); + }); + } else { + // إخفاء كل الأخطاء أولاً + document.querySelectorAll('.highlight-number, .highlight-missing, .highlight-meaning, .completely-missing, .partially-missing').forEach(el => { + el.style.display = 'none'; }); - }; - tempImage.src = img._element.src; - } - function resetImage() { - if (!fabricCanvas) return; - - // إعادة الصورة إلى حالتها الأصلية - fabric.Image.fromURL(originalImageData, function(img) { - const containerWidth = document.getElementById('imageCanvas').parentElement.clientWidth; - const scale = containerWidth / img.width; - - img.scale(scale); - - fabricCanvas.setWidth(img.width * scale); - fabricCanvas.setHeight(img.height * scale); - fabricCanvas.setBackgroundImage(img, fabricCanvas.renderAll.bind(fabricCanvas)); - - // إزالة أي عناصر إضافية - fabricCanvas.clear(); - - // إعادة تعيين حالة القص - isInCropMode = false; - cropRect = null; - document.getElementById('cropImage').textContent = 'قص الصورة'; - - // تحديث الصورة المعدلة - updateModifiedImage(); - }); - } + // إضفاء شفافية على جميع الجمل + document.querySelectorAll('.sentence-with-error').forEach(el => { + el.classList.add('opacity-50'); + }); - function improveContrast() { - if (!fabricCanvas) return; - - const img = fabricCanvas.backgroundImage; - - // إنشاء كانفاس مؤقت - const tempCanvas = document.createElement('canvas'); - const tempContext = tempCanvas.getContext('2d'); - - tempCanvas.width = img.width * img.scaleX; - tempCanvas.height = img.height * img.scaleY; - - // رسم الصورة على الكانفاس المؤقت - tempContext.drawImage(img._element, 0, 0, tempCanvas.width, tempCanvas.height); - - // الحصول على بيانات الصورة - const imageData = tempContext.getImageData(0, 0, tempCanvas.width, tempCanvas.height); - const data = imageData.data; - - // تحسين التباين - const factor = 1.5; // عامل التباين + // إظهار الأخطاء المطلوبة فقط + let selector = ''; + if (filterType === 'number') selector = '.highlight-number'; + else if (filterType === 'missing') selector = '.highlight-missing, .completely-missing, .partially-missing'; + else if (filterType === 'meaning') selector = '.highlight-meaning'; - for (let i = 0; i < data.length; i += 4) { - // الحصول على القيم RGB - const r = data[i]; - const g = data[i + 1]; - const b = data[i + 2]; - - // حساب القيمة الجديدة - data[i] = Math.min(255, Math.max(0, factor * (r - 128) + 128)); - data[i + 1] = Math.min(255, Math.max(0, factor * (g - 128) + 128)); - data[i + 2] = Math.min(255, Math.max(0, factor * (b - 128) + 128)); + document.querySelectorAll(selector).forEach(el => { + el.style.display = ''; + // إزالة الشفافية عن الجمل المتأثرة + const errorId = el.getAttribute('data-error-id'); + if (errorId) { + document.querySelectorAll(`.sentence-with-error[data-error-id="${errorId}"]`).forEach(sentence => { + sentence.classList.remove('opacity-50'); + }); + } + }); } - - // وضع البيانات المعدلة في الكانفاس - tempContext.putImageData(imageData, 0, 0); - - // تحديث صورة الخلفية - fabric.Image.fromURL(tempCanvas.toDataURL(), function(newImg) { - fabricCanvas.setBackgroundImage(newImg, fabricCanvas.renderAll.bind(fabricCanvas)); - - updateModifiedImage(); - }); - } - function updateModifiedImage() { - // تحديث الصورة المعدلة في مصفوفة الصفحات - if (fabricCanvas && currentPageIndex !== -1) { - documentPages[currentPageIndex].imageData = fabricCanvas.toDataURL(); - } + // تطبيق الفلتر على العرض المقسم والتفاعلي + // (الرمز يشبه العرض الكلاسيكي مع تعديلات بسيطة للتناسب مع هيكل هذه العروض) }