Аннотации
Добавьте поддержку аннотаций в просмотрщик
Аннотации в Doconut работают в двух направлениях: пользователи рисуют их в виджете браузера, а сервер сохраняет их на каждой странице, либо ваш код создает их программно и загружает в открытую сессию. В любом случае они отображаются на страницах и могут быть внедрены в экспорт PDF/PNG.
Поддержка аннотаций ограничена возможностью лицензии Annotation (предоставляется автоматически при активной временной лицензии).
Включить пользовательский интерфейс аннотаций
Аннотация — это модуль Viewer, а не отдельная панель инструментов. Полная страница должна включать ресурсы Viewer, панель инструментов Viewer, монтирование Viewer и инициализированный objViewer; затем лента Annotation монтируется и привязывается к тому же экземпляру.
Выполняйте вывод пакетов аннотаций вместе с пакетами просмотрщика — они ограничены лицензией, поэтому теги появляются только при доступной возможности:
@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 видимой в разметке:
<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 лицензирована:
<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, принадлежащей хосту:
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):
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).
Экспорт с внедрёнными аннотациями
// 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");
});Экспорты используют тот же механизм «сжигания», что и рендеринг на экране, поэтому то, что видят пользователи, содержится в файле.
Рабочий процесс сохранения
- Откройте документ и получите его токен.
- Загрузите ранее сохранённый XML или закодированные данные в этот токен.
- Позвольте виджету читать и редактировать аннотации сессии.
- Получите XML с помощью
GetAnnotationXML(token), когда ваше приложение решит сохранить данные. - Экспортируйте PDF/PNG, когда требуется плоский результат.
- Закройте сессию документа.
Не используйте непрозрачный токен viewer в качестве постоянного идентификатора аннотации. Связывайте сохранённые данные аннотаций со своими идентификаторами документа и версии.
Примечания по безопасности и рендерингу
- Запросы аннотаций используют ту же безопасность сессии/токена, что и запросы страниц.
- Относительный
ImageAnnotationURL разрешается от хоста запроса и должен оставаться доступным для сервера во время burn time. - Проверяйте и контролируйте любой пользовательский URL изображения, чтобы избежать подделки серверных запросов.
- При экспорте применяется то же решение о лицензии/кастомном водяном знаке, что и при рендеринге страниц на экране.
- Большие свободные нагрузки и экспорты высокого разрешения увеличивают использование памяти; тестируйте реальные документы и значения масштабирования.
Устранение неполадок
| Симптом | Проверка |
|---|---|
| Лента аннотаций отсутствует | Annotation возможность и четыре флага CSS/script аннотаций |
| Обратный вызов сохранения сообщает об ошибке | Истечение срока действия токена/сессии и middleware BasePath |
| Аннотации C# не отображаются | Нумерация страниц начинается с единицы, и данные были загружены в активный токен |
| Изображение аннотации отображается на экране, но не в экспорте | Сервер может достичь URL изображения во время burn-in |
| Повторно открытый документ не содержит аннотаций | Сохраняйте XML/данные вне сессии просмотрщика, затем загрузите их в новый токен |
Была ли эта страница полезной?