본문 바로가기
목차
Python/PDF

ReportLab-Python에서 PDF문서 만들기:

by ds31x 2025. 9. 19.
728x90
반응형

 

ReportLab 은 Python에서 PDF 문서를 프로그래밍 방식으로 생성할 수 있는 BSD 라이선스 하에 배포되는 오픈소스 Python 라이브러리임.

  • Canvas(저수준)와 Platypus(고수준) 두 가지 API를 제공해서
  • 간단한 보고서부터 복잡한 차트와 표가 포함된 전문적인 문서까지 모두 만들 수 있음.
  • 단, 처음부터 PDF문서를 만들 때 유용: 기존의 PDF를 합치거나 변경 또는 text추출은 PyMuPDF등이 보다 나은 선택임.
    • 단, PyMuPDF는 오픈소스 프로젝트에서만 무료이지 상업용은 라이선스가 필요함.

BSD 라이선스는
상업적 패키지에서도 사용할 수 있는
매우 자유로운 라이선스임.

 

설치는 pip 또는 conda 모두 지원함.

pip install reportlab   # pip로 설치
conda install reportlab # conda로 설치.

 

상업용 버전도 지원하는데,

상업용 버전의 경우, RML (Report Markup Language)을 통해 HTML처럼 마크업으로 PDF 생성하게 해주는 기능과 더 많은 템플릿 기능을 지원함.

 

참고할 URL은 다음과 같음:

 

튜토리얼
https://docs.reportlab.com/reportlab/userguide/ch1_intro/

 

Chapter 1: Introduction - ReportLab Docs

Introduction About this document This document is an introduction to the ReportLab PDF library. Some previous programming experience is presumed and familiarity with the Python Programming language is recommended. If you are new to Python, we tell you in t

docs.reportlab.com

공식 URLs
https://docs.reportlab.com/

 

ReportLab Docs

Preppy The fast, light templating system we bundle with ReportLab PLUS (BSD Licence) Docs

docs.reportlab.com


0) ReportLab 전체 구조 한눈에 보기

  • Canvas 레벨:
    • 저수준 API.
    • 좌표 찍어 텍스트/도형을 직접 그리는 방식.
  • Platypus 레벨:
    • 고수준 문서 조립
    • Flowable 이라는 블록 요소들을 쌓아 문서 구성.

Platypus는 "Page Layout and Typography Using Scripts" 의 줄임말임.
오리너구리(platypus) 를 연상시키는 작명.

 

테이블로만 구성된 파일은 보통 다음의 Flowable들로 구성하면 된다:

SimpleDocTemplate + Table + Paragraph, and so on.
더보기

Typography(타이포그래피)는 문자, 글꼴, 그리고 글자 배열의 시각적 설계를 다루는 분야


1) SimpleDocTemplate — 문서의 뼈대 만들기

역할은 다음과 같음:

  • 페이지 크기, 여백, 페이지 구성 규칙을 정함.
  • Flowable 리스트를 넘겨받아, 이를 build() 메서드 호출로 렌더링.
from reportlab.platypus import SimpleDocTemplate
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib.units import mm

doc = SimpleDocTemplate(
    "table.pdf",
    pagesize=landscape(A4),   # A4 가로 (세로는 A4)
    leftMargin=15*mm, rightMargin=15*mm,
    topMargin=15*mm, bottomMargin=15*mm,
)
  • pagesize 단위: pt(포인트). 1pt ≈ 0.3528mm
  • landscape(A4) : 가로 방향 전환. 크기는 A4.
  • 여백은 mm 유틸(아래 7번 참고)로 가독성 있게 표현함.

2) Table — 표(테이블) Flowable

역할은 다음과 같음:

  • 2차원 데이터(리스트의 리스트)를 표로 렌더링.
  • Pandas의 DataFrame 객체를 출력할 때는 Table만으로 충분함.
from reportlab.platypus import Table

data = [
    ["ColA", "ColB", "ColC"],   # 헤더
    ["a1", "b1", "c1"],
    ["a2", "b2", "c2"],
]

table = Table(
          data, 
          colWidths=[80, 120, 100],  # 단위: pt
        )
  • data[0]가 보통 헤더로 쓰임.(강제 규칙은 아니나, 일반적으로 스타일에서 첫 행을 헤더로 처리).
  • 컬럼 폭은 colWidths 리스트로 지정하거나, 나중에 table._argW = [...]로 직접 설정 가능함.
  • 긴 표는 자동으로 페이지가 넘어감(Flowable),
  • repeatRows=1 옵션을 추가할 경우, 각 장마다 헤더 반복 가능.
table = Table(data, repeatRows=1)

3) TableStyle — 표 스타일링(색, 정렬, 패딩, 테두리…)

역할음 다음과 같음:

  • Table 객체를 랜더링시 적용할 스타일을 설정.
from reportlab.platypus import TableStyle
from reportlab.lib import colors

table.setStyle(TableStyle([
    # (속성명, (시작열, 시작행), (끝열, 끝행), 값)
    ('BACKGROUND', (0,0), (-1,0), colors.grey),      # 1행(헤더) 배경
    ('TEXTCOLOR',  (0,0), (-1,0), colors.whitesmoke),# 1행(헤더) 글자색
    ('FONT',       (0,0), (-1,-1), 'Helvetica', 8.5),# 전체 폰트/크기
    ('ALIGN',      (0,0), (-1,-1), 'CENTER'),        # 가로 정렬
    ('VALIGN',     (0,0), (-1,-1), 'MIDDLE'),        # 세로 정렬
    ('GRID',       (0,0), (-1,-1), 0.3, colors.grey),# 테두리
    ('TOPPADDING', (0,0), (-1,0), 6),                # 헤더 위쪽 패딩
    ('BOTTOMPADDING',(0,0),(-1,0), 6),               # 헤더 아래쪽 패딩
    ('LEFTPADDING',(0,1), (-1,-1), 3),               # 본문 좌패딩
    ('RIGHTPADDING',(0,1),(-1,-1), 3),               # 본문 우패딩
]))
  • 셀 범위 표기: (col_start, row_start), (col_end, row_end), 여기서 -1은 마지막을 의미.

자주 쓰는 속성은 다음과 같음:

  • BACKGROUND, TEXTCOLOR, FONT, ALIGN, VALIGN, GRID,
  • TOPPADDING, BOTTOMPADDING, LEFTPADDING, RIGHTPADDING

4) Paragraph + ParagraphStyle — 셀 내용의 줄바꿈/스타일

Paragraph 클래스의 역할은 다음과 같음:

  • 셀 내용을 Paragraph 객체로 만들면 긴 텍스트일 경우 자동 줄바꿈이 이루어짐.
  • CJK 줄바꿈을 사용시 한글 등에 적절한 줄바꿈을 이루어짐.
  • 정렬/폰트/크기를 세밀하게 컨트롤.

다음은 Paragraph 클래스를 생성시 필요한 ParagraphStyle 객체를 만드는 방법임:

from reportlab.platypus import Paragraph
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle

styles = getSampleStyleSheet()

header_style = ParagraphStyle(
    name="Header",
    parent=styles["Normal"],
    fontName="NanumGothic",   # 등록된 폰트명
    fontSize=9,
    leading=12,
    alignment=1,              # 0=LEFT, 1=CENTER, 2=RIGHT, 4=JUSTIFY
    textColor=colors.whitesmoke,
    wordWrap='CJK',           # CJK(한중일) 줄바꿈 허용
)

cell_style = ParagraphStyle(
    name="Cell",
    parent=styles["Normal"],
    fontName="NanumGothic",
    fontSize=8.5,
    leading=12,
    alignment=1,
    wordWrap='CJK',
)
  • getSampleStyleSheet() 함수는 ReportLab에서 문단(Paragraph), 제목(Heading), 본문(BodyText) 등 문서 내 텍스트의 스타일을 손쉽게 적용할 수 있도록 미리 정의된 스타일 모음(style dictionary)을 반환하는 함수
    • print(styles.byName.keys()) 를 통해 미리 정의된 스타일들의 키 값을 확인 가능함
  • wordWrap='CJK'를 주면 띄어쓰기가 적은 한글/한자도 자연스럽게 줄바꿈.
  • leading은 줄간격(pt). fontSize보다 조금 크게 두어야 보기 편함.
  • fontName에 지정하는 폰트는 한글 사용을 위해서 pdfmetrics.registerFont()함수로 미리 등록해두어야 함(아래 참고).

이 같이 설정한 ParagraphStyle은 다음과 같이 Paragraph 에 적용가능함:

header = [Paragraph(str(c), header_style) for c in df.columns]
row = [Paragraph(str(x), cell_style) for x in df.iloc[0]]

5) 폰트 등록: pdfmetrics.registerFont + TTFont

역할은 다음과 같음:

  • 시스템 폰트 파일(TTF/OTF)을 ReportLab에 등록
  • 한글 출력을 위해 필요함.
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

pdfmetrics.registerFont(TTFont("NanumGothic", "/Library/Fonts/NanumGothic.ttf"))
  • 등록 후 ParagraphStyle(fontName="NanumGothic")로 사용.
  • 미등록 상태에서 한글 사용 → 네모(□) 출력 or 오류.
  • Windows 예시: C:/Windows/Fonts/NanumGothic.ttf

참고로,

TrueType 기반(OpenType with TrueType outlines) 이면 사용 가능하지만,

CFF 기반(OpenType with PostScript outlines) 인 경우 ReportLab의 TTFont()로는 사용할 수 없음.

2025.09.20 - [CE] - Font: TTF vs. OTF

 

Font: TTF vs. OTF

개발자 친화적인 아이콘을 font로 제공하는 Nerd Fonts에서는 아직까지 TTF만을 지원할 정도로, 아직까지는 TTF가 터미널 및 IDE에서는 가장 안정적으로 동작한다. 하지만, 프로그램 라이브러리의 resou

ds31x.tistory.com


6) 컬럼 폭 자동 계산: pdfmetrics.stringWidth()

역할은 다음과 같음:

  • 특정 폰트/크기에서 문자열의 실제 폭(pt) 을 계산해 반환.
  • 컬럼 폭을 데이터 길이에 맞게 설정할 때 필요함.
from reportlab.pdfbase import pdfmetrics

w = pdfmetrics.stringWidth("Longest Text", "NanumGothic", 8.5) + 8  # padding 포함(+8)로.
  • 첫번째 인자로 지정된 str 객체를 두번째 인자로 지정된 font와 세번째 인자로 지정된 크기로 출력시 필요한 width를 반환함.
  • 컬럼별로 “헤더 vs 샘플 데이터(예: 상위 200행)”의 최대 폭을 구해 colWidths 리스트 작성시 필요.
  • 페이지에 설정된 폭(= 페이지 폭 − 좌/우 여백)를 초과하면 비율로 스케일링 처리가 필요함.

7) 색상/단위/페이지 도구 모음

색을 지정하는 방법의 예는 다음과 같음:

from reportlab.lib import colors
colors.grey 
colors.whitesmoke 
colors.HexColor('#RRGGBB')

단위는 기본이 pt이므로, mm와 같은 단위 도구를 사용하는 것이 가독성을 높임.

from reportlab.lib.units import mm 
30*mm # 30mm에 해당하는 pt 계산

랜더링할 페이지의 크기를 미리 정의하고 있는 클래스 및 세로보기, 가로보기등도 지원.

from reportlab.lib.pagesizes import A4, letter, landscape, portrait

8) 반복 헤더와 긴 표 자동 나눔

  • 표가 길면 doc.build([table]) 시 자동으로 페이지 분할이 일어남.
  • Table(..., repeatRows=1) : 앞서 말했듯이 첫 행을 각 페이지마다 반복시킴.
  • 너무 큰 표(너무 많은 열/폭)는 레이아웃 실패할 수 있음:
    • colWidths 의 지정값을 적절히 스케일링해야함.
    • 또는 landscape 활용.

9) 간단한 예제

아래 코드는 바로 돌려볼 수 있는 가장 작은 예시입니다.

import pandas as pd
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.units import mm

# 폰트 등록 (경로는 시스템에 맞게 변경): 5번 절 참고
pdfmetrics.registerFont(TTFont("NanumGothic", "/Library/Fonts/NanumGothic.ttf"))

# 샘플 데이터
df = pd.DataFrame({
    "이름": ["김철수", "이영희", "박민수"],
    "소개": ["데이터 사이언티스트", "머신러닝 엔지니어", "리서처(한/영 텍스트 자동 줄바꿈 테스트)"],
    "점수": [95.2, 88.05, 76.3333],
})

# 스타일: 4번 절 참고
styles = getSampleStyleSheet()
header_style = ParagraphStyle(
    name="Header", parent=styles["Normal"], fontName="NanumGothic",
    fontSize=9, leading=12, alignment=1, textColor=colors.whitesmoke, wordWrap='CJK'
)
cell_style = ParagraphStyle(
    name="Cell", parent=styles["Normal"], fontName="NanumGothic",
    fontSize=8.5, leading=12, alignment=1, wordWrap='CJK'
)

# 데이터 를 Paragraph로 감싸기: 4번 절 참고
data = [[Paragraph(str(c), header_style) for c in df.columns]]
for _, row in df.iterrows():
    data.append([Paragraph("" if pd.isna(v) else str(v), cell_style) for v in row])

# 5) 테이블 + 스타일 : 2번절과 3번절 참고
table = Table(data, repeatRows=1)
table.setStyle(TableStyle([
    # 헤더 행(0행) 배경색을 회색으로 지정
    ('BACKGROUND', (0,0), (-1,0), colors.grey),
    # 헤더 행(0행)의 텍스트 색상을 흰색(whitesmoke)으로 지정
    ('TEXTCOLOR',  (0,0), (-1,0), colors.whitesmoke),
    # 전체 테이블의 폰트를 'NanumGothic'으로 지정하고, 크기는 8.5pt로 설정
    ('FONT',       (0,0), (-1,-1), 'NanumGothic', 8.5),
    # 모든 셀의 텍스트를 가로 방향으로 가운데 정렬
    ('ALIGN',      (0,0), (-1,-1), 'CENTER'),
    # 모든 셀의 텍스트를 세로 방향으로 가운데 정렬
    ('VALIGN',     (0,0), (-1,-1), 'MIDDLE'),
    # 모든 셀 경계선을 두께 0.3pt, 회색으로 표시 
    ('GRID',       (0,0), (-1,-1), 0.3, colors.grey),
    # 헤더 행(0행)의 위쪽 여백(top padding)을 6pt로 설정
    ('TOPPADDING', (0,0), (-1,0), 6),
    # 헤더 행(0행)의 아래쪽 여백(bottom padding)을 6pt로 설정
    ('BOTTOMPADDING',(0,0),(-1,0), 6),
    # 본문 영역(1행부터 끝행까지)의 왼쪽 여백(left padding)을 3pt로 설정
    ('LEFTPADDING',(0,1), (-1,-1), 3),
    # 본문 영역(1행부터 끝행까지)의 오른쪽 여백(right padding)을 3pt로 설정
    ('RIGHTPADDING',(0,1),(-1,-1), 3),
]))

# 문서 생성 : 1번절 참고
doc = SimpleDocTemplate(
    "mini_table.pdf",
    pagesize=landscape(A4),
    leftMargin=15*mm, rightMargin=15*mm, # 7번 절 참고. 
    topMargin=15*mm, bottomMargin=15*mm,
)

# 8번절 참고
doc.build([table])

10) 기타

  • 정렬: 숫자 컬럼만 Right Alignment 로 바꾸려면,
    • TableStyle 객체를 만들 때, 해당 컬럼 셀 범위에 ALIGN을 별도로 적용(3번 절 참고)하거나
    • 숫자용 ParagraphStyle(alignment=2) 를 따로 만들어 컬럼 타입별로 구분하여 적용.

같이보면 좋은 자료들

 

2023.06.28 - [Python/PDF] - [PDF] Text 추출하기: PyPDF2 vs. PyMuPDF

 

[PDF] Text 추출하기: PyPDF2 vs. PyMuPDF

version : PyPDF 2.11.1 (from Mamba), PyMuPDF 1.22.5 (from pip) 여러 pdf처리 library가 있지만, 그나마 제일 많이 써본 터라 PyPDF2를 선호한다. 추출 정확도는 PyMuPDF보다 좀 떨어지는 거 같지만, 익숙함이 주는 편의

ds31x.tistory.com

2023.06.28 - [Python/PDF] - [PDF] Merge PDF

 

[PDF] Merge PDF

PyMuPDF 1.22.5 (from pip) PyMuPDF를 이용하여, 여러 PDF를 그냥 합쳐주는 간단한 프로그램을 만들어 봤다.(문서 합쳐서 제출하는 일이 잦았나보다. 예제 생각하다가 이게 떠오르다니... --;;) 참고로, fitz가

ds31x.tistory.com

2023.06.22 - [utils] - [Linux] Simple Merge PDF files

 

[Linux] Simple Merge PDF files

pdf pro등은 무겁다보니, 많은 경우 인터넷만 되면 동작하는 ilovepdf 를 많이 사용하는 것을 보게 된다.문제는 다들 가지고 있는 원격지에 파일을 전송하는 불안감이...(회사나 연구실 정보가 많은

ds31x.tistory.com

 

728x90

'Python > PDF' 카테고리의 다른 글

[PDF] Merge PDF  (0) 2023.06.28
[PDF] Text 추출하기: PyPDF2 vs. PyMuPDF  (0) 2023.06.28