Adnotacje

Dodaj obsługę adnotacji w przeglądarce

Adnotacje w Doconut działają w dwóch kierunkach: użytkownicy rysują je w widżecie przeglądarki, a serwer przechowuje je per strona, lub Twój kod tworzy je i ładuje do otwartej sesji. W każdym przypadku są renderowane na stronach i mogą być wbudowane w eksporty PDF lub PNG.

Wsparcie adnotacji jest uzależnione od możliwości licencyjnej Annotation (przyznawane automatycznie przy aktywnej licencji tymczasowej).

Włącz interfejs UI adnotacji

Adnotacja jest modułem Viewer, a nie samodzielnym paskiem narzędzi. Strona musi zawierać zasoby Viewer, pasek narzędzi Viewer, miejsce montażu Viewer oraz zainicjowany objViewer; wówczas wstążka Annotation jest montowana i podłączana do tej samej instancji.

Emituj pliki adnotacji wraz z plikami przeglądarki. Są one uzależnione od licencji, więc ich tagi pojawiają się tylko wtedy, gdy możliwość jest przyznana. W MVC 5, buduj tagi w kontrolerze (widoki nie mają @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;

Zachowaj pełną kompozycję Viewer w znacznikach:

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>

Pliki Annotation generują wstążkę wewnątrz annBarMount; nie kopiuj jej przycisków ani dialogów. Zainicjuj najpierw docViewer, a następnie utwórz wstążkę tylko wtedy, gdy serwer potwierdzi, że Annotation jest licencjonowane:

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>

Bez tej możliwości, ReferenceScripts pomija skrypt wstążki, więc warunek po stronie serwera if chroni niezabezpieczone wywołanie doconutAnnotationBar(...) przed zepsuciem strony. W Web Forms, napisz tę samą ochronę jako <% if (IsAnnotationEnabled) { %> … <% } %>.

Zapisywanie z wstążki wysyła dane do DocImage.axd (AnnSave), które przechowuje je w sesji dokumentu per strona. Ładowanie (AnnLoad) odbywa się automatycznie, gdy strona z adnotacjami jest renderowana.

Otwieraj i zamykaj wstążkę z własnego paska narzędzi przy użyciu annBar.open() i annBar.close(). Jej publiczne API:

MetodaCel
attach(objViewer)Połącz wstążkę z zainicjowanym przeglądarką; wymagane jednorazowo
open() / close()Rozpocznij lub zakończ edycję adnotacji
reset()Przywróć wstążkę do zamkniętego, nieedytowanego stanu
isOpen() / annotating()Odczytaj stan wstążki / stan edycji adnotacji w przeglądarce
reopenEditable()Ponownie załaduj adnotacje bieżącej strony jako edytowalne obiekty
updateActionState()Odśwież przyciski zapisu i usuwania po zmianach hosta
headerSlot()Opcjonalny slot nagłówka dla kontrolerów zarządzanych przez hosta

onStatus, onToast, onLayout, onEditStart i onEditEnd są opcjonalnymi wywołaniami zwrotnymi hosta. Obiekt endpoints może również udostępniać exportPdf, exportPng, imageUpload i imageList; kontrolki bez skonfigurowanego punktu końcowego pozostają ukryte.

Dwa rodzaje wywołań zwrotnych, podłączone na dwa sposoby

Kod adnotacji używa dwóch stylów wywołań zwrotnych i każdy z nich cicho nie działa, gdy jest podłączony w odwrotny sposób:

  • onAnnLoaded, onAnnSaved, onAnnSaveError, onAnnClosed są opcjami docViewer({...}), podobnie jak każde wywołanie zwrotne przeglądarki.
  • Created, Changed, Deleted i Properties są globalnymi funkcjami wyszukiwanymi po nazwie: identyfikator przeglądarki jest prefiksem zdarzenia. Dla przeglądarki w #div_ctlDoc identyfikator to ctlDoc, więc funkcje to ctlDoc_Created, ctlDoc_Changed, ctlDoc_Deleted i ctlDoc_Properties. Błędnie napisana lub przemianowana funkcja po prostu nigdy nie zostaje wywołana.

SaveAnnotations() nie zwraca informacji, czy zapisano: wynik przychodzi przez onAnnSaved lub onAnnSaveError. Kod, który odczytuje jej wartość zwracaną, nigdy nie usuwa flagi „niezapisane zmiany”.

Tworzenie adnotacji w C#

Uzyskaj menedżera powiązanego z otwartą sesją, dodaj adnotacje i załaduj je (z using Doconut.Annotations; dla typów oraz using System.Drawing; dla Rectangle i 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);
}

Typy adnotacji

Wszystkie typy znajdują się w Doconut.Annotations i dziedziczą po BaseAnnotation (numer strony plus ograniczający Rectangle):

TypUwagi
StampAnnotationZnak tekstowy z rozmiarem czcionki, obramowaniem, kolorem; obsługuje Opacity, Rotate
NoteAnnotationNotatka samoprzylepna z tekstem, kolorem tła, rozmiarem czcionki, TitleColor
RectangleAnnotationKolory obramowania i wypełnienia, Title/ShowTitle
CircleAnnotationObramowanie i wypełnienie, ShowBorder
EllipseAnnotationObramowanie i wypełnienie, ShowBorder
TriangleAnnotationKolor obramowania, BackColor, ShowBorder
LineAnnotationProsta linia o określonej szerokości i kolorze
ArrowAnnotationLinia z grotami; konfigurowalny Direction (ArrowDirection, punkty kompasu, domyślnie E)
FreehandAnnotationSwobodny odcinek z zakodowanych punktów FreehandData
ImageAnnotationObraz z URL. Względny URL jest rozwiązywany względem hosta bieżącego żądania, gdy adnotacja jest dodawana — zobacz notatkę o eksporcie poniżej

API AnnotationManager

CzłonekCel
Add(BaseAnnotation)Umieść adnotację w kolejce
GetAnnotations() / GetAnnotations(int page)Sprawdź, co menedżer przechowuje
ClearAnnotations() / ClearAnnotations(int page)Usuń wszystkie / per strona
GetAnnotationData() / GetAnnotationData(int page)Zakodowane dane adnotacji — obudowa, którą konsumuje widget
GetAnnotationXml()Forma XML

Viewer odzwierciedla operacje ładowania i odczytu względem sesji: LoadAnnotationData(token, manager) lub LoadAnnotationData(token, encodedData), LoadAnnotationXML(token, xml) oraz GetAnnotationXML(token).

Eksport z wbudowanymi adnotacjami

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

Eksporty używają tego samego mechanizmu wypalania co renderowanie na ekranie oraz tej samej licencji i decyzji o niestandardowym znaku wodnym.

Adnotacje obrazów na hoście intranetowym eksportowane jako „Image blocked”

Podgląd i eksport pobierają obraz ImageAnnotation z różnych miejsc:

ŚrodowiskoKto pobiera obrazNa hoście intranetowym lub deweloperskim
Podgląd w przeglądarcePrzeglądarka użytkownikaObraz się wyświetla
ExportAnnotationsToPdfAsync / …PngZipAsyncSerwerCzerwony "Obraz zablokowany" symbol zastępczy

Gdy serwer pobiera obraz, aby go wbudować, odrzuca adresy prowadzące z powrotem do prywatnych sieci: loopback (localhost, 127.0.0.1), 0.0.0.0, link‑local (169.254.x.x), prywatne zakresy 10.x, 172.16–31.x i 192.168.x, carrier‑grade NAT (100.64.0.0/10) oraz adresy IPv6 link‑local i unique‑local. Chroni to serwer przed użyciem do dotarcia do wewnętrznych systemów poprzez spreparowany URL obrazu i nie ma przełącznika, który można wyłączyć.

Względny URL obrazu jest rozwiązywany względem hosta bieżącego żądania, więc na localhost, na serwerze intranetowym lub na dowolnej stronie dostępnej przez prywatny adres, obraz wyświetla się w podglądzie, a eksport zawiera zamiast niego symbol zastępczy. To działanie ochrony, a nie błąd renderowania. Jeśli eksporty muszą zawierać obraz, udostępnij go z hosta o publicznym adresie i użyj pełnego (absolutnego) URL. URL obrazu, którego nie da się w ogóle rozwiązać, powoduje zniknięcie tej jednej adnotacji bez błędu — sprawdź wyrenderowane piksele lub log sieciowy, a nie kod statusu.

Przebieg utrwalania

  1. Otwórz dokument i uzyskaj jego token.
  2. Załaduj wcześniej zapisane XML lub zakodowane dane do tego tokenu.
  3. Pozwól widgetowi odczytać i edytować adnotacje sesji.
  4. Pobierz XML za pomocą GetAnnotationXML(token), gdy aplikacja zdecyduje się na utrwalenie.
  5. Wyeksportuj PDF lub PNG, gdy wymagany jest spłaszczony plik wyjściowy.
  6. Zamknij sesję dokumentu.

Nie używaj tokenu podglądu jako trwałego identyfikatora adnotacji — wygasa wraz z sesją, a recykling puli aplikacji kończy każdą sesję na pojedynczym serwerze. Przechowuj dane adnotacji względem własnych identyfikatorów dokumentu i wersji.

Uwagi dotyczące bezpieczeństwa i renderowania

  • Żądania adnotacji używają takiego samego bezpieczeństwa sesji i tokenu jak żądania stron.
  • Waliduj i kontroluj każdy URL obrazu podany przez użytkownika.
  • Duże ładunki odręczne i eksporty wysokiej rozdzielczości zwiększają zużycie pamięci; testuj realistyczne dokumenty i wartości przybliżenia.

Rozwiązywanie problemów

ObjawSprawdź
Brak wstążki adnotacjiFunkcjonalność Annotation oraz cztery flagi CSS/skryptów adnotacji
Zapisywanie zgłasza błądWygaszenie tokenu lub sesji oraz BasePath widgetu
ctlDoc_Created (lub inna funkcja zdarzenia) nigdy nie uruchamia sięNazwa funkcji: id podglądu + _ + zdarzenie, jako funkcja globalna
Adnotacje C# nie pojawiają sięNumery stron zaczynają się od 1, a dane zostały załadowane do aktywnego tokenu
Adnotacja obrazu wyświetla się na ekranie, ale nie w eksporcieHost obrazu ma prywatny lub adres loopback — zobacz powyżej
Ponownie otwarty dokument nie ma adnotacjiZachowaj XML lub dane poza sesją podglądu, a następnie załaduj je do nowego tokenu

Czy ta strona była pomocna?