Аннотации

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

Аннотации в 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">
    <!-- Элементы управления просмотрщиком, включая кнопку, открывающую 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>

Сохранение из ленты отправляет данные через промежуточный слой (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) =>
{
    // Привязан к размерам страниц открытой сессии
    var manager = viewer.GetAnnotationManager(token);
    var pageCount = viewer.GetPageCount(token);

    // Один штамп на страницу
    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));

    // Загрузить в сессию — widget получает их через AnnLoad и
    // renderer встраивает их в экспорт image/PDF.
    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)Закодированная строка данных аннотации — Base64‑обёртка (что потребляет виджет)
GetAnnotationXml()XML‑формат

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

Экспорт с «запечёнными» аннотациями

csharp
// PDF всех страниц с наложенными на них аннотациями
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");
});

// Или ZIP с PNG‑файлами по страницам
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, когда требуется плоский (flattened) результат.
  6. Закройте сессию документа.

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

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

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

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

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

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