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

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

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>

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

MetodoScopo
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, onAnnClosed sono opzioni di docViewer({...}), come ogni callback del visualizzatore.
  • Created, Changed, Deleted, e Properties sono funzioni globali trovate per nome: l'ID del visualizzatore è prefisso dell'evento. Per il visualizzatore in #div_ctlDoc l'ID è ctlDoc, quindi le funzioni sono ctlDoc_Created, ctlDoc_Changed, ctlDoc_Deleted e ctlDoc_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):

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

TipoNote
StampAnnotationMarcatura di testo con dimensione del carattere, bordo, colore; supporta Opacity, Rotate
NoteAnnotationNota adesiva con testo, colore di sfondo, dimensione del carattere, TitleColor
RectangleAnnotationColori del bordo e di riempimento, Title/ShowTitle
CircleAnnotationBordo e riempimento, ShowBorder
EllipseAnnotationBordo e riempimento, ShowBorder
TriangleAnnotationColore del bordo, BackColor, ShowBorder
LineAnnotationLinea retta con spessore e colore
ArrowAnnotationLinea con punta a freccia; Direction impostabile (ArrowDirection, punti cardinali, default E)
FreehandAnnotationTratto libero da punti codificati FreehandData
ImageAnnotationImmagine 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

MembroScopo
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

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

ContestoChi scarica l'immagineSu un host intranet o di sviluppo
Il visualizzatore nel browserIl browser dell'utenteL'immagine appare
ExportAnnotationsToPdfAsync / …PngZipAsyncIl serverUn 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

  1. Apri il documento e ottieni il suo token.
  2. Carica XML o dati codificati precedentemente memorizzati in quel token.
  3. Consenti al widget di leggere e modificare le annotazioni della sessione.
  4. Recupera l'XML con GetAnnotationXML(token) quando la tua applicazione decide di persistere.
  5. Esporta PDF o PNG quando è richiesto un deliverable appiattito.
  6. 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

SintomoVerifica
La barra degli strumenti delle Annotazioni è mancanteLa capacità Annotation e i quattro flag CSS/script delle annotazioni
Il salvataggio segnala un erroreScadenza del token o della sessione, e il BasePath del widget
ctlDoc_Created (o un'altra funzione evento) non viene mai eseguitaIl nome della funzione: id del visualizzatore + _ + evento, come funzione globale
Le annotazioni C# non appaionoI numeri di pagina partono da 1, e i dati sono stati caricati nel token attivo
Un'annotazione immagine è visibile sullo schermo ma non nell'esportazioneL'host dell'immagine ha un indirizzo privato o di loopback — vedi sopra
Un documento riaperto non ha annotazioniPersisti XML o dati al di fuori della sessione del visualizzatore, quindi caricali nel nuovo token

Questa pagina è stata utile?