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

Anotación 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; la cinta de anotaciones se monta y se adjunta a esa misma instancia.

Emita los paquetes de anotaciones junto con 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">
    <!-- Controles del visor, incluido el botón que abre Anotación -->
</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 Anotación 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.

Abrir y cerrar desde cualquier barra de herramientas del Viewer controlada por el host:

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

La API pública de la cinta es:

MétodoPropósito
attach(objViewer)Conecta la cinta al visor inicializado; requerido una vez
open() / close()Entrar o salir del modo de edición de anotaciones
reset()Devuelve 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/eliminar después de cambios en el host
headerSlot()Obtener la ranura de extensión de encabezado opcional 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 Viewer, Search y Anotación, consulte Quick Start.

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

csharp
app.MapPost("/api/annotations/load-sample", (string token, Viewer viewer) =>
{
    // Vinculado a las dimensiones de página de la sesión abierta
    var manager = viewer.GetAnnotationManager(token);
    var pageCount = viewer.GetPageCount(token);

    // Un sello por página
    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));

    // Cargar en la sesión — el widget los recupera mediante AnnLoad y el
    // renderizador los incrusta en las exportaciones de imagen/PDF.
    viewer.LoadAnnotationData(token, manager);
    return Results.Ok();
});

Tipos de anotación

Todos los tipos viven 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
RectangleAnnotationColores de borde y relleno, Title/ShowTitle
CircleAnnotationBorde y relleno, ShowBorder
EllipseAnnotationBorde y 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 en el momento de la generación) — debe ser accesible desde el servidor (p. ej., un archivo bajo wwwroot servido por UseStaticFiles)

La API de AnnotationManager

MiembroPropósito
Add(BaseAnnotation)Encola una anotación
GetAnnotations() / GetAnnotations(int page)Inspeccionar lo que el manager contiene
ClearAnnotations() / ClearAnnotations(int page)Eliminar todas / por página
GetAnnotationData() / GetAnnotationData(int page)Cadena codificada de datos de anotación — un sobre 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 Base64 de GetAnnotationData()), LoadAnnotationXML(token, xml), GetAnnotationXML(token).

Exportar con anotaciones quemadas

csharp
// PDF de todas las páginas con anotaciones renderizadas sobre ellas
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");
});

// O un ZIP de PNGs por página
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 motor que el renderizado en pantalla, por lo que lo que los usuarios ven es lo que el archivo contiene.

Flujo de trabajo de persistencia

  1. Abra el documento y obtenga su token.
  2. Cargue XML o datos codificados previamente almacenados en ese token.
  3. Permita que el widget lea y edite las anotaciones de la sesión.
  4. Recupere 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 anotación persistidos con sus propios identificadores de documento y versión.

Notas de seguridad y renderizado

  • Las solicitudes de anotación utilizan la misma seguridad de sesión/token que las solicitudes de página.
  • Una URL relativa de ImageAnnotation se 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.
  • Los grandes paquetes de dibujo a mano alzada 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 de Annotation y los cuatro indicadores CSS/script de anotación
La devolución de llamada de guardado informa un errorExpiración de 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 la generación
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?