הערות
הוספת תמיכה בהערות לצופה
הערות ב‑Doconut פועלות בשני כיוונים: משתמשים מציירים אותן בווידג'ט של הדפדפן והשרת שומר אותן לכל עמוד, או שהקוד שלך בונה אותן וטוען אותן למפגש פתוח. בכל מקרה הן מוצגות בעמודים וניתן לשלבן בייצוא PDF או PNG.
תמיכת ההערות מוגבלת על‑ידי יכולת הרישיון Annotation (ניתנת אוטומטית תחת רישיון Temporary פעיל).
הפעלת ממשק המשתמש של ההערות
הערה היא מודול של Viewer, ולא סרגל כלים עצמאי. העמוד חייב לכלול את משאבי ה‑Viewer, סרגל הכלים של ה‑Viewer, את מיקום ה‑Viewer, ו‑objViewer מאותחל; לאחר מכן רצועת ההערות מותקנת ומצורפת לאותו מופע.
הפץ את קבצי ההערות יחד עם קבצי ה‑viewer. הם מוגבלים ברישיון, ולכן התגים שלהם מופיעים רק כאשר היכולת ניתנת. ב‑MVC 5, בנה את התגים בבקר (ל‑views אין @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>קבצי ההערות מייצרים את הרצועה בתוך annBarMount; אין צורך להעתיק את הכפתורים או הדיאלוגים שלה. אתחל תחילה את docViewer, ולאחר מכן צור את הרצועה רק כאשר השרת מאשר שההערה מורשית:
@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(). ה‑API הציבורי שלה:
| מתודה | מטרה |
|---|---|
attach(objViewer) | מחבר את הרצועה לצופה המאותחל; נדרש פעם אחת |
open() / close() | כניסה או יציאה ממצב עריכת ההערות |
reset() | מחזיר את הרצועה למצב סגור, ללא עריכה |
isOpen() / annotating() | קרא את מצב הרצועה / את מצב עריכת ההערות של הצופה |
reopenEditable() | טען מחדש את ההערות של העמוד הנוכחי כאובייקטים ניתנים לעריכה |
updateActionState() | מרענן את פקדי השמירה והמחיקה לאחר שינויי המארח |
headerSlot() | מקום הכותרת האופציונלי לבקרות שבבעלות המארח |
onStatus, onToast, onLayout, onEditStart ו‑onEditEnd הם callbacks אופציונליים של המארח. האובייקט endpoints יכול גם לספק exportPdf, exportPng, imageUpload, ו‑imageList; בקרות ללא נקודת קצה מוגדרת נשארות מוסתרות.
שני סוגי callback, מחוברים בשתי דרכים
קוד ההערות משתמש בשני סגנונות של callback, וכל אחד מהם נכשל בשקט כאשר מחובר בצורה ההפוכה:
onAnnLoaded,onAnnSaved,onAnnSaveError,onAnnClosedהם אפשרויות שלdocViewer({...}), כמו כל callback של מציג.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 יחסית נפתרת ביחס למארח הבקשה הנוכחית כאשר מוסיפים את ההערה — ראו את ההערה על הייצוא למטה |
ממשק ה‑AnnotationManager API
| חבר | מטרה |
|---|---|
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"
הצופה והייצוא משיגים את תמונת ה‑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 של תמונה מעוצב, ואין אפשרות לבטל זאת.
כתובת URL של תמונה יחסית נפתרת יחסית למארח של הבקשה הנוכחית, ולכן ב‑localhost, בשרת אינטרנט פנימי, או בכל אתר שנגיש דרך כתובת פרטית, התמונה מוצגת בצופה והייצוא מכיל את מציין המקום במקום זאת. זהו המנגנון הפועל, ולא כשל בתצוגה. אם הייצוא חייב לכלול את התמונה, יש לספק אותה ממארח עם כתובת ציבורית ולהשתמש ב‑URL מוחלט. כתובת URL של תמונה שלא ניתנת לפתרון כלל גורמת להסבר אחד להיעלם ללא שגיאה — בדקו את הפיקסלים המוצגים או את יומן הרשת, ולא קוד סטטוס.
תהליך שמירה
- פתחו את המסמך וקבלו את האסימון שלו.
- טענו XML או נתונים מקודדים שנשמרו מראש לתוך האסימון.
- אפשרו ל‑widget לקרוא ולערוך את ההסברים של ההפעלה.
- קבלו XML עם
GetAnnotationXML(token)כאשר היישום שלכם מחליט לשמור. - ייצאו PDF או PNG כאשר נדרש קובץ משטחים.
- סגרו את הפעלת המסמך.
אל תשתמשו באסימון הצופה כמזהה קבוע של ההסבר — הוא מת יחד עם ההפעלה, והמחזור של מאגר היישום מסיים כל הפעלה בשרת יחיד. שמרו את נתוני ההסברים מול מזהי המסמך והגרסה שלכם.
הערות אבטחה והצגה
- בקשות ההסבר משתמשות באותה אבטחת הפעלה ואסימון כמו בקשות דף.
- אמתו ושלטו בכל כתובת URL של תמונה שמסופקת על ידי משתמש.
- מטענים חופשיים גדולים וייצוא ברזולוציה גבוהה מגבירים את השימוש בזיכרון; בדקו מסמכים ריאליים וערכי זום.
פתרון בעיות
| סימפטום | בדיקה |
|---|---|
| סרגל ההסברים חסר | יכולת Annotation והארבעה דגלי CSS/סקריפט של ההסברים |
| שמירה מדווחת על שגיאה | תוקף האסימון או ההפעלה, ו‑BasePath של ה‑widget |
ctlDoc_Created (או פונקציית אירוע אחרת) לעולם לא רצה | שם הפונקציה: מזהה הצופה + _ + אירוע, כפונקציה גלובלית |
| ההסברים ב‑C# אינם מופיעים | מספרי העמודים מתחילים מ‑1, והנתונים נטענו לאסימון הפעיל |
| הסבר תמונה מוצג על המסך אך לא בייצוא | למארח התמונה יש כתובת פרטית או לולאה — ראו למעלה |
| מסמך שנפתח מחדש אינו מכיל הסברים | שמרו XML או נתונים מחוץ להפעלת הצופה, ואז טענו אותם לאסימון החדש |
האם דף זה היה מועיל?