Spaces:
Sleeping
Sleeping
| 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("<Configure>", self.render_page) | |
| self.canvas.bind("<Button-1>", self.on_click) | |
| self.root.bind("<Right>", self.next_slide) | |
| self.root.bind("<Left>", self.prev_slide) | |
| self.root.bind("<Escape>", 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) | |