Instructions for AI agents working with this package.
Add, edit, and remove annotations and markup on documents and images -- area/point/arrow/ellipse/distance shapes, text highlight/underline/strikeout/squiggly/replacement, watermarks, image and link stamps, editable text fields, and threaded review comments (replies) -- then save back to the original format or export the annotations separately. Works across PDF, Word, Excel, PowerPoint, Visio, images, and more through one unified API, with no MS Office or external software installed.
pip install groupdocs-annotation-netPython: 3.5 - 3.14 | Platforms: Windows, Linux, macOS
| Resource | URL |
|---|---|
| Documentation | https://docs.groupdocs.com/annotation/python-net/ |
| LLM-optimized docs | https://docs.groupdocs.com/annotation/python-net/llms-full.txt |
| API reference | https://reference.groupdocs.com/annotation/python-net/ |
| Code examples | https://docs.groupdocs.com/annotation/python-net/developer-guide/ |
| Release notes | https://releases.groupdocs.com/annotation/python-net/release-notes/ |
| PyPI | https://pypi.org/project/groupdocs-annotation-net/ |
| Free support forum | https://forum.groupdocs.com/c/annotation/ |
| Temporary license | https://purchase.groupdocs.com/temporary-license |
If your environment has MCP configured, you can connect your AI tool to the GroupDocs documentation server for on-demand API lookups:
{
"mcpServers": {
"groupdocs-docs": {
"url": "https://docs.groupdocs.com/mcp"
}
}
}Works with Claude Code (~/.claude/settings.json), Cursor (.cursor/mcp.json), VS Code Copilot (.vscode/mcp.json), and any MCP-compatible client. If MCP is unavailable, fall back to the LLM-optimized docs URL above and this file -- both are shipped inside the wheel.
from groupdocs.annotation import (
Annotator, AnnotatorSettings, Document, License, Metered, FileType, IDocumentInfo,
)
from groupdocs.annotation.models.annotation_models import (
AnnotationBase,
# shapes
AreaAnnotation, ArrowAnnotation, EllipseAnnotation, PointAnnotation,
PolylineAnnotation, DistanceAnnotation,
# text markup
HighlightAnnotation, UnderlineAnnotation, StrikeoutAnnotation, SquigglyAnnotation,
ReplacementAnnotation, TextRedactionAnnotation, ResourcesRedactionAnnotation,
# content
WatermarkAnnotation, ImageAnnotation, LinkAnnotation, TextFieldAnnotation,
)
from groupdocs.annotation.models import (
Rectangle, Point, Reply, User, PageInfo, VersionsList,
BorderStyle, BoxStyle, PenStyle, HorizontalAlignment, VerticalAlignment, RotationDocument,
)
from groupdocs.annotation.options import (
LoadOptions, SaveOptions, PreviewOptions, PreviewFormats, AnnotationType,
CreatePageStream, ReleasePageStream,
)
from groupdocs.annotation.exceptions import (
AnnotatorException, FileTypeNotSupportedException,
PasswordProtectedFileException, CorruptedOrDamagedFileException,
)
from groupdocs.annotation.logging import ConsoleLogger
from groupdocs.annotation.cache import FileCacheAnnotator is the entry point. The flow is always: open → one or more add(...) calls → save(). Each annotation is a plain object you configure with properties, then hand to add. Use Annotator as a context manager so the native document handle is released.
from groupdocs.annotation import Annotator
from groupdocs.annotation.models.annotation_models import AreaAnnotation
from groupdocs.annotation.models import Rectangle
with Annotator("document.pdf") as annotator:
area = AreaAnnotation()
area.box = Rectangle(100, 100, 200, 80) # x, y, width, height
area.page_number = 0
area.message = "Review this"
annotator.add(area)
annotator.save("annotated.pdf")Annotator(...) constructor. Annotator(file_path) or Annotator(stream), optionally with LoadOptions and/or AnnotatorSettings: Annotator("doc.pdf", LoadOptions(password="...")), Annotator(stream, LoadOptions(), AnnotatorSettings(...)).
add(...) accepts a single annotation or a list of annotations. save(...) writes to a path or a writable stream; pass SaveOptions to control output paging / which annotation types to render. By default save() renders all annotations onto the document.
Coordinates & colors. Geometry uses Rectangle(x, y, width, height) and Point(x, y) from groupdocs.annotation.models. Colors are ARGB integers (e.g. 65535), not Color objects.
from groupdocs.annotation.models.annotation_models import AreaAnnotation, EllipseAnnotation
from groupdocs.annotation.models import Rectangle
area = AreaAnnotation()
area.box = Rectangle(100, 100, 200, 80)
area.background_color = 65535 # ARGB int
area.pen_color = 255 # ARGB int
area.opacity = 0.7
area.page_number = 0
area.message = "Flagged region"
ell = EllipseAnnotation()
ell.box = Rectangle(50, 200, 120, 60)
ell.page_number = 0
with Annotator("document.pdf") as annotator:
annotator.add([area, ell]) # add several at once
annotator.save("annotated.pdf")Shape types share box, background_color/pen_color, and opacity. ArrowAnnotation/DistanceAnnotation/PolylineAnnotation use the same box/points surface. (Note: in 26.6 the nullable pen_width (byte?) and pen_style (PenStyle?) properties cannot yet be set from Python — a plain int does not narrow to byte?/enum across the bridge.)
from groupdocs.annotation.models.annotation_models import HighlightAnnotation
from groupdocs.annotation.models import Point
hl = HighlightAnnotation()
hl.font_color = 65535
hl.page_number = 0
# Quad points around the target text (top-left, top-right, bottom-right, bottom-left):
hl.points = [Point(80, 730), Point(240, 730), Point(240, 750), Point(80, 750)]
with Annotator("document.pdf") as annotator:
annotator.add(hl)
annotator.save("highlighted.pdf")Text-markup types expose points (a list of Point describing the marked region) and font_color; the rest of the markup family (UnderlineAnnotation, StrikeoutAnnotation, SquigglyAnnotation, ReplacementAnnotation) follows the same shape.
from groupdocs.annotation.models import Reply
r1 = Reply(); r1.comment = "Please double-check this"
r2 = Reply(); r2.comment = "Confirmed"
area.replies = [r1, r2] # attach a comment thread to any annotationReply has comment, user (a User), replied_on, parent_reply, id.
WatermarkAnnotation, ImageAnnotation (set image_path), LinkAnnotation (set url), and TextFieldAnnotation are created and added the same way — configure their properties, then annotator.add(...).
with Annotator("annotated.pdf") as annotator:
annotations = annotator.get() # all annotations -> list
annotator.update(annotations[0]) # push a modified annotation back
annotator.remove(annotations[0]) # by object, or annotator.remove(id)
annotator.save("edited.pdf")with Annotator("annotated.pdf") as annotator:
for v in annotator.get_versions_list():
for a in annotator.get_version(v):
print(v, a.id, a.message)from groupdocs.annotation.options import PreviewOptions, PreviewFormats
def create_page_stream(page_number):
return open(f"page-{page_number}.png", "wb")
with Annotator("document.pdf") as annotator:
info = annotator.document.get_document_info() # format, page count, page sizes
opts = PreviewOptions(create_page_stream)
opts.preview_format = PreviewFormats.PNG
opts.page_numbers = [0, 1]
annotator.document.generate_preview(opts)annotator.import_annotations_from_document(path) and annotator.export_annotations_from_xml_file(path) move annotations between a document/XML file and the current Annotator (each takes a single path string). Save afterwards to persist.
import io
with Annotator("document.pdf") as annotator:
annotator.add(area)
buf = io.BytesIO()
annotator.save(buf) # BytesIO is updated after save
data = buf.getvalue()from groupdocs.annotation import License
# From file
License().set_license("path/to/license.lic")
# From stream
with open("license.lic", "rb") as f:
License().set_license(f)Or auto-apply: export GROUPDOCS_LIC_PATH="path/to/license.lic"
Metered licensing is also available:
from groupdocs.annotation import Metered
Metered().set_metered_key("public-key", "private-key")
print(Metered().get_consumption_quantity(), Metered().get_consumption_credit())Evaluation vs licensed. Without a license the library still runs, but output carries an evaluation watermark (PDF) or an equivalent mark, and a page/document-count cap applies. Set GROUPDOCS_LIC_PATH (or call License().set_license(...)) and re-run to clear these. A 30-day full license is free: https://purchase.groupdocs.com/temporary-license
| Method | Returns | Description |
|---|---|---|
Annotator(file_path / stream [, load_options [, settings]]) |
Open by path or binary stream; optional LoadOptions / AnnotatorSettings. Context manager. |
|
add(annotation) / add([annotations]) |
None |
Add one annotation or a list. |
update(annotation) |
None |
Replace an existing annotation (matched by id). |
remove(annotation) / remove(id) |
None |
Remove by object or by id; a list is also accepted. |
get() |
list |
All annotations on the document. |
get(annotation_type) |
list |
Annotations of one AnnotationType. |
get_versions_list() |
list |
Available annotation versions. |
get_version(version) |
list |
Annotations in a given version. |
save(path) / save(stream) / save(path, SaveOptions) |
None |
Render annotations and write the result. |
import_annotations_from_document(path) |
None |
Pull annotations from a document/XML into this annotator. |
export_annotations_from_xml_file(path) |
None |
Move annotations between an XML file and this annotator. |
dispose() |
None |
Release native resources (handled by with). |
Annotator properties: document (Document — get_document_info(), generate_preview(...)), rotation, process_pages. AnnotatorSettings carries logger / cache.
| Type | Notes |
|---|---|
AreaAnnotation, EllipseAnnotation |
box (Rectangle), background_color, pen_color, opacity. (pen_style/pen_width not settable from Python in 26.6 — see Shape annotations note.) |
ArrowAnnotation, DistanceAnnotation, PolylineAnnotation |
Line/shape markups; box + points. |
PointAnnotation |
A single comment anchor at a point. |
HighlightAnnotation, UnderlineAnnotation, StrikeoutAnnotation, SquigglyAnnotation |
Text markup; points (quad) + font_color. |
ReplacementAnnotation, TextRedactionAnnotation, ResourcesRedactionAnnotation |
Replace / redact text or resources. |
WatermarkAnnotation |
Text watermark stamp. |
ImageAnnotation |
Image stamp (image_path). |
LinkAnnotation |
Hyperlink over a region (url). |
TextFieldAnnotation |
Editable text field. |
| All | Inherit id, message, page_number, replies, created_on, user, type from AnnotationBase. |
| Type | Notes |
|---|---|
LoadOptions(password=..., version=..., font_directories=...) |
Open protected / versioned input. |
SaveOptions |
annotation_types, first_page, last_page, only_annotated_pages, version. |
PreviewOptions(create_page_stream [, release_page_stream]) |
preview_format, page_numbers, width, height, resolution. |
PreviewFormats |
PNG, JPEG, BMP. |
AnnotationType |
Enum of annotation kinds (filter for get(...)). |
Rectangle(x, y, width, height) / Point(x, y) |
Geometry (from ...models). |
Reply |
comment, user, replied_on, parent_reply, id. |
PenStyle, BorderStyle, BoxStyle, HorizontalAlignment, VerticalAlignment, RotationDocument |
Styling enums. |
License().set_license(path_or_stream) · Metered().set_metered_key(public, private) · Metered().get_consumption_quantity() · Metered().get_consumption_credit()
- Properties: use
snake_case-- auto-mapped to .NETPascalCase - Context managers:
with Annotator(...) as a:ensures the document handle is released - Build then add: construct an annotation object, set its properties, then
add(it)— oradd([...])for several - Colors are ARGB ints: e.g.
annotation.background_color = 65535(not aColorobject) - Geometry:
Rectangle(x, y, width, height),Point(x, y); text markup uses apointsquad - Replies: attach a
list[Reply]to any annotation'srepliesfor review comments - Streams: pass
open("file", "rb")orio.BytesIO(data)where .NET expects a Stream;BytesIOis updated aftersave(stream) - Enums: case-insensitive, lazy-loaded (e.g.,
PreviewFormats.PNG,AnnotationType) - Exceptions: catch
PasswordProtectedFileException/FileTypeNotSupportedException/CorruptedOrDamagedFileException/AnnotatorException(all subclass the product-rootGroupDocsAnnotationException)
| Platform | Requirements |
|---|---|
| Windows | None |
| Linux | apt install libgdiplus libfontconfig1 ttf-mscorefonts-installer |
| macOS | brew install mono-libgdiplus |
Evaluation watermark on output -- no license. Apply one with License().set_license(...) or set GROUPDOCS_LIC_PATH; a free 30-day license is at https://purchase.groupdocs.com/temporary-license
PasswordProtectedFileException -- the source is encrypted. Open it with Annotator(path, LoadOptions(password="...")).
FileTypeNotSupportedException / CorruptedOrDamagedFileException -- the format isn't supported or the file is damaged. Check it against the supported-formats list.
System.Drawing.Common is not supported -- install libgdiplus: sudo apt install libgdiplus (Linux) / brew install mono-libgdiplus (macOS)
Gdip type initializer exception -- outdated libgdiplus: brew reinstall mono-libgdiplus (macOS)
Garbled text / missing fonts -- install fonts: sudo apt install ttf-mscorefonts-installer fontconfig && sudo fc-cache -f
DllNotFoundException: libSkiaSharp -- stale system copy conflicts with bundled version. Rename it: sudo mv /usr/local/lib/libSkiaSharp.dylib /usr/local/lib/libSkiaSharp.dylib.bak
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT errors -- do NOT set this. Install ICU: sudo apt install libicu-dev
TypeLoadException -- reinstall: pip install --force-reinstall groupdocs-annotation-net
Still stuck? Post your question at https://forum.groupdocs.com/c/annotation/ -- the development team responds directly.