Skip to content

[serializer/hwp] 편집으로 만든 글상자의 SHAPE_COMPONENT 저장 워드에 글상자 비트(0x0100_0000)가 없어 한컴 2024가 HWP를 손상 문서로 거부한다 #8172

Closed

Description

@planet6897

증상

편집 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)과 기존 사각형에 글을 넣어 글상자가 생기는 편집. 수정할 때 함께 확인이 필요하다.

검증 기준

Activity

  1. planet6897 commented on Oct 26, 2026

    @planet6897
    ContributorAuthor

    devel 반영 확인 — 닫습니다

    PR #8176 의 수정이 #8228(3e02fe274)로 devel 에 들어갔습니다. devel 4e690c930 으로 다시 확인했습니다.

    • 회귀 검사 issue_8172_textbox_shape_storage_bit 3건 PASS:
      • created_textbox_carries_storage_bit_and_rectangle_does_not
      • storage_bit_survives_hwp_save_and_reopen
      • attaching_and_detaching_text_box_keeps_storage_bit_in_sync
    • 한/글 2024(13.0.0.564) 직접 열기: 이슈의 재현 경로를 fresh WASM 으로 그대로 밟아 HWP 를 만들었습니다(createBlankDocument → createShapeControl → exportHwp). 이 파일을 COM Open 으로 열었고, 확인 질문은 모두 「아니요·취소」로 답하게 했습니다.
    입력 #8228 이전 WASM (7f813a048) 현행 devel WASM
    사각형 1개 열림 열림
    글상자 1개 열기 거부 열림 (개체 1, 1쪽)
    글상자 + 사각형 열기 거부 열림 (개체 2, 1쪽)

    이전 빌드의 거부는 이 판정이 결함을 실제로 잡는다는 음성 대조입니다. 현행 빌드 산출물은 한/글 2024 Print(Hancom PDF)로도 정상 출력됩니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions