#!/usr/bin/env python3 import sys, os, subprocess, re, shutil, json, tempfile, struct from pypdf import PdfReader, PdfWriter INKSCAPE_TIMEOUT = 30 def fallback_page(reader, i, path): w = PdfWriter() w.add_page(reader.pages[i]) with open(path, 'wb') as fh: w.write(fh) tmpdir = sys.argv[1] bg_pdf = sys.argv[2] output = sys.argv[3] content_file = next( (os.path.join(tmpdir, f) for f in os.listdir(tmpdir) if f.endswith('.content')), None ) if not content_file: sys.exit(1) with open(content_file) as f: data = json.load(f) doc_uuid = os.path.basename(content_file)[:-8] rm_dir = os.path.join(tmpdir, doc_uuid) page_ids = [p['id'] for p in data.get('cPages', {}).get('pages', [])] page_rm = {} for i, pid in enumerate(page_ids): rm = os.path.join(rm_dir, pid + '.rm') if os.path.exists(rm): page_rm[i] = rm if not page_rm: sys.exit(1) reader = PdfReader(bg_pdf) n_pages = len(reader.pages) work = tempfile.mkdtemp() parts = [] for i in range(n_pages): part_out = os.path.join(work, 'p' + str(i) + '.pdf') if i not in page_rm: fallback_page(reader, i, part_out) parts.append(part_out) continue try: # PDF-Seitengröße in Punkten pdf_w_pt = float(reader.pages[i].mediabox.width) pdf_h_pt = float(reader.pages[i].mediabox.height) dpi = 150 png_w = int(round(pdf_w_pt * dpi / 72)) png_h = int(round(pdf_h_pt * dpi / 72)) # Bg-Seite als PNG rendern bg_base = os.path.join(work, 'bg' + str(i)) subprocess.run( ['pdftoppm', '-r', str(dpi), '-png', '-f', str(i+1), '-l', str(i+1), '-singlefile', bg_pdf, bg_base], capture_output=True, timeout=20 ) bg_png = bg_base + '.png' if not os.path.exists(bg_png): raise FileNotFoundError('PNG fehlt') # .rm -> SVG svg_out = os.path.join(work, 'a' + str(i) + '.svg') r = subprocess.run(['rmc', '-t', 'svg', '-o', svg_out, page_rm[i]], capture_output=True, timeout=20) if r.returncode != 0 or not os.path.exists(svg_out): raise RuntimeError('rmc fehlgeschlagen') svg = open(svg_out).read() # Weissen Hintergrund entfernen svg = re.sub( r']*/?>', lambda m: '' if re.search(r'fill\s*[=:]\s*["\']?\s*(?:white|#fff(?:fff)?)', m.group(0), re.I) else m.group(0), svg ) # ViewBox X-Offset auslesen vb = re.search(r'viewBox=["\']([^"\']+)["\']', svg) vb_vals = [float(x) for x in vb.group(1).split()] if vb else [0, 0, pdf_w_pt, pdf_h_pt] vb_x = vb_vals[0] vb_y = vb_vals[1] # Inneren SVG-Inhalt extrahieren inner_m = re.search(r']*>(.*)', svg, re.DOTALL) inner = inner_m.group(1) if inner_m else '' # Annotation-Koordinaten sind in PDF-Punkten # Skalierung: PDF-Punkte -> PNG-Pixel pts_to_px = png_w / pdf_w_pt # reMarkable Seitenrand proportional zur PDF-Breite (empirisch: 75.5pt bei A4) rm_margin_left = 75.5 * pdf_w_pt / 595.275 # reMarkable Seitenrand proportional zur PDF-Breite (empirisch: 75.5pt bei A4) rm_margin_left = 75.5 * pdf_w_pt / 595.275 # Composite SVG comp_svg = os.path.join(work, 'c' + str(i) + '.svg') with open(comp_svg, 'w') as fh: fh.write('\n') fh.write('\n') fh.write(' \n') fh.write(' \n') fh.write(' ' + inner + '\n') fh.write(' \n') fh.write('') # Composite -> PDF r = subprocess.run( ['inkscape', comp_svg, '--export-type=pdf', '--export-filename=' + part_out], capture_output=True, timeout=INKSCAPE_TIMEOUT ) if r.returncode != 0 or not os.path.exists(part_out) or os.path.getsize(part_out) < 100: raise RuntimeError('inkscape fehlgeschlagen') parts.append(part_out) print(' Seite ' + str(i+1) + ' mit Annotation', flush=True) except Exception as e: print(' Seite ' + str(i+1) + ' Fallback: ' + str(e), flush=True) fallback_page(reader, i, part_out) parts.append(part_out) if len(parts) == 1: shutil.copy(parts[0], output) elif parts: subprocess.run(['pdfunite'] + parts + [output], check=True) shutil.rmtree(work)