注釈

ビューアに注釈サポートを追加する

Doconut の注釈は二方向で機能します。ユーザーがブラウザウィジェット上で描画し、サーバーがページごとに保持するか、またはコードで注釈を作成してオープンセッションにロードします。どちらの場合もページに描画され、PDF や PNG のエクスポート時に焼き込むことができます。

注釈サポートは Annotation ライセンス機能で制御されます(有効な Temporary ライセンスの下で自動的に付与されます)。

注釈 UI を有効にする

注釈は Viewer のモジュールであり、スタンドアロンのツールバーではありません。ページには Viewer のリソース、Viewer ツールバー、Viewer のマウント、そして初期化された objViewer を含める必要があります。その後、Annotation リボンがマウントされ、同じインスタンスに添付されます。

注釈ファイルを Viewer ファイルと一緒に出力します。これらはライセンスで制御されるため、機能が付与されたときにのみタグが表示されます。MVC 5 では、コントローラ内でタグを構築します(ビューには @inject がありません):

csharp
var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();
ViewBag.ViewerCss = viewer.ReferenceCss(new CssConfig
{
    IncludeViewerCss     = true,
    IncludeAnnotationCss = true    // jquery-ui.min.css + annotationBar.css
});
ViewBag.ViewerScripts = viewer.ReferenceScripts(new ScriptConfig
{
    IncludeJQuery            = true,
    IncludeViewerScripts     = true,
    IncludeAnnotationScripts = true,  // jquery-ui, raphael.js, annotation.js
    IncludeAnnotationBar     = true   // the embedded annotation ribbon
});
ViewBag.IsAnnotationEnabled = viewer.IsAnnotationEnabled;

マークアップ内に完全な 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>

annBarMount 内で Annotation ファイルがリボンを生成します。ボタンやダイアログをコピーする必要はありません。まず docViewer を初期化し、サーバーが注釈がライセンスされていることを確認したときにのみリボンを作成します:

html
@Html.Raw(ViewBag.ViewerScripts)
<script>
    var annBar = null;
    var currentToken = '';

    var objViewer = $('#div_ctlDoc').docViewer({
        BasePath: '/',
        ResPath:  'doconut-res/images',
        onAnnLoaded:    function () { if (annBar) annBar.handleAnnLoaded(); },
        onAnnSaved:     function () { if (annBar) annBar.handleAnnSaved(); },
        onAnnSaveError: function () { if (annBar) annBar.handleAnnSaveError(); },
        onAnnClosed:    function () { if (annBar) annBar.handleAnnClosed(); },
        onError:        function (message) { console.error('Viewer error:', message); }
    });

    @if ((bool)ViewBag.IsAnnotationEnabled)
    {
        <text>
    annBar = $('#annBarMount').doconutAnnotationBar({
        docId: 'ctlDoc',
        getRequestParams: function () { return { token: currentToken }; },
        onLayout: function () { requestAnimationFrame(function () { objViewer.Refit(); }); }
    });
    annBar.attach(objViewer);
        </text>
    }
</script>

機能がない場合、ReferenceScripts はリボンのスクリプトを除外するため、サーバー側の if が保護されていない doconutAnnotationBar(...) 呼び出しがページを壊すのを防ぎます。Web Forms では、同じガードを <% if (IsAnnotationEnabled) { %> … <% } %> のように記述します。

リボンからの保存はデータを DocImage.axd(AnnSave)にポストし、ページごとのドキュメントセッションに保存します。ロード(AnnLoad)は、注釈付きページがレンダリングされると自動的に行われます。

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 も提供できます。設定されたエンドポイントがないコントロールは非表示のままです。

コールバックの2種類、2通りの接続方法

アノテーションコードは2つのコールバックスタイルを使用しており、逆方向に接続するとそれぞれが黙って失敗します。

  • onAnnLoaded、onAnnSaved、onAnnSaveError、onAnnClosed は docViewer({...}) の オプション であり、すべてのビューアコールバックと同様です。
  • Created、Changed、Deleted、Properties は 名前で検索されるグローバル関数 です。ビューアの ID がイベント名の前に付加されます。#div_ctlDoc のビューアの ID は ctlDoc なので、関数は ctlDoc_Created、ctlDoc_Changed、ctlDoc_Deleted、ctlDoc_Properties となります。綴りミスや名前変更があると、関数は呼び出されません。

SaveAnnotations() は保存成功かどうかを返しません。結果は onAnnSaved または onAnnSaveError で通知されます。戻り値を読むコードは「未保存の変更」フラグをクリアしません。

C# でアノテーションを作成

オープンセッションにバインドされたマネージャーを取得し、アノテーションを追加してロードします(型は using Doconut.Annotations;、Rectangle と Color は using System.Drawing; を使用)。

csharp
// POST /Annotations/LoadSample?token=...
[HttpPost]
public ActionResult LoadSample(string token)
{
    var viewer = Doconut.DoconutHost.Services.GetRequiredService<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 viewer fetches them, and the renderer burns them into
    // image/PDF exports.
    viewer.LoadAnnotationData(token, manager);
    return new HttpStatusCodeResult(200);
}

アノテーションタイプ

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 ポイントからのフリーストローク
ImageAnnotationURL からの画像。相対 URL はアノテーション追加時に現在のリクエストのホストに対して解決されます—以下のエクスポート注記を参照

AnnotationManager API

メンバー目的
Add(BaseAnnotation)アノテーションをキューに追加
GetAnnotations() / GetAnnotations(int page)マネージャーが保持しているアノテーションを確認
ClearAnnotations() / ClearAnnotations(int page)すべて(またはページ単位)を削除
GetAnnotationData() / GetAnnotationData(int page)エンコードされたアノテーションデータ — ウィジェットが消費するラッパー
GetAnnotationXml()XML 形式

Viewer はセッションに対するロードと読み取り操作を鏡像します:LoadAnnotationData(token, manager) または LoadAnnotationData(token, encodedData)、LoadAnnotationXML(token, xml)、GetAnnotationXML(token)。

アノテーションを焼き込んだエクスポート

csharp
// PDF of all pages with annotations rendered onto them
public async Task<ActionResult> ExportPdf(string token)
{
    var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();
    byte[] pdf = await viewer.ExportAnnotationsToPdfAsync(token, zoom: 100, ct: Response.ClientDisconnectedToken);
    return File(pdf, "application/pdf", "export.pdf");
}

// Or a ZIP of per-page PNGs
public async Task<ActionResult> ExportPngZip(string token)
{
    var viewer = Doconut.DoconutHost.Services.GetRequiredService<Viewer>();
    byte[] zip = await viewer.ExportAnnotationsToPngZipAsync(token, zoom: 100, ct: Response.ClientDisconnectedToken);
    return File(zip, "application/zip", "annotations-png.zip");
}

エクスポートは画面上のレンダリングと同じバーナーを使用し、同じライセンスとカスタム透かしの判断が適用されます。

社内ネットワークホストで画像注釈が「Image blocked」としてエクスポートされる

ビューアとエクスポートは ImageAnnotation の画像を異なる場所から取得します:

対象画像をダウンロードする側社内ネットワークまたは開発ホスト上
ブラウザ内のビューアユーザーのブラウザ画像が表示される
ExportAnnotationsToPdfAsync / …PngZipAsyncサーバー赤い "Image blocked" プレースホルダー

サーバーが画像を取得して埋め込もうとすると、プライベートネットワークへ戻るアドレスは拒否されます。対象となるのはループバック (localhost, 127.0.0.1)、0.0.0.0、リンクローカル (169.254.x.x)、プライベートレンジ 10.x, 172.16–31.x, 192.168.x、キャリアグレード NAT (100.64.0.0/10)、および IPv6 のリンクローカルとユニークローカルアドレスです。これは、細工された画像 URL を通じて内部システムへアクセスさせることを防止するための保護であり、無効化できるスイッチはありません。

相対パスの画像 URL は現在のリクエストのホストに対して解決されるため、localhost や社内サーバー、あるいはプライベートアドレスでアクセスしたサイト上では、ビューアには画像が表示されますが、エクスポートにはプレースホルダーが入ります。これはレンダリング失敗ではなくガードが機能しているためです。エクスポートに画像を必ず含めたい場合は、パブリックアドレスを持つホストから提供し、絶対 URL を使用してください。画像 URL がまったく解決できない場合は、その注釈はエラーなしで消失します—ステータスコードではなく、描画されたピクセルやネットワークログで確認してください。

永続化ワークフロー

  1. ドキュメントを開き、トークンを取得します。
  2. 以前に保存した XML またはエンコード済みデータをそのトークンにロードします。
  3. ウィジェットにセッションの注釈を読み取り、編集させます。
  4. アプリケーションが永続化を決定したとき、GetAnnotationXML(token) で XML を取得します。
  5. フラット化された成果物が必要な場合は、PDF または PNG をエクスポートします。
  6. ドキュメントセッションを閉じます。

ビューアトークンを永続的な注釈識別子として使用しないでください—セッションとともに失効し、アプリケーションプールのリサイクルで単一サーバー上のすべてのセッションが終了します。注釈データは自分のドキュメントおよびバージョン識別子に紐付けて保存してください。

セキュリティとレンダリングに関する注意点

  • 注釈リクエストはページリクエストと同じセッションおよびトークンのセキュリティを使用します。
  • ユーザーが提供した画像 URL を必ず検証・制御してください。
  • 大容量のフリーハンドペイロードや高解像度エクスポートはメモリ使用量を増大させます。実際のドキュメントとズーム値で十分にテストしてください。

トラブルシューティング

症状確認項目
注釈リボンが表示されないAnnotation 機能と 4 つの注釈 CSS/スクリプトフラグが有効か
保存時にエラーが報告されるトークンまたはセッションの有効期限切れ、ウィジェットの BasePath が正しいか
ctlDoc_Created(または他のイベント関数)が実行されない関数名は「ビューア ID + _ + イベント名」になっているか、グローバル関数として定義されているか
C# の注釈が表示されないページ番号は 1 から始まり、データがアクティブなトークンにロードされているか
画像注釈が画面上には表示されるがエクスポートに含まれない画像のホストがプライベートまたはループバックアドレスになっていないか(上記参照)
再度開いたドキュメントに注釈がないビューアセッション外で XML またはデータを永続化し、新しいトークンにロードしているか

このページは役に立ちましたか?