소개

시작하기

npm 패키지 설치부터 뷰어 배포와 첫 저장 API 연결까지 적용하는 절차입니다. 소스와 배포 조건은 GitHub 저장소의 Apache-2.0 LICENSE에서 확인할 수 있습니다.

사전 준비

  • Node.js와 npminko-pdf-sdk 패키지를 설치하는 데 사용합니다.
  • 정적 파일을 서빙할 수 있는 웹 서버 — Nginx·Apache·Tomcat·IIS 등 무엇이든 가능합니다.
정적 배포 방식입니다

npm은 검증된 정적 산출물을 받는 설치 경로입니다. 실행 시 별도 서버 모듈이나 번들러 없이 정적 파일과 <script>로 로드할 수 있습니다. 호스트 앱의 저장 API·인증·권한·보안 정책은 별도로 구현하고 검증해야 합니다.

1단계 — npm 패키지 설치

terminal bash
npm install inko-pdf-sdk

설치된 패키지의 viewer/에는 뷰어 본체가, sdk/inko-sdk.js에는 호스트 페이지용 래퍼가 들어 있습니다.

npm 파일명과 NextH 데모 파일명

npm 패키지는 래퍼를 sdk/inko-sdk.js로 제공합니다. NextH의 self-hosted 데모는 같은 공개 API 번들을 기존 산출물 이름인 /pdfv/sdk/pdfv-sdk.js에서 제공합니다. 실제 배포한 파일명에 맞춰 script src를 지정하세요. 두 번들 모두 window.Inko만 등록합니다.

2단계 — 뷰어 배포

node_modules/inko-pdf-sdk/viewer/의 내용과 node_modules/inko-pdf-sdk/sdk/inko-sdk.js를 이용자 서버 /pdfv/ 경로에 위 구조대로 배치합니다.

배포 후 디렉터리 구조
host-server/
└─ pdfv/                  ← 이용자 서버에서 직접 제공하는 정적 자산
   ├─ index.html          ← 뷰어 본체 (iframe으로 로드됨)
   ├─ assets/             ← 뷰어 JS·CSS 번들
   └─ sdk/
      └─ inko-sdk.js      ← 호스트 페이지에서 로드하는 SDK 래퍼
Same-Origin Sub-Path 권장

뷰어를 호스트 페이지와 같은 도메인의 하위 경로(예: https://erp.example.com/pdfv/)에 두면 뷰어와 호스트 사이의 origin 구성이 단순해집니다. 별도 도메인 배포는 CSP·frame-ancestors·PDF CORS를 구성하고 도입 환경에서 호환성을 검증해야 합니다.

3단계 — SDK 로드

PDF를 띄울 페이지에서 SDK 스크립트를 로드합니다. 브라우저 전역 객체 window.Inko가 등록됩니다.

호스트 페이지 html
<!-- npm의 sdk/inko-sdk.js를 아래 URL로 배포한 예 -->
<script src="/pdfv/sdk/inko-sdk.js"></script>

4단계 — 마운트

컨테이너 요소를 두고 Inko.mount()를 호출하면, 컨테이너 안에 뷰어 iframe이 생성되고 PDF가 로드됩니다.

최소 마운트 html
<div id="pdf-container" style="width:100%; height:80vh"></div>

<script>
  var viewer = Inko.mount('#pdf-container', {
    src: '/pdfv/index.html',          // 뷰어 본체 URL (필수)
    pdfUrl: '/files/document.pdf',    // 표시할 PDF
    fileName: 'document.pdf'
  });
</script>

src는 1단계에서 배포한 뷰어 본체의 URL입니다. 컨테이너의 크기가 곧 뷰어의 크기이므로, 컨테이너에 명시적인 높이를 지정해야 합니다.

전체 예제 — 저장까지

실제 운영에서는 저장 콜백(onSave)으로 받은 canvasData를 자체 DB에 적재합니다. 아래는 저장·이어서 편집·에러 처리까지 포함한 전체 흐름입니다.

contract-view.html — 전체 예제 html
<div id="pdf-container" style="width:100%; height:80vh"></div>
<script src="/pdfv/sdk/inko-sdk.js"></script>
<script>
  var viewer = Inko.mount('#pdf-container', {
    src: '/pdfv/index.html',
    pdfUrl: '/files/contract.pdf',
    fileName: 'contract.pdf',

    // 직전에 저장해 둔 버전이 있으면 그 지점부터 이어서 편집.
    // SAVED_CANVAS_DATA는 예시용 이름 — 호스트 서버가 페이지에 심어 주는 값입니다 (없으면 undefined)
    initialCanvasData: window.SAVED_CANVAS_DATA || undefined,

    onReady: function () {
      console.log('뷰어 준비 완료');
    },
    onSave: async function (canvasData, ok, msg) {
      if (!ok) { alert('저장 실패: ' + msg); return; }
      // ok는 SDK 직렬화 성공. 호스트 DB 저장 성공은 API 응답으로 확인합니다.
      var response = await fetch('/api/annotations', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ documentId: 'contract-123', baseVersion: 7, canvasData: canvasData })
      });
      if (!response.ok) throw new Error('저장 API 실패');
    },
    onError: function (err) {
      console.error('[Inko]', err);
    }
  });

  // 외부 버튼에서 저장 트리거도 가능
  // document.querySelector('#save-btn').onclick = function () { viewer.save(); };
</script>
드로잉 상태와 PDF 바이트를 구분하세요

save()/onSave는 펜·형광펜·텍스트·도형을 다시 편집할 수 있는 canvasData 문자열을 다룹니다. await viewer.exportPdf()는 지원하는 네이티브 AcroForm의 현재 값을 반영한 ArrayBuffer를 반환하지만 Inko 드로잉은 PDF에 합성하지 않습니다. 두 결과가 필요하면 호스트 저장소에 각각 보관하세요.

예제 속 이름은 자리표시자입니다

window.SAVED_CANVAS_DATA·/api/annotations·docId: 123처럼 예제에 등장하는 변수·API 경로·ID는 설명용 이름입니다. 호스트 앱의 실제 변수와 엔드포인트로 바꿔 사용하세요. 이 규칙은 모든 문서의 예제에 동일하게 적용됩니다.

동작 확인 체크리스트

확인 항목기대 동작
페이지 접속컨테이너 영역에 뷰어 툴바와 PDF 첫 페이지가 표시됩니다.
펜으로 드로잉PDF 위에 필기가 그려지고 onChange 콜백이 호출됩니다.
contentSelect 내용 선택Text Layer가 있는 PDF에서 원문 텍스트를 선택·복사할 수 있습니다. 이미지 PDF에 OCR을 추가하지는 않습니다.
검색·책갈피검색은 가상화된 전체 페이지의 Unicode 리터럴을 찾고, 책갈피는 내장 outline이 있는 문서에서만 표시됩니다.
저장 버튼 클릭onSave 콜백에 canvasData 문자열이 전달됩니다.
exportPdf()지원하는 네이티브 AcroForm 값이 반영된 PDF ArrayBuffer가 반환되며, canvasData 드로잉은 포함되지 않습니다.
저장 후 재접속 + initialCanvasData 주입직전 작업이 복원된 상태로 열립니다.

여기까지 확인되면 통합이 완료된 것입니다. 다음 단계로 저장과 버전 관리에서 버전 테이블 설계를, 커스터마이징에서 브랜드 적용 방법을 확인하세요. 진행 중 막히는 부분이 있으면 FAQ · 트러블슈팅에서 증상별 해결 방법을 찾아보세요.