IT/SW Dev.

쉬운 AI Coding - Pdf 생성, 분리, 병합 (chatgpt, python)

부티형 2025. 2. 22. 21:29
반응형

아래는 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_())

코드 설명

  1. PDF 생성
    • create_pdf 함수는 reportlab을 사용하여 텍스트를 포함한 PDF를 생성합니다.
    • 생성 탭에서는 텍스트와 파일명을 입력한 후 저장 위치를 선택하면 PDF가 생성됩니다.
  2. PDF 분리
    • split_pdf 함수는 PyPDF2를 사용해 지정된 페이지 범위의 PDF 페이지들을 추출하여 새로운 PDF로 저장합니다.
    • 분리 탭에서는 원본 PDF 파일 선택, 시작/끝 페이지 번호를 지정하고 결과 폴더를 선택할 수 있습니다.
  3. PDF 병합
    • merge_pdfs 함수는 pikepdf를 이용하여 여러 PDF 파일을 하나로 병합합니다.
    • 병합 탭에서는 병합할 PDF 파일들을 순서대로 추가하고, 병합 결과 파일의 저장 위치를 선택합니다.

이 코드는 PyQt5를 기반으로 GUI를 구성하며, 각 기능별로 별도의 탭에서 작업할 수 있도록 되어 있습니다. 각 함수와 버튼 클릭 이벤트에 주석을 달아 코드의 흐름을 쉽게 이해할 수 있도록 했습니다.

 

실행 예시)

 

반응형