Files
_Geulbeot/03.Code/업로드용/converters/hwp_style_mapping.py
2026-03-19 09:02:27 +09:00

418 lines
13 KiB
Python

# -*- 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}")