Аннотации

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

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

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

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

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

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

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

Сохраняйте полную композицию просмотрщика видимой в разметке:

html
<nav id="toolbar" aria-label="Document viewer controls">
    <!-- Элементы управления просмотрщиком, включая кнопку, открывающую Annotation -->
</nav>
<div id="annBarMount"></div>
<div id="divDocViewer"><div id="div_ctlDoc"></div></div>

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

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* поддерживают синхронизацию ленты с жизненным циклом просмотрщика.

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

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; элементы управления без настроенного конечного пункта остаются скрытыми. Для последовательности запуска комбинированного просмотрщика, поиска и аннотации см. Быстрый старт.

Пакет аннотаций добавляет инструменты авторинга в браузере, но данные по‑прежнему принадлежат серверной сессии документа, идентифицированной токеном. Повторное открытие источника создаёт новую сессию; сохраняйте 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));

    // Загрузить в сессию — виджет получает их через AnnLoad и
    // renderer встраивает их в экспорт изображений/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, когда требуется уплощённый результат.
  6. Закройте сессию документа.

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

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

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

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

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

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