# -*- 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/7200 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'}, 'right': {'type': 'SOLID', 'width': '0.12mm', 'color': '#000000'}, 'top': {'type': 'SOLID', 'width': '0.12mm', 'color': '#000000'}, 'bottom': {'type': 'SOLID', 'width': '0.12mm', 'color': '#000000'}, 'background': '#E8F5E9' or None, } Returns: { 'border-left': '0.12mm solid #000000', 'border-right': '0.12mm solid #000000', 'border-top': '0.12mm solid #000000', 'border-bottom': '0.12mm solid #000000', 'background-color': '#E8F5E9', } """ css = {} for side in ['left', 'right', 'top', 'bottom']: border = bf_element_attrs.get(side, {}) css[f'border-{side}'] = hwpx_border_to_css(border) bg = bf_element_attrs.get('background') if bg and bg != 'none': css['background-color'] = bg return css def hwpx_align_to_css(hwpx_align): """HWPX 정렬 값 → CSS text-align""" return ALIGN_TO_CSS.get(hwpx_align, 'left') def css_align_to_hwpx(css_align): """CSS text-align → HWPX 정렬 값""" return CSS_TO_ALIGN.get(css_align, 'LEFT') def hwpx_line_spacing_to_css(spacing_type, spacing_value): """HWPX 줄 간격 → CSS line-height Args: spacing_type: 'PERCENT' | 'FIXED' | 'BETWEEN_LINES' | 'AT_LEAST' spacing_value: 숫자값 Returns: CSS line-height 문자열 (예: '160%', '24pt') """ if spacing_type == 'PERCENT': return f'{spacing_value}%' elif spacing_type == 'FIXED': pt = hwpunit_to_pt(spacing_value) return f'{pt:.1f}pt' else: return f'{spacing_value}%' # 기본 근사 # ================================================================ # 용지 크기 사전 정의 (§7.1) # ================================================================ PAPER_SIZES = { 'A4': {'width_mm': 210, 'height_mm': 297, 'width_hu': 59528, 'height_hu': 84188}, 'A3': {'width_mm': 297, 'height_mm': 420, 'width_hu': 84188, 'height_hu': 119055}, 'B5': {'width_mm': 176, 'height_mm': 250, 'width_hu': 49896, 'height_hu': 70866}, 'Letter': {'width_mm': 215.9, 'height_mm': 279.4, 'width_hu': 61200, 'height_hu': 79200}, 'Legal': {'width_mm': 215.9, 'height_mm': 355.6, 'width_hu': 61200, 'height_hu': 100800}, } def detect_paper_size(width_hu, height_hu, tolerance=200): """HWPUNIT 용지 크기 → 용지 이름 추정 Args: width_hu: 가로 크기 (HWPUNIT) height_hu: 세로 크기 (HWPUNIT) tolerance: 허용 오차 (HWPUNIT) Returns: 'A4', 'A3', 'Letter' 등 또는 'custom' """ for name, size in PAPER_SIZES.items(): if (abs(width_hu - size['width_hu']) <= tolerance and abs(height_hu - size['height_hu']) <= tolerance): return name # landscape 체크 if (abs(width_hu - size['height_hu']) <= tolerance and abs(height_hu - size['width_hu']) <= tolerance): return f'{name}_landscape' return 'custom' # ================================================================ # 편의 함수 # ================================================================ def css_style_string(css_dict): """CSS dict → CSS style 문자열 예: {'border-left': '1px solid #000', 'padding': '5mm'} → 'border-left: 1px solid #000; padding: 5mm;' """ return ' '.join(f'{k}: {v};' for k, v in css_dict.items() if v) def mm_format(hwpunit, decimal=1): """HWPUNIT → 'Xmm' 포맷 문자열""" mm = hwpunit_to_mm(hwpunit) return f'{mm:.{decimal}f}mm'