adsfolio.pdf.annotation_merge
Move annotations between PDF editions of the same paper into a new copy.
The target PDF is never modified. A user places each source annotation on the target, or waives it explicitly, and the result is written to a new file after it has been re-read and checked. The companion annotation_matching module finds candidate text locations. Every placement still needs explicit approval.
- class adsfolio.pdf.annotation_merge.AnnotationItem(item_id, provenance, source_sha256, page, xref, subtype, supported, companion, reason, rect, content='', title='', subject='', icon_name='', stroke=(), fill=(), opacity=-1.0, flags=0, border_width=None, dashes=(), quads=(), vertices=(), ink=(), line_ends=None, text='', page_rotation=0)[source]
Bases:
objectOne annotation found in the source PDF, with the data needed to copy it.
Coordinates are PDF points with the origin at the top-left of the page, as reported by PyMuPDF. supported is false for subtypes that cannot be copied without approximation; companion marks popup windows, which belong to a parent annotation and carry no content of their own.
- Parameters:
- exception adsfolio.pdf.annotation_merge.AnnotationMergeError[source]
Bases:
RuntimeErrorRaised when a merge cannot be performed or fails verification.
- class adsfolio.pdf.annotation_merge.MergeInput(path, sha256)[source]
Bases:
objectAn original PDF path and the exact content used for a saved merge.
- class adsfolio.pdf.annotation_merge.MergeReport(output_path, transferred=<factory>, already_present=<factory>, waived=<factory>, unresolved=<factory>, companions=<factory>, verified=False, input_files=(), output_sha256='')[source]
Bases:
objectOutcome of one merge. complete is true only if nothing is left over.
- Parameters:
- input_files: tuple[adsfolio.pdf.annotation_merge.MergeInput, ...] = ()
- exception adsfolio.pdf.annotation_merge.MissingDependencyError[source]
Bases:
RuntimeErrorRaised when PyMuPDF is not installed.
- class adsfolio.pdf.annotation_merge.Placement(item_id, page, rects=(), point=None)[source]
Bases:
objectWhere one source annotation goes in the target.
Markup annotations use rects (word-line boxes on page). Other supported annotations use point, the target position of the source annotation’s top-left corner; the shape is translated, never scaled.
- Parameters:
- class adsfolio.pdf.annotation_merge.WordIndex(path)[source]
Bases:
objectWords of every page of one PDF, read once for repeated exact lookups.
- Parameters:
path (Path)
- adsfolio.pdf.annotation_merge.build_merged_copy(target_path, source_path, items, placements, waived, output_path)[source]
Write a verified copy of the target with the placed source annotations.
- Parameters:
target_path (Path) – The canonical PDF and the PDF whose annotations are copied. Both are only read.
source_path (Path) – The canonical PDF and the PDF whose annotations are copied. Both are only read.
items (list of AnnotationItem) – Result of enumerate_annotations for source_path.
placements (dict) – User placements keyed by item_id.
waived (set of str) – item_id values the user chose not to copy. Only unsupported, non-popup items may be waived.
output_path (Path) – New file. An existing path is never overwritten, and its parent directory must already exist.
- Returns:
Items neither placed nor waived are listed as unresolved. Nothing is written unless every transferred annotation re-reads correctly; otherwise AnnotationMergeError is raised.
- Return type:
Notes
Each copied annotation carries a private provenance key (hash of the source file, page and object number). A later merge of the same source into the merged copy reports those items as already present instead of adding duplicates. Provenance is lost if another tool rewrites the annotation, and it does not link different files of one paper.
- adsfolio.pdf.annotation_merge.enumerate_annotations(path)[source]
List every annotation object in path, supported or not.
Links, form widgets, popups and all other objects in a page’s annotation array are included, so nothing is skipped silently. Annotations that an earlier merge added to this file are listed like any other.
- Parameters:
path (pathlib.Path)
- Return type:
- adsfolio.pdf.annotation_merge.import_fitz()[source]
Import PyMuPDF, or raise MissingDependencyError with install advice.
- Return type:
- adsfolio.pdf.annotation_merge.line_rects(words)[source]
Merge PyMuPDF word tuples into one bounding box per text line.
- adsfolio.pdf.annotation_merge.placement_error(item, placement, page_rect, page_rotation=0)[source]
Return why placement is invalid for item, or None if it is valid.
- Parameters:
placement (adsfolio.pdf.annotation_merge.Placement)
page_rotation (int)
- Return type:
str | None
- adsfolio.pdf.annotation_merge.suggest_exact_placement(item, index)[source]
Suggest a markup placement when the source words occur exactly once.
The source words must appear as the identical run of words exactly once in the whole target. Case, punctuation and hyphenation must agree; anything else returns None. The caller must still ask the user to confirm.
- Parameters:
- Return type: