import fitz # PyMuPDF import os import tkinter as tk import sys import subprocess from tkinter import filedialog from PIL import Image, ImageTk class PDFWindowViewer: def __init__(self, pdf_path): self.doc = fitz.open(pdf_path) self.total_pages = len(self.doc) self.current_page = 0 self.root = tk.Tk() self.root.title("Movable PDF Viewer") self.root.geometry("1024x768") # Start size, can resize manually self.root.configure(bg='black') self.canvas = tk.Canvas(self.root, bg='black', highlightthickness=0) self.canvas.pack(fill=tk.BOTH, expand=True) self.tk_img = None self.img_size = None self.canvas.bind("", self.render_page) self.canvas.bind("", self.on_click) self.root.bind("", self.next_slide) self.root.bind("", self.prev_slide) self.root.bind("", lambda e: self.root.destroy()) self.root.mainloop() def render_page(self, event=None): page = self.doc[self.current_page] canvas_width = self.canvas.winfo_width() canvas_height = self.canvas.winfo_height() pix = page.get_pixmap(dpi=150) img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples) scale = min(canvas_width / pix.width, canvas_height / pix.height) new_size = (int(pix.width * scale), int(pix.height * scale)) img = img.resize(new_size, Image.Resampling.LANCZOS) self.img_size = new_size self.tk_img = ImageTk.PhotoImage(img) self.canvas.delete("all") x = (canvas_width - new_size[0]) // 2 y = (canvas_height - new_size[1]) // 2 self.canvas.create_image(x, y, anchor=tk.NW, image=self.tk_img) self.root.title(f"Slide {self.current_page + 1} / {self.total_pages}") def next_slide(self, event=None): if self.current_page < self.total_pages - 1: self.current_page += 1 self.render_page() def prev_slide(self, event=None): if self.current_page > 0: self.current_page -= 1 self.render_page() def on_click(self, event=None): canvas_w = self.canvas.winfo_width() canvas_h = self.canvas.winfo_height() img_w, img_h = self.img_size x_offset = (canvas_w - img_w) // 2 y_offset = (canvas_h - img_h) // 2 x_click = event.x - x_offset y_click = event.y - y_offset if not (0 <= x_click <= img_w and 0 <= y_click <= img_h): return # clicked outside the image # Convert canvas coordinates to PDF coordinates page = self.doc[self.current_page] pdf_w, pdf_h = page.rect.width, page.rect.height scale = img_w / pdf_w x_pdf = x_click / scale y_pdf = pdf_h - (y_click / scale) click_point = fitz.Point(x_pdf, y_pdf) links = page.get_links() for link in links: rect = link.get("from") if rect and rect.contains(click_point): uri = link.get("uri") or link.get("file") if uri: print(f"▶️ Opening: {uri}") try: if sys.platform.startswith("win"): os.startfile(uri) elif sys.platform.startswith("darwin"): # macOS try: subprocess.Popen(["open", uri]) except: if uri.startswith("run:"): filepath = uri.replace("run:", "") else: filepath = uri subprocess.Popen(["open", filepath]) else: # Linux and others if uri.startswith("run:"): filepath = uri.replace("run:", "") else: filepath = uri subprocess.Popen(["xdg-open", filepath]) except Exception as e: print(f"❌ Failed to open: {uri} | {e}") return # Stop after first matching link print("⚠️ No clickable link matched the click location.") # Select PDF pdf_file = filedialog.askopenfilename(title="Select PDF", filetypes=[("PDF files", "*.pdf")]) if pdf_file: PDFWindowViewer(pdf_file)