Annotazioni

Aggiungi il supporto alle annotazioni al visualizzatore

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 programmaticamente e le carica in una sessione aperta. In entrambi i casi vengono renderizzate nelle pagine e possono essere incorporate nelle esportazioni PDF/PNG.

Il supporto alle annotazioni è controllato 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 completa deve includere le risorse Viewer, la barra degli strumenti Viewer, il mount Viewer e l'objViewer inizializzato; il Ribbon delle annotazioni viene quindi montato e collegato alla stessa istanza.

Emetti i bundle delle annotazioni insieme ai bundle del visualizzatore — sono controllati dalla licenza, quindi i tag compaiono solo quando la capacità è disponibile:

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

Mantieni la composizione completa del Viewer visibile 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>

Il bundle delle annotazioni genera il DOM del Ribbon all'interno di annBarMount; non è necessario copiare i suoi pulsanti o il markup del dialogo. Inizializza prima docViewer, quindi crea il Ribbon solo quando il server conferma che le annotazioni sono licenziate:

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>

Il salvataggio dal Ribbon invia i dati tramite il middleware (AnnSave), che li memorizza nella sessione del documento per pagina. Il caricamento (AnnLoad) avviene automaticamente quando una pagina con annotazioni viene renderizzata. I quattro callback onAnn* mantengono il Ribbon sincronizzato con il ciclo di vita del visualizzatore.

Aprilo e chiudilo da qualsiasi barra degli strumenti del Viewer gestita dall'host:

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

L'API pubblica del Ribbon è:

MetodoScopo
attach(objViewer)Collega il Ribbon al viewer inizializzato; richiesto una sola volta
open() / close()Entra o esci dalla modalità di 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 la disponibilità dei controlli di salvataggio/eliminazione dopo le modifiche dell'host
headerSlot()Ottieni lo slot di estensione opzionale dell'intestazione per i controlli gestiti dall'host

onStatus, onToast, onLayout, onEditStart e onEditEnd sono callback opzionali dell'host. L'oggetto endpoints può inoltre fornire exportPdf, exportPng, imageUpload e imageList; i controlli senza un endpoint configurato rimangono nascosti. Per la sequenza di avvio combinata di Viewer, Search e Annotation, vedere Guida rapida.

Il bundle delle annotazioni aggiunge gli strumenti di authoring del browser, ma i dati appartengono ancora alla sessione del documento lato server identificata dal token. Riaprire la sorgente crea una nuova sessione; persisti l'XML o l'involucro di annotazione codificato nella tua applicazione se le annotazioni devono sopravvivere oltre la durata della sessione.

Crea 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/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();
});

Tipi di annotazione

Tutti i tipi risiedono in Doconut.Annotations ed ereditano da BaseAnnotation (numero di pagina + Rectangle di delimitazione):

TipoNote
StampAnnotationTimbro di testo con dimensione del carattere, bordo, colore; supporta Opacity, Rotate
NoteAnnotationNota adesiva con testo, colore di sfondo, dimensione del carattere, TitleColor
RectangleAnnotationBordo + colori di riempimento, Title/ShowTitle
CircleAnnotationBordo + riempimento, ShowBorder
EllipseAnnotationBordo + riempimento, ShowBorder
TriangleAnnotationColore del bordo, BackColor, ShowBorder
LineAnnotationLinea retta con spessore e colore
ArrowAnnotationLinea con punta di freccia; Direction impostabile (tipo ArrowDirection, punti cardinali, default E)
FreehandAnnotationTratto libero da punti FreehandData codificati
ImageAnnotationImmagine da un URL. Un URL relativo viene risolto rispetto all'host della richiesta quando l'annotazione viene aggiunta (solo il recupero dell'immagine avviene al momento del rendering) — deve essere raggiungibile dal server (ad esempio un file sotto wwwroot servito da UseStaticFiles)

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)Stringa di dati di annotazione codificata — un involucro Base64 (ciò che il widget consuma)
GetAnnotationXml()Forma XML

Viewer rispecchia le operazioni di caricamento/lettura rispetto a una sessione: LoadAnnotationData(token, manager) o LoadAnnotationData(token, encodedData) (l'involucro Base64 da GetAnnotationData()), LoadAnnotationXML(token, xml), GetAnnotationXML(token).

Esporta con annotazioni incorporate

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");
});

Le esportazioni usano lo stesso processo di rendering di quello a schermo, quindi ciò che gli utenti vedono è ciò che il file contiene.

Flusso di lavoro di persistenza

  1. Apri il documento e ottieni il suo token.
  2. Carica l'XML o i 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/PNG quando è richiesto un deliverable appiattito.
  6. Chiudi la sessione del documento.

Non utilizzare il token opaco del viewer come identificatore permanente delle annotazioni. Associa i dati delle annotazioni persistiti ai tuoi identificatori di documento e versione.

Note su sicurezza e rendering

  • Le richieste di annotazione usano la stessa sicurezza sessione/token delle richieste di pagina.
  • Un URL ImageAnnotation relativo è risolto dall'host della richiesta e deve rimanere raggiungibile dal server al momento del rendering.
  • Convalida e controlla qualsiasi URL di immagine fornito dall'utente per evitare falsificazioni di richieste lato server.
  • Le esportazioni applicano la stessa decisione di licenza/acqua personalizzata del rendering delle pagine a schermo.
  • Carichi di dati freehand grandi e esportazioni ad alta risoluzione aumentano l'uso della memoria; testa documenti realistici e valori di zoom.

Risoluzione dei problemi

SintomoVerifica
Il ribbon delle annotazioni è mancanteCapacità Annotation e i quattro flag CSS/script delle annotazioni
Il callback di salvataggio segnala un erroreScadenza del token/sessione e middleware BasePath
Le annotazioni C# non appaionoLa numerazione delle pagine parte da 1 e i dati sono stati caricati nel token attivo
L'annotazione immagine appare a schermo ma non nell'esportazioneIl server può raggiungere l'URL dell'immagine durante il rendering
Il documento riaperto non ha annotazioniPersisti XML/dati al di fuori della sessione del viewer, poi caricali nel nuovo token

Questa pagina è stata utile?