Większość odpowiedzi na to pytanie ma pięć lat, dotyczy wyłącznie PDF i zaczyna się od prośby o mierzenie współrzędnych względem strony, której nie widać. Oto wersja z 2026 roku: siedem wywołań, dwie konwencje określające, gdzie faktycznie pojawia się znacznik, oraz jedyna linijka, której potrzebuje kontener Linux.
Wszystko poniżej działa na .NET 8 i .NET 10, na Windows i Linux, bez zainstalowanego Adobe Acrobat i Microsoft Office — a ten sam kod działa na DOCX, XLSX, PPTX i obrazach. Zmienia się tylko ścieżka do pliku.
Wersja gotowa do uruchomienia znajduje się na GitHubie: annotate-pdf-csharp-net.
Instalacja
dotnet add package GroupDocs.Annotation
Bez licencji biblioteka działa w trybie ewaluacyjnym, a wynik zawiera znaki oceny. Tymczasowa licencja usuwa je:
new License().SetLicense("GroupDocs.Annotation.lic");
Otwórz dokument
using (Annotator annotator = new Annotator("quarterly-review.pdf"))
{
// add, read or remove markup here
}
Jedna rzecz, którą warto wiedzieć od razu: annotator trzyma plik otwarty aż do jego zwolnienia i nie może zapisać nad plikiem, który odczytuje. Zapisz do innej ścieżki lub do pliku tymczasowego, który później przeniesiesz na miejsce.
Nie mierz współrzędnych — wyszukaj tekst
To jest część, którą pomijają większość tutoriali, a to właśnie ona sprawia, że kod adnotacji przetrwa następną wersję dokumentu. GetDocumentInfo() zwraca każdą stronę wraz z jej rozmiarem i liniami tekstu, każda z ramką:
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
}
}
}
Teraz adnotację można umieścić tam, gdzie dokument wskazuje dzisiaj, zamiast tam, gdzie wskazywał w zeszłym roku.
Dodaj podświetlenie z komentarzem i odpowiedzią
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 to komentarz. Replies rozpoczyna wątek — w czytniku PDF jest to notatka z odpowiedzią, w Wordzie to komentarz z odpowiedzią pod nim. User pozwala później znaleźć i usunąć dokładnie tę adnotację recenzenta, pozostawiając inne nietknięte.
Dwie konwencje, które obejmują wszystkich
Spójrz uważnie na dwie linie obliczające top i bottom oraz na PageNumber.
Numery stron są liczone podwójnie. PageInfo.PageNumber jest 1‑based. AnnotationBase.PageNumber jest 0‑based. Mieszając je, każda adnotacja jest o jedną stronę przesunięta — a te przeznaczone na ostatnią stronę znikają, ponieważ adnotacja skierowana do nieistniejącej strony jest pomijana przy zapisie, bez wyjątku i ostrzeżenia. Jeśli adnotacja „nie pojawiła się”, sprawdź indeks strony przed czymkolwiek innym.
Dwa pochodzenia. TextLineInfo i AreaAnnotation.Box są mierzone od górnego lewego rogu. Znaczniki tekstowe — podświetlenie, podkreślenie, przekreślenie, falisty — są mierzone od dolnego lewego, co tak działa w PDF.
| Element | Pochodzenie | Numeracja stron |
|---|---|---|
PageInfo, TextLineInfo |
top-left | 1-based |
AnnotationBase.PageNumber |
— | 0-based |
AreaAnnotation.Box |
top-left | — |
HighlightAnnotation.Points |
bottom-left | — |
PreviewOptions.PageNumbers |
— | 1-based |
Cztery punkty tworzą jeden prostokąt w następującej kolejności: górny lewy, górny prawy, dolny lewy, dolny prawy. Więcej prostokątów może następować w tej samej liście, co pozwala jednemu komentarzowi objąć zdanie rozciągające się na trzy linie.
Odczytaj adnotacje
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() odczytuje to, co dokument faktycznie zawiera — w tym adnotacje dodane przez osobę w Wordzie lub Acrobat, co umożliwia pełny obieg przez inne narzędzia.
Jedna uwaga warta poznania: Id jest przypisywane podczas odczytu dokumentu, nie jest w nim przechowywane. Jest wystarczająco stabilne, aby usunąć lub zaktualizować adnotację w tej samej sesji, i nie jest kluczem do bazy danych. Aby przechowywać adnotacje poza plikiem, użyj importu i eksportu.
Usuń je ponownie
annotator.Remove(1); // by id
annotator.Remove(annotator.Get()
.Where(a => a.User?.Name == "Reviewer").ToList()); // or by author
Renderuj strony
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);
Przydatne w trzech miejscach: interfejs webowy wyświetlający dokument, test potwierdzający, że adnotacja trafiła tam, gdzie zamierzałeś, oraz agent, który musi zobaczyć własną pracę.
Na Linuxie
Kod się nie zmienia. Zmienia się kontener:
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/*
Obrazy środowiska uruchomieniowego .NET są dostarczane bez czcionek, a renderowanie tekstu ich potrzebuje. Ta jedyna linijka jest różnicą między poprawnym wynikiem a pustymi stronami — i jest najczęstszym pytaniem o Linux dotyczących bibliotek dokumentów w ogóle.
Co dalej
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"
- Dokumentacja: Annotate a PDF in C#
- Pozwól LLM przejrzeć umowę i zostawić prawdziwe komentarze — te same wywołania, sterowane modelem
- Zbuduj interfejs adnotacji w przeglądarce — te same wywołania, sterowane przez osobę w przeglądarce