feat(certification): GS인증 신청서 PDF 자동 생성

certification/generate_pdf.py: reportlab 기반 한글 PDF 생성기
  - 맑은고딕(malgun.ttf) 자동 탐색 + 등록
  - 7개 섹션 구성:
    1. 신청자 정보 (회사명, 대표자, 사업자번호 등)
    2. 소프트웨어 정보 (제품명, 버전, 등급, 분류)
    3. 제품 개요 (설명문, 핵심기능 5개, 지원환경)
    4. 시험 요청 항목 (ISO/IEC 25010 8개 품질특성)
    5. 제출 서류 목록 (11개 필수/선택)
    6. 예상 수수료 (650만원)
    7. 서약 및 서명란

산출물: GS인증_신청서_GUARDiA_ITSM_v2.0.pdf (118.8 KB)
  - A4 용지, 2페이지
  - 네이비 테마 디자인
  - 페이지 번호 + 회사명 푸터
  - 바로 출력/제출 가능

실행: cd certification && python3 generate_pdf.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
DESKTOP-TKLFCPR\ython 2026-05-30 10:21:19 +09:00
parent 7373d74204
commit 0854a0fea2
2 changed files with 592 additions and 0 deletions

Binary file not shown.

View File

@ -0,0 +1,592 @@
"""
GUARDiA ITSM GS인증 신청서 PDF 생성기
실행: python generate_pdf.py
"""
import os
import sys
from pathlib import Path
from datetime import datetime
# reportlab 임포트
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# ── 한글 폰트 등록 ────────────────────────────────────────────
def register_korean_fonts():
"""Windows/Linux에서 한글 폰트 자동 탐색 후 등록."""
font_candidates = [
# Windows
"C:/Windows/Fonts/malgun.ttf",
"C:/Windows/Fonts/malgunbd.ttf",
"C:/Windows/Fonts/gulim.ttc",
"C:/Windows/Fonts/batang.ttc",
# Linux
"/usr/share/fonts/truetype/nanum/NanumGothic.ttf",
"/usr/share/fonts/truetype/unfonts-core/UnDotum.ttf",
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
"/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc",
]
font_bold_candidates = [
"C:/Windows/Fonts/malgunbd.ttf",
"/usr/share/fonts/truetype/nanum/NanumGothicBold.ttf",
]
# 일반 폰트
for path in font_candidates:
if os.path.exists(path):
try:
pdfmetrics.registerFont(TTFont("KorFont", path))
print(f" 폰트 등록: {path}")
break
except Exception as e:
continue
# 볼드 폰트
for path in font_bold_candidates:
if os.path.exists(path):
try:
pdfmetrics.registerFont(TTFont("KorFontBold", path))
break
except Exception:
continue
# 폰트 패밀리 등록
try:
from reportlab.pdfbase.pdfmetrics import registerFontFamily
registerFontFamily("KorFont", normal="KorFont", bold="KorFontBold")
except Exception:
pass
# ── 색상 정의 ──────────────────────────────────────────────────
NAVY = colors.HexColor("#1e3a5f")
BLUE = colors.HexColor("#0051A2")
ACCENT = colors.HexColor("#00A3E0")
LIGHT_BG = colors.HexColor("#EBF3FB")
GRAY = colors.HexColor("#64748B")
LIGHT_GRAY = colors.HexColor("#F3F4F6")
WHITE = colors.white
BLACK = colors.black
GREEN = colors.HexColor("#065F46")
GREEN_BG = colors.HexColor("#D1FAE5")
# ── 페이지 설정 ────────────────────────────────────────────────
W, H = A4
MARGIN = 20 * mm
def make_styles():
"""스타일 정의."""
register_korean_fonts()
base_font = "KorFont" if "KorFont" in pdfmetrics.getRegisteredFontNames() else "Helvetica"
bold_font = "KorFontBold" if "KorFontBold" in pdfmetrics.getRegisteredFontNames() else "Helvetica-Bold"
styles = {
"cover_title": ParagraphStyle("cover_title", fontName=bold_font, fontSize=26,
textColor=WHITE, alignment=TA_CENTER, leading=32, spaceAfter=6),
"cover_sub": ParagraphStyle("cover_sub", fontName=base_font, fontSize=13,
textColor=colors.HexColor("#BDE3FF"), alignment=TA_CENTER, leading=18),
"cover_info": ParagraphStyle("cover_info", fontName=base_font, fontSize=11,
textColor=WHITE, alignment=TA_CENTER, leading=16),
"section": ParagraphStyle("section", fontName=bold_font, fontSize=13,
textColor=WHITE, leading=18, leftIndent=6),
"h2": ParagraphStyle("h2", fontName=bold_font, fontSize=11,
textColor=NAVY, leading=16, spaceBefore=8, spaceAfter=4),
"body": ParagraphStyle("body", fontName=base_font, fontSize=9.5,
textColor=BLACK, leading=15, spaceAfter=3),
"body_center": ParagraphStyle("body_center", fontName=base_font, fontSize=9.5,
textColor=BLACK, leading=15, alignment=TA_CENTER),
"small": ParagraphStyle("small", fontName=base_font, fontSize=8.5,
textColor=GRAY, leading=13),
"bold": ParagraphStyle("bold", fontName=bold_font, fontSize=9.5,
textColor=BLACK, leading=15),
"footer": ParagraphStyle("footer", fontName=base_font, fontSize=8,
textColor=GRAY, alignment=TA_CENTER, leading=12),
"note": ParagraphStyle("note", fontName=base_font, fontSize=8.5,
textColor=GRAY, leading=13, leftIndent=10),
}
return styles, base_font, bold_font
def build_pdf(output_path: str):
"""PDF 빌드 메인 함수."""
styles, base_font, bold_font = make_styles()
doc = SimpleDocTemplate(
output_path,
pagesize=A4,
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=MARGIN, bottomMargin=MARGIN,
title="GUARDiA ITSM GS인증 신청서",
author="(주)지오정보기술",
subject="GS인증 (Good Software) 1등급 신청",
)
story = []
W_CONTENT = W - 2 * MARGIN
# ── 표지 (Page 1) ─────────────────────────────────────────
# 파란 헤더 배너
header_table = Table(
[[Paragraph("GS인증 신청서", styles["cover_title"]),
Paragraph("Good Software Certification Application", styles["cover_sub"])]],
colWidths=[W_CONTENT],
)
header_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), NAVY),
("ROUNDEDCORNERS", (0, 0), (-1, -1), [8]),
("TOPPADDING", (0, 0), (-1, -1), 20),
("BOTTOMPADDING",(0, 0), (-1, -1), 20),
("LEFTPADDING", (0, 0), (-1, -1), 20),
("SPAN", (0,0), (0,-1)),
]))
story.append(header_table)
story.append(Spacer(1, 10*mm))
# 제품 로고 영역
logo_data = [
[Paragraph("GUARDiA ITSM", ParagraphStyle("logo", fontName=bold_font,
fontSize=36, textColor=BLUE, alignment=TA_CENTER))],
[Paragraph("AI 기반 레거시 인프라 자율 운영 플랫폼", ParagraphStyle("logo_sub",
fontName=base_font, fontSize=14, textColor=GRAY, alignment=TA_CENTER))],
[Spacer(1, 4*mm)],
[Paragraph("Version 2.0.0 | 2026년 9월 신청", ParagraphStyle("ver",
fontName=base_font, fontSize=11, textColor=GRAY, alignment=TA_CENTER))],
]
logo_table = Table(logo_data, colWidths=[W_CONTENT])
logo_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), LIGHT_BG),
("ROUNDEDCORNERS", (0,0), (-1,-1), [8]),
("TOPPADDING", (0,0), (-1,-1), 16),
("BOTTOMPADDING", (0,0), (-1,-1), 16),
]))
story.append(logo_table)
story.append(Spacer(1, 8*mm))
# 제출 정보 박스
submit_data = [
["제 출 처", "한국정보통신기술협회 (TTA) SW시험인증연구소"],
["신 청 등 급", "GS 1등급 (Good Software 1st Grade)"],
["신 청 일 자", "2026년 9월"],
["문 의 처", "031-724-0114 | sw@tta.or.kr"],
]
submit_table = Table(
[[Paragraph(k, styles["bold"]), Paragraph(v, styles["body"])]
for k, v in submit_data],
colWidths=[40*mm, W_CONTENT - 40*mm],
)
submit_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (0, -1), NAVY),
("BACKGROUND", (1, 0), (1, -1), WHITE),
("TEXTCOLOR", (0, 0), (0, -1), WHITE),
("FONTNAME", (0, 0), (0, -1), bold_font),
("FONTNAME", (1, 0), (1, -1), base_font),
("FONTSIZE", (0, 0), (-1, -1), 9.5),
("ALIGN", (0, 0), (0, -1), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING",(0, 0), (-1, -1), 6),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")),
]))
story.append(submit_table)
# 표지 하단
story.append(Spacer(1, 12*mm))
story.append(HRFlowable(width=W_CONTENT, color=BLUE, thickness=2))
story.append(Spacer(1, 4*mm))
story.append(Paragraph(
"(주)지오정보기술 | Copyright © 2026 All Rights Reserved.",
styles["footer"]
))
story.append(PageBreak())
# ── 1. 신청자 정보 ─────────────────────────────────────────
def section_header(title, subtitle=""):
data = [[Paragraph(f" {title}", styles["section"])]]
t = Table(data, colWidths=[W_CONTENT])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("ROUNDEDCORNERS", (0,0), (-1,-1), [6]),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
]))
return t
def info_table(data_rows, col_widths=None):
"""키-값 테이블 생성."""
if col_widths is None:
col_widths = [45*mm, W_CONTENT - 45*mm]
rows = []
for k, v in data_rows:
rows.append([
Paragraph(k, styles["bold"]),
Paragraph(v, styles["body"]),
])
t = Table(rows, colWidths=col_widths)
t.setStyle(TableStyle([
("BACKGROUND", (0, 0), (0, -1), LIGHT_BG),
("FONTNAME", (0, 0), (0, -1), bold_font),
("FONTSIZE", (0, 0), (-1, -1), 9.5),
("ALIGN", (0, 0), (0, -1), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING",(0, 0), (-1, -1), 6),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")),
]))
return t
story.append(section_header("1. 신청자 정보"))
story.append(Spacer(1, 4*mm))
story.append(info_table([
("회 사 명", "(주)지오정보기술"),
("대 표 자", "(대표이사명)"),
("사업자등록번호", "000-00-00000"),
("주 소", "서울특별시 (상세주소)"),
("전 화", "02-000-0000"),
("팩 스", "02-000-0001"),
("이 메 일", "gs@zioinfo.co.kr"),
("담 당 자", "(담당자명) / 개발팀장"),
("담당자 연락처", "010-0000-0000"),
]))
story.append(Spacer(1, 8*mm))
# ── 2. 소프트웨어 정보 ────────────────────────────────────
story.append(section_header("2. 소프트웨어 정보"))
story.append(Spacer(1, 4*mm))
story.append(info_table([
("제 품 명", "GUARDiA ITSM"),
("버 전", "2.0.0"),
("빌 드 일", "2026-08-31"),
("신청 등급", "GS 1등급"),
("제품 분류", "시스템 관리 소프트웨어"),
("세부 분류", "IT서비스관리(ITSM) / 인프라 자동화 플랫폼"),
("운영 환경", "서버 소프트웨어"),
("저작권 등록번호", "C-2026-XXXXXX (등록 후 기입)"),
("소프트웨어사업자", "제XXX호 (신고 후 기입)"),
]))
story.append(Spacer(1, 8*mm))
# ── 3. 제품 개요 ──────────────────────────────────────────
story.append(section_header("3. 제품 개요"))
story.append(Spacer(1, 4*mm))
# 제품 설명 박스
desc_text = (
"GUARDiA ITSM은 1,000개 이상 공공기관의 레거시 IT 인프라를 AI로 자율 운영하는 "
"온프레미스 통합 관리 플랫폼입니다. 메신저 한 줄 명령으로 에이전트 설치 없이 "
"SSH/SFTP를 통해 WAS 배포·운영을 완전 자동화하며, SR 관리, 인시던트 대응, "
"변경관리, CMDB, PMS 등 ITSM 전 기능을 단일 플랫폼에서 제공합니다."
)
desc_table = Table([[Paragraph(desc_text, styles["body"])]], colWidths=[W_CONTENT])
desc_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), LIGHT_BG),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("ROUNDEDCORNERS",(0,0), (-1,-1), [6]),
]))
story.append(desc_table)
story.append(Spacer(1, 5*mm))
# 핵심 기능
story.append(Paragraph("핵심 기능", styles["h2"]))
features = [
("1", "AI ChatOps", "메신저 25개 명령어로 인프라 실시간 제어"),
("2", "에이전트리스 배포", "SSH/SFTP 기반 — 대상 서버 소프트웨어 설치 불필요"),
("3", "통합 ITSM", "SR·인시던트·변경관리·SLA·CMDB 완비"),
("4", "PMS 프로젝트 관리", "WBS·산출물·일간/주간/월간 자동 보고서"),
("5", "보안 완전성", "JWT+MFA+AES-256-GCM+PAM+취약점스캔"),
]
feat_data = [[Paragraph(no, styles["body_center"]),
Paragraph(name, styles["bold"]),
Paragraph(desc, styles["body"])]
for no, name, desc in features]
feat_table = Table(feat_data, colWidths=[10*mm, 50*mm, W_CONTENT-60*mm])
feat_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (0, -1), NAVY),
("TEXTCOLOR", (0, 0), (0, -1), WHITE),
("FONTNAME", (0, 0), (0, -1), bold_font),
("BACKGROUND", (1, 0), (1, -1), LIGHT_BG),
("FONTSIZE", (0, 0), (-1, -1), 9.5),
("ALIGN", (0, 0), (0, -1), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING",(0, 0), (-1, -1), 5),
("LEFTPADDING", (0, 0), (-1, -1), 6),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")),
]))
story.append(feat_table)
story.append(Spacer(1, 5*mm))
# 지원 환경
story.append(Paragraph("지원 환경", styles["h2"]))
env_data = [
["서버 OS", "Ubuntu 20.04+, CentOS 7+, RHEL 8+, Windows Server 2019+"],
["브라우저", "Chrome 90+, Firefox 88+, Edge 90+"],
["최소 사양", "CPU 2코어, RAM 8GB, 디스크 20GB"],
]
env_table = Table(
[[Paragraph(k, styles["bold"]), Paragraph(v, styles["body"])]
for k, v in env_data],
colWidths=[30*mm, W_CONTENT-30*mm],
)
env_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), LIGHT_BG),
("FONTSIZE", (0,0), (-1,-1), 9.5),
("ALIGN", (0,0), (0,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#CBD5E1")),
]))
story.append(env_table)
story.append(PageBreak())
# ── 4. 시험 요청 항목 ──────────────────────────────────────
story.append(section_header("4. 시험 요청 항목 (ISO/IEC 25010)"))
story.append(Spacer(1, 4*mm))
qualities = [
("기능 적합성", "Functional Suitability", "", "기능 완전성, 정확성, 적절성"),
("성능 효율성", "Performance Efficiency", "", "동시 100명 기준 응답시간 3초 이내"),
("호 환 성", "Compatibility", "", "4개 OS, 주요 브라우저 지원"),
("사 용 성", "Usability", "", "웹접근성(KWCAG 2.1 AA) 포함"),
("신 뢰 성", "Reliability", "", "백업/복구, 에러 처리 포함"),
("보 안 성", "Security", "", "시큐어코딩 점검 내장"),
("유지보수성", "Maintainability", "", "버전 정보, 오류코드 체계 포함"),
("이 식 성", "Portability", "", "설치/제거 스크립트 완비"),
]
qual_header = [[
Paragraph("품질 특성", styles["section"]),
Paragraph("ISO 명칭", styles["section"]),
Paragraph("요청", styles["section"]),
Paragraph("비고", styles["section"]),
]]
qual_data = [
[Paragraph(k, styles["bold"]),
Paragraph(en, styles["small"]),
Paragraph(req, ParagraphStyle("check", fontName=bold_font, fontSize=12,
textColor=GREEN, alignment=TA_CENTER)),
Paragraph(note, styles["body"])]
for k, en, req, note in qualities
]
qual_table = Table(
qual_header + qual_data,
colWidths=[30*mm, 45*mm, 15*mm, W_CONTENT-90*mm],
)
qual_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), NAVY),
("TEXTCOLOR", (0, 0), (-1, 0), WHITE),
("BACKGROUND", (0, 1), (-1, -1), WHITE),
("ROWBACKGROUNDS",(0, 1), (-1, -1), [WHITE, LIGHT_BG]),
("FONTSIZE", (0, 0), (-1, -1), 9.5),
("ALIGN", (0, 0), (-1, 0), "CENTER"),
("ALIGN", (2, 1), (2, -1), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING",(0, 0), (-1, -1), 6),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")),
]))
story.append(qual_table)
story.append(Spacer(1, 8*mm))
# ── 5. 제출 서류 목록 ─────────────────────────────────────
story.append(section_header("5. 제출 서류 목록"))
story.append(Spacer(1, 4*mm))
docs = [
("필수", "GS인증 신청서", "본 문서"),
("필수", "사업자등록증 사본", "법인"),
("필수", "소프트웨어사업자 신고확인서", "과학기술정보통신부"),
("필수", "제품 기능 명세서", "certification/04_기술문서/ 참조"),
("필수", "사용자 매뉴얼 (PDF)", "관리자용 + 일반사용자용"),
("필수", "설치·제거 매뉴얼", "setup/uninstall.sh 포함"),
("필수", "소프트웨어 패키지", "설치 USB 또는 다운로드 링크"),
("필수", "저작권 등록증", "한국저작권위원회"),
("필수", "심사 수수료 납부 영수증", "약 650만원"),
("선택", "기술특허 출원서", "에이전트리스 자동화 방법"),
("선택", "GS이전버전 인증서", "없음 (신규 신청)"),
]
doc_header = [[
Paragraph("구분", styles["section"]),
Paragraph("서류명", styles["section"]),
Paragraph("비고", styles["section"]),
]]
doc_data = []
for kind, name, note in docs:
bg = GREEN_BG if kind == "필수" else LIGHT_BG
doc_data.append([
Paragraph(kind, ParagraphStyle("dk", fontName=bold_font, fontSize=9,
textColor=GREEN if kind=="필수" else GRAY, alignment=TA_CENTER)),
Paragraph(name, styles["bold"]),
Paragraph(note, styles["body"]),
])
doc_table = Table(
doc_header + doc_data,
colWidths=[18*mm, 65*mm, W_CONTENT-83*mm],
)
doc_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), NAVY),
("TEXTCOLOR", (0, 0), (-1, 0), WHITE),
("ROWBACKGROUNDS",(0, 1), (-1, -1), [WHITE, LIGHT_BG]),
("FONTSIZE", (0, 0), (-1, -1), 9.5),
("ALIGN", (0, 0), (-1, 0), "CENTER"),
("ALIGN", (0, 1), (0, -1), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING",(0, 0), (-1, -1), 5),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")),
]))
story.append(doc_table)
story.append(Spacer(1, 8*mm))
# ── 6. 수수료 ─────────────────────────────────────────────
story.append(section_header("6. 예상 수수료"))
story.append(Spacer(1, 4*mm))
fee_data = [
["심사 수수료", "5,000,000원"],
["시험 수수료", "1,500,000원"],
["합 계", "6,500,000원 (VAT 별도)"],
]
fee_table = Table(
[[Paragraph(k, styles["bold"]), Paragraph(v, styles["body"])]
for k, v in fee_data],
colWidths=[40*mm, W_CONTENT-40*mm],
)
fee_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (0, -1), LIGHT_BG),
("BACKGROUND", (0, 2), (-1, 2), LIGHT_BG),
("FONTNAME", (0, 2), (-1, 2), bold_font),
("FONTSIZE", (0, 0), (-1, -1), 9.5),
("ALIGN", (0, 0), (0, -1), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING",(0, 0), (-1, -1), 6),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")),
]))
story.append(fee_table)
story.append(Spacer(1, 5*mm))
story.append(Paragraph(
"※ 실제 금액은 TTA 접수 시 확정됩니다. 위 금액은 2026년 기준 추정치입니다.",
styles["note"]
))
story.append(Spacer(1, 10*mm))
# ── 7. 서약 및 서명 ───────────────────────────────────────
story.append(section_header("7. 서약 및 서명"))
story.append(Spacer(1, 6*mm))
pledge_text = (
"본 신청서에 기재한 내용이 사실과 다름이 없음을 확인하며, "
"한국정보통신기술협회(TTA)의 GS인증 심사 규정을 준수할 것을 서약합니다. "
"심사 중 제출 자료의 허위 기재 사실이 밝혀질 경우 인증이 취소될 수 있음을 인지합니다."
)
pledge_table = Table([[Paragraph(pledge_text, styles["body"])]], colWidths=[W_CONTENT])
pledge_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), LIGHT_BG),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
]))
story.append(pledge_table)
story.append(Spacer(1, 10*mm))
# 날짜 및 서명
sign_data = [
["신 청 일", "2026년 월 일", ""],
["", "", ""],
["회 사 명", "(주)지오정보기술", ""],
["대 표 자", "", " (인)"],
]
sign_table = Table(
[[Paragraph(k, styles["bold"]),
Paragraph(v, styles["body"]),
Paragraph(s, styles["body"])]
for k, v, s in sign_data],
colWidths=[30*mm, 90*mm, 30*mm],
)
sign_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (0, -1), LIGHT_BG),
("FONTSIZE", (0, 0), (-1, -1), 10),
("ALIGN", (0, 0), (0, -1), "CENTER"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 8),
("BOTTOMPADDING",(0, 0), (-1, -1), 8),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("LINEBELOW", (1, 3), (1, 3), 1, BLACK),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")),
]))
story.append(sign_table)
# ── 최종 푸터 ─────────────────────────────────────────────
story.append(Spacer(1, 12*mm))
story.append(HRFlowable(width=W_CONTENT, color=BLUE, thickness=1.5))
story.append(Spacer(1, 4*mm))
footer_data = [
Paragraph("한국정보통신기술협회 (TTA) | 경기도 성남시 분당구 양현로 322", styles["footer"]),
Paragraph("Tel: 031-724-0114 | E-mail: sw@tta.or.kr | www.tta.or.kr", styles["footer"]),
Spacer(1, 3*mm),
Paragraph(
f"본 문서는 GUARDiA ITSM GS인증 신청을 위해 (주)지오정보기술이 작성한 공식 신청서입니다. "
f"생성일: {datetime.now().strftime('%Y-%m-%d')}",
styles["footer"]
),
]
for item in footer_data:
story.append(item)
# ── PDF 빌드 ──────────────────────────────────────────────
def on_first_page(canvas, doc):
canvas.saveState()
canvas.restoreState()
def on_later_pages(canvas, doc):
canvas.saveState()
# 페이지 번호
canvas.setFont("Helvetica", 8)
canvas.setFillColor(GRAY)
canvas.drawRightString(W - MARGIN, 10*mm, f"- {doc.page} -")
canvas.drawString(MARGIN, 10*mm, "GUARDiA ITSM GS인증 신청서 | (주)지오정보기술")
canvas.restoreState()
doc.build(story, onFirstPage=on_first_page, onLaterPages=on_later_pages)
size_kb = Path(output_path).stat().st_size / 1024
print(f"\n[OK] PDF 생성 완료: {output_path}")
print(f" 파일 크기: {size_kb:.1f} KB")
if __name__ == "__main__":
cert_dir = Path(__file__).parent
output = cert_dir / "GS인증_신청서_GUARDiA_ITSM_v2.0.pdf"
import sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
print("GUARDiA ITSM GS인증 신청서 PDF 생성 중...")
build_pdf(str(output))