技術記事

DelphiでのXFDFとしてのPDF注釈のインポートとエクスポート

HotPDFは、現在ロードされているドキュメントに対して動作する2つの関数ExportLoadedAnnotationsToXFDFおよびImportLoadedAnnotationsFromXFDFを介して、PDF注釈をXFDFとしてエクスポートおよびインポートします。XFDFはISO 19444-1として標準化されたXML形式の注釈交換フォーマットであり、この一対の関数を使用することで、DelphiまたはC++BuilderプログラムからAcrobatやサードパーティ製の校正ツールにコメントを渡し、マークアップされた結果を書き戻すことができます。この処理において、注釈が配置されているページのコンテンツ自体が書き換えられることはありません

これが解決する双方向のユースケースを想定してみましょう。レビュー担当者がAcrobatで生成されたレポートを開き、ずれている図の上に赤い矢印を置き、誤った合計値を丸で囲み、余白にメモを入力し、そのコメントを小さなXFDFファイルにエクスポートします。あるいはその逆で、自社プログラムでマークアップ自体を作成し、HotPDFを使用していない他のユーザーに送信する必要がある場合です。いずれの場合も、注釈は双方が理解できるXMLとしてやり取りされ、PDFページ自体はバイト単位で元の状態を維持します

FDFとXFDFの違いは何ですか?

FDFとXFDFは、2つの異なる構文で同じデータを保持しますが、他のツールにどのファイルを渡すかを決定する際にはこの違いが重要になります。FDFはPDF仕様自体の中で定義された古いフォームデータ形式であり、PDFオブジェクトの構文を使用するため、FDFファイルは簡素化されたPDFのように見え、読み取りにはPDFを認識できるパーサーが必要です。XFDFは同じデータのXML表現であり、ISO 19444-1として独立して標準化されているため、プラットフォームに関係なくあらゆるXMLライブラリで開いたり、差分を比較したり、生成したりできます。どちらの形式も、ISO 19444-1の第6.3項で規定されている<fields>ツリー内のフォームフィールド値と、<annots>ツリー内の注釈を保持できます。HotPDFはこれらの役割を分割し、フォームデータはExportLoadedFormToXFDFを介してルーティングし、ExportLoadedAnnotationsToXFDF<annots>専用としています。Webサービス、Javaの校正サーバー、またはスクリプトとコメントをやり取りする場合、XFDFを使用すれば相手側にPDFパーサーを組み込むよう強いる必要がなくなります

DelphiでPDF注釈をXFDFとしてエクスポートするにはどうすればよいですか?

HotPDFは、ロードされたドキュメントのすべてのページを走査し、サポートされている注釈ごとに1つのXFDF要素を出力し、書き込まれた注釈の数を返すことで注釈をエクスポートします。まずPDFをロードし、出力先のパス指定してExportLoadedAnnotationsToXFDFを呼び出します。戻り値の整数はシリアル化された注釈の数を示します。値が0以下の場合は何もエクスポートされず、ファイルも書き込まれなかったことを意味し、ドキュメントにサポート対象のサブタイプの注釈が含まれていなかったことを示します

var
  Pdf: THotPDF;
  Written: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('report-reviewed.pdf', '') > 0 then
    begin
      // Write one XFDF element per supported annotation on every page
      Written := Pdf.ExportLoadedAnnotationsToXFDF('comments.xfdf');
      if Written <= 0 then
        ShowMessage('No supported annotations were found');
    end;
  finally
    Pdf.Free;
  end;
end;

出力されるXFDFはプレーンで読みやすいXMLです。HotPDFは、ISO 19444-1名前空間で<xfdf>ルート、<annots>コンテナを書き込み、その下に0ベースのページインデックス、色、および形状を属性または子要素として持つ注釈ごとの子要素を出力します。黄色の塗りつぶしと開いた矢頭を持つ線が、塗りつぶされた多角形の隣にある場合、次のようにシリアル化されます

注釈サブタイプのXFDF要素へのマッピング

各注釈サブタイプは、独自の幾何学定義を持つ特定のISO 19444-1要素にマッピングされ、HotPDFは独自の構造を作るのではなくこれらの標準規格に従います。線注釈は、注釈のL配列から直接取得した2つの端点の座標ペアを保持するstartおよびend属性を持ち、LE線端スタイルはheadおよびtail属性に変換されます。多角形および折れ線注釈は、ポイントリストを属性ではなく、セミコロンで区切られたx,yのペアとして<vertices>子要素内に配置します。これは、この子要素を想定しているリーダーは、他の場所に隠されているポイントを暗黙的に無視してしまうためです。複数の独立したストロークを保持できるフリーハンド(Ink)注釈は、ストロークごとに1つの<gesture>子要素を持つ<inklist>要素をネストするため、複数ストロークの署名は1つの画像データではなく個別のジェスチャーとして保持されます

リッチテキスト、色、および境界線のスタイルは幾何学データとともに保持されます。メモのリッチテキスト本文は<contents-richtext>子要素として書き込まれます。PDFがIC配列に保存する内部塗りつぶし(円、四角形、多角形、または線の矢頭の内部の描画、および墨消しボックスの塗りつぶし)は、#RRGGBB形式のinterior-color属性として処理されます。境界線の幅、ダッシュパターン、および雲形境界効果は、widthdashesstyle、およびintensity属性にマッピングされ、雲形アウトラインのコールアウトは相手側でも雲形として再現されます。HotPDFはまた、マークアップ注釈に添付されているポップアップウィンドウも保持し、ポップアップの形状と開閉状態を注釈のPopupディクショナリにインポートします。さらに、テキスト注釈の開閉およびレビュー状態も引き継ぐため、レビューされたドキュメントは形状だけでなく、レビュー担当者が依存するワークフローのメタデータも維持します

ロードされたドキュメントへのXFDFのインポート

HotPDFは、XMLを解析し、NewLoadedAnnotationを介して要素ごとに新しい注釈を作成し、要素で指定されたページに添付し、追加された注釈の数を返すことでXFDFをインポートします。このワークフローはエクスポートと対称的です。ベースとなるPDFをロードし、レビュー用のファイルを指定してImportLoadedAnnotationsFromXFDFを呼び出し、ロードされたドキュメントを保存して新しいマークアップを永続化します。ファイルが見つからないか、XMLが解析できない場合、関数は0を返し、ロードされたドキュメントは変更されません

var
  Pdf: THotPDF;
  Added: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('report.pdf', '') > 0 then
    begin
      Added := Pdf.ImportLoadedAnnotationsFromXFDF('comments.xfdf');
      if Added > 0 then
        Pdf.SaveLoadedDocument('report-annotated.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

各XFDF要素には独自のページインデックスが指定されているため、複数のファイルを順番にインポートする場合でも、注釈は作成された正確なページに配置されます。これにより、1回保存する前に、複数のレビュー担当者からのコメントを安全に同じドキュメントに集約できます。以下の例では、2人のレビュー担当者のコメントを1つのマージされたコピーに統合します。ファイルとしてやり取りするのではなく、コード内で注釈オブジェクト自体を構築および編集する方法については、HotPDFがDelphiから直接PDF注釈オブジェクトを作成および編集する方法を参照してください

var
  Pdf: THotPDF;
  Total, I: Integer;
  Files: array[0..1] of string;
begin
  Files[0] := 'alice-comments.xfdf';
  Files[1] := 'bob-comments.xfdf';
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('master.pdf', '') > 0 then
    begin
      Total := 0;
      for I := Low(Files) to High(Files) do
        Inc(Total, Pdf.ImportLoadedAnnotationsFromXFDF(Files[I]));
      if Total > 0 then
        Pdf.SaveLoadedDocument('master-merged.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

正常にラウンドトリップするものとそうでないもの

HotPDFは、ISO 19444-1で規定されている注釈サブタイプをラウンドトリップさせ、リーダーが誤解するような出力を防ぐために、それ以外のサブタイプは意図的にスキップします。サポートされているセットは、実際の校正作業の大部分を占めるマークアップタイプを網羅しています。テキストノート、フリーテキスト、線、四角形、円、多角形、折れ線、4つのテキストマークアップタイプ(ハイライト、下線、打ち消し線、波線)、スタンプ、フリーハンド、キャレット(挿入記号)、さらに添付ファイル、音声、墨消し、リンクの合計18種類のサブタイプです。このリストに含まれないサブタイプの注釈はエクスポート時に無視され、空のデータが書き込まれるのではなくスキップされるため、関数が返す件数には加算されません

リッチテキストに関しては注意が必要です。HotPDFは書式設定されたテキストとプレーンなコンテンツが保持されるように<contents-richtext>本文を保存しますが、XFDFが保持するのはコメントのテキストとスタイル情報であり、レンダリングされたアピアランスストリームではありません。そのため、受け取り側のアプリケーションは、HotPDFの正確なピクセルを再現するのではなく、自身のフォントとレイアウトでポップアップを再描画します。このラウンドトリップ処理は、ピクセルレベルの描画再現ではなく、コンテンツと意図を正確に伝達するものとして扱ってください。装飾されたコンテンツが注釈ストリームではなくXFAフォームデータ内にある場合は動作が異なり、これについてはHotPDFによるXFA exData、リッチテキスト、およびハイパーリンクの処理で解説しています

文字レベルの処理は、想定以上に厳格に管理されており、これはまさに望ましい動作です。HotPDFはテキストを書き込む際、ISO 19444-1の第5.8.2項のエスケープルールを適用し、XMLで重要な文字や制御バイトをエンコードします。これにより、アンパサンド、山括弧、または改行を含むコメントでも、規格に準拠したパーサーが受け入れる正常なXMLが生成されます。インポート時にはこのルールの逆変換を行います。そのため、スプレッドシートからコピー&ペーストされた記号などを含むメモでも、ファイルを破損させることなく元の状態で復元できます

注釈の交換はロード済みドキュメントAPIが処理する機能の一部であり、他の処理と組み合わせることができます。レビュー担当者のXFDFをインポートし、ページを調整したり、ドキュメントのメタデータを編集したり、ファイルをフラット化またはアクセス権を変更した上で、次の処理のために新しいXFDFをエクスポートします。これらの機能はすべて、DelphiおよびC++Builder用の標準のHotPDF Componentに含まれており、そのリファレンスには注釈サブタイプの全サポート状況と、関連するフォームデータXFDF関数がドキュメント化されています