Σχόλια

Προσθήκη υποστήριξης σχολίων στον προβολέα

Οι Σχόλια στο Doconut λειτουργούν σε δύο κατευθύνσεις: οι χρήστες τα σχεδιάζουν στο widget του προγράμματος περιήγησης και ο διακομιστής τα αποθηκεύει ανά σελίδα, ή ο κώδικάς σας τα δημιουργεί και τα φορτώνει σε μια ανοιχτή συνεδρία. Σε κάθε περίπτωση εμφανίζονται στις σελίδες και μπορούν να ενσωματωθούν σε εξαγωγές PDF ή PNG.

Η υποστήριξη Σχολίων ελέγχεται από τη δυνατότητα άδειας Annotation (χορηγείται αυτόματα με ενεργή προσωρινή άδεια).

Ενεργοποίηση του UI σχολίων

Το Annotation είναι ένα module του Viewer, όχι μια αυτόνομη γραμμή εργαλείων. Η σελίδα πρέπει να περιλαμβάνει τους πόρους του Viewer, τη γραμμή εργαλείων του Viewer, το mount του Viewer και ένα αρχικοποιημένο objViewer; το Annotation 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 στο markup:

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>

Τα αρχεία Annotation δημιουργούν το Ribbon μέσα στο annBarMount; δεν χρειάζεται να αντιγράψετε τα κουμπιά ή τα διαλόγους του. Αρχικοποιήστε πρώτα το docViewer, έπειτα δημιουργήστε το Ribbon μόνο όταν ο διακομιστής επιβεβαιώσει ότι το Annotation είναι αδειοδοτημένο:

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 παραλείπει το script του 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; τα ελεγκτικά χωρίς ρυθμισμένο endpoint παραμένουν κρυμμένα.

Δύο είδη 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):

TypeΣημειώσεις
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 λύνει σε σχέση με τον τρέχοντα κεντρικό υπολογιστή του αιτήματος όταν προστίθεται το σχόλιο — δείτε την παρατήρηση εξαγωγής παρακάτω

Το API του AnnotationManager

ΜέλοςΣκοπός
Add(BaseAnnotation)Προσθέτει ένα σχόλιο στην ουρά
GetAnnotations() / GetAnnotations(int page)Επιθεωρεί τι κρατά ο διαχειριστής
ClearAnnotations() / ClearAnnotations(int page)Αφαιρεί όλα / ανά σελίδα
GetAnnotationData() / GetAnnotationData(int page)Κωδικοποιημένα δεδομένα σχολίων — το περιτύλιγμα που καταναλώνει το widget
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");
}

Οι εξαγωγές χρησιμοποιούν τον ίδιο μηχανισμό ενσωμάτωσης με την απόδοση στην οθόνη, καθώς και την ίδια άδεια και την ίδια απόφαση για προσαρμοσμένο υδατογράφημα.

Σχόλια εικόνας σε εσωτερικό διακομιστή εξάγονται ως "Εικόνα αποκλεισμένη"

Ο προβολέας και η εξαγωγή λαμβάνουν την εικόνα ενός ImageAnnotation από διαφορετικές πηγές:

ΕπιδερμίδαΠοιος κατεβάζει την εικόναΣε εσωτερικό ή διακομιστή ανάπτυξης
Ο προβολέας στο πρόγραμμα περιήγησηςΤο πρόγραμμα περιήγησης του χρήστηΗ εικόνα εμφανίζεται
ExportAnnotationsToPdfAsync / …PngZipAsyncΟ διακομιστήςΈνας κόκκινος "Εικόνα αποκλεισμένη" δείκτης

Όταν ο διακομιστής ανακτά μια εικόνα για ενσωμάτωση, απορρίπτει διευθύνσεις που οδηγούν πίσω σε ιδιωτικά δίκτυα: 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, carrier‑grade NAT (100.64.0.0/10) και τις IPv6 link‑local και unique‑local διευθύνσεις. Αυτό προστατεύει τον διακομιστή από το να χρησιμοποιηθεί για πρόσβαση σε εσωτερικά συστήματα μέσω μιας προσαρμοσμένης URL εικόνας, και δεν υπάρχει επιλογή για απενεργοποίηση.

Μια σχετική URL εικόνας επιλύεται σε σχέση με τον κεντρικό υπολογιστή του τρέχοντος αιτήματος, έτσι ώστε σε localhost, σε εσωτερικό διακομιστή ή σε οποιονδήποτε ιστότοπο που προσπελάζεται μέσω ιδιωτικής διεύθυνσης, η εικόνα εμφανίζεται στον προβολέα και η εξαγωγή περιέχει το δείκτη αντί για αυτήν. Αυτό είναι η λειτουργία της προστασίας, όχι αποτυχία απόδοσης. Εάν οι εξαγωγές πρέπει να περιλαμβάνουν την εικόνα, σερβίρετέ την από έναν κεντρικό υπολογιστή με δημόσια διεύθυνση και χρησιμοποιήστε απόλυτη URL. Μια URL εικόνας που δεν μπορεί καθόλου να επιλυθεί κάνει αυτήν τη σημείωση να εξαφανιστεί χωρίς σφάλμα — ελέγξτε τα αποδομένα pixel ή το αρχείο καταγραφής δικτύου, όχι τον κωδικό κατάστασης.

Ροή διατήρησης

  1. Ανοίξτε το έγγραφο και αποκτήστε το διακριτικό του.
  2. Φορτώστε το προηγουμένως αποθηκευμένο XML ή κωδικοποιημένα δεδομένα στο διακριτικό αυτό.
  3. Επιτρέψτε στο widget να διαβάσει και να επεξεργαστεί τις σημειώσεις της συνεδρίας.
  4. Ανακτήστε το XML με GetAnnotationXML(token) όταν η εφαρμογή σας αποφασίσει να αποθηκεύσει.
  5. Εξάγετε PDF ή PNG όταν απαιτείται ένα επίπεδο παραδοτέο.
  6. Κλείστε τη συνεδρία του εγγράφου.

Μην χρησιμοποιείτε το διακριτικό του προβολέα ως μόνιμο αναγνωριστικό σημείωσης — λήγει με τη συνεδρία, και η επαναφορά της ομάδας εφαρμογών τερματίζει κάθε συνεδρία σε έναν μοναδικό διακομιστή. Αποθηκεύστε τα δεδομένα σημειώσεων σε σχέση με τα δικά σας αναγνωριστικά εγγράφου και έκδοσης.

Σημειώσεις ασφαλείας και απόδοσης

  • Τα αιτήματα σημειώσεων χρησιμοποιούν την ίδια ασφάλεια συνεδρίας και διακριτικού όπως τα αιτήματα σελίδας.
  • Επικυρώστε και ελέγξτε οποιαδήποτε URL εικόνας που παρέχεται από τον χρήστη.
  • Μεγάλα ελεύθερα payloads και εξαγωγές υψηλής ανάλυσης αυξάνουν τη χρήση μνήμης· δοκιμάστε ρεαλιστικά έγγραφα και τιμές ζουμ.

Επίλυση προβλημάτων

ΣύμπτωμαΈλεγχος
Η κορδέλα Σημειώσεων λείπειΗ δυνατότητα Annotation και οι τέσσερις σημαίες CSS/σκριπτ σημειώσεων
Η αποθήκευση αναφέρει σφάλμαΛήξη διακριτικού ή συνεδρίας, και το BasePath του widget
ctlDoc_Created (ή άλλη λειτουργία συμβάντος) δεν εκτελείται ποτέΤο όνομα της λειτουργίας: id προβολέα + _ + συμβάν, ως παγκόσμια λειτουργία
Οι σημειώσεις C# δεν εμφανίζονταιΟι αριθμοί σελίδων αρχίζουν από το 1, και τα δεδομένα φορτώθηκαν στο ενεργό διακριτικό
Μια σημείωση εικόνας εμφανίζεται στην οθόνη αλλά όχι στην εξαγωγήΟ κεντρικός υπολογιστής της εικόνας έχει ιδιωτική ή loopback διεύθυνση — δείτε παραπάνω
Ένα ξανά ανοιγμένο έγγραφο δεν έχει σημειώσειςΑποθηκεύστε το XML ή τα δεδομένα εκτός της συνεδρίας προβολέα, έπειτα φορτώστε τα στο νέο διακριτικό

Ήταν αυτή η σελίδα χρήσιμη;