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.
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 paquets d'annotation en même temps que les paquets 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">
<!-- 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éez 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: '/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 et fermez-le depuis n'importe quelle barre d'outils du Viewer détenue par l'hôte :
annBar.open();
annBar.close();L'API publique du ruban est :
| Méthode | Objectif |
|---|---|
attach(objViewer) | Connecte le ruban au viewer initialisé ; requis une fois |
open() / close() | Entrer ou quitter le mode d'édition d'annotation |
reset() | Restaure le ruban à son état fermé, non éditable |
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 détenus par 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 du 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 délimitant) :
| Type | Remarques |
|---|---|
StampAnnotation | Tampon texte avec taille de police, bordure, couleur ; prend en charge Opacity, Rotate |
NoteAnnotation | Note autocollante avec texte, couleur de fond, 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 FreehandData encodés |
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 l'incorporation) — 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 d'incorporation que le rendu à l'écran, ainsi ce que les utilisateurs voient est 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 viewer 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
ImageAnnotationrelative est résolue à partir de l'hôte de la requête et doit rester accessible au serveur au moment de l'incorporation. - Validez et contrôlez toute URL d'image fournie par l'utilisateur afin d'éviter les falsifications de requêtes 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 tracés libres et les exportations haute résolution augmentent l'utilisation de mémoire ; testez des documents réalistes et des valeurs de zoom.
Dépannage
| Symptôme | Vérification |
|---|---|
| Le ruban d'annotation est manquant | Annotation capacité et les quatre indicateurs CSS/script d'annotation |
| Le rappel d'enregistrement 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'exportation | Le serveur peut atteindre l'URL de l'image pendant l'incorporation |
| Le document rouvert n'a aucune annotation | Persisté le XML/les données en dehors de la session du viewer, puis les charger dans le nouveau jeton |
Cette page vous a-t-elle été utile ?