Аннотации

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

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

Поддержка аннотаций ограничена возможностью лицензии 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: '/',
        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) =>
{
    // 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):

TypeПримечания
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

MemberНазначение
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 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. Закройте сессию документа.

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

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

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

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

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

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