From 3b8ff63a935b15c8f9f00eb4228c9d8c8c95d810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B2=BD=EB=AF=BC?= Date: Thu, 19 Mar 2026 09:02:27 +0900 Subject: [PATCH] Upload hwp_style_mapping.py --- .../업로드용/converters/hwp_style_mapping.py | 417 ++++++++++++++++++ 1 file changed, 417 insertions(+) create mode 100644 03.Code/업로드용/converters/hwp_style_mapping.py diff --git a/03.Code/업로드용/converters/hwp_style_mapping.py b/03.Code/업로드용/converters/hwp_style_mapping.py new file mode 100644 index 0000000..ad9db68 --- /dev/null +++ b/03.Code/업로드용/converters/hwp_style_mapping.py @@ -0,0 +1,417 @@ +# -*- coding: utf-8 -*- +""" +HWP ㅽ 紐 v2.0 +HTML (Role) HWP ㅽ + + + v2.0 蹂寃쎌ы: +- pyhwpx API apply_to_hwp() + +- CharShape/ParaShape 吏 + ㅼ 諛⑹ 媛 ㅽ +from dataclasses import dataclass +from typing import Dict, Optional +from enum import Enum + + +class HwpStyleType(Enum): + """HWP ㅽ """ + PARAGRAPH = "paragraph" + CHARACTER = "character" + + +@dataclass +class HwpStyle: + """HWP ㅽ + + + + + + + + + + + + + + + +# ============================================================================= +# 湲곕낯 ㅽ + +# ============================================================================= +DEFAULT_STYLES: Dict[str, HwpStyle] = { + # 吏 + "COVER_TITLE": HwpStyle( + id=100, name="吏紐", type=HwpStyleType.PARAGRAPH, + font_size=32, font_bold=True, align="center", + space_before=20, space_after=10, font_color="1a365d" + ), + "COVER_SUBTITLE": HwpStyle( + id=101, name="吏遺", type=HwpStyleType.PARAGRAPH, + font_size=18, font_bold=False, align="center", + font_color="555555" + ), + "COVER_INFO": HwpStyle( + id=102, name="吏 蹂", type=HwpStyleType.PARAGRAPH, + font_size=12, align="center", font_color="666666" + ), + + # 紐⑹감 + "TOC_H1": HwpStyle( + id=110, name="紐⑹감1", type=HwpStyleType.PARAGRAPH, + font_size=12, font_bold=True, indent_left=0 + ), + "TOC_H2": HwpStyle( + id=111, name="紐⑹감2", type=HwpStyleType.PARAGRAPH, + font_size=11, indent_left=20 + ), + "TOC_H3": HwpStyle( + id=112, name="紐⑹감3", type=HwpStyleType.PARAGRAPH, + font_size=10, indent_left=40, font_color="666666" + ), + + # 紐 + 痢 (媛 1~7 留ㅽ) + "H1": HwpStyle( + id=1, name="媛 1", type=HwpStyleType.PARAGRAPH, + font_size=20, font_bold=True, align="left", + space_before=30, space_after=15, font_color="1a365d" + ), + "H2": HwpStyle( + id=2, name="媛 2", type=HwpStyleType.PARAGRAPH, + font_size=16, font_bold=True, align="left", + space_before=20, space_after=10, font_color="2c5282" + ), + "H3": HwpStyle( + id=3, name="媛 3", type=HwpStyleType.PARAGRAPH, + font_size=14, font_bold=True, align="left", + space_before=15, space_after=8, font_color="2b6cb0" + ), + "H4": HwpStyle( + id=4, name="媛 4", type=HwpStyleType.PARAGRAPH, + font_size=12, font_bold=True, align="left", + space_before=10, space_after=5, indent_left=10 + ), + "H5": HwpStyle( + id=5, name="媛 5", type=HwpStyleType.PARAGRAPH, + font_size=11, font_bold=True, align="left", + space_before=8, space_after=4, indent_left=20 + ), + "H6": HwpStyle( + id=6, name="媛 6", type=HwpStyleType.PARAGRAPH, + font_size=11, font_bold=False, align="left", + indent_left=30 + ), + "H7": HwpStyle( + id=7, name="媛 7", type=HwpStyleType.PARAGRAPH, + font_size=10.5, font_bold=False, align="left", + indent_left=40 + ), + + # 蹂몃Ц + "BODY": HwpStyle( + id=20, name="諛湲", type=HwpStyleType.PARAGRAPH, + font_size=11, align="justify", + line_spacing=180, indent_first=10 + ), + "LIST_ITEM": HwpStyle( + id=8, name="媛 8", type=HwpStyleType.PARAGRAPH, + font_size=11, align="left", + indent_left=15, line_spacing=160 + ), + "HIGHLIGHT_BOX": HwpStyle( + id=21, name="媛議ꕕ", type=HwpStyleType.PARAGRAPH, + font_size=10.5, align="left", + bg_color="f7fafc", indent_left=10, indent_first=0 + ), + + # + "TABLE": HwpStyle( + id=30, name=" ", type=HwpStyleType.PARAGRAPH, + font_size=10, align="center" + ), + "TH": HwpStyle( + id=11, name="紐", type=HwpStyleType.PARAGRAPH, + font_size=10, font_bold=True, align="center", + bg_color="e2e8f0" + ), + "TD": HwpStyle( + id=31, name=" 댁 ", type=HwpStyleType.PARAGRAPH, + font_size=10, align="left" + ), + "TABLE_CAPTION": HwpStyle( + id=19, name=" 罹 +몃┝ + "FIGURE": HwpStyle( + id=32, name="洹몃┝", type=HwpStyleType.PARAGRAPH, + font_size=10, align="center" + ), + "FIGURE_CAPTION": HwpStyle( + id=18, name="洹몃┝罹 +고 + "UNKNOWN": HwpStyle( + id=0, name="諛湲", type=HwpStyleType.PARAGRAPH, + font_size=10, align="left" + ), +} + +# 媛 踰 留㼼 (StyleShortcut ) +ROLE_TO_OUTLINE_NUM = { + "H1": 1, + "H2": 2, + "H3": 3, + "H4": 4, + "H5": 5, + "H6": 6, + "H7": 7, + "LIST_ITEM": 8, + "BODY": 0, # 諛湲 + "COVER_TITLE": 0, + "COVER_SUBTITLE": 0, + "COVER_INFO": 0, +} + +# HWP ㅽ + 留㼼 +ROLE_TO_STYLE_NAME = { + "H1": "媛 1", + "H2": "媛 2", + "H3": "媛 3", + "H4": "媛 4", + "H5": "媛 5", + "H6": "媛 6", + "H7": "媛 7", + "LIST_ITEM": "媛 8", + "BODY": "諛湲", + "COVER_TITLE": "吏紐", + "COVER_SUBTITLE": "吏遺", + "TH": "紐", + "TD": " 댁 ", + "TABLE_CAPTION": " 罹 +몃┝罹 + 湲", +} + + +class HwpStyleMapper: + """HTML HWP ㅽ """ + + def __init__(self, custom_styles: Optional[Dict[str, HwpStyle]] = None): + self.styles = DEFAULT_STYLES.copy() + if custom_styles: + self.styles.update(custom_styles) + + def get_style(self, role: str) -> HwpStyle: + return self.styles.get(role, self.styles["UNKNOWN"]) + + def get_style_id(self, role: str) -> int: + return self.get_style(role).id + + def get_all_styles(self) -> Dict[str, HwpStyle]: + return self.styles + + +class HwpStyGenerator: + """ + HTML ㅽ HWP ㅽ⑷린 + + pyhwpx API瑜 ъ : + 1. 蹂 + ㅽ蹂 + 2. + ㅽ 쎌 + CharShape/ParaShape 吏 + 3. 媛 ㅽ 留㼼 諛 + 異異 ㅽ + 곗 """ + for role, style_dict in html_styles.items(): + if role in DEFAULT_STYLES: + base = DEFAULT_STYLES[role] + + # color 泥 - # 嫄 + color = style_dict.get('color', base.font_color) + if isinstance(color, str): + color = color.lstrip('#') + + self.styles[role] = HwpStyle( + id=base.id, + name=base.name, + type=base.type, + font_size=style_dict.get('font_size', base.font_size), + font_bold=style_dict.get('bold', base.font_bold), + font_color=color, + align=style_dict.get('align', base.align), + line_spacing=style_dict.get('line_spacing', base.line_spacing), + space_before=style_dict.get('space_before', base.space_before), + space_after=style_dict.get('space_after', base.space_after), + indent_left=style_dict.get('indent_left', base.indent_left), + indent_first=style_dict.get('indent_first', base.indent_first), + bg_color=style_dict.get('bg_color', base.bg_color), + ) + else: + # 湲곕낯 ㅽ ъ + self.styles[role] = DEFAULT_STYLES.get('UNKNOWN') + + # + 대 湲곕낯媛쇰 梨 + + for role in DEFAULT_STYLES: + if role not in self.styles: + self.styles[role] = DEFAULT_STYLES[role] + + def apply_to_hwp(self, hwp) -> Dict[str, HwpStyle]: + """ HwpStyle 留㼼 諛 ㅽ + 鍮 + + 깊 (API 臾몄 ) + # for role, style in self.styles.items(): + # self._create_or_update_style(hwp, role, style) + + if not self.styles: + self.styles = DEFAULT_STYLES.copy() + + print(f" + ㅽ + 猷: {len(self.styles)}媛") + return self.styles + + def _create_or_update_style(self, hwp, role: str, style: HwpStyle): + """HWP ㅽ + """ + try: + # 1. ㅽ 紐⑤ + hwp.HAction.GetDefault("ModifyStyle", hwp.HParameterSet.HStyle.HSet) + hwp.HParameterSet.HStyle.StyleName = style.name + + # 2. 湲 ⑥ + color_hex = style.font_color.lstrip('#') + if len(color_hex) == 6: + r, g, b = int(color_hex[0:2], 16), int(color_hex[2:4], 16), int(color_hex[4:6], 16) + text_color = hwp.RGBColor(r, g, b) + else: + text_color = hwp.RGBColor(0, 0, 0) + + hwp.HParameterSet.HStyle.CharShape.Height = hwp.PointToHwpUnit(style.font_size) + hwp.HParameterSet.HStyle.CharShape.Bold = style.font_bold + hwp.HParameterSet.HStyle.CharShape.TextColor = text_color + + # 3. 臾몃 紐⑥ + align_map = {'left': 0, 'center': 1, 'right': 2, 'justify': 3} + hwp.HParameterSet.HStyle.ParaShape.Align = align_map.get(style.align, 3) + hwp.HParameterSet.HStyle.ParaShape.LineSpacing = int(style.line_spacing) + hwp.HParameterSet.HStyle.ParaShape.SpaceBeforePara = hwp.PointToHwpUnit(style.space_before) + hwp.HParameterSet.HStyle.ParaShape.SpaceAfterPara = hwp.PointToHwpUnit(style.space_after) + + # 4. ㅽ + hwp.HAction.Execute("ModifyStyle", hwp.HParameterSet.HStyle.HSet) + print(f" ㅽ ") + + except Exception as e: + print(f" [寃쎄 ] ㅽ + ㅽ : {e}") + + def get_style(self, role: str) -> HwpStyle: + """대 ㅽ + + ⑥ """ + style = self.get_style(role) + + try: + # RGB 蹂 ⑥ + ㅼ + hwp.HAction.GetDefault("CharShape", hwp.HParameterSet.HCharShape.HSet) + hwp.HParameterSet.HCharShape.Height = hwp.PointToHwpUnit(style.font_size) + hwp.HParameterSet.HCharShape.Bold = style.font_bold + hwp.HParameterSet.HCharShape.TextColor = text_color + hwp.HAction.Execute("CharShape", hwp.HParameterSet.HCharShape.HSet) + + except Exception as e: + print(f" [寃쎄 ] 湲 ⑥ ㅽ ({role}): {e}") + + def apply_para_shape(self, hwp, role: str): + """ + 臾몃 紐⑥ """ + style = self.get_style(role) + + try: + # + align_actions = { + 'left': "ParagraphShapeAlignLeft", + 'center': "ParagraphShapeAlignCenter", + 'right': "ParagraphShapeAlignRight", + 'justify': "ParagraphShapeAlignJustify" + } + if style.align in align_actions: + hwp.HAction.Run(align_actions[style.align]) + + # 臾몃 紐⑥ + + ㅼ + hwp.HAction.GetDefault("ParagraphShape", hwp.HParameterSet.HParaShape.HSet) + p = hwp.HParameterSet.HParaShape + p.LineSpaceType = 0 # 쇱 + 쇳 + p.LineSpacing = int(style.line_spacing) + p.LeftMargin = hwp.MiliToHwpUnit(style.indent_left) + p.IndentMargin = hwp.MiliToHwpUnit(style.indent_first) + p.SpaceBeforePara = hwp.PointToHwpUnit(style.space_before) + p.SpaceAfterPara = hwp.PointToHwpUnit(style.space_after) + hwp.HAction.Execute("ParagraphShape", p.HSet) + + except Exception as e: + print(f" [寃쎄 ] 臾몃 紐⑥ ㅽ ({role}): {e}") + + def apply_style(self, hwp, role: str): + """ + 泥 ㅽ (湲몃 )""" + self.apply_char_shape(hwp, role) + self.apply_para_shape(hwp, role) + + def export_sty(self, hwp, output_path: str) -> bool: + """ㅽ대낫닿린 ( + 誘몄由] .sty 대낫닿린 + 誘몄 嫄 대━ +# ============================================================================= +import re + +NUMBERING_PATTERNS = { + 'H1': re.compile(r'^(\d+)\.\s*'), # "1. " 嫄 + 'H2': re.compile(r'^(\d+)\.(\d+)\s*'), # "1.1 " 嫄 + 'H3': re.compile(r'^(\d+)\.(\d+)\.(\d+)\s*'), # "1.1.1 " 嫄 + 'H4': re.compile(r'^[媛- . " 嫄 + 'H5': re.compile(r'^(\d+)\)\s*'), # "1) " 嫄 + 'H6': re.compile(r'^\((\d+)\)\s*'), # "(1) " 嫄 + 'H7': re.compile(r'^[△™bㅲβ╈㎮ⓥ]\s*'), # " " 嫄 + 'LIST_ITEM': re.compile(r'^[\-]\s*'), # " " 嫄 +} + +def strip_numbering(text: str, role: str) -> str: + """ + 곕 +ㅽ /湲고 嫄 + HWP 媛 湲곕μ + 깊濡 以蹂 諛⑹ + """ + if not text: + return text + + pattern = NUMBERING_PATTERNS.get(role) + if pattern: + return pattern.sub('', text).strip() + + return text.strip() + + +if __name__ == "__main__": + # +ㅽ + print("=== ㅽ + ㅽ ===") + + gen = HwpStyGenerator() + + # HTML ㅽ裕щ + size={style.font_size}pt, bold={style.font_bold}, color=#{style.font_color}")