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 tu 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.
Emite 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
}))Mantén 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 necesitas copiar sus botones o el marcado del diálogo. Inicializa docViewer primero, luego crea la Cinta solo cuando el servidor confirme que Anotaciones está licenciada:
<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>Guardar desde la Cinta envía 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 una página con anotaciones se renderiza. Los cuatro callbacks onAnn* mantienen la Cinta sincronizada con el ciclo de vida del visor.
Ábrela y ciérrala desde cualquier barra de herramientas del Visor controlada por el host:
annBar.open();
annBar.close();La API pública de la Cinta es:
| Método | Propósito |
|---|---|
attach(objViewer) | Conecta la Cinta al visor inicializado; requerido una vez |
open() / close() | Entrar o salir de la edición de anotaciones |
reset() | Restablece la Cinta a su estado cerrado, sin edición |
isOpen() / annotating() | Lee el estado de la Cinta / el estado de edición de anotaciones del visor |
reopenEditable() | Recarga las anotaciones de la página actual como objetos editables |
updateActionState() | Actualiza la disponibilidad de los controles de guardar/borrar después de cambios del host |
headerSlot() | Obtiene la ranura opcional de extensión de encabezado para controles controlados por el 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, consulta 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; persiste el XML o el sobre de anotaciones codificado en tu aplicación si las anotaciones deben sobrevivir más allá de la vida de la sesión.
Crear anotaciones en C#
Obtén un administrador vinculado a la sesión abierta, agrega anotaciones y cárgalas (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 anotaciones
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; soporta 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, por defecto 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 al quemar) — debe ser accesible desde el servidor (p. ej., un archivo bajo wwwroot servido por UseStaticFiles) |
La API de AnnotationManager
| Miembro | Propósito |
|---|---|
Add(BaseAnnotation) | Encola una anotación |
GetAnnotations() / GetAnnotations(int page) | Inspecciona lo que el administrador contiene |
ClearAnnotations() / ClearAnnotations(int page) | Elimina 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 replica 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 incorporadas
// 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 usan el mismo quemador que la renderización en pantalla, por lo que lo que ven los usuarios es lo que contiene el archivo.
Flujo de trabajo de persistencia
- Abre el documento y obtén su token.
- Carga el XML o los datos codificados previamente almacenados en ese token.
- Permite que el widget lea y edite las anotaciones de la sesión.
- Recupera el XML con
GetAnnotationXML(token)cuando tu aplicación decida persistir. - Exporta PDF/PNG cuando se requiera un entregable aplanado.
- Cierra la sesión del documento.
No utilices el token opaco del visor como identificador permanente de anotaciones. Asocia los datos de anotaciones persistidos con tus propios identificadores de documento y versión.
Notas de seguridad y renderizado
- Las solicitudes de anotaciones usan la misma seguridad de sesión/token que las solicitudes de página.
- Una URL relativa de
ImageAnnotationse resuelve desde el host de la solicitud y debe seguir siendo accesible para el servidor al quemar. - Valida y controla 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 la renderización de página en pantalla.
- Cargas grandes de trazos libres y exportaciones de alta resolución aumentan el uso de memoria; prueba documentos realistas y valores de zoom.
Solución de problemas
| Síntoma | Verificación |
|---|---|
| Falta la cinta de anotaciones | Capacidad Annotation y las cuatro banderas de CSS/script de anotaciones |
| El callback de guardado informa un error | Expiración del token/sesión y BasePath del middleware |
| Las anotaciones C# no aparecen | La numeración de páginas comienza en 1 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 el quemado |
| 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?