概要
このブログでは、Pythonといくつかのライブラリを使用して、機能豊富なPDFビュアーを作成する方法を詳細に解説します。このビュアーは、PDFファイルをドラッグ&ドロップで読み込み、ページをナビゲートし、選択範囲を画像としてコピーまたは保存する機能を備えています。
使用例
このPDFビュアーは、教育資料の作成、業務報告書の編集、研究論文のレビューなど、さまざまな場面で活用できます。たとえば、PDFファイルの特定の部分を画像としてクリップボードにコピーしたり、JPGファイルとして保存したりすることができます。
必要なPythonライブラリとインストール方法
このプロジェクトには以下のライブラリが必要です:
tkinter
: Pythonの標準GUIツールキットPyMuPDF
(別名fitz
): PDFファイルを操作するためのライブラリPillow
: 画像処理ライブラリtkinterdnd2
: ドラッグアンドドロップ機能を実装するためのライブラリpywin32
: Windowsのクリップボード操作をサポートするライブラリ
これらのライブラリのインストール方法は以下の通りです:
pip install pymupdf Pillow tkinterdnd2 pywin32
使用手順
- 上記のライブラリをインストールします。
- 提供されたコードをメモ帳などに丸々コピーしてPythonファイル(例:pdf_viewer.py)に保存します。
- 保存したPythonファイルを実行します。
- PDFファイルをウィンドウにドラッグ&ドロップするか、「Open File」ボタンをクリックしてファイルを選択し、PDFを表示します。
- 画面下部に表示されるナビゲーションボタンを使用して、ページ間を移動します。
- 範囲選択ツールを使用してPDFの任意の部分を選択し、「Copy to Clipboard」ボタンをクリックして選択範囲をクリップボードにコピーします。
- または、「Save as Image」ボタンをクリックして選択範囲をJPG画像として保存します。
注意点
- PDFファイルのパスに特殊文字が含まれている場合、エラーが発生する可能性があります。これを避けるために、ファイルパスを適切に処理する必要があります。
- 一部のPDFファイルでは、保護されているために読み込みができないことがあります。これらのファイルは事前に保護解除が必要です。
プログラム
下記のコードをメモ帳などに丸々コピーしてPythonファイル(例:pdf_viewer.py)にしてください。
import tkinter as tk
from tkinter import filedialog
from tkinterdnd2 import DND_FILES, TkinterDnD
import fitz # PyMuPDF
from PIL import Image, ImageTk
import io
import win32clipboard
class PDFViewer:
def __init__(self, root):
self.root = root
self.canvas = tk.Canvas(root, cursor="cross")
self.canvas.pack(fill=tk.BOTH, expand=1)
self.doc = None
self.page = None
self.img = None
self.zoom = 1 # Zoom factor for 72 ppi (default PDF is 72 ppi)
self.canvas.bind("<ButtonPress-1>", self.start_select)
self.canvas.bind("<B1-Motion>", self.on_drag)
self.canvas.bind("<ButtonRelease-1>", self.end_select)
self.selection_rect = None
self.start_x = None
self.start_y = None
self.end_x = None
self.end_y = None
self.label = tk.Label(root)
self.label.pack(fill=tk.BOTH, expand=True)
btn_frame = tk.Frame(root)
btn_frame.pack(fill=tk.X)
self.prev_btn = tk.Button(btn_frame, text="<< Previous", command=self.prev_page)
self.prev_btn.pack(side=tk.LEFT)
self.next_btn = tk.Button(btn_frame, text="Next >>", command=self.next_page)
self.next_btn.pack(side=tk.RIGHT)
open_btn = tk.Button(btn_frame, text="Open File", command=self.open_file_dialog)
open_btn.pack(side=tk.LEFT)
copy_btn = tk.Button(btn_frame, text="Copy to Clipboard", command=self.copy_to_clipboard)
copy_btn.pack(side=tk.RIGHT)
save_img_btn = tk.Button(btn_frame, text="Save as Image", command=self.save_as_image)
save_img_btn.pack(side=tk.RIGHT)
# ファイルパス表示用ラベル
self.file_path_label = tk.Label(root, text="No file selected")
self.file_path_label.pack(side=tk.TOP)
# 座標表示用ラベル
self.coord_label = tk.Label(root, text="X: 0, Y: 0")
self.coord_label.pack(side=tk.BOTTOM)
self.label.bind('<Motion>', self.mouse_move) # マウス移動イベントをバインド
root.drop_target_register(DND_FILES)
root.dnd_bind('<<Drop>>', self.drop)
def open_file_dialog(self):
file_path = filedialog.askopenfilename(filetypes=[("PDF files", "*.pdf")])
if file_path:
self.file_path_label.config(text=f"Selected file: {file_path}")
self.open_pdf(file_path)
def open_pdf(self, path):
self.doc = fitz.open(path)
self.total_pages = len(self.doc)
self.current_page = 0
self.show_page(self.current_page)
def show_page(self, page_num):
self.page = self.doc.load_page(page_num)
pix = self.page.get_pixmap(matrix=fitz.Matrix(self.zoom, self.zoom))
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
self.img = ImageTk.PhotoImage(image=img)
self.canvas.create_image(0, 0, image=self.img, anchor="nw")
self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL))
# Adjust window size
self.root.geometry(f"{pix.width}x{pix.height + 100}")
def start_select(self, event):
self.canvas.delete(self.selection_rect)
self.start_x = self.canvas.canvasx(event.x)
self.start_y = self.canvas.canvasy(event.y)
self.selection_rect = self.canvas.create_rectangle(self.start_x, self.start_y, self.start_x, self.start_y, outline="red")
def on_drag(self, event):
curX = self.canvas.canvasx(event.x)
curY = self.canvas.canvasy(event.y)
self.canvas.coords(self.selection_rect, self.start_x, self.start_y, curX, curY)
def end_select(self, event):
self.end_x = self.canvas.canvasx(event.x)
self.end_y = self.canvas.canvasy(event.y)
def get_selected_image(self):
if not self.selection_rect or not self.doc:
return None
x1, y1, x2, y2 = map(int, self.canvas.coords(self.selection_rect))
# Ensure the selection is within the image bounds
x1, y1 = max(0, x1), max(0, y1)
x2, y2 = min(self.page.rect.width * self.zoom, x2), min(self.page.rect.height * self.zoom, y2)
clip_img = self.page.get_pixmap(matrix=fitz.Matrix(2, 2), clip=fitz.Rect(x1 / self.zoom, y1 / self.zoom, x2 / self.zoom, y2 / self.zoom))
img = Image.frombytes("RGB", [clip_img.width, clip_img.height], clip_img.samples)
return img
def copy_to_clipboard(self):
img = self.get_selected_image()
if img is None:
return
output = io.BytesIO()
img.save(output, format='BMP')
data = output.getvalue()[14:] # Remove BMP header
output.close()
# Use win32clipboard to put the image in the clipboard
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
win32clipboard.CloseClipboard()
def save_as_image(self):
img = self.get_selected_image()
if img is None:
return
file_path = filedialog.asksaveasfilename(defaultextension=".jpg", filetypes=[("JPEG files", "*.jpg")])
if file_path:
img.save(file_path, format='JPEG')
def prev_page(self):
if self.current_page > 0:
self.current_page -= 1
self.show_page(self.current_page)
def next_page(self):
if self.current_page < self.total_pages - 1:
self.current_page += 1
self.show_page(self.current_page)
def mouse_move(self, event):
if self.page:
# ラベルに座標を表示
self.coord_label.config(text=f"X: {event.x}, Y: {self.page.rect.height - event.y}")
def drop(self, event):
file_path = event.data
file_path = file_path.strip('\'"{}')
self.file_path_label.config(text=f"Selected file: {file_path}")
self.open_pdf(file_path)
def open_file():
file_path = filedialog.askopenfilename(filetypes=[("PDF files", "*.pdf")])
if file_path:
viewer.load_pdf(file_path)
root = TkinterDnD.Tk()
root.title('PDF Viewer')
root.geometry('800x600')
viewer = PDFViewer(root)
menu = tk.Menu(root)
root.config(menu=menu)
file_menu = tk.Menu(menu)
menu.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Open", command=open_file)
root.mainloop()
あるいは、下のテキストファイルをダウンロードし、「.txt」を「.py」に変えることでそのまま使えます。
pdf-viewerくんv3
まとめ
このブログでは、Pythonを使用して機能豊富なPDFビュアーを開発する方法を紹介しました。GUI操作により、ユーザーフレンドリーで直感的なPDF管理ツールを簡単に作成できます。このツールは、教育、ビジネス、研究など多岐にわたる分野での文書管理とレビューに役立つでしょう