A framework-agnostic, universal document renderer with optional chunked loading polyrender.wisp.place/
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

add READMEs for packages

aria 06252cfd ee6d7aa8

+455
+292
packages/core/README.md
··· 1 + # @polyrender/core 2 + 3 + Framework-agnostic TypeScript library for rendering documents in the browser. Supports PDF, EPUB, DOCX, ODT, ODS, CSV/TSV, source code, and plain text with a unified API. 4 + 5 + For React support, see [`@polyrender/react`](https://www.npmjs.com/package/@polyrender/react). 6 + 7 + ## Installation 8 + 9 + ```bash 10 + npm install @polyrender/core 11 + ``` 12 + 13 + Install peer dependencies only for the formats you need: 14 + 15 + ```bash 16 + npm install pdfjs-dist # PDF 17 + npm install epubjs # EPUB 18 + npm install docx-preview # DOCX 19 + npm install jszip # ODT 20 + npm install xlsx # ODS 21 + npm install papaparse # CSV/TSV 22 + npm install highlight.js # Code, Markdown, JSON, XML/HTML 23 + ``` 24 + 25 + ## Usage 26 + 27 + ```typescript 28 + import { PolyRender } from '@polyrender/core' 29 + import '@polyrender/core/styles.css' 30 + 31 + const viewer = new PolyRender(document.getElementById('viewer')!, { 32 + source: { type: 'url', url: '/document.pdf' }, 33 + theme: 'dark', 34 + toolbar: true, 35 + onReady: (info) => console.log(`Loaded: ${info.pageCount} pages`), 36 + onPageChange: (page, total) => console.log(`Page ${page} of ${total}`), 37 + }) 38 + 39 + // Imperative control 40 + viewer.goToPage(5) 41 + viewer.setZoom('fit-width') 42 + viewer.setZoom(1.5) 43 + 44 + // Clean up 45 + viewer.destroy() 46 + ``` 47 + 48 + ## Document Sources 49 + 50 + ### File (binary data) 51 + 52 + ```typescript 53 + // From a File input 54 + const file = inputElement.files[0] 55 + source = { type: 'file', data: file, filename: file.name } 56 + 57 + // From an ArrayBuffer 58 + source = { type: 'file', data: arrayBuffer, mimeType: 'application/pdf' } 59 + ``` 60 + 61 + ### URL 62 + 63 + ```typescript 64 + source = { type: 'url', url: '/document.pdf' } 65 + 66 + // With custom headers (e.g., auth) 67 + source = { 68 + type: 'url', 69 + url: '/api/documents/123.pdf', 70 + fetchOptions: { headers: { Authorization: 'Bearer ...' } }, 71 + } 72 + ``` 73 + 74 + ### Pre-rendered Pages 75 + 76 + ```typescript 77 + // Direct array 78 + source = { 79 + type: 'pages', 80 + pages: [ 81 + { pageNumber: 1, imageUrl: '/pages/1.webp', width: 1654, height: 2339 }, 82 + { pageNumber: 2, imageUrl: '/pages/2.webp', width: 1654, height: 2339 }, 83 + ], 84 + } 85 + 86 + // Lazy fetch adapter 87 + source = { 88 + type: 'pages', 89 + pages: { 90 + totalPages: 500, 91 + fetchPage: async (pageNumber) => ({ 92 + pageNumber, 93 + imageUrl: `/api/pages/${pageNumber}.webp`, 94 + width: 1654, 95 + height: 2339, 96 + }), 97 + }, 98 + } 99 + ``` 100 + 101 + ### Chunked PDF 102 + 103 + ```typescript 104 + source = { 105 + type: 'chunked', 106 + totalPages: 500, 107 + chunks: { 108 + totalChunks: 10, 109 + totalPages: 500, 110 + fetchChunk: async (index) => { 111 + const res = await fetch(`/api/chunks/${index}.pdf`) 112 + return { 113 + data: await res.arrayBuffer(), 114 + pageStart: index * 50 + 1, 115 + pageEnd: Math.min((index + 1) * 50, 500), 116 + } 117 + }, 118 + getChunkIndexForPage: (page) => Math.floor((page - 1) / 50), 119 + }, 120 + // Optional: fast browse images while chunks load 121 + browsePages: { 122 + totalPages: 500, 123 + fetchPage: async (pageNumber) => ({ 124 + pageNumber, 125 + imageUrl: `/api/browse/${pageNumber}.webp`, 126 + width: 1654, 127 + height: 2339, 128 + }), 129 + }, 130 + } 131 + ``` 132 + 133 + ## Options 134 + 135 + ```typescript 136 + new PolyRender(container, { 137 + source, // Required 138 + format?: DocumentFormat, // Override auto-detection 139 + theme?: 'dark' | 'light' | 'system', // Default: 'dark' 140 + className?: string, // Extra CSS class on root element 141 + initialPage?: number, // Starting page (default: 1) 142 + zoom?: number | 'fit-width' | 'fit-page' | 'auto', 143 + toolbar?: boolean | ToolbarConfig, 144 + 145 + // Callbacks 146 + onReady?: (info: DocumentInfo) => void, 147 + onPageChange?: (page: number, totalPages: number) => void, 148 + onZoomChange?: (zoom: number) => void, 149 + onError?: (error: PolyRenderError) => void, 150 + onLoadingChange?: (loading: boolean) => void, 151 + 152 + // Format-specific 153 + pdf?: PdfOptions, 154 + epub?: EpubOptions, 155 + code?: CodeOptions, 156 + csv?: CsvOptions, 157 + odt?: OdtOptions, 158 + ods?: OdsOptions, 159 + }) 160 + ``` 161 + 162 + ### Format-specific Options 163 + 164 + **PDF** 165 + ```typescript 166 + pdf: { 167 + workerSrc?: string, // pdf.js worker URL 168 + cMapUrl?: string, // Character map directory 169 + textLayer?: boolean, // Enable text selection (default true) 170 + annotationLayer?: boolean // Show PDF annotations (default false) 171 + } 172 + ``` 173 + 174 + **EPUB** 175 + ```typescript 176 + epub: { 177 + flow?: 'paginated' | 'scrolled', // Default: 'paginated' 178 + fontSize?: number, // Font size in px (default 16) 179 + fontFamily?: string, // Font override 180 + } 181 + ``` 182 + 183 + **Code** 184 + ```typescript 185 + code: { 186 + language?: string, // Force language (auto-detected from extension) 187 + lineNumbers?: boolean, // Default true 188 + wordWrap?: boolean, // Default false 189 + tabSize?: number, // Default 2 190 + } 191 + ``` 192 + 193 + **CSV/TSV** 194 + ```typescript 195 + csv: { 196 + delimiter?: string, // Auto-detected 197 + header?: boolean, // First row is header (default true) 198 + maxRows?: number, // Default 10000 199 + sortable?: boolean, // Default true 200 + } 201 + ``` 202 + 203 + **ODT** 204 + ```typescript 205 + odt: { 206 + fontSize?: number, // Base font size in px (default 16) 207 + fontFamily?: string, // Font override 208 + } 209 + ``` 210 + 211 + **ODS** 212 + ```typescript 213 + ods: { 214 + maxRows?: number, // Max rows per sheet (default 10000) 215 + sortable?: boolean, // Default true 216 + header?: boolean, // First row is header (default true) 217 + } 218 + ``` 219 + 220 + ## Events 221 + 222 + Subscribe to events using `.on()` (returns an unsubscribe function): 223 + 224 + ```typescript 225 + const off = viewer.on('pagechange', ({ page, totalPages }) => { 226 + console.log(`${page} / ${totalPages}`) 227 + }) 228 + 229 + // Later: 230 + off() 231 + ``` 232 + 233 + Available events: `ready`, `pagechange`, `zoomchange`, `loadingchange`, `error`, `destroy`. 234 + 235 + ## Theming 236 + 237 + PolyRender uses CSS custom properties prefixed `--dv-*`. Override them on the `.polyrender` root element: 238 + 239 + ```css 240 + .my-viewer .polyrender { 241 + --dv-bg: #1e1e2e; 242 + --dv-surface: #2a2a3e; 243 + --dv-text: #cdd6f4; 244 + --dv-accent: #89b4fa; 245 + --dv-border: #45475a; 246 + } 247 + ``` 248 + 249 + Built-in themes: `dark` (default), `light`, `system`. 250 + 251 + ## Custom Renderers 252 + 253 + ```typescript 254 + import { PolyRender, BaseRenderer } from '@polyrender/core' 255 + import type { PolyRenderOptions, DocumentFormat } from '@polyrender/core' 256 + 257 + class MarkdownRenderer extends BaseRenderer { 258 + readonly format: DocumentFormat = 'custom-markdown' 259 + 260 + protected async onMount(viewport: HTMLElement, options: PolyRenderOptions) { 261 + const text = await this.loadText(options.source) 262 + viewport.innerHTML = myMarkdownLib.render(text) 263 + this.setReady({ format: 'custom-markdown', pageCount: 1 }) 264 + } 265 + 266 + protected onDestroy() {} 267 + } 268 + 269 + PolyRender.registerRenderer('custom-markdown', () => new MarkdownRenderer()) 270 + ``` 271 + 272 + ## Supported Formats 273 + 274 + | Format | Peer Dependency | Auto-detected Extensions | 275 + |--------|----------------|--------------------------| 276 + | PDF | `pdfjs-dist` | `.pdf` | 277 + | EPUB | `epubjs` | `.epub` | 278 + | DOCX | `docx-preview` | `.docx`, `.doc` | 279 + | ODT | `jszip` | `.odt` | 280 + | ODS | `xlsx` | `.ods` | 281 + | CSV/TSV | `papaparse` | `.csv`, `.tsv` | 282 + | Code | `highlight.js` | `.js`, `.ts`, `.py`, `.rs`, `.go`, +80 more | 283 + | Text | _(none)_ | `.txt` | 284 + | Markdown | `highlight.js` | `.md` | 285 + | JSON | `highlight.js` | `.json` | 286 + | XML/HTML | `highlight.js` | `.xml`, `.html`, `.svg` | 287 + | Pages | _(none)_ | N/A (explicit `type: 'pages'`) | 288 + | Chunked PDF | `pdfjs-dist` | N/A (explicit `type: 'chunked'`) | 289 + 290 + ## License 291 + 292 + Zlib
+163
packages/react/README.md
··· 1 + # @polyrender/react 2 + 3 + React component and hook for rendering documents in the browser. A thin wrapper around [`@polyrender/core`](https://www.npmjs.com/package/@polyrender/core) that handles React lifecycle, cleanup, and ref-based imperative control. 4 + 5 + Supports PDF, EPUB, DOCX, ODT, ODS, CSV/TSV, source code, and plain text. 6 + 7 + ## Installation 8 + 9 + ```bash 10 + npm install @polyrender/react @polyrender/core 11 + ``` 12 + 13 + Install peer dependencies only for the formats you need: 14 + 15 + ```bash 16 + npm install pdfjs-dist # PDF 17 + npm install epubjs # EPUB 18 + npm install docx-preview # DOCX 19 + npm install jszip # ODT 20 + npm install xlsx # ODS 21 + npm install papaparse # CSV/TSV 22 + npm install highlight.js # Code, Markdown, JSON, XML/HTML 23 + ``` 24 + 25 + ## Quick Start 26 + 27 + ```tsx 28 + import { DocumentViewer } from '@polyrender/react' 29 + import '@polyrender/core/styles.css' 30 + 31 + function App() { 32 + return ( 33 + <DocumentViewer 34 + source={{ type: 'url', url: '/report.pdf' }} 35 + theme="dark" 36 + style={{ width: '100%', height: '80vh' }} 37 + onReady={(info) => console.log(`${info.pageCount} pages`)} 38 + onPageChange={(page, total) => console.log(`${page}/${total}`)} 39 + /> 40 + ) 41 + } 42 + ``` 43 + 44 + ## `<DocumentViewer>` 45 + 46 + Drop-in component. Props mirror `PolyRenderOptions` from `@polyrender/core`. 47 + 48 + ### Props 49 + 50 + | Prop | Type | Default | Description | 51 + |------|------|---------|-------------| 52 + | `source` | `DocumentSource \| null` | — | The document to render | 53 + | `format` | `DocumentFormat` | auto | Override format detection | 54 + | `theme` | `'dark' \| 'light' \| 'system'` | `'dark'` | Color theme | 55 + | `className` | `string` | — | Extra CSS class on the root element | 56 + | `style` | `React.CSSProperties` | — | Styles for the wrapper div (set width/height here) | 57 + | `initialPage` | `number` | `1` | Starting page | 58 + | `zoom` | `number \| 'fit-width' \| 'fit-page' \| 'auto'` | — | Initial zoom | 59 + | `toolbar` | `boolean \| ToolbarConfig` | `true` | Toolbar visibility/config | 60 + | `showPageNumbers` | `boolean` | — | Show page numbers | 61 + | `onReady` | `(info: DocumentInfo) => void` | — | Fired when document is loaded | 62 + | `onPageChange` | `(page, total) => void` | — | Fired on page navigation | 63 + | `onZoomChange` | `(zoom: number) => void` | — | Fired on zoom change | 64 + | `onError` | `(error: PolyRenderError) => void` | — | Fired on unrecoverable error | 65 + | `onLoadingChange` | `(loading: boolean) => void` | — | Fired on loading state change | 66 + | `pdf` | `PdfOptions` | — | PDF-specific options | 67 + | `epub` | `EpubOptions` | — | EPUB-specific options | 68 + | `code` | `CodeOptions` | — | Code-specific options | 69 + | `csv` | `CsvOptions` | — | CSV/TSV-specific options | 70 + 71 + ### Imperative Control via Ref 72 + 73 + ```tsx 74 + import { useRef } from 'react' 75 + import { DocumentViewer, type DocumentViewerRef } from '@polyrender/react' 76 + import '@polyrender/core/styles.css' 77 + 78 + function App() { 79 + const viewerRef = useRef<DocumentViewerRef>(null) 80 + 81 + return ( 82 + <> 83 + <DocumentViewer 84 + ref={viewerRef} 85 + source={{ type: 'url', url: '/report.pdf' }} 86 + style={{ width: '100%', height: '80vh' }} 87 + /> 88 + <button onClick={() => viewerRef.current?.goToPage(1)}>First page</button> 89 + <button onClick={() => viewerRef.current?.setZoom('fit-width')}>Fit width</button> 90 + </> 91 + ) 92 + } 93 + ``` 94 + 95 + `DocumentViewerRef` exposes: `goToPage(page)`, `setZoom(zoom)`, `getCurrentPage()`, `getPageCount()`, `getZoom()`. 96 + 97 + ## `useDocumentRenderer` (headless hook) 98 + 99 + For building fully custom UI around the renderer: 100 + 101 + ```tsx 102 + import { useDocumentRenderer } from '@polyrender/react' 103 + import '@polyrender/core/styles.css' 104 + 105 + function CustomViewer({ url }: { url: string }) { 106 + const { containerRef, state, goToPage, setZoom } = useDocumentRenderer({ 107 + source: { type: 'url', url }, 108 + theme: 'dark', 109 + toolbar: false, 110 + }) 111 + 112 + return ( 113 + <div> 114 + <div ref={containerRef} style={{ width: '100%', height: '600px' }} /> 115 + <div> 116 + <button onClick={() => goToPage(state.currentPage - 1)}>Prev</button> 117 + <span>{state.currentPage} / {state.totalPages}</span> 118 + <button onClick={() => goToPage(state.currentPage + 1)}>Next</button> 119 + <button onClick={() => setZoom(state.zoom * 1.2)}>Zoom In</button> 120 + </div> 121 + </div> 122 + ) 123 + } 124 + ``` 125 + 126 + ### Return value 127 + 128 + | Field | Type | Description | 129 + |-------|------|-------------| 130 + | `containerRef` | `RefObject<HTMLDivElement>` | Attach to your container element | 131 + | `state` | `PolyRenderState` | Current viewer state | 132 + | `goToPage` | `(page: number) => void` | Navigate to a page | 133 + | `setZoom` | `(zoom) => void` | Set zoom level | 134 + | `ready` | `boolean` | `true` after `onReady` fires | 135 + | `error` | `PolyRenderError \| null` | Current error, if any | 136 + 137 + `PolyRenderState` contains: `loading`, `error`, `currentPage`, `totalPages`, `zoom`, `documentInfo`. 138 + 139 + ## Document Sources 140 + 141 + See [`@polyrender/core`](https://www.npmjs.com/package/@polyrender/core) for full documentation on `FileSource`, `UrlSource`, `PagesSource`, and `ChunkedSource`. 142 + 143 + ## Theming 144 + 145 + Import and apply styles from `@polyrender/core`: 146 + 147 + ```tsx 148 + import '@polyrender/core/styles.css' 149 + ``` 150 + 151 + Override CSS custom properties on the `.polyrender` root element: 152 + 153 + ```css 154 + .my-viewer .polyrender { 155 + --dv-bg: #1e1e2e; 156 + --dv-text: #cdd6f4; 157 + --dv-accent: #89b4fa; 158 + } 159 + ``` 160 + 161 + ## License 162 + 163 + Zlib