この質問への回答のほとんどは5年前のもので、PDF専用で、見えないページに対して座標を測定するよう求めます。こちらが2026年版です:7つの呼び出し、マークアップが実際に配置される場所を決める2つの慣例、そしてLinuxコンテナが必要とする1行です。

以下のすべては .NET 8 と .NET 10 上で、Windows と Linux の両方で動作し、Adobe Acrobat や Microsoft Office はインストールされていません — 同じコードが DOCX、XLSX、PPTX、画像 でも動作します。変更が必要なのはファイルパスだけです。

実行可能なバージョンは GitHub にあります:

annotate-pdf-csharp-net.

インストール

dotnet add package GroupDocs.Annotation

ライセンスがない場合、ライブラリは評価モードで動作し、出力に評価マークが付加されます。
temporary licence を使用するとそれらが除去されます:

new License().SetLicense("GroupDocs.Annotation.lic");

ドキュメントを開く

using (Annotator annotator = new Annotator("quarterly-review.pdf"))
{
    // add, read or remove markup here
}

最初に知っておくべきことは、Annotator はファイルを破棄されるまで開いたままにし、読み取っているファイルに上書き保存することはできません。別のパスに保存するか、後で所定の場所に移動する一時ファイルに保存してください。

座標を測定しない — テキストを検索する

これはほとんどのチュートリアルが省略する部分で、ドキュメントの次のバージョンでもアノテーションコードが機能し続ける要因です。GetDocumentInfo() は各ページのサイズとテキスト行を返し、各行はボックスを持ちます:

IDocumentInfo info = annotator.Document.GetDocumentInfo();

foreach (PageInfo page in info.PagesInfo)
{
    foreach (TextLineInfo line in page.TextLines)
    {
        if (line.Text.Contains("Hosting costs"))
        {
            // line.LeftIndent, line.TopIndent, line.Width, line.Height — points, from the top-left
        }
    }
}

これで、注釈は昨年の記載場所ではなく、現在のドキュメントの記載内容に基づいて配置できます。

コメントと返信付きハイライトを追加する

float left   = (float)line.LeftIndent;
float right  = (float)(line.LeftIndent + line.Width);
float top    = (float)(page.Height - line.TopIndent);
float bottom = (float)(page.Height - line.TopIndent - line.Height);

annotator.Add(new HighlightAnnotation
{
    PageNumber = page.PageNumber - 1,
    BackgroundColor = 0xFFE699,
    Opacity = 0.5,
    Message = "Check this against the invoice",
    CreatedOn = DateTime.Now,
    User = new User { Id = 1, Name = "Reviewer", Role = Role.Editor },
    Points = new List<Point>
    {
        new Point(left, top),    new Point(right, top),
        new Point(left, bottom), new Point(right, bottom)
    },
    Replies = new List<Reply>
    {
        new Reply { Comment = "Confirmed with finance.", RepliedOn = DateTime.Now }
    }
});

annotator.Save("result.pdf");

Message はコメントです。Replies はスレッドを開始します — PDFリーダーでは返信付きのノート、Wordでは下に返信が付くコメントになります。User は後続の処理でこのレビュアーのマークアップを正確に見つけて削除し、他の人のものはそのままにしておくために使用されます。

すべての人がつまずく2つの慣例

top と bottom を計算する2行、そして PageNumber に注意してください。

ページ番号は2回数えられます。 PageInfo.PageNumber は 1ベースです。AnnotationBase.PageNumber は 0ベースです。これらを混在させると、すべての注釈が1ページずれ、最後のページ向けのものは消えてしまいます。存在しないページに向けられた注釈は保存時に例外や警告なしに削除されます。マークアップが「表示されなかった」場合は、まずページインデックスを確認してください。

2つの基準点。 TextLineInfo と AreaAnnotation.Box は 左上 から測定されます。テキストのマークアップ(ハイライト、下線、取り消し線、波線)は 左下 から測定され、これは PDF の仕様です。

メンバー 基準点 ページ番号付け
PageInfo, TextLineInfo 左上 1ベース
AnnotationBase.PageNumber — 0ベース
AreaAnnotation.Box 左上 —
HighlightAnnotation.Points 左下 —
PreviewOptions.PageNumbers — 1ベース

4つの点で1つの矩形が構成され、順序は左上、右上、左下、右下です。同じリストにさらに矩形を追加でき、これにより1つのコメントが3行にまたがる文全体をカバーします。

マークアップを読み戻す

using (Annotator annotator = new Annotator("result.pdf"))
{
    foreach (AnnotationBase a in annotator.Get())
    {
        Console.WriteLine($"#{a.Id} {a.Type} on page {a.PageNumber + 1}: {a.Message}");

        foreach (Reply reply in a.Replies)
            Console.WriteLine($"   reply: {reply.Comment}");
    }
}

Get() はドキュメントが実際に含んでいるものを読み取ります — Word や Acrobat で追加された注釈も含まれ、他のツールとの往復が可能になる要因です。

知っておくべき注意点として、Id はドキュメントを読み取っている間に割り当てられ、ファイルに保存されません。同一セッション内で注釈を削除または更新するには十分に安定していますが、データベースのキーとしては使用できません。ファイル外に注釈を保持したい場合は、インポートとエクスポートを利用してください。

再度削除する

annotator.Remove(1);                                      // by id
annotator.Remove(annotator.Get()
    .Where(a => a.User?.Name == "Reviewer").ToList());     // or by author

ページをレンダリングする

PreviewOptions previewOptions = new PreviewOptions(n => File.Create($"page_{n}.png"));
previewOptions.PreviewFormat = PreviewFormats.PNG;
previewOptions.PageNumbers = new[] { 1, 2 };     // 1-based
previewOptions.RenderAnnotations = true;
previewOptions.Resolution = 144;

annotator.Document.GeneratePreview(previewOptions);

これは3つの場面で役立ちます:ドキュメントを表示するWeb UI、マークアップが意図した場所に配置されたことを検証するテスト、そして自分の作業を確認するエージェントです。

Linux上で

コードは変更不要です。コンテナ側で以下を行います:

FROM mcr.microsoft.com/dotnet/runtime:8.0
RUN apt-get update \
 && apt-get install -y --no-install-recommends fontconfig fonts-liberation \
 && rm -rf /var/lib/apt/lists/*

.NET ランタイムイメージにはフォントが含まれておらず、テキスト描画にはフォントが必要です。その1行が正しい出力と空白ページの違いを生み、文書ライブラリ全般に関する Linux の最も一般的な質問でもあります。

次に何をすべきか

git clone https://github.com/groupdocs-annotation/annotate-pdf-csharp-net.git
cd annotate-pdf-csharp-net
dotnet run --project src/AnnotatePdf -- highlight samples/quarterly-review.pdf --text "Hosting costs"