Аннотации

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

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

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

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

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

Emit the annotation bundles alongside the viewer bundles — they are license-gated, so the tags only appear when the capability is available:

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

Keep the complete Viewer composition visible in the markup:

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>

The Annotation bundle generates the Ribbon DOM inside annBarMount; you do not need to copy its buttons or dialog markup. Initialize docViewer first, then create the Ribbon only when the server confirms that Annotation is licensed:

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>

Saving from the Ribbon posts data through the middleware (AnnSave), which stores it in the document session per page. Loading (AnnLoad) happens automatically when a page with annotations renders. The four onAnn* callbacks keep the Ribbon synchronized with the viewer lifecycle.

Open and close it from any host-owned Viewer toolbar:

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

The public Ribbon API is:

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

onStatus, onToast, onLayout, onEditStart, and onEditEnd are optional host callbacks. The endpoints object can additionally provide exportPdf, exportPng, imageUpload, and imageList; controls without a configured endpoint remain hidden. For the combined Viewer, Search, and Annotation startup sequence, see Быстрый старт.

The annotation bundle adds the browser authoring tools, but the data still belongs to the server-side document session identified by the token. Reopening the source creates a new session; persist the XML or encoded annotation envelope in your application if annotations must survive beyond the session lifetime.

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

Get a manager bound to the open session, add annotations, and load them (with using Doconut.Annotations; for the types and using System.Drawing; for 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();
});

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

All types live in Doconut.Annotations and inherit from BaseAnnotation (page number + bounding 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 mirrors the load/read operations against a session: LoadAnnotationData(token, manager) or LoadAnnotationData(token, encodedData) (the Base64 wire envelope from 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");
});

Exports use the same burner as on-screen rendering, so what users see is what the file contains.

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

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

Do not use the opaque viewer token as a permanent annotation identifier. Associate persisted annotation data with your own document and version identifiers.

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

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

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

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

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