Більшість відповідей на це питання старі п’ять років, лише для PDF і починаються з того, що просять вас вимірювати координати щодо сторінки, яку ви не бачите. Ось версія 2026 року: сім викликів, дві конвенції, які визначають, куди саме потрапляє розмітка, і один рядок, потрібний Linux‑контейнеру.
Все нижче працює на .NET 8 і .NET 10, на Windows і Linux, без встановлених Adobe Acrobat та Microsoft Office — і той самий код працює з DOCX, XLSX, PPTX та зображеннями. Змінюється лише шлях до файлу.
Запускова версія розміщена на GitHub:
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 тримає файл відкритим, доки не буде звільнений, і не може перезаписати файл, який читає. Збережіть у інший шлях або у тимчасовий файл, який потім перемістите на місце.
Не вимірюйте координати — шукайте текст
Це частина, яку пропускає більшість підручників, і саме вона дозволяє коду анотацій вижити в наступній версії документа. 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 має з індексацією 1.
AnnotationBase.PageNumber має з індексацією 0. Змішайте їх, і кожна анотація буде зміщена на одну сторінку — а анотації, призначені для останньої сторінки, зникають, бо анотація, що посилається на неіснуючу сторінку, відкидається під час збереження без виключення і без попередження. Якщо розмітка «не з’явилася», спершу перевірте індекс сторінки.
Два походження. TextLineInfo і AreaAnnotation.Box вимірюються від верхнього лівого кута. Текстова розмітка — виділення, підкреслення, закреслення, хвилясте — вимірюється від нижнього лівого кута, що відповідає способу роботи PDF.
| Член | Походження | Нумерація сторінок |
|---|---|---|
PageInfo, TextLineInfo |
верхній лівий | з індексацією 1 |
AnnotationBase.PageNumber |
— | з індексацією 0 |
AreaAnnotation.Box |
верхній лівий | — |
HighlightAnnotation.Points |
нижній лівий | — |
PreviewOptions.PageNumbers |
— | з індексацією 1 |
Чотири точки утворюють один прямокутник у такому порядку: верхній лівий, верхній правий, нижній лівий, нижній правий. Додаткові прямокутники можуть слідувати в тому ж списку, що дозволяє одній коментарі охопити речення, що переноситься на три рядки.
Прочитати розмітку назад
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);
Корисно в трьох випадках: веб‑інтерфейс, що показує документ, тест, що доводить, що розмітка опинилася там, куди ви планували, і агент, якому потрібно переглянути свою роботу.
На 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 runtime постачаються без шрифтів, а рендеринг тексту їх потребує. Цей один рядок — різниця між правильним виводом і порожніми сторінками — і це найпоширеніше питання про 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"
- Документація: Annotate a PDF in C#
- Let an LLM review a contract and leave real comments — ті ж виклики, керовані моделлю
- Build a browser annotation UI — ті ж виклики, керовані людиною у браузері