diff --git a/03.Code/geulbeot_업로드용/domain/hwpx/hwpx_utils.py b/03.Code/geulbeot_업로드용/domain/hwpx/hwpx_utils.py new file mode 100644 index 0000000..b71fbbb --- /dev/null +++ b/03.Code/geulbeot_업로드용/domain/hwpx/hwpx_utils.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- +""" +HWP/HWPX ↔ HTML/CSS 변환 유틸리티 + +hwpx_domain_guide.md의 매핑 테이블을 코드화. +하드코딩 없이 이 모듈의 함수/상수를 참조하여 정확한 변환을 수행한다. + +참조: 한글과컴퓨터 "글 문서 파일 구조 5.0" (revision 1.3, 2018-11-08) +""" + + +# ================================================================ +# §1. 단위 변환 +# ================================================================ + +def hwpunit_to_mm(hwpunit): + """HWPUNIT → mm (1 HWPUNIT = 1/720 inch)""" + return hwpunit / 7200 * 25.4 + +def hwpunit_to_pt(hwpunit): + """HWPUNIT → pt""" + return hwpunit / 7200 * 72 + +def hwpunit_to_px(hwpunit, dpi=96): + """HWPUNIT → px (기본 96dpi)""" + return hwpunit / 7200 * dpi + +def mm_to_hwpunit(mm): + """mm → HWPUNIT""" + return mm / 25.4 * 7200 + +def pt_to_hwpunit(pt): + """pt → HWPUNIT""" + return pt / 72 * 7200 + +def px_to_hwpunit(px, dpi=96): + """px → HWPUNIT""" + return px / dpi * 7200 + +def charsize_to_pt(hwp_size): + """HWP 글자 크기 → pt (100 스케일 제거) + 예: 1000 → 10pt, 2400 → 24pt + """ + return hwp_size / 100 + +def pt_to_charsize(pt): + """pt → HWP 글자 크기 + 예: 10pt → 1000, 24pt → 2400 + """ + return int(pt * 100) + + +# ================================================================ +# §1.3 색상 변환 +# ================================================================ + +def colorref_to_css(colorref): + """HWP COLORREF (0x00BBGGRR) → CSS #RRGGBB + + HWP는 리틀 엔디안 BGR 순서: + - 0x00FF0000 → B=255,G=0,R=0 → #0000ff (파랑) + - 0x000000FF → B=0,G=0,R=255 → #ff0000 (빨강) + """ + r = colorref & 0xFF + g = (colorref >> 8) & 0xFF + b = (colorref >> 16) & 0xFF + return f'#{r:02x}{g:02x}{b:02x}' + +def css_to_colorref(css_color): + """CSS #RRGGBB → HWP COLORREF (0x00BBGGRR)""" + css_color = css_color.lstrip('#') + if len(css_color) == 3: # 단축형 #rgb → #rrggbb + css_color = ''.join(c * 2 for c in css_color) + r = int(css_color[0:2], 16) + g = int(css_color[2:4], 16) + b = int(css_color[4:6], 16) + return (b << 16) | (g << 8) | r + + +# ================================================================ +# §2. 테두리/배경 (BorderFill) 매핑 +# ================================================================ + +# §2.1 테두리선 종류: HWPX type → CSS border-style +BORDER_TYPE_TO_CSS = { + 'NONE': 'none', + 'SOLID': 'solid', + 'DASH': 'dashed', + 'DOT': 'dotted', + 'DASH_DOT': 'dashed', # CSS 근사 + 'DASH_DOT_DOT': 'dashed', # CSS 근사 + 'LONG_DASH': 'dashed', + 'CIRCLE': 'dotted', # CSS 근사 (큰 동그라미 → dot) + 'DOUBLE': 'double', + 'THIN_THICK': 'double', # CSS 근사 + 'THICK_THIN': 'double', # CSS 근사 + 'THIN_THICK_THIN':'double', # CSS 근사 + 'WAVE': 'solid', # CSS 근사 (물결 → 실선) + 'DOUBLE_WAVE': 'double', # CSS 근사 + 'THICK_3D': 'ridge', + 'THICK_3D_REV': 'groove', + '3D': 'outset', + '3D_REV': 'inset', +} + +# CSS border-style → HWPX type (역방향) +CSS_TO_BORDER_TYPE = { + 'none': 'NONE', + 'solid': 'SOLID', + 'dashed': 'DASH', + 'dotted': 'DOT', + 'double': 'DOUBLE', + 'ridge': 'THICK_3D', + 'groove': 'THICK_3D_REV', + 'outset': '3D', + 'inset': '3D_REV', +} + +# §2.2 HWP 바이너리 테두리 굵기 값 → 실제 mm +BORDER_WIDTH_HWP_TO_MM = { + 0: 0.1, 1: 0.12, 2: 0.15, 3: 0.2, 4: 0.25, 5: 0.3, + 6: 0.4, 7: 0.5, 8: 0.6, 9: 0.7, 10: 1.0, 11: 1.5, + 12: 2.0, 13: 3.0, 14: 4.0, 15: 5.0, +} + + +# ================================================================ +# §5. 정렬 매핑 +# ================================================================ + +# §5.1 HWPX align → CSS text-align +ALIGN_TO_CSS = { + 'JUSTIFY': 'justify', + 'LEFT': 'left', + 'RIGHT': 'right', + 'CENTER': 'center', + 'DISTRIBUTE': 'justify', # CSS 근사 + 'DISTRIBUTE_SPACE': 'justify', # CSS 근사 +} + +# CSS text-align → HWPX align (역방향) +CSS_TO_ALIGN = { + 'justify': 'JUSTIFY', + 'left': 'LEFT', + 'right': 'RIGHT', + 'center': 'CENTER', +} + + +# ================================================================ +# §5.2 줄 간격 매핑 +# ================================================================ + +LINE_SPACING_TYPE_TO_CSS = { + 'PERCENT': 'percent', # 글자에 따라 (%) → line-height: 160% + 'FIXED': 'fixed', # 고정값 → line-height: 24pt + 'BETWEEN_LINES': 'between', # 여백만 지정 + 'AT_LEAST': 'at_least', # 최소 +} + + +# ================================================================ +# 종합 변환 함수 +# ================================================================ + +def hwpx_border_to_css(border_attrs): + """HWPX 테두리 속성 dict → CSS border 문자열 + + Args: + border_attrs: {'type': 'SOLID', 'width': '0.12mm', 'color': '#000000'} + + Returns: + '0.12mm solid #000000' 또는 'none' + """ + btype = border_attrs.get('type', 'NONE') + if btype == 'NONE' or btype is None: + return 'none' + + width = border_attrs.get('width', '0.12mm') + color = border_attrs.get('color', '#000000') + css_style = BORDER_TYPE_TO_CSS.get(btype, 'solid') + + return f'{width} {css_style} {color}' + + +def css_border_to_hwpx(css_border): + """CSS border 문자열 → HWPX 속성 dict + + Args: + css_border: '0.12mm solid #000000' 또는 'none' + + Returns: + {'type': 'SOLID', 'width': '0.12mm', 'color': '#000000'} + """ + if not css_border or css_border.strip() == 'none': + return {'type': 'NONE', 'width': '0mm', 'color': '#000000'} + + parts = css_border.strip().split() + width = parts[0] if len(parts) > 0 else '0.12mm' + style = parts[1] if len(parts) > 1 else 'solid' + color = parts[2] if len(parts) > 2 else '#000000' + + return { + 'type': CSS_TO_BORDER_TYPE.get(style, 'SOLID'), + 'width': width, + 'color': color, + } + + +def hwpx_borderfill_to_css(bf_element_attrs): + """HWPX 전체 속성 → CSS dict + + Args: + bf_element_attrs: { + 'left': {'type': 'SOLID', 'width': '0.12mm', 'color': '#000000'}, + ... + } + """ + return { + 'border-left': hwpx_border_to_css(bf_element_attrs.get('left', {})), + 'border-right': hwpx_border_to_css(bf_element_attrs.get('right', {})), + 'border-top': hwpx_border_to_css(bf_element_attrs.get('top', {})), + 'border-bottom': hwpx_border_to_css(bf_element_attrs.get('bottom', {})), + }