diff --git a/DocumentOperator.Infrastructure/Services/PdfProcessing/DevExpressPdfProcessor.cs b/DocumentOperator.Infrastructure/Services/PdfProcessing/DevExpressPdfProcessor.cs
index 7bfb97e..59df11c 100644
--- a/DocumentOperator.Infrastructure/Services/PdfProcessing/DevExpressPdfProcessor.cs
+++ b/DocumentOperator.Infrastructure/Services/PdfProcessing/DevExpressPdfProcessor.cs
@@ -390,6 +390,247 @@ public class DevExpressPdfProcessor : IPdfProcessor
#endregion
+ #region PDF Annotation
+
+ ///
+ /// Adds an annotation to a PDF document at the specified page and rectangle.
+ ///
+ public async Task AddAnnotationAsync(
+ Stream pdfStream,
+ Domain.Models.ValueObjects.AnnotationType annotationType,
+ int pageNumber,
+ (double X1, double Y1, double X2, double Y2) rectangle,
+ string? content = null,
+ string? author = null,
+ string? color = null,
+ Domain.Models.ValueObjects.TextMarkupStyle? textMarkupStyle = null)
+ {
+ // 1. Input validation
+ ArgumentNullException.ThrowIfNull(pdfStream, nameof(pdfStream));
+
+ if (pdfStream.Length == 0)
+ throw new BadRequestException("PDF stream cannot be empty");
+
+ if (pdfStream.Position != 0)
+ throw new BadRequestException($"PDF stream must be at position 0 (current position: {pdfStream.Position})");
+
+ if (pageNumber < 1)
+ throw new BadRequestException($"Page number must be >= 1 (provided: {pageNumber})");
+
+ // Validate content requirement
+ if (annotationType is Domain.Models.ValueObjects.AnnotationType.FreeText
+ or Domain.Models.ValueObjects.AnnotationType.StickyNote)
+ {
+ if (string.IsNullOrWhiteSpace(content))
+ throw new BadRequestException($"{annotationType} annotation requires content");
+ }
+
+ // Validate TextMarkup style requirement
+ if (annotationType == Domain.Models.ValueObjects.AnnotationType.TextMarkup && textMarkupStyle == null)
+ throw new BadRequestException("TextMarkup annotation requires textMarkupStyle parameter");
+
+ byte[] annotatedPdfBytes;
+
+ try
+ {
+ // 2. Load PDF
+ using var processor = new PdfDocumentProcessor();
+ processor.LoadDocument(pdfStream);
+
+ // 3. Validate page number
+ int pageCount = processor.Document.Pages.Count;
+ if (pageNumber > pageCount)
+ throw new BadRequestException($"Page number {pageNumber} exceeds document page count ({pageCount})");
+
+ // 4. Get page facade (zero-based index)
+ var pageFacade = processor.DocumentFacade.Pages[pageNumber - 1];
+
+ // 5. Create annotation rectangle
+ var pdfRectangle = new PdfRectangle(rectangle.X1, rectangle.Y1, rectangle.X2, rectangle.Y2);
+
+ // 6. Parse color (default to yellow for highlights, red for others)
+ PdfRGBColor annotationColor = ParseColor(color) ?? (annotationType == Domain.Models.ValueObjects.AnnotationType.TextMarkup
+ ? new PdfRGBColor(1.0, 1.0, 0) // Yellow
+ : new PdfRGBColor(1.0, 0, 0)); // Red
+
+ // 7. Add annotation based on type
+ switch (annotationType)
+ {
+ case Domain.Models.ValueObjects.AnnotationType.TextMarkup:
+ AddTextMarkupAnnotation(pageFacade, pdfRectangle, textMarkupStyle!.Value, content, author, annotationColor);
+ break;
+
+ case Domain.Models.ValueObjects.AnnotationType.FreeText:
+ AddFreeTextAnnotation(pageFacade, pdfRectangle, content!, author, annotationColor);
+ break;
+
+ case Domain.Models.ValueObjects.AnnotationType.StickyNote:
+ AddStickyNoteAnnotation(pageFacade, pdfRectangle, content!, author, annotationColor);
+ break;
+
+ case Domain.Models.ValueObjects.AnnotationType.Circle:
+ AddCircleAnnotation(pageFacade, pdfRectangle, content, author, annotationColor);
+ break;
+
+ case Domain.Models.ValueObjects.AnnotationType.Square:
+ AddSquareAnnotation(pageFacade, pdfRectangle, content, author, annotationColor);
+ break;
+
+ default:
+ throw new BadRequestException($"Unsupported annotation type: {annotationType}");
+ }
+
+ // 8. Save annotated PDF
+ using var outputStream = new MemoryStream();
+ processor.SaveDocument(outputStream);
+ annotatedPdfBytes = outputStream.ToArray();
+ }
+ catch (BadRequestException)
+ {
+ throw; // Re-throw our own exceptions
+ }
+ catch (Exception ex)
+ {
+ throw new BadRequestException($"Failed to add annotation: {ex.Message}");
+ }
+
+ return await Task.FromResult(annotatedPdfBytes);
+ }
+
+ private void AddTextMarkupAnnotation(
+ PdfPageFacade pageFacade,
+ PdfRectangle rectangle,
+ Domain.Models.ValueObjects.TextMarkupStyle style,
+ string? content,
+ string? author,
+ PdfRGBColor color)
+ {
+ // Map our enum to DevExpress enum
+ var devExpressStyle = style switch
+ {
+ Domain.Models.ValueObjects.TextMarkupStyle.Highlight => PdfTextMarkupAnnotationType.Highlight,
+ Domain.Models.ValueObjects.TextMarkupStyle.Underline => PdfTextMarkupAnnotationType.Underline,
+ Domain.Models.ValueObjects.TextMarkupStyle.Strikeout => PdfTextMarkupAnnotationType.StrikeOut,
+ _ => throw new BadRequestException($"Unsupported text markup style: {style}")
+ };
+
+ var annotation = pageFacade.AddTextMarkupAnnotation(rectangle, devExpressStyle);
+
+ if (annotation != null)
+ {
+ annotation.Color = color;
+ if (!string.IsNullOrWhiteSpace(author))
+ annotation.Author = author;
+ if (!string.IsNullOrWhiteSpace(content))
+ annotation.Contents = content;
+ }
+ }
+
+ private void AddFreeTextAnnotation(
+ PdfPageFacade pageFacade,
+ PdfRectangle rectangle,
+ string content,
+ string? author,
+ PdfRGBColor color)
+ {
+ var annotation = pageFacade.AddFreeTextAnnotation(rectangle, content);
+
+ if (annotation != null)
+ {
+ annotation.Color = color;
+ if (!string.IsNullOrWhiteSpace(author))
+ annotation.Author = author;
+ }
+ }
+
+ private void AddStickyNoteAnnotation(
+ PdfPageFacade pageFacade,
+ PdfRectangle rectangle,
+ string content,
+ string? author,
+ PdfRGBColor color)
+ {
+ // Sticky note uses a point (top-left corner of rectangle)
+ var point = new PdfPoint(rectangle.Left, rectangle.Top);
+ var annotation = pageFacade.AddTextAnnotation(point);
+
+ if (annotation != null)
+ {
+ annotation.Color = color;
+ annotation.Contents = content;
+ if (!string.IsNullOrWhiteSpace(author))
+ annotation.Author = author;
+ }
+ }
+
+ private void AddCircleAnnotation(
+ PdfPageFacade pageFacade,
+ PdfRectangle rectangle,
+ string? content,
+ string? author,
+ PdfRGBColor color)
+ {
+ var annotation = pageFacade.AddCircleAnnotation(rectangle);
+
+ if (annotation != null)
+ {
+ annotation.Color = color;
+ if (!string.IsNullOrWhiteSpace(author))
+ annotation.Author = author;
+ if (!string.IsNullOrWhiteSpace(content))
+ annotation.Contents = content;
+ }
+ }
+
+ private void AddSquareAnnotation(
+ PdfPageFacade pageFacade,
+ PdfRectangle rectangle,
+ string? content,
+ string? author,
+ PdfRGBColor color)
+ {
+ var annotation = pageFacade.AddSquareAnnotation(rectangle);
+
+ if (annotation != null)
+ {
+ annotation.Color = color;
+ if (!string.IsNullOrWhiteSpace(author))
+ annotation.Author = author;
+ if (!string.IsNullOrWhiteSpace(content))
+ annotation.Contents = content;
+ }
+ }
+
+ ///
+ /// Parses hex color string (e.g., "FF0000" for red) to PdfRGBColor
+ ///
+ private PdfRGBColor? ParseColor(string? hexColor)
+ {
+ if (string.IsNullOrWhiteSpace(hexColor))
+ return null;
+
+ try
+ {
+ // Remove '#' if present
+ hexColor = hexColor.TrimStart('#');
+
+ if (hexColor.Length != 6)
+ throw new BadRequestException($"Color must be 6-digit hex (e.g., 'FF0000'), got: '{hexColor}'");
+
+ int r = Convert.ToInt32(hexColor.Substring(0, 2), 16);
+ int g = Convert.ToInt32(hexColor.Substring(2, 2), 16);
+ int b = Convert.ToInt32(hexColor.Substring(4, 2), 16);
+
+ return new PdfRGBColor(r / 255.0, g / 255.0, b / 255.0);
+ }
+ catch (Exception ex)
+ {
+ throw new BadRequestException($"Invalid color format: '{hexColor}'. Expected 6-digit hex (e.g., 'FF0000'). Error: {ex.Message}");
+ }
+ }
+
+ #endregion
+
#region Private Helpers
///