├── CTkPDFViewer ├── __init__.py ├── ctk_pdf_viewer.py └── ctk_pdf_viewer_navigate.py ├── LICENSE └── README.md /CTkPDFViewer/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | CustomTkinter PDF Viewer widget 3 | Author: Akash Bora 4 | License: MIT 5 | This is a pdf view widger for customtkinter 6 | Homepage: https://github.com/Akascape/CTkPDFViewer 7 | """ 8 | 9 | __version__ = '0.2' 10 | 11 | from .ctk_pdf_viewer import CTkPDFViewer 12 | from .ctk_pdf_viewer_navigate import CTkPDFViewerNavigate 13 | -------------------------------------------------------------------------------- /CTkPDFViewer/ctk_pdf_viewer.py: -------------------------------------------------------------------------------- 1 | """ 2 | CTkPDFViewer is a pdf viewer widget for customtkinter. 3 | Author: Akash Bora 4 | License: MIT 5 | """ 6 | 7 | import customtkinter 8 | from PIL import Image 9 | import fitz 10 | from threading import Thread 11 | import math 12 | import io 13 | import os 14 | 15 | class CTkPDFViewer(customtkinter.CTkScrollableFrame): 16 | 17 | def __init__(self, 18 | master: any, 19 | file: str, 20 | page_width: int = 600, 21 | page_height: int = 700, 22 | page_separation_height: int = 2, 23 | **kwargs): 24 | 25 | super().__init__(master, **kwargs) 26 | 27 | self.page_width = page_width 28 | self.page_height = page_height 29 | self.separation = page_separation_height 30 | self.pdf_images = [] 31 | self.labels = [] 32 | self.file = file 33 | 34 | self.percentage_view = 0 35 | self.percentage_load = customtkinter.StringVar() 36 | 37 | self.loading_message = customtkinter.CTkLabel(self, textvariable=self.percentage_load, justify="center") 38 | self.loading_message.pack(pady=10) 39 | 40 | self.loading_bar = customtkinter.CTkProgressBar(self, width=100) 41 | self.loading_bar.set(0) 42 | self.loading_bar.pack(side="top", fill="x", padx=10) 43 | 44 | self.after(250, self.start_process) 45 | 46 | def start_process(self): 47 | Thread(target=self.add_pages).start() 48 | 49 | def add_pages(self): 50 | """ add images and labels """ 51 | self.percentage_bar = 0 52 | open_pdf = fitz.open(self.file) 53 | 54 | for page in open_pdf: 55 | page_data = page.get_pixmap() 56 | pix = fitz.Pixmap(page_data, 0) if page_data.alpha else page_data 57 | img = Image.open(io.BytesIO(pix.tobytes('ppm'))) 58 | label_img = customtkinter.CTkImage(img, size=(self.page_width, self.page_height)) 59 | self.pdf_images.append(label_img) 60 | 61 | self.percentage_bar = self.percentage_bar + 1 62 | percentage_view = (float(self.percentage_bar) / float(len(open_pdf)) * float(100)) 63 | self.loading_bar.set(percentage_view) 64 | self.percentage_load.set(f"Loading {os.path.basename(self.file)} \n{int(math.floor(percentage_view))}%") 65 | 66 | self.loading_bar.pack_forget() 67 | self.loading_message.pack_forget() 68 | open_pdf.close() 69 | 70 | for i in self.pdf_images: 71 | label = customtkinter.CTkLabel(self, image=i, text="") 72 | label.pack(pady=(0, self.separation)) 73 | self.labels.append(label) 74 | 75 | def configure(self, **kwargs): 76 | """ configurable options """ 77 | 78 | if "file" in kwargs: 79 | self.file = kwargs.pop("file") 80 | self.pdf_images = [] 81 | for i in self.labels: 82 | i.destroy() 83 | self.labels = [] 84 | self.after(250, self.start_process) 85 | 86 | if "page_width" in kwargs: 87 | self.page_width = kwargs.pop("page_width") 88 | for i in self.pdf_images: 89 | i.configure(size=(self.page_width, self.page_height)) 90 | 91 | if "page_height" in kwargs: 92 | self.page_height = kwargs.pop("page_height") 93 | for i in self.pdf_images: 94 | i.configure(size=(self.page_width, self.page_height)) 95 | 96 | if "page_separation_height" in kwargs: 97 | self.separation = kwargs.pop("page_separation_height") 98 | for i in self.labels: 99 | i.pack_forget() 100 | i.pack(pady=(0,self.separation)) 101 | 102 | super().configure(**kwargs) 103 | -------------------------------------------------------------------------------- /CTkPDFViewer/ctk_pdf_viewer_navigate.py: -------------------------------------------------------------------------------- 1 | """ 2 | CTkPDFViewer is a pdf viewer widget for customtkinter. 3 | Author: Akash Bora 4 | License: MIT 5 | """ 6 | 7 | import customtkinter 8 | from PIL import Image 9 | import fitz 10 | import io 11 | 12 | 13 | class CTkPDFViewerNavigate(customtkinter.CTkFrame): 14 | 15 | def __init__(self, 16 | master: any, 17 | file: str, 18 | page_width: int = 600, 19 | page_height: int = 700, 20 | **kwargs): 21 | 22 | super().__init__(master, **kwargs) 23 | 24 | self.page_width = page_width 25 | self.page_height = page_height 26 | self.file = file 27 | 28 | self.current_page = 0 29 | self.total_pages = 0 30 | self.open_pdf = None 31 | self.page_images = [] 32 | 33 | # Create widgets 34 | self.image_label = customtkinter.CTkLabel(self, text="") 35 | self.image_label.pack(pady=10) 36 | 37 | self.navigation_frame = customtkinter.CTkFrame(self, fg_color="transparent") 38 | self.navigation_frame.pack(pady=10) 39 | 40 | self.left_button = customtkinter.CTkButton(self.navigation_frame, text="◀", command=self.prev_page) 41 | self.left_button.pack(side="left", padx=10) 42 | 43 | self.page_entry = customtkinter.CTkEntry(self.navigation_frame, width=50, validate="key", justify="right", 44 | border_width=0, fg_color="transparent") 45 | self.page_entry.pack(side="left", padx=1) 46 | self.page_entry.configure(validatecommand=(self.register(self.validate_number), "%P")) 47 | self.page_entry.bind("", self.goto_page) 48 | self.page_entry.bind("", self.goto_page_key_release) 49 | 50 | self.total_pages_label = customtkinter.CTkLabel(self.navigation_frame, text="") 51 | self.total_pages_label.pack(side="left", padx=(0, 10)) 52 | 53 | self.right_button = customtkinter.CTkButton(self.navigation_frame, text="▶", command=self.next_page) 54 | self.right_button.pack(side="left", padx=10) 55 | 56 | self.load_pdf() 57 | 58 | def load_pdf(self): 59 | """ Load the PDF file and extract page images """ 60 | self.open_pdf = fitz.open(self.file) 61 | self.total_pages = len(self.open_pdf) 62 | 63 | self.page_images = [] 64 | for page in self.open_pdf: 65 | pixmap = page.get_pixmap() 66 | pix = fitz.Pixmap(pixmap, 0) if pixmap.alpha else pixmap 67 | img = Image.open(io.BytesIO(pix.tobytes("ppm"))) 68 | resized_img = img.resize((self.page_width, self.page_height)) 69 | ctk_image = customtkinter.CTkImage(resized_img, size=(self.page_width, self.page_height)) 70 | self.page_images.append(ctk_image) 71 | 72 | self.total_pages_label.configure(text=f"/{self.total_pages}") 73 | self.show_page(0) 74 | 75 | def validate_number(self, value: str) -> bool: 76 | """ Validate that the input contains only numbers """ 77 | return value.isdigit() or value == "" 78 | 79 | def show_page(self, page_number: int): 80 | """ Display the specified page number """ 81 | if 0 <= page_number < self.total_pages: 82 | self.current_page = page_number 83 | self.image_label.configure(image=self.page_images[page_number]) 84 | 85 | self.page_entry.delete(0, "end") 86 | self.page_entry.insert(0, str(self.current_page + 1)) 87 | 88 | def prev_page(self): 89 | """ Navigate to the previous page """ 90 | if self.current_page > 0: 91 | self.show_page(self.current_page - 1) 92 | 93 | def next_page(self): 94 | """ Navigate to the next page """ 95 | if self.current_page < self.total_pages - 1: 96 | self.show_page(self.current_page + 1) 97 | 98 | def goto_page(self, event=None): 99 | """ Navigate to a specific page entered by the user """ 100 | try: 101 | page_number = int(self.page_entry.get()) - 1 102 | if 0 <= page_number < self.total_pages: 103 | self.show_page(page_number) 104 | except ValueError: 105 | pass # Ignore invalid input 106 | 107 | def goto_page_key_release(self, event=None): 108 | """ Navigate to the page during key release """ 109 | try: 110 | page_number = int(self.page_entry.get()) - 1 111 | if 0 <= page_number < self.total_pages: 112 | self.show_page(page_number) 113 | except ValueError: 114 | pass # Ignore invalid input 115 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Akash Bora 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CTkPDFViewer 2 | A simple yet powerfull pdf viewer widget for customtkinter! This can be helpful for adding **documentation (in the form of PDF)** inside your application. 3 | 4 | Users can quicky view the offline copy of your documentation. 5 | 6 | ## Features 7 | - load pdf with ease 8 | - configure page width and height 9 | - scrollable pages 10 | - configurable options 11 | 12 | ## Installation 13 | ### [GitHub repo size](https://github.com/Akascape/CTkPDFViewer/archive/refs/heads/main.zip) 14 | 15 | **Requirements** 16 | - [PyMuPDF](https://pypi.org/project/PyMuPDF/) `pip install PyMuPDF` 17 | 18 | **Download the source code, paste the `CTkPDFViewer` folder in the directory where your program is present.** 19 | 20 | ## Usage 21 | ### CTkPDFViewer 22 | ```python 23 | import customtkinter 24 | from CTkPDFViewer import * 25 | 26 | root = customtkinter.CTk() 27 | root.geometry("700x600") 28 | pdf_frame = CTkPDFViewer(root, file="my_file.pdf") 29 | pdf_frame.pack(fill="both", expand=True, padx=10, pady=10) 30 | root.mainloop() 31 | ``` 32 | 33 | ![example](https://github.com/Akascape/CTkPDFViewer/assets/89206401/1324243e-da47-4bd8-af46-cded53cb7b51) 34 | 35 | ### CTkPDFViewerNavigate 36 | ```python 37 | import customtkinter 38 | from CTkPDFViewer import * 39 | 40 | root = customtkinter.CTk() 41 | root.geometry("700x600") 42 | pdf_frame = CTkPDFViewerNavigate(root, file="my_file.pdf") 43 | pdf_frame.pack(fill="both", expand=True, padx=10, pady=10) 44 | root.mainloop() 45 | ``` 46 | 47 | ## Arguments 48 | | Parameter | Description | 49 | |-----------| ------------| 50 | | **master** | parent widget | 51 | | **file** | the PDF file you want to view | 52 | | page_width | **optional**, change the width of the pages | 53 | | page_height | **optional**, change the height of the pages | 54 | | page_separation_height | change the _pady_ between the pages | 55 | | **other frame parameters | _All other ctkscrollable frame parameters can be passed_ | 56 | 57 | You can also change all these parameters using the `.configure()` method. Eg: `pdf_frame.configure(file="new_file.pdf", ...)` 58 | 59 | That's all, hope it will help! 60 | --------------------------------------------------------------------------------