diff --git a/certification/GS인증_신청서_GUARDiA_ITSM_v2.0.pdf b/certification/GS인증_신청서_GUARDiA_ITSM_v2.0.pdf new file mode 100644 index 00000000..6225c040 Binary files /dev/null and b/certification/GS인증_신청서_GUARDiA_ITSM_v2.0.pdf differ diff --git a/certification/generate_pdf.py b/certification/generate_pdf.py new file mode 100644 index 00000000..97af3e92 --- /dev/null +++ b/certification/generate_pdf.py @@ -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))