Sebagian besar jawaban untuk pertanyaan ini berusia lima tahun, hanya untuk PDF, dan dimulai dengan meminta Anda mengukur koordinat terhadap halaman yang tidak dapat Anda lihat. Berikut versi 2026: tujuh panggilan, dua konvensi yang menentukan di mana markup sebenarnya ditempatkan, dan satu baris yang dibutuhkan oleh kontainer Linux.

Semua di bawah ini berjalan pada .NET 8 dan .NET 10, di Windows dan Linux, tanpa Adobe Acrobat dan tanpa Microsoft Office terpasang — dan kode yang sama bekerja pada DOCX, XLSX, PPTX dan gambar. Hanya jalur file yang berubah.

Versi yang dapat dijalankan ada di GitHub: annotate-pdf-csharp-net.

Instal

dotnet add package GroupDocs.Annotation

Tanpa lisensi, perpustakaan berjalan dalam mode evaluasi dan outputnya membawa tanda evaluasi. Sebuah temporary licence menghilangkannya:

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

Membuka dokumen

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

Satu hal yang perlu diketahui di awal: annotator menjaga file tetap terbuka sampai dibuang, dan tidak dapat menyimpan di atas file yang sedang dibacanya. Simpan ke jalur yang berbeda, atau ke file sementara yang Anda pindahkan ke tempatnya setelahnya.

Jangan mengukur koordinat — cari teks

Ini adalah bagian yang paling banyak tutorial lewati, dan merupakan bagian yang membuat kode anotasi bertahan pada versi dokumen berikutnya. GetDocumentInfo() mengembalikan setiap halaman dengan ukuran dan baris teksnya, masing-masing dengan sebuah kotak:

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
        }
    }
}

Sekarang anotasi dapat ditempatkan pada apa pun yang dikatakan dokumen hari ini, bukan pada tempat yang dikatakan tahun lalu.

Tambahkan sorotan dengan komentar dan balasan

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 adalah komentar. Replies memulai sebuah utas — di pembaca PDF itu adalah catatan dengan respons, di Word itu adalah komentar dengan balasan di bawahnya. User adalah yang memungkinkan proses selanjutnya menemukan dan menghapus markup peninjau ini secara tepat dan membiarkan markup orang lain tetap.

Dua konvensi yang menangkap semua orang

Perhatikan dengan saksama dua baris yang menghitung top dan bottom, serta PageNumber.

Nomor halaman dihitung dua kali. PageInfo.PageNumber bersifat berbasis 1. AnnotationBase.PageNumber bersifat berbasis 0. Campur keduanya dan setiap anotasi akan berada satu halaman salah — dan anotasi yang ditujukan untuk halaman terakhir menghilang, karena anotasi yang diarahkan ke halaman yang tidak ada dibuang saat menyimpan, tanpa pengecualian dan tanpa peringatan. Jika markup “tidak muncul”, periksa indeks halaman terlebih dahulu.

Dua asal. TextLineInfo dan AreaAnnotation.Box diukur dari sudut atas-kiri. Markup teks — sorotan, garis bawah, coret, bergelombang — diukur dari bawah-kiri, yang merupakan cara kerja PDF.

Anggota Asal Penomoran halaman
PageInfo, TextLineInfo atas-kiri 1-based
AnnotationBase.PageNumber — 0-based
AreaAnnotation.Box atas-kiri —
HighlightAnnotation.Points bawah-kiri —
PreviewOptions.PageNumbers — 1-based

Empat titik membentuk satu persegi panjang, dalam urutan ini: atas-kiri, atas-kanan, bawah-kiri, bawah-kanan. Lebih banyak persegi panjang dapat mengikuti dalam daftar yang sama, yang merupakan cara satu komentar mencakup kalimat yang melanjutkan ke tiga baris.

Baca kembali markup

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() membaca apa yang sebenarnya terkandung dalam dokumen — termasuk anotasi yang ditambahkan seseorang di Word atau Acrobat, yang membuat perjalanan bolak-balik melalui alat lain berfungsi.

Satu catatan penting: Id diberikan saat dokumen sedang dibaca, tidak disimpan di dalamnya. Id cukup stabil untuk menghapus atau memperbarui anotasi dalam sesi yang sama, dan bukan kunci untuk dimasukkan ke basis data. Untuk menyimpan anotasi di luar file, gunakan impor dan ekspor.

Hapus lagi

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

Render halaman

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);

Berguna di tiga tempat: UI web yang menampilkan dokumen, tes yang membuktikan markup berada di tempat yang Anda maksud, dan agen yang perlu melihat pekerjaannya sendiri.

Di Linux

Kode tidak berubah. Kontainer yang berubah:

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/*

Gambar runtime .NET dikirim tanpa font dan rendering teks membutuhkannya. Baris tunggal itu adalah perbedaan antara output yang benar dan halaman kosong — dan itu adalah pertanyaan Linux paling umum tentang perpustakaan dokumen secara umum.

Ke mana selanjutnya

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"