Hầu hết các câu trả lời cho câu hỏi này đều đã cũ năm năm, chỉ hỗ trợ PDF và bắt đầu bằng việc yêu cầu bạn đo tọa độ so với một trang mà bạn không thể nhìn thấy. Đây là phiên bản 2026: bảy lời gọi, hai quy ước quyết định vị trí thực tế của đánh dấu, và một dòng lệnh duy nhất mà một container Linux cần.

Mọi thứ dưới đây chạy trên .NET 8 và .NET 10, trên Windows và Linux, không cần Adobe Acrobat và không cần cài đặt Microsoft Office — và cùng một đoạn mã hoạt động với DOCX, XLSX, PPTX và hình ảnh. Chỉ cần thay đổi đường dẫn tệp.

Phiên bản có thể chạy được nằm trên GitHub:
annotate-pdf-csharp-net.

Cài đặt

dotnet add package GroupDocs.Annotation

Nếu không có giấy phép, thư viện sẽ chạy ở chế độ đánh giá và đầu ra sẽ có dấu hiệu đánh giá. Một
giấy phép tạm thời sẽ loại bỏ chúng:

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

Mở tài liệu

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

Một điều cần biết ngay từ đầu: annotator giữ tệp mở cho đến khi nó được giải phóng, và nó không thể lưu đè lên tệp đang đọc. Hãy lưu vào một đường dẫn khác, hoặc vào một tệp tạm thời rồi di chuyển vào vị trí cuối cùng.

Không đo tọa độ — tìm kiếm văn bản

Đây là phần mà hầu hết các hướng dẫn bỏ qua, và cũng là phần giúp mã chú thích tồn tại qua các phiên bản tài liệu tiếp theo. GetDocumentInfo() trả về mọi trang cùng kích thước và các dòng văn bản, mỗi dòng có một hộp:

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

Bây giờ chú thích có thể được đặt dựa trên những gì tài liệu hiện đang chứa, thay vì dựa trên những gì nó đã nói vào năm ngoái.

Thêm một đoạn tô sáng kèm bình luận và trả lời

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 là bình luận. Replies bắt đầu một chuỗi trả lời — trong trình đọc PDF nó là một ghi chú có phản hồi, trong Word nó là một bình luận có trả lời phía dưới. User cho phép một lần chạy sau này tìm và xóa chính xác đánh dấu của người đánh giá này mà không ảnh hưởng đến những người khác.

Hai quy ước khiến mọi người đều hiểu

Hãy nhìn kỹ vào hai dòng tính toán top và bottom, và vào PageNumber.

Số trang được đếm hai lần. PageInfo.PageNumber là dựa trên 1.
AnnotationBase.PageNumber là dựa trên 0. Khi trộn lẫn chúng, mọi chú thích sẽ lệch một trang — và những chú thích dành cho trang cuối cùng sẽ biến mất, vì một chú thích được gán cho một trang không tồn tại sẽ bị bỏ qua khi lưu, mà không có ngoại lệ hay cảnh báo. Nếu “đánh dấu không xuất hiện”, hãy kiểm tra chỉ số trang trước mọi thứ khác.

Hai gốc tọa độ. TextLineInfo và AreaAnnotation.Box được đo từ góc trên‑trái.
Đánh dấu văn bản — tô sáng, gạch chân, gạch ngang, gợn sóng — được đo từ góc dưới‑trái, đó là cách PDF hoạt động.

Thành viên Gốc Đánh số trang
PageInfo, TextLineInfo trên‑trái dựa trên 1
AnnotationBase.PageNumber — dựa trên 0
AreaAnnotation.Box trên‑trái —
HighlightAnnotation.Points dưới‑trái —
PreviewOptions.PageNumbers — dựa trên 1

Bốn điểm tạo thành một hình chữ nhật, theo thứ tự: trên‑trái, trên‑phải, dưới‑trái, dưới‑phải. Nhiều hình chữ nhật có thể tiếp nối trong cùng một danh sách, đó là cách một bình luận duy nhất bao phủ một câu dài qua ba dòng.

Đọc lại các đánh dấu

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() đọc những gì tài liệu thực sự chứa — bao gồm cả các chú thích mà người dùng thêm vào trong Word hoặc Acrobat, điều này giúp vòng quay qua các công cụ khác hoạt động.

Một lưu ý đáng biết: Id được gán khi tài liệu đang được đọc, không được lưu trong tài liệu. Nó đủ ổn định để xóa hoặc cập nhật một chú thích trong cùng một phiên, và không phải là khóa để đưa vào cơ sở dữ liệu. Để lưu các chú thích bên ngoài tệp, hãy sử dụng nhập và xuất.

Xóa lại

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

Kết xuất các trang

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

Có ích ở ba nơi: giao diện web hiển thị tài liệu, một bài kiểm tra chứng minh các đánh dấu đã được đặt đúng vị trí, và một tác nhân cần xem lại công việc của mình.

Trên Linux

Mã không thay đổi. Container thì thay đổi:

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

Các ảnh Docker runtime .NET không đi kèm phông chữ và việc hiển thị văn bản cần chúng. Dòng lệnh đơn này là sự khác biệt giữa đầu ra đúng và các trang trắng — và là câu hỏi Linux phổ biến nhất về các thư viện tài liệu nói chung.

Bước tiếp theo

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"