Skip to content

Commit

Permalink
ENH: Add Polygon annotation
Browse files Browse the repository at this point in the history
See #107
  • Loading branch information
MartinThoma committed Jan 15, 2023
1 parent f0c0a1d commit a6e2b1c
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 1 deletion.
26 changes: 26 additions & 0 deletions docs/user/adding-pdf-annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,32 @@ If you want the rectangle to be filled, use the `interiour_color="ff0000"` param

This method uses the "square" annotation type of the PDF format.

## Polygon

If you want to add a polygon like this:

![](annotation-polygon.png)

you can use the {py:class}`AnnotationBuilder <pypdf.generic.AnnotationBuilder>`:

```python
pdf_path = os.path.join(RESOURCE_ROOT, "crazyones.pdf")
reader = PdfReader(pdf_path)
page = reader.pages[0]
writer = PdfWriter()
writer.add_page(page)

# Add the line
annotation = AnnotationBuilder.polygon(
vertices=[(50, 550), (200, 650), (70, 750), (50, 700)],
)
writer.add_annotation(page_number=0, annotation=annotation)

# Write the annotated file to disk
with open("annotated-pdf.pdf", "wb") as fp:
writer.write(fp)
```

## Link

If you want to add a link, you can use
Expand Down
Binary file added docs/user/annotation-polygon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 28 additions & 1 deletion pypdf/generic/_annotations.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional, Tuple, Union
from typing import List, Optional, Tuple, Union

from ._base import (
BooleanObject,
Expand Down Expand Up @@ -210,6 +210,33 @@ def rectangle(

return square_obj

@staticmethod
def polygon(vertices: List[Tuple[float, float]]) -> DictionaryObject:
if len(vertices) == 0:
raise ValueError("A polygon needs at least 1 vertex with two coordinates")
x_min, y_min = vertices[0][0], vertices[0][1]
x_max, y_max = vertices[0][0], vertices[0][1]
for x, y in vertices:
x_min = min(x_min, x)
y_min = min(y_min, y)
x_max = min(x_max, x)
y_max = min(y_max, y)
rect = RectangleObject((x_min, y_min, x_max, y_max))
coord_list = []
for x, y in vertices:
coord_list.append(NumberObject(x))
coord_list.append(NumberObject(y))
obj = DictionaryObject(
{
NameObject("/Type"): NameObject("/Annot"),
NameObject("/Subtype"): NameObject("/Polygon"),
NameObject("/Vertices"): ArrayObject(coord_list),
NameObject("/IT"): NameObject("PolygonCloud"),
NameObject("/Rect"): RectangleObject(rect),
}
)
return obj

@staticmethod
def link(
rect: Union[RectangleObject, Tuple[float, float, float, float]],
Expand Down
22 changes: 22 additions & 0 deletions tests/test_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,28 @@ def test_annotation_builder_free_text():
os.remove(target) # comment this out for manual inspection


def test_annotation_builder_polygon():
# Arrange
pdf_path = RESOURCE_ROOT / "crazyones.pdf"
reader = PdfReader(pdf_path)
page = reader.pages[0]
writer = PdfWriter()
writer.add_page(page)

# Act
annotation = AnnotationBuilder.polygon(
vertices=[(50, 550), (200, 650), (70, 750), (50, 700)],
)
writer.add_annotation(0, annotation)

# Assert: You need to inspect the file manually
target = "annotated-pdf.pdf"
with open(target, "wb") as fp:
writer.write(fp)

os.remove(target) # comment this out for manual inspection


def test_annotation_builder_line():
# Arrange
pdf_path = RESOURCE_ROOT / "crazyones.pdf"
Expand Down

0 comments on commit a6e2b1c

Please sign in to comment.