Анотації

Додайте підтримку анотацій до переглядача

Анотації в Doconut працюють у два напрямки: користувачі малюють їх у віджеті браузера, а сервер зберігає їх по сторінках, або ваш код створює їх і завантажує у відкриту сесію. У будь‑якому випадку вони відображаються на сторінках і можуть бути вбудовані у PDF або PNG експорт.

Підтримка анотацій обмежена можливістю ліцензії Annotation (надається автоматично при активній тимчасовій ліцензії).

Увімкнути інтерфейс анотацій

Анотація — це модуль Viewer, а не окрема панель інструментів. Сторінка повинна включати ресурси Viewer, панель інструментів Viewer, монтаж Viewer та ініціалізований objViewer; потім стрічка анотацій монтується та приєднується до того ж екземпляра.

Виведіть файли анотацій разом з файлами переглядача. Вони обмежені ліцензією, тому їхні теги з’являються лише коли можливість надана. У MVC 5 створюйте теги у контролері (у представленнях немає @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;

Збережіть повну композицію Viewer у розмітці:

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>

Файли анотацій генерують стрічку всередині annBarMount; ви не копіюєте її кнопки чи діалогові вікна. Спочатку ініціалізуйте docViewer, а потім створюйте стрічку лише коли сервер підтвердить, що анотація ліцензована:

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>

Без можливості ReferenceScripts не включає скрипт стрічки, тому серверна умова if захищає виклик doconutAnnotationBar(...) від поломки сторінки. У Web Forms напишіть таку ж захисну умову: <% if (IsAnnotationEnabled) { %> … <% } %>.

Збереження зі стрічки надсилає дані до DocImage.axd (AnnSave), який зберігає їх у сесії документа по сторінках. Завантаження (AnnLoad) відбувається автоматично, коли сторінка з анотаціями відображається.

Відкривайте та закривайте стрічку зі своєї панелі інструментів за допомогою annBar.open() та annBar.close(). Її публічний API:

MethodPurpose
attach(objViewer)Підключає стрічку до ініціалізованого переглядача; потрібно один раз
open() / close()Відкрити або закрити режим редагування анотацій
reset()Повертає стрічку у закрите, не редагувальне стан
isOpen() / annotating()Отримати стан стрічки / стан редагування анотацій у переглядачі
reopenEditable()Перезавантажити анотації поточної сторінки як редаговані об’єкти
updateActionState()Оновити елементи управління збереженням та видаленням після змін хоста
headerSlot()Опціональний слот заголовка для елементів, керованих хостом

onStatus, onToast, onLayout, onEditStart та onEditEnd — це необов’язкові зворотні виклики хоста. Об’єкт endpoints також може містити exportPdf, exportPng, imageUpload та imageList; елементи без налаштованого кінцевого пункту залишаються прихованими.

Два типи зворотних викликів, підключені двома способами

Код анотацій використовує два стилі зворотних викликів, і кожен безшумно не працює, коли підключений іншим способом:

  • onAnnLoaded, onAnnSaved, onAnnSaveError, onAnnClosed є опціями docViewer({...}), як і будь‑який зворотний виклик переглядача.
  • Created, Changed, Deleted та Properties є глобальними функціями, що знаходяться за іменем: ідентифікатор переглядача додається як префікс до події. Для переглядача в #div_ctlDoc ідентифікатор — ctlDoc, тому функції називаються ctlDoc_Created, ctlDoc_Changed, ctlDoc_Deleted та ctlDoc_Properties. Помилково написана або перейменована функція просто ніколи не викликається.

SaveAnnotations() не повертає, чи було збережено: результат надходить через onAnnSaved або onAnnSaveError. Код, який читає його повернене значення, ніколи не скидає прапорець «незбережені зміни».

Створення анотацій у C#

Отримайте менеджер, прив’язаний до відкритої сесії, додайте анотації та завантажте їх (з using Doconut.Annotations; для типів та using System.Drawing; для Rectangle і 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);
}

Типи анотацій

Усі типи знаходяться в Doconut.Annotations і успадковуються від BaseAnnotation (номер сторінки та обмежувальний Rectangle):

ТипПримітки
StampAnnotationТекстова печатка з розміром шрифту, рамкою, кольором; підтримує Opacity, Rotate
NoteAnnotationНотатка‑стикер з текстом, кольором фону, розміром шрифту, TitleColor
RectangleAnnotationРамка та кольори заповнення, Title/ShowTitle
CircleAnnotationКоло з рамкою та заповненням, ShowBorder
EllipseAnnotationЕліпс з рамкою та заповненням, ShowBorder
TriangleAnnotationТрикутник з кольором рамки, BackColor, ShowBorder
LineAnnotationПряма лінія з шириною та кольором
ArrowAnnotationЛінія зі стрілкою; налаштовується Direction (ArrowDirection, напрямки компаса, за замовчуванням E)
FreehandAnnotationВільний штрих з закодованих точок FreehandData
ImageAnnotationЗображення за URL. Відносний URL розв’язується відносно хоста поточного запиту, коли анотація додається — дивіться примітку про експорт нижче

API AnnotationManager

ЧленПризначення
Add(BaseAnnotation)Додати анотацію у чергу
GetAnnotations() / GetAnnotations(int page)Переглянути, що містить менеджер
ClearAnnotations() / ClearAnnotations(int page)Видалити всі / по сторінці
GetAnnotationData() / GetAnnotationData(int page)Закодовані дані анотацій — обгортка, яку споживає віджет
GetAnnotationXml()XML‑форма

Viewer відтворює операції завантаження та читання щодо сесії: LoadAnnotationData(token, manager) або LoadAnnotationData(token, encodedData), LoadAnnotationXML(token, xml) та GetAnnotationXML(token).

Експорт з вбудованими анотаціями

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

Експорти використовують той самий процес вбудовування, що й відображення на екрані, а також ту ж ліцензію та рішення щодо користувацького водяного знака.

Анотації зображень на інтранет‑хості експортуються як «Image blocked»

Переглядач і процес експорту отримують зображення ImageAnnotation з різних джерел:

СередовищеХто завантажує зображенняНа інтранет‑хості або сервері розробки
Переглядач у браузеріБраузер користувачаЗображення відображається
ExportAnnotationsToPdfAsync / …PngZipAsyncСерверЧервоний "Image blocked" заповнювач

Коли сервер завантажує зображення для вбудовування, він відхиляє адреси, що повертаються у приватні мережі: loopback (localhost, 127.0.0.1), 0.0.0.0, link‑local (169.254.x.x), приватні діапазони 10.x, 172.16–31.x та 192.168.x, carrier‑grade NAT (100.64.0.0/10), а також IPv6 link‑local і unique‑local адреси. Це захищає сервер від використання для доступу до внутрішніх систем через спеціально сформовану URL‑адресу зображення, і вимкнути цей захист неможливо.

Відносна URL‑адреса зображення розв’язується відносно хоста поточного запиту, тому на localhost, на інтранет‑сервері або на будь‑якому сайті, доступному через приватну адресу, зображення відображається у переглядачі, а експорт містить лише заповнювач. Це робота захисного механізму, а не збій рендерингу. Якщо в експорті має бути зображення, розмістіть його на хості з публічною адресою та використайте абсолютну URL‑адресу. URL‑адреса зображення, яку взагалі не вдається розв’язати, призведе до того, що ця анотація зникне без помилки — перевіряйте відрендерені пікселі або мережевий журнал, а не код статусу.

Робочий процес збереження

  1. Відкрийте документ і отримайте його токен.
  2. Завантажте раніше збережений XML або закодовані дані у цей токен.
  3. Дайте віджету прочитати та редагувати анотації сеансу.
  4. Отримайте XML за допомогою GetAnnotationXML(token), коли ваш застосунок вирішить зберегти дані.
  5. Експортуйте PDF або PNG, коли потрібен сплощений результат.
  6. Закрийте сеанс документа.

Не використовуйте токен переглядача як постійний ідентифікатор анотації — він зникає разом із сеансом, а перезапуск пулу застосунків завершує кожен сеанс на окремому сервері. Зберігайте дані анотацій у зв’язку зі своїми ідентифікаторами документів та їх версій.

Примітки щодо безпеки та рендерингу

  • Запити анотацій використовують ту ж безпеку сеансу та токену, що й запити сторінок.
  • Перевіряйте та контролюйте будь‑яку URL‑адресу зображення, яку надає користувач.
  • Великі довільні дані та експорти високої роздільної здатності збільшують використання пам’яті; тестуйте реалістичні документи та значення масштабування.

Усунення проблем

СимптомПеревірка
Панель інструментів анотацій відсутняНаявність можливості Annotation та чотири прапорці CSS/скриптів анотації
При збереженні виникає помилкаТермін дії токену або сеансу, а також BasePath віджету
ctlDoc_Created (або інша функція події) ніколи не виконуєтьсяПравильна назва функції: ідентифікатор переглядача + _ + назва події, оголошена глобально
C# анотації не відображаютьсяНомери сторінок починаються з 1, і дані були завантажені в активний токен
Анотація зображення відображається на екрані, але не в експортіХост зображення має приватну або loopback‑адресу — див. вище
При повторному відкритті документа анотації відсутніЗбережіть XML або дані поза сеансом переглядача, потім завантажте їх у новий токен

Чи була ця сторінка корисною?