Annotationen
Fügen Sie dem Viewer Annotationsunterstützung hinzu
Annotationen in Doconut funktionieren in zwei Richtungen: Benutzer zeichnen sie im Browser‑Widget und der Server speichert sie pro Seite, oder Ihr Code erstellt sie und lädt sie in eine offene Sitzung. In beiden Fällen werden sie auf den Seiten gerendert und können in PDF‑ oder PNG‑Exporte eingebettet werden.
Die Unterstützung für Annotationen ist durch die Lizenzfunktion Annotation geschützt (wird automatisch bei einer aktiven temporären Lizenz gewährt).
Aktivieren der Annotation‑Benutzeroberfläche
Annotation ist ein Viewer‑Modul, keine eigenständige Symbolleiste. Die Seite muss die Viewer‑Ressourcen, die Viewer‑Symbolleiste, den Viewer‑Mount und ein initialisiertes objViewer einbinden; das Annotation‑Ribbon wird dann montiert und an dieselbe Instanz angehängt.
Geben Sie die Annotationsdateien zusammen mit den Viewer‑Dateien aus. Sie sind lizenzgeschützt, sodass ihre Tags nur erscheinen, wenn die Funktion gewährt ist. In MVC 5 erstellen Sie die Tags im Controller (Views haben kein @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;Behalten Sie die vollständige Viewer‑Zusammensetzung im Markup bei:
<nav id="toolbar" aria-label="Document viewer controls">
<!-- Viewer‑Steuerelemente, einschließlich der Schaltfläche, die Annotation öffnet -->
</nav>
<div id="annBarMount"></div>
<div id="divDocViewer"><div id="div_ctlDoc"></div></div>Die Annotationsdateien erzeugen das Ribbon innerhalb von annBarMount; Sie kopieren dessen Schaltflächen oder Dialoge nicht. Initialisieren Sie zuerst docViewer und erstellen Sie das Ribbon nur, wenn der Server bestätigt, dass Annotation lizenziert ist:
@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>Ohne die Funktion lässt ReferenceScripts das Skript des Ribbons weg, sodass die serverseitige if‑Anweisung einen ungeschützten Aufruf von doconutAnnotationBar(...) davon abhält, die Seite zu beschädigen. In Web Forms schreiben Sie dieselbe Prüfung als <% if (IsAnnotationEnabled) { %> … <% } %>.
Das Speichern über das Ribbon sendet die Daten an DocImage.axd (AnnSave), das sie in der Dokument‑Sitzung pro Seite speichert. Das Laden (AnnLoad) erfolgt automatisch, wenn eine Seite mit Annotationen gerendert wird.
Öffnen und schließen Sie das Ribbon über Ihre eigene Symbolleiste mit annBar.open() und annBar.close(). Seine öffentliche API:
| Methode | Zweck |
|---|---|
attach(objViewer) | Verbinden Sie das Ribbon mit dem initialisierten Viewer; einmalig erforderlich |
open() / close() | In den Annotations‑Bearbeitungsmodus wechseln bzw. diesen verlassen |
reset() | Das Ribbon in den geschlossenen, nicht‑bearbeitenden Zustand zurücksetzen |
isOpen() / annotating() | Den Ribbon‑Zustand bzw. den Annotations‑Bearbeitungszustand des Viewers auslesen |
reopenEditable() | Die Annotationen der aktuellen Seite als editierbare Objekte neu laden |
updateActionState() | Die Speicher‑ und Lösch‑Steuerelemente nach Änderungen des Hosts aktualisieren |
headerSlot() | Der optionale Header‑Slot für host‑eigene Steuerelemente |
onStatus, onToast, onLayout, onEditStart und onEditEnd sind optionale Host‑Callbacks. Das Objekt endpoints kann außerdem exportPdf, exportPng, imageUpload und imageList bereitstellen; Steuerelemente ohne konfigurierten Endpunkt bleiben verborgen.
Zwei Arten von Callback, auf zwei Arten verdrahtet
Der Annotationscode verwendet zwei Callback‑Stile, und jeder schlägt stillschweigend fehl, wenn er auf die andere Weise verdrahtet wird:
onAnnLoaded,onAnnSaved,onAnnSaveError,onAnnClosedsind Optionen vondocViewer({...}), wie jeder Viewer‑Callback.Created,Changed,DeletedundPropertiessind globale Funktionen, die per Namen gefunden werden: die Viewer‑ID wird dem Ereignis vorangestellt. Für den Viewer in#div_ctlDocist die IDctlDoc, sodass die FunktionenctlDoc_Created,ctlDoc_Changed,ctlDoc_DeletedundctlDoc_Propertiesheißen. Eine falsch geschriebene oder umbenannte Funktion wird einfach nie aufgerufen.
SaveAnnotations() gibt nicht zurück, ob gespeichert wurde: das Ergebnis kommt über onAnnSaved oder onAnnSaveError. Code, der seinen Rückgabewert ausliest, löscht nie das Flag für „ungenügende Änderungen“.
Anmerkungen in C# erstellen
Erhalte einen Manager, der an die offene Sitzung gebunden ist, füge Anmerkungen hinzu und lade sie (mit using Doconut.Annotations; für die Typen und using System.Drawing; für Rectangle und 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);
}Anmerkungstypen
Alle Typen befinden sich in Doconut.Annotations und erben von BaseAnnotation (Seitennummer plus begrenzendes Rectangle):
| Type | Notes |
|---|---|
StampAnnotation | Textstempel mit Schriftgröße, Rand, Farbe; unterstützt Opacity, Rotate |
NoteAnnotation | Haftnotiz mit Text, Hintergrundfarbe, Schriftgröße, TitleColor |
RectangleAnnotation | Rand- und Füllfarben, Title/ShowTitle |
CircleAnnotation | Rand und Füllung, ShowBorder |
EllipseAnnotation | Rand und Füllung, ShowBorder |
TriangleAnnotation | Randfarbe, BackColor, ShowBorder |
LineAnnotation | Gerade Linie mit Breite und Farbe |
ArrowAnnotation | Linie mit Pfeilspitze; einstellbare Direction (ArrowDirection, Himmelsrichtungen, Standard E) |
FreehandAnnotation | Freihandstrich aus kodierten FreehandData‑Punkten |
ImageAnnotation | Bild von einer URL. Eine relative URL wird gegen den Host der aktuellen Anfrage aufgelöst, wenn die Anmerkung hinzugefügt wird – siehe den Hinweis zum Export unten |
Die AnnotationManager-API
| Member | Purpose |
|---|---|
Add(BaseAnnotation) | Annotation zur Warteschlange hinzufügen |
GetAnnotations() / GetAnnotations(int page) | Untersuchen, was der Manager enthält |
ClearAnnotations() / ClearAnnotations(int page) | Alle entfernen / pro Seite |
GetAnnotationData() / GetAnnotationData(int page) | Kodierte Annotationsdaten — das Datenpaket, das das Widget verarbeitet |
GetAnnotationXml() | XML‑Form |
Viewer spiegelt die Lade- und Lesevorgänge gegen eine Sitzung wider: LoadAnnotationData(token, manager) oder LoadAnnotationData(token, encodedData), LoadAnnotationXML(token, xml) und GetAnnotationXML(token).
Export mit eingebrannten Anmerkungen
// 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");
}Exporte verwenden denselben Brenner wie die Bildschirmausgabe und dieselbe Lizenz‑ und benutzerdefinierte Wasserzeichen‑Entscheidung.
Bildannotationen auf einem Intranet‑Host exportieren als „Bild blockiert“
Der Viewer und der Export holen das Bild einer ImageAnnotation aus unterschiedlichen Quellen:
| Oberfläche | Wer lädt das Bild herunter | Auf einem Intranet‑ oder Entwicklungs‑Host |
|---|---|---|
| Der Viewer im Browser | Der Browser des Benutzers | Das Bild wird angezeigt |
ExportAnnotationsToPdfAsync / …PngZipAsync | Der Server | Ein roter „Bild blockiert“‑Platzhalter |
Wenn der Server ein Bild abruft, um es einzubetten, verweigert er Adressen, die zurück in private Netze führen: Loopback (localhost, 127.0.0.1), 0.0.0.0, Link‑Local (169.254.x.x), die privaten Bereiche 10.x, 172.16–31.x und 192.168.x, Carrier‑Grade‑NAT (100.64.0.0/10) sowie IPv6‑Link‑Local‑ und Unique‑Local‑Adressen. Dies schützt den Server davor, über eine manipulierte Bild‑URL interne Systeme zu erreichen, und es gibt keinen Schalter, um das zu deaktivieren.
Eine relative Bild‑URL wird relativ zum Host der aktuellen Anfrage aufgelöst. Deshalb wird bei localhost, einem Intranet‑Server oder einer Site, die über eine private Adresse erreichbar ist, das Bild im Viewer angezeigt, während der Export stattdessen den Platzhalter enthält. Das ist die Schutzfunktion, kein Rendering‑Fehler. Wenn Exporte das Bild enthalten sollen, muss es von einem Host mit öffentlicher Adresse bereitgestellt und eine absolute URL verwendet werden. Eine Bild‑URL, die überhaupt nicht aufgelöst werden kann, lässt diese eine Annotation ohne Fehlermeldung verschwinden – prüfen Sie die gerenderten Pixel oder das Netzwerk‑Log, nicht den Statuscode.
Persistenz‑Workflow
- Öffnen Sie das Dokument und holen Sie sein Token.
- Laden Sie zuvor gespeicherte XML‑ oder kodierte Daten in dieses Token.
- Lassen Sie das Widget die Sitzungs‑Annotationen lesen und bearbeiten.
- Rufen Sie XML mit
GetAnnotationXML(token)ab, wenn Ihre Anwendung die Persistierung ausführen soll. - Exportieren Sie PDF oder PNG, wenn ein flaches Auslieferungsformat benötigt wird.
- Schließen Sie die Dokumentsitzung.
Verwenden Sie das Viewer‑Token nicht als dauerhaften Annotations‑Identifier – es verfällt mit der Sitzung, und ein Recycle des Anwendungspools beendet jede Sitzung auf einem einzelnen Server. Speichern Sie Annotationsdaten gegen Ihre eigenen Dokument‑ und Versions‑Identifier.
Sicherheits‑ und Rendering‑Hinweise
- Annotations‑Anfragen nutzen dieselbe Sitzungs‑ und Token‑Sicherheit wie Seiten‑Anfragen.
- Validieren und kontrollieren Sie jede vom Benutzer bereitgestellte Bild‑URL.
- Große Freihand‑Payloads und hochauflösende Exporte erhöhen den Speicherverbrauch; testen Sie realistische Dokumente und Zoom‑Werte.
Fehlerbehebung
| Symptom | Prüfung |
|---|---|
| Das Annotation‑Ribbon fehlt | Die Annotation‑Fähigkeit und die vier Annotation‑CSS/Script‑Flags |
| Beim Speichern wird ein Fehler gemeldet | Token‑ oder Sitzungs‑Ablauf, und das BasePath‑Attribut des Widgets |
ctlDoc_Created (oder eine andere Ereignis‑Funktion) wird nie ausgeführt | Der Funktionsname: Viewer‑ID + _ + Ereignis, als globale Funktion |
| C#‑Annotationen werden nicht angezeigt | Seitenzahlen beginnen bei 1, und die Daten wurden in das aktive Token geladen |
| Eine Bildannotation wird im Viewer angezeigt, aber nicht im Export | Der Host des Bildes hat eine private oder Loopback‑Adresse – siehe oben |
| Ein erneut geöffnetes Dokument hat keine Annotationen | Persistieren Sie XML oder Daten außerhalb der Viewer‑Sitzung und laden Sie sie in das neue Token |
War diese Seite hilfreich?