macOS에서 편집 가능한 한컴오피스 없이
@rhwp/core0.8.6,kordoc4.12.3,python-hwpx6.3.0만으로 파일을 만들었고, 한컴에서 어떻게 열리는지는 2026-09-07 기준 한컴독스 웹과 거기서 내려받은 한글 for macOS 12.30(구독 없이는 읽기 전용)으로 확인했습니다. 도구들이 빠르게 바뀌고 있어 버전이 다르면 결과도 다를 수 있습니다.
행정 양식 중에는 표 안에 “변경 전 / 변경 후” 사진 칸이 있는 것이 많습니다. 소방서에 내는 이행결과 증빙자료 제출 서식 같은 것인데, 저는 이런 서식을 편집할 수 있는 한컴오피스가 없는 맥에서 프로그램으로 채워야 했습니다. 사진 두 장을 표 칸에 넣어 hwp로 저장하는 것이 전부인 일이었습니다.
결과부터 적으면, 오픈소스 라이브러리로 넣은 사진은 파서와 렌더러에서는 칸 안에 있었지만 한컴에서 열면 표 밖으로 떠 있었습니다. 되는 길은 hwp를 hwpx로 바꿔 XML의 셀 문단 안에 그림을 직접 쓰는 것이었고, 그렇게 만든 hwpx와 거기서 되돌린 hwp는 둘 다 한컴에서 제자리에 열렸습니다. 이 글은 그 실패와 우회, 그리고 그 과정에서 알게 된 한컴의 그림 크기 규칙을 정리한 것입니다.
rhwp로 넣으면 rhwp에서는 들어간 것처럼 보입니다
hwp 파일을 한컴 없이 고쳐 저장하는 오픈소스 중 제가 실제 문서로 검증해 쓰고 있는 것은 rhwp입니다. Rust로 만든 엔진을 WASM으로 감싼 것이라 Node에서 바로 돌고, 문단 추가와 줄배치 재계산, SVG·PNG 렌더까지 됩니다. 사진용 API도 있어서 insertPicture에 셀 경로를 넘겨 표 칸에 그림을 넣는 식으로 쓰게 되어 있습니다.
그래서 소방서 서식의 빈 사진 칸에 더미 이미지 두 장을 넣고 rhwp로 렌더했습니다. 아래처럼 칸 안에 정확히 들어가 있었습니다.

독립 구현 리더인 kordoc으로 같은 파일을 markdown으로 뽑아도 이미지가 표 안에서 읽혔습니다. 파서 통과, 렌더러 통과, 다른 파서 통과까지 세 번을 확인했으니 여기서 끝났다고 생각했습니다.
한컴에서 열었더니 표 밖 2쪽에 떠 있었습니다
같은 파일을 한컴독스에 올리자 사진은 표 안에 없고 다음 쪽에 개체 두 개로 떠 있었습니다. 한글 for macOS로 열어도 똑같았습니다.

원인은 rhwp의 설계에 있었습니다. @rhwp/core의 타입 정의를 보면 insertPicture에 셀 경로를 주면 표 셀 영역에 floating picture로 삽입한다고 설명하고, 반환값도 그림이 들어간 셀이 아니라 표가 놓인 바깥 문단의 새 형제 컨트롤 번호를 돌려줍니다. 즉 그림은 셀 안이 아니라 표 옆에 종이 좌표로 붙어 있고, rhwp 렌더러는 그 좌표를 그대로 그리니 칸 안처럼 보였던 것입니다. 한컴에서는 그 컨트롤이 표 밖의 개체로 나타납니다.
이미 사진이 들어 있는 양식이라면 사정이 다릅니다. 기존 그림 컨트롤을 그대로 두고 이미지 데이터만 바꾸는 assignPictureImage로 만든 파일은 한컴에서 문제없이 열렸습니다. 컨트롤이 원래 셀 안에 있었으니까요. 문제는 빈 칸에 새로 넣는 경우뿐이고, HTML 붙여넣기 API에 <img>를 넣는 방법도 시험했지만 rhwp가 그림 태그를 무시했습니다.
hwpx로 바꾸면 셀 안에 직접 쓸 수 있습니다
hwp는 바이너리라 라이브러리가 열어 주는 길로만 갈 수 있지만, hwpx는 zip 안에 XML이 든 포맷이라 셀 문단을 찾아 그 안에 그림 요소를 직접 넣을 수 있습니다. rhwp는 hwp를 hwpx로 내보내는 exportHwpx와 반대 방향의 exportHwp를 둘 다 갖고 있어서, hwp → hwpx → 편집 → hwp로 돌아오는 경로가 성립합니다.
hwpx 편집은 python-hwpx를 썼습니다. 이 라이브러리의 add_picture는 새 문단을 만들어 그림을 넣는 함수라 표 칸에는 못 쓰지만, 그림 요소를 만드는 내부 함수는 따로 있어서 그것으로 요소만 만들고 셀 문단의 <hp:run>에 직접 끼웠습니다. 핵심은 treatAsChar="1", 즉 글자 취급으로 넣는 것입니다.
import xml.etree.ElementTree as ETfrom lxml import etreefrom hwpx.document import HwpxDocumentfrom hwpx.oxml._document_impl import _create_picture_element
NS = {"hp": "http://www.hancom.co.kr/hwpml/2011/paragraph"}doc = HwpxDocument.open("form.hwpx")sec = doc.sections[0]tbl = [t for t in sec.element.findall(".//hp:tbl", NS) if t.get("rowCnt") == "2" and t.get("colCnt") == "2"][0] # 전·후 사진 칸 표cell = tbl.findall(".//hp:tc", NS)[0]run = cell.find("hp:subList/hp:p/hp:run", NS)
item = doc.media.add_image(open("before.jpg", "rb").read(), "jpg")pic = _create_picture_element(item.item_id, 17000, 12750, treat_as_char=True)run.insert(0, etree.fromstring(ET.tostring(pic))) # 섹션 트리는 lxml이라 변환이 필요
sec.mark_dirty()doc.save_to_path("form-with-photo.hwpx")크기 단위는 HWPUNIT이고 1인치가 7200 HWPUNIT입니다. 17000 × 12750이면 대략 6cm × 4.5cm입니다. 이렇게 만든 hwpx를 한컴독스에 올리자 네 칸 모두 제자리에 들어갔습니다.

hwp로 되돌리자 그림이 확대되어 잘렸습니다
hwpx가 통과했으니 rhwp를 써서 hwp로 되돌려 다시 열었는데, 이번에는 사진이 칸 안에 있긴 한데 몇 배로 확대되어 왼쪽 위 일부만 보였습니다.

rhwp로 두 파일의 그림 속성을 읽으면 값이 완전히 같아서 라이브러리 쪽에서는 원인이 안 보였습니다. 그래서 한컴이 직접 저장한 문서의 그림 레코드를 pyhwp로 덤프해 비교했습니다. 한컴은 그림의 원본 크기를 이미지 픽셀 수 × 75로 적고, 표시 크기는 원본에 대한 축소 비율(scale matrix)로 따로 적습니다. 96dpi 기준으로 1픽셀이 75 HWPUNIT이라 그렇습니다. 800 × 600 픽셀 이미지를 6cm 폭으로 넣는다면 원본 크기 60000 × 45000에 축소 비율 0.2833이 됩니다.
제가 만든 hwpx는 원본 크기 자리에 표시 크기를 그대로 적었습니다. hwpx로 열 때는 그래도 제대로 나왔지만, hwp로 변환된 뒤에는 원본의 일부만 표시 크기 안에 확대되어 보였습니다. orgSz, imgRect, imgClip, imgDim에 픽셀 × 75를 적고 scaMatrix에 축소 비율을 넣어 다시 만들자 hwp도 통과했습니다.
<hp:orgSz width="60000" height="45000"/> <!-- 800×600 px × 75 --><hp:curSz width="17000" height="12750"/><hp:imgRect><hc:pt0 x="0" y="0"/><hc:pt1 x="60000" y="0"/> <hc:pt2 x="60000" y="45000"/><hc:pt3 x="0" y="45000"/></hp:imgRect><hp:imgClip left="0" right="60000" top="0" bottom="45000"/><hp:imgDim dimwidth="60000" dimheight="45000"/><hp:renderingInfo> <hc:scaMatrix e1="0.2833" e2="0" e3="0" e4="0" e5="0.2833" e6="0"/></hp:renderingInfo><hp:pos treatAsChar="1" .../>한컴에서 열리는지는 한컴독스로 확인합니다
한컴독스는 브라우저에서 hwp와 hwpx를 열어 주는 한컴의 웹 서비스입니다. 파일을 올리면 현행 한컴 엔진이 그린 결과를 바로 볼 수 있어서, 한컴 없이 맥이나 리눅스에서 hwp를 만드는 사람에게는 이것이 사실상 유일한 확인 방법입니다. 이 글의 결과도 전부 여기서 열어 본 것이고, 실패 사례는 한글 for macOS에서도 같았습니다.
맥이라면 한컴독스에서 내려받는 한글 for macOS도 있습니다. 구독 없이는 읽기 전용이라 편집은 안 되지만, hwp와 hwpx를 여는 데는 충분해서 파일을 만들 때마다 브라우저에 올리지 않아도 됩니다.
재현은 위 소방서 서식과 아무 JPEG 두 장이면 됩니다. 다음에는 같은 방식으로 표 칸의 긴 문장 줄바꿈이 한컴에서 어떻게 다시 계산되는지를 정리해 볼 생각입니다.