Repository navigation
[serializer/hwp] 편집으로 만든 글상자의 SHAPE_COMPONENT 저장 워드에 글상자 비트(0x0100_0000)가 없어 한컴 2024가 HWP를 손상 문서로 거부한다 #8172
Copy link
Copy link
Closed
Description
증상
편집 API createShapeControl로 **글상자(shapeType: 'textbox')**를 넣은 문서를 HWP로 저장하면, 한컴 2024(13.0.0.3901)가 열 때 "문서가 손상되었거나 변조되었을 수 있음"이라고 경고한다. 무인 변환은 거부된다(HANCOM_DOCUMENT_OPEN_REJECTED).
- studio의 글상자 그리기(
input-handler.ts의shapePlacementType = 'textbox')도 같은 경로를 쓴다. 그래서 studio에서 글상자를 그린 뒤 HWP로 저장한 문서가 모두 해당될 수 있다. - 같은 문서를 HWPX로 저장하면 한컴이 정상으로 연다.
원인 — SHAPE_COMPONENT offset 36 저장 워드에 글상자 비트가 없다
src/document_core/commands/object_ops/shape.rs:1309(create_shape_control_native)는 도형 종류와 관계없이 다음 값을 넣는다.
flip: 0x00080000, // 한컴 기본값글상자에서도 0x0008_0000이고 글상자 비트 0x0100_0000이 없다. 저장소의 다른 두 경로는 이미 글상자에 이 비트를 세운다.
src/document_core/converters/hwpx_to_hwp.rs:1051-1066: 글상자가 실재할 때만0x0108_0000을 쓴다. 주석에 "이 storage 비트가 없으면 한컴이 개체 이후 레코드 스트림을 이어 읽지 못하는 케이스"와 "글상자 없는 일반 사각형에 세우면 한컴이 거부"가 기록되어 있다.src/parser/hwpx/section.rs:3651:ShapeStorageKind::TextBoxDrawing => 0x0100_0000이다(Hancom 2020의 HWP5 저장값 근거).
편집 생성 경로만 이 계약을 따르지 않는다.
근거 — 단계 분리와 한 필드 교체 실험 (한컴 2024 MCP, engine 2024)
입력 (모두 createBlankDocument 기반, exportHwp) |
한컴 2024 |
|---|---|
| 빈 문서 / 글 입력 / 문단 나누기 | 정상 |
떠 있는 사각형(rectangle) |
정상 |
그림(insertPicture) |
정상 |
빈 글상자(textbox) |
거부 |
실문서 rhwp-studio/public/samples/biz_plan.hwp 다시 저장 |
정상 |
| 같은 실문서 + 글상자 1개 | 거부 |
글상자 문서의 Section0만 바꿔 다시 포장했다. 다른 스트림은 그대로이고 CFB V3로 포장했다.
| 변형 | 한컴 2024 |
|---|---|
| rhwp Section0 그대로(포장만 다시) | 거부 |
| 사각형 문서를 같은 방식으로 다시 포장 | 정상 (포장 방법은 무관) |
| 한컴이 같은 문서(HWPX 경유)를 HWP로 저장한 Section0 | 정상 |
한컴 Section0에서 차이 레코드를 하나씩 rhwp 값으로 되돌림: CTRL_HEADER 끝 2바이트 / LIST_HEADER 속성(0x20) / 안쪽 LINE_SEG 폭 / 첫 PARA_HEADER·쪽 테두리 |
모두 정상 |
| 한컴 Section0에서 SHAPE_COMPONENT만 rhwp 값으로 되돌림 | 거부 |
rhwp Section0에서 SHAPE_COMPONENT offset 36 워드만 0x0008_0000 → 0x0100_0000(한컴 값) |
정상 |
같은 워드만 0x0108_0000(hwpx_to_hwp 값) |
정상 |
같은 레코드의 다른 차이(그림자 색 b2b2b2)만 한컴 값 |
거부 |
결론: 거부를 일으키는 것은 글상자 SHAPE_COMPONENT offset 36 워드에 0x0100_0000 비트가 빠진 것이다. 사각형은 같은 0x0008_0000으로도 정상으로 열린다.
재현
const d = HwpDocument.createEmpty(); d.createBlankDocument();
d.insertText(0, 0, 0, '앞 글 뒤 글');
d.createShapeControl(JSON.stringify({ sectionIdx: 0, paraIdx: 0, charOffset: 7,
width: 17008, height: 5669, horzOffset: 0, vertOffset: 2000,
treatAsChar: false, textWrap: 'TopAndBottom', shapeType: 'textbox' }));
fs.writeFileSync('textbox.hwp', d.exportHwp()); // 한컴 2024: 손상 경고rhwp dump-records textbox.hwp의 SHAPE_COMPONENT에서 … 25 16 00 00 | 00 00 08 00 | 00 00 38 21 …가 offset 36 워드이다.
수정 방향 제안
- 편집 생성 경로도 글상자가 실재하면 글상자 비트를 세운다. 예를 들어
hwpx_to_hwp와 같은0x0108_0000을 쓴다. - 글상자 없는 도형은 지금처럼
0x0008_0000을 유지한다. - 같은 규칙을 공통 helper로 묶어 세 경로(HWPX parser materialize, hwpx_to_hwp, 편집 생성)가 함께 쓰게 하는 편이 좋다.
- 반례로 둘 사례: 글상자 없는 사각형(비트를 세우면 거부된다는 기존 기록), 기존 문서에서 불러온 도형의 원래 flip 값 보존.
- 미확인 관련 경로: 묶음 생성(
shape.rs:2093,0x00080000)과 기존 사각형에 글을 넣어 글상자가 생기는 편집. 수정할 때 함께 확인이 필요하다.
검증 기준
- upstream/devel
605ccfd92에서 빌드한 WASM(pkg/)으로 실험했다. - 한컴 2024 MCP(
hwp2024Convert, engine 2024, hancom_version 13.0.0.3901)를 사용했다. - 재현 파일은 모두 위 스크립트로 만든 공개 합성 입력이거나 저장소의 공개 샘플이다.
- 발견 경위: rhwp-studio AI 도우미 작업([rhwp-studio] AI 도우미 대화 패널 — 문서 읽기·편집·새 문서 작성 (P0, Claude) #8169, PR feat(studio): AI 도우미 대화 패널 — 문서 읽기·편집·새 문서 작성 (P0, Claude) #8170) 중 [layout] 편집으로 넣은 글자처럼 취급 글상자가 줄 안이 아니라 줄 위로 넘겨 그려진다 — 한컴은 줄을 상자 높이만큼 내림 (합성 입력) #8167·#8168의 한컴 기준 PDF를 만들다가 HWP 저장본이 거부되어 조사했다.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
feat: 기여 PR 10건 통합과 조판·Studio 회귀 보정 (#8228)
planet6897 commentedon Oct 26, 2026
devel 반영 확인 — 닫습니다
PR #8176 의 수정이 #8228(
3e02fe274)로 devel 에 들어갔습니다. devel4e690c930으로 다시 확인했습니다.issue_8172_textbox_shape_storage_bit3건 PASS:created_textbox_carries_storage_bit_and_rectangle_does_notstorage_bit_survives_hwp_save_and_reopenattaching_and_detaching_text_box_keeps_storage_bit_in_synccreateBlankDocument→createShapeControl→exportHwp). 이 파일을 COMOpen으로 열었고, 확인 질문은 모두 「아니요·취소」로 답하게 했습니다.7f813a048)이전 빌드의 거부는 이 판정이 결함을 실제로 잡는다는 음성 대조입니다. 현행 빌드 산출물은 한/글 2024 Print(Hancom PDF)로도 정상 출력됩니다.