반응형
아래는 PyQt5와 함께 reportlab, PyPDF2, pikepdf를 이용해 PDF를 생성, 분리, 병합하는 예제 코드입니다. 이 코드는 각 기능에 대해 간단한 GUI 인터페이스를 제공하며, 각 함수에 주석을 추가해 이해하기 쉽게 작성했습니다.
반응형
import sys
import os
import platform
from PyQt5.QtWidgets import (
QApplication, QWidget, QTabWidget, QVBoxLayout, QPushButton,
QLineEdit, QLabel, QFileDialog, QHBoxLayout, QMessageBox, QSpinBox
)
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import PyPDF2
import pikepdf
# 한글 폰트 등록 함수 (운영체제에 따라 적절한 폰트 선택)
def register_korean_font():
system = platform.system()
# 실행 파일이 있는 폴더 기준으로 폰트 파일 경로 설정
base_dir = os.path.dirname(os.path.abspath(__file__))
font_name = None
font_path = None
if system == "Windows":
font_name = "Malgun"
font_path = os.path.join(base_dir, "malgun.ttf")
elif system == "Darwin": # macOS
font_name = "AppleGothic"
font_path = os.path.join(base_dir, "AppleGothic.ttf")
elif system == "Linux":
font_name = "NanumGothic"
font_path = os.path.join(base_dir, "NanumGothic.ttf")
else:
raise Exception("지원하지 않는 운영체제입니다.")
if not os.path.exists(font_path):
raise FileNotFoundError(f"{font_path} 파일이 존재하지 않습니다. 해당 폰트 파일을 준비해주세요.")
pdfmetrics.registerFont(TTFont(font_name, font_path))
return font_name
# PDF 생성 함수 (reportlab 사용)
def create_pdf(file_path, text):
# 한글 폰트 등록 (최초 한 번 호출하면 됨)
font_name = register_korean_font()
c = canvas.Canvas(file_path, pagesize=letter)
width, height = letter
# 등록한 한글 폰트를 사용
c.setFont(font_name, 12)
# 텍스트를 페이지 중앙에 배치 (적절하게 위치 조정)
c.drawString(100, height - 100, text)
c.showPage()
c.save()
# PDF 분리 함수 (PyPDF2 사용)
def split_pdf(source_path, output_dir, start_page, end_page):
with open(source_path, 'rb') as infile:
reader = PyPDF2.PdfReader(infile)
writer = PyPDF2.PdfWriter()
# 페이지 번호는 0부터 시작하므로 start_page-1 부터 end_page까지 처리
for i in range(start_page - 1, min(end_page, len(reader.pages))):
writer.add_page(reader.pages[i])
# 분리된 PDF 저장
output_path = os.path.join(output_dir, "split_output.pdf")
with open(output_path, 'wb') as outfile:
writer.write(outfile)
return output_path
# PDF 병합 함수 (pikepdf 사용)
def merge_pdfs(pdf_paths, output_path):
merged_pdf = pikepdf.Pdf.new()
for path in pdf_paths:
src_pdf = pikepdf.Pdf.open(path)
for page in src_pdf.pages:
merged_pdf.pages.append(page)
merged_pdf.save(output_path)
merged_pdf.close()
return output_path
# 메인 윈도우 클래스
class PDFTool(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("PDF Tool - 생성, 분리, 병합")
self.resize(600, 400)
self.tabs = QTabWidget()
self.create_tab = QWidget()
self.split_tab = QWidget()
self.merge_tab = QWidget()
self.tabs.addTab(self.create_tab, "PDF 생성")
self.tabs.addTab(self.split_tab, "PDF 분리")
self.tabs.addTab(self.merge_tab, "PDF 병합")
self.init_create_tab()
self.init_split_tab()
self.init_merge_tab()
layout = QVBoxLayout()
layout.addWidget(self.tabs)
self.setLayout(layout)
# PDF 생성 탭 초기화 (reportlab 이용)
def init_create_tab(self):
layout = QVBoxLayout()
self.create_text_input = QLineEdit()
self.create_text_input.setPlaceholderText("PDF에 들어갈 텍스트 입력 (한글 지원)")
layout.addWidget(QLabel("텍스트 입력:"))
layout.addWidget(self.create_text_input)
self.create_filename_input = QLineEdit()
self.create_filename_input.setPlaceholderText("저장할 파일명 (예: output.pdf)")
layout.addWidget(QLabel("파일명:"))
layout.addWidget(self.create_filename_input)
create_btn = QPushButton("PDF 생성")
create_btn.clicked.connect(self.handle_create_pdf)
layout.addWidget(create_btn)
self.create_tab.setLayout(layout)
def handle_create_pdf(self):
text = self.create_text_input.text()
file_name = self.create_filename_input.text().strip()
if not file_name:
QMessageBox.warning(self, "오류", "파일명을 입력하세요.")
return
save_path, _ = QFileDialog.getSaveFileName(self, "저장할 위치 선택", file_name, "PDF Files (*.pdf)")
if save_path:
try:
create_pdf(save_path, text)
QMessageBox.information(self, "성공", f"PDF가 생성되었습니다: {save_path}")
except Exception as e:
QMessageBox.critical(self, "오류", f"PDF 생성 중 오류 발생: {e}")
# PDF 분리 탭 초기화 (PyPDF2 이용)
def init_split_tab(self):
layout = QVBoxLayout()
self.split_file_path = QLineEdit()
self.split_file_path.setReadOnly(True)
btn_select_file = QPushButton("PDF 파일 선택")
btn_select_file.clicked.connect(self.select_split_file)
file_layout = QHBoxLayout()
file_layout.addWidget(self.split_file_path)
file_layout.addWidget(btn_select_file)
layout.addWidget(QLabel("분리할 PDF 파일:"))
layout.addLayout(file_layout)
self.start_page_spin = QSpinBox()
self.start_page_spin.setMinimum(1)
self.start_page_spin.setValue(1)
layout.addWidget(QLabel("시작 페이지 번호:"))
layout.addWidget(self.start_page_spin)
self.end_page_spin = QSpinBox()
self.end_page_spin.setMinimum(1)
self.end_page_spin.setValue(1)
layout.addWidget(QLabel("끝 페이지 번호:"))
layout.addWidget(self.end_page_spin)
split_btn = QPushButton("PDF 분리")
split_btn.clicked.connect(self.handle_split_pdf)
layout.addWidget(split_btn)
self.split_tab.setLayout(layout)
def select_split_file(self):
file_path, _ = QFileDialog.getOpenFileName(self, "PDF 파일 선택", "", "PDF Files (*.pdf)")
if file_path:
self.split_file_path.setText(file_path)
try:
with open(file_path, 'rb') as infile:
reader = PyPDF2.PdfReader(infile)
total_pages = len(reader.pages)
self.start_page_spin.setMaximum(total_pages)
self.end_page_spin.setMaximum(total_pages)
self.end_page_spin.setValue(total_pages)
except Exception as e:
QMessageBox.critical(self, "오류", f"PDF 파일 열기 실패: {e}")
def handle_split_pdf(self):
source_path = self.split_file_path.text()
if not source_path or not os.path.exists(source_path):
QMessageBox.warning(self, "오류", "분리할 PDF 파일을 선택하세요.")
return
start_page = self.start_page_spin.value()
end_page = self.end_page_spin.value()
if start_page > end_page:
QMessageBox.warning(self, "오류", "시작 페이지가 끝 페이지보다 클 수 없습니다.")
return
output_dir = QFileDialog.getExistingDirectory(self, "결과 저장 폴더 선택")
if output_dir:
try:
output_file = split_pdf(source_path, output_dir, start_page, end_page)
QMessageBox.information(self, "성공", f"분리된 PDF가 생성되었습니다: {output_file}")
except Exception as e:
QMessageBox.critical(self, "오류", f"PDF 분리 중 오류 발생: {e}")
# PDF 병합 탭 초기화 (pikepdf 이용)
def init_merge_tab(self):
layout = QVBoxLayout()
self.merge_files = []
self.merge_files_display = QLineEdit()
self.merge_files_display.setReadOnly(True)
layout.addWidget(QLabel("병합할 PDF 파일들 (순서대로):"))
layout.addWidget(self.merge_files_display)
btn_add_file = QPushButton("PDF 파일 추가")
btn_add_file.clicked.connect(self.add_merge_file)
layout.addWidget(btn_add_file)
btn_clear_files = QPushButton("파일 목록 초기화")
btn_clear_files.clicked.connect(self.clear_merge_files)
layout.addWidget(btn_clear_files)
merge_btn = QPushButton("PDF 병합")
merge_btn.clicked.connect(self.handle_merge_pdf)
layout.addWidget(merge_btn)
self.merge_tab.setLayout(layout)
def add_merge_file(self):
file_path, _ = QFileDialog.getOpenFileName(self, "병합할 PDF 파일 선택", "", "PDF Files (*.pdf)")
if file_path:
self.merge_files.append(file_path)
self.merge_files_display.setText("; ".join(self.merge_files))
def clear_merge_files(self):
self.merge_files = []
self.merge_files_display.clear()
def handle_merge_pdf(self):
if not self.merge_files:
QMessageBox.warning(self, "오류", "병합할 PDF 파일을 하나 이상 선택하세요.")
return
save_path, _ = QFileDialog.getSaveFileName(self, "병합된 PDF 저장", "merged_output.pdf", "PDF Files (*.pdf)")
if save_path:
try:
merge_pdfs(self.merge_files, save_path)
QMessageBox.information(self, "성공", f"PDF가 병합되었습니다: {save_path}")
except Exception as e:
QMessageBox.critical(self, "오류", f"PDF 병합 중 오류 발생: {e}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = PDFTool()
window.show()
sys.exit(app.exec_())
코드 설명
- PDF 생성
- create_pdf 함수는 reportlab을 사용하여 텍스트를 포함한 PDF를 생성합니다.
- 생성 탭에서는 텍스트와 파일명을 입력한 후 저장 위치를 선택하면 PDF가 생성됩니다.
- PDF 분리
- split_pdf 함수는 PyPDF2를 사용해 지정된 페이지 범위의 PDF 페이지들을 추출하여 새로운 PDF로 저장합니다.
- 분리 탭에서는 원본 PDF 파일 선택, 시작/끝 페이지 번호를 지정하고 결과 폴더를 선택할 수 있습니다.
- PDF 병합
- merge_pdfs 함수는 pikepdf를 이용하여 여러 PDF 파일을 하나로 병합합니다.
- 병합 탭에서는 병합할 PDF 파일들을 순서대로 추가하고, 병합 결과 파일의 저장 위치를 선택합니다.
이 코드는 PyQt5를 기반으로 GUI를 구성하며, 각 기능별로 별도의 탭에서 작업할 수 있도록 되어 있습니다. 각 함수와 버튼 클릭 이벤트에 주석을 달아 코드의 흐름을 쉽게 이해할 수 있도록 했습니다.
실행 예시)
반응형
'IT > SW Dev.' 카테고리의 다른 글
Vercel 과 잘 맞는 무료 데이터 베이스 - Neon, PlanetScale, Supabase (1) | 2025.03.01 |
---|---|
문장속 단어빈도 분석 및 시각화 WordCloud(워드 클라우드) - python (0) | 2025.02.28 |
쉬운 AI Coding - 마우스 커서 자동 이동 (chatgpt,python, pyautogui) (1) | 2025.02.22 |
쉬운 AI Coding - Python UI 개발툴(Tkinter, pyQt, kivy) (1) | 2025.02.19 |
쉬운 AI Coding - EasyOCR(텍스트 추출) 패키지 및 Python 예제 (0) | 2025.02.19 |