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 programmatiquement 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.
La prise en charge des annotations est conditionnée 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 bundles d'annotation en même temps que les bundles du viewer — ils sont conditionnés par la licence, de sorte que 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">
<!-- Contrôles du visualiseur, y compris le bouton qui ouvre Annotation -->
</nav>
<div id="annBarMount"></div>
<div id="divDocViewer"><div id="div_ctlDoc"></div></div>Le bundle d'annotation génère le DOM du ruban dans annBarMount ; vous n'avez pas besoin de copier ses boutons ou le balisage du dialogue. Initialise d'abord docViewer, puis créez le ruban uniquement lorsque le serveur confirme que l'annotation est licenciée :
<script>
let annBar = null;
let currentToken = '';
const objViewer = $('#div_ctlDoc').docViewer({
BasePath: 'doconut',
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() | Ramener 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 des Annotations, voir Démarrage rapide.
Le bundle 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.
Construire 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) =>
{
// Lié aux dimensions de page de la session ouverte
var manager = viewer.GetAnnotationManager(token);
var pageCount = viewer.GetPageCount(token);
// Un tampon par 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));
// Charger dans la session — le widget les récupère via AnnLoad et le
// renderer les intègre dans les exportations image/PDF.
viewer.LoadAnnotationData(token, manager);
return Results.Ok();
});Types d’annotation
Tous les types résident 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 | Couleurs de bordure + 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 de la gravure) — 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 toutes les annotations / 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).
Exportation avec annotations incorporées
// PDF de toutes les pages avec les annotations rendues dessus
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");
});
// Ou un ZIP de PNGs par page
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 gravure que le rendu à l’écran, de sorte que 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 persister. - Exportez en 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.
Notes de sécurité et de rendu
- Les requêtes d'annotation utilisent la même sécurité de session/jeton 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 de la génération. - 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 dessin à main levée et les exportations haute résolution augmentent l'utilisation de la mémoire ; testez des documents réalistes et les valeurs de zoom.
Dépannage
| Symptôme | Vérification |
|---|---|
| Le ruban d'annotation est manquant | Annotation fonctionnalité et les quatre indicateurs CSS/script d'annotation |
| Le rappel de sauvegarde signale une erreur | Expiration du jeton/de la 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 jeton actif |
| L'annotation d'image apparaît à l'écran mais pas dans l'export | Le serveur peut atteindre l'URL de l'image pendant la génération |
| 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 jeton |
Cette page était-elle utile ?