import fitz # PyMuPDF import sys def expand_pdf_for_notes(input_pdf, output_pdf, bg_color=(1, 1, 1), mode='notes_only', stitch_direction='horizontal', add_space=True, pattern=None, pattern_color=(0.8, 0.8, 0.8)): """ Expand or rearrange a PDF for note-taking. Args: input_pdf (str): Path to input PDF file. output_pdf (str): Path to output PDF file. bg_color (tuple): RGB color for the notes area background. mode (str): The processing mode: 'notes_only', 'split', or 'stitch'. stitch_direction (str): For 'stitch' mode, how to rearrange columns ('horizontal' or 'vertical'). add_space (bool): If True, add space for notes. pattern (str): Name of the pattern to draw ('grid', 'dots'). pattern_color (tuple): RGB color for the pattern. """ doc = fitz.open(input_pdf) new_doc = fitz.open() for page_num in range(len(doc)): page = doc[page_num] orig_rect = page.rect orig_width = orig_rect.width orig_height = orig_rect.height left_half_clip = fitz.Rect(0, 0, orig_width / 2, orig_height) right_half_clip = fitz.Rect(orig_width / 2, 0, orig_width, orig_height) if mode == 'split': # Create a new page for the left half new_page_width = orig_width / 2 if not add_space else orig_width left_page = new_doc.new_page(width=new_page_width, height=orig_height) left_page.show_pdf_page(fitz.Rect(0, 0, orig_width / 2, orig_height), doc, page_num, clip=left_half_clip) if add_space: notes_rect = fitz.Rect(orig_width / 2, 0, orig_width, orig_height) left_page.draw_rect(notes_rect, color=None, fill=bg_color) if pattern: _draw_pattern(new_doc, left_page, notes_rect, pattern, pattern_color) # Create a new page for the right half right_page = new_doc.new_page(width=new_page_width, height=orig_height) right_page.show_pdf_page(fitz.Rect(0, 0, orig_width / 2, orig_height), doc, page_num, clip=right_half_clip) if add_space: notes_rect = fitz.Rect(orig_width / 2, 0, orig_width, orig_height) right_page.draw_rect(notes_rect, color=None, fill=bg_color) if pattern: _draw_pattern(new_doc, right_page, notes_rect, pattern, pattern_color) elif mode == 'stitch': if stitch_direction == 'horizontal': new_width = orig_width if add_space: new_width *= 2 new_page = new_doc.new_page(width=new_width, height=orig_height) new_page.show_pdf_page(fitz.Rect(0, 0, orig_width / 2, orig_height), doc, page_num, clip=left_half_clip) new_page.show_pdf_page(fitz.Rect(orig_width / 2, 0, orig_width, orig_height), doc, page_num, clip=right_half_clip) if add_space: notes_rect = fitz.Rect(orig_width, 0, new_width, orig_height) new_page.draw_rect(notes_rect, color=None, fill=bg_color) if pattern: _draw_pattern(new_doc, new_page, notes_rect, pattern, pattern_color) else: # vertical new_width = orig_width / 2 if add_space: new_width = orig_width new_height = orig_height * 2 new_page = new_doc.new_page(width=new_width, height=new_height) new_page.show_pdf_page(fitz.Rect(0, 0, orig_width / 2, orig_height), doc, page_num, clip=left_half_clip) new_page.show_pdf_page(fitz.Rect(0, orig_height, orig_width / 2, new_height), doc, page_num, clip=right_half_clip) if add_space: notes_rect = fitz.Rect(orig_width / 2, 0, new_width, new_height) new_page.draw_rect(notes_rect, color=None, fill=bg_color) if pattern: _draw_pattern(new_doc, new_page, notes_rect, pattern, pattern_color) elif mode == 'notes_only': if add_space: new_page = new_doc.new_page(width=orig_width * 2, height=orig_height) right_rect = fitz.Rect(orig_width, 0, orig_width * 2, orig_height) new_page.draw_rect(right_rect, color=None, fill=bg_color) if pattern: _draw_pattern(new_doc, new_page, right_rect, pattern, pattern_color) new_page.show_pdf_page(fitz.Rect(0, 0, orig_width, orig_height), doc, page_num) else: new_doc.insert_pdf(doc, from_page=page_num, to_page=page_num) else: # Default to copying the page if mode is unknown new_doc.insert_pdf(doc, from_page=page_num, to_page=page_num) new_doc.save(output_pdf) total_pages = len(new_doc) new_doc.close() doc.close() print(f"✓ Successfully created: {output_pdf}") print(f" Pages processed: {total_pages}") def _draw_pattern(doc, page, rect, pattern, color): if pattern == 'grid': _draw_grid(page, rect, color=color) elif pattern == 'dots': _draw_dots(doc, page, rect, color=color) def _draw_grid(page, rect, spacing=20, color=(0.8, 0.8, 0.8)): # Draw vertical lines for x in range(int(rect.x0), int(rect.x1), spacing): page.draw_line(fitz.Point(x, rect.y0), fitz.Point(x, rect.y1), color=color, width=0.5) # Draw horizontal lines for y in range(int(rect.y0), int(rect.y1), spacing): page.draw_line(fitz.Point(rect.x0, y), fitz.Point(rect.x1, y), color=color, width=0.5) def _draw_dots(doc, page, rect, spacing=20, radius=1, color=(0.8, 0.8, 0.8)): """Creates a tileable dot pattern using a Form XObject for efficiency.""" # Create a small rectangle for one pattern unit stamp_rect = fitz.Rect(0, 0, spacing, spacing) # Create a new PDF for the stamp stamp_doc = fitz.open() stamp_page = stamp_doc.new_page(width=spacing, height=spacing) # Draw a single dot in the corner of the stamp page stamp_page.draw_circle(fitz.Point(radius, radius), radius, color=color, fill=color) # Convert the stamp page to a stamp (Form XObject) and get its cross-reference number stamp_xref = doc.get_xref(stamp_doc.convert_to_pdf()) stamp_doc.close() # Tile the stamp across the target rectangle for x in range(int(rect.x0), int(rect.x1), spacing): for y in range(int(rect.y0), int(rect.y1), spacing): page.show_pdf_page(fitz.Rect(x, y, x + spacing, y + spacing), stamp_xref) def main(): """Main function with command-line interface""" import argparse parser = argparse.ArgumentParser(description="Expand or rearrange a PDF for note-taking.") parser.add_argument("input_pdf", help="Path to input PDF file.") parser.add_argument("output_pdf", nargs='?', help="Path to output PDF file.") parser.add_argument("--mode", choices=['notes_only', 'split', 'stitch'], default='notes_only', help="Processing mode.") parser.add_argument("--stitch-direction", choices=['horizontal', 'vertical'], default='horizontal', help="Direction for 'stitch' mode.") parser.add_argument("--no-space", action='store_true', help="Don't add extra space for notes.") parser.add_argument("--bg", default='white', help="Background color (white, lightgray, cream)." ) args = parser.parse_args() output_pdf = args.output_pdf if not output_pdf: suffix = f'_{args.mode}' if args.mode == 'stitch': suffix += f'_{args.stitch_direction[:4]}' if not args.no_space: suffix += '_notes' suffix += '.pdf' output_pdf = args.input_pdf.replace('.pdf', suffix) bg_colors = { 'white': (1, 1, 1), 'lightgray': (0.95, 0.95, 0.95), 'cream': (1, 0.99, 0.94), } bg_color = bg_colors.get(args.bg, (1, 1, 1)) try: expand_pdf_for_notes( args.input_pdf, output_pdf, bg_color=bg_color, mode=args.mode, stitch_direction=args.stitch_direction, add_space=not args.no_space ) except Exception as e: print(f"Error: {e}") sys.exit(1) if __name__ == "__main__": main()