Аннотации

Добавьте поддержку аннотаций в просмотрщик

Аннотации в Doconut работают в двух направлениях: пользователи рисуют их в браузерном виджете, а сервер сохраняет их постранично, либо ваш код создает их программно и загружает в открытую сессию. В любом случае они отображаются на страницах и могут быть «выпечены» в экспорты PDF/PNG.

Поддержка аннотаций ограничена возможностью лицензии Annotation (выдаётся автоматически при активной временной лицензии).

Включить пользовательский интерфейс аннотаций

Annotation — модуль Viewer, а не отдельная панель инструментов. Полная страница должна включать ресурсы Viewer, панель инструментов Viewer, монтирование Viewer и инициализированный objViewer; затем лента Annotation монтируется и привязывается к тому же экземпляру.

Эмитируйте пакеты аннотаций вместе с пакетами просмотрщика — они лицензируются, поэтому теги появляются только при доступности возможности:

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

Сохраните полную композицию 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>

Пакет Annotation генерирует DOM‑ленты внутри annBarMount; копировать её кнопки или разметку диалогов не требуется. Сначала инициализируйте docViewer, затем создайте ленту только после того, как сервер подтвердит наличие лицензии Annotation:

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>

Сохранение из ленты отправляет данные через middleware (AnnSave), который сохраняет их в сессии документа постранично. Загрузка (AnnLoad) происходит автоматически, когда страница с аннотациями отрисовывается. Четыре обратных вызова onAnn* синхронизируют ленту с жизненным циклом просмотрщика.

Открыть и закрыть её можно из любой панели инструментов Viewer, принадлежащей хосту:

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

Публичный API ленты:

МетодНазначение
attach(objViewer)Подключить ленту к инициализированному просмотрщику; требуется один раз
open() / close()Начать или завершить редактирование аннотаций
reset()Вернуть ленту в закрытое состояние без редактирования
isOpen() / annotating()Получить состояние ленты / состояние редактирования аннотаций в просмотрщике
reopenEditable()Перезагрузить аннотации текущей страницы как редактируемые объекты
updateActionState()Обновить доступность элементов управления сохранением/удалением после изменений хоста
headerSlot()Получить необязательный слот расширения заголовка для элементов управления, принадлежащих хосту

onStatus, onToast, onLayout, onEditStart и onEditEnd — необязательные обратные вызовы хоста. Объект endpoints может дополнительно предоставлять exportPdf, exportPng, imageUpload и imageList; элементы без настроенного конечного пункта остаются скрытыми. Для последовательности запуска комбинированного Viewer, Search и Annotation см. Быстрый старт.

Пакет аннотаций добавляет инструменты авторинга в браузере, но данные всё равно принадлежат серверной сессии документа, идентифицируемой токеном. Повторное открытие источника создаёт новую сессию; сохраняйте XML или закодированный конверт аннотаций в вашем приложении, если аннотации должны сохраняться после завершения сессии.

Создание аннотаций в C#

Получите менеджер, привязанный к открытой сессии, добавьте аннотации и загрузите их (с using Doconut.Annotations; для типов и using System.Drawing; для 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();
});

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

Все типы находятся в 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 разрешается относительно хоста запроса при добавлении аннотации (загрузка изображения происходит только при «выпекании») — он должен быть доступен серверу (например, файл в wwwroot, обслуживаемый UseStaticFiles)

API AnnotationManager

ЧленНазначение
Add(BaseAnnotation)Поставить аннотацию в очередь
GetAnnotations() / GetAnnotations(int page)Посмотреть, что хранит менеджер
ClearAnnotations() / ClearAnnotations(int page)Удалить все / по странице
GetAnnotationData() / GetAnnotationData(int page)Закодированная строка данных аннотации — envelope в Base64 (что потребляет виджет)
GetAnnotationXml()XML-форма

Viewer отражает операции загрузки/чтения относительно сессии: LoadAnnotationData(token, manager) или LoadAnnotationData(token, encodedData) (envelope в Base64 из GetAnnotationData()), LoadAnnotationXML(token, xml), GetAnnotationXML(token).

Экспорт с встраиванием аннотаций

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

Экспорты используют тот же механизм «выпекания», что и отрисовка на экране, поэтому то, что видят пользователи, попадает в файл.

Рабочий процесс сохранения

  1. Откройте документ и получите его токен.
  2. Загрузите ранее сохранённый XML или закодированные данные в этот токен.
  3. Позвольте виджету читать и редактировать аннотации сессии.
  4. Получите XML с помощью GetAnnotationXML(token), когда ваше приложение решит сохранить.
  5. Экспортируйте PDF/PNG, когда требуется плоский результат.
  6. Закройте сессию документа.

Не используйте непрозрачный токен просмотрщика в качестве постоянного идентификатора аннотации. Связывайте сохранённые данные аннотаций со своими идентификаторами документов и их версиями.

Примечания по безопасности и рендерингу

  • Запросы аннотаций используют ту же безопасность сессии/токена, что и запросы страниц.
  • Относительный URL ImageAnnotation разрешается из хоста запроса и должен оставаться доступным серверу во время «выпекания».
  • Проверяйте и контролируйте любой пользовательский URL изображения, чтобы избежать подделки запросов на стороне сервера.
  • Экспорты применяют те же решения по лицензии/кастомному водяному знаку, что и рендеринг страниц на экране.
  • Большие данные свободных штрихов и экспорты высокого разрешения увеличивают использование памяти; тестируйте реальные документы и значения масштабирования.

Устранение неполадок

СимптомПроверка
Отсутствует лента аннотацийВозможность Annotation и четыре флага CSS/скриптов аннотаций
Обратный вызов сохранения сообщает об ошибкеИстечение срока действия токена/сессии и middleware BasePath
Аннотации C# не отображаютсяНумерация страниц начинается с единицы, и данные были загружены в активный токен
Изображение аннотации отображается на экране, но не в экспортеСервер может достичь URL изображения во время «выпекания»
Повторно открытый документ не имеет аннотацийСохраните XML/данные вне сессии просмотрщика, затем загрузите их в новый токен

Была ли эта страница полезной?