claude-code-hwp-mcp 0.3.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -35,18 +35,30 @@ def insert_text_with_color(hwp, text, rgb=None):
35
35
  def insert_text_with_style(hwp, text, style=None):
36
36
  """서식 지정 텍스트 삽입.
37
37
  style: {
38
- "color": [r,g,b], # 글자 색상
39
- "bold": True/False, # 굵게
40
- "italic": True/False, # 기울임
41
- "underline": True/False, # 밑줄
42
- "font_size": 12.0, # 글자 크기 (pt)
43
- "font_name": "맑은 고딕", # 글꼴
44
- "bg_color": [r,g,b], # 배경 색상
45
- "strikeout": True/False, # 취소선
46
- "char_spacing": -5, # 자간 (%, 기본 0)
47
- "width_ratio": 90, # 장평 (%, 기본 100)
48
- "font_name_hanja": "바탕",# 한자 글꼴
49
- "font_name_japanese": "", # 일본어 글꼴
38
+ "color": [r,g,b], # 글자 색상
39
+ "bold": True/False, # 굵게
40
+ "italic": True/False, # 기울임
41
+ "underline": True/False, # 밑줄 (bool → 실선)
42
+ "underline_type": 0-7, # 밑줄 종류 (0=없음,1=실선,2=이중,3=점선,4=파선,5=1점쇄선,6=물결,7=굵은실선)
43
+ "underline_color": [r,g,b], # 밑줄 색상
44
+ "font_size": 12.0, # 글자 크기 (pt)
45
+ "font_name": "맑은 고딕", # 글꼴 (한글+라틴 동시)
46
+ "font_name_latin": "Arial", # 라틴 전용 글꼴
47
+ "bg_color": [r,g,b], # 배경 색상
48
+ "strikeout": True/False, # 취소선 (bool → 단일)
49
+ "strikeout_type": 0-3, # 취소선 종류 (0=없음,1=단일,2=이중,3=굵은)
50
+ "strikeout_color": [r,g,b], # 취소선 색상
51
+ "char_spacing": -5, # 자간 (%, 기본 0)
52
+ "width_ratio": 90, # 장평 (%, 기본 100)
53
+ "font_name_hanja": "바탕", # 한자 글꼴
54
+ "font_name_japanese": "", # 일본어 글꼴
55
+ "superscript": True/False, # 위 첨자
56
+ "subscript": True/False, # 아래 첨자
57
+ "outline": True/False, # 외곽선
58
+ "shadow": True/False, # 그림자
59
+ "emboss": True/False, # 양각
60
+ "engrave": True/False, # 음각
61
+ "small_caps": True/False, # 작은 대문자
50
62
  }
51
63
  삽입 후 원래 서식으로 복원.
52
64
  """
@@ -59,22 +71,20 @@ def insert_text_with_style(hwp, text, style=None):
59
71
 
60
72
  # 현재 서식 저장
61
73
  act.GetDefault("CharShape", pset.HSet)
62
- saved_color = pset.TextColor
63
- saved_bold = pset.Bold
64
- saved_italic = pset.Italic
65
- saved_underline = pset.UnderlineType
66
- saved_height = pset.Height
67
- saved_strikeout = pset.StrikeOutType
68
- saved_char_spacing = None
69
- saved_width_ratio = None
70
- try:
71
- saved_char_spacing = pset.SpacingHangul
72
- except Exception:
73
- pass
74
- try:
75
- saved_width_ratio = pset.RatioHangul
76
- except Exception:
77
- pass
74
+ saved = {}
75
+ saved['TextColor'] = pset.TextColor
76
+ saved['Bold'] = pset.Bold
77
+ saved['Italic'] = pset.Italic
78
+ saved['UnderlineType'] = pset.UnderlineType
79
+ saved['Height'] = pset.Height
80
+ saved['StrikeOutType'] = pset.StrikeOutType
81
+ for attr in ['SpacingHangul', 'RatioHangul', 'SuperScript', 'SubScript',
82
+ 'OutLineType', 'ShadowType', 'Emboss', 'Engrave', 'SmallCaps',
83
+ 'UnderlineColor', 'StrikeOutColor']:
84
+ try:
85
+ saved[attr] = getattr(pset, attr)
86
+ except Exception:
87
+ saved[attr] = None
78
88
 
79
89
  # 새 서식 적용
80
90
  act.GetDefault("CharShape", pset.HSet)
@@ -86,18 +96,36 @@ def insert_text_with_style(hwp, text, style=None):
86
96
  pset.Bold = 1 if style["bold"] else 0
87
97
  if "italic" in style:
88
98
  pset.Italic = 1 if style["italic"] else 0
89
- if "underline" in style:
99
+ if "underline_type" in style:
100
+ pset.UnderlineType = int(style["underline_type"])
101
+ elif "underline" in style:
90
102
  pset.UnderlineType = 1 if style["underline"] else 0
103
+ if "underline_color" in style:
104
+ uc = style["underline_color"]
105
+ try:
106
+ pset.UnderlineColor = hwp.RGBColor(uc[0], uc[1], uc[2])
107
+ except Exception as e:
108
+ print(f"[WARN] UnderlineColor: {e}", file=sys.stderr)
91
109
  if "font_size" in style:
92
110
  pset.Height = int(style["font_size"] * 100) # pt → HWP 단위
93
111
  if "font_name" in style:
94
112
  pset.FaceNameHangul = style["font_name"]
95
113
  pset.FaceNameLatin = style["font_name"]
114
+ if "font_name_latin" in style:
115
+ pset.FaceNameLatin = style["font_name_latin"]
96
116
  if "bg_color" in style:
97
117
  bg = style["bg_color"]
98
118
  pset.ShadeColor = hwp.RGBColor(bg[0], bg[1], bg[2])
99
- if "strikeout" in style:
119
+ if "strikeout_type" in style:
120
+ pset.StrikeOutType = int(style["strikeout_type"])
121
+ elif "strikeout" in style:
100
122
  pset.StrikeOutType = 1 if style["strikeout"] else 0
123
+ if "strikeout_color" in style:
124
+ sc = style["strikeout_color"]
125
+ try:
126
+ pset.StrikeOutColor = hwp.RGBColor(sc[0], sc[1], sc[2])
127
+ except Exception as e:
128
+ print(f"[WARN] StrikeOutColor: {e}", file=sys.stderr)
101
129
  if "char_spacing" in style:
102
130
  try:
103
131
  pset.SpacingHangul = int(style["char_spacing"])
@@ -120,6 +148,77 @@ def insert_text_with_style(hwp, text, style=None):
120
148
  pset.FaceNameJapanese = style["font_name_japanese"]
121
149
  except Exception as e:
122
150
  print(f"[WARN] {e}", file=sys.stderr)
151
+ # 위/아래 첨자
152
+ if "superscript" in style:
153
+ try:
154
+ pset.SuperScript = 1 if style["superscript"] else 0
155
+ except Exception as e:
156
+ print(f"[WARN] SuperScript: {e}", file=sys.stderr)
157
+ if "subscript" in style:
158
+ try:
159
+ pset.SubScript = 1 if style["subscript"] else 0
160
+ except Exception as e:
161
+ print(f"[WARN] SubScript: {e}", file=sys.stderr)
162
+ # 외곽선/그림자/양각/음각/작은대문자
163
+ if "outline" in style:
164
+ try:
165
+ pset.OutLineType = 1 if style["outline"] else 0
166
+ except Exception as e:
167
+ print(f"[WARN] OutLineType: {e}", file=sys.stderr)
168
+ if "shadow" in style:
169
+ try:
170
+ pset.ShadowType = 1 if style["shadow"] else 0
171
+ except Exception as e:
172
+ print(f"[WARN] ShadowType: {e}", file=sys.stderr)
173
+ if "emboss" in style:
174
+ try:
175
+ pset.Emboss = 1 if style["emboss"] else 0
176
+ except Exception as e:
177
+ print(f"[WARN] Emboss: {e}", file=sys.stderr)
178
+ if "engrave" in style:
179
+ try:
180
+ pset.Engrave = 1 if style["engrave"] else 0
181
+ except Exception as e:
182
+ print(f"[WARN] Engrave: {e}", file=sys.stderr)
183
+ if "small_caps" in style:
184
+ try:
185
+ pset.SmallCaps = 1 if style["small_caps"] else 0
186
+ except Exception as e:
187
+ print(f"[WARN] SmallCaps: {e}", file=sys.stderr)
188
+ # 그림자 색상/오프셋
189
+ if "shadow_color" in style:
190
+ try:
191
+ sc = style["shadow_color"]
192
+ pset.ShadowColor = hwp.RGBColor(sc[0], sc[1], sc[2])
193
+ except Exception as e:
194
+ print(f"[WARN] ShadowColor: {e}", file=sys.stderr)
195
+ if "shadow_offset_x" in style:
196
+ try:
197
+ pset.ShadowOffsetX = int(style["shadow_offset_x"])
198
+ except Exception as e:
199
+ print(f"[WARN] ShadowOffsetX: {e}", file=sys.stderr)
200
+ if "shadow_offset_y" in style:
201
+ try:
202
+ pset.ShadowOffsetY = int(style["shadow_offset_y"])
203
+ except Exception as e:
204
+ print(f"[WARN] ShadowOffsetY: {e}", file=sys.stderr)
205
+ # 밑줄/취소선 모양
206
+ if "underline_shape" in style:
207
+ try:
208
+ pset.UnderlineShape = int(style["underline_shape"])
209
+ except Exception as e:
210
+ print(f"[WARN] UnderlineShape: {e}", file=sys.stderr)
211
+ if "strikeout_shape" in style:
212
+ try:
213
+ pset.StrikeOutShape = int(style["strikeout_shape"])
214
+ except Exception as e:
215
+ print(f"[WARN] StrikeOutShape: {e}", file=sys.stderr)
216
+ # 커닝
217
+ if "use_kerning" in style:
218
+ try:
219
+ pset.UseKerning = 1 if style["use_kerning"] else 0
220
+ except Exception as e:
221
+ print(f"[WARN] UseKerning: {e}", file=sys.stderr)
123
222
 
124
223
  act.Execute("CharShape", pset.HSet)
125
224
 
@@ -127,24 +226,24 @@ def insert_text_with_style(hwp, text, style=None):
127
226
 
128
227
  # 원래 서식 복원
129
228
  act.GetDefault("CharShape", pset.HSet)
130
- pset.TextColor = saved_color
131
- pset.Bold = saved_bold
132
- pset.Italic = saved_italic
133
- pset.UnderlineType = saved_underline
134
- pset.Height = saved_height
135
- pset.StrikeOutType = saved_strikeout
136
- if saved_char_spacing is not None:
137
- try:
138
- pset.SpacingHangul = saved_char_spacing
139
- pset.SpacingLatin = saved_char_spacing
140
- except Exception as e:
141
- print(f"[WARN] {e}", file=sys.stderr)
142
- if saved_width_ratio is not None:
143
- try:
144
- pset.RatioHangul = saved_width_ratio
145
- pset.RatioLatin = saved_width_ratio
146
- except Exception as e:
147
- print(f"[WARN] {e}", file=sys.stderr)
229
+ pset.TextColor = saved['TextColor']
230
+ pset.Bold = saved['Bold']
231
+ pset.Italic = saved['Italic']
232
+ pset.UnderlineType = saved['UnderlineType']
233
+ pset.Height = saved['Height']
234
+ pset.StrikeOutType = saved['StrikeOutType']
235
+ for attr in ['SpacingHangul', 'RatioHangul', 'SuperScript', 'SubScript',
236
+ 'OutLineType', 'ShadowType', 'Emboss', 'Engrave', 'SmallCaps',
237
+ 'UnderlineColor', 'StrikeOutColor']:
238
+ if saved.get(attr) is not None:
239
+ try:
240
+ setattr(pset, attr, saved[attr])
241
+ if attr == 'SpacingHangul':
242
+ pset.SpacingLatin = saved[attr]
243
+ if attr == 'RatioHangul':
244
+ pset.RatioLatin = saved[attr]
245
+ except Exception as e:
246
+ print(f"[WARN] Restore {attr}: {e}", file=sys.stderr)
148
247
  act.Execute("CharShape", pset.HSet)
149
248
 
150
249
 
@@ -206,6 +305,71 @@ def set_paragraph_style(hwp, style=None):
206
305
  pset.RightMargin = int(style["right_margin"] * 100)
207
306
  except Exception as e:
208
307
  print(f"[WARN] {e}", file=sys.stderr)
308
+ # 문단 앞 페이지 나누기
309
+ if "page_break_before" in style:
310
+ try:
311
+ pset.PagebreakBefore = 1 if style["page_break_before"] else 0
312
+ except Exception as e:
313
+ print(f"[WARN] PagebreakBefore: {e}", file=sys.stderr)
314
+ # 다음 문단과 함께
315
+ if "keep_with_next" in style:
316
+ try:
317
+ pset.KeepWithNext = 1 if style["keep_with_next"] else 0
318
+ except Exception as e:
319
+ print(f"[WARN] KeepWithNext: {e}", file=sys.stderr)
320
+ # 과부/고아 방지
321
+ if "widow_orphan" in style:
322
+ try:
323
+ pset.WidowOrphan = 1 if style["widow_orphan"] else 0
324
+ except Exception as e:
325
+ print(f"[WARN] WidowOrphan: {e}", file=sys.stderr)
326
+ # 줄 바꿈
327
+ if "line_wrap" in style:
328
+ try:
329
+ pset.LineWrap = int(style["line_wrap"])
330
+ except Exception as e:
331
+ print(f"[WARN] LineWrap: {e}", file=sys.stderr)
332
+ # 그리드에 맞춤
333
+ if "snap_to_grid" in style:
334
+ try:
335
+ pset.SnapToGrid = 1 if style["snap_to_grid"] else 0
336
+ except Exception as e:
337
+ print(f"[WARN] SnapToGrid: {e}", file=sys.stderr)
338
+ # 한영 자동 간격
339
+ if "auto_space_eAsian_eng" in style:
340
+ try:
341
+ pset.AutoSpaceEAsianEng = 1 if style["auto_space_eAsian_eng"] else 0
342
+ except Exception as e:
343
+ print(f"[WARN] AutoSpaceEAsianEng: {e}", file=sys.stderr)
344
+ if "auto_space_eAsian_num" in style:
345
+ try:
346
+ pset.AutoSpaceEAsianNum = 1 if style["auto_space_eAsian_num"] else 0
347
+ except Exception as e:
348
+ print(f"[WARN] AutoSpaceEAsianNum: {e}", file=sys.stderr)
349
+ # 영문 줄바꿈
350
+ if "break_latin_word" in style:
351
+ try:
352
+ pset.BreakLatinWord = int(style["break_latin_word"])
353
+ except Exception as e:
354
+ print(f"[WARN] BreakLatinWord: {e}", file=sys.stderr)
355
+ # 제목 수준
356
+ if "heading_type" in style:
357
+ try:
358
+ pset.HeadingType = int(style["heading_type"])
359
+ except Exception as e:
360
+ print(f"[WARN] HeadingType: {e}", file=sys.stderr)
361
+ # 줄 함께 유지
362
+ if "keep_lines_together" in style:
363
+ try:
364
+ pset.KeepLinesTogether = 1 if style["keep_lines_together"] else 0
365
+ except Exception as e:
366
+ print(f"[WARN] KeepLinesTogether: {e}", file=sys.stderr)
367
+ # 문단 압축
368
+ if "condense" in style:
369
+ try:
370
+ pset.Condense = int(style["condense"])
371
+ except Exception as e:
372
+ print(f"[WARN] Condense: {e}", file=sys.stderr)
209
373
 
210
374
  act.Execute("ParaShape", pset.HSet)
211
375
 
@@ -463,8 +627,31 @@ def fill_table_cells_by_tab(hwp, table_idx, cells):
463
627
  cell_style = cell.get("style")
464
628
  if cell_style:
465
629
  insert_text_with_style(hwp, text, cell_style)
630
+ # 셀 정렬은 텍스트 삽입 후 TableCellAlign 액션으로 적용
631
+ if "align" in cell_style:
632
+ align_action = {
633
+ "left": "TableCellAlignLeftCenter",
634
+ "center": "TableCellAlignCenterCenter",
635
+ "right": "TableCellAlignRightCenter",
636
+ "justify": "TableCellAlignLeftCenter",
637
+ }
638
+ action = align_action.get(cell_style["align"], "TableCellAlignLeftCenter")
639
+ hwp.HAction.Run(action)
466
640
  else:
467
641
  hwp.insert_text(text)
642
+
643
+ # 셀 수직 정렬/여백 설정 (HCell)
644
+ vert_align = cell.get("vert_align") # "top"|"middle"|"bottom"
645
+ if vert_align:
646
+ try:
647
+ va_map = {"top": 0, "middle": 1, "bottom": 2}
648
+ pset_cell = hwp.HParameterSet.HCell
649
+ hwp.HAction.GetDefault("CellShape", pset_cell.HSet)
650
+ pset_cell.VertAlign = va_map.get(vert_align, 0)
651
+ hwp.HAction.Execute("CellShape", pset_cell.HSet)
652
+ except Exception as e:
653
+ print(f"[WARN] VertAlign: {e}", file=sys.stderr)
654
+
468
655
  result["filled"] += 1
469
656
 
470
657
  except Exception as e:
@@ -573,8 +760,9 @@ def insert_markdown(hwp, md_text):
573
760
  i += 1
574
761
  continue
575
762
  # 셀 파싱
576
- cells = [c.strip() for c in row_text.split('|')]
577
- cells = [c for c in cells if c] # 빈 문자열 제거
763
+ # H3 fix: 유지 (앞뒤 빈 요소만 제거)
764
+ raw_cells = row_text.split('|')
765
+ cells = [c.strip() for c in raw_cells[1:-1]] # | 앞뒤 빈 요소 제거, 중간 빈 셀 유지
578
766
  table_lines.append(cells)
579
767
  i += 1
580
768
  # 표 생성
@@ -907,12 +1095,8 @@ def set_cell_background_color(hwp, table_idx, cells):
907
1095
  hwp.TableRightCell()
908
1096
  current_tab = target_tab
909
1097
 
910
- # 셀 배경색 설정 (CellShape BackColor)
911
- act = hwp.HAction
912
- pset = hwp.HParameterSet.HCellShape
913
- act.GetDefault("CellShape", pset.HSet)
914
- pset.BackColor = hwp.RGBColor(r, g, b)
915
- act.Execute("CellShape", pset.HSet)
1098
+ # 셀 배경색 설정 (pyhwpx 내장 cell_fill 사용)
1099
+ hwp.cell_fill((r, g, b))
916
1100
  result["colored"] += 1
917
1101
 
918
1102
  except Exception as e:
@@ -934,14 +1118,19 @@ def set_table_border_style(hwp, table_idx, cells=None, style=None):
934
1118
 
935
1119
  table_idx: 표 인덱스
936
1120
  cells: 특정 셀만 적용 시 [{"tab": int}, ...] (None이면 표 전체)
937
- style: {"line_type": int, "line_width": int} — HWP 테두리 속성
938
- line_type: 0=없음, 1=실선, 2=파선, 3=점선, 4=1점쇄선, 5=2점쇄선
939
- line_width: pt 단위 (기본 0.12mm → 약 0.4pt)
1121
+ style: {
1122
+ "line_type": int, # 0=없음, 1=실선, 2=파선, 3=점선, 4=1점쇄선, 5=2점쇄선
1123
+ "line_width": int, # pt 단위
1124
+ "color": "#RRGGBB", # 테두리 색상
1125
+ "edges": ["left","right","top","bottom"], # 적용할 방향 (생략 시 전체)
1126
+ }
940
1127
  """
941
1128
  if style is None:
942
1129
  style = {}
943
- line_type = style.get("line_type", 1) # 기본: 실선
1130
+ line_type = style.get("line_type", 1)
944
1131
  line_width = style.get("line_width", 0)
1132
+ border_color = style.get("color") # "#RRGGBB" 또는 None
1133
+ edges = style.get("edges", ["left", "right", "top", "bottom"]) # 기본: 전체
945
1134
 
946
1135
  try:
947
1136
  hwp.get_into_nth_table(table_idx)
@@ -958,13 +1147,21 @@ def set_table_border_style(hwp, table_idx, cells=None, style=None):
958
1147
  act = hwp.HAction
959
1148
  pset = hwp.HParameterSet.HCellBorderFill
960
1149
  act.GetDefault("CellBorderFill", pset.HSet)
961
- # 4방향 테두리 설정
962
- for attr in ["Left", "Right", "Top", "Bottom"]:
963
- line = getattr(pset, f"{attr}Border", None)
964
- if line:
965
- line.Type = line_type
1150
+ # 방향별 테두리 설정
1151
+ edge_map = {"left": "Left", "right": "Right", "top": "Top", "bottom": "Bottom"}
1152
+ for edge_name in edges:
1153
+ prop = edge_map.get(edge_name)
1154
+ if prop:
1155
+ setattr(pset, f"BorderType{prop}", line_type)
966
1156
  if line_width:
967
- line.Width = line_width
1157
+ setattr(pset, f"BorderWidth{prop}", line_width)
1158
+ if border_color:
1159
+ try:
1160
+ r, g, b = _hex_to_rgb(border_color)
1161
+ color_attr = f"BorderColor{prop}" if prop != "Left" else "BorderCorlorLeft" # typo in COM
1162
+ setattr(pset, color_attr, hwp.RGBColor(r, g, b))
1163
+ except Exception as e:
1164
+ print(f"[WARN] BorderColor {prop}: {e}", file=sys.stderr)
968
1165
  act.Execute("CellBorderFill", pset.HSet)
969
1166
  modified += 1
970
1167
  except Exception as e:
@@ -977,12 +1174,20 @@ def set_table_border_style(hwp, table_idx, cells=None, style=None):
977
1174
  act = hwp.HAction
978
1175
  pset = hwp.HParameterSet.HCellBorderFill
979
1176
  act.GetDefault("CellBorderFill", pset.HSet)
980
- for attr in ["Left", "Right", "Top", "Bottom"]:
981
- line = getattr(pset, f"{attr}Border", None)
982
- if line:
983
- line.Type = line_type
1177
+ edge_map = {"left": "Left", "right": "Right", "top": "Top", "bottom": "Bottom"}
1178
+ for edge_name in edges:
1179
+ prop = edge_map.get(edge_name)
1180
+ if prop:
1181
+ setattr(pset, f"BorderType{prop}", line_type)
984
1182
  if line_width:
985
- line.Width = line_width
1183
+ setattr(pset, f"BorderWidth{prop}", line_width)
1184
+ if border_color:
1185
+ try:
1186
+ r, g, b = _hex_to_rgb(border_color)
1187
+ color_attr = f"BorderColor{prop}" if prop != "Left" else "BorderCorlorLeft"
1188
+ setattr(pset, color_attr, hwp.RGBColor(r, g, b))
1189
+ except Exception as e:
1190
+ print(f"[WARN] BorderColor {prop}: {e}", file=sys.stderr)
986
1191
  act.Execute("CellBorderFill", pset.HSet)
987
1192
  return {"status": "ok", "applied": "whole_table"}
988
1193
  finally: