# jit-viewer

Document preview SDK for Vue 3, React, and vanilla JavaScript.

Supports PDF, DOCX, XLSX/XLS, CSV/TSV, PPTX/PPT, OFD, TXT, Markdown, syntax-highlighted code files, HTML, images, video, CAD, 3D model preview, built-in watermark rendering, and runtime-friendly request adapters.

Chinese docs:
- [README.zh-CN.md](./README.zh-CN.md)
- [CHANGELOG.md](./CHANGELOG.md)

## What's New

### 1.5.0 - 2026-04-17

- **PDF rendering rewrite**: replaced `@vue-office/pdf` with `pdfjs-dist` — all pages now render without a virtual-scroll cap
- **Fully offline PDF worker**: bundled via `?raw` import + Blob URL, no CDN dependency required
- **Copyright protection upgrade**: branding removal now triggers a full-screen lock overlay disabling all SDK features
- Fixed: branding `element.remove()` no longer bypasses the copyright lock
- Fixed: false copyright lock during Viewer init before branding element laid out
- Fixed: PDF pages silently skipped on first render due to canvas default width (300 px)
- Added: smart loading indicator for large PDFs showing file size and current phase
- Removed unused dev dependencies: `@vue-office/pdf`, `@rollup/plugin-commonjs`, `@rollup/plugin-inject`, `rollup-plugin-polyfill-node`, `vue-demi`

### 1.4.2 - 2026-04-12

- Fixed: TXT preview garbled text for GBK / GB2312 / UTF-16 encoded files — replaced `blob.text()` with smart multi-encoding detection (BOM → strict UTF-8 → GBK heuristic → Big5 → lenient UTF-8 fallback)
- Non-UTF-8 files now show an encoding badge (e.g. `GBK`, `UTF-16 LE`) in the top-right corner

### 1.4.1 - 2026-04-12

- Fixed: DXF Worker absolute path causing `[commonjs--resolver] Could not resolve entry module` error in downstream Rollup/Vite builds — worker is now inlined as a Blob via `?worker&inline`
- Fixed: `ReferenceError: Cannot access 'autoRotateSpeed' before initialization` when uploading 3D model files — resolved Temporal Dead Zone issue in `Model3DRender.vue` setup

### 1.3.2 - 2026-03-22

- Added `circle` (radial sunburst) and `mirror` (symmetric) audio visualizer modes; mode now cycles `bars → mirror → circle → wave`
- New runtime audio API: `setAudioVisualizerMode`, `setAudioVisualizerColor`, `getAudioVisualizerMode`
- `AudioPlaybackState` now includes `visualizerMode`; new exported type `AudioVisualizerMode`
- Fixed: uploading unsupported formats (`.doc`, `.psd`, etc.) no longer causes an endless loading spinner
- Improved unsupported-format feedback UI: file icon with extension badge, filename, and clear status message
- Fixed: `handleError` now immediately clears the loading overlay before displaying the error

### 1.3.1 - 2026-03-22

- Fixed per-line background and rounded-corner artifacts in code preview
- Replaced the gradient background on the code container with a solid color to eliminate the row-level color variation
- Added explicit `background: none; border-radius: 0` resets on `pre.jv-code-line` and inner `code` elements to prevent global CSS bleed-through
- Code preview now looks clean and consistent, similar to GitHub / VS Code style

### 1.3.0 - 2026-04-07

- Added the `model3d` file type with first-wave support for `GLB / glTF / STL`
- Added runtime 3D APIs for reset camera, auto-rotate, wireframe, animation toggling, screenshot capture, light/material tuning, and preset application
- Added built-in 3D scene presets for product showcase, white model review, night outline, and warm gallery
- Expanded the full HTML demo with 3D STL / glTF samples, debug controls, and preset import/export workflows

### 1.2.2 - 2026-04-06

- Expanded video preview controls with resume playback, callbacks, runtime control APIs, and configurable toolbar/custom control items
- Added `video.nativeControls` to control browser-native video controls independently
- Fixed custom video controls not rendering immediately after upload or metadata load
- Fixed the toolbar `More` menu overlay being clipped by the top toolbar
- Added a visual video debug panel to the full vanilla HTML demo and improved local SDK asset loading order

See the full history in [CHANGELOG.md](./CHANGELOG.md).

## Features

- Multi-format preview: PDF, Office, OFD, Markdown, code, HTML, images, video, CSV, CAD
- 3D preview: built-in `GLB / glTF / STL` browser rendering
- CAD preview: built-in `DXF` rendering with adapter hooks for converted `DWG / DWF` assets
- Cross-framework usage: Vue 3, React, vanilla HTML/JS
- Built-in toolbar: zoom, rotate, print, download, pagination
- Theme and locale support: light / dark, `zh-CN` / `en`
- Watermark support: text watermark, image watermark, opacity, rotation, tiling gap, top/bottom layer
- File source support: local file, remote URL, Blob, ArrayBuffer, proxy URL, custom request adapter

## Installation

```bash
npm install jit-viewer
```

## Quick Start

### Vue 3

```vue
<template>
  <div ref="containerRef" class="viewer-container"></div>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import { createViewer } from 'jit-viewer'
import 'jit-viewer/style.css'

const containerRef = ref<HTMLElement | null>(null)
let viewer: ReturnType<typeof createViewer> | null = null

onMounted(() => {
  viewer = createViewer({
    target: containerRef.value!,
    file: '/demo/test.csv',
    toolbar: true,
    width: '100%',
    height: 640
  })

  viewer.mount()
})

onUnmounted(() => {
  viewer?.destroy()
})
</script>

<style scoped>
.viewer-container {
  width: 100%;
  height: 640px;
}
</style>
```

### React

```tsx
import { useEffect, useRef } from 'react'
import { createViewer, type ViewerInstance } from 'jit-viewer'
import 'jit-viewer/style.css'

export default function App() {
  const containerRef = useRef<HTMLDivElement | null>(null)
  const viewerRef = useRef<ViewerInstance | null>(null)

  useEffect(() => {
    if (!containerRef.current)
      return

    viewerRef.current = createViewer({
      target: containerRef.current,
      file: '/demo/test.mp4',
      toolbar: true,
      height: 640
    })

    viewerRef.current.mount()

    return () => viewerRef.current?.destroy()
  }, [])

  return <div ref={containerRef} style={{ width: '100%', height: 640 }} />
}
```

### Vanilla HTML / JS

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>JitViewer Demo</title>
  <link rel="stylesheet" href="https://unpkg.com/jit-viewer@1.4.2/dist/iife/jit-viewer.min.css" />
</head>
<body>
  <div id="viewer" style="width:100%;height:640px;"></div>

  <script src="https://unpkg.com/jit-viewer@1.4.2/dist/iife/jit-viewer.min.js"></script>
  <script>
    const viewer = JitViewer.createViewer({
      target: '#viewer',
      file: '/demo/test.pdf',
      toolbar: true,
      width: '100%',
      height: 640
    })

    viewer.mount()
  </script>
</body>
</html>
```

## Core API

### `createViewer(options)`

Creates a viewer instance.

Key `ViewerOptions`:

| Option | Type | Description |
| --- | --- | --- |
| `target` | `HTMLElement \| string` | Mount target |
| `file` | `FileSource` | File source |
| `type` | `FileType` | Explicit file type when auto detection is not enough |
| `filename` | `string` | Display and download filename |
| `toolbar` | `ToolbarConfig \| boolean` | Toolbar config |
| `theme` | `Theme` | `light`, `dark`, or custom theme |
| `locale` | `Locale` | `zh-CN`, `en`, or custom locale |
| `watermark` | `WatermarkConfig \| null` | Preview watermark config |
| `proxyUrl` | `string` | Custom proxy endpoint |
| `requestAdapter` | `RequestAdapter` | Custom fetch adapter |
| `video` | `VideoOptions` | Video preview config for seek control, resume playback, and callbacks |
| `cad` | `CadOptions` | CAD preview config for `DXF` rendering and converted `DWG / DWF` assets |
| `model3d` | `Model3DOptions` | 3D model preview config for `GLB / glTF / STL` and adapter-based assets |
| `renderOptions` | `{ zoom?: number; rotate?: number; page?: number }` | Initial render state |

### `ViewerInstance`

| Method | Description |
| --- | --- |
| `mount(target?)` | Mount the viewer |
| `destroy()` | Destroy the viewer |
| `setFile(file, filename?)` | Replace the current file |
| `setOptions(options)` | Update file, theme, toolbar, watermark, and runtime options |
| `getFile()` | Get current file info |
| `zoom(scale)` | Set zoom |
| `rotate(degree)` | Rotate content |
| `reset()` | Reset zoom and rotate |
| `fullscreen(enable?)` | Toggle fullscreen |
| `prevPage()` / `nextPage()` / `gotoPage(page)` | Pagination controls |
| `getPageInfo()` | Get page state |
| `print()` | Print current view |
| `download()` | Download current file |
| `playVideo()` / `pauseVideo()` / `toggleVideoPlayback()` | Control video playback |
| `seekVideo(time)` | Seek to a specific video time |
| `getVideoState()` | Get current video playback state |
| `resetModel3DCamera()` | Reset the 3D camera |
| `setModel3DAutoRotate(enabled)` | Toggle 3D auto-rotation |
| `setModel3DAutoRotateSpeed(speed)` | Set the auto-rotate speed |
| `setModel3DExposure(exposure)` | Set the 3D exposure |
| `setModel3DLightIntensity(intensity)` | Set scene light intensity |
| `setModel3DMaterialRoughness(value)` | Tune material roughness |
| `setModel3DMaterialMetalness(value)` | Tune material metalness |
| `toggleModel3DWireframe()` | Toggle 3D wireframe mode |
| `toggleModel3DAnimation()` | Toggle 3D animation playback |
| `captureModel3DScreenshot()` | Capture a 3D screenshot |
| `getModel3DState()` | Get the current 3D model state |
| `applyModel3DPreset(preset)` | Apply a 3D preset object |
| `getModel3DPreset()` | Export the current 3D preset |
| `applyBuiltInModel3DPreset(id)` | Apply a built-in 3D preset |
| `getBuiltInModel3DPresets()` | Get built-in 3D preset definitions |
| `setTheme(theme)` | Update theme |
| `setLocale(locale)` | Update locale |
| `setToolbar(config)` | Update toolbar |
| `on(event, handler)` / `off(event, handler)` | Event subscription |
| `getState()` | Get runtime state |

## Watermark

Watermarks are configured with `ViewerOptions.watermark`. They apply only to the preview layer and do not modify the original file.

```ts
import { createViewer } from 'jit-viewer'
import 'jit-viewer/style.css'

const viewer = createViewer({
  target: '#viewer',
  file: '/demo/report.pdf',
  watermark: {
    type: 'text',
    content: 'CONFIDENTIAL',
    opacity: 0.18,
    rotate: -24,
    gapX: 140,
    gapY: 90,
    position: 'top'
  }
})

viewer.mount()
```

## Supported Formats

| Type | Extensions |
| --- | --- |
| PDF | `.pdf` |
| Word | `.docx` |
| Excel | `.xlsx`, `.xls` |
| CSV | `.csv`, `.tsv` |
| PowerPoint | `.pptx`, `.ppt` |
| OFD | `.ofd` |
| Text | `.txt` |
| Markdown | `.md`, `.markdown` |
| HTML | `.html`, `.htm` |
| Images | `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`, `.svg`, `.bmp`, `.tiff`, `.tif`, `.ico` |
| Video | `.mp4`, `.webm`, `.ogg`, `.ogv`, `.mov` |
| CAD | `.dxf` (built-in rendering), `.dwg`, `.dwf`, `.dwfx`, `.dwt` (via `cad.previewUrl` / `cad.adapter`) |
| 3D Model | `.glb`, `.gltf`, `.stl` (first-wave built-in rendering), `.obj`, `.ply` (adapter / extension-based) |

## Video Preview

Video preview supports blocking manual seek, resuming from the last playback position, and runtime playback callbacks.

When `disableSeek` is enabled, the SDK automatically switches to its own control bar so the native seek bar is no longer exposed.
Use `nativeControls: false` to hide the browser's native video controls independently.
When `nativeControls: false` is set and custom controls are not explicitly disabled, the SDK falls back to its own controls or toolbar controls automatically.
If the main toolbar includes the `video` item, playback controls move to the toolbar and the in-player custom controls are hidden.
By default, `play / restart / time / progress` stay inline and `mute / speed` move into a `More` menu.

```ts
import { createViewer } from 'jit-viewer'
import 'jit-viewer/style.css'

const viewer = createViewer({
  target: '#viewer',
  file: '/demo/training.mp4',
  video: {
    disableSeek: true,
    nativeControls: false,
    resumePlayback: 'local',
    toolbarItems: ['playback', 'time', 'progress'],
    toolbarMoreItems: ['mute', 'speed'],
    customControlItems: ['playback', 'progress', 'time'],
    onEnded: (state) => {
      console.log('ended', state.currentTime)
    }
  }
})

viewer.mount()

await viewer.playVideo()
viewer.pauseVideo()
viewer.seekVideo(120)
```

You can also subscribe to runtime video events:

```ts
viewer.on('video:ended', (state) => {
  console.log('ended', state.currentTime)
})
```

You can also trim the toolbar-level video controls directly:

```ts
const viewer = createViewer({
  target: '#viewer',
  file: '/demo/training.mp4',
  toolbar: {
    position: 'both',
    items: [
      { type: 'fullscreen' },
      { type: 'video', options: { items: ['playback', 'time'], moreItems: ['mute', 'speed'] } },
      { type: 'download' }
    ]
  }
})
```

## 3D Model Preview

3D preview currently supports `GLB / glTF / STL` out of the box. For business-specific pipelines or converted engineering assets, use `model3d.previewUrl` or `model3d.adapter`.

```ts
import { createViewer } from 'jit-viewer'
import 'jit-viewer/style.css'

const viewer = createViewer({
  target: '#viewer',
  file: '/demo/part.stl',
  model3d: {
    autoRotate: true,
    autoRotateSpeed: 1.2,
    showGrid: true,
    showAxes: true,
    environment: 'studio',
    exposure: 1,
    lightIntensity: 1
  }
})

viewer.mount()
```

You can also fine-tune the 3D scene at runtime:

```ts
viewer.setModel3DAutoRotate(true)
viewer.setModel3DAutoRotateSpeed(1.4)
viewer.setModel3DExposure(1.1)
viewer.setModel3DLightIntensity(1.2)
viewer.setModel3DMaterialRoughness(0.32)
viewer.setModel3DMaterialMetalness(0.46)
```

Presets can be exported and reused:

```ts
const preset = viewer.getModel3DPreset()

viewer.applyModel3DPreset({
  environment: 'neutral',
  exposure: 1.05,
  lightIntensity: 0.95
})

viewer.applyBuiltInModel3DPreset('white-model-review')
console.log(preset, viewer.getBuiltInModel3DPresets())
```

## Links

- [Chinese README](./README.zh-CN.md)
- [Changelog](./CHANGELOG.md)
- [GitHub](https://github.com/jitOffice/jit-viewer-sdk)
- [Homepage](https://jitword.com/jit-viewer.html)
