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 UI de anotaciones

Annotation es un módulo del Viewer, no una barra de herramientas independiente. La página completa debe incluir los recursos del Viewer, la barra de herramientas del Viewer, el montaje del Viewer y el objViewer inicializado; luego la cinta de Annotation 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
@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 Viewer visible en el marcado:

html
<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 Annotation 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 Annotation está licenciado:

html
<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.

Ábrala y ciérrela desde cualquier barra de herramientas del Viewer administrada por el host:

javascript
annBar.open();
annBar.close();

La API pública de la cinta es:

MétodoPropó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 de guardar/eliminar después de cambios del host
headerSlot()Obtener la ranura opcional de extensión de encabezado para controles administrados por el host

onStatus, onToast, onLayout, onEditStart y onEditEnd son callbacks opcionales del host. El objeto endpoints puede proporcionar adicionalmente exportPdf, exportPng, imageUpload e imageList; los controles sin un endpoint configurado permanecen ocultos. Para la secuencia de inicio combinada de Viewer, Search y Annotation, 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 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):

csharp
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):

TipoNotas
StampAnnotationSello de texto con tamaño de fuente, borde, color; soporta Opacity, Rotate
NoteAnnotationNota adhesiva con texto, color de fondo, tamaño de fuente, TitleColor
RectangleAnnotationBorde + colores de relleno, Title/ShowTitle
CircleAnnotationBorde + relleno, ShowBorder
EllipseAnnotationBorde + relleno, ShowBorder
TriangleAnnotationColor de borde, BackColor, ShowBorder
LineAnnotationLínea recta con ancho y color
ArrowAnnotationLínea con punta de flecha; Direction configurable (tipo ArrowDirection, puntos cardinales, predeterminado E)
FreehandAnnotationTrazo libre a partir de puntos codificados FreehandData
ImageAnnotationImagen 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

MiembroPropó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 incorporadas

csharp
// 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

  1. Abra el documento y obtenga su token.
  2. Cargue el XML o los datos codificados almacenados previamente en ese token.
  3. Permita que el widget lea y edite las anotaciones de la sesión.
  4. Recupere el XML con GetAnnotationXML(token) cuando su aplicación decida persistir.
  5. Exporte PDF/PNG cuando se requiera un entregable aplanado.
  6. Cierre la sesión del documento.

No utilice el token opaco del visor como identificador permanente de anotaciones. Asocie los datos de anotaciones persistidos con sus 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 ImageAnnotation se resuelve desde el host de la solicitud y debe seguir siendo accesible para el servidor al quemar.
  • 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 la renderización de página en pantalla.
  • Las cargas útiles grandes de trazos libres y las exportaciones de alta resolución aumentan el uso de memoria; pruebe documentos realistas y valores de zoom.

Solución de problemas

SíntomaVerificación
Falta la cinta de anotacionesCapacidad Annotation y las cuatro banderas CSS/script de anotaciones
El callback de guardado informa un errorExpiración del token/sesión y middleware BasePath
Las anotaciones C# no aparecenLa numeración de páginas 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ónEl servidor puede alcanzar la URL de la imagen durante el quemado
El documento reabierto no tiene anotacionesPersistir XML/datos fuera de la sesión del visor, luego cargarlos en el nuevo token

¿Fue útil esta página?