# pdf-live

<p align="center">
  <img src="screenshots/hero.png" alt="pdf-live" width="480">
</p>

<p align="center">
  A drop-in PDF viewer Web Component powered by <a href="https://mozilla.github.io/pdf.js/">mozilla/pdf.js</a>.<br>
  Add <code>&lt;pdf-live&gt;</code> to your page and get a full toolbar, thumbnails, zoom, print, download, dark mode, and i18n out of the box.
</p>

<p align="center">
  <a href="https://www.npmjs.com/package/pdf-live"><img src="https://img.shields.io/npm/v/pdf-live.svg" alt="npm version"></a>
  <a href="https://github.com/shumatsumonobu/pdf-live/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/pdf-live.svg" alt="license"></a>
</p>

## Features

- **Zero config** — works as a single HTML tag
- **Toolbar** — zoom in/out, fit to page/width, page navigation, print, download
- **Thumbnail panel** — collapsible side panel with page previews
- **Dark / Light theme** — toggle at runtime, persisted via `localStorage`
- **Ctrl+Wheel zoom** — smooth zoom with mouse wheel
- **Password protection** — built-in password modal with sync/async verification
- **i18n** — 12 languages (de, en, es, fr, it, ja, ko, nl, pt_br, ru, zh_cn, zh_tw)
- **CJK support** — optional CMap for fonts that require it
- **Mobile-ready** — responsive layout, thumbnail panel auto-hides on small viewports

## Demo

| Demo | Description |
|------|-------------|
| [Light Theme](https://shumatsumonobu.github.io/pdf-live/light.html) | Default light theme viewer. |
| [Dark Theme](https://shumatsumonobu.github.io/pdf-live/dark.html) | Dark theme with `restoretheme="false"`. |
| [Password](https://shumatsumonobu.github.io/pdf-live/password.html) | Synchronous password verification. |
| [Async Password](https://shumatsumonobu.github.io/pdf-live/password-async.html) | Asynchronous (server-side) password verification. |
| [Events](https://shumatsumonobu.github.io/pdf-live/events.html) | `documentLoaded` and `pageChange` event handling. |
| [i18n](https://shumatsumonobu.github.io/pdf-live/i18n.html) | Japanese locale (`lang="ja"`) with password protection. |

## Install

```bash
npm i pdf-live
```

## Quick Start

1. **CSS** — add the stylesheet to your `<head>`:

   ```html
   <link rel="stylesheet" href="node_modules/pdf-live/dist/pdf-live.css">
   ```

2. **HTML** — place the `<pdf-live>` element in your `<body>`:

   ```html
   <pdf-live
     src="sample.pdf"
     lang="en"
     worker="node_modules/pdf-live/dist/pdf.worker.js"
   ></pdf-live>
   ```

3. **JS** — import the module:

   ```html
   <!-- ES Module -->
   <script type="module">
     import './node_modules/pdf-live/dist/pdf-live.esm.js';
   </script>

   <!-- UMD -->
   <script src="node_modules/pdf-live/dist/pdf-live.js"></script>
   ```

   With a bundler (webpack, Vite, etc.) you can use the package name directly:

   ```js
   import 'pdf-live';
   ```

## Usage

```js
const pdflive = document.querySelector('pdf-live');

pdflive
  .on('documentLoaded', () => {
    console.log('PDF loaded');
  })
  .on('pageChange', pageNum => {
    console.log(`Page ${pageNum}`);
  });
```

## API

### Attributes

| Attribute | Required | Description |
|-----------|:--------:|-------------|
| `src` | Yes | URL of the PDF document. |
| `worker` | Yes | Path to the pdf.js worker script. Use `node_modules/pdf-live/dist/pdf.worker.js`. |
| `lang` | | UI language. One of `de`, `en`, `es`, `fr`, `it`, `ja`, `ko`, `nl`, `pt_br`, `ru`, `zh_cn`, `zh_tw`. Defaults to `en`. |
| `title` | | Document title shown in the toolbar, browser tab, and used as the print/download filename. Falls back to the filename in the `src` URL. |
| `protected` | | Enables password protection. A password modal is shown before loading the document. Requires a `passwordEnter` event listener. |
| `cmap` | | Path to CMap files for CJK font support. Use `node_modules/pdf-live/dist/cmaps`. |
| `restoretheme` | | Set to `"false"` to disable automatic theme restoration from `localStorage`. |

### Static Methods

#### `PDFLiveElement.define()`

Registers the `<pdf-live>` custom element with the browser. Called automatically on import, so manual invocation is usually unnecessary.

Returns `PDFLiveElement` class.

#### `PDFLiveElement.createElement()`

Convenience factory that registers the custom element (if needed) and creates a new instance. Useful for programmatic usage in frameworks.

Returns `PDFLiveElement` instance.

```js
import PDFLiveElement from 'pdf-live';

const pdflive = PDFLiveElement.createElement();
pdflive.setAttribute('src', 'document.pdf');
pdflive.setAttribute('worker', 'node_modules/pdf-live/dist/pdf.worker.js');
document.body.appendChild(pdflive);
```

### Instance Methods

#### `on(type, listener)` → `PDFLiveElement`

Registers an event listener. Returns the element for chaining.

```js
pdflive.on('pageChange', pageNum => { /* ... */ });
```

**Parameters:**

| Name | Type | Description |
|------|------|-------------|
| `type` | `string` | Event type: `"pageChange"`, `"documentLoaded"`, or `"passwordEnter"`. |
| `listener` | `Function` | Callback function. |

#### `getCurrentPageNumber()` → `number`

Returns the current 1-based page number.

```js
const page = pdflive.getCurrentPageNumber(); // 1
```

#### `print()` → `Promise<void>`

Opens the browser print dialog with the loaded PDF.

```js
await pdflive.print();
```

#### `download()` → `Promise<void>`

Downloads the PDF. Uses the `title` attribute as the filename if set.

```js
await pdflive.download();
```

### Events

Listen with `on()`. All listeners are chainable.

#### `documentLoaded`

Fired when the PDF document has finished loading and rendering.

```js
pdflive.on('documentLoaded', () => {
  console.log('Ready');
});
```

#### `pageChange`

Fired when the visible page changes. Receives the 1-based page number.

```js
pdflive.on('pageChange', pageNum => {
  console.log(`Page ${pageNum}`);
});
```

#### `passwordEnter`

Fired when the user submits a password. The listener must return `true` if the password is correct, `false` otherwise. Supports async listeners.

```js
// Sync
pdflive.on('passwordEnter', password => {
  return password === 'secret';
});

// Async (server-side verification)
pdflive.on('passwordEnter', async password => {
  const res = await fetch('/api/verify', {
    method: 'POST',
    body: JSON.stringify({ password }),
  });
  return (await res.json()).ok;
});
```

> **Note:** The `<pdf-live>` element must have the `protected` attribute for this event to work.

## Changelog

See [CHANGELOG.md](CHANGELOG.md).

## Author

**shumatsumonobu** · [GitHub](https://github.com/shumatsumonobu) · [X](https://x.com/shumatsumonobu) · [Facebook](https://www.facebook.com/takuya.motoshima.7)

## License

[MIT](LICENSE)
