注释
为查看器添加注释支持
在 Doconut 中,注释有两种工作方式:用户在浏览器小部件中绘制注释,服务器按页面持久化它们;或者你的代码以编程方式构建注释并加载到打开的会话中。无论哪种方式,注释都会在页面上渲染,并且可以烧录到 PDF/PNG 导出中。
注释支持受 Annotation 许可证功能的限制(在有效的临时许可证下会自动授予)。
启用注释 UI
注释是 Viewer 的一个模块,而不是独立的工具栏。完整页面必须包含 Viewer 资源、Viewer 工具栏、Viewer 挂载点以及已初始化的 objViewer;随后会挂载并附加 Annotation Ribbon 到同一实例。
将注释捆绑包与 viewer 捆绑包一起输出——它们受许可证限制,因此只有在功能可用时标签才会出现:
@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 捆绑包会在 annBarMount 内生成 Ribbon DOM;你无需复制其按钮或对话框标记。首先初始化 docViewer,然后仅在服务器确认已授权 Annotation 时创建 Ribbon:
<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>从 Ribbon 保存会通过中间件 (AnnSave) 将数据发送,后者将其按页面存储在文档会话中。加载 (AnnLoad) 会在带有注释的页面渲染时自动发生。四个 onAnn* 回调保持 Ribbon 与 viewer 生命周期同步。
可从任何宿主拥有的 Viewer 工具栏打开或关闭它:
annBar.open();
annBar.close();公共 Ribbon API 如下:
| Method | Purpose |
|---|---|
attach(objViewer) | 将 Ribbon 连接到已初始化的 viewer;仅需一次 |
open() / close() | 进入或离开注释编辑 |
reset() | 将 Ribbon 恢复到关闭的、非编辑状态 |
isOpen() / annotating() | 读取 Ribbon 状态 / viewer 的注释编辑状态 |
reopenEditable() | 重新加载当前页面的注释为可编辑对象 |
updateActionState() | 在宿主更改后刷新保存/删除控件的可用性 |
headerSlot() | 获取可选的标题扩展槽,以供宿主拥有的控件使用 |
onStatus、onToast、onLayout、onEditStart 和 onEditEnd 是可选的宿主回调。endpoints 对象还可以提供 exportPdf、exportPng、imageUpload 和 imageList;未配置端点的控件将保持隐藏。有关组合 Viewer、Search 和 Annotation 的启动顺序,请参阅 快速入门。
annotation 捆绑包添加了浏览器创作工具,但数据仍属于由 token 标识的服务器端文档会话。重新打开源会创建新会话;如果注释必须在会话生命周期之外保留,请在应用程序中持久化 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):
| 类型 | 备注 |
|---|---|
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 提供服务) |
AnnotationManager API
| 成员 | 目的 |
|---|---|
Add(BaseAnnotation) | 将注释加入队列 |
GetAnnotations() / GetAnnotations(int page) | 检查管理器持有的注释 |
ClearAnnotations() / ClearAnnotations(int page) | 删除全部 / 按页 |
GetAnnotationData() / GetAnnotationData(int page) | 编码的注释数据字符串——Base64 传输信封(widget 消费的) |
GetAnnotationXml() | XML 形式 |
Viewer 对会话执行相同的加载/读取操作:LoadAnnotationData(token, manager) 或 LoadAnnotationData(token, encodedData)(来自 GetAnnotationData() 的 Base64 传输信封),LoadAnnotationXML(token, xml),GetAnnotationXML(token)。
导出并烧录注释
下面的代码展示了如何导出带有注释的 PDF 或 PNG ZIP:
// 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");
});导出使用与屏幕渲染相同的烧录器,因此用户看到的内容即为文件中包含的内容。
持久化工作流
- 打开文档并获取其 token。
- 将先前存储的 XML 或编码数据加载到该 token 中。
- 让小部件读取并编辑会话注释。
- 当你的应用决定持久化时,使用
GetAnnotationXML(token)获取 XML。 - 在需要平面交付物时导出 PDF/PNG。
- 关闭文档会话。
不要将不透明的 viewer token 用作永久的注释标识符。请将持久化的注释数据与您自己的文档和版本标识符关联。
安全性和渲染说明
- 注释请求使用与页面请求相同的会话/token 安全机制。
- 相对的
ImageAnnotationURL 从请求主机解析,并且在烧录时必须仍能被服务器访问。 - 验证并控制任何用户提供的图像 URL,以避免服务器端请求伪造。
- 导出遵循与屏幕页面渲染相同的许可证/自定义水印决策。
- 大型自由笔画负载和高分辨率导出会增加内存使用;请使用真实文档和缩放值进行测试。
故障排查
| 症状 | 检查 |
|---|---|
| 注释 Ribbon 缺失 | Annotation 功能以及四个注释 CSS/脚本标志 |
| 保存回调报告错误 | Token/会话过期以及中间件 BasePath |
| C# 注释未出现 | 页码从 1 开始,且数据已加载到活动 token 中 |
| 图像注释在屏幕上显示但在导出中缺失 | 服务器在烧录期间能够访问图像 URL |
| 重新打开的文档没有注释 | 在 viewer 会话外持久化 XML/数据,然后加载到新 token 中 |
此页面有帮助吗?