291 lines
10 KiB
Python
291 lines
10 KiB
Python
import os
|
|
import sys
|
|
import docx
|
|
from docx import Document
|
|
from docx.shared import Inches, Pt, RGBColor
|
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
from docx.enum.table import WD_TABLE_ALIGNMENT
|
|
from docx.oxml import parse_xml
|
|
from docx.oxml.ns import nsdecls
|
|
|
|
def set_cell_background(cell, hex_color):
|
|
tcPr = cell._tc.get_or_add_tcPr()
|
|
shd = parse_xml(f'<w:shd {nsdecls("w")} w:fill="{hex_color}"/>')
|
|
tcPr.append(shd)
|
|
|
|
def set_cell_margins(cell, top=100, bottom=100, left=150, right=150):
|
|
tcPr = cell._tc.get_or_add_tcPr()
|
|
tcMar = parse_xml(
|
|
f'<w:tcMar {nsdecls("w")}>'
|
|
f' <w:top w:w="{top}" w:type="dxa"/>'
|
|
f' <w:bottom w:w="{bottom}" w:type="dxa"/>'
|
|
f' <w:left w:w="{left}" w:type="dxa"/>'
|
|
f' <w:right w:w="{right}" w:type="dxa"/>'
|
|
f'</w:tcMar>'
|
|
)
|
|
tcPr.append(tcMar)
|
|
|
|
def set_table_borders(table, color="D0D7DE", sz="4", val="single"):
|
|
tblPr = table._tbl.tblPr
|
|
tblBorders = parse_xml(
|
|
f'<w:tblBorders {nsdecls("w")}>'
|
|
f' <w:top w:val="{val}" w:sz="{sz}" w:space="0" w:color="{color}"/>'
|
|
f' <w:bottom w:val="{val}" w:sz="{sz}" w:space="0" w:color="{color}"/>'
|
|
f' <w:left w:val="none"/>'
|
|
f' <w:right w:val="none"/>'
|
|
f' <w:insideH w:val="{val}" w:sz="{sz}" w:space="0" w:color="{color}"/>'
|
|
f' <w:insideV w:val="none"/>'
|
|
f'</w:tblBorders>'
|
|
)
|
|
tblPr.append(tblBorders)
|
|
|
|
def add_header_banner(doc, title, subtitle, doc_type="운영 매뉴얼", version="v1.1", date_str="2026년 8월"):
|
|
p_meta = doc.add_paragraph()
|
|
p_meta.paragraph_format.space_before = Pt(0)
|
|
p_meta.paragraph_format.space_after = Pt(4)
|
|
r_meta = p_meta.add_run(f"SMARTFARM HMI SYSTEM | {doc_type.upper()} | {version}")
|
|
r_meta.font.name = "Malgun Gothic"
|
|
r_meta.font.size = Pt(9)
|
|
r_meta.font.bold = True
|
|
r_meta.font.color.rgb = RGBColor(0x00, 0x77, 0x91)
|
|
|
|
p_title = doc.add_paragraph()
|
|
p_title.paragraph_format.space_before = Pt(4)
|
|
p_title.paragraph_format.space_after = Pt(6)
|
|
r_title = p_title.add_run(title)
|
|
r_title.font.name = "Malgun Gothic"
|
|
r_title.font.size = Pt(22)
|
|
r_title.font.bold = True
|
|
r_title.font.color.rgb = RGBColor(0x1B, 0x36, 0x5D)
|
|
|
|
p_sub = doc.add_paragraph()
|
|
p_sub.paragraph_format.space_before = Pt(0)
|
|
p_sub.paragraph_format.space_after = Pt(14)
|
|
r_sub = p_sub.add_run(subtitle)
|
|
r_sub.font.name = "Malgun Gothic"
|
|
r_sub.font.size = Pt(11)
|
|
r_sub.font.color.rgb = RGBColor(0x55, 0x55, 0x55)
|
|
|
|
tbl = doc.add_table(rows=2, cols=4)
|
|
tbl.alignment = WD_TABLE_ALIGNMENT.CENTER
|
|
set_table_borders(tbl, color="CCCCCC", sz="4")
|
|
|
|
meta_headers = ["문서명", "시스템명", "버전 / 작성일", "대상 플랫폼"]
|
|
meta_values = [doc_type, "SmartFarmHMI (FMX Multi-Platform)", f"{version} ({date_str})", "Windows, Android, iOS, macOS, Linux"]
|
|
|
|
for i in range(4):
|
|
c_h = tbl.cell(0, i)
|
|
c_h.text = meta_headers[i]
|
|
set_cell_background(c_h, "F0F4F8")
|
|
set_cell_margins(c_h, top=80, bottom=80, left=100, right=100)
|
|
p = c_h.paragraphs[0]
|
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
for r in p.runs:
|
|
r.font.name = "Malgun Gothic"
|
|
r.font.size = Pt(8.5)
|
|
r.font.bold = True
|
|
r.font.color.rgb = RGBColor(0x1B, 0x36, 0x5D)
|
|
|
|
c_v = tbl.cell(1, i)
|
|
c_v.text = meta_values[i]
|
|
set_cell_background(c_v, "FFFFFF")
|
|
set_cell_margins(c_v, top=80, bottom=80, left=100, right=100)
|
|
p = c_v.paragraphs[0]
|
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
for r in p.runs:
|
|
r.font.name = "Malgun Gothic"
|
|
r.font.size = Pt(8.5)
|
|
r.font.color.rgb = RGBColor(0x33, 0x33, 0x33)
|
|
|
|
col_w = [1.5, 2.0, 1.4, 1.6]
|
|
for row in tbl.rows:
|
|
for i, w in enumerate(col_w):
|
|
row.cells[i].width = Inches(w)
|
|
|
|
doc.add_paragraph().paragraph_format.space_after = Pt(12)
|
|
|
|
def add_heading_1(doc, text):
|
|
p = doc.add_paragraph()
|
|
p.paragraph_format.space_before = Pt(16)
|
|
p.paragraph_format.space_after = Pt(6)
|
|
p.paragraph_format.keep_with_next = True
|
|
r = p.add_run(text)
|
|
r.font.name = "Malgun Gothic"
|
|
r.font.size = Pt(14)
|
|
r.font.bold = True
|
|
r.font.color.rgb = RGBColor(0x1B, 0x36, 0x5D)
|
|
return p
|
|
|
|
def add_heading_2(doc, text):
|
|
p = doc.add_paragraph()
|
|
p.paragraph_format.space_before = Pt(12)
|
|
p.paragraph_format.space_after = Pt(4)
|
|
p.paragraph_format.keep_with_next = True
|
|
r = p.add_run(text)
|
|
r.font.name = "Malgun Gothic"
|
|
r.font.size = Pt(11.5)
|
|
r.font.bold = True
|
|
r.font.color.rgb = RGBColor(0x00, 0x77, 0x91)
|
|
return p
|
|
|
|
def add_heading_3(doc, text):
|
|
p = doc.add_paragraph()
|
|
p.paragraph_format.space_before = Pt(8)
|
|
p.paragraph_format.space_after = Pt(2)
|
|
p.paragraph_format.keep_with_next = True
|
|
r = p.add_run(text)
|
|
r.font.name = "Malgun Gothic"
|
|
r.font.size = Pt(10)
|
|
r.font.bold = True
|
|
r.font.color.rgb = RGBColor(0x33, 0x33, 0x33)
|
|
return p
|
|
|
|
def add_body_p(doc, text, bold_prefix=None):
|
|
p = doc.add_paragraph()
|
|
p.paragraph_format.space_before = Pt(2)
|
|
p.paragraph_format.space_after = Pt(4)
|
|
p.paragraph_format.line_spacing = 1.15
|
|
if bold_prefix:
|
|
r_bp = p.add_run(bold_prefix)
|
|
r_bp.font.name = "Malgun Gothic"
|
|
r_bp.font.size = Pt(9.5)
|
|
r_bp.font.bold = True
|
|
r_bp.font.color.rgb = RGBColor(0x1B, 0x36, 0x5D)
|
|
r = p.add_run(text)
|
|
r.font.name = "Malgun Gothic"
|
|
r.font.size = Pt(9.5)
|
|
r.font.color.rgb = RGBColor(0x33, 0x33, 0x33)
|
|
return p
|
|
|
|
def add_bullet_item(doc, text, bold_prefix=None, level=0):
|
|
p = doc.add_paragraph(style='List Bullet')
|
|
p.paragraph_format.space_before = Pt(1)
|
|
p.paragraph_format.space_after = Pt(2)
|
|
p.paragraph_format.line_spacing = 1.15
|
|
p.paragraph_format.left_indent = Inches(0.25 * (level + 1))
|
|
if bold_prefix:
|
|
r_bp = p.add_run(bold_prefix)
|
|
r_bp.font.name = "Malgun Gothic"
|
|
r_bp.font.size = Pt(9.5)
|
|
r_bp.font.bold = True
|
|
r_bp.font.color.rgb = RGBColor(0x22, 0x22, 0x22)
|
|
r = p.add_run(text)
|
|
r.font.name = "Malgun Gothic"
|
|
r.font.size = Pt(9.5)
|
|
r.font.color.rgb = RGBColor(0x33, 0x33, 0x33)
|
|
return p
|
|
|
|
def add_callout(doc, text, title="참고 / 주의사항", bg_color="F0F4F8", border_color="1B365D"):
|
|
tbl = doc.add_table(rows=1, cols=1)
|
|
tbl.alignment = WD_TABLE_ALIGNMENT.CENTER
|
|
tbl.autofit = False
|
|
|
|
cell = tbl.cell(0, 0)
|
|
cell.width = Inches(6.5)
|
|
set_cell_background(cell, bg_color)
|
|
set_cell_margins(cell, top=100, bottom=100, left=160, right=160)
|
|
|
|
tcPr = cell._tc.get_or_add_tcPr()
|
|
tcBorders = parse_xml(
|
|
f'<w:tcBorders {nsdecls("w")}>'
|
|
f' <w:top w:val="none"/>'
|
|
f' <w:left w:val="single" w:sz="24" w:space="0" w:color="{border_color}"/>'
|
|
f' <w:bottom w:val="none"/>'
|
|
f' <w:right w:val="none"/>'
|
|
f'</w:tcBorders>'
|
|
)
|
|
tcPr.append(tcBorders)
|
|
|
|
p = cell.paragraphs[0]
|
|
p.paragraph_format.space_before = Pt(2)
|
|
p.paragraph_format.space_after = Pt(2)
|
|
p.paragraph_format.line_spacing = 1.15
|
|
|
|
r_title = p.add_run(f"📌 {title}\n")
|
|
r_title.font.name = "Malgun Gothic"
|
|
r_title.font.size = Pt(9.5)
|
|
r_title.font.bold = True
|
|
r_title.font.color.rgb = RGBColor(0x1B, 0x36, 0x5D)
|
|
|
|
r_text = p.add_run(text)
|
|
r_text.font.name = "Malgun Gothic"
|
|
r_text.font.size = Pt(9)
|
|
r_text.font.color.rgb = RGBColor(0x33, 0x33, 0x33)
|
|
|
|
doc.add_paragraph().paragraph_format.space_after = Pt(4)
|
|
|
|
def add_code_block(doc, code_text):
|
|
tbl = doc.add_table(rows=1, cols=1)
|
|
tbl.alignment = WD_TABLE_ALIGNMENT.CENTER
|
|
tbl.autofit = False
|
|
|
|
cell = tbl.cell(0, 0)
|
|
cell.width = Inches(6.5)
|
|
set_cell_background(cell, "F8F9FA")
|
|
set_cell_margins(cell, top=100, bottom=100, left=140, right=140)
|
|
|
|
tcPr = cell._tc.get_or_add_tcPr()
|
|
tcBorders = parse_xml(
|
|
f'<w:tcBorders {nsdecls("w")}>'
|
|
f' <w:top w:val="single" w:sz="4" w:space="0" w:color="E0E0E0"/>'
|
|
f' <w:left w:val="single" w:sz="16" w:space="0" w:color="4A90E2"/>'
|
|
f' <w:bottom w:val="single" w:sz="4" w:space="0" w:color="E0E0E0"/>'
|
|
f' <w:right w:val="single" w:sz="4" w:space="0" w:color="E0E0E0"/>'
|
|
f'</w:tcBorders>'
|
|
)
|
|
tcPr.append(tcBorders)
|
|
|
|
p = cell.paragraphs[0]
|
|
p.paragraph_format.space_before = Pt(2)
|
|
p.paragraph_format.space_after = Pt(2)
|
|
p.paragraph_format.line_spacing = 1.1
|
|
|
|
r = p.add_run(code_text)
|
|
r.font.name = "Consolas"
|
|
r.font.size = Pt(8.5)
|
|
r.font.color.rgb = RGBColor(0x22, 0x22, 0x22)
|
|
|
|
doc.add_paragraph().paragraph_format.space_after = Pt(4)
|
|
|
|
def format_table(table, col_widths, headers, data, align_cols=None):
|
|
table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
|
set_table_borders(table, color="D0D7DE", sz="6")
|
|
|
|
# Header Row
|
|
hdr_cells = table.rows[0].cells
|
|
for i, title in enumerate(headers):
|
|
hdr_cells[i].text = title
|
|
set_cell_background(hdr_cells[i], "1B365D")
|
|
set_cell_margins(hdr_cells[i], top=100, bottom=100, left=120, right=120)
|
|
p = hdr_cells[i].paragraphs[0]
|
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
for r in p.runs:
|
|
r.font.name = "Malgun Gothic"
|
|
r.font.size = Pt(9)
|
|
r.font.bold = True
|
|
r.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
|
|
|
|
# Data Rows
|
|
for row_idx, row_data in enumerate(data):
|
|
row_cells = table.add_row().cells
|
|
bg_c = "F8F9FB" if row_idx % 2 == 1 else "FFFFFF"
|
|
for col_idx, text in enumerate(row_data):
|
|
row_cells[col_idx].text = str(text)
|
|
set_cell_background(row_cells[col_idx], bg_c)
|
|
set_cell_margins(row_cells[col_idx], top=80, bottom=80, left=120, right=120)
|
|
p = row_cells[col_idx].paragraphs[0]
|
|
if align_cols and col_idx < len(align_cols):
|
|
p.alignment = align_cols[col_idx]
|
|
else:
|
|
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
for r in p.runs:
|
|
r.font.name = "Malgun Gothic"
|
|
r.font.size = Pt(8.5)
|
|
r.font.color.rgb = RGBColor(0x33, 0x33, 0x33)
|
|
|
|
for row in table.rows:
|
|
for i, w in enumerate(col_widths):
|
|
row.cells[i].width = Inches(w)
|
|
|
|
print("Base helper updated.")
|