حاشیهنویسیها
پشتیبانی از حاشیهنویسی را به نمایشگر اضافه کنید
حاشیهنویسیها در Doconut به دو جهت کار میکنند: کاربران آنها را در ویجت مرورگر میکشند و سرور آنها را برای هر صفحه نگه میدارد، یا کد شما آنها را میسازد و در یک جلسه باز بارگذاری میکند. به هر حال، این حاشیهنویسیها بر روی صفحات رندر میشوند و میتوانند در خروجیهای PDF یا PNG سوزانده شوند.
پشتیبانی از حاشیهنویسی توسط قابلیت مجوز Annotation محدود شده است (بهصورت خودکار تحت یک مجوز موقت فعال اعطا میشود).
فعالسازی رابط کاربری حاشیهنویسی
حاشیهنویسی یک ماژول Viewer است، نه یک نوار ابزار مستقل. صفحه باید منابع Viewer، نوار ابزار Viewer، محل نصب Viewer و یک objViewer مقداردهی اولیه شده را شامل شود؛ سپس نوار Ribbon حاشیهنویسی نصب و به همان نمونه متصل میشود.
فایلهای حاشیهنویسی را همراه با فایلهای Viewer صادر کنید. این فایلها تحت مجوز هستند، بنابراین برچسبهای آنها فقط زمانی ظاهر میشوند که قابلیت مجوز داده شده باشد. در MVC 5، برچسبها را در کنترلر بسازید (نمایها @inject ندارند):
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 را در مارکآپ نگه دارید:
<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.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):
// 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).
خروجی با حاشیهنویسیهای سوخته
// 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 تصویر که بهکل قابل حل نیست، باعث میشود آن حاشیهنویسی بدون خطا ناپدید شود — پیکسلهای رندر شده یا لاگ شبکه را بررسی کنید، نه کد وضعیت.
جریان کار پایداری
- سند را باز کنید و توکن آن را دریافت کنید.
- XML یا دادههای رمزگذاریشده قبلی را در آن توکن بارگذاری کنید.
- اجازه دهید ویجت حاشیهنویسیهای جلسه را بخواند و ویرایش کند.
- هنگام تصمیم برنامه برای ذخیره، XML را با
GetAnnotationXML(token)بازیابی کنید. - در صورت نیاز به تحویل مسطح، PDF یا PNG را خروجی بگیرید.
- جلسه سند را ببندید.
از توکن Viewer بهعنوان شناسه دائمی حاشیهنویسی استفاده نکنید — با پایان جلسه از بین میرود و بازسازی Application Pool تمام جلسات را روی یک سرور خاتمه میدهد. دادههای حاشیهنویسی را در مقابل شناسههای سند و نسخه خود ذخیره کنید.
نکات امنیتی و رندر
- درخواستهای حاشیهنویسی از همان امنیت جلسه و توکن همانند درخواستهای صفحه استفاده میکنند.
- هر URL تصویری که توسط کاربر ارائه میشود را اعتبارسنجی و کنترل کنید.
- بارهای بزرگ دستنویس و خروجیهای با وضوح بالا مصرف حافظه را افزایش میدهند؛ اسناد واقعی و مقادیر زوم را تست کنید.
عیبیابی
| علامت | بررسی |
|---|---|
| نوار Ribbon حاشیهنویسی موجود نیست | قابلیت Annotation و چهار پرچم CSS/اسکریپت حاشیهنویسی |
| ذخیرهسازی خطا میدهد | انقضای توکن یا جلسه، و BasePath ویجت |
ctlDoc_Created (یا تابع رویداد دیگر) اجرا نمیشود | نام تابع: شناسه viewer + _ + نام رویداد، بهصورت تابع سراسری |
| حاشیهنویسیهای C# نمایش داده نمیشوند | شماره صفحات از ۱ شروع میشود و دادهها در توکن فعال بارگذاری شدهاند |
| یک حاشیهنویسی تصویر روی صفحه نشان داده میشود اما در خروجی نیست | میزبان تصویر دارای آدرس خصوصی یا loopback است — به بخش بالا مراجعه کنید |
| سندی که دوباره باز شده هیچ حاشیهنویسیای ندارد | XML یا دادهها را خارج از جلسه Viewer ذخیره کنید، سپس در توکن جدید بارگذاری کنید |
آیا این صفحه مفید بود؟