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 mantiene por página, o su código las crea y las carga en una sesión abierta. De cualquier manera se renderizan en las páginas y pueden incrustarse en exportaciones PDF o 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
Annotation es un módulo del Viewer, no una barra de herramientas independiente. La página debe incluir los recursos del Viewer, la barra de herramientas del Viewer, el montaje del Viewer y un objViewer inicializado; luego la Cinta de Anotaciones se monta y se adjunta a esa misma instancia.
Emita los archivos de anotación con los archivos del visor. Están restringidos por licencia, por lo que sus etiquetas aparecen solo cuando se concede la capacidad. En MVC 5, construya las etiquetas en el controlador (las vistas no tienen @inject):
var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();
ViewBag.ViewerCss = viewer.ReferenceCss(new CssConfig
{
IncludeViewerCss = true,
IncludeAnnotationCss = true // jquery-ui.min.css + annotationBar.css
});
ViewBag.ViewerScripts = viewer.ReferenceScripts(new ScriptConfig
{
IncludeJQuery = true,
IncludeViewerScripts = true,
IncludeAnnotationScripts = true, // jquery-ui, raphael.js, annotation.js
IncludeAnnotationBar = true // the embedded annotation ribbon
});
ViewBag.IsAnnotationEnabled = viewer.IsAnnotationEnabled;Mantenga la composición completa del Viewer 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>Los archivos de Annotation generan la Cinta dentro de annBarMount; no copie sus botones ni diálogos. Inicialice docViewer primero, luego cree la Cinta solo cuando el servidor confirme que Annotation está licenciado:
@Html.Raw(ViewBag.ViewerScripts)
<script>
var annBar = null;
var currentToken = '';
var objViewer = $('#div_ctlDoc').docViewer({
BasePath: '/',
ResPath: 'doconut-res/images',
onAnnLoaded: function () { if (annBar) annBar.handleAnnLoaded(); },
onAnnSaved: function () { if (annBar) annBar.handleAnnSaved(); },
onAnnSaveError: function () { if (annBar) annBar.handleAnnSaveError(); },
onAnnClosed: function () { if (annBar) annBar.handleAnnClosed(); },
onError: function (message) { console.error('Viewer error:', message); }
});
@if ((bool)ViewBag.IsAnnotationEnabled)
{
<text>
annBar = $('#annBarMount').doconutAnnotationBar({
docId: 'ctlDoc',
getRequestParams: function () { return { token: currentToken }; },
onLayout: function () { requestAnimationFrame(function () { objViewer.Refit(); }); }
});
annBar.attach(objViewer);
</text>
}
</script>Sin la capacidad, ReferenceScripts omite el script de la Cinta, de modo que el if del lado del servidor evita que una llamada sin protección a doconutAnnotationBar(...) rompa la página. En Web Forms, escriba la misma protección como <% if (IsAnnotationEnabled) { %> … <% } %>.
Guardar desde la Cinta envía los datos a DocImage.axd (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.
Abra y cierre la Cinta desde su propia barra de herramientas con annBar.open() y annBar.close(). Su API pública:
| 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() | 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 los controles de guardar y eliminar después de cambios en el host |
headerSlot() | El espacio opcional de encabezado para controles pertenecientes al host |
onStatus, onToast, onLayout, onEditStart y onEditEnd son callbacks opcionales del host. El objeto endpoints también puede proporcionar exportPdf, exportPng, imageUpload y imageList; los controles sin un endpoint configurado permanecen ocultos.
Dos tipos de devolución de llamada, conectados de dos maneras
El código de anotaciones usa dos estilos de devolución de llamada, y cada uno falla silenciosamente cuando se conecta de la otra manera:
onAnnLoaded,onAnnSaved,onAnnSaveError,onAnnClosedson opciones dedocViewer({...}), como cualquier devolución de llamada del visor.Created,Changed,DeletedyPropertiesson funciones globales encontradas por nombre: el id del visor precede al evento. Para el visor en#div_ctlDocel id esctlDoc, así que las funciones sonctlDoc_Created,ctlDoc_Changed,ctlDoc_DeletedyctlDoc_Properties. Una función mal escrita o renombrada simplemente nunca se llama.SaveAnnotations()no devuelve si se guardó: el resultado llega a través deonAnnSavedoonAnnSaveError. El código que lee su valor de retorno nunca borra su bandera de "cambios no guardados".
Construir anotaciones en C#
Obtenga un gestor vinculado a la sesión abierta, añada anotaciones y cárguelas (con using Doconut.Annotations; para los tipos y using System.Drawing; para Rectangle y Color):
// POST /Annotations/LoadSample?token=...
[HttpPost]
public ActionResult LoadSample(string token)
{
var viewer = Doconut.DoconutHost.Services.GetRequiredService<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 viewer fetches them, and the renderer burns them into
// image/PDF exports.
viewer.LoadAnnotationData(token, manager);
return new HttpStatusCodeResult(200);
}Tipos de anotación
Todos los tipos viven en Doconut.Annotations y heredan de BaseAnnotation (número de página más 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 y relleno, ShowBorder |
EllipseAnnotation | Borde y 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 (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 actual cuando se agrega la anotación — vea la nota de exportación a continuación |
La API de AnnotationManager
| Miembro | Propósito |
|---|---|
Add(BaseAnnotation) | Encolar una anotación |
GetAnnotations() / GetAnnotations(int page) | Inspeccionar lo que el gestor contiene |
ClearAnnotations() / ClearAnnotations(int page) | Eliminar todas / por página |
GetAnnotationData() / GetAnnotationData(int page) | Datos de anotación codificados — el sobre que consume el widget |
GetAnnotationXml() | Forma XML |
Viewer refleja las operaciones de carga y lectura contra una sesión: LoadAnnotationData(token, manager) o LoadAnnotationData(token, encodedData), LoadAnnotationXML(token, xml), y GetAnnotationXML(token).
Exportar con anotaciones incrustadas
// PDF of all pages with annotations rendered onto them
public async Task<ActionResult> ExportPdf(string token)
{
var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();
byte[] pdf = await viewer.ExportAnnotationsToPdfAsync(token, zoom: 100, ct: Response.ClientDisconnectedToken);
return File(pdf, "application/pdf", "export.pdf");
}
// Or a ZIP of per-page PNGs
public async Task<ActionResult> ExportPngZip(string token)
{
var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();
byte[] zip = await viewer.ExportAnnotationsToPngZipAsync(token, zoom: 100, ct: Response.ClientDisconnectedToken);
return File(zip, "application/zip", "annotations-png.zip");
}Las exportaciones usan el mismo quemador que el renderizado en pantalla, y la misma decisión de licencia y marca de agua personalizada.
Anotaciones de imagen en un host intranet exportan como "Imagen bloqueada"
El visor y la exportación obtienen la imagen de una ImageAnnotation desde diferentes lugares:
| Superficie | Quién descarga la imagen | En un host intranet o de desarrollo |
|---|---|---|
| El visor en el navegador | El navegador del usuario | La imagen aparece |
ExportAnnotationsToPdfAsync / …PngZipAsync | El servidor | Un marcador de posición rojo "Image blocked" |
Cuando el servidor obtiene una imagen para incrustarla, rechaza direcciones que apuntan de nuevo a redes privadas: loopback (localhost, 127.0.0.1), 0.0.0.0, link-local (169.254.x.x), los rangos privados 10.x, 172.16–31.x y 192.168.x, NAT de nivel de operador (100.64.0.0/10) y direcciones IPv6 link-local y unique-local. Esto protege al servidor de ser usado para alcanzar sistemas internos mediante una URL de imagen manipulada, y no existe un interruptor para desactivarlo.
Una URL de imagen relativa se resuelve contra el host de la solicitud actual, por lo que en localhost, en un servidor intranet, o en cualquier sitio al que se acceda mediante una dirección privada, la imagen se muestra en el visor y la exportación contiene el marcador de posición en su lugar. Eso es la protección funcionando, no un error de renderizado. Si las exportaciones deben contener la imagen, sírvala desde un host con una dirección pública y use una URL absoluta. Una URL de imagen que no pueda resolverse en absoluto hace que esa anotación desaparezca sin error — revise los píxeles renderizados o el registro de red, no un código de estado.
Flujo de trabajo de persistencia
- Abra el documento y obtenga su token.
- Cargue XML o datos codificados previamente almacenados en ese token.
- Permita que el widget lea y edite las anotaciones de la sesión.
- Recupere XML con
GetAnnotationXML(token)cuando su aplicación decida persistir. - Exporte PDF o PNG cuando se requiera un entregable aplanado.
- Cierre la sesión del documento.
No utilice el token del visor como identificador permanente de anotaciones — desaparece con la sesión, y un reciclado del pool de aplicaciones finaliza cada sesión en un solo servidor. Almacene los datos de anotaciones asociados a sus propios identificadores de documento y versión.
Notas de seguridad y renderizado
- Las solicitudes de anotaciones usan la misma seguridad de sesión y token que las solicitudes de página.
- Valide y controle cualquier URL de imagen suministrada por el usuario.
- Las cargas útiles grandes de dibujo libre 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 | La capacidad Annotation y los cuatro indicadores CSS/script de anotación |
| Al guardar se informa un error | Expiración del token o de la sesión, y el BasePath del widget |
ctlDoc_Created (u otra función de evento) nunca se ejecuta | El nombre de la función: id del visor + _ + evento, como función global |
| Las anotaciones C# no aparecen | Los números de página son basados en uno, y los datos se cargaron en el token activo |
| Una anotación de imagen se muestra en pantalla pero no en la exportación | El host de la imagen tiene una dirección privada o de loopback — vea arriba |
| Un documento reabierto no tiene anotaciones | Persista XML o datos fuera de la sesión del visor, luego cárguelos en el nuevo token |
¿Fue útil esta página?