Recursive AI

Highlight text in a PDF in C#

OnePDF is a free, open-source PDF library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

Add a yellow highlight and a review comment without changing the underlying text.

Terminal
dotnet add package RecursiveAI.OnePdf

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

input.pdf is the three-page sample in the example zip.

C#
using OnePdf;
 
using var doc = PdfDocument.Open("input.pdf",
    new PdfOpenOptions { AllowEditing = true });
doc.Pages[0].AddMarkupAnnotation(new()
{
    Kind = PdfMarkupKind.Highlight,
    Quads = [PdfMarkupQuad.FromRectangle(
        new PdfRectangle(72, 697, 110, 712))],
    Color = [1, 1, 0],
    Contents = "Check this figure"
});
doc.Save("highlighted.pdf");
Console.WriteLine("Saved highlighted.pdf");
Text
Saved highlighted.pdf

Unzip the example, then dotnet run.

input.pdf is included in the zip: an original, public-domain sample with three identical, unrotated pages. Each page contains this line in 12-point Helvetica at (72, 700):

Text
Review this figure

The full program

The four PdfRectangle(72, 697, 110, 712) numbers are left, bottom, right and top, in points measured from the bottom-left on this sample page. The full program finds these coordinates for you by extracting the word “Review” on the first page, then highlights its bounds and reopens the result to read the annotation.

C#
using OnePdf;
 
using var doc = PdfDocument.Open("input.pdf",
    new PdfOpenOptions { AllowEditing = true });
var page = doc.Pages[0];
// This sample has upright text and no page rotation.
var words = page.ExtractText().Blocks
    .SelectMany(block => block.Lines)
    .SelectMany(line => line.Words)
    .Where(word => word.Text == "Review").ToArray();
if (words.Length == 0)
    throw new InvalidOperationException("Review was not found.");
page.AddMarkupAnnotation(new()
{
    Kind = PdfMarkupKind.Highlight,
    Quads = words.Select(word =>
        PdfMarkupQuad.FromRectangle(word.Bounds)).ToArray(),
    Color = [1, 1, 0],
    Contents = "Check this figure",
    Title = "Reviewer",
    Name = "review-1"
});
doc.Save("highlighted.pdf");
using var saved = PdfDocument.Open("highlighted.pdf");
var annotation = saved.Pages[0].Annotations.Single();
Console.WriteLine($"Matched words: {words.Length}");
Console.WriteLine($"{annotation.Subtype}: {annotation.Contents}");

dotnet run prints:

Text
Matched words: 1
Highlight: Check this figure

Invalid markup geometry throws ArgumentException before the annotation is added.

Variations

Underline the same known rectangle and append an incremental update:

C#
using OnePdf;
 
using var doc = PdfDocument.Open("input.pdf",
    new PdfOpenOptions { AllowEditing = true });
doc.Pages[0].AddMarkupAnnotation(new()
{
    Kind = PdfMarkupKind.Underline,
    Quads = [PdfMarkupQuad.FromRectangle(
        new PdfRectangle(72, 697, 110, 712))],
    Contents = "Check this word"
});
using (var output = File.Create("underlined.pdf"))
    doc.SaveIncremental(output);
Console.WriteLine("Saved underlined.pdf");
Text
Saved underlined.pdf

The short answer

Open the PDF with editing enabled, identify the text’s position, and pass explicit quadrilaterals to AddMarkupAnnotation with Kind = PdfMarkupKind.Highlight. Save the result to another path. OnePDF creates the annotation’s normal appearance as well as its geometry, so the file carries the drawing of the highlight, not just a request for a viewer to invent one.

The snippet at the top marks a known rectangle. The full program extracts words and marks the word “Review” using its bounds. Both add review markup; neither deletes or replaces the page’s text.

How can I highlight text in a PDF for free?

OnePDF is free and open source under MIT or Apache-2.0. Install RecursiveAI.OnePdf in a .NET 10 project and open the file with AllowEditing = true. No separate annotation package is needed. Set Contents to the review comment and Title to the reviewer’s name if you want that information in the annotation.

The markup documentation describes the generated appearance: a highlight uses the Multiply blend mode. Color components are numbers between zero and one. Three components specify RGB, so [1, 1, 0] is yellow. Opacity is optional. The default annotation flags include Print, allowing a viewer to include the annotation when printing.

A comment is annotation data, not new page text. Whether a viewer opens a comment panel automatically is separate from storing the comment. You can supply an explicit Popup when you need a linked pop-up annotation and its rectangle.

Is there a way to highlight text in a PDF?

Yes, when you know the geometry. For a searchable PDF, extract the page’s blocks, lines and words and select the words you want. The full program uses an exact, case-sensitive word match. It does not implement phrase matching, substring matching or optical character recognition. A scan without a text layer will not produce the target word through this extraction path.

For upright text on an unrotated page, PdfMarkupQuad.FromRectangle(word.Bounds) covers the word. For a phrase spanning lines, keep the separate word or line areas rather than making one large rectangle that also covers the intervening whitespace.

Rotated text needs more care. Text extraction reports geometry in upright page space with the page’s rotation applied. Markup quadrilaterals use default user space: the page’s stored coordinates before display rotation or cropping, with y increasing upward. On this sample, the origin is at the bottom-left and one point is 1/72 inch; a PDF’s UserUnit can change that scale. See drawing coordinates for the coordinate rules. Convert between those spaces for rotated pages before constructing the quads. For angled text, an axis-aligned word box is not its oriented outline: construct the four corners explicitly. The corners are ordered upper left, upper right, lower left, lower right, with “upper” defined relative to the text’s ascenders, not necessarily the page’s top.

How do I highlight text in a PDF and save it?

Use Save for an ordinary full rewrite. Reopen the result and read Pages[0].Annotations to inspect the subtype and comment, as the full program does. Extraction ignores annotations, so it still returns the original text rather than the comment.

On signed files, use SaveIncremental to retain the original bytes. Retaining signed bytes does not mean every modification is permitted. Certification levels describe changes the signer permits: P=1 allows no changes; P=2 allows form filling and signing; P=3 also allows annotation changes. The signed-file rules allow new markup for certification level P=3, but report annotation changes as disallowed for P=1 or P=2. Approval signatures without certification have the library’s documented conservative allowance only when the update appends the supported markup and linked pop-ups without other changes. A full save rewrites the file and drops the signatures’ coverage.

All onepdf recipes