Files
Copies/copienator/annotation_layout.py
T

76 lines
2.3 KiB
Python

"""Answer provenance and reproducible annotation spacing (200 dpi pixels)."""
from hashlib import sha256
from pathlib import Path
import shutil
import pymupdf
from copienator import configuration
from copienator.json_io import atomic_write_json, read_json
def fingerprint(path):
return sha256(Path(path).read_bytes()).hexdigest()
def metadata_path(pdf):
return Path(pdf).with_suffix('.layout.json')
def answer_parts(pdf):
"""Ignore stale sidecars after a PDF is replaced or manually edited."""
if pdf is None or not Path(pdf).is_file():
return []
try:
data = read_json(metadata_path(pdf), default={})
if data.get('sha256') == fingerprint(pdf):
parts = data.get('parts', [])
if isinstance(parts, list) and all(isinstance(part, dict) for part in parts):
return parts
except (OSError, ValueError, TypeError, AttributeError):
pass
return []
def save_parts(pdf, parts):
atomic_write_json(metadata_path(pdf), {
'version': 1, 'sha256': fingerprint(pdf), 'parts': parts,
})
def page_parts(pdf):
parts = answer_parts(pdf)
if not Path(pdf).is_file():
return []
with pymupdf.open(pdf) as document:
if len(parts) == len(document):
return parts
return [{'margin_status': 'unknown'} for _ in document]
def copy_answer_pdf(source, destination):
parts = answer_parts(source)
shutil.copy(source, destination)
save_parts(destination, parts)
def move_answer_pdf(source, destination):
parts = answer_parts(source)
shutil.move(str(source), str(destination))
save_parts(destination, parts)
metadata_path(source).unlink(missing_ok=True)
def layout_for(pdf):
low = any(part.get('margin_status') == 'low' for part in answer_parts(pdf))
margin = (configuration.ANNOTATION_LOW_MARGIN_LEFT if low
else configuration.ANNOTATION_MARGIN_LEFT)
if type(margin) is not int or margin < 0:
raise ValueError('Annotation left margins must be non-negative integers')
return {'version': 1, 'margin_left': margin, 'narrow_left_pad_limit': 300}
def legacy_layout():
# Old annotation scans were produced with these literal constants.
return {'version': 1, 'margin_left': 300, 'narrow_left_pad_limit': 300}