Annotations
Ajouter la prise en charge des annotations au visualiseur
Les annotations dans Doconut fonctionnent dans deux sens : les utilisateurs les dessinent dans le widget du navigateur et le serveur les persiste par page, ou votre code les crée de manière programmatique et les charge dans une session ouverte. Dans les deux cas, elles sont rendues sur les pages et peuvent être incorporées dans les exportations PDF/PNG.
Le support des annotations est conditionné par la capacité de licence Annotation (accordée automatiquement avec une licence Temporaire active).
Activer l'interface d'annotation
L'annotation est un module du Viewer, pas une barre d'outils autonome. La page complète doit inclure les ressources du Viewer, la barre d'outils du Viewer, le montage du Viewer, et le objViewer initialisé ; le ruban d'annotation est alors monté et attaché à cette même instance.
Émettez les paquets d'annotation en même temps que les paquets du viewer — ils sont conditionnés par la licence, donc les balises n'apparaissent que lorsque la capacité est disponible :
@Html.Raw(Viewer.ReferenceCss(new CssConfig
{
IncludeViewerCss = true,
IncludeAnnotationCss = true // jquery-ui.min.css + annotationBar.css
}))
@Html.Raw(Viewer.ReferenceScripts(new ScriptConfig
{
IncludeJQuery = true,
IncludeViewerScripts = true,
IncludeAnnotationScripts = true, // jquery-ui, raphael.js, annotation.js
IncludeAnnotationBar = true // the embedded annotation ribbon
}))Conservez la composition complète du Viewer visible dans le balisage :
<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>Le paquet d'annotation génère le DOM du ruban à l'intérieur de annBarMount ; vous n'avez pas besoin de copier ses boutons ou le balisage du dialogue. Initialise d'abord docViewer, puis crée le ruban uniquement lorsque le serveur confirme que l'annotation est sous licence :
<script>
let annBar = null;
let currentToken = '';
const objViewer = $('#div_ctlDoc').docViewer({
BasePath: '/',
ResPath: 'doconut-res/images',
onAnnLoaded: () => annBar?.handleAnnLoaded(),
onAnnSaved: () => annBar?.handleAnnSaved(),
onAnnSaveError: () => annBar?.handleAnnSaveError(),
onAnnClosed: () => annBar?.handleAnnClosed(),
onError: (message) => console.error('Viewer error:', message)
});
@if (Viewer.IsAnnotationEnabled)
{
<text>
annBar = $('#annBarMount').doconutAnnotationBar({
docId: 'ctlDoc',
getRequestParams: () => ({ token: currentToken }),
onStatus: (message) => console.log(message),
onToast: (message, type) => console.log(type, message),
onLayout: () => requestAnimationFrame(() => objViewer.Refit())
});
annBar.attach(objViewer);
</text>
}
</script>L'enregistrement depuis le ruban envoie les données via le middleware (AnnSave), qui les stocke dans la session du document par page. Le chargement (AnnLoad) se produit automatiquement lorsqu'une page contenant des annotations est rendue. Les quatre rappels onAnn* maintiennent le ruban synchronisé avec le cycle de vie du viewer.
Ouvrez‑le et fermez‑le depuis n'importe quelle barre d'outils du Viewer appartenant à l'hôte :
annBar.open();
annBar.close();L'API publique du ruban est :
| Méthode | Objectif |
|---|---|
attach(objViewer) | Connecter le ruban au viewer initialisé ; requis une fois |
open() / close() | Entrer ou quitter le mode d'édition d'annotation |
reset() | Rétablir le ruban à son état fermé, non‑édition |
isOpen() / annotating() | Lire l'état du ruban / l'état d'édition d'annotation du viewer |
reopenEditable() | Recharger les annotations de la page actuelle en tant qu'objets éditables |
updateActionState() | Actualiser la disponibilité des contrôles d'enregistrement/suppression après les changements de l'hôte |
headerSlot() | Obtenir le slot d'extension d'en-tête optionnel pour les contrôles appartenant à l'hôte |
onStatus, onToast, onLayout, onEditStart et onEditEnd sont des rappels d'hôte optionnels. L'objet endpoints peut en outre fournir exportPdf, exportPng, imageUpload et imageList ; les contrôles sans point de terminaison configuré restent cachés. Pour la séquence de démarrage combinée du Viewer, de la Recherche et de l'Annotation, voir Démarrage rapide.
Le paquet d'annotation ajoute les outils d'édition du navigateur, mais les données appartiennent toujours à la session de document côté serveur identifiée par le jeton. Réouvrir la source crée une nouvelle session ; conservez le XML ou l'enveloppe d'annotation encodée dans votre application si les annotations doivent survivre au-delà de la durée de vie de la session.
Créer des annotations en C#
Obtenez un gestionnaire lié à la session ouverte, ajoutez des annotations et chargez‑les (avec using Doconut.Annotations; pour les types et using System.Drawing; pour Rectangle/Color) :
app.MapPost("/api/annotations/load-sample", (string token, Viewer 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 widget fetches them via AnnLoad and the
// renderer burns them into image/PDF exports.
viewer.LoadAnnotationData(token, manager);
return Results.Ok();
});Types d'annotation
Tous les types se trouvent dans Doconut.Annotations et héritent de BaseAnnotation (numéro de page + rectangle de délimitation Rectangle) :
| Type | Remarques |
|---|---|
StampAnnotation | Tampon texte avec taille de police, bordure, couleur ; prend en charge Opacity, Rotate |
NoteAnnotation | Note autocollante avec texte, couleur d'arrière‑plan, taille de police, TitleColor |
RectangleAnnotation | Bordure + couleurs de remplissage, Title/ShowTitle |
CircleAnnotation | Bordure + remplissage, ShowBorder |
EllipseAnnotation | Bordure + remplissage, ShowBorder |
TriangleAnnotation | Couleur de bordure, BackColor, ShowBorder |
LineAnnotation | Ligne droite avec épaisseur et couleur |
ArrowAnnotation | Ligne avec pointe de flèche ; Direction réglable (type ArrowDirection, points cardinaux, défaut E) |
FreehandAnnotation | Trait libre à partir de points encodés FreehandData |
ImageAnnotation | Image provenant d'une URL. Une URL relative est résolue par rapport à l'hôte de la requête lorsque l'annotation est ajoutée (seul le téléchargement de l'image se produit au moment du rendu) — elle doit être accessible depuis le serveur (par ex. un fichier sous wwwroot servi par UseStaticFiles) |
L'API AnnotationManager
| Membre | Objectif |
|---|---|
Add(BaseAnnotation) | Mettre en file d'attente une annotation |
GetAnnotations() / GetAnnotations(int page) | Inspecter ce que le gestionnaire contient |
ClearAnnotations() / ClearAnnotations(int page) | Supprimer tout / par page |
GetAnnotationData() / GetAnnotationData(int page) | Chaîne de données d'annotation encodée — une enveloppe Base64 (ce que le widget consomme) |
GetAnnotationXml() | Forme XML |
Viewer reflète les opérations de chargement/lecture sur une session : LoadAnnotationData(token, manager) ou LoadAnnotationData(token, encodedData) (l'enveloppe Base64 provenant de GetAnnotationData()), LoadAnnotationXML(token, xml), GetAnnotationXML(token).
Exporter avec les annotations incorporées
// PDF of all pages with annotations rendered onto them
app.MapGet("/api/annotations/export-pdf", async (string token, Viewer viewer) =>
{
byte[] pdf = await viewer.ExportAnnotationsToPdfAsync(token, zoom: 100);
return Results.File(pdf, "application/pdf", "export.pdf");
});
// Or a ZIP of per-page PNGs
app.MapGet("/api/annotations/export-png-zip", async (string token, Viewer viewer) =>
{
byte[] zip = await viewer.ExportAnnotationsToPngZipAsync(token, zoom: 100);
return Results.File(zip, "application/zip", "annotations-png.zip");
});Les exportations utilisent le même moteur de rendu que l'affichage à l'écran, ainsi ce que les utilisateurs voient est exactement ce que le fichier contient.
Flux de travail de persistance
- Ouvrez le document et obtenez son jeton.
- Chargez le XML ou les données encodées précédemment stockées dans ce jeton.
- Laissez le widget lire et modifier les annotations de la session.
- Récupérez le XML avec
GetAnnotationXML(token)lorsque votre application décide de le persister. - Exportez PDF/PNG lorsqu'un livrable aplati est requis.
- Fermez la session du document.
N'utilisez pas le jeton opaque du visualiseur comme identifiant permanent d'annotation. Associez les données d'annotation persistées à vos propres identifiants de document et de version.
Sécurité et notes de rendu
- Les requêtes d'annotation utilisent la même sécurité de session/token que les requêtes de page.
- Une URL relative
ImageAnnotationest résolue à partir de l'hôte de la requête et doit rester accessible au serveur lors du rendu. - Validez et contrôlez toute URL d'image fournie par l'utilisateur afin d'éviter une falsification de requête côté serveur.
- Les exportations appliquent la même décision de licence/marque d'eau personnalisée que le rendu de page à l'écran.
- Les gros chargements de dessins libres et les exportations haute résolution augmentent l'utilisation de la mémoire ; testez des documents réalistes et des valeurs de zoom.
Dépannage
| Symptôme | Vérification |
|---|---|
| Le ruban d'annotation est absent | capacité Annotation et les quatre indicateurs CSS/script d'annotation |
| Le rappel de sauvegarde signale une erreur | Expiration du token/session et middleware BasePath |
| Les annotations C# n'apparaissent pas | La numérotation des pages commence à 1 et les données ont été chargées dans le token actif |
| L'annotation d'image apparaît à l'écran mais pas dans l'export | Le serveur peut atteindre l'URL de l'image pendant le rendu |
| Le document rouvert n'a aucune annotation | Conservez le XML/les données en dehors de la session du visualiseur, puis chargez-les dans le nouveau token |
Cette page était-elle utile ?