• Y
  • List All
  • Feedback
    • This Project
    • All Projects
Profile Account settings Log out
  • Favorite
  • Project
  • All
Loading...
  • Log in
  • Sign up
hylee / salarySplitSend star
  • Project homeH
  • CodeC
  • IssueI
  • Pull requestP
  • Review R
  • MilestoneM
  • BoardB
  • Files
  • Commit
  • Branches
salarySplitSendsrcpdf_preview.py
Download as .zip file
File name
Commit message
Commit date
__pycache__
first commit
08-28
src
first commit
08-28
test
first commit
08-28
README.md
first commit
08-28
build.bat
first commit
08-28
email_config.json
first commit
08-28
employees.json
first commit
08-28
payslip_splitter_gui_v3.py
first commit
08-28
requirements.txt
first commit
08-28
급여명세서분할기_메일기능_v4.exe
.exe file add
08-28
File name
Commit message
Commit date
__pycache__
first commit
08-28
email_sender.py
first commit
08-28
employee_manager.py
first commit
08-28
pdf_preview.py
first commit
08-28
hehihoho3@gmail.com 08-28 722a0e6 first commit UNIX
Raw Open in browser Change history
import fitz # PyMuPDF from PIL import Image, ImageTk import io import os import tkinter as tk from tkinter import ttk, messagebox import platform import subprocess class PDFPreviewManager: """PDF 미리보기 관리 클래스""" def __init__(self): self.preview_cache = {} # 미리보기 이미지 캐싱 def get_pdf_info(self, pdf_path: str) -> dict: """PDF 기본 정보 추출""" try: if not pdf_path or not os.path.exists(pdf_path): return { 'error': 'PDF 파일을 찾을 수 없습니다.', 'page_count': 0, 'file_size': 0, 'is_encrypted': False, 'title': '', 'author': '', 'creation_date': '', 'file_name': os.path.basename(pdf_path) if pdf_path else '', 'file_path': pdf_path or '' } doc = fitz.open(pdf_path) metadata = doc.metadata or {} info = { 'page_count': doc.page_count, 'file_size': os.path.getsize(pdf_path), 'is_encrypted': doc.is_encrypted, 'title': metadata.get('title', '') or '', 'author': metadata.get('author', '') or '', 'creation_date': metadata.get('creationDate', '') or '', 'file_name': os.path.basename(pdf_path), 'file_path': pdf_path } doc.close() return info except Exception as e: return { 'error': str(e), 'page_count': 0, 'file_size': 0, 'is_encrypted': False, 'title': '', 'author': '', 'creation_date': '', 'file_name': os.path.basename(pdf_path) if pdf_path else '', 'file_path': pdf_path or '' } def create_preview_thumbnail(self, pdf_path: str, target_size=(250, 350)) -> Image.Image: """PDF 첫 페이지 썸네일 생성""" cache_key = f"{pdf_path}_{target_size}" # 캐시에 있으면 반환 if cache_key in self.preview_cache: return self.preview_cache[cache_key] try: doc = fitz.open(pdf_path) if doc.page_count == 0: doc.close() return None page = doc[0] # 첫 페이지 # 미리보기 크기에 맞게 해상도 계산 zoom_x = target_size[0] / page.rect.width zoom_y = target_size[1] / page.rect.height zoom = min(zoom_x, zoom_y) * 1.5 # 약간 높은 해상도 mat = fitz.Matrix(zoom, zoom) pix = page.get_pixmap(matrix=mat) img_data = pix.tobytes("png") # PIL Image로 변환 image = Image.open(io.BytesIO(img_data)) image.thumbnail(target_size, Image.Resampling.LANCZOS) doc.close() # 캐시에 저장 self.preview_cache[cache_key] = image return image except Exception as e: print(f"미리보기 생성 실패 ({pdf_path}): {e}") return None def format_file_size(self, size_bytes: int) -> str: """파일 크기를 읽기 쉬운 형태로 변환""" if size_bytes < 1024: return f"{size_bytes}B" elif size_bytes < 1024**2: return f"{size_bytes/1024:.1f}KB" else: return f"{size_bytes/(1024**2):.1f}MB" def clear_cache(self): """캐시 클리어""" self.preview_cache.clear() def open_file_with_system(self, pdf_path: str) -> bool: """시스템 기본 프로그램으로 파일 열기""" try: if platform.system() == 'Windows': os.startfile(pdf_path) elif platform.system() == 'Darwin': # macOS subprocess.run(['open', pdf_path]) else: # Linux subprocess.run(['xdg-open', pdf_path]) return True except Exception as e: print(f"파일 열기 실패 ({pdf_path}): {e}") return False class PDFPreviewDialog: """PDF 미리보기 다이얼로그""" def __init__(self, parent, pdf_path: str, employee_name: str, preview_manager: PDFPreviewManager): self.parent = parent self.pdf_path = pdf_path self.employee_name = employee_name self.preview_manager = preview_manager # PDF 정보 가져오기 self.pdf_info = preview_manager.get_pdf_info(pdf_path) # 다이얼로그 창 생성 self.window = tk.Toplevel(parent) self.window.title(f"미리보기 - {employee_name}") self.window.geometry("450x650") self.window.resizable(True, True) # 창을 모달로 설정 self.window.transient(parent) self.window.grab_set() # 창을 화면 중앙에 위치 self.center_window() self.setup_ui() def center_window(self): """창을 화면 중앙에 위치시키기""" self.window.update_idletasks() width = self.window.winfo_width() height = self.window.winfo_height() pos_x = (self.window.winfo_screenwidth() // 2) - (width // 2) pos_y = (self.window.winfo_screenheight() // 2) - (height // 2) self.window.geometry(f'{width}x{height}+{pos_x}+{pos_y}') def setup_ui(self): """UI 구성""" main_frame = ttk.Frame(self.window, padding=10) main_frame.pack(fill='both', expand=True) # 상단: 파일 정보 info_frame = ttk.LabelFrame(main_frame, text="📋 파일 정보", padding=10) info_frame.pack(fill='x', pady=(0, 10)) # 정보 표시 info_grid = ttk.Frame(info_frame) info_grid.pack(fill='x') # 파일명 ttk.Label(info_grid, text="파일명:", font=('맑은 고딕', 9, 'bold')).grid( row=0, column=0, sticky='w', padx=(0, 10) ) ttk.Label(info_grid, text=self.pdf_info.get('file_name', '알 수 없음')).grid( row=0, column=1, sticky='w' ) # 직원명 ttk.Label(info_grid, text="직원명:", font=('맑은 고딕', 9, 'bold')).grid( row=1, column=0, sticky='w', padx=(0, 10), pady=2 ) ttk.Label(info_grid, text=self.employee_name).grid( row=1, column=1, sticky='w', pady=2 ) # 페이지 수 page_count = self.pdf_info.get('page_count', 0) ttk.Label(info_grid, text="페이지 수:", font=('맑은 고딕', 9, 'bold')).grid( row=2, column=0, sticky='w', padx=(0, 10), pady=2 ) ttk.Label(info_grid, text=f"{page_count}페이지").grid( row=2, column=1, sticky='w', pady=2 ) # 파일 크기 file_size = self.preview_manager.format_file_size(self.pdf_info.get('file_size', 0)) ttk.Label(info_grid, text="파일 크기:", font=('맑은 고딕', 9, 'bold')).grid( row=3, column=0, sticky='w', padx=(0, 10), pady=2 ) ttk.Label(info_grid, text=file_size).grid( row=3, column=1, sticky='w', pady=2 ) # 암호화 상태 if self.pdf_info.get('is_encrypted'): ttk.Label(info_grid, text="보안:", font=('맑은 고딕', 9, 'bold')).grid( row=4, column=0, sticky='w', padx=(0, 10), pady=2 ) security_label = ttk.Label(info_grid, text="🔒 비밀번호 보호됨", foreground='orange') security_label.grid(row=4, column=1, sticky='w', pady=2) # 중앙: 미리보기 이미지 preview_frame = ttk.LabelFrame(main_frame, text="📖 첫 페이지 미리보기", padding=10) preview_frame.pack(fill='both', expand=True, pady=(0, 10)) # 스크롤 가능한 캔버스 생성 canvas_frame = ttk.Frame(preview_frame) canvas_frame.pack(fill='both', expand=True) self.canvas = tk.Canvas(canvas_frame, bg='white', relief='sunken', bd=2) scrollbar_v = ttk.Scrollbar(canvas_frame, orient='vertical', command=self.canvas.yview) scrollbar_h = ttk.Scrollbar(canvas_frame, orient='horizontal', command=self.canvas.xview) self.canvas.configure(yscrollcommand=scrollbar_v.set, xscrollcommand=scrollbar_h.set) # 미리보기 이미지 로드 및 표시 self.load_preview_image() # 그리드 배치 self.canvas.grid(row=0, column=0, sticky='nsew') scrollbar_v.grid(row=0, column=1, sticky='ns') scrollbar_h.grid(row=1, column=0, sticky='ew') canvas_frame.grid_rowconfigure(0, weight=1) canvas_frame.grid_columnconfigure(0, weight=1) # 하단: 버튼들 button_frame = ttk.Frame(main_frame) button_frame.pack(fill='x', pady=(10, 0)) # 버튼 배치 ttk.Button(button_frame, text="📂 시스템으로 열기", command=self.open_with_system).pack(side='left', padx=(0, 5)) ttk.Button(button_frame, text="📁 폴더 열기", command=self.open_folder).pack(side='left', padx=5) ttk.Button(button_frame, text="🔄 새로고침", command=self.refresh_preview).pack(side='left', padx=5) # 오른쪽에 닫기 버튼 ttk.Button(button_frame, text="닫기", command=self.close_dialog).pack(side='right') def load_preview_image(self): """미리보기 이미지 로드""" try: if 'error' in self.pdf_info: # 오류 메시지 표시 self.canvas.create_text(200, 100, text=f"미리보기를 생성할 수 없습니다.\n{self.pdf_info['error']}", fill='red', font=('맑은 고딕', 12), anchor='center') return # 미리보기 이미지 생성 thumbnail = self.preview_manager.create_preview_thumbnail(self.pdf_path, (350, 500)) if thumbnail: # PIL Image를 PhotoImage로 변환 self.photo = ImageTk.PhotoImage(thumbnail) # 캔버스에 이미지 표시 self.canvas.delete("all") # 기존 내용 제거 self.canvas.create_image(thumbnail.width//2, thumbnail.height//2, image=self.photo, anchor='center') # 스크롤 영역 설정 self.canvas.configure(scrollregion=self.canvas.bbox("all")) else: # 미리보기 실패 메시지 self.canvas.create_text(200, 100, text="미리보기를 생성할 수 없습니다.\nPDF 파일이 손상되었거나\n지원하지 않는 형식일 수 있습니다.", fill='red', font=('맑은 고딕', 12), anchor='center') except Exception as e: # 예외 발생시 오류 메시지 표시 self.canvas.create_text(200, 100, text=f"미리보기 로드 실패:\n{str(e)}", fill='red', font=('맑은 고딕', 12), anchor='center') def open_with_system(self): """시스템 기본 프로그램으로 열기""" success = self.preview_manager.open_file_with_system(self.pdf_path) if not success: messagebox.showerror("오류", "파일을 열 수 없습니다.\n기본 PDF 뷰어가 설치되어 있는지 확인해주세요.") def open_folder(self): """파일이 있는 폴더 열기""" try: folder_path = os.path.dirname(self.pdf_path) if platform.system() == 'Windows': subprocess.run(['explorer', '/select,', self.pdf_path]) elif platform.system() == 'Darwin': # macOS subprocess.run(['open', '-R', self.pdf_path]) else: # Linux subprocess.run(['xdg-open', folder_path]) except Exception as e: messagebox.showerror("오류", f"폴더를 열 수 없습니다:\n{str(e)}") def refresh_preview(self): """미리보기 새로고침""" # 캐시에서 제거 cache_key = f"{self.pdf_path}_(350, 500)" if cache_key in self.preview_manager.preview_cache: del self.preview_manager.preview_cache[cache_key] # PDF 정보 다시 로드 self.pdf_info = self.preview_manager.get_pdf_info(self.pdf_path) # 미리보기 이미지 다시 로드 self.load_preview_image() messagebox.showinfo("새로고침", "미리보기가 새로고침되었습니다.") def close_dialog(self): """다이얼로그 닫기""" self.window.grab_release() # 모달 해제 self.window.destroy()

          
        
    
    
Copyright Yona authors & © NAVER Corp. & NAVER LABS Supported by NAVER CLOUD PLATFORM

or
Sign in with github login with Google Sign in with Google
Reset password | Sign up