التعليقات التوضيحية

إضافة دعم التعليقات التوضيحية إلى العارض

تعمل التعليقات التوضيحية في Doconut في اتجاهين: يرسمها المستخدمون في عنصر المتصفح وتحتفظ بها الخادم لكل صفحة، أو يبنيها الكود الخاص بك ويحمّلها في جلسة مفتوحة. في كلتا الحالتين تُعرض على الصفحات ويمكن دمجها في تصديرات PDF أو PNG.

يتم التحكم في دعم التعليقات التوضيحية بقدرة الترخيص Annotation (تُمنح تلقائيًا بموجب ترخيص مؤقت نشط).

تمكين واجهة المستخدم للتعليقات التوضيحية

التعليق هو وحدة في العارض، وليس شريط أدوات مستقل. يجب أن تتضمن الصفحة موارد العارض، شريط أدوات العارض، نقطة تثبيت العارض، وobjViewer المُهيأ؛ ثم يتم تثبيت شريط التعليقات التوضيحية وإرفاقه بنفس المثيل.

أدرج ملفات التعليقات التوضيحية مع ملفات العارض. هي مقيدة بالترخيص، لذا تظهر وسومها فقط عندما تُمنح القدرة. في MVC 5، قم بإنشاء الوسوم في المتحكم (العروض لا تحتوي على @inject):

csharp
var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();
ViewBag.ViewerCss = viewer.ReferenceCss(new CssConfig
{
    IncludeViewerCss     = true,
    IncludeAnnotationCss = true    // jquery-ui.min.css + annotationBar.css
});
ViewBag.ViewerScripts = viewer.ReferenceScripts(new ScriptConfig
{
    IncludeJQuery            = true,
    IncludeViewerScripts     = true,
    IncludeAnnotationScripts = true,  // jquery-ui, raphael.js, annotation.js
    IncludeAnnotationBar     = true   // شريط التعليقات التوضيحية المدمج
});
ViewBag.IsAnnotationEnabled = viewer.IsAnnotationEnabled;

احتفظ بتكوين العارض الكامل في العلامات:

html
<nav id="toolbar" aria-label="Document viewer controls">
    <!-- عناصر تحكم العارض، بما في ذلك الزر الذي يفتح التعليقات التوضيحية -->
</nav>
<div id="annBarMount"></div>
<div id="divDocViewer"><div id="div_ctlDoc"></div></div>

تنشئ ملفات التعليقات التوضيحية الشريط داخل annBarMount؛ لا تحتاج إلى نسخ أزراره أو حواراته. قم بتهيئة docViewer أولاً، ثم أنشئ الشريط فقط عندما يؤكد الخادم أن التعليقات التوضيحية مرخصة:

html
@Html.Raw(ViewBag.ViewerScripts)
<script>
    var annBar = null;
    var currentToken = '';

    var objViewer = $('#div_ctlDoc').docViewer({
        BasePath: '/',
        ResPath:  'doconut-res/images',
        onAnnLoaded:    function () { if (annBar) annBar.handleAnnLoaded(); },
        onAnnSaved:     function () { if (annBar) annBar.handleAnnSaved(); },
        onAnnSaveError: function () { if (annBar) annBar.handleAnnSaveError(); },
        onAnnClosed:    function () { if (annBar) annBar.handleAnnClosed(); },
        onError:        function (message) { console.error('Viewer error:', message); }
    });

    @if ((bool)ViewBag.IsAnnotationEnabled)
    {
        <text>
    annBar = $('#annBarMount').doconutAnnotationBar({
        docId: 'ctlDoc',
        getRequestParams: function () { return { token: currentToken }; },
        onLayout: function () { requestAnimationFrame(function () { objViewer.Refit(); }); }
    });
    annBar.attach(objViewer);
        </text>
    }
</script>

بدون القدرة، تقوم ReferenceScripts بحذف سكريبت الشريط، لذا يحافظ الشرط على جانب الخادم if على استدعاء doconutAnnotationBar(...) غير المحمي من كسر الصفحة. في Web Forms، اكتب الحماية نفسها كـ <% if (IsAnnotationEnabled) { %> … <% } %>.

يقوم الحفظ من الشريط بإرسال البيانات إلى DocImage.axd (AnnSave)، التي تخزنها في جلسة الوثيقة لكل صفحة. التحميل (AnnLoad) يحدث تلقائيًا عندما يتم عرض صفحة تحتوي على تعليقات توضيحية.

افتح وأغلق الشريط من شريط الأدوات الخاص بك باستخدام annBar.open() و annBar.close(). واجهته العامة:

الطريقةالغرض
attach(objViewer)ربط الشريط بالعارض المهيأ؛ مطلوب مرة واحدة
open() / close()بدء أو إنهاء تحرير التعليقات التوضيحية
reset()إعادة الشريط إلى حالته المغلقة غير القابلة للتحرير
isOpen() / annotating()قراءة حالة الشريط / حالة تحرير التعليقات في العارض
reopenEditable()إعادة تحميل تعليقات الصفحة الحالية ككائنات قابلة للتحرير
updateActionState()تحديث حالة أدوات الحفظ والحذف بعد تغييرات المضيف
headerSlot()فتحة الرأس الاختيارية للتحكمات المملوكة للمضيف

onStatus، onToast، onLayout، onEditStart، و onEditEnd هي ردود نداء اختيارية من المضيف. يمكن لكائن endpoints أيضًا توفير exportPdf، exportPng، imageUpload، و imageList؛ العناصر التي لا تملك نقطة نهاية مُكوَّنة تظل مخفية.

نوعان من رد النداء، موصلان بطريقتين

يستخدم كود التعليقات التوضيحية نمطين من ردود النداء، وكل منهما يفشل بصمت عندما يتم توصيله بالطريقة الأخرى:

  • onAnnLoaded، onAnnSaved، onAnnSaveError، onAnnClosed هي خيارات في docViewer({...})، مثل كل ردود النداء للمشاهد.
  • Created، Changed، Deleted، وProperties هي دوال عامة يتم العثور عليها بالاسم: يتم إضافة معرف المشاهد كبادئة للحدث. بالنسبة للمشاهد في #div_ctlDoc يكون المعرف هو ctlDoc، لذا تكون الدوال ctlDoc_Created، ctlDoc_Changed، ctlDoc_Deleted، وctlDoc_Properties. إذا كان هناك خطأ إملائي أو تم إعادة تسمية الدالة فإنها ببساطة لا تُستدعى أبداً.

SaveAnnotations() لا تُعيد ما إذا تم الحفظ أم لا: النتيجة تصل عبر onAnnSaved أو onAnnSaveError. الكود الذي يقرأ قيمة إرجاعها لا يزيل علم "التغييرات غير المحفوظة" أبداً.

بناء التعليقات التوضيحية في C#

احصل على مدير مرتبط بالجلسة المفتوحة، أضف التعليقات التوضيحية، وحمّلها (باستخدام using Doconut.Annotations; للأنواع و using System.Drawing; لـ Rectangle و Color):

csharp
// إرسل POST إلى /Annotations/LoadSample?token=...
[HttpPost]
public ActionResult LoadSample(string token)
{
    var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();

    // مرتبط بأبعاد صفحات الجلسة المفتوحة
    var manager   = viewer.GetAnnotationManager(token);
    var pageCount = viewer.GetPageCount(token);

    // طابع واحد لكل صفحة
    for (int page = 1; page <= pageCount; page++)
    {
        manager.Add(new StampAnnotation(page, new Rectangle(30, 20, 240, 90),
            $"PAGE {page}", 28, 4, Color.Maroon)
        {
            Opacity = 60,
            Rotate  = -8
        });
    }

    manager.Add(new NoteAnnotation(1, new Rectangle(420, 150, 220, 120),
        "Loaded from C# code.", Color.FromArgb(255, 255, 255, 170), 14));

    // تحميل إلى الجلسة - المشاهد يجلبها، والعارض يدمجها في
    // تصديرات صورة/PDF.
    viewer.LoadAnnotationData(token, manager);
    return new HttpStatusCodeResult(200);
}

أنواع التعليقات التوضيحية

جميع الأنواع موجودة في Doconut.Annotations وتورث من BaseAnnotation (رقم الصفحة بالإضافة إلى Rectangle المحدد):

النوعملاحظات
StampAnnotationختم نصي مع حجم الخط، الحد، اللون؛ يدعم Opacity، Rotate
NoteAnnotationملاحظة لاصقة مع نص، لون خلفية، حجم الخط، TitleColor
RectangleAnnotationألوان الحد والملء، Title/ShowTitle
CircleAnnotationحد وملء، ShowBorder
EllipseAnnotationحد وملء، ShowBorder
TriangleAnnotationلون الحد، BackColor، ShowBorder
LineAnnotationخط مستقيم مع عرض ولون
ArrowAnnotationخط مع رأس سهم؛ يمكن ضبط Direction (ArrowDirection، نقاط البوصلة، الافتراضي E)
FreehandAnnotationضربة حرة من نقاط FreehandData المشفرة
ImageAnnotationصورة من URL. يتم حل URL نسبي بالنسبة لمضيف الطلب الحالي عندما تُضاف التعليقة — انظر ملاحظة التصدير أدناه

واجهة برمجة تطبيقات AnnotationManager

العضوالغرض
Add(BaseAnnotation)إضافة تعليق توضيحي إلى الطابور
GetAnnotations() / GetAnnotations(int page)فحص ما يحتفظ به المدير
ClearAnnotations() / ClearAnnotations(int page)إزالة جميعها / لكل صفحة
GetAnnotationData() / GetAnnotationData(int page)بيانات التعليقات المشفرة — الغلاف الذي يستهلكه الودجت
GetAnnotationXml()صيغة XML

Viewer يعكس عمليات التحميل والقراءة مقابل جلسة: LoadAnnotationData(token, manager) أو LoadAnnotationData(token, encodedData)، LoadAnnotationXML(token, xml)، و GetAnnotationXML(token).

تصدير مع دمج التعليقات التوضيحية

csharp
// PDF لجميع الصفحات مع التعليقات التوضيحية المرسومة عليها
public async Task<ActionResult> ExportPdf(string token)
{
    var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();
    byte[] pdf = await viewer.ExportAnnotationsToPdfAsync(token, zoom: 100, ct: Response.ClientDisconnectedToken);
    return File(pdf, "application/pdf", "export.pdf");
}

// أو ملف ZIP لصور PNG لكل صفحة
public async Task<ActionResult> ExportPngZip(string token)
{
    var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();
    byte[] zip = await viewer.ExportAnnotationsToPngZipAsync(token, zoom: 100, ct: Response.ClientDisconnectedToken);
    return File(zip, "application/zip", "annotations-png.zip");
}

تستخدم عمليات التصدير نفس أداة الدمج كما في العرض على الشاشة، ونفس الترخيص وقرار العلامة المائية المخصصة.

توضيحات الصورة على مضيف إنترانت تُصدّر كـ "Image blocked"

العارض وعملية التصدير يجلبان صورة ImageAnnotation من أماكن مختلفة:

السطحمن يقوم بتحميل الصورةعلى مضيف إنترانت أو تطوير
العارض في المتصفحمتصفح المستخدمتظهر الصورة
ExportAnnotationsToPdfAsync / …PngZipAsyncالخادمعنصر نائب أحمر "Image blocked"

عند قيام الخادم بجلب صورة لإدماجها، يرفض العناوين التي تعيد إلى الشبكات الخاصة: حلقة رجوع (localhost, 127.0.0.1), 0.0.0.0, رابط‑محلي (169.254.x.x), النطاقات الخاصة 10.x, 172.16–31.x, و 192.168.x, NAT من نوع الناقل (100.64.0.0/10), وعناوين IPv6 رابط‑محلية وفريدة‑محلية. هذا يحمي الخادم من أن يُستَخدم للوصول إلى الأنظمة الداخلية عبر عنوان صورة مُصمم، ولا يوجد مفتاح لإيقاف هذا السلوك.

عنوان URL نسبي للصورة يُحلّ ضد مضيف الطلب الحالي، لذا على localhost، أو على خادم إنترانت، أو على أي موقع يُوصل عبر عنوان خاص، تُظهر الصورة في العارض وتحتوي عملية التصدير على العنصر النائب بدلاً من ذلك. هذا هو الحماية تعمل، وليس فشلًا في العرض. إذا كان يجب أن تحتوي الصادرات على الصورة، قدّمها من مضيف له عنوان عام واستخدم عنوان URL مطلق. عنوان URL للصورة لا يمكن حله نهائيًا يجعل ذلك التوضيح يختفي دون خطأ — تحقق من البكسلات المُرسَمة أو سجل الشبكة، وليس من رمز الحالة.

سير عمل الحفظ

  1. افتح المستند واحصل على رمزه.
  2. حمّل XML أو البيانات المشفرة المخزنة مسبقًا إلى ذلك الرمز.
  3. اسمح للأداة بقراءة وتحرير توضيحات الجلسة.
  4. استرجع XML باستخدام GetAnnotationXML(token) عندما يقرر تطبيقك حفظ البيانات.
  5. صدّر PDF أو PNG عندما يكون مطلوبًا ملف مسطح.
  6. أغلق جلسة المستند.

لا تستخدم رمز العارض كمعرّف دائم للتوضيح — فهو ينتهي مع الجلسة، وإعادة تدوير مجموعة التطبيقات تُنهي كل الجلسات على خادم واحد. احفظ بيانات التوضيح مقابل مستندك ومعرفات الإصدار الخاصة بك.

ملاحظات الأمان والعرض

  • طلبات التوضيح تستخدم نفس أمان الجلسة والرمز مثل طلبات الصفحة.
  • تحقق من صحة أي عنوان URL لصورة مقدم من المستخدم وتحكم فيه.
  • الأحمال الكبيرة للرسومات الحرة والتصديرات عالية الدقة تستهلك المزيد من الذاكرة؛ اختبر مستندات واقعية وقيم التكبير.

استكشاف الأخطاء وإصلاحها

العَرَضالتحقق
شريط التوضيح مفقودخاصية Annotation وأعلام CSS/سكريبت الأربعة للتوضيح
الحفظ يُظهر خطأانتهاء صلاحية الرمز أو الجلسة، ومسار BasePath الخاص بالأداة
ctlDoc_Created (أو دالة حدث أخرى) لا تُنفّذ أبدًااسم الدالة: معرف العارض + _ + الحدث، كدالة عامة
توضيحات C# لا تظهرأرقام الصفحات تبدأ من 1، وتم تحميل البيانات إلى الرمز النشط
توضيح صورة يظهر على الشاشة لكنه غير موجود في التصديرمضيف الصورة لديه عنوان خاص أو حلقة رجوع — راجع أعلاه
المستند المفتوح مرة أخرى لا يحتوي على توضيحاتاحفظ XML أو البيانات خارج جلسة العارض، ثم حمّلها إلى الرمز الجديد

هل كانت هذه الصفحة مفيدة؟