Annotazioni
Add annotation support to the viewer
Le annotazioni in Doconut funzionano in due direzioni: gli utenti le disegnano nel widget del browser e il server le conserva per pagina, oppure il tuo codice le crea e le carica in una sessione aperta. In entrambi i casi vengono visualizzate nelle pagine e possono essere incorporate nelle esportazioni PDF o PNG.
Il supporto alle annotazioni è limitato dalla capacità di licenza Annotation (concessa automaticamente con una licenza Temporanea attiva).
Abilita l'interfaccia delle annotazioni
L'annotazione è un modulo Viewer, non una barra degli strumenti autonoma. La pagina deve includere le risorse Viewer, la barra degli strumenti Viewer, il mount Viewer e un objViewer inizializzato; il Ribbon delle annotazioni viene quindi montato e collegato alla stessa istanza.
Emetti i file delle annotazioni insieme ai file del viewer. Sono soggetti a licenza, quindi i loro tag compaiono solo quando la capacità è concessa. In MVC 5, genera i tag nel controller (le viste non hanno @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;Mantieni la composizione completa del Viewer nel markup:
<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>I file delle annotazioni generano il Ribbon all'interno di annBarMount; non devi copiare i suoi pulsanti o dialoghi. Inizializza prima docViewer, quindi crea il Ribbon solo quando il server conferma che le annotazioni sono licenziate:
@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>Senza la capacità, ReferenceScripts omette lo script del Ribbon, quindi l'if lato server impedisce che una chiamata non protetta a doconutAnnotationBar(...) rompa la pagina. In Web Forms, scrivi la stessa guardia come <% if (IsAnnotationEnabled) { %> … <% } %>.
Il salvataggio dal Ribbon invia i dati a DocImage.axd (AnnSave), che li memorizza nella sessione del documento per pagina. Il caricamento (AnnLoad) avviene automaticamente quando una pagina con annotazioni viene renderizzata.
Apri e chiudi il Ribbon dalla tua barra degli strumenti con annBar.open() e annBar.close(). La sua API pubblica:
| Metodo | Scopo |
|---|---|
attach(objViewer) | Collega il Ribbon al viewer inizializzato; richiesto una sola volta |
open() / close() | Entra o esci dalla modifica delle annotazioni |
reset() | Riporta il Ribbon al suo stato chiuso, non in modifica |
isOpen() / annotating() | Leggi lo stato del Ribbon / lo stato di modifica delle annotazioni del viewer |
reopenEditable() | Ricarica le annotazioni della pagina corrente come oggetti modificabili |
updateActionState() | Aggiorna i controlli di salvataggio e cancellazione dopo le modifiche dell'host |
headerSlot() | Lo slot opzionale dell'intestazione per i controlli gestiti dall'host |
onStatus, onToast, onLayout, onEditStart e onEditEnd sono callback opzionali dell'host. L'oggetto endpoints può anche fornire exportPdf, exportPng, imageUpload e imageList; i controlli senza un endpoint configurato rimangono nascosti.
Due tipi di callback, collegati in due modi
Il codice di annotazione utilizza due stili di callback, e ciascuno fallisce silenziosamente quando collegato nell'altro modo:
onAnnLoaded,onAnnSaved,onAnnSaveError,onAnnClosedsono opzioni didocViewer({...}), come ogni callback del visualizzatore.Created,Changed,Deleted, ePropertiessono funzioni globali trovate per nome: l'ID del visualizzatore è prefisso dell'evento. Per il visualizzatore in#div_ctlDocl'ID èctlDoc, quindi le funzioni sonoctlDoc_Created,ctlDoc_Changed,ctlDoc_DeletedectlDoc_Properties. Una funzione scritta in modo errato o rinominata non viene semplicemente mai chiamata.
SaveAnnotations() non restituisce se ha salvato: il risultato arriva tramite onAnnSaved o onAnnSaveError. Il codice che legge il valore di ritorno non cancella mai il flag delle "modifiche non salvate".
Creare annotazioni in C#
Ottieni un manager legato alla sessione aperta, aggiungi annotazioni e caricale (con using Doconut.Annotations; per i tipi e using System.Drawing; per Rectangle e 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);
}Tipi di annotazione
Tutti i tipi risiedono in Doconut.Annotations ed ereditano da BaseAnnotation (numero di pagina più Rectangle di delimitazione):
| Tipo | Note |
|---|---|
StampAnnotation | Marcatura di testo con dimensione del carattere, bordo, colore; supporta Opacity, Rotate |
NoteAnnotation | Nota adesiva con testo, colore di sfondo, dimensione del carattere, TitleColor |
RectangleAnnotation | Colori del bordo e di riempimento, Title/ShowTitle |
CircleAnnotation | Bordo e riempimento, ShowBorder |
EllipseAnnotation | Bordo e riempimento, ShowBorder |
TriangleAnnotation | Colore del bordo, BackColor, ShowBorder |
LineAnnotation | Linea retta con spessore e colore |
ArrowAnnotation | Linea con punta a freccia; Direction impostabile (ArrowDirection, punti cardinali, default E) |
FreehandAnnotation | Tratto libero da punti codificati FreehandData |
ImageAnnotation | Immagine da un URL. Un URL relativo viene risolto rispetto all'host della richiesta corrente quando l'annotazione viene aggiunta — vedi la nota sull'esportazione sotto |
L'API di AnnotationManager
| Membro | Scopo |
|---|---|
Add(BaseAnnotation) | Accoda un'annotazione |
GetAnnotations() / GetAnnotations(int page) | Ispeziona ciò che il manager contiene |
ClearAnnotations() / ClearAnnotations(int page) | Rimuove tutti / per pagina |
GetAnnotationData() / GetAnnotationData(int page) | Dati di annotazione codificati — l'involucro consumato dal widget |
GetAnnotationXml() | Forma XML |
Viewer rispecchia le operazioni di caricamento e lettura su una sessione: LoadAnnotationData(token, manager) o LoadAnnotationData(token, encodedData), LoadAnnotationXML(token, xml) e GetAnnotationXML(token).
Esporta con annotazioni incorporate
// 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");
}Le esportazioni usano lo stesso motore di incorporamento della resa a schermo, e la stessa decisione di licenza e filigrana personalizzata.
Annotazioni immagine su un host intranet esportano come "Image blocked"
Il visualizzatore e l'esportazione recuperano l'immagine di un ImageAnnotation da luoghi diversi:
| Contesto | Chi scarica l'immagine | Su un host intranet o di sviluppo |
|---|---|---|
| Il visualizzatore nel browser | Il browser dell'utente | L'immagine appare |
ExportAnnotationsToPdfAsync / …PngZipAsync | Il server | Un segnaposto rosso "Image blocked" |
Quando il server recupera un'immagine per incorporarla, rifiuta gli indirizzi che puntano nuovamente a reti private: loopback (localhost, 127.0.0.1), 0.0.0.0, link-local (169.254.x.x), gli intervalli privati 10.x, 172.16–31.x e 192.168.x, NAT di tipo carrier‑grade (100.64.0.0/10) e gli indirizzi IPv6 link‑local e unique‑local. Questo protegge il server dall'essere usato per raggiungere sistemi interni tramite un URL immagine manipolato, e non esiste un'opzione per disattivarlo.
Un URL immagine relativo viene risolto rispetto all'host della richiesta corrente, quindi su localhost, su un server intranet o su qualsiasi sito raggiunto tramite un indirizzo privato, l'immagine viene mostrata nel visualizzatore e l'esportazione contiene invece il segnaposto. Questo è il meccanismo di protezione in azione, non un errore di rendering. Se le esportazioni devono contenere l'immagine, servila da un host con un indirizzo pubblico e utilizza un URL assoluto. Un URL immagine che non può essere risolto affatto fa scomparire quell'annotazione senza generare un errore — controlla i pixel renderizzati o il log di rete, non il codice di stato.
Flusso di lavoro di persistenza
- Apri il documento e ottieni il suo token.
- Carica XML o dati codificati precedentemente memorizzati in quel token.
- Consenti al widget di leggere e modificare le annotazioni della sessione.
- Recupera l'XML con
GetAnnotationXML(token)quando la tua applicazione decide di persistere. - Esporta PDF o PNG quando è richiesto un deliverable appiattito.
- Chiudi la sessione del documento.
Non utilizzare il token del visualizzatore come identificatore permanente delle annotazioni — scade con la sessione, e un riciclo del pool di applicazioni termina ogni sessione su un singolo server. Memorizza i dati delle annotazioni associandoli ai tuoi identificatori di documento e versione.
Note su sicurezza e rendering
- Le richieste di annotazione utilizzano la stessa sicurezza di sessione e token delle richieste di pagina.
- Convalida e controlla qualsiasi URL immagine fornito dall'utente.
- Carichi di disegni a mano libera di grandi dimensioni e esportazioni ad alta risoluzione aumentano l'uso di memoria; testa documenti realistici e valori di zoom.
Risoluzione dei problemi
| Sintomo | Verifica |
|---|---|
| La barra degli strumenti delle Annotazioni è mancante | La capacità Annotation e i quattro flag CSS/script delle annotazioni |
| Il salvataggio segnala un errore | Scadenza del token o della sessione, e il BasePath del widget |
ctlDoc_Created (o un'altra funzione evento) non viene mai eseguita | Il nome della funzione: id del visualizzatore + _ + evento, come funzione globale |
| Le annotazioni C# non appaiono | I numeri di pagina partono da 1, e i dati sono stati caricati nel token attivo |
| Un'annotazione immagine è visibile sullo schermo ma non nell'esportazione | L'host dell'immagine ha un indirizzo privato o di loopback — vedi sopra |
| Un documento riaperto non ha annotazioni | Persisti XML o dati al di fuori della sessione del visualizzatore, quindi caricali nel nuovo token |
Questa pagina è stata utile?