Anotaciones
Agregar soporte de anotaciones al visor
Las anotaciones en Doconut funcionan en dos direcciones: los usuarios las dibujan en el widget del navegador y el servidor las persiste por página, o su código las crea programáticamente y las carga en una sesión abierta. De cualquier manera se renderizan en las páginas y pueden incorporarse en exportaciones PDF/PNG.
El soporte de anotaciones está restringido por la capacidad de licencia Annotation (concedida automáticamente bajo una licencia Temporal activa).
Habilitar la interfaz de anotaciones
La anotación es un módulo del Visor, no una barra de herramientas independiente. La página completa debe incluir los recursos del Visor, la barra de herramientas del Visor, el montaje del Visor y el objViewer inicializado; luego la Cinta de Anotaciones se monta y se adjunta a esa misma instancia.
Emita los paquetes de anotaciones junto a los paquetes del visor — están restringidos por licencia, por lo que las etiquetas solo aparecen cuando la capacidad 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
}))Mantenga la composición completa del Visor visible en el marcado:
<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>El paquete de Anotaciones genera el DOM de la Cinta dentro de annBarMount; no necesita copiar sus botones ni el marcado del diálogo. Inicialice docViewer primero, luego cree la Cinta solo cuando el servidor confirme que la Anotación está licenciada:
<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>Guardar desde la Cinta envía los datos a través del middleware (AnnSave), que los almacena en la sesión del documento por página. La carga (AnnLoad) ocurre automáticamente cuando se renderiza una página con anotaciones. Los cuatro callbacks onAnn* mantienen la Cinta sincronizada con el ciclo de vida del visor.
Ábrala y ciérrela desde cualquier barra de herramientas del Visor propiedad del host:
annBar.open();
annBar.close();La API pública de la Cinta es:
| Método | Propósito |
|---|---|
attach(objViewer) | Conectar la Cinta al visor inicializado; requerido una vez |
open() / close() | Entrar o salir de la edición de anotaciones |
reset() | Restablecer la Cinta a su estado cerrado, sin edición |
isOpen() / annotating() | Leer el estado de la Cinta / el estado de edición de anotaciones del visor |
reopenEditable() | Recargar las anotaciones de la página actual como objetos editables |
updateActionState() | Actualizar la disponibilidad de los controles guardar/borrar después de cambios del host |
headerSlot() | Obtener la ranura opcional de extensión de encabezado para controles propiedad del host |
onStatus, onToast, onLayout, onEditStart y onEditEnd son callbacks opcionales del host. El objeto endpoints puede proporcionar adicionalmente exportPdf, exportPng, imageUpload y imageList; los controles sin un endpoint configurado permanecen ocultos. Para la secuencia de inicio combinada del Visor, Búsqueda y Anotaciones, vea Inicio rápido.
El paquete de anotaciones agrega las herramientas de autoría del navegador, pero los datos siguen perteneciendo a la sesión del documento del lado del servidor identificada por el token. Reabrir la fuente crea una nueva sesión; persista el XML o el sobre de anotaciones codificado en su aplicación si las anotaciones deben sobrevivir más allá de la vida útil de la sesión.
Crear anotaciones en C#
Obtenga un administrador vinculado a la sesión abierta, agregue anotaciones y cárguelas (con using Doconut.Annotations; para los tipos y using System.Drawing; para 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();
});Tipos de anotación
Todos los tipos se encuentran en Doconut.Annotations y heredan de BaseAnnotation (número de página + Rectangle delimitador):
| Tipo | Notas |
|---|---|
StampAnnotation | Sello de texto con tamaño de fuente, borde, color; admite Opacity, Rotate |
NoteAnnotation | Nota adhesiva con texto, color de fondo, tamaño de fuente, TitleColor |
RectangleAnnotation | Colores de borde y relleno, Title/ShowTitle |
CircleAnnotation | Borde + relleno, ShowBorder |
EllipseAnnotation | Borde + relleno, ShowBorder |
TriangleAnnotation | Color de borde, BackColor, ShowBorder |
LineAnnotation | Línea recta con ancho y color |
ArrowAnnotation | Línea con punta de flecha; Direction configurable (tipo ArrowDirection, puntos cardinales, predeterminado E) |
FreehandAnnotation | Trazo libre a partir de puntos codificados FreehandData |
ImageAnnotation | Imagen desde una URL. Una URL relativa se resuelve contra el host de la solicitud cuando se agrega la anotación (solo la obtención de la imagen ocurre en el momento de la renderización) — debe ser accesible desde el servidor (p. ej., un archivo bajo wwwroot servido por UseStaticFiles) |
La API de AnnotationManager
| Miembro | Propósito |
|---|---|
Add(BaseAnnotation) | Encolar una anotación |
GetAnnotations() / GetAnnotations(int page) | Inspeccionar lo que el administrador contiene |
ClearAnnotations() / ClearAnnotations(int page) | Eliminar todo / por página |
GetAnnotationData() / GetAnnotationData(int page) | Cadena codificada de datos de anotación — un sobre de transmisión Base64 (lo que consume el widget) |
GetAnnotationXml() | Forma XML |
Viewer refleja las operaciones de carga/lectura contra una sesión: LoadAnnotationData(token, manager) o LoadAnnotationData(token, encodedData) (el sobre de transmisión Base64 de GetAnnotationData()), LoadAnnotationXML(token, xml), GetAnnotationXML(token).
Exportar con anotaciones incrustadas
// 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");
});Las exportaciones utilizan el mismo proceso que la renderización en pantalla, por lo que lo que los usuarios ven es lo que contiene el archivo.
Flujo de trabajo de persistencia
- Abra el documento y obtenga su token.
- Cargue el XML o los datos codificados previamente almacenados en ese token.
- Permita que el widget lea y edite las anotaciones de la sesión.
- Recupere el XML con
GetAnnotationXML(token)cuando su aplicación decida persistir. - Exporte PDF/PNG cuando se requiera un entregable aplanado.
- Cierre la sesión del documento.
No utilice el token opaco del visor como identificador permanente de anotaciones. Asocie los datos de anotación persistidos con sus propios identificadores de documento y versión.
Notas de seguridad y renderizado
- Las solicitudes de anotación usan la misma seguridad de sesión/token que las solicitudes de página.
- Una URL relativa de
ImageAnnotationse resuelve a partir del host de la solicitud y debe seguir siendo accesible para el servidor en el momento de la generación. - Valide y controle cualquier URL de imagen suministrada por el usuario para evitar falsificaciones de solicitudes del lado del servidor.
- Las exportaciones aplican la misma decisión de licencia/marca de agua personalizada que el renderizado de página en pantalla.
- Las cargas útiles de dibujo a mano alzada grandes y las exportaciones de alta resolución aumentan el uso de memoria; pruebe documentos realistas y valores de zoom.
Solución de problemas
| Síntoma | Verificación |
|---|---|
| Falta la cinta de anotaciones | Capacidad Annotation y los cuatro indicadores CSS/script de anotación |
| La devolución de llamada de guardado informa un error | Expiración de token/sesión y middleware BasePath |
| Las anotaciones C# no aparecen | La numeración de página comienza en uno y los datos se cargaron en el token activo |
| La anotación de imagen aparece en pantalla pero no en la exportación | El servidor puede alcanzar la URL de la imagen durante la generación |
| El documento reabierto no tiene anotaciones | Persistir XML/datos fuera de la sesión del visor, luego cargarlos en el nuevo token |
¿Fue útil esta página?