注释

为查看器添加注释支持

Doconut 中的注释有两种工作方式:用户在浏览器小部件中绘制注释,服务器按页持久化;或者你的代码以编程方式构建注释并加载到打开的会话中。无论哪种方式,注释都会在页面上渲染,并且可以嵌入到 PDF/PNG 导出中。

注释支持受 Annotation 许可证功能的限制(在有效的临时许可证下会自动授予)。

启用注释 UI

注释是 Viewer 的一个模块,而不是独立的工具栏。完整页面必须包含 Viewer 资源、Viewer 工具栏、Viewer 挂载点,以及已初始化的 objViewer;随后会挂载注释功能区并附加到同一实例上。

将注释捆绑包与 Viewer 捆绑包一起输出——它们受许可证限制,因此只有在具备相应功能时标签才会出现:

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  // 嵌入的注释功能区
}))

在标记中保持完整的 Viewer 组合可见:

html
<nav id="toolbar" aria-label="Document viewer controls">
    <!-- 查看器控件,包括打开 Annotation 的按钮 -->
</nav>
<div id="annBarMount"></div>
<div id="divDocViewer"><div id="div_ctlDoc"></div></div>

注释捆绑包在 annBarMount 内生成功能区 DOM;你无需复制其按钮或对话框标记。先初始化 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* 回调确保功能区与 Viewer 生命周期保持同步。

可从任何宿主拥有的 Viewer 工具栏打开或关闭它:

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

公共功能区 API 如下:

方法用途
attach(objViewer)将功能区连接到已初始化的 Viewer;仅需一次
open() / close()进入或退出注释编辑
reset()将功能区恢复为关闭的、非编辑状态
isOpen() / annotating()读取功能区状态 / Viewer 的注释编辑状态
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) =>
{
    // 绑定到打开会话的页面尺寸
    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 提供的文件)

AnnotationManager API(注释管理器 API)

成员用途
Add(BaseAnnotation)排队注释
GetAnnotations() / GetAnnotations(int page)检查管理器持有的内容
ClearAnnotations() / ClearAnnotations(int page)全部移除 / 按页移除
GetAnnotationData() / GetAnnotationData(int page)编码的注释数据字符串 — Base64 传输封装(小部件使用的内容)
GetAnnotationXml()XML 形式

Viewer 镜像针对会话的加载/读取操作:LoadAnnotationData(token, manager) 或 LoadAnnotationData(token, encodedData)(来自 GetAnnotationData() 的 Base64 传输封装),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");
});

// 或每页 PNG 的 ZIP 包
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. 当您的应用决定持久化时,使用 GetAnnotationXML(token) 获取 XML。
  5. 需要平面交付物时导出 PDF/PNG。
  6. 关闭文档会话。

不要将不透明的查看器令牌用作永久的注释标识符。请将持久化的注释数据与您自己的文档和版本标识符关联。

安全性和渲染说明

  • 注释请求使用与页面请求相同的会话/令牌安全机制。
  • 相对 ImageAnnotation URL 从请求主机解析,并且在渲染时必须仍然可被服务器访问。
  • 验证并控制任何用户提供的图像 URL,以避免服务器端请求伪造。
  • 导出遵循与屏幕页面渲染相同的许可证/自定义水印决策。
  • 大型手绘负载和高分辨率导出会增加内存使用;请测试真实文档和缩放值。

故障排除

症状检查
注释功能区缺失Annotation 能力以及四个注释 CSS/脚本 标志
保存回调报告错误令牌/会话过期以及中间件 BasePath
C# 注释未出现页面编号从 1 开始,且数据已加载到活动令牌中
图像注释在屏幕上显示但在导出中缺失服务器在渲染期间能够访问图像 URL
重新打开的文档没有注释在查看器会话之外持久化 XML/数据,然后加载到新令牌中

此页面对您有帮助吗?