diff --git a/requirements.txt b/requirements.txt new file mode 100755 index 0000000..41cb3bb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +fitz==0.0.1.dev2 +PyMuPDF==1.26.5 diff --git a/scripts/extract_pages.py b/scripts/extract_pages.py new file mode 100755 index 0000000..2a5af55 --- /dev/null +++ b/scripts/extract_pages.py @@ -0,0 +1,19 @@ +import argparse +import fitz + +def extract_pages(input_pdf_path, output_pdf_path, start, end): + doc = fitz.open(input_pdf_path) + doc2 = fitz.open() + doc2.insert_pdf(doc, from_page=start-1, to_page=end-1) # pages 0 to 9 (10 pages total) + doc2.save(output_pdf_path, garbage=4, deflate=True, deflate_images=True, deflate_fonts=True) + doc2.close() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Extracts pages from a PDF") + 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", help="Page number of first page to extract", type=int) + parser.add_argument("end", help="Page number of last page to extract", type=int) + args = parser.parse_args() + extract_pages(args.input_pdf, args.output_pdf, args.start, args.end) diff --git a/scripts/join_files.py b/scripts/join_files.py new file mode 100755 index 0000000..b7f75e2 --- /dev/null +++ b/scripts/join_files.py @@ -0,0 +1,17 @@ +import argparse +import fitz + +def join_files(first_pdf, second_pdf, output_pdf): + doc = fitz.open() + doc.insert_file(first_pdf) + doc.insert_file(second_pdf) + doc.save(output_pdf) + doc.close() + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Join two PDFs into a third PDF") + parser.add_argument("first_pdf", help="Path to the first PDF file.") + parser.add_argument("second_pdf", help="Path to the second PDF file.") + parser.add_argument("output_pdf", help="Path to destination PDF file.") + args = parser.parse_args() + join_files(args.first_pdf, args.second_pdf, args.output_pdf) diff --git a/scripts/side_by_side.py b/scripts/side_by_side.py new file mode 100755 index 0000000..97cc95b --- /dev/null +++ b/scripts/side_by_side.py @@ -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) + diff --git a/scripts/watermark_remover.py b/scripts/watermark_remover.py new file mode 100755 index 0000000..7d926bd --- /dev/null +++ b/scripts/watermark_remover.py @@ -0,0 +1,24 @@ +import argparse +import fitz # PyMuPDF + +def remove_text_from_pdf(input_pdf_path, output_pdf_path, watermark): + doc = fitz.open(input_pdf_path) + for page in doc: + text_instances = page.search_for(watermark) + for text in text_instances: + print(f'Rect found: {text}') + page.add_redact_annot(text) + + page.apply_redactions(fitz.PDF_REDACT_IMAGE_NONE) + + doc.save(output_pdf_path, garbage=4, deflate=True, deflate_images=True, deflate_fonts=True) + doc.close() + print(f"Processed PDF saved as '{output_pdf_path}'.") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Remove specified text and links from a PDF.") + 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("watermark", help="Text to remove from the output PDF file.") + args = parser.parse_args() + remove_text_from_pdf(args.input_pdf, args.output_pdf, args.watermark)