@rhwp/editor
v0.8.7
Published
HWP 에디터 웹 컴포넌트 — iframe 기반 임베드
Maintainers
Readme
@rhwp/editor
알(R), 모두의 한글 — 3줄로 HWP 에디터를 웹 페이지에 임베드
웹 페이지에 HWP 에디터를 통째로 임베드합니다. 메뉴, 툴바, 서식, 표 편집 — rhwp-studio의 모든 기능을 그대로 사용할 수 있습니다.
SDK는 지원되는 Studio와 MessageChannel v1을 협상해 binary를 transferable로 전송합니다.
구버전 Studio에는 기존 postMessage protocol로 자동 전환되며 기존 공개 API는 유지됩니다.
getRendererDiagnostics()는 renderer-diagnostics-v1 capability를 협상한 Studio에서만 사용할 수 있습니다.
getFontDecisionTrace()는 font-decision-trace-v1 capability를 협상한 Studio에서만 사용할 수 있으며,
현재 snapshot을 읽을 뿐 font load, 권한 요청, repaint 또는 backend 변경을 시작하지 않습니다.
호스트와 studioUrl은 HTTP(S) origin만 지원합니다. file:, data:, 브라우저 확장처럼
origin이 null이거나 불투명한 환경의 연결은 SDK와 Studio 양쪽에서 거부합니다.
온라인 데모 에서 먼저 체험해보세요.
설치
npm install @rhwp/editor빠른 시작 — 3줄이면 충분합니다
<!DOCTYPE html>
<html>
<head>
<title>내 HWP 에디터</title>
<style>
#editor { width: 100%; height: 100vh; }
</style>
</head>
<body>
<div id="editor"></div>
<script type="module">
import { createEditor } from '@rhwp/editor';
const editor = await createEditor('#editor');
</script>
</body>
</html>이것만으로 메뉴바, 툴바, 편집 영역, 상태 표시줄이 포함된 완전한 HWP 에디터가 표시됩니다.
웹페이지에서 JavaScript 로 제어하기 — createStudio
createEditor 의 상위 집합입니다. 같은 것을 만들고, studio 의 메뉴·커맨드를 조종하고
문서를 HwpCtrl API 로 편집할 수 있습니다.
import { createStudio } from '@rhwp/editor';
const studio = await createStudio('#app', {
plugins: ['hwpctrl'], // HwpCtrl API 사용
chrome: { statusbar: false }, // 메뉴·툴바·상태표시줄 표시
});
await studio.loadFile(buffer, 'doc.hwp');
// studio 커맨드 — 메뉴·툴바·키보드와 같은 경로를 탑니다
await studio.commands.execute('edit:copy');
const commands = await studio.commands.list(); // {id, label, enabled, opensDialog}
// 문서 편집 — 여러 호출을 한 메시지·한 트랜잭션으로
await studio.hwpctrl.batch(h => {
h.PutFieldText('기안자', '홍길동');
h.PutFieldText('기안일', '2026-08-12');
});
const bytes = await studio.hwpctrl.exportBytes();
await studio.hwpctrl.undo(); // 위 배치 전체가 undo 1스텝
studio.destroy();알아 둘 것 셋.
- 배치를 쓰세요. 실측상 100회 호출이 배치면 postMessage 1회·11ms, 개별이면 100회·583ms 입니다 (약 45배). 배치 하나가 트랜잭션 하나이고 undo 도 1스텝입니다.
- 대화상자를 여는 커맨드는 기본 거절됩니다(
{ok:false, reason:'needs-dialog'}). 자동화가 그것을 열면 사람이 누를 때까지 응답이 멈춥니다. 사용자가 앞에 있는 통합에서는studio.commands.execute(id, params, { allowDialog: true })로 풉니다. - 컨테이너 이동은 지원하지 않습니다. iframe 을 다른 요소로 옮기면 브라우저가 문서를 재로드해
편집 상태가 사라집니다. 화면상의 이동·숨김은 컨테이너 CSS 로 하고(
display:none은 상태를 보존합니다), 정말 옮겨야 하면destroy()후 다시 만듭니다.
chrome:{ menu:false } 로 메뉴를 숨겨도 커맨드 레지스트리는 살아 있어 commands.execute 가 계속
동작합니다 — "UI 없는 편집기" 구성이 이것으로 섭니다.
검증
브리지 전 계약(자동화·플러그인·hwpctrl·성능)을 한 번에 확인하려면 저장소에서:
cd rhwp-studio && npm run gate:bridgeHWP 파일 로드
import { createEditor } from '@rhwp/editor';
const editor = await createEditor('#editor');
// 파일 선택 또는 fetch로 HWP 데이터 가져오기
const response = await fetch('document.hwp');
const buffer = await response.arrayBuffer();
// 에디터에 로드
const result = await editor.loadFile(buffer, 'document.hwp');
console.log(`${result.pageCount}페이지 로드 완료`);API
createEditor(container, options?)
에디터를 생성하고 컨테이너에 마운트합니다.
const editor = await createEditor('#editor');
// 또는
const editor = await createEditor(document.getElementById('editor'));옵션:
| 옵션 | 기본값 | 설명 |
|------|--------|------|
| studioUrl | https://edwardkim.github.io/rhwp/ | rhwp-studio HTTP(S) URL. opaque origin은 지원하지 않음 |
| width | '100%' | iframe 너비 |
| height | '100%' | iframe 높이 |
| renderer | 'canvas2d' | 문서 단위 renderer 요청. auto, canvas2d, canvaskit 중 하나 |
| requestTimeoutMs | method별 기본값 | 모든 method 제한 시간 override(ms). 일반 10초, load/export 60초 |
| handshakeTimeoutMs | 1000 | v1 협상 후 legacy 전환까지의 제한 시간(ms) |
editor.loadFile(data, fileName?, options?)
HWP 파일을 로드합니다.
const result = await editor.loadFile(buffer, 'sample.hwp');
// result = { pageCount: 5 }
// 안내창에서 사용자가 직접 선택하도록 열기
await editor.loadFile(buffer, 'sample.hwpx', { suppressDialogs: false });options:
| 옵션 | 기본값 | 설명 |
|------|--------|------|
| skipUnsavedGuard | false | 미저장 변경 확인 없이 문서 교체 |
| suppressDialogs | true | 로드 후 안내창(HWPX 검증, 로컬 글꼴 감지) 없이 열기 |
suppressDialogs를 생략하면 검증 경고는 '그대로 열기'로 처리하고 글꼴은 웹 대체 글꼴로 표시하여loadFile응답을 즉시 반환합니다. 안내창(HWPX 비표준 lineseg 검증, 로컬 글꼴 감지)에서 사용자가 직접 선택해야 하는 대화형 흐름이 필요하면suppressDialogs: false를 명시하세요.
editor.pageCount()
현재 문서의 페이지 수를 반환합니다.
const count = await editor.pageCount();문서 에이전트 exact command
서버가 만든 HWP/HWPX 문단 교체 후보는 현재 문서의 epoch, change sequence, 전체 문서 SHA, target 원문·서식·인접 문맥 SHA를 모두 결속한 뒤 한 snapshot transaction으로 적용할 수 있습니다.
const state = await editor.getDocumentState();
const selection = await editor.getSelectionContext();
if (!selection.editable || !selection.target) throw new Error('편집할 수 없는 target');
const receipt = await editor.applyTextCommand({
schemaVersion: 1,
commandId: crypto.randomUUID(),
expectedDocumentEpoch: state.documentEpoch,
expectedChangeSeq: state.changeSeq,
expectedDocumentSha256: state.documentSha256,
target: selection.target,
expectedBeforeSha256: candidate.beforeSha256,
expectedFormatSha256: candidate.formatSha256,
expectedAdjacentContextSha256: candidate.adjacentContextSha256,
replacement: candidate.replacement,
});
await editor.focusTarget(receipt.target);
const off = editor.onDocumentChanged(event => console.log(event.reason, event.changeSeq));
await editor.revertTextCommand({
schemaVersion: 1,
commandId: receipt.commandId,
expectedDocumentEpoch: receipt.documentEpoch,
expectedChangeSeq: receipt.afterChangeSeq,
expectedAfterDocumentSha256: receipt.afterDocumentSha256,
expectedAfterSha256: receipt.afterTextSha256,
});
off();v1 target은 control·field·혼합 글자 서식이 없는 본문 문단 전체(최대 4,000 Unicode code point)로
제한됩니다. apply 뒤 다른 편집이 있으면 agent revert는 실패합니다. TRANSACTION_FAILED와
RENDER_FAILED에는 snapshot 복구 여부인 error.recovered가 포함됩니다. SHA 정규형과 오류 계약은
document_agent_command.md를
참조하세요.
editor.getPageSvg(page?)
특정 페이지를 SVG 문자열로 렌더링합니다.
const svg = await editor.getPageSvg(0); // 첫 페이지editor.getRendererDiagnostics(page?)
선택된 renderer와 0부터 시작하는 페이지별 readiness 진단을 반환합니다.
Studio가 renderer-diagnostics-v1 capability를 제공하지 않으면 명시적으로 실패합니다.
auto에서는 문서 capability preflight, 실제 선택 이유, fallback 이유, 문서 revision과
resource generation을 selection에서 확인할 수 있습니다. 현재 Studio는 selection을
반환하지만, 같은 v1 capability를 제공하는 이전 Studio와의 additive 호환을 위해 이 필드는
없을 수도 있습니다. selectionError는 preflight/리소스/replay 실패를, top-level
initializationError는 Studio 앱 초기화 실패를 나타냅니다. preflight의
requiredFontFamilies는 자동 선택에서 첫 replay 전에 준비해야 하는 문서 폰트 목록입니다.
기존 v1 소비자 호환을 위해 top-level request.backend.backend는 계속 canvas2d 또는
canvaskit만 반환하며, auto 요청과 실제 선택 과정은 additive selection에서 확인합니다.
const diagnostics = await editor.getRendererDiagnostics(0);
console.log(diagnostics.schemaVersion, diagnostics.effectiveBackend);editor.getFontDecisionTrace(page?, options?)
문자가 document face에서 layout metric과 backend paint 후보까지 도달한 계보를 반환합니다.
page는 0부터 시작하고 maxCharacters는 1..4096만 허용합니다. 기본값은 1024이며, 상한에 도달하면
조용히 자르지 않고 status: 'truncated', recordsOmitted와 reason을 반환합니다. Studio가
font-decision-trace-v1 capability를 제공하지 않거나 입력이 범위를 벗어나면 명시적으로 실패합니다.
const trace = await editor.getFontDecisionTrace(0, { maxCharacters: 256 });
console.log(trace.schemaVersion, trace.status, trace.layoutHash.value);
for (const record of trace.records) {
console.log(
record.source.character,
record.document.face,
record.layoutMetric.widthSource,
record.paint.canvas2d.certainty,
record.paint.canvaskit.certainty,
);
}layoutHash는 backend 환경과 무관한 portable layout 계보를, normalizedHash는 현재 backend capability
snapshot까지 포함한 결과를 고정합니다. Canvas2D가 실제 glyph face를 공개하지 않으면 CSS chain만
기록하고 certainty: 'notObserved'로 남깁니다. trace는 font 선택 authority나 fallback 교정 API가
아닙니다.
editor.exportHwp()
현재 편집 중인 문서를 HWP 바이너리로 내보냅니다.
const bytes = await editor.exportHwp();
const blob = new Blob([bytes], { type: 'application/x-hwp' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'document.hwp';
a.click();
URL.revokeObjectURL(url);editor.exportHwpx()
현재 편집 중인 문서를 HWPX(ZIP+XML) 바이너리로 내보냅니다.
const bytes = await editor.exportHwpx();
const blob = new Blob([bytes], { type: 'application/vnd.hancom.hwpx' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'document.hwpx';
a.click();
URL.revokeObjectURL(url);editor.exportHwpVerify()
HWP 직렬화 + 자기 재로드 검증 메타데이터를 반환합니다 (#178).
검증 메타데이터만 반환하며, 실제 HWP bytes 가 필요하면 exportHwp() 를 별도 호출하세요.
const verify = await editor.exportHwpVerify();
// {
// bytesLen: 678912,
// pageCountBefore: 9,
// pageCountAfter: 9,
// recovered: true
// }
if (!verify.recovered || verify.pageCountBefore !== verify.pageCountAfter) {
console.warn('HWP 직렬화 검증 실패', verify);
}editor.exportHml()
현재 편집 중인 문서를 HML(XML) 바이너리로 내보냅니다.
const bytes = await editor.exportHml();
const blob = new Blob([bytes], { type: 'application/xml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'document.hml';
a.click();
URL.revokeObjectURL(url);editor.getHmlSaveState()
현재 문서의 HML 저장 가능 여부와 blocker 목록을 반환합니다.
저장 가능 여부는 hmlSavable로 판정하고, 실패 원인은 blockers 배열로 표시하세요.
exportHml()의 오류 문자열을 파싱하지 마세요.
const state = await editor.getHmlSaveState();
// {
// sourceFormat: 'hwp',
// hmlSavable: false,
// blockers: [
// {
// code: 'HML_SOURCE_REQUIRED',
// xmlPath: '/HWPML',
// message: 'HML source metadata is required',
// preserved: false
// }
// ]
// }
if (state.hmlSavable) {
const bytes = await editor.exportHml();
// ... 저장 진행
} else {
for (const item of state.blockers) {
console.warn(`HML 저장 불가 [${item.code}] ${item.xmlPath} — ${item.message}`);
}
}반환값:
| 필드 | 타입 | 설명 |
|------|------|------|
| sourceFormat | string | 현재 문서의 원본 형식 — 'hwp', 'hwpx', 'hml' |
| hmlSavable | boolean | HML로 저장 가능하면 true |
| blockers | HmlSaveBlocker[] | 저장을 막는 요소 목록. hmlSavable이 true이면 빈 배열 |
HmlSaveBlocker:
| 필드 | 타입 | 설명 |
|------|------|------|
| code | string | blocker 종류 코드 |
| xmlPath | string | 해당 요소의 XML 경로 |
| message | string | 사람이 읽을 수 있는 사유 |
| preserved | false | 항상 false — 해당 요소가 HML로 보존되지 않음을 뜻함 |
editor.notifySaved(fileName?)
내보내기 바이트의 영속화(서버 업로드 또는 호스트 핸드오프) 완료를 스튜디오에 통지합니다 (#2660). 통지 시 스튜디오는 미저장(dirty) 상태를 해제하고 자동복구 draft(IndexedDB)를 삭제합니다. draft 삭제가 완료된 뒤 resolve되므로, resolve 이후 창을 닫아도 안전합니다.
const bytes = await editor.exportHwp();
await uploadToServer(bytes); // 호스트 저장
await editor.notifySaved(); // 저장 완료 통지 — dirty 해제 + 복구 draft 삭제fileName(선택): 호스트가 저장에 사용한 파일 이름. 전달하면 스튜디오의 현재 파일 이름이 갱신됩니다.- 반환:
{ ok: true, wasDirty: boolean }—wasDirty는 통지 시점에 미저장 변경이 있었는지 여부(멱등 호출 관찰용). - 스튜디오가
notify-saved-v1capability를 광고하지 않으면(구버전 스튜디오 또는 legacy 폴백 연결) 요청을 보내지 않고 예외를 던집니다.
editor.destroy()
에디터를 제거합니다.
editor.destroy();저장 계약 — 내보내기와 저장 완료 통지
exportHwp()/exportHwpx()/exportHml()은 직렬화 바이트만 반환하며 문서를
"저장됨"으로 표시하지 않습니다. 호스트 저장(업로드)이 실패할 수 있으므로,
스튜디오는 호스트가 notifySaved()로 확정하기 전까지 자동복구 draft를
보존합니다. 통지 없이 종료하면 다음 실행 시 "문서 복구" 안내가 표시됩니다.
iframe 임베드 (업로드 확정 후 통지):
const bytes = await editor.exportHwp();
try {
await uploadToServer(bytes);
await editor.notifySaved(); // 업로드 "성공 시에만" 호출
} catch (error) {
// 통지하지 않음 — 복구 draft가 보존되어 재시도/복구 가능
}팝업 통합 (핸드오프 즉시 통지 후 닫기):
스튜디오를 window.open으로 띄우고 window.opener.postMessage로 바이트를
넘기는 통합에서는, 핸드오프 시점에 통지하고 닫습니다. 바이트를 수신한 호스트가
이후 영속화(재시도 포함)를 책임집니다.
window.opener?.postMessage(
{ action: 'documentExported', content: base64, format: 'hwp' },
HOST_ORIGIN, // '*' 대신 반드시 명시적 오리진 사용 (문서 유출 방지)
);
await editor.notifySaved(); // draft 삭제 완료까지 대기
window.close();SDK 없이 스튜디오 페이지 안에서 직접 통합(포크/주입)하는 경우에는 같은 동작의
window.rhwpStudio.notifySaved(fileName?)를 사용할 수 있습니다.
디버깅 도구 — rhwpDev
iframe 안의 rhwp-studio에는 개발 모드 전용 디버깅 헬퍼 rhwpDev가 내장되어 있습니다. 검색, 커서 이동, 문단 ID 점검 등을 콘솔에서 직접 호출할 수 있습니다.
접근 방법
rhwpDev는 iframe 내부 window에 등록됩니다. 브라우저 DevTools의 콘솔 컨텍스트를 iframe으로 전환한 후 호출하세요.
Chrome / Edge: DevTools 콘솔 좌측 상단의 컨텍스트 드롭다운(top ▾)에서 rhwp-studio iframe 선택
Firefox: 콘솔 우측 상단의 iframe 버튼 → iframe URL 선택
// iframe 컨텍스트로 전환 후
rhwpDev.help(); // 사용법 안내주의: rhwp-studio가 DEV 모드(vite dev server)로 빌드된 경우에만
rhwpDev가 등록됩니다. 프로덕션 빌드(기본 호스팅 URLhttps://edwardkim.github.io/rhwp/)에는 포함되지 않습니다. 디버깅 도구가 필요하면 셀프 호스팅 환경에서vite dev모드로 실행하세요.
주요 메서드
rhwpDev.search(text, includeCells?)
문서 영역에서 모든 매치를 일괄 검색합니다. 반환: SearchHit[]
const hits = rhwpDev.search("사업계획");
// → 9 match(es), console.table 자동 표시
//
// [{sec: 0, para: 2, charOffset: 0, length: 13}, ...]
// 표 셀/글상자 내부 포함
const allHits = rhwpDev.search("사업계획", true);rhwpDev.goto(hit, options?)
SearchHit으로 커서를 이동하고 화면을 스크롤합니다. 반환: boolean
const hits = rhwpDev.search("사업계획");
// 3번째 매치로 이동 + 선택 (인덱스 2)
rhwpDev.goto(hits[2]);
// 캐럿만 이동 (선택 없음)
rhwpDev.goto(hits[2], { select: false });
// 한 줄 패턴
rhwpDev.goto(rhwpDev.search("text")[0]);rhwpDev.showAllIds(pageNum?)
페이지의 모든 문단 ID를 console.table로 표시합니다.
rhwpDev.showAllIds(0); // 첫 페이지만
rhwpDev.showAllIds(); // 전체 페이지rhwpDev.findNearest(targetId, pageNum?)
가장 가까운 paragraph 인덱스를 검색합니다.
rhwpDev.findNearest(618);
// → { paraIdx: 0, distance: 618, text: "...", container: "cell[p0,c2,i0]" }rhwpDev.help()
전체 사용법과 예시를 콘솔에 출력합니다.
SearchHit 구조
interface SearchHit {
sec: number; // 구역 인덱스
para: number; // 본문: 문단 인덱스 / 셀: parentPara
charOffset: number; // 문단 내 글자 오프셋
length: number; // 매치 길이
cellContext?: { // 셀/글상자 매치 시
parentPara: number;
ctrlIdx: number;
cellIdx: number;
cellPara: number;
};
}활용 예시 — 매치 일괄 강조
// 모든 "회사" 매치를 순차적으로 보여주기
const hits = rhwpDev.search("회사");
let i = 0;
setInterval(() => {
if (i >= hits.length) return;
rhwpDev.goto(hits[i++]);
}, 1500);폰트 안내
기본 동작 — 별도 설정 없이 사용 가능
@rhwp/editor가 기본으로 연결하는 rhwp-studio 배포본은 오픈소스 폰트를 포함하므로
별도 폰트 설정 없이 바로 사용할 수 있습니다. @rhwp/editor 패키지 자체에는 폰트 파일이나
UI runtime dependency가 포함되지 않습니다.
HWP 문서에서 사용된 한컴 전용 폰트(한컴바탕, HY명조 등)는 자동으로 오픈소스 폰트로 폴백됩니다.
내장 폴백 폰트
| HWP 원본 폰트 | 자동 폴백 | |--------------|----------| | 한컴바탕, HY명조, 바탕, 궁서 | Noto Serif KR → 나눔명조 → serif | | 한컴돋움, HY고딕, 돋움, 굴림 | Noto Sans KR → 나눔고딕 → sans-serif | | 함초롬바탕 | Noto Serif KR → 나눔명조 → serif | | 함초롬돋움, 맑은 고딕 | Pretendard → Noto Sans KR → sans-serif | | Arial, Calibri, Verdana | Pretendard → sans-serif | | Times New Roman | Noto Serif KR → serif | | Courier New | D2Coding → monospace |
폰트 품질
- 내장 폰트만으로 대부분의 HWP 문서를 원본에 가깝게 렌더링할 수 있습니다
- 글자 간격이 원본과 미세하게 다를 수 있으나, 문서 내용 열람에는 지장이 없습니다
- 한컴 전용 폰트가 설치된 PC에서는 원본과 동일하게 표시됩니다
셀프 호스팅 시 폰트
저장소의 canonical source는 assets/fonts/이고, rhwp-studio build는 이를 배포본의 runtime fonts/
경로로 노출합니다. 셀프 호스팅 서버에서는 이 runtime 경로가 함께 배포되므로 별도 설정 없이
오픈소스 폰트를 사용합니다. 추가 폰트는 assets/fonts/에 WOFF2를 추가하고
rhwp-studio/src/core/font-loader.ts에 @font-face와 로딩 정책을 등록해야 합니다.
셀프 호스팅
기본적으로 https://edwardkim.github.io/rhwp/에 호스팅된 에디터를 사용합니다.
자체 서버에서 호스팅하려면:
# rhwp-studio 빌드
cd rhwp-studio
npm install
npx vite build --base=/your-path/
# 빌드 결과물(dist/)을 서버에 배포외부(CDN) 웹폰트를 사용할 수 없는 환경 — 함초롬체 CDN은 비상업적 사용 조건이므로 상업 환경, 또는 오프라인/폐쇄망 — 에서는 빌드 시점 스위치로 CDN 로드를 끕니다. 조판(줄바꿈·페이지 분할)은 내장 폰트 메트릭 기준이라 변하지 않고, 화면 표시만 번들/시스템 글꼴로 대체됩니다:
RHWP_DISABLE_EXTERNAL_WEBFONTS=1 npx vite build --base=/your-path/브라우저 확장 storage에 저장된 disableExternalWebFonts 설정이 있으면
그 값이 빌드 스위치보다 우선합니다.
const editor = await createEditor('#editor', {
studioUrl: 'https://your-domain.com/your-path/'
});패키지 비교
| 패키지 | 용도 | |--------|------| | @rhwp/core | WASM 파서/렌더러 (직접 API 호출) | | @rhwp/editor | 완전한 에디터 UI (iframe 임베드) |
- 뷰어만 필요하면 →
@rhwp/core - 편집 기능이 필요하면 →
@rhwp/editor
Third-Party Licenses
이 패키지는 iframe을 통해 rhwp-studio를 임베드하며, 내부적으로 @rhwp/core WASM 엔진을 사용합니다.
Rust 크레이트 (WASM 엔진)
| 크레이트 | 라이선스 | |---------|---------| | wasm-bindgen / web-sys / js-sys | MIT OR Apache-2.0 | | quick-xml | MIT | | cfb | MIT | | flate2 | MIT OR Apache-2.0 | | encoding_rs | (Apache-2.0 OR MIT) AND BSD-3-Clause | | usvg / svg2pdf | Apache-2.0 OR MIT | | pdf-writer | MIT OR Apache-2.0 | | unicode-segmentation / unicode-width | MIT OR Apache-2.0 | | image | MIT OR Apache-2.0 |
내장 웹 폰트
| 폰트 | 라이선스 | |------|---------| | Pretendard (Regular, Bold) | SIL Open Font License 1.1 | | Noto Sans KR (Regular, Bold) | SIL Open Font License 1.1 | | Noto Serif KR (Regular, Bold) | SIL Open Font License 1.1 | | 나눔고딕 | SIL Open Font License 1.1 | | 나눔명조 | SIL Open Font License 1.1 | | 고운바탕 | SIL Open Font License 1.1 | | 고운돋움 | SIL Open Font License 1.1 | | D2Coding | SIL Open Font License 1.1 |
프론트엔드
| 패키지 | 라이선스 | |--------|---------| | TypeScript | Apache-2.0 | | Vite | MIT |
전체 목록: THIRD_PARTY_LICENSES.md
모든 의존성은 MIT 라이선스와 호환됩니다.
Notice
본 제품은 한글과컴퓨터의 한글 문서 파일(.hwp) 공개 문서를 참고하여 개발하였습니다.
Trademark
"한글", "한컴", "HWP", "HWPX"는 주식회사 한글과컴퓨터의 등록 상표입니다. 본 패키지는 한글과컴퓨터와 제휴, 후원, 승인 관계가 없는 독립적인 오픈소스 프로젝트입니다.
License
MIT
