הערות

הוספת תמיכה בהערות לצופה

הערות ב‑Doconut פועלות בשני כיוונים: משתמשים מציירים אותן בווידג'ט של הדפדפן והשרת שומר אותן לכל עמוד, או שהקוד שלך בונה אותן וטוען אותן למפגש פתוח. בכל מקרה הן מוצגות בעמודים וניתן לשלבן בייצוא PDF או PNG.

תמיכת ההערות מוגבלת על‑ידי יכולת הרישיון Annotation (ניתנת אוטומטית תחת רישיון Temporary פעיל).

הפעלת ממשק המשתמש של ההערות

הערה היא מודול של Viewer, ולא סרגל כלים עצמאי. העמוד חייב לכלול את משאבי ה‑Viewer, סרגל הכלים של ה‑Viewer, את מיקום ה‑Viewer, ו‑objViewer מאותחל; לאחר מכן רצועת ההערות מותקנת ומצורפת לאותו מופע.

הפץ את קבצי ההערות יחד עם קבצי ה‑viewer. הם מוגבלים ברישיון, ולכן התגים שלהם מופיעים רק כאשר היכולת ניתנת. ב‑MVC 5, בנה את התגים בבקר (ל‑views אין @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>

קבצי ההערות מייצרים את הרצועה בתוך 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(). ה‑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):

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 יחסית נפתרת ביחס למארח הבקשה הנוכחית כאשר מוסיפים את ההערה — ראו את ההערה על הייצוא למטה

ממשק ה‑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).

ייצוא עם ההערות משולבות בתמונה

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"

הצופה והייצוא משיגים את תמונת ה‑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 של תמונה שלא ניתנת לפתרון כלל גורמת להסבר אחד להיעלם ללא שגיאה — בדקו את הפיקסלים המוצגים או את יומן הרשת, ולא קוד סטטוס.

תהליך שמירה

  1. פתחו את המסמך וקבלו את האסימון שלו.
  2. טענו XML או נתונים מקודדים שנשמרו מראש לתוך האסימון.
  3. אפשרו ל‑widget לקרוא ולערוך את ההסברים של ההפעלה.
  4. קבלו XML עם GetAnnotationXML(token) כאשר היישום שלכם מחליט לשמור.
  5. ייצאו PDF או PNG כאשר נדרש קובץ משטחים.
  6. סגרו את הפעלת המסמך.

אל תשתמשו באסימון הצופה כמזהה קבוע של ההסבר — הוא מת יחד עם ההפעלה, והמחזור של מאגר היישום מסיים כל הפעלה בשרת יחיד. שמרו את נתוני ההסברים מול מזהי המסמך והגרסה שלכם.

הערות אבטחה והצגה

  • בקשות ההסבר משתמשות באותה אבטחת הפעלה ואסימון כמו בקשות דף.
  • אמתו ושלטו בכל כתובת URL של תמונה שמסופקת על ידי משתמש.
  • מטענים חופשיים גדולים וייצוא ברזולוציה גבוהה מגבירים את השימוש בזיכרון; בדקו מסמכים ריאליים וערכי זום.

פתרון בעיות

סימפטוםבדיקה
סרגל ההסברים חסריכולת Annotation והארבעה דגלי CSS/סקריפט של ההסברים
שמירה מדווחת על שגיאהתוקף האסימון או ההפעלה, ו‑BasePath של ה‑widget
ctlDoc_Created (או פונקציית אירוע אחרת) לעולם לא רצהשם הפונקציה: מזהה הצופה + _ + אירוע, כפונקציה גלובלית
ההסברים ב‑C# אינם מופיעיםמספרי העמודים מתחילים מ‑1, והנתונים נטענו לאסימון הפעיל
הסבר תמונה מוצג על המסך אך לא בייצואלמארח התמונה יש כתובת פרטית או לולאה — ראו למעלה
מסמך שנפתח מחדש אינו מכיל הסבריםשמרו XML או נתונים מחוץ להפעלת הצופה, ואז טענו אותם לאסימון החדש

האם דף זה היה מועיל?