اکثر پاسخ‌های این سؤال پنج سال پیش هستند، فقط برای PDF و با این شروع می‌شوند که از شما می‌خواهند مختصات را نسبت به صفحه‌ای که نمی‌توانید ببینید اندازه‌گیری کنید. این نسخهٔ 2026 است: هفت فراخوان، دو قراردادی که تعیین می‌کنند نشانه‌گذاری دقیقاً کجا قرار می‌گیرد، و یک خطی که یک کانتینر لینوکس نیاز دارد.

تمام موارد زیر بر روی .NET 8 و .NET 10، در ویندوز و لینوکس اجرا می‌شوند، بدون نصب Adobe Acrobat و Microsoft Office — و همان کد بر روی DOCX, XLSX, PPTX و تصاویر کار می‌کند. فقط مسیر فایل تغییر می‌کند.

نسخهٔ قابل اجرا در گیت‌هاب موجود است: annotate-pdf-csharp-net.

نصب

dotnet add package GroupDocs.Annotation

بدون لایسنس، کتابخانه در حالت ارزیابی اجرا می‌شود و خروجی دارای علامت‌های ارزیابی است. یک لایسنس موقت این علامت‌ها را حذف می‌کند:

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

باز کردن سند

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

یک نکتهٔ مهم از ابتدا: Annotator فایل را تا زمان آزادسازی (Dispose) باز نگه می‌دارد و نمی‌تواند روی فایلی که می‌خواند ذخیره‌سازی کند. باید به مسیر دیگری ذخیره کنید، یا به یک فایل موقت که پس از آن به مکان نهایی منتقل می‌کنید.

مختصات را اندازه‌گیری نکنید — به‌جای آن متن را جستجو کنید

این بخشی است که اکثر آموزش‌ها از آن می‌گذرد، و همان بخشی است که باعث می‌شود کد حاشیه‌نویسی در نسخهٔ بعدی سند نیز کار کند. 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 همان چیزی است که به مرحلهٔ بعدی اجازه می‌دهد حاشیه‌نویسی دقیقاً این مرورگر را پیدا و حذف کند و دیگران را دست‌نخورده بگذارد.

دو قراردادی که همه را در بر می‌گیرد

به‌دقت به دو خطی که top و bottom را محاسبه می‌کنند، و به PageNumber نگاه کنید.

شمارهٔ صفحات دو بار شمارش می‌شوند. PageInfo.PageNumber یک‌پایه است.
AnnotationBase.PageNumber صفرپایه است. اگر آن‌ها را مخلوط کنید، هر حاشیه‌نویسی یک صفحه اشتباه خواهد بود — و حاشیه‌نویسی‌های مربوط به آخرین صفحه ناپدید می‌شوند، زیرا حاشیه‌نویسی‌ای که به صفحه‌ای که وجود ندارد ارجاع داده شده است، هنگام ذخیره حذف می‌شود، بدون استثنا و بدون هشدار. اگر حاشیه‌نویسی «ظاهر نشد»، ابتدا شاخص صفحه را بررسی کنید.

دو مبدأ. TextLineInfo و AreaAnnotation.Box از گوشهٔ بالا‑چپ اندازه‌گیری می‌شوند.
نشانه‌گذاری متن — برجسته‌سازی، زیرخط، خط‌خورده، خط‌موج‌دار — از پایین‑چپ اندازه‌گیری می‌شود، که همان‌طور که PDF عمل می‌کند.

Member Origin Page numbering
PageInfo, TextLineInfo بالا‑چپ 1-based
AnnotationBase.PageNumber — 0-based
AreaAnnotation.Box بالا‑چپ —
HighlightAnnotation.Points پایین‑چپ —
PreviewOptions.PageNumbers — 1-based

چهار نقطه یک مستطیل را تشکیل می‌دهند، به این ترتیب: بالا‑چپ، بالا‑راست، پایین‑چپ، پایین‑راست. مستطیل‌های بیشتری می‌توانند در همان فهرست ادامه یابند، که این روش است که یک نظر واحد یک جملهٔ چندخطی را پوشش می‌دهد.

خواندن حاشیه‌نویسی

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

در سه مکان مفید است: یک رابط کاربری وب که سند را نشان می‌دهد، یک تست که ثابت می‌کند حاشیه‌نویسی در جایی که منظور شما بوده قرار گرفته، و یک عامل که نیاز دارد به کار خود نگاه کند.

در لینوکس

کد تغییر نمی‌کند. کانتینر تغییر می‌کند:

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 بدون فونت‌ها عرضه می‌شوند و رندر متن به آن‌ها نیاز دارد. آن خط واحد تفاوت بین خروجی صحیح و صفحات خالی است — و رایج‌ترین سؤال لینوکس درباره کتابخانه‌های سند به‌طور کلی است.

گام‌های بعدی

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"