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):
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:
<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.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:
| Metoda | Cel |
|---|---|
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,onAnnClosedsą opcjamidocViewer({...}), podobnie jak każde wywołanie zwrotne przeglądarki.Created,Changed,DeletediPropertiessą globalnymi funkcjami wyszukiwanymi po nazwie: identyfikator przeglądarki jest prefiksem zdarzenia. Dla przeglądarki w#div_ctlDocidentyfikator toctlDoc, więc funkcje toctlDoc_Created,ctlDoc_Changed,ctlDoc_DeletedictlDoc_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):
// 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):
| Typ | Uwagi |
|---|---|
StampAnnotation | Znak tekstowy z rozmiarem czcionki, obramowaniem, kolorem; obsługuje Opacity, Rotate |
NoteAnnotation | Notatka samoprzylepna z tekstem, kolorem tła, rozmiarem czcionki, TitleColor |
RectangleAnnotation | Kolory obramowania i wypełnienia, Title/ShowTitle |
CircleAnnotation | Obramowanie i wypełnienie, ShowBorder |
EllipseAnnotation | Obramowanie i wypełnienie, ShowBorder |
TriangleAnnotation | Kolor obramowania, BackColor, ShowBorder |
LineAnnotation | Prosta linia o określonej szerokości i kolorze |
ArrowAnnotation | Linia z grotami; konfigurowalny Direction (ArrowDirection, punkty kompasu, domyślnie E) |
FreehandAnnotation | Swobodny odcinek z zakodowanych punktów FreehandData |
ImageAnnotation | Obraz 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łonek | Cel |
|---|---|
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
// 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:
| Środowisko | Kto pobiera obraz | Na hoście intranetowym lub deweloperskim |
|---|---|---|
| Podgląd w przeglądarce | Przeglądarka użytkownika | Obraz się wyświetla |
ExportAnnotationsToPdfAsync / …PngZipAsync | Serwer | Czerwony "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
- Otwórz dokument i uzyskaj jego token.
- Załaduj wcześniej zapisane XML lub zakodowane dane do tego tokenu.
- Pozwól widgetowi odczytać i edytować adnotacje sesji.
- Pobierz XML za pomocą
GetAnnotationXML(token), gdy aplikacja zdecyduje się na utrwalenie. - Wyeksportuj PDF lub PNG, gdy wymagany jest spłaszczony plik wyjściowy.
- 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
| Objaw | Sprawdź |
|---|---|
| Brak wstążki adnotacji | Funkcjonalność Annotation oraz cztery flagi CSS/skryptów adnotacji |
| Zapisywanie zgłasza błąd | Wygaszenie 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 eksporcie | Host obrazu ma prywatny lub adres loopback — zobacz powyżej |
| Ponownie otwarty dokument nie ma adnotacji | Zachowaj XML lub dane poza sesją podglądu, a następnie załaduj je do nowego tokenu |
Czy ta strona była pomocna?