@kimdayoun/hwpx-mcp 0.3.0 → 0.3.3
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.
- package/CHANGELOG.md +148 -0
- package/README.md +17 -46
- package/dist/HangingIndentCalculator.js +136 -166
- package/dist/HwpxDocument.d.ts +152 -12
- package/dist/HwpxDocument.js +780 -444
- package/dist/HwpxParser.js +18 -2
- package/dist/index.js +95 -40
- package/dist/types.d.ts +5 -0
- package/package.json +3 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
`@kimdayoun/hwpx-mcp` 의 주요 변경 사항을 기록합니다.
|
|
4
|
+
|
|
5
|
+
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/) 를 따르고,
|
|
6
|
+
버전은 [유의적 버전](https://semver.org/lang/ko/) 을 따릅니다.
|
|
7
|
+
|
|
8
|
+
## [0.3.3] - 2026-09-24
|
|
9
|
+
|
|
10
|
+
0.3.2 는 npm 에 게시하지 않았습니다. 게시 전에 윈도우 한/글로 직접 렌더하다가,
|
|
11
|
+
MCP 응답이 성공이고 `analyze_xml` 도 통과하는데 화면이 틀어진 곳을 찾았습니다.
|
|
12
|
+
0.3.2 의 수정은 모두 이 버전에 포함돼 있습니다.
|
|
13
|
+
|
|
14
|
+
변경 내역은 [#13](https://github.com/Dayoooun/hwpx-mcp/pull/13),
|
|
15
|
+
[#14](https://github.com/Dayoooun/hwpx-mcp/pull/14) 에 있습니다.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **문단과 표를 섞어 넣으면 저장본 순서가 뒤집히던 문제.** `[제목, 표, 제목, 표]` 로
|
|
20
|
+
넣었는데 한/글에서는 `[제목, 제목, 표, 표]` 로 보였습니다. 새 문서에서 문단 두 개만
|
|
21
|
+
넣어도 순서가 바뀌었습니다. 위치를 요소 번호 대신 앞 요소의 id 로 기록하고,
|
|
22
|
+
삽입·복제·이동을 호출한 순서대로 저장합니다.
|
|
23
|
+
- **다시 연 문서에서 복제한 문단을 고치면 원본이 바뀌던 문제.** 0.3.2 에서 고친 증상이
|
|
24
|
+
"저장 → 다시 열기 → 복제 → 수정" 경로에는 남아 있었습니다. 수정 대상 문단을
|
|
25
|
+
저장 시점에 다시 찾습니다. 수정한 뒤 다른 문단을 옮기거나 지워도 그 문단이 바뀝니다.
|
|
26
|
+
- **표 뒤에 넣은 문단이 표 첫 칸 안으로 들어가던 문제.**
|
|
27
|
+
- **표 안 글자가 셀 테두리에 붙던 문제.** 새로 만든 표의 셀 안 여백이 0 이었습니다.
|
|
28
|
+
한/글로 쟀을 때 글자가 테두리에서 0pt 떨어져 있었고, 이제 왼쪽 5pt, 위 1.4pt 떨어집니다.
|
|
29
|
+
- **표 안 표가 부모 칸을 뚫고 나가던 문제.** 중첩 표 칸 폭이 부모와 상관없이
|
|
30
|
+
80mm 로 고정돼 있었습니다. 부모 칸 안쪽 폭에 맞추고, 표 위에 생기던 빈 줄도 없앴습니다.
|
|
31
|
+
- **내어쓰기 둘째 줄이 첫 줄 본문보다 오른쪽에서 시작하던 문제.** 마커 폭을 추정식으로
|
|
32
|
+
계산해 한/글보다 평균 4.14pt 넓게 잡았습니다. 마커 35종을 한/글로 그려 잰 폭으로
|
|
33
|
+
바꿨고, 오차는 평균 0.04pt 입니다. 함초롬바탕·함초롬돋움만 실측했습니다.
|
|
34
|
+
- **`insert_table_row` 의 `cell_texts` 가 다른 칸으로 들어가던 문제.** 기준 행에 여러 줄
|
|
35
|
+
칸이 있으면 `['가','나','다']` 가 `['가','나다','']` 로 들어갔고, 새 행이 세 줄 높이가
|
|
36
|
+
됐습니다.
|
|
37
|
+
|
|
38
|
+
### 알려진 제한
|
|
39
|
+
|
|
40
|
+
- run 이 여러 개인 문단을 `update_paragraph_text` 로 고치면 새 글자가 run 사이에
|
|
41
|
+
흩어지거나 사라질 수 있습니다. 미주·수식이 섞인 문단은 수정이 반영되지 않을 수
|
|
42
|
+
있습니다. 이 버전 이전부터 있던 문제입니다.
|
|
43
|
+
|
|
44
|
+
## [0.3.2] - 2026-09-22
|
|
45
|
+
|
|
46
|
+
외부 사용 리뷰에서 보고된 8건을 MCP stdio 클라이언트로 직접 재현한 뒤 수정했습니다.
|
|
47
|
+
리뷰 대응 내역은 [#10](https://github.com/Dayoooun/hwpx-mcp/pull/10) 에 정리돼 있습니다.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- **표 행·열 삽입이 저장본을 깨뜨리던 문제.** 복제한 행·셀의 본문을 비우는 정규식
|
|
52
|
+
`<(hp|hs):t([^>]*)>` 에 태그명 뒤 경계가 없어 `<hp:tc>` 를 `<hp:t>` 로 삼켰습니다.
|
|
53
|
+
`<hp:subList>` 이하가 통째로 사라지고 닫는 태그만 남아 한/글이 파일을 열지 못했습니다.
|
|
54
|
+
`insert_table_row` · `insert_table_column` 양쪽 해당.
|
|
55
|
+
- **`copy_paragraph` 직후 텍스트를 교체하면 원본까지 바뀌던 문제.** 저장 파이프라인이
|
|
56
|
+
텍스트 갱신을 문단 복제보다 먼저 적용해, 복제 후 인덱스로 지정한 대상이 복제 전 XML 의
|
|
57
|
+
원본 문단으로 해석됐습니다. 복제·이동을 모든 텍스트 갱신 앞으로 옮겼습니다.
|
|
58
|
+
중간에 `save_document` 를 끼우던 우회가 더 이상 필요 없습니다.
|
|
59
|
+
- **`create_document` · `save_document` 의 경로 무시.** 새 문서가 실제 위치 없이
|
|
60
|
+
`new-document.hwpx` 라는 상대 파일명을 들고 있어 저장이 서버 프로세스 cwd 로 갔습니다.
|
|
61
|
+
목적지가 없으면 조용히 cwd 로 가지 않고 명시적으로 실패합니다.
|
|
62
|
+
- **병합으로 덮인 셀에 쓰면 성공이라 답하고 저장 시 사라지던 문제.** HWPX 는 덮인 위치에
|
|
63
|
+
`<hp:tc>` 를 두지 않아 직렬화 대상이 없습니다. 이제 어느 마스터 셀에 써야 하는지
|
|
64
|
+
알려주며 거부합니다.
|
|
65
|
+
- **`get_tool_guide` 의 `topic` 이 무시되던 문제.** 모르는 값에 조용히 전체 참조를
|
|
66
|
+
반환하던 것을 가능한 값 목록을 담은 오류로 바꿨습니다.
|
|
67
|
+
- **복제 문단·행·열의 `linesegarray` 미초기화.** 원본의 고정 줄 배치를 물려받아 긴 글을
|
|
68
|
+
넣으면 글자가 겹쳤습니다. `get_section_xml` → 문자열 치환 → `set_section_xml` 수작업이
|
|
69
|
+
필요 없어집니다.
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- **필수 인자 누락 시 원인을 밝힙니다.** `section_index` 를 빠뜨리면
|
|
74
|
+
`Failed to insert paragraph` 가 떠 문서 손상처럼 보였습니다. 스키마의 `required` 를
|
|
75
|
+
디스패처에서 검사해 어떤 인자가 빠졌는지 이름으로 답합니다.
|
|
76
|
+
예: `Missing required arguments for insert_paragraph: section_index, after_index`
|
|
77
|
+
- **`save_document` 응답에 절대경로를 돌려줍니다.** `path` 와 `backup_path` 가 추가됐고,
|
|
78
|
+
`output_path` 외에 `file_path` 도 받습니다.
|
|
79
|
+
- **`batch_fill_table` 이 실패한 셀을 보고하고 나머지를 계속 채웁니다.** 반환값에 `failed`
|
|
80
|
+
목록(좌표·값·이유)이 추가됐습니다. 이전에는 덮인 셀 하나가 배치 전체를 중단시켰습니다.
|
|
81
|
+
|
|
82
|
+
### Added
|
|
83
|
+
|
|
84
|
+
- **`create_document({file_path})`** — 목적지를 미리 지정하면 이후 `save_document` 가
|
|
85
|
+
인자 없이도 그 경로에 씁니다.
|
|
86
|
+
- **`paraPrIDRef` · `charPrIDRef` 노출** — `get_paragraph` · `get_paragraphs` 가 스타일
|
|
87
|
+
값과 함께 원본 숫자 ID 를 돌려줍니다. XML 을 직접 조립할 때 `section0.xml` 을 정규식으로
|
|
88
|
+
파지 않아도 됩니다.
|
|
89
|
+
|
|
90
|
+
### 알려진 제한
|
|
91
|
+
|
|
92
|
+
- `updateTableCell` 은 셀마다 문서 전체를 직렬화해 undo 스택에 쌓습니다. 실측상 100×30 표
|
|
93
|
+
전수 쓰기 1,725ms 중 1,501ms(87%)가 여기서 발생합니다. 구조 변경이라 이 릴리스의 수정
|
|
94
|
+
범위에 포함하지 않았습니다.
|
|
95
|
+
|
|
96
|
+
## [0.3.1] - 2026-09-08
|
|
97
|
+
|
|
98
|
+
- 저장 임시 파일·백업 경로의 링크 공격 방어 및 실패 시 원본 보존
|
|
99
|
+
- 여러 `hp:t`에 분리된 텍스트의 저장 누락 수정 및 특수문자 보존
|
|
100
|
+
- 개인 Windows 문서에 의존하던 테스트를 합성 HWPX 회귀 테스트로 교체
|
|
101
|
+
- 의존성 갱신 및 기존 절대·상대경로 사용법의 호환성 검사 추가
|
|
102
|
+
- 작업 폴더 제한은 추가하지 않았으며, 수식 저장 누락은 이 릴리스의 수정 범위에 포함하지 않음
|
|
103
|
+
|
|
104
|
+
## 0.3.0 - 2026-01-28
|
|
105
|
+
|
|
106
|
+
- **대규모 XML persistence 수정**: 8개 조작에 대한 save-reload 지원 추가
|
|
107
|
+
- `insertTableRow` / `deleteTableRow`
|
|
108
|
+
- `insertTableColumn` / `deleteTableColumn`
|
|
109
|
+
- `copyParagraph` / `moveParagraph` (+ 같은 섹션 인덱스 버그 수정)
|
|
110
|
+
- `setHeader` / `setFooter`
|
|
111
|
+
- **depth-aware element indexing**: 중첩 태그 내부 요소를 무시하는 안전한 파싱
|
|
112
|
+
- **undo/redo 안전성**: pending 배열 초기화로 메모리/XML 비동기화 방지
|
|
113
|
+
- **replaceText 수정**: XML entity 불일치 해결
|
|
114
|
+
- **mergeCells 수정**: indexOf 모호성 해결
|
|
115
|
+
- **테스트**: 24개 E2E 테스트 (16 기본 + 8 persistence)
|
|
116
|
+
|
|
117
|
+
## 0.2.0
|
|
118
|
+
|
|
119
|
+
- **신규 기능**: 테이블 셀 내 내어쓰기(Hanging Indent) 자동 적용
|
|
120
|
+
- `update_table_cell` 시 마커(○, 1., 가., (1) 등) 감지하여 자동 내어쓰기
|
|
121
|
+
- 멀티라인 텍스트의 각 줄에 독립적으로 내어쓰기 적용
|
|
122
|
+
- `set_table_cell_hanging_indent`, `get_table_cell_hanging_indent` 도구 추가
|
|
123
|
+
- **버그 수정**: 병렬 테이블 업데이트 시 XML 손상 문제 해결
|
|
124
|
+
- 문서별 Lock 추가로 병렬 요청 직렬화 (race condition 방지)
|
|
125
|
+
- `findTableCellInXml()` 중첩 테이블 처리 개선 (balanced bracket 매칭)
|
|
126
|
+
- 여러 테이블 동시 수정 후 저장 시 "Broken tag structure" 오류 수정
|
|
127
|
+
- **버그 수정**: 여러 테이블에 내어쓰기 적용 시 stale position 문제 해결
|
|
128
|
+
- 테이블 인덱스 내림차순 처리로 위치 변경 영향 방지
|
|
129
|
+
- 각 테이블 처리 시 위치 정보 재계산
|
|
130
|
+
- **테스트 강화**: Red Team 스트레스 테스트 추가 (238개 테스트)
|
|
131
|
+
- 50~200개 테이블 대량 수정 테스트
|
|
132
|
+
- 중첩 테이블 + 내어쓰기 + 이미지 복합 테스트
|
|
133
|
+
- 병렬 업데이트 시나리오 테스트
|
|
134
|
+
|
|
135
|
+
## 0.1.1
|
|
136
|
+
|
|
137
|
+
- **버그 수정**: `update_table_cell` 후 `save_document` 시 빈 셀 변경사항이 저장되지 않던 문제 수정
|
|
138
|
+
- Self-closing XML run 태그 (`<hp:run ... />`) 처리 지원 추가
|
|
139
|
+
- ID 기반 테이블 매칭으로 정확한 XML 업데이트 구현
|
|
140
|
+
- 원본 XML 구조를 최대한 보존하면서 텍스트만 수정
|
|
141
|
+
|
|
142
|
+
## 0.1.0
|
|
143
|
+
|
|
144
|
+
- 최초 릴리스
|
|
145
|
+
|
|
146
|
+
[0.3.3]: https://github.com/Dayoooun/hwpx-mcp/compare/v0.3.2...v0.3.3
|
|
147
|
+
[0.3.2]: https://github.com/Dayoooun/hwpx-mcp/compare/v0.3.1...v0.3.2
|
|
148
|
+
[0.3.1]: https://github.com/Dayoooun/hwpx-mcp/releases/tag/v0.3.1
|
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# HWPX MCP Server — `@
|
|
1
|
+
# HWPX MCP Server — `@kimdayoun/hwpx-mcp`
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@kimdayoun/hwpx-mcp)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://modelcontextprotocol.io/)
|
|
6
6
|
|
|
7
7
|
> **이 저장소가 원본입니다.** 정본: https://github.com/Dayoooun/hwpx-mcp
|
|
8
|
-
> npm 배포본은 **`@
|
|
8
|
+
> npm 배포본은 **`@kimdayoun/hwpx-mcp`** 하나뿐입니다.
|
|
9
9
|
> `hwpx-mcp`, `hwpx-mcp-server` 등 스코프 없는 동명 패키지는 이 프로젝트와 무관한 제3자 배포본입니다.
|
|
10
10
|
|
|
11
11
|
HWP/HWPX 문서를 AI로 읽고 편집할 수 있는 Model Context Protocol (MCP) 서버입니다.
|
|
@@ -21,13 +21,13 @@ HWP/HWPX 문서를 AI로 읽고 편집할 수 있는 Model Context Protocol (MCP
|
|
|
21
21
|
## 설치
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npm install -g @
|
|
24
|
+
npm install -g @kimdayoun/hwpx-mcp
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
설치 없이 바로 실행하려면:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
npx -y @
|
|
30
|
+
npx -y @kimdayoun/hwpx-mcp
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
소스에서 빌드하려면:
|
|
@@ -50,7 +50,7 @@ npm run build
|
|
|
50
50
|
"mcpServers": {
|
|
51
51
|
"hwpx-mcp": {
|
|
52
52
|
"command": "npx",
|
|
53
|
-
"args": ["-y", "@
|
|
53
|
+
"args": ["-y", "@kimdayoun/hwpx-mcp"]
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -66,7 +66,7 @@ npm run build
|
|
|
66
66
|
"mcpServers": {
|
|
67
67
|
"hwpx-mcp": {
|
|
68
68
|
"command": "npx",
|
|
69
|
-
"args": ["-y", "@
|
|
69
|
+
"args": ["-y", "@kimdayoun/hwpx-mcp"]
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -79,8 +79,8 @@ npm run build
|
|
|
79
79
|
|------|------|
|
|
80
80
|
| `get_tool_guide` | 도구 가이드 조회 |
|
|
81
81
|
| `open_document` | 문서 열기 (HWPX/HWP) |
|
|
82
|
-
| `create_document` | 새 HWPX 문서
|
|
83
|
-
| `save_document` | 문서
|
|
82
|
+
| `create_document` | 새 HWPX 문서 생성. `file_path`를 주면 이후 `save_document`가 그 경로에 씁니다 |
|
|
83
|
+
| `save_document` | 문서 저장. `output_path`(또는 `file_path`)로 대상 지정, 응답에 절대경로 반환 |
|
|
84
84
|
| `close_document` | 문서 닫기 |
|
|
85
85
|
| `list_open_documents` | 열린 문서 목록 |
|
|
86
86
|
| `get_document_metadata` | 메타데이터 조회 |
|
|
@@ -301,55 +301,26 @@ AI 동작:
|
|
|
301
301
|
```bash
|
|
302
302
|
cd mcp-server
|
|
303
303
|
npm test # vitest 단위 테스트
|
|
304
|
+
npm run test:security # 빌드 후 실제 MCP 저장 경로 공격·반복 저장·실패 정리 검사
|
|
304
305
|
node test-mcp-e2e.mjs # 16개 기본 E2E 테스트
|
|
305
306
|
node test-new-persistence-e2e.mjs # 8개 persistence E2E 테스트
|
|
306
307
|
```
|
|
307
308
|
|
|
309
|
+
문단 저장 회귀 테스트는 개인 PC의 문서 경로 대신 합성 HWPX를 사용합니다.
|
|
310
|
+
여러 `hp:t`로 나뉜 텍스트·빈 문자열·XML 특수문자·동일한 문단 ID/본문의 대상 선택을 검사하며,
|
|
311
|
+
중첩 표 삭제는 최상위 표 목록과 셀 내부 중첩 표의 보존을 각각 확인합니다.
|
|
312
|
+
|
|
308
313
|
## 알려진 제한사항
|
|
309
314
|
|
|
310
315
|
- **각주/미주/북마크/하이퍼링크 삽입**: 메모리에서만 동작, save 후 XML 미반영 (읽기는 정상)
|
|
311
316
|
- **HWP 파일**: 읽기 전용 (편집 불가)
|
|
312
317
|
- **secPr 문단 스타일**: 첫 번째 특수 문단에서 스타일 reload 제한
|
|
318
|
+
- **파일 접근 범위**: 현재 서버는 작업 폴더 밖의 절대경로·상위경로·디렉터리 심볼릭 링크를 차단하지 않습니다. 신뢰하는 로컬 MCP 클라이언트에서만 사용하고, OS 권한 또는 컨테이너로 접근 범위를 제한하세요. `stdio` 실행을 네트워크 인증·격리로 간주하면 안 됩니다.
|
|
319
|
+
- **저장 안전성**: `save_document`는 목적지와 같은 디렉터리 안에 비공개 임시 디렉터리를 만들고 검증 후 rename합니다. 기존 `.tmp` 파일은 사용하지 않으며, `.bak`이 심볼릭 링크 등 일반 파일이 아니면 저장을 거부합니다. 이 조치는 작업 폴더 밖 접근 제한을 대신하지 않습니다.
|
|
313
320
|
|
|
314
321
|
## 변경 이력
|
|
315
322
|
|
|
316
|
-
|
|
317
|
-
- **대규모 XML persistence 수정**: 8개 조작에 대한 save-reload 지원 추가
|
|
318
|
-
- `insertTableRow` / `deleteTableRow`
|
|
319
|
-
- `insertTableColumn` / `deleteTableColumn`
|
|
320
|
-
- `copyParagraph` / `moveParagraph` (+ 같은 섹션 인덱스 버그 수정)
|
|
321
|
-
- `setHeader` / `setFooter`
|
|
322
|
-
- **depth-aware element indexing**: 중첩 태그 내부 요소를 무시하는 안전한 파싱
|
|
323
|
-
- **undo/redo 안전성**: pending 배열 초기화로 메모리/XML 비동기화 방지
|
|
324
|
-
- **replaceText 수정**: XML entity 불일치 해결
|
|
325
|
-
- **mergeCells 수정**: indexOf 모호성 해결
|
|
326
|
-
- **테스트**: 24개 E2E 테스트 (16 기본 + 8 persistence)
|
|
327
|
-
|
|
328
|
-
### 0.2.0
|
|
329
|
-
- **신규 기능**: 테이블 셀 내 내어쓰기(Hanging Indent) 자동 적용
|
|
330
|
-
- `update_table_cell` 시 마커(○, 1., 가., (1) 등) 감지하여 자동 내어쓰기
|
|
331
|
-
- 멀티라인 텍스트의 각 줄에 독립적으로 내어쓰기 적용
|
|
332
|
-
- `set_table_cell_hanging_indent`, `get_table_cell_hanging_indent` 도구 추가
|
|
333
|
-
- **버그 수정**: 병렬 테이블 업데이트 시 XML 손상 문제 해결
|
|
334
|
-
- 문서별 Lock 추가로 병렬 요청 직렬화 (race condition 방지)
|
|
335
|
-
- `findTableCellInXml()` 중첩 테이블 처리 개선 (balanced bracket 매칭)
|
|
336
|
-
- 여러 테이블 동시 수정 후 저장 시 "Broken tag structure" 오류 수정
|
|
337
|
-
- **버그 수정**: 여러 테이블에 내어쓰기 적용 시 stale position 문제 해결
|
|
338
|
-
- 테이블 인덱스 내림차순 처리로 위치 변경 영향 방지
|
|
339
|
-
- 각 테이블 처리 시 위치 정보 재계산
|
|
340
|
-
- **테스트 강화**: Red Team 스트레스 테스트 추가 (238개 테스트)
|
|
341
|
-
- 50~200개 테이블 대량 수정 테스트
|
|
342
|
-
- 중첩 테이블 + 내어쓰기 + 이미지 복합 테스트
|
|
343
|
-
- 병렬 업데이트 시나리오 테스트
|
|
344
|
-
|
|
345
|
-
### 0.1.1
|
|
346
|
-
- **버그 수정**: `update_table_cell` 후 `save_document` 시 빈 셀 변경사항이 저장되지 않던 문제 수정
|
|
347
|
-
- Self-closing XML run 태그 (`<hp:run ... />`) 처리 지원 추가
|
|
348
|
-
- ID 기반 테이블 매칭으로 정확한 XML 업데이트 구현
|
|
349
|
-
- 원본 XML 구조를 최대한 보존하면서 텍스트만 수정
|
|
350
|
-
|
|
351
|
-
### 0.1.0
|
|
352
|
-
- 최초 릴리스
|
|
323
|
+
버전별 변경 사항은 [CHANGELOG.md](./CHANGELOG.md) 를 참고하세요.
|
|
353
324
|
|
|
354
325
|
## 라이선스
|
|
355
326
|
|
|
@@ -13,175 +13,143 @@
|
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.HangingIndentCalculator = void 0;
|
|
15
15
|
/**
|
|
16
|
-
* 문자 너비 테이블 (em
|
|
17
|
-
* 실제 한글 문서에서 자주 사용되는 마커 문자들의 상대적 너비
|
|
16
|
+
* 문자 너비 테이블 (em 단위).
|
|
18
17
|
*
|
|
19
|
-
*
|
|
18
|
+
* 값은 Windows 한/글이 함초롬바탕 10pt 로 그린 PDF 에서 **글자 전진폭**
|
|
19
|
+
* (다음 글자 x − 이 글자 x) 을 잰 것이다. 추정치가 아니다.
|
|
20
|
+
* 측정 원본: src/fixtures/hancom-marker-widths.json
|
|
21
|
+
*
|
|
22
|
+
* 이전 표는 전각 1.0em·공백 0.5em 에 폰트 보정 1.25 를 곱해, 한/글보다
|
|
23
|
+
* 평균 4.14pt(최대 6.30pt) 넓게 계산했다. 한/글은 전각 기호를 0.996em,
|
|
24
|
+
* 공백을 0.498em 로 그리므로 보정 계수는 1.0 이 맞다.
|
|
25
|
+
*
|
|
26
|
+
* 표에 없는 글자는 getCharWidth 의 범주별 기본값을 쓴다.
|
|
20
27
|
*/
|
|
21
28
|
const CHAR_WIDTH_TABLE = {
|
|
22
|
-
//
|
|
23
|
-
'○':
|
|
24
|
-
'●':
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
|
|
57
|
-
'
|
|
58
|
-
|
|
59
|
-
'
|
|
60
|
-
|
|
61
|
-
'
|
|
62
|
-
|
|
63
|
-
'
|
|
64
|
-
'
|
|
65
|
-
'
|
|
66
|
-
'
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
'
|
|
70
|
-
'
|
|
71
|
-
|
|
72
|
-
'
|
|
73
|
-
|
|
74
|
-
'
|
|
75
|
-
|
|
76
|
-
'
|
|
77
|
-
|
|
78
|
-
'
|
|
79
|
-
|
|
80
|
-
'
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
'바': 1.0,
|
|
84
|
-
'사': 1.0,
|
|
85
|
-
'아': 1.0,
|
|
86
|
-
'자': 1.0,
|
|
87
|
-
'차': 1.0,
|
|
88
|
-
'카': 1.0,
|
|
89
|
-
'타': 1.0,
|
|
90
|
-
'파': 1.0,
|
|
91
|
-
'하': 1.0,
|
|
92
|
-
// 원문자 (전각)
|
|
93
|
-
'①': 1.0,
|
|
94
|
-
'②': 1.0,
|
|
95
|
-
'③': 1.0,
|
|
96
|
-
'④': 1.0,
|
|
97
|
-
'⑤': 1.0,
|
|
98
|
-
'⑥': 1.0,
|
|
99
|
-
'⑦': 1.0,
|
|
100
|
-
'⑧': 1.0,
|
|
101
|
-
'⑨': 1.0,
|
|
102
|
-
'⑩': 1.0,
|
|
103
|
-
'⑪': 1.0,
|
|
104
|
-
'⑫': 1.0,
|
|
105
|
-
'⑬': 1.0,
|
|
106
|
-
'⑭': 1.0,
|
|
107
|
-
'⑮': 1.0,
|
|
108
|
-
'⑯': 1.0,
|
|
109
|
-
'⑰': 1.0,
|
|
110
|
-
'⑱': 1.0,
|
|
111
|
-
'⑲': 1.0,
|
|
112
|
-
'⑳': 1.0,
|
|
113
|
-
// 로마 숫자/알파벳 대문자 (반각이지만 한글 폰트에서 조금 넓음)
|
|
114
|
-
'I': 0.4,
|
|
115
|
-
'V': 0.7,
|
|
116
|
-
'X': 0.7,
|
|
29
|
+
// 전각 기호 (실측 0.996em: ○ ● □ ■ ▶ ①)
|
|
30
|
+
'○': 0.996,
|
|
31
|
+
'●': 0.996,
|
|
32
|
+
'◆': 0.996,
|
|
33
|
+
'◇': 0.996,
|
|
34
|
+
'■': 0.996,
|
|
35
|
+
'□': 0.996,
|
|
36
|
+
'★': 0.996,
|
|
37
|
+
'☆': 0.996,
|
|
38
|
+
'◎': 0.996,
|
|
39
|
+
'◉': 0.996,
|
|
40
|
+
'▶': 0.996,
|
|
41
|
+
'▷': 0.996,
|
|
42
|
+
'►': 0.996,
|
|
43
|
+
'➢': 0.996,
|
|
44
|
+
'➣': 0.996,
|
|
45
|
+
'➤': 0.996,
|
|
46
|
+
'→': 0.996,
|
|
47
|
+
'⇒': 0.996,
|
|
48
|
+
'▣': 0.996,
|
|
49
|
+
'▤': 0.996,
|
|
50
|
+
'▥': 0.996,
|
|
51
|
+
// 폭이 좁은 기호 (실측: ※ 0.756, • 0.456)
|
|
52
|
+
'※': 0.756,
|
|
53
|
+
'•': 0.456,
|
|
54
|
+
'▪': 0.456,
|
|
55
|
+
'▻': 0.456,
|
|
56
|
+
'▸': 0.456,
|
|
57
|
+
'▹': 0.456,
|
|
58
|
+
// 체크·별 기호 (실측 ✓ 0.996)
|
|
59
|
+
'✓': 0.996,
|
|
60
|
+
'✔': 0.996,
|
|
61
|
+
'✗': 0.996,
|
|
62
|
+
'✘': 0.996,
|
|
63
|
+
'✦': 0.996,
|
|
64
|
+
'✧': 0.996,
|
|
65
|
+
// 대시 (실측 - 0.834, — 0.852)
|
|
66
|
+
'-': 0.834,
|
|
67
|
+
'–': 0.834,
|
|
68
|
+
'—': 0.852,
|
|
69
|
+
// 숫자 (실측 1 0.576, 0 0.588)
|
|
70
|
+
'0': 0.588,
|
|
71
|
+
'1': 0.576,
|
|
72
|
+
'2': 0.588,
|
|
73
|
+
'3': 0.588,
|
|
74
|
+
'4': 0.588,
|
|
75
|
+
'5': 0.588,
|
|
76
|
+
'6': 0.588,
|
|
77
|
+
'7': 0.588,
|
|
78
|
+
'8': 0.588,
|
|
79
|
+
'9': 0.588,
|
|
80
|
+
// 구두점·공백 (실측 . 0.294, ( 0.498, ) 0.504, 공백 0.498)
|
|
81
|
+
'.': 0.294,
|
|
82
|
+
')': 0.504,
|
|
83
|
+
'(': 0.498,
|
|
84
|
+
':': 0.294,
|
|
85
|
+
' ': 0.498,
|
|
86
|
+
// 알파벳 (실측 A 0.744, B 0.66, I 0.372, a 0.492)
|
|
87
|
+
'I': 0.372,
|
|
88
|
+
'V': 0.744,
|
|
89
|
+
'X': 0.744,
|
|
117
90
|
'L': 0.6,
|
|
118
|
-
'C': 0.
|
|
119
|
-
'D': 0.
|
|
91
|
+
'C': 0.744,
|
|
92
|
+
'D': 0.744,
|
|
120
93
|
'M': 0.9,
|
|
121
|
-
'A': 0.
|
|
122
|
-
'B': 0.
|
|
123
|
-
'E': 0.
|
|
94
|
+
'A': 0.744,
|
|
95
|
+
'B': 0.66,
|
|
96
|
+
'E': 0.66,
|
|
124
97
|
'F': 0.6,
|
|
125
|
-
'G': 0.
|
|
126
|
-
'H': 0.
|
|
127
|
-
|
|
128
|
-
'
|
|
129
|
-
'
|
|
130
|
-
'
|
|
131
|
-
'
|
|
132
|
-
'e': 0.55,
|
|
98
|
+
'G': 0.744,
|
|
99
|
+
'H': 0.744,
|
|
100
|
+
'a': 0.492,
|
|
101
|
+
'b': 0.492,
|
|
102
|
+
'c': 0.492,
|
|
103
|
+
'd': 0.492,
|
|
104
|
+
'e': 0.492,
|
|
133
105
|
'f': 0.35,
|
|
134
|
-
'g': 0.
|
|
135
|
-
'h': 0.
|
|
136
|
-
// 콜론 (마커 뒤에 올 수 있음)
|
|
137
|
-
':': 0.35,
|
|
138
|
-
// 법률/공문서 한글 문자
|
|
139
|
-
'제': 1.0,
|
|
140
|
-
'조': 1.0,
|
|
141
|
-
'항': 1.0,
|
|
142
|
-
'호': 1.0,
|
|
143
|
-
'목': 1.0,
|
|
144
|
-
'의': 1.0,
|
|
106
|
+
'g': 0.492,
|
|
107
|
+
'h': 0.492,
|
|
145
108
|
};
|
|
109
|
+
/** 한글 음절 전진폭 (실측 가·본 0.972em). */
|
|
110
|
+
const HANGUL_SYLLABLE_WIDTH = 0.972;
|
|
111
|
+
/** 원문자·전각 기호 기본 전진폭 (실측 ① 0.996em). */
|
|
112
|
+
const FULL_WIDTH_SYMBOL = 0.996;
|
|
146
113
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
*
|
|
153
|
-
* 실측 기반 값 (한글에서 실제 렌더링 너비 / em 값)
|
|
114
|
+
* 폰트별 보정 계수 — CHAR_WIDTH_TABLE(함초롬바탕 실측) 대비 배율.
|
|
115
|
+
*
|
|
116
|
+
* 함초롬 계열만 한/글에서 실측했다(1.0). 나머지는 측정하지 않았으므로
|
|
117
|
+
* 종전 표가 함초롬(1.25) 대비 두던 **상대 비율**만 옮겼다
|
|
118
|
+
* (예: 맑은 고딕 1.35/1.25 = 1.08). 새 폰트를 쓰는 양식이 생기면
|
|
119
|
+
* scripts 쪽 측정 문서로 다시 재서 이 값을 교체한다.
|
|
154
120
|
*/
|
|
155
121
|
const FONT_FACTOR_TABLE = {
|
|
156
|
-
// 기본값
|
|
157
|
-
'default': 1.
|
|
158
|
-
// 한컴 폰트
|
|
159
|
-
'함초롬바탕': 1.
|
|
160
|
-
'함초롬돋움': 1.
|
|
161
|
-
'
|
|
162
|
-
'
|
|
163
|
-
|
|
164
|
-
'
|
|
165
|
-
|
|
166
|
-
'
|
|
167
|
-
'
|
|
168
|
-
'
|
|
169
|
-
'
|
|
170
|
-
'
|
|
171
|
-
|
|
172
|
-
'
|
|
173
|
-
|
|
174
|
-
'
|
|
175
|
-
'
|
|
176
|
-
'
|
|
177
|
-
|
|
178
|
-
'
|
|
179
|
-
|
|
180
|
-
'
|
|
181
|
-
'
|
|
182
|
-
|
|
183
|
-
'
|
|
184
|
-
|
|
122
|
+
// 기본값 — 한/글 새 문서 기본 글꼴이 함초롬바탕이다
|
|
123
|
+
'default': 1.0,
|
|
124
|
+
// 한컴 폰트 (실측)
|
|
125
|
+
'함초롬바탕': 1.0,
|
|
126
|
+
'함초롬돋움': 1.0,
|
|
127
|
+
'HCR Batang': 1.0,
|
|
128
|
+
'HCR Dotum': 1.0,
|
|
129
|
+
'한컴바탕': 1.04,
|
|
130
|
+
'한컴돋움': 1.04,
|
|
131
|
+
// 마이크로소프트 폰트 (미측정, 종전 상대비)
|
|
132
|
+
'맑은 고딕': 1.08,
|
|
133
|
+
'맑은고딕': 1.08,
|
|
134
|
+
'Malgun Gothic': 1.08,
|
|
135
|
+
'바탕': 1.04,
|
|
136
|
+
'돋움': 1.04,
|
|
137
|
+
'굴림': 1.04,
|
|
138
|
+
'궁서': 1.08,
|
|
139
|
+
// 나눔 폰트 (미측정, 종전 상대비)
|
|
140
|
+
'나눔고딕': 1.04,
|
|
141
|
+
'나눔명조': 1.04,
|
|
142
|
+
'NanumGothic': 1.04,
|
|
143
|
+
'NanumMyeongjo': 1.04,
|
|
144
|
+
'나눔바른고딕': 1.024,
|
|
145
|
+
// Adobe 폰트 (미측정, 종전 상대비)
|
|
146
|
+
'본고딕': 1.0,
|
|
147
|
+
'본명조': 1.0,
|
|
148
|
+
'Noto Sans KR': 1.0,
|
|
149
|
+
'Noto Serif KR': 1.0,
|
|
150
|
+
// 영문 폰트 (미측정, 종전 상대비)
|
|
151
|
+
'Arial': 0.8,
|
|
152
|
+
'Times New Roman': 0.8,
|
|
185
153
|
};
|
|
186
154
|
/**
|
|
187
155
|
* 폰트 보정 계수 가져오기
|
|
@@ -247,17 +215,19 @@ class HangingIndentCalculator {
|
|
|
247
215
|
if (CHAR_WIDTH_TABLE[char] !== undefined) {
|
|
248
216
|
return CHAR_WIDTH_TABLE[char];
|
|
249
217
|
}
|
|
250
|
-
// 한글
|
|
218
|
+
// 한글 음절 (가-힣)
|
|
251
219
|
if (/[가-힣]/.test(char)) {
|
|
252
|
-
return
|
|
220
|
+
return HANGUL_SYLLABLE_WIDTH;
|
|
253
221
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (code >=
|
|
257
|
-
|
|
222
|
+
const code = char.codePointAt(0) ?? 0;
|
|
223
|
+
// 원문자 ①–⑳ 등 Enclosed Alphanumerics, 도형 기호 블록, 전각 형태
|
|
224
|
+
if ((code >= 0x2460 && code <= 0x24FF) ||
|
|
225
|
+
(code >= 0x25A0 && code <= 0x25FF) ||
|
|
226
|
+
(code >= 0xFF00 && code <= 0xFFEF)) {
|
|
227
|
+
return FULL_WIDTH_SYMBOL;
|
|
258
228
|
}
|
|
259
|
-
//
|
|
260
|
-
return 0.
|
|
229
|
+
// 표에 없는 반각 문자 — 실측한 소문자 폭을 쓴다
|
|
230
|
+
return 0.492;
|
|
261
231
|
}
|
|
262
232
|
/**
|
|
263
233
|
* 마커 문자열의 너비를 em 단위로 계산
|