Added scripts
This commit is contained in:
Executable
+115
@@ -0,0 +1,115 @@
|
||||
import argparse
|
||||
import fitz # PyMuPDF
|
||||
|
||||
|
||||
def side_by_side_preserve_links(input_pdf: str, output_pdf: str, start_page: int = 1):
|
||||
src = fitz.open(input_pdf)
|
||||
out = fitz.open()
|
||||
|
||||
# Map each source page -> (output_page_index, x_offset, y_offset)
|
||||
# In this simple layout:
|
||||
# - even source pages go on the LEFT at x_offset=0
|
||||
# - odd source pages go on the RIGHT at x_offset=width_of_left_page (of that pair)
|
||||
placement = {}
|
||||
|
||||
# 1) Create output pages and place page content, while recording placement
|
||||
out_page_index = 0
|
||||
i = start_page - 1
|
||||
while i < src.page_count:
|
||||
left = src.load_page(i)
|
||||
left_rect = left.rect
|
||||
|
||||
right = src.load_page(i + 1) if (i + 1) < src.page_count else None
|
||||
right_rect = right.rect if right else fitz.Rect(0, 0, 0, 0)
|
||||
|
||||
new_width = left_rect.width + (right_rect.width if right else 0) - 1
|
||||
new_height = max(left_rect.height, right_rect.height if right else 0)
|
||||
|
||||
new_page = out.new_page(width=new_width, height=new_height)
|
||||
|
||||
# Show left page
|
||||
left_target = fitz.Rect(0, 0, left_rect.width, left_rect.height)
|
||||
new_page.show_pdf_page(left_target, src, i)
|
||||
placement[i] = (out_page_index, 0.0, 0.0)
|
||||
|
||||
# Show right page
|
||||
if right:
|
||||
right_x = left_rect.width - 1
|
||||
right_target = fitz.Rect(right_x, 0, right_x + right_rect.width, right_rect.height)
|
||||
new_page.show_pdf_page(right_target, src, i + 1)
|
||||
placement[i + 1] = (out_page_index, right_x, 0.0)
|
||||
|
||||
out_page_index += 1
|
||||
i += 2
|
||||
|
||||
# Helper: map a source destination (page + point) into the 2-up output coordinate space
|
||||
def map_dest(src_page_num: int, pt: fitz.Point | None):
|
||||
# Destination output page index
|
||||
dest_out_page, dest_xoff, dest_yoff = placement[src_page_num]
|
||||
if pt is None:
|
||||
return dest_out_page, None
|
||||
return dest_out_page, fitz.Point(pt.x + dest_xoff, pt.y + dest_yoff)
|
||||
|
||||
# Helper: map a source link rectangle ("from") into the output coordinate space
|
||||
def map_from_rect(src_page_num: int, r: fitz.Rect):
|
||||
out_page_num, xoff, yoff = placement[src_page_num]
|
||||
return out_page_num, fitz.Rect(r.x0 + xoff, r.y0 + yoff, r.x1 + xoff, r.y1 + yoff)
|
||||
|
||||
# 2) Copy links (annotations) from each source page to the correct output page
|
||||
for spn in range(src.page_count):
|
||||
links = src.load_page(spn).get_links()
|
||||
if not links:
|
||||
continue
|
||||
|
||||
for lnk in links:
|
||||
# Map the clickable rectangle
|
||||
out_pn, new_from = map_from_rect(spn, lnk["from"])
|
||||
out_page = out.load_page(out_pn)
|
||||
|
||||
kind = lnk.get("kind")
|
||||
|
||||
# Common fields
|
||||
new_link = {
|
||||
"kind": kind,
|
||||
"from": new_from,
|
||||
}
|
||||
|
||||
# External URL links
|
||||
if kind == fitz.LINK_URI:
|
||||
new_link["uri"] = lnk.get("uri", "")
|
||||
|
||||
# Internal "go to page" links
|
||||
elif kind == fitz.LINK_GOTO:
|
||||
dest_src_page = lnk.get("page")
|
||||
if dest_src_page is None or dest_src_page < 0 or dest_src_page >= src.page_count:
|
||||
continue # skip broken destinations
|
||||
|
||||
dest_out_page, new_to = map_dest(dest_src_page, lnk.get("to"))
|
||||
new_link["page"] = dest_out_page
|
||||
if new_to is not None:
|
||||
new_link["to"] = new_to
|
||||
if "zoom" in lnk and lnk["zoom"] is not None:
|
||||
new_link["zoom"] = lnk["zoom"]
|
||||
|
||||
# Named destinations / file launches etc.
|
||||
# You can add more cases if you use them, but most PDFs are URI + GOTO.
|
||||
else:
|
||||
# Try a best-effort copy for other kinds (may or may not be supported)
|
||||
for k in ("page", "to", "zoom", "file", "uri", "name"):
|
||||
if k in lnk and lnk[k] is not None:
|
||||
new_link[k] = lnk[k]
|
||||
|
||||
out_page.insert_link(new_link)
|
||||
|
||||
out.save(output_pdf)
|
||||
out.close()
|
||||
src.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Places pages side by side")
|
||||
parser.add_argument("input_pdf", help="Path to the source PDF file.")
|
||||
parser.add_argument("output_pdf", help="Path to the destination PDF file.")
|
||||
parser.add_argument("start_page", help="Allows to skip n number of pages", type=int)
|
||||
args = parser.parse_args()
|
||||
side_by_side_preserve_links(args.input_pdf, args.output_pdf, args.start_page)
|
||||
|
||||
Reference in New Issue
Block a user