حاشیه‌نویسی‌ها

پشتیبانی از حاشیه‌نویسی را به نمایشگر اضافه کنید

حاشیه‌نویسی‌ها در Doconut به دو جهت کار می‌کنند: کاربران آنها را در ویجت مرورگر می‌کشند و سرور آنها را برای هر صفحه نگه می‌دارد، یا کد شما آنها را می‌سازد و در یک جلسه باز بارگذاری می‌کند. به هر حال، این حاشیه‌نویسی‌ها بر روی صفحات رندر می‌شوند و می‌توانند در خروجی‌های PDF یا PNG سوزانده شوند.

پشتیبانی از حاشیه‌نویسی توسط قابلیت مجوز Annotation محدود شده است (به‌صورت خودکار تحت یک مجوز موقت فعال اعطا می‌شود).

فعال‌سازی رابط کاربری حاشیه‌نویسی

حاشیه‌نویسی یک ماژول Viewer است، نه یک نوار ابزار مستقل. صفحه باید منابع Viewer، نوار ابزار Viewer، محل نصب Viewer و یک objViewer مقداردهی اولیه شده را شامل شود؛ سپس نوار Ribbon حاشیه‌نویسی نصب و به همان نمونه متصل می‌شود.

فایل‌های حاشیه‌نویسی را همراه با فایل‌های Viewer صادر کنید. این فایل‌ها تحت مجوز هستند، بنابراین برچسب‌های آنها فقط زمانی ظاهر می‌شوند که قابلیت مجوز داده شده باشد. در 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   // the embedded annotation ribbon
});
ViewBag.IsAnnotationEnabled = viewer.IsAnnotationEnabled;

ترکیب کامل Viewer را در مارک‌آپ نگه دارید:

html
<nav id="toolbar" aria-label="Document viewer controls">
    <!-- Viewer controls, including the button that opens Annotation -->
</nav>
<div id="annBarMount"></div>
<div id="divDocViewer"><div id="div_ctlDoc"></div></div>

فایل‌های حاشیه‌نویسی نوار Ribbon را داخل annBarMount تولید می‌کنند؛ شما نباید دکمه‌ها یا دیالوگ‌های آن را کپی کنید. ابتدا docViewer را مقداردهی اولیه کنید، سپس نوار Ribbon را فقط زمانی ایجاد کنید که سرور تأیید کند حاشیه‌نویسی مجوز دارد:

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 اسکریپت نوار Ribbon را حذف می‌کند، بنابراین شرط سمت سرور if از فراخوانی بدون محافظ doconutAnnotationBar(...) که ممکن است صفحه را خراب کند، جلوگیری می‌کند. در Web Forms، همان محافظ را به صورت <% if (IsAnnotationEnabled) { %> … <% } %> بنویسید.

ذخیره‌سازی از نوار Ribbon داده‌ها را به DocImage.axd (AnnSave) ارسال می‌کند، که آنها را در جلسه سند برای هر صفحه ذخیره می‌کند. بارگذاری (AnnLoad) به‌صورت خودکار زمانی رخ می‌دهد که صفحه‌ای با حاشیه‌نویسی‌ها رندر شود.

نوار Ribbon را از نوار ابزار خود با annBar.open() و annBar.close() باز و بسته کنید. API عمومی آن:

متدهدف
attach(objViewer)متصل کردن نوار Ribbon به Viewer مقداردهی اولیه شده؛ یک بار مورد نیاز
open() / close()ورود یا خروج از ویرایش حاشیه‌نویسی
reset()بازگرداندن نوار Ribbon به حالت بسته و غیر ویرایشی
isOpen() / annotating()خواندن وضعیت نوار Ribbon / وضعیت ویرایش حاشیه‌نویسی Viewer
reopenEditable()بارگذاری مجدد حاشیه‌نویسی‌های صفحه فعلی به‌عنوان اشیای قابل ویرایش
updateActionState()به‌روزرسانی کنترل‌های ذخیره و حذف پس از تغییرات میزبان
headerSlot()اسلات سرصفحه اختیاری برای کنترل‌های متعلق به میزبان

onStatus، onToast، onLayout، onEditStart و onEditEnd کال‌بک‌های اختیاری میزبان هستند. شیء endpoints می‌تواند همچنین exportPdf، exportPng، imageUpload و imageList را فراهم کند؛ کنترل‌هایی که نقطه انتهایی پیکربندی نشده دارند، مخفی می‌مانند.

دو نوع فراخوانی (callback)، به دو روش متصل شده‌اند

کد حاشیه‌نویسی از دو سبک فراخوانی استفاده می‌کند و هر کدام هنگام متصل شدن به روش دیگر به‌صورت ساکت شکست می‌خورند:

  • 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>();

    // Bound to the open session's page dimensions
    var manager   = viewer.GetAnnotationManager(token);
    var pageCount = viewer.GetPageCount(token);

    // One stamp per page
    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));

    // Load into the session - the viewer fetches them, and the renderer burns them into
    // image/PDF exports.
    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 relative هنگام افزودن حاشیه‌نویسی نسبت به میزبان درخواست جاری حل می‌شود — به یادداشت خروجی زیر مراجعه کنید

API مدیر حاشیه‌نویسی (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 of all pages with annotations rendered onto them
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");
}

// Or a ZIP of per-page PNGs
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»

Viewer و فرآیند خروجی تصویر ImageAnnotation را از مکان‌های متفاوتی دریافت می‌کنند:

سطحچه کسی تصویر را دانلود می‌کنددر یک میزبان اینترانت یا توسعه
Viewer در مرورگرمرورگر کاربرتصویر نمایش داده می‌شود
ExportAnnotationsToPdfAsync / …PngZipAsyncسروریک جایگزین قرمز "Image blocked"

زمانی که سرور تصویری را برای درج دریافت می‌کند، آدرس‌هایی که به شبکه‌های خصوصی باز می‌گردند را رد می‌کند: loopback (localhost, 127.0.0.1)، 0.0.0.0، link‑local (169.254.x.x)، محدوده‌های خصوصی 10.x، 172.16–31.x، و 192.168.x، NAT سطح حامل (100.64.0.0/10)، و آدرس‌های link‑local و unique‑local IPv6. این کار سرور را از استفاده برای دسترسی به سیستم‌های داخلی از طریق یک URL تصویر دست‌ساخت محافظت می‌کند و هیچ سوئیچی برای غیرفعال کردن این رفتار وجود ندارد.

یک URL تصویر نسبی نسبت به میزبان درخواست جاری حل می‌شود، بنابراین روی localhost، روی یک سرور اینترانت، یا روی هر سایتی که از طریق یک آدرس خصوصی دسترسی دارد، تصویر در Viewer نشان داده می‌شود و خروجی به جای آن جایگزین قرمز را شامل می‌شود. این رفتار محافظتی است، نه یک خطای رندر. اگر خروجی‌ها باید تصویر را داشته باشند، آن را از یک میزبان با آدرس عمومی سرو کنید و از یک URL مطلق استفاده کنید. یک URL تصویر که به‌کل قابل حل نیست، باعث می‌شود آن حاشیه‌نویسی بدون خطا ناپدید شود — پیکسل‌های رندر شده یا لاگ شبکه را بررسی کنید، نه کد وضعیت.

جریان کار پایداری

  1. سند را باز کنید و توکن آن را دریافت کنید.
  2. XML یا داده‌های رمزگذاری‌شده قبلی را در آن توکن بارگذاری کنید.
  3. اجازه دهید ویجت حاشیه‌نویسی‌های جلسه را بخواند و ویرایش کند.
  4. هنگام تصمیم برنامه برای ذخیره، XML را با GetAnnotationXML(token) بازیابی کنید.
  5. در صورت نیاز به تحویل مسطح، PDF یا PNG را خروجی بگیرید.
  6. جلسه سند را ببندید.

از توکن Viewer به‌عنوان شناسه دائمی حاشیه‌نویسی استفاده نکنید — با پایان جلسه از بین می‌رود و بازسازی Application Pool تمام جلسات را روی یک سرور خاتمه می‌دهد. داده‌های حاشیه‌نویسی را در مقابل شناسه‌های سند و نسخه خود ذخیره کنید.

نکات امنیتی و رندر

  • درخواست‌های حاشیه‌نویسی از همان امنیت جلسه و توکن همانند درخواست‌های صفحه استفاده می‌کنند.
  • هر URL تصویری که توسط کاربر ارائه می‌شود را اعتبارسنجی و کنترل کنید.
  • بارهای بزرگ دست‌نویس و خروجی‌های با وضوح بالا مصرف حافظه را افزایش می‌دهند؛ اسناد واقعی و مقادیر زوم را تست کنید.

عیب‌یابی

علامتبررسی
نوار Ribbon حاشیه‌نویسی موجود نیستقابلیت Annotation و چهار پرچم CSS/اسکریپت حاشیه‌نویسی
ذخیره‌سازی خطا می‌دهدانقضای توکن یا جلسه، و BasePath ویجت
ctlDoc_Created (یا تابع رویداد دیگر) اجرا نمی‌شودنام تابع: شناسه viewer + _ + نام رویداد، به‌صورت تابع سراسری
حاشیه‌نویسی‌های C# نمایش داده نمی‌شوندشماره صفحات از ۱ شروع می‌شود و داده‌ها در توکن فعال بارگذاری شده‌اند
یک حاشیه‌نویسی تصویر روی صفحه نشان داده می‌شود اما در خروجی نیستمیزبان تصویر دارای آدرس خصوصی یا loopback است — به بخش بالا مراجعه کنید
سندی که دوباره باز شده هیچ حاشیه‌نویسی‌ای نداردXML یا داده‌ها را خارج از جلسه Viewer ذخیره کنید، سپس در توکن جدید بارگذاری کنید

آیا این صفحه مفید بود؟