diff --git a/README.md b/README.md
index 3376ce3..e0a61d7 100644
--- a/README.md
+++ b/README.md
@@ -91,6 +91,74 @@ function App() {
- **`style`** : style object to change container css properties
- **`className`** : apply a className to the control
+## PDF documents
+
+PDF pages are rendered in the browser with [pdf.js](https://mozilla.github.io/pdf.js/) (the first time a PDF is processed, it's loaded lazily in a Web Worker).
+Pages are rendered by batch to accommodate very large documents.
+
+### `usePDFDocument` hook
+
+The simplest way to display a PDF in a React component:
+
+```jsx
+import { useState } from 'react';
+import { AnnotationViewer, usePDFDocument } from 'react-mindee-js';
+
+function Document({ file }) {
+ const { status, numPages, pages, loading, loadPage, error } = usePDFDocument(
+ file,
+ { prefetch: 5, batch: 3 },
+ );
+ const [current, setCurrent] = useState(0);
+
+ if (status === 'error') return
{error.message}
;
+ if (status !== 'ready') return
Opening…
;
+
+ return (
+ <>
+
+ {pages[current] && (
+
+ )}
+ >
+ );
+}
+```
+
+### `openPDF` (framework-agnostic)
+
+For custom scheduling, use the handle directly. Page numbers are **1-based**, like pdf.js.
+
+```js
+import { openPDF } from 'react-mindee-js';
+
+const doc = await openPDF(file, { maxPages: 100 });
+doc.numPages; // 5
+
+const first = await doc.getPage(1, { priority: 'high' }); // blob: URL
+for await (const { pageNumber, image } of doc.getPages(2, 4)) {
+ // inclusive range, yielded as soon as each page is ready
+}
+
+await doc.destroy(); // revokes every blob: URL handed out by this handle
+```
+
+### `getPDFPageCount` / `getImagesFromPDF`
+
+`getPDFPageCount(source)` returns the page count without rendering. `getImagesFromPDF` still works but is **deprecated**: it renders and holds every page in memory at once. Prefer `usePDFDocument` or `openPDF`.
+
## Browser support
React mindee supports all recent browsers and works where React works. However, you may need check the [SSR](/docs/ssr) section.
diff --git a/cypress/integration/api.spec.ts b/cypress/integration/api.spec.ts
index ac038b2..a016afc 100644
--- a/cypress/integration/api.spec.ts
+++ b/cypress/integration/api.spec.ts
@@ -15,8 +15,10 @@ describe('built library public API', () => {
'getImagesFromPDF',
'getPDFPageCount',
'getZoomScale',
+ 'openPDF',
'setShapeConfig',
'toBase64',
+ 'usePDFDocument',
];
it('exposes exactly the documented runtime exports, all of them functions', () => {
diff --git a/cypress/integration/pdf.spec.tsx b/cypress/integration/pdf.spec.tsx
index 7cee748..9e6499c 100644
--- a/cypress/integration/pdf.spec.tsx
+++ b/cypress/integration/pdf.spec.tsx
@@ -130,4 +130,33 @@ describe('built library PDF support', () => {
cy.wrap(getPDFPageCount(multiPage)).should('equal', PAGE_COUNT);
});
});
+
+ it('renders pages on demand through openPDF with the inlined worker', () => {
+ cy.window().then((win) => {
+ cy.spy(win, 'Worker').as('worker');
+ });
+ loadDist({ fresh: true }).then(({ openPDF }) => {
+ cy.then(async () => {
+ const handle = await openPDF(multiPage);
+ const image = await handle.getPage(1);
+ const size = await imageDimensions(image);
+ await handle.destroy();
+ return { numPages: handle.numPages, image, size };
+ }).should(({ numPages, image, size }) => {
+ expect(numPages).to.equal(PAGE_COUNT);
+ expect(image).to.match(/^blob:/);
+ expect(size.width * size.height).to.be.greaterThan(0);
+ });
+ });
+ cy.get('@worker').should((spy) => {
+ expect(spy.calledOnce).to.equal(true);
+ expect(String(spy.firstCall.args[0])).to.match(/^blob:/);
+ });
+ });
+
+ it('exposes usePDFDocument from the built entry point', () => {
+ loadDist().then(({ usePDFDocument }) => {
+ expect(usePDFDocument).to.be.a('function');
+ });
+ });
});
diff --git a/docs/docs/API/annotation-viewer-api.mdx b/docs/docs/API/annotation-viewer-api.mdx
index d726353..ff7a868 100644
--- a/docs/docs/API/annotation-viewer-api.mdx
+++ b/docs/docs/API/annotation-viewer-api.mdx
@@ -28,7 +28,7 @@ type AnnotationShape = {
}
```
-> **image** property should be either an URL object or a base64. If you have an object of type [File](https://developer.mozilla.org/fr/docs/Web/API/File) ( typically generated by file upload libraries) instead you may need to use [URL.createObjectURL()](https://developer.mozilla.org/fr/docs/Web/API/URL/createObjectURL) to generate a valid URL object.
+> **image** property should be either an URL (including `blob:` object URLs) or a base64 data URL. If you have an object of type [File](https://developer.mozilla.org/fr/docs/Web/API/File) (typically generated by file upload libraries), you may need to use [URL.createObjectURL()](https://developer.mozilla.org/fr/docs/Web/API/URL/createObjectURL) to generate a valid URL object instead. JPEG, PNG, TIFF (first page) and HEIC/HEIF (primary image) are supported; TIFF and HEIF are converted in the browser, the HEIF decoder being downloaded on first use. For PDF files, see [usePDFDocument](/docs/Utils/use-pdf-document).
:::caution
@@ -54,9 +54,9 @@ interface AnnotationViewerOptions {
onMouseLeave?: (polygon: Line) => void
onClick?: (polygon: Line) => void
zoom?: {
- modifier: number
- max: number
- defaultZoom: number
+ modifier: number // multiplier applied per wheel step
+ max: number // highest magnification, relative to the fitted image (1 = fits the viewer)
+ defaultZoom: number // accepted for backward compatibility, currently unused
}
}
```
diff --git a/docs/docs/Tutorial/pdf-file.md b/docs/docs/Tutorial/pdf-file.md
index fb060e2..eba272c 100644
--- a/docs/docs/Tutorial/pdf-file.md
+++ b/docs/docs/Tutorial/pdf-file.md
@@ -4,17 +4,54 @@ sidebar_position: 2
# PDF file
-> If you got a PDF file in hand, you can use [getImagesFromPDF](/docs/Utils/get-images-form-pdf-api) to extract pages in base64 format and pass them to `` using a proper state.
+> PDF pages are rendered in the browser on demand. Use the [usePDFDocument](/docs/Utils/use-pdf-document) hook to display a document progressively: the first pages are rendered right away, the rest only when the user reaches them.
```tsx
-getImagesFromPDF(pdfFile).then((images: string[]) => {
- console.log(images) // ===> [base64_1, base64_2, ...]
-})
+import { useState } from 'react'
+import { AnnotationViewer, usePDFDocument } from 'react-mindee-js'
+
+function PdfViewer({ file }: { file: File }) {
+ const { status, numPages, pages, loading, loadPage } = usePDFDocument(file)
+ const [current, setCurrent] = useState(0)
+
+ const goTo = (index: number) => {
+ setCurrent(index)
+ loadPage(index) // renders the page and the rest of its batch if needed
+ }
+
+ if (status !== 'ready') return
+ )}
+ >
+ )
+}
```
-### Full example
+Outside React, or for custom scheduling, use [openPDF](/docs/Utils/open-pdf) directly:
+
+```ts
+const doc = await openPDF(file)
+const firstPage = await doc.getPage(1)
+// ...
+await doc.destroy()
+```
-
+:::caution
+Page images are `blob:` object URLs that live until the document is destroyed. If you need to send one to a server, read it back with `fetch(url).then((r) => r.blob())` first.
+:::
diff --git a/docs/docs/Utils/draw-layer.mdx b/docs/docs/Utils/draw-layer.mdx
index b5f75f0..af0d9ee 100644
--- a/docs/docs/Utils/draw-layer.mdx
+++ b/docs/docs/Utils/draw-layer.mdx
@@ -1,5 +1,5 @@
---
-sidebar_position: 4
+sidebar_position: 7
---
# drawLayer
diff --git a/docs/docs/Utils/draw-shape.mdx b/docs/docs/Utils/draw-shape.mdx
index 2a55804..35672f4 100644
--- a/docs/docs/Utils/draw-shape.mdx
+++ b/docs/docs/Utils/draw-shape.mdx
@@ -1,5 +1,5 @@
---
-sidebar_position: 2
+sidebar_position: 5
---
# drawShape
diff --git a/docs/docs/Utils/get-images-form-pdf-api.mdx b/docs/docs/Utils/get-images-form-pdf-api.mdx
index 4dd0e30..d893c2e 100644
--- a/docs/docs/Utils/get-images-form-pdf-api.mdx
+++ b/docs/docs/Utils/get-images-form-pdf-api.mdx
@@ -1,15 +1,23 @@
---
-sidebar_position: 1
+sidebar_position: 3
---
-# getImageFromPDF
+# getImagesFromPDF
-- Type : `(file: string) => Promise`
+:::caution Deprecated
+`getImagesFromPDF` renders and holds **every** page in memory at once. Use [openPDF](/docs/Utils/open-pdf) or the [usePDFDocument](/docs/Utils/use-pdf-document) hook to render pages progressively instead.
+:::
-> The parameter should be a valid Object URL of type string. If you have an object of type [File](https://developer.mozilla.org/fr/docs/Web/API/File) ( typically generated by file upload libraries) in hand you must use [URL.createObjectURL()](https://developer.mozilla.org/fr/docs/Web/API/URL/createObjectURL) to generate a valid URL object.
+- Type : `(source: PDFSource, maxPages?: number, onSuccess?: () => void, resolution?: number) => Promise`
+
+> Renders every page of a PDF to a PNG data URL. Pages that fail to render are skipped, so the result may be shorter than the page count. `source` accepts a URL string, a `File`/`Blob`, an `ArrayBuffer` or a `Uint8Array`.
```javascript
getImagesFromPDF(file).then((images) => {
- // Do something with images pdf pages
+ // images: ['data:image/png;base64,...', ...]
})
```
+
+- **`maxPages`** : rejects with a `TooManyPagesError` when the document has more pages.
+- **`onSuccess`** : called once the document has been accepted, before pages are rendered.
+- **`resolution`** : target pixel count of each page, see [PDFPageOptions](/docs/Utils/open-pdf#page-options).
diff --git a/docs/docs/Utils/get-pdf-page-count.mdx b/docs/docs/Utils/get-pdf-page-count.mdx
index 4d2c976..e6caca7 100644
--- a/docs/docs/Utils/get-pdf-page-count.mdx
+++ b/docs/docs/Utils/get-pdf-page-count.mdx
@@ -1,15 +1,20 @@
---
-sidebar_position: 2
+sidebar_position: 4
---
# getPDFPageCount
-- Type : `(file: string) => Promise`
+- Type : `(source: PDFSource) => Promise`
+- `PDFSource` : `string | Blob | ArrayBuffer | Uint8Array`
-> The parameter should be a valid Object URL of type string. If you have an object of type [File](https://developer.mozilla.org/fr/docs/Web/API/File) ( typically generated by file upload libraries) in hand you must use [URL.createObjectURL()](https://developer.mozilla.org/fr/docs/Web/API/URL/createObjectURL) to generate a valid URL object.
+> Returns the number of pages without rendering anything.
```javascript
getPDFPageCount(file).then((pageCount) => {
// Do something with the page count
})
```
+
+:::tip
+If you also need to render pages, call [openPDF](/docs/Utils/open-pdf) once and read `numPages` from the handle so the document is only parsed once.
+:::
diff --git a/docs/docs/Utils/open-pdf.mdx b/docs/docs/Utils/open-pdf.mdx
new file mode 100644
index 0000000..5ab9cd6
--- /dev/null
+++ b/docs/docs/Utils/open-pdf.mdx
@@ -0,0 +1,102 @@
+---
+sidebar_position: 1
+---
+
+# openPDF
+
+- Type : `(source: PDFSource, options?: OpenPDFOptions) => Promise`
+
+> Parses a PDF once and returns a handle from which pages are rendered **on demand**. Only the pages you ask for are rasterized, the requested one first, and memory is released by `destroy()`. This is the primitive behind [usePDFDocument](/docs/Utils/use-pdf-document) and the recommended way to display documents with more than a handful of pages.
+
+pdf.js and its Web Worker are downloaded lazily, the first time a PDF is processed, and a single worker is shared by every document in the tab.
+
+```ts
+import { openPDF } from 'react-mindee-js'
+
+const doc = await openPDF(file, { maxPages: 100 })
+doc.numPages // 5
+
+const first = await doc.getPage(1, { priority: 'high' }) // blob: URL
+for await (const { pageNumber, image } of doc.getPages(2, 4)) {
+ // inclusive range, yielded as soon as each page is ready
+}
+
+await doc.destroy() // revokes every blob: URL handed out by this handle
+```
+
+## Source
+
+`PDFSource = string | Blob | ArrayBuffer | Uint8Array`
+
+A URL or data URL string, a [File](https://developer.mozilla.org/docs/Web/API/File)/`Blob` (typically from a file input or upload library), or raw bytes. You no longer need `URL.createObjectURL()` to pass a `File`.
+
+## Options
+
+```ts
+interface OpenPDFOptions {
+ /** Rejects with a `TooManyPagesError` when the document has more pages. */
+ maxPages?: number
+ /** Aborting stops the download/parse and rejects with an `AbortError`. */
+ signal?: AbortSignal
+}
+```
+
+## Handle
+
+```ts
+interface PDFDocumentHandle {
+ readonly numPages: number
+ /** Renders a single page. `pageNumber` is 1-based, like pdf.js. */
+ getPage: (pageNumber: number, options?: PDFPageOptions) => Promise
+ /** Renders pages `from` to `to` inclusive (1-based) one after the other, yielding each as soon as it is ready. */
+ getPages: (from: number, to: number, options?: PDFPageOptions) => AsyncIterable
+ /** Cancels pending renders, revokes every object URL and releases the pdf.js document. */
+ destroy: () => Promise
+}
+
+interface PDFRenderedPage {
+ pageNumber: number // 1-based
+ image: string
+}
+```
+
+:::caution
+Page numbers are **1-based**. `getPage(0)`, a page above `numPages` or an inverted range reject with a `RangeError` before anything is rendered.
+:::
+
+## Page options
+
+```ts
+interface PDFPageOptions {
+ resolution?: number // default 1 500 000 px (width × height), must be > 0
+ output?: 'object-url' | 'data-url' // default 'object-url'
+ priority?: 'high' | 'low' // default 'high' for getPage, 'low' for getPages
+ signal?: AbortSignal
+}
+```
+
+- **`resolution`** : target pixel count of the render. The viewer displays 1.5 Mpx; use a much lower value such as `50_000` for thumbnails. Renders at different resolutions are cached independently.
+- **`output`** : `object-url` returns a `blob:` URL backed by a PNG blob, which is light to hold in memory and is revoked by `destroy()`. `data-url` returns a self-contained base64 PNG string.
+- **`priority`** : `high` puts the request ahead of everything still waiting, so a page the user explicitly asked for is drawn before background prefetches. Requesting an already queued page again with `high` promotes it.
+- **`signal`** : aborting removes the request from the queue (or cancels the in-progress render) and rejects **that caller's** promise with an `AbortError`. Other callers waiting on the same page are unaffected; the render itself is only cancelled once nobody waits for it.
+
+## Behaviour
+
+- Pages render one at a time per document, in priority order. Every successful render is cached per page, resolution and output for the lifetime of the handle.
+- Several calls for the same page share one render.
+- `getPages` requests one page at a time, so breaking out of the `for await` loop leaves nothing pending.
+- After `destroy()`, every pending call rejects (even a render that was about to complete) and the handle is unusable.
+
+:::caution
+Images are `blob:` object URLs by default: they only exist in the current tab and only until `destroy()`. To upload one, read it back first (`await fetch(url).then((r) => r.blob())`) or request `{ output: 'data-url' }`.
+:::
+
+## Errors
+
+| `error.name` | Cause |
+| :-------------------- | :------------------------------------------------------ |
+| `InvalidPDFException` | The source is not a readable PDF |
+| `TooManyPagesError` | The document has more pages than `maxPages` |
+| `RangeError` | A page number or range is outside `1..numPages` |
+| `AbortError` | The request's `signal` was aborted |
+| `Error` | A render was requested after `destroy()` |
diff --git a/docs/docs/Utils/set-shape-config.mdx b/docs/docs/Utils/set-shape-config.mdx
index 57d0840..e6a4299 100644
--- a/docs/docs/Utils/set-shape-config.mdx
+++ b/docs/docs/Utils/set-shape-config.mdx
@@ -1,5 +1,5 @@
---
-sidebar_position: 3
+sidebar_position: 6
---
# setShapeConfig
diff --git a/docs/docs/Utils/use-pdf-document.mdx b/docs/docs/Utils/use-pdf-document.mdx
new file mode 100644
index 0000000..f1890bc
--- /dev/null
+++ b/docs/docs/Utils/use-pdf-document.mdx
@@ -0,0 +1,105 @@
+---
+sidebar_position: 2
+---
+
+# usePDFDocument
+
+- Type : `(source: PDFSource | null | undefined, options?: UsePDFDocumentOptions) => UsePDFDocumentResult`
+
+> React hook that opens a PDF with [openPDF](/docs/Utils/open-pdf) and renders its pages progressively: the first `prefetch` pages right away, then `batch` pages at a time whenever `loadPage` is called for a page that is not rendered yet. The document and every image URL it produced are released automatically when `source` changes or the component unmounts.
+
+```tsx
+import { useState } from 'react'
+import { AnnotationViewer, usePDFDocument } from 'react-mindee-js'
+
+function Document({ file }: { file: File }) {
+ const { status, pages, loading, loadPage, error } = usePDFDocument(file, {
+ prefetch: 5,
+ batch: 3,
+ })
+ const [current, setCurrent] = useState(0)
+
+ if (status === 'error') return
{error?.message}
+ if (status !== 'ready') return
Opening…
+
+ return (
+ <>
+
+ {pages[current] && (
+
+ )}
+ >
+ )
+}
+```
+
+## Options
+
+```ts
+interface UsePDFDocumentOptions {
+ prefetch?: number // default 5, integer >= 0
+ batch?: number // default 3, integer >= 1
+ resolution?: number // see PDFPageOptions.resolution
+ maxPages?: number // see OpenPDFOptions.maxPages
+}
+```
+
+- **`prefetch`** : pages rendered as soon as the document opens.
+- **`batch`** : pages rendered together when `loadPage` asks for one that is not rendered yet. The first `prefetch` pages form one block, the remaining pages are grouped `batch` at a time after it.
+
+## Result
+
+```ts
+interface UsePDFDocumentResult {
+ status: 'idle' | 'opening' | 'ready' | 'error'
+ error: Error | null
+ numPages: number
+ pages: (string | undefined)[]
+ loading: ReadonlySet
+ loadPage: (index: number) => void
+ loadRange: (from: number, to: number) => void
+ document: PDFDocumentHandle | null
+}
+```
+
+- **`pages`** : one slot per page, **0-based**. A slot is `undefined` until the page has been rendered, then holds an image URL usable as `data.image`.
+- **`loading`** : indexes whose render is queued or in progress, handy for per-page skeletons.
+- **`loadPage(index)`** : ensures the page and the rest of its batch are rendered. Idempotent and cheap, so call it liberally (on scroll, click, keyboard navigation…). The page itself is requested with high priority so it is drawn before any pending prefetch, even if it was already queued.
+- **`loadRange(from, to)`** : renders every page in `[from, to)` with low priority. Bounds are clamped to the document; non-integer indexes (here and in `loadPage`) are ignored.
+- **`document`** : the underlying handle for advanced use, `null` until `status` is `ready`.
+
+:::caution
+The hook is **0-based** (array semantics); the handle is **1-based** (pdf.js semantics). Do not mix them up when using `document` directly.
+:::
+
+:::tip
+Keep `source` referentially stable between renders (store the `File` in state, do not rebuild a data URL on every render), otherwise the hook closes and reopens the document each time. Changing `maxPages` also reopens the document. A document still downloading or parsing when the source changes (or the component unmounts) is aborted immediately. Changing `resolution` keeps the document but clears `pages` and aborts any pending render at the old value and re-renders every page that was loaded (or loading) at the new value (renders are cached per resolution, so switching back is instant).
+:::
+
+### Example: load pages as they scroll into view
+
+```tsx
+const { pages, loadPage } = usePDFDocument(file)
+
+useEffect(() => {
+ const observer = new IntersectionObserver((entries) => {
+ entries
+ .filter((entry) => entry.isIntersecting)
+ .forEach((entry) => loadPage(Number(entry.target.dataset.index)))
+ })
+ document.querySelectorAll('[data-index]').forEach((el) => observer.observe(el))
+ return () => observer.disconnect()
+}, [pages.length, loadPage])
+```
diff --git a/docs/docs/customization.md b/docs/docs/customization.md
index 92a64ef..ce0ec2e 100644
--- a/docs/docs/customization.md
+++ b/docs/docs/customization.md
@@ -24,7 +24,7 @@ sidebar_position: 6
individual shape [**config**](https://konvajs.org/api/Konva.Shape.html) passed on each item of the list of shapes override the **shapeConfig** property
:::
-- `zoom`:
+- `zoom`: wheel zoom settings. `modifier` is the multiplier applied per wheel step; `max` is the highest magnification reachable (`1` = fits, `3` = three times larger), independently of the image pixel size. `defaultZoom` is accepted for backward compatibility but currently has no effect.
##### Example
diff --git a/docs/docs/intro.md b/docs/docs/intro.md
index 2a1aabb..a90f581 100644
--- a/docs/docs/intro.md
+++ b/docs/docs/intro.md
@@ -16,7 +16,7 @@ This library was made for building frontend interfaces on top of **[Mindee](http
## Features
-- Support for image and PDF files
+- Support for image (JPEG, PNG, TIFF, HEIC/HEIF) and PDF files, with progressive page rendering for PDFs
- Interactive shapes with events binding
- Extensible styling API
- Controllable state props and modular architecture
@@ -25,20 +25,20 @@ This library was made for building frontend interfaces on top of **[Mindee](http
## Compatibility
-The React SDK is compatible with `React 16.8.0 +`
+The React SDK is compatible with `React 18` and `React 19`, and requires `Node.js >= 22.13` to build.
## Installation and dependencies
The easiest way to use react-select is to install it from npm and build it into your app with Webpack.
```bash
-npm install --save react-mindee-js@1.3.0
+npm install --save react-mindee-js
```
or using yarn
```
-yarn add react-mindee-js@1.3.0
+yarn add react-mindee-js
```
## Usage
@@ -94,6 +94,10 @@ function App() {
- **`style`** : style object to change container css properties
- **`className`** : apply a className to the control
+## PDF documents
+
+PDF pages are rendered on demand with the [usePDFDocument](/docs/Utils/use-pdf-document) hook or the lower-level [openPDF](/docs/Utils/open-pdf) handle. See the [PDF tutorial](/docs/Tutorial/pdf-file).
+
## Browser support
React mindee supports all recent browsers and works where React works. However, you may need check the [SSR](/docs/ssr) section.
diff --git a/docs/docs/migration.md b/docs/docs/migration.md
index fff6dd6..c4403c5 100644
--- a/docs/docs/migration.md
+++ b/docs/docs/migration.md
@@ -2,7 +2,39 @@
sidebar_position: 5
---
-# Migration from v1.2 to v1.3
+# Migration
+
+## From v1.x to v2.0
+
+### Requirements
+
+- `Node.js >= 22.13` to build. The published package targets modern browsers and is compatible with React 18 and 19.
+- The package is ESM only. pdf.js and the HEIC decoder are loaded lazily as separate chunks, so your bundler must support dynamic `import()` (every mainstream bundler does).
+
+### `zoom.max` is now relative to the fitted image
+
+Previously `options.zoom.max` was compared against the absolute canvas scale, so the reachable magnification depended on the pixel size of the image. It is now a factor relative to the image **fitted in the container** (`1` = fits). If you relied on the old behaviour, lower large values (a `max` of `10` now means "ten times the fitted size").
+
+### PDF helpers
+
+| v1 | v2 |
+|:---------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------|
+| `getImagesFromPDF(url)` renders every page | **Deprecated.** Use [usePDFDocument](/docs/Utils/use-pdf-document) or [openPDF](/docs/Utils/open-pdf) to render on demand |
+| `getImagesFromPDF(url, maxPages, onSuccess)` | Still supported, now also accepts a `File`/`Blob`/`ArrayBuffer` and a trailing `resolution` |
+| `getPDFPageCount(url)` | Unchanged, also accepts a `File`/`Blob`/`ArrayBuffer` |
+| URL string required | Any `PDFSource`; `URL.createObjectURL()` is no longer needed for a `File` |
+| Pages returned as base64 data URLs | New APIs return `blob:` object URLs by default (lighter, revoked on `destroy()`); pass `{ output: 'data-url' }` for base64 |
+
+### Image formats
+
+- HEIC/HEIF files are now detected by every HEIF MIME type (`image/heic`, `image/heif` and their `-sequence` variants); previously only `image/heic` was converted.
+- The HEIC decoder (`heic2any`) is loaded lazily, only when such a file is displayed.
+
+### Removed
+
+- `loadPdfDocument` (internal pdf.js loader) is no longer exported. Use `openPDF`.
+
+## From v1.2 to v1.3
> Version 1.3 API becomes simpler. Here is the changes :
diff --git a/eslint.config.ts b/eslint.config.ts
index 6c97a3b..4d835d3 100644
--- a/eslint.config.ts
+++ b/eslint.config.ts
@@ -112,6 +112,7 @@ export default defineConfig(
globals: { ...globals.mocha, cy: 'readonly', Cypress: 'readonly' },
},
rules: {
+ '@typescript-eslint/no-deprecated': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
diff --git a/src/common/types.ts b/src/common/types.ts
index db8bea7..f98081e 100644
--- a/src/common/types.ts
+++ b/src/common/types.ts
@@ -38,6 +38,7 @@ export type AnnotationViewerOptions = BaseOptions & {
modifier: number;
/** Highest magnification reachable through the wheel. */
max: number;
+ /** @deprecated Unused since 1.3.3; kept so existing option objects still type-check. */
defaultZoom: number;
};
};
diff --git a/src/hooks/usePDFDocument.spec.tsx b/src/hooks/usePDFDocument.spec.tsx
new file mode 100644
index 0000000..27d9d7f
--- /dev/null
+++ b/src/hooks/usePDFDocument.spec.tsx
@@ -0,0 +1,493 @@
+import {
+ Component,
+ useEffect,
+ useState,
+ type JSX,
+ type ReactNode,
+} from 'react';
+import multiPage from 'cypress/assets/multi-page.pdf';
+
+import {
+ usePDFDocument,
+ type UsePDFDocumentOptions,
+ type UsePDFDocumentResult,
+} from './usePDFDocument';
+
+const PAGE_COUNT = 5;
+
+type HarnessProps = {
+ source: string | null;
+ options?: UsePDFDocumentOptions;
+ onChange: (result: UsePDFDocumentResult) => void;
+};
+
+/**
+ * Renders the hook and reports every result to the test, which drives
+ * `loadPage` through the latest reported value.
+ */
+const Harness = ({ source, options, onChange }: HarnessProps): JSX.Element => {
+ const result = usePDFDocument(source, options);
+ useEffect(() => {
+ onChange(result);
+ }, [result, onChange]);
+ return (
+