# Viewer Comparison Lab — Hasil Observasi

Tanggal: 2026-10-06. Env: Node 24.21.0, npm 11.19.0, OS linux 7.0.0-38-generic x64,
Chromium headless 154 (verifikasi browser), server `python3 -m http.server 3000 --directory viewer-lab`.

Sumber PDF tunggal: `public/sample-sop.pdf` (5 halaman A4, 8091 byte, dibuat via `pdf-lib@1.17.1`
oleh `tools/generate-sample-pdf.mjs`; `pdfinfo` → `Pages: 5`). Setiap demo memuat kopinya sendiri
(`demo-XX/dist/sample-sop.pdf`, kecuali ngx via `dist/assets/sample-sop.pdf`).

## Metode Pengukuran

- Install time: `time npm install --no-audit --no-fund` per demo (demo-02 tambah `--legacy-peer-deps`;
  lihat catatan peer di bawah). Angka dari laporan subagen pembangun + pengukuran langsung.
- Bundle/build size: `du -sh dist` dan `du -sh node_modules` setelah build sukses.
- Render speed: `console.log` `performance.now()` di dalam demo (pdfjs 262 ms load + 258 ms render
  5 halaman; pdftron `documentLoaded in 2779ms`) + observasi browser headless (jumlah `<canvas>`,
  teks halaman, `pageerror` kosong).
- Proteksi: toggle ON → cek class `protected`, tombol download/print hilang, `contextmenu`/`copy`/
  `Ctrl+C/P/S` di-`preventDefault`; toggle OFF → pulih; bypass: `fetch()` PDF langsung
  (status 200 + 8091 byte) saat proteksi ON.
- Friksi lisensi: buka tanpa key, catat string watermark/banner/console verbatim dari DevTools.

## Tabel Perbandingan (angka observasi, bukan estimasi)

| Viewer | Versi | Lisensi | Install time | node_modules | dist (build) | Render (observasi) | Proteksi UI | Bypass | Cocok stack | Catatan |
|---|---|---|---|---|---|---|---|---|---|---|
| pdfjs-dist | 6.4.299 | Apache-2.0 (bebas) | ~30 dtk | 89M | 2.7M (JS 487KB + worker 2.2MB) | load 262 ms + render 258 ms, 5 canvas, 0 error | toggle sembunyikan download/print, blokir klik-kanan/copy/Ctrl+C/P/S | fetch 200/8091B saat ON | vanilla/apa pun | Paling ringan; `getDocument` wajib objek `{url}` di v6 (string ditolak) |
| react-pdf | 11.0.0 (+pdfjs 6.4.299, React 19.2.0) | MIT (bebas) | ~9 dtk (+`--legacy-peer-deps`) | 132M | 3.1M (JS 879KB + worker 2.2MB + css 11KB) | 6 canvas (5 hal + buffer), Hal 1/5 + text-layer, 0 error | conditional-render tombol (absen di DOM saat ON) + class + listener | fetch 200/8091B saat ON | React | Lihat catatan React 19.1.1 di bawah |
| ngx-extended-pdf-viewer | 30.0.1 (Angular 20.3.x) | MIT (bebas) | ~60 dtk (scaffold 22 dtk + install 49 dtk + lib 11 dtk) | 327M | 23M (JS 1.13MB + pdf.js assets 22MB) | 3 canvas, toolbar "of 5", 0 error | `[showDownloadButton]/[showPrintButton]` + class + listener | fetch 200/8091B saat ON | Angular | Butuh entri assets lib di `angular.json`; `dist/browser/` dihapus agar `dist/index.html` seragam |
| @pdftron/webviewer | 12.2.0 | komersial trial | ~2 mnt 49 dtk | 200M | 177M (webviewer 177M + JS 34KB) | `documentLoaded in 2779ms`, 0 pageerror | `disableElements(['downloadButton','printButton'])` + class + listener | fetch 200/8091B saat ON | vanilla/React/Angular (iframe engine) | Watermark + console trial (lihat bawah); butuh 177M aset `public/webviewer` |
| @nutrient-sdk/viewer | 1.22.0 | komersial trial | ~1 mnt 33 dtk | 178M (lib 156M) | 5.6M (JS 5.8MB/gzip 1.7MB) | load resolved, toolbar penuh, 0 pageerror | `setToolbarItems` filter print/export-pdf/download + class + listener | fetch 200/8091B saat ON | vanilla (framework wrapper tersedia) | Watermark full-page "For Evaluation Purposes Only"; butuh `baseUrl` CDN |
| @syncfusion/ej2-pdfviewer | 35.1.37 | komersial trial | ~1 mnt 37 dtk | 405M | 9.6M (JS 10MB/gzip 3.3MB) | "1 of 5" + teks SOP, 0 pageerror | `toolbarSettings.toolbarItems` tanpa Download/Print + `enableDownload/enablePrint` + class + listener | fetch 200/8091B saat ON | vanilla/Angular/React (EJ2) | Banner trial + butuh pola init khusus (lihat bawah) |

## Catatan per viewer

- **pdfjs-dist 6.4.299 (vanilla).** Install tercepat kedua (~30 dtk, 89M), dist terkecil (2.7M).
  Render tercepat (total ~520 ms untuk 5 halaman). API v6 menolak `getDocument(string)` —
  error `expected either data, range, or url parameter`; wajib `getDocument({ url })`.
  Tanpa watermark/dering lisensi apa pun. Rekomendasi default bila DMS butuh viewer ringan
  tanpa ketergantungan framework.
- **react-pdf 11.0.0.** Dua deviasi dari rencana, keduanya dipaksa fakta:
  (1) `@vitejs/plugin-react@4.3.3` peer-nya hanya vite 4/5, jadi install butuh
  `--legacy-peer-deps`; (2) **`react@19.1.1` cacat kemasan**: `react-dom` CJS-nya meng-hardcode
  cek `isomorphicReactPackageVersion "19.0.0"` sementara `react` terinstal 19.1.1 → minified
  error #527 dan layar putih. Diperbaiki dengan menaikkan pasangan ke **19.2.0**
  (`react`/`react-dom` `^19.2.0` di `package.json`; `react-pdf` sendiri memang peer `^19.2.0`).
  `overrides pdfjs-dist 6.4.299` ter-dedupe bersih. Build 6.25 dtk, 0 error, text-layer rapi.
- **ngx-extended-pdf-viewer 30.0.1.** Satu-satunya demo Angular (CLI 20.3.38 lokal, tanpa global).
  MIT, tanpa watermark. Dua jebakan: (a) aset pdf.js lib (~22M: `viewer-*.mjs`, cmaps, locale)
  TIDAK ikut build tanpa entri `{glob,input:"node_modules/ngx-extended-pdf-viewer/assets",
  output:"assets"}` di `angular.json` (gejala: `ERR_ABORTED .../assets/viewer-6.2.1099.min.mjs`);
  (b) builder `@angular/build:application` selalu emit subdir `browser/` — dihapus pasca-build
  agar `dist/index.html` seragam dengan hub. Build 8.4 dtk; dist 23M (terbesar kedua setelah pdftron).
- **@pdftron/webviewer 12.2.0.** Install terlama (~2:49, 200M) dan dist terbesar (177M, hampir
  semuanya aset `dist/webviewer/` via skrip `postinstall cp`). Console tanpa key (verbatim):
  `Thank you for downloading WebViewer! During the 7-day trial, there are no feature limitations,
  except for a watermark on output documents. To extend the trial, get a key from
  https://dev.apryse.com/.` Plus `Your server has not been configured to serve WebAssembly threads`
  dan `Could not use incremental download ... file is not linearized` (non-fatal).
  Render 2779 ms (terlama — engine WASM). Kunci trial via https://dev.apryse.com/ (form, email).
- **@nutrient-sdk/viewer 1.22.0.** `node_modules` 178M (lib 156M) tapi dist hanya 5.6M karena
  aset WASM diambil dari CDN (`baseUrl: https://cdn.cloud.nutrient.io/pspdfkit-web@1.22.0/` —
  wajib sejak 1.9.0; tanpa itu warning + fallback CDN). Tanpa `preloadWorker()` ada warning
  prosedural (non-fatal). Watermark paling mengganggu: overlay diagonal merah
  **"For Evaluation Purposes Only"** menutupi tiap halaman (screenshot terverifikasi).
  API terverifikasi: `NutrientViewer.load({container, document, baseUrl})` →
  `instance.setToolbarItems(items => items.filter(...))` (sembunyikan `print`/`export-pdf`/
  `download`). Key trial via https://dashboard.nutrient.io/sign_up.
- **@syncfusion/ej2-pdfviewer 35.1.37.** `node_modules` terbesar (405M, 41 paket) tapi dist
  moderat 9.6M. Tiga fakta API (semuanya diverifikasi dari sumber, bukan tebakan):
  (1) `resourceUrl` harus **folder** `.../dist/ej2-pdfviewer-lib` (pdfium.js/wasm), bukan file
  `.min.js` — salah path = viewer tidak init; (2) nama field toolbar yang benar
  `toolbarSettings.toolbarItems` (spec `toolbarItem` tidak ada di 35.1.37 — kode set keduanya);
  (3) dengan `serviceUrl=''` (standalone, `clientSideRendering=true`) viewer TIDAK me-request
  `documentPath` URL sama sekali (network trace kosong) dan `load(bytes)` sebelum pdfium siap
  melempar lalu memunculkan `invalidFilePopup` — diperbaiki dengan `load()` setelah event
  `resourcesLoaded` + fallback retry 4 dtk (`viewer.pageCount === 0`). Tanpa `registerLicense`
  yang valid muncul banner `sf-license-validation-banner`: `This application was built using a
  trial version of Syncfusion® Essential Studio® ...` + tombol `Claim your free account`
  (https://www.syncfusion.com/account/claim-license-key). Key trial via akun Syncfusion.

## Rangkuman Proteksi

Seragam di 6 demo dan **terbukti UI-only**: toggle ON memblokir klik-kanan, copy/cut/paste,
`Ctrl/Cmd+C/P/S`, dan menyembunyikan/menghapus tombol download/print (mekanisme per viewer
pada tabel). Namun pada SEMUA demo, `fetch()` ke file PDF mengembalikan **200 + 8091 byte**
saat proteksi ON — file tetap diambil via DevTools → Network, View Source, curl/wget, atau
screenshot/OCR canvas; print browser tetap bisa. Tidak ada DRM; proteksi server-side
(permission, autentikasi, URL bertanda-tangan, watermark server) di luar lingkup lab ini.

## Rekomendasi untuk DMS Tahap 4

- Butuh ringan/framework-agnostik: **pdfjs-dist vanilla** (2.7M, ~0.5 dtk render, bebas).
- DMS berbasis React: **react-pdf** (tapi naikkan React ke ^19.2.0 — 19.1.1 rusak).
- DMS berbasis Angular: **ngx-extended-pdf-viewer** (siapkan 23M aset + entri `angular.json`).
- Komersial hanya bila butuh anotasi/form/redaksi bawaan dan anggaran lisensi ada:
  pdftron paling berat (177M aset), nutrient watermark paling mengganggu,
  syncfusion butuh pola init standalone di atas. Ketiganya tetap tidak menyelesaikan
  proteksi tanpa backend — uji coba mereka hanya soal UX/fitur, bukan DRM.
