# Migrating from Aspose.PDF for .NET

Source: https://sdk.pdf.net/docs/v1/migration/from-aspose



This guide moves the user-facing part of an Aspose.PDF for .NET application to `@sdk-pdf/react`. Aspose.PDF runs in your .NET process, on a server or in a desktop app, and changes documents in code; `@sdk-pdf/react` runs in the browser, where users see and edit the document themselves. Your .NET backend keeps fetching and storing the PDF bytes, and keeps its server-side work, such as conversion, signing, and PDF/A. Showing pages, filling forms, editing text, page operations, and saving move to your React app. Work through the checklist below; each step links to the section that covers it.

> **Beta**: `@sdk-pdf/react` is in beta, and its exports and APIs may change between releases. Pin the exact package version and review the changes when you update.

## Migration checklist [#migration-checklist]

* [ ] Read the [overview](https://sdk.pdf.net/docs/v1/migration/from-aspose#overview): how the two integrations differ, and the API map for every Aspose.PDF call you use.
* [ ] Sort your Aspose.PDF code into what users see and edit, which moves to the browser, and what stays on the server.
* [ ] [Install the package and set up `PdfProviders`](https://sdk.pdf.net/docs/v1/migration/from-aspose#install-and-set-up), with a content policy that matches what users may change.
* [ ] [Serve the stored PDF and load it with `loadPdf`](https://sdk.pdf.net/docs/v1/migration/from-aspose#load-and-reset-a-document), including the password flow.
* [ ] [Replace the page images from `PngDevice` with `Viewport` and `PageCanvas`](https://sdk.pdf.net/docs/v1/migration/from-aspose#display-the-document).
* [ ] [Move page operations from page numbers to the page hooks](https://sdk.pdf.net/docs/v1/migration/from-aspose#pages-and-page-operations).
* [ ] [Extract pages in the browser, and keep merging on the server](https://sdk.pdf.net/docs/v1/migration/from-aspose#merge-and-split).
* [ ] [Move form filling from `document.Form` to widgets](https://sdk.pdf.net/docs/v1/migration/from-aspose#forms).
* [ ] [Move text extraction, search, and replacement to the programmatic editor and `useDocumentSearch`](https://sdk.pdf.net/docs/v1/migration/from-aspose#text-extraction-and-replacement).
* [ ] [Move text and images placed at coordinates to the insertion tools and hooks, and keep rotated, layered, or transparent stamps on the server](https://sdk.pdf.net/docs/v1/migration/from-aspose#add-text-images-and-stamps).
* [ ] [Replace `Save` round trips with `prepareDocument`](https://sdk.pdf.net/docs/v1/migration/from-aspose#saving-and-downloading).
* [ ] [Move error handling and logging](https://sdk.pdf.net/docs/v1/migration/from-aspose#errors-and-logging) and [cleanup](https://sdk.pdf.net/docs/v1/migration/from-aspose#cleanup-and-lifecycle).
* [ ] [Plan for features without an equivalent](https://sdk.pdf.net/docs/v1/migration/from-aspose#features-without-an-equivalent).
* [ ] Remove `Aspose.PDF`, the license file, and the `SetLicense` call from every project that no longer needs them, along with the endpoints that render page images. Test encrypted, malformed, large, and form-heavy PDFs, page operations, and the round trip between the browser and your server.

## Overview [#overview]

<details>
  <summary>
    How the two integrations differ
  </summary>

  **Aspose.PDF for .NET** is a document-processing library:

  * It installs from NuGet as `Aspose.PDF` and runs inside your .NET process: an ASP.NET backend, a background service, or a desktop app. It has no browser UI: users see the files and images your code produces.
  * A `Document` holds a whole PDF: `Pages` (numbered from 1), `Form` with its fields, `Outlines`, and `Info`. Your code changes it through the object model and writes it back with `Save`, to a file or a stream.
  * Text is read and replaced with absorbers that visit pages (`TextAbsorber`, `TextFragmentAbsorber`). New content is added as text fragments, images, stamps, and annotations at coordinates in points.
  * Pages become images through devices (`PngDevice`, `JpegDevice`, and others) at a resolution you choose.
  * Beyond editing PDFs, it converts HTML to PDF and PDF to Word, signs, encrypts, optimizes, and produces PDF/A.
  * Without a license it runs in evaluation mode: output carries an evaluation watermark, and only the first four pages of a document are processed.

  **@sdk-pdf/react** runs in the browser: the SDK owns the document, and you own the UI.

  * [`PdfProviders`](https://sdk.pdf.net/docs/v1/reference/PdfProviders) hosts the document state inside your React tree, and [`useLoadPdf`](https://sdk.pdf.net/docs/v1/reference/useLoadPdf) loads a document from bytes.
  * [`Viewport`](https://sdk.pdf.net/docs/v1/reference/Viewport) renders the pages and handles selection, text editing, form filling, links, and zoom on its own; [`PageCanvas`](https://sdk.pdf.net/docs/v1/reference/PageCanvas) renders single pages for thumbnails and previews. Toolbars and panels are your React components, driven by hooks.
  * Edits don't rewrite the PDF file. Each change is an operation in a CRDT, which powers undo and redo; [`useDocumentSaving`](https://sdk.pdf.net/docs/v1/reference/useDocumentSaving) builds the final PDF when you need it.
  * Pages and blocks are addressed by session-scoped ids. Positions are user-space units (1/72 of an inch, the same as Aspose.PDF points), and widget boxes are measured from the bottom-left corner of the page.

  | Concern                  | Aspose.PDF for .NET                                        | @sdk-pdf/react                                                                        |
  | ------------------------ | ---------------------------------------------------------- | ------------------------------------------------------------------------------------- |
  | Where it runs            | Your .NET server or desktop app                            | The user's browser, in your React app                                                 |
  | Who changes the document | Your code, before anyone sees the result                   | The user in `Viewport`, and your code through hooks                                   |
  | Showing pages            | Images from `PngDevice` and the other devices              | `Viewport` for the main view, `PageCanvas` for thumbnails                             |
  | Document access          | The `Document` object model                                | State hooks that re-render, command hooks, and event hooks                            |
  | Pages                    | Numbers from 1 in `document.Pages`                         | Session-scoped page ids from `usePages`                                               |
  | Forms                    | Fields by name in `document.Form`                          | Widgets with a `fieldName`, values bound with `useWidgetValue`                        |
  | Text                     | Absorbers find text fragments, and your code replaces them | Users edit paragraphs in place; `useProgrammaticEditor` reads and rewrites paragraphs |
  | Output                   | `Save` to a file or stream, as PDF or other formats        | `prepareDocument()` bytes or `downloadDocument()`, PDF only                           |
  | Setup                    | The NuGet package and a license file                       | The package from a private registry                                                   |

  What this means for your code:

  1. **Split the work.** Viewing, form filling, text edits, page operations, and saving move to the browser. Extracting pages into a new PDF moves too. Conversion, HTML-to-PDF, signing, OCR, PDF/A, compression, encryption, merging, and stamps with rotation, background layering, or opacity stay on your server with Aspose.PDF, as [Features without an equivalent](https://sdk.pdf.net/docs/v1/migration/from-aspose#features-without-an-equivalent) lists.
  2. **Serve bytes, not images.** Endpoints that render pages to PNG give way to one endpoint that returns the stored PDF; `Viewport` renders it in the browser.
  3. **Hand edits to the user.** Code that fills fields, replaces text, or reorders pages before anyone sees the file becomes UI the user drives, or hook calls in the browser.
  4. **Receive a finished PDF.** The browser uploads the complete document from `prepareDocument()`; the server stores it, or runs its remaining Aspose.PDF steps on it.
  5. **Convert page numbers at the boundary.** The browser works with page ids; requests to the server carry page numbers.
  6. **New in the browser.** Zoom and undo have no counterpart in Aspose.PDF: [`useViewportScale`](https://sdk.pdf.net/docs/v1/reference/useViewportScale) and [`useUndoRedo`](https://sdk.pdf.net/docs/v1/reference/useUndoRedo) cover them, thumbnails come from `PageCanvas` instead of small PNG images, and [Building a PDF application](https://sdk.pdf.net/docs/v1/guides/building-a-pdf-application) shows how the pieces fit.
</details>

<details>
  <summary>
    API map
  </summary>

  Rows marked **No equivalent** are covered in [Features without an equivalent](https://sdk.pdf.net/docs/v1/migration/from-aspose#features-without-an-equivalent).

  | Aspose.PDF for .NET                                                     | @sdk-pdf/react                                                                                                                                         | Notes                                                                                                                                                                   |
  | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | `dotnet add package Aspose.PDF`, `license.SetLicense(path)`             | `npm install @sdk-pdf/react`, `<PdfProviders>`                                                                                                         | No license file to apply.                                                                                                                                               |
  | `new Document(stream)`                                                  | `loadPdf(Uint8Array)` from [`useLoadPdf`](https://sdk.pdf.net/docs/v1/reference/useLoadPdf)                                                                               | Fetch the stored bytes from your server first.                                                                                                                          |
  | `new Document(stream, password)`                                        | `loadPdf(data, { password })`                                                                                                                          |                                                                                                                                                                         |
  | `InvalidPasswordException`                                              | `InvalidPasswordError`                                                                                                                                 | The rejected `loadPdf` promise.                                                                                                                                         |
  | `new Document(path, new HtmlLoadOptions())` and other load options      | **No equivalent**                                                                                                                                      | PDF only: convert on the server.                                                                                                                                        |
  | `Dispose()`, `using`                                                    | `resetPdf()` from [`useResetPdf`](https://sdk.pdf.net/docs/v1/reference/useResetPdf)                                                                                      | See [Cleanup and lifecycle](https://sdk.pdf.net/docs/v1/migration/from-aspose#cleanup-and-lifecycle).                                                                                                                    |
  | `PngDevice`, `JpegDevice`, `Resolution`, `Process(page, stream)`        | [`Viewport`](https://sdk.pdf.net/docs/v1/reference/Viewport)                                                                                                              | Rendered in the browser.                                                                                                                                                |
  | `new PngDevice(width, height)` for thumbnails                           | [`PageCanvas`](https://sdk.pdf.net/docs/v1/reference/PageCanvas)                                                                                                          | Sized with CSS, rendered lazily.                                                                                                                                        |
  | `document.Pages.Count`                                                  | [`usePageCount`](https://sdk.pdf.net/docs/v1/reference/usePageCount)                                                                                                      |                                                                                                                                                                         |
  | `document.Pages[pageNumber]`                                            | `pages[pageNumber - 1].id`, with `pages` from `usePages()`                                                                                             | Ids, not numbers.                                                                                                                                                       |
  | `page.GetPageRect(true)`                                                | `width` and `height` of the [`usePages`](https://sdk.pdf.net/docs/v1/reference/usePages) entries                                                                          | Both with the rotation applied; `page.Rect` ignores it.                                                                                                                 |
  | `Pages.Add()`, `Pages.Insert(pageNumber)`                               | `createPage(width, height, insertAfterPageId?)` from [`usePageCreate`](https://sdk.pdf.net/docs/v1/reference/usePageCreate)                                               | Inserts after a page, or appends.                                                                                                                                       |
  | `Pages.Delete(pageNumber)`, `Pages.Delete(int[])`                       | `deletePages(pageIdOrIds)` from [`usePageDelete`](https://sdk.pdf.net/docs/v1/reference/usePageDelete)                                                                    |                                                                                                                                                                         |
  | `Pages.Add(page)` followed by `Pages.Delete(pageNumber)` to move a page | `moveBefore` or `moveAfter` from [`usePageMove`](https://sdk.pdf.net/docs/v1/reference/usePageMove)                                                                       |                                                                                                                                                                         |
  | `page.Rotate = Rotation.on90`                                           | `setPageRotation(pageIdOrIds, 90)` from [`useSetPageRotation`](https://sdk.pdf.net/docs/v1/reference/useSetPageRotation)                                                  | Degrees instead of a `Rotation` value.                                                                                                                                  |
  | —                                                                       | `duplicatePage(sourcePageId, insertAfterPageId?)` from [`usePageDuplicate`](https://sdk.pdf.net/docs/v1/reference/usePageDuplicate)                                       |                                                                                                                                                                         |
  | `page.SetPageSize(width, height)`, `MediaBox`, `CropBox`                | **No equivalent**                                                                                                                                      | `createPage` sizes new pages.                                                                                                                                           |
  | Splitting pages into a new document                                     | `prepareDocument({ pageIds })` from [`useDocumentSaving`](https://sdk.pdf.net/docs/v1/reference/useDocumentSaving)                                                        | Saves only the listed pages, in the listed order. See [Merge and split](https://sdk.pdf.net/docs/v1/migration/from-aspose#merge-and-split).                                                                              |
  | `Pages.Add(otherDocument.Pages)`                                        | **No equivalent**                                                                                                                                      | See [Merge and split](https://sdk.pdf.net/docs/v1/migration/from-aspose#merge-and-split).                                                                                                                                |
  | `document.Form[name]`, `foreach (Field field in document.Form)`         | [`useWidgets`](https://sdk.pdf.net/docs/v1/reference/useWidgets) filtered by `fieldName`                                                                                  | Widgets, not fields.                                                                                                                                                    |
  | `TextBoxField.Value`, `CheckboxField.Checked`                           | [`useWidgetValue`](https://sdk.pdf.net/docs/v1/reference/useWidgetValue)                                                                                                  | Inside a component.                                                                                                                                                     |
  | `new TextBoxField(page, rectangle)`, `Form.Add(field, pageNumber)`      | `createWidgets(props)` from [`useWidgetsCreate`](https://sdk.pdf.net/docs/v1/reference/useWidgetsCreate)                                                                  | A corner and a size instead of two corners.                                                                                                                             |
  | `field.ReadOnly = true`                                                 | `setWidgetProps(ids, { isReadOnly: true })` from [`useSetWidgetProps`](https://sdk.pdf.net/docs/v1/reference/useSetWidgetProps)                                           |                                                                                                                                                                         |
  | `Form.Delete(name)`                                                     | `deleteBlocks(widgetIds)` from [`useBlockDelete`](https://sdk.pdf.net/docs/v1/reference/useBlockDelete)                                                                   |                                                                                                                                                                         |
  | `document.Flatten()`, `Form.Flatten()`                                  | **No equivalent**                                                                                                                                      | `isReadOnly` locks the fields instead.                                                                                                                                  |
  | `Form.ExportToJson`, `Form.ImportFromJson`                              | **No equivalent**                                                                                                                                      |                                                                                                                                                                         |
  | `TextAbsorber`                                                          | `getParagraphsByPage`, or `getAllParagraphIds` with `getPlainTextParagraphs`, from [`useProgrammaticEditor`](https://sdk.pdf.net/docs/v1/reference/useProgrammaticEditor) | Paragraphs, not one string.                                                                                                                                             |
  | `TextFragmentAbsorber` to find text                                     | [`useDocumentSearch`](https://sdk.pdf.net/docs/v1/reference/useDocumentSearch)                                                                                            | Highlights the matches in the main view; no regular expressions.                                                                                                        |
  | `textFragment.Text = value`                                             | `getParagraphEditor(paragraphId)`, then `selectAll`, `deleteAll`, and `insertText`                                                                     | Rewrites the whole paragraph.                                                                                                                                           |
  | `TextBuilder.AppendText(textFragment)`                                  | `createParagraph(pageId, x, y)` from [`useParagraphCreate`](https://sdk.pdf.net/docs/v1/reference/useParagraphCreate)                                                     | Or the `addText` tool.                                                                                                                                                  |
  | `TextState` font, size, and color                                       | [`useSelectionTextStyle`](https://sdk.pdf.net/docs/v1/reference/useSelectionTextStyle)                                                                                    | Applies to the user's selection.                                                                                                                                        |
  | `page.AddImage(stream, rectangle)`                                      | [`useStampAnnotationCreate`](https://sdk.pdf.net/docs/v1/reference/useStampAnnotationCreate) with [`stageImage`](https://sdk.pdf.net/docs/v1/reference/stageImage)                           | A stamp annotation at a corner and a size; the `{ type: 'addImage', image }` tool places one where the user clicks.                                                     |
  | `TextStamp`, `ImageStamp`, `page.AddStamp(stamp)`                       | The `addImage` and `addText` tools, `createParagraph`, `useStampAnnotationCreate`                                                                      | Partly: no rotation, background layering, or opacity.                                                                                                                   |
  | `page.Annotations.Add(new InkAnnotation(...))`                          | [`useInkAnnotationCreate`](https://sdk.pdf.net/docs/v1/reference/useInkAnnotationCreate)                                                                                  | Freehand paths with a color, a width, and a blend mode.                                                                                                                 |
  | `page.Annotations.Add(annotation)` for other types                      | **No equivalent**                                                                                                                                      | Existing shapes, lines, and ink display and can be restyled through the selection with [`useSelectionAnnotationProps`](https://sdk.pdf.net/docs/v1/reference/useSelectionAnnotationProps). |
  | `document.Save(stream)`                                                 | `prepareDocument()` from [`useDocumentSaving`](https://sdk.pdf.net/docs/v1/reference/useDocumentSaving)                                                                   | Resolves with a `Uint8Array`.                                                                                                                                           |
  | —                                                                       | `downloadDocument()`                                                                                                                                   | Saves the PDF on the user's device.                                                                                                                                     |
  | `document.Save(path, SaveFormat.DocX)` and other formats                | **No equivalent**                                                                                                                                      |                                                                                                                                                                         |
  | `InvalidPdfFileFormatException`                                         | `DocumentFormatError`                                                                                                                                  | See [Errors and logging](https://sdk.pdf.net/docs/v1/migration/from-aspose#errors-and-logging).                                                                                                                          |
  | `PdfException`                                                          | `GenericError`, the base of every SDK error                                                                                                            |                                                                                                                                                                         |
  | —                                                                       | [`logMessages`](https://sdk.pdf.net/docs/v1/reference/logMessages), `LogLevel`                                                                                            | Problems the SDK worked around.                                                                                                                                         |
  | `Encrypt`, `Decrypt`, `Optimize`, `OptimizeResources`, `Convert`        | **No equivalent**                                                                                                                                      | Keep them on your server.                                                                                                                                               |
  | `PdfFileSignature.Sign`                                                 | **No equivalent**                                                                                                                                      | The SDK never signs.                                                                                                                                                    |
  | `RedactionAnnotation.Redact()`                                          | **No equivalent**                                                                                                                                      |                                                                                                                                                                         |
  | `document.Outlines`                                                     | **No equivalent**                                                                                                                                      | No bookmarks.                                                                                                                                                           |
</details>

## Install and set up [#install-and-set-up]

### Before: Aspose.PDF [#before-asposepdf]

Aspose.PDF for .NET installs from NuGet as `Aspose.PDF` and reads its license file in your application code:

```sh
dotnet add package Aspose.PDF
```

```csharp
// Without a license, Aspose.PDF runs in evaluation mode: an evaluation watermark,
// and only the first four pages of a document are processed.
var license = new Aspose.Pdf.License();
license.SetLicense("Aspose.Pdf.lic");
```

### After: @sdk-pdf/react [#after-sdk-pdfreact]

Add the `@sdk-pdf` scope to an `.npmrc` next to your `package.json`, keep the token in an environment variable, and install; [Getting started](https://sdk.pdf.net/docs/v1/guides/getting-started#installation) has the details:

```ini
@sdk-pdf:registry=https://registry.sdk.pdf.net
//registry.sdk.pdf.net/:_authToken=${PDF_NET_REGISTRY_TOKEN}
```

```sh
npm install @sdk-pdf/react
```

Set up the project as [Getting started](https://sdk.pdf.net/docs/v1/guides/getting-started#project-setup) describes: import the stylesheet, render the SDK on the client only, alias `canvas` in the bundler, and turn off React Strict Mode.

Then wrap the page that shows a document in [`PdfProviders`](https://sdk.pdf.net/docs/v1/reference/PdfProviders). Its props must keep their identity between renders, so define static values outside the component and memoize the configuration:

```tsx
import { useMemo } from 'react'
import { CONTENT_POLICY_PRESET, InteractionTool, PdfProviders } from '@sdk-pdf/react'
import '@sdk-pdf/react/style.css'

const reviewTool: InteractionTool = { type: 'textSelect' }
const editTool: InteractionTool = { type: 'select' }

export function DocumentPage({
  documentId,
  canEdit,
}: {
  documentId: string
  canEdit: boolean
}) {
  // The PDF application re-renders whenever a PdfProviders prop changes identity.
  const config = useMemo(
    () => ({
      theme: { viewportPadding: 16 },
      // Reviewers read and fill forms; editors also change text, annotations, links, and
      // widgets.
      initialInteractionTool: canEdit ? editTool : reviewTool,
      initialContentPolicy: canEdit
        ? CONTENT_POLICY_PRESET.PDF_EDITOR
        : CONTENT_POLICY_PRESET.PDF_VIEWER,
    }),
    [canEdit]
  )

  return (
    <PdfProviders {...config}>
      <DocumentLoader documentId={documentId} canEdit={canEdit} />
    </PdfProviders>
  )
}
```

### What to know [#what-to-know]

* **No license file.** The install is the registry scope and the package; there is no license file to apply at startup. Keep the Aspose.PDF license wherever the server still runs Aspose.PDF.
* **Decide what users may change.** With Aspose.PDF, only your server code changes a document. In the browser, the content policy decides what users may change in [`Viewport`](https://sdk.pdf.net/docs/v1/reference/Viewport), and `PdfProviders` defaults to [`CONTENT_POLICY_PRESET.PDF_EDITOR`](https://sdk.pdf.net/docs/v1/reference/CONTENT_POLICY_PRESET), where text, annotations, links, and widgets are all editable. For users who only read and fill forms, start with `PDF_VIEWER` and the `textSelect` tool, as above.
* **Initial values.** `initialContentPolicy` and `initialInteractionTool` apply when the viewport first mounts; switch them at runtime with [`useSetInteractionMode`](https://sdk.pdf.net/docs/v1/reference/useSetInteractionMode), as in [Add text, images, and stamps](https://sdk.pdf.net/docs/v1/migration/from-aspose#add-text-images-and-stamps).
* **React only.** Every SDK hook and component renders inside `PdfProviders`.

## Load and reset a document [#load-and-reset-a-document]

### Before: Aspose.PDF [#before-asposepdf-1]

`Document` opens a PDF from a file path or a stream. For an encrypted file, the constructor takes the user or the owner password, and a wrong password throws `InvalidPasswordException`. The document lives until it is disposed, and opening another file means constructing another `Document`:

```csharp
using System;
using System.IO;
using Aspose.Pdf;

public static class Documents
{
    public static int CountPages(Stream input, string password)
    {
        try
        {
            using (var document = string.IsNullOrEmpty(password)
                ? new Document(input)
                : new Document(input, password))
            {
                Console.WriteLine("Encrypted: {0}", document.IsEncrypted);
                return document.Pages.Count;
            }
        }
        catch (InvalidPasswordException)
        {
            // The browser asks the user again and posts the new password.
            return -1;
        }
    }
}
```

### After: @sdk-pdf/react [#after-sdk-pdfreact-1]

The browser fetches the stored PDF from your .NET backend and passes the bytes to `loadPdf` from [`useLoadPdf`](https://sdk.pdf.net/docs/v1/reference/useLoadPdf); the password prompt runs in the browser:

```tsx
import { useEffect, useRef, useState } from 'react'
import { InvalidPasswordError, useLoadPdf, useResetPdf } from '@sdk-pdf/react'

function DocumentLoader({
  documentId,
  canEdit,
}: {
  documentId: string
  canEdit: boolean
}) {
  const [loadState, loadPdf] = useLoadPdf()
  const resetPdf = useResetPdf()
  // The fetched bytes, kept for a password retry and tagged with their document.
  const bytesRef = useRef<{ documentId: string; data: Uint8Array } | null>(null)
  const [loadedDocumentId, setLoadedDocumentId] = useState<string | null>(null)
  const [needsPassword, setNeedsPassword] = useState(false)

  // Another document was requested: unload the current one, whose ids and revisions are
  // now stale.
  useEffect(() => {
    if (loadedDocumentId === null || loadedDocumentId === documentId) return
    resetPdf()
    setLoadedDocumentId(null)
    setNeedsPassword(false)
  }, [documentId, loadedDocumentId, resetPdf])

  async function open(password?: string) {
    let bytes = bytesRef.current
    if (bytes?.documentId !== documentId) {
      // Your .NET endpoint returns the stored PDF bytes.
      const response = await fetch(`/api/documents/${documentId}`)
      if (!response.ok)
        throw new Error(`The document request failed with status ${response.status}`)
      bytes = { documentId, data: new Uint8Array(await response.arrayBuffer()) }
      bytesRef.current = bytes
    }

    try {
      // loadPdf reads from the buffer it receives, so keep the original for a retry and
      // pass a copy.
      await loadPdf(bytes.data.slice(), { password })
      setLoadedDocumentId(documentId)
      setNeedsPassword(false)
    } catch (error) {
      if (error instanceof InvalidPasswordError) {
        // The password is missing or wrong: ask for it, then call open(password) again.
        setNeedsPassword(true)
        return
      }
      throw error
    }
  }

  if (needsPassword)
    return <PasswordPrompt onSubmit={(password) => open(password).catch(showError)} />
  if (loadState.status === 'loading') return <p>Loading the document…</p>
  if (loadState.status === 'ready' && loadedDocumentId === documentId) {
    return <PdfApplication documentId={documentId} canEdit={canEdit} />
  }

  return <button onClick={() => open().catch(showError)}>Open</button>
}
```

When `documentId` changes, the effect calls `resetPdf()` from [`useResetPdf`](https://sdk.pdf.net/docs/v1/reference/useResetPdf), which returns the load state to `'initial'`, and the next `open()` fetches the new file.

### What to know [#what-to-know-1]

* **The server serves bytes.** The endpoint behind `fetch` returns the stored PDF unchanged, without opening it with Aspose.PDF, unless it prefills fields first, as in [Forms](https://sdk.pdf.net/docs/v1/migration/from-aspose#forms). Headers, cookies, and access checks stay on that request.
* **PDF only.** `loadPdf` takes PDF bytes. Where Aspose.PDF opens HTML with `HtmlLoadOptions` or other formats with their load options, convert on the server and send the resulting PDF.
* **Passwords are a rejection.** Without a password, the empty password is tried, as the PDF specification prescribes; a missing or wrong password rejects with `InvalidPasswordError`, the counterpart of `InvalidPasswordException`. The password goes to `loadPdf` in the browser, so your server no longer receives it to open the file.
* **Keep a copy for retries.** The document reads from the given buffer for its whole lifetime, and another `loadPdf` call can detach it. To reuse the bytes, pass a copy: `loadPdf(data.slice(), { password })`.
* **Reset invalidates ids.** After `resetPdf()`, page and block ids, widget snapshots, and document revisions from the old document are no longer valid.

## Display the document [#display-the-document]

### Before: Aspose.PDF [#before-asposepdf-2]

Aspose.PDF has no viewer. A web application renders pages to images on the server with a device and shows them in `<img>` elements. `PngDevice` takes an image resolution, or fixed image dimensions for thumbnails, and `Process` writes one page to a stream:

```csharp
using System.IO;
using Aspose.Pdf;
using Aspose.Pdf.Devices;

public static class PageImages
{
    public static byte[] RenderPage(Stream input, int pageNumber)
    {
        using (var document = new Document(input))
        using (var imageStream = new MemoryStream())
        {
            var pngDevice = new PngDevice(new Resolution(150));
            // Pages are numbered from 1.
            pngDevice.Process(document.Pages[pageNumber], imageStream);
            return imageStream.ToArray();
        }
    }
}
```

### After: @sdk-pdf/react [#after-sdk-pdfreact-2]

[`Viewport`](https://sdk.pdf.net/docs/v1/reference/Viewport) renders the pages in the browser, and your components render everything around it. A typical layout puts the page panel beside the viewport and the toolbar above it; [`PageCanvas`](https://sdk.pdf.net/docs/v1/reference/PageCanvas) draws the thumbnails in [Pages and page operations](https://sdk.pdf.net/docs/v1/migration/from-aspose#pages-and-page-operations):

```tsx
import { useViewportAutoFitScale, Viewport } from '@sdk-pdf/react'

function PdfApplication({
  documentId,
  canEdit,
}: {
  documentId: string
  canEdit: boolean
}) {
  // Opens each document fitted to the viewport width.
  useViewportAutoFitScale('width')

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '180px 1fr', height: '100vh' }}>
      <PagesPanel canEdit={canEdit} />
      <div style={{ display: 'grid', gridTemplateRows: 'auto 1fr', minHeight: 0 }}>
        <header>
          {/* Editing controls only for editors: the content policy is a mode, not a
             permission. */}
          {canEdit && <InsertToolbar />}
          <SaveControls documentId={documentId} />
        </header>
        {/* The viewport fills its parent, so the parent needs an explicit size. */}
        <div style={{ minHeight: 0 }}>
          <Viewport />
        </div>
      </div>
    </div>
  )
}
```

### What to know [#what-to-know-2]

* **No image endpoint, no resolution to pick.** `Viewport` renders the pages as users scroll and zoom, and `PageCanvas` follows the canvas's displayed CSS size or an explicit `scale`. `PageCanvas` renders lazily: an off-screen canvas does no work.
* **Pages are interactive.** Selection, text editing, form filling, links, and zoom gestures work inside `Viewport`, limited by the content policy. Zoom controls come from [`useViewportScale`](https://sdk.pdf.net/docs/v1/reference/useViewportScale) and [`useViewportFitScale`](https://sdk.pdf.net/docs/v1/reference/useViewportFitScale).
* **Previews show the loaded file.** `PageCanvas` draws the page as loaded from the file, with the rotation applied live; the main view shows the edits made during the session. Previews that show those edits are coming soon.
* **Only the loaded document.** `PageCanvas` takes a page id of the document in `PdfProviders`. Images that leave the app, such as previews in a document list or an email, still come from `PngDevice` on your server.
* **Render only when ready.** `Viewport` and `PageCanvas` throw `DocumentNotLoadedError` when no document is loaded.
* **Styling.** The SDK renders unstyled: `theme.classNames`, `theme.styles`, and CSS variables such as `--selection-color` attach your design, as [Theming and customization](https://sdk.pdf.net/docs/v1/guides/theming-and-customization) describes.

## Pages and page operations [#pages-and-page-operations]

### Before: Aspose.PDF [#before-asposepdf-3]

`document.Pages` is a collection numbered from 1. Pages are added, inserted, and deleted by number, rotated with the `Rotate` property, and moved by adding the page at the end and deleting the original:

```csharp
using System;
using Aspose.Pdf;

public static class PageOperations
{
    public static void Rearrange(Document document)
    {
        Console.WriteLine("Pages: {0}", document.Pages.Count);

        // A blank page at position 2, and another at the end.
        document.Pages.Insert(2);
        var lastPage = document.Pages.Add();
        // A4 in points: 1 inch is 72 points.
        lastPage.SetPageSize(597.6, 842.4);

        document.Pages[1].Rotate = Rotation.on90;

        // Moves page 3 to the end: add it again, then delete the original.
        document.Pages.Add(document.Pages[3]);
        document.Pages.Delete(3);
    }
}
```

### After: @sdk-pdf/react [#after-sdk-pdfreact-3]

[`usePages`](https://sdk.pdf.net/docs/v1/reference/usePages) returns the pages in document order, so a page number is a position in that list. Each page operation is a hook that returns a command taking page ids: [`usePageCreate`](https://sdk.pdf.net/docs/v1/reference/usePageCreate), [`usePageDelete`](https://sdk.pdf.net/docs/v1/reference/usePageDelete), [`usePageMove`](https://sdk.pdf.net/docs/v1/reference/usePageMove), and [`useSetPageRotation`](https://sdk.pdf.net/docs/v1/reference/useSetPageRotation). A page panel with thumbnails for everyone and page operations for editors:

```tsx
import {
  PageCanvas,
  usePageCreate,
  usePageDelete,
  usePageMove,
  usePages,
  useScrollToPage,
  useSetPageRotation,
} from '@sdk-pdf/react'

function PagesPanel({ canEdit }: { canEdit: boolean }) {
  const pages = usePages()
  const scrollToPage = useScrollToPage()
  const createPage = usePageCreate()
  const deletePages = usePageDelete()
  const { moveBefore, moveAfter } = usePageMove()
  const setPageRotation = useSetPageRotation()

  return (
    <nav>
      {pages.map((page, index) => (
        <div key={page.id}>
          <button onClick={() => scrollToPage(page.id)}>
            {/* The render resolution follows the canvas CSS size: style .thumbnail to the
               width you need. */}
            <PageCanvas
              pageId={page.id}
              className="thumbnail"
              role="img"
              aria-label={`Page ${index + 1}`}
            />
          </button>
          {canEdit && (
            <div role="toolbar">
              {/* Absolute degrees, like assigning page.Rotate: add to the current angle
                 yourself. */}
              <button onClick={() => setPageRotation(page.id, page.rotation + 90)}>
                Rotate
              </button>
              <button
                disabled={index === 0}
                onClick={() => moveBefore(page.id, pages[index - 1]?.id)}
              >
                Move up
              </button>
              <button
                disabled={index === pages.length - 1}
                onClick={() => moveAfter(page.id, pages[index + 1]?.id)}
              >
                Move down
              </button>
              <button disabled={pages.length <= 1} onClick={() => deletePages(page.id)}>
                Delete
              </button>
              {/* Like Pages.Insert(index + 2): a blank page right after this one, in
                 user-space units. */}
              <button onClick={() => createPage(595, 842, page.id)}>
                Insert blank page
              </button>
            </div>
          )}
        </div>
      ))}
    </nav>
  )
}
```

### What to know [#what-to-know-3]

| Operation | Aspose.PDF                                               | @sdk-pdf/react                                                                                                           |
| --------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Count     | `document.Pages.Count`                                   | [`usePageCount`](https://sdk.pdf.net/docs/v1/reference/usePageCount), which re-renders only when the count changes                          |
| Insert    | `Pages.Insert(pageNumber)` at a position, `Pages.Add()`  | `createPage(width, height, insertAfterPageId?)`: after a page, or appended; `width` and `height` required                |
| Delete    | `Pages.Delete(pageNumber)`, `Pages.Delete(int[])`        | `deletePages(pageIdOrIds)`: several pages in one call are one undo step                                                  |
| Move      | `Pages.Add(page)` then `Pages.Delete(pageNumber)`        | `moveBefore(ids, beforePageId?)` (start when omitted) or `moveAfter(ids, afterPageId?)` (end when omitted)               |
| Rotate    | `page.Rotate = Rotation.on90`                            | `setPageRotation(ids, degrees)`: absolute quarter turns; `-90` and `450` are normalized, angles off a quarter turn throw |
| Duplicate | —                                                        | `duplicatePage(sourcePageId, insertAfterPageId?)` from [`usePageDuplicate`](https://sdk.pdf.net/docs/v1/reference/usePageDuplicate), async  |
| Size      | `GetPageRect(true)`, `SetPageSize(width, height)`, boxes | `width` and `height` of each `usePages` entry, with the rotation applied; no resizing, no page boxes                     |

* **Ids survive reordering; numbers don't.** An id keeps pointing at the same page while pages are added, removed, and rearranged, where a number moves to another page. Ids are session-scoped: persist page numbers, not ids. `getPageIndex(pageId)` from [`usePageIndexGetter`](https://sdk.pdf.net/docs/v1/reference/usePageIndexGetter) resolves an id to its zero-based index inside a handler.
* **Same units.** Aspose.PDF points and user-space units are both 1/72 of an inch, so stored page sizes carry over. A4 is roughly 595 × 842.
* **Undo and stale ids.** Deleting, moving, or rotating several pages in one call is a single undo step, and an undo can restore a deleted page together with its blocks. A command given a stale id throws `InvalidInputError`.
* **Navigation is new.** `scrollToPage(pageId)` from [`useScrollToPage`](https://sdk.pdf.net/docs/v1/reference/useScrollToPage) scrolls the viewport to a page, and [`useCurrentPageId`](https://sdk.pdf.net/docs/v1/reference/useCurrentPageId) re-renders with the page in the middle of the viewport.

## Merge and split [#merge-and-split]

### Before: Aspose.PDF [#before-asposepdf-4]

Merging appends the pages of one `Document` to another, and splitting copies pages into new documents:

```csharp
using System.IO;
using Aspose.Pdf;

public static class DocumentAssembly
{
    public static byte[] Merge(Stream first, Stream second)
    {
        using (var document1 = new Document(first))
        using (var document2 = new Document(second))
        using (var output = new MemoryStream())
        {
            document1.Pages.Add(document2.Pages);
            document1.Save(output);
            return output.ToArray();
        }
    }

    public static byte[] ExtractPages(Stream input, int[] pageNumbers)
    {
        using (var source = new Document(input))
        using (var result = new Document())
        using (var output = new MemoryStream())
        {
            foreach (var pageNumber in pageNumbers)
            {
                result.Pages.Add(source.Pages[pageNumber]);
            }
            result.Save(output);
            return output.ToArray();
        }
    }
}
```

### After: @sdk-pdf/react [#after-sdk-pdfreact-4]

Extracting pages moves to the browser: `prepareDocument({ pageIds })` from [`useDocumentSaving`](https://sdk.pdf.net/docs/v1/reference/useDocumentSaving) builds a PDF with only the listed pages, in the listed order, with the user's edits:

```tsx
import { useDocumentSaving } from '@sdk-pdf/react'

// The counterpart of ExtractPages, with page ids instead of page numbers.
function useExtractPages() {
  const { prepareDocument } = useDocumentSaving()

  return async (pageIds: string[]) => {
    // The loaded document stays as it is; a page id may appear only once.
    const bytes = await prepareDocument({ pageIds })
    return new Blob([bytes as BlobPart], { type: 'application/pdf' })
  }
}
```

Merging stays on your server. For a merge, the server returns the merged PDF, and the browser opens it like any other document: `resetPdf()` from [`useResetPdf`](https://sdk.pdf.net/docs/v1/reference/useResetPdf), then `loadPdf` from [`useLoadPdf`](https://sdk.pdf.net/docs/v1/reference/useLoadPdf) with the new bytes.

### What to know [#what-to-know-4]

* **Pages come from one document.** Importing pages from another file and merging have no equivalent. To repeat a page in the extracted file, duplicate it first with [`usePageDuplicate`](https://sdk.pdf.net/docs/v1/reference/usePageDuplicate).
* **Numbers at the boundary.** Page ids mean nothing to the server. When a request carries pages, `getPageIndex(pageId)` from [`usePageIndexGetter`](https://sdk.pdf.net/docs/v1/reference/usePageIndexGetter) returns a zero-based index, so add 1 for `document.Pages[pageNumber]`.
* **Send the current bytes.** The stored file doesn't include the user's unsaved edits; `prepareDocument()` builds the PDF with them, so a merge request uploads its result.

## Forms [#forms]

### Before: Aspose.PDF [#before-asposepdf-5]

`document.Form` holds the fields. The string indexer returns a field by name and throws when no field has that name; each field type has its own value property:

```csharp
using System;
using Aspose.Pdf;
using Aspose.Pdf.Forms;

public static class ContractForm
{
    public static void Prefill(Document document, string customerName)
    {
        // true searches the whole field hierarchy, which a full name such as
        // customer.name needs.
        if (document.Form.HasField("customer.name", true))
        {
            var nameField = document.Form["customer.name"] as TextBoxField;
            nameField.Value = customerName;
        }

        var termsField = document.Form["terms.accepted"] as CheckboxField;
        termsField.Checked = false;

        foreach (Field field in document.Form)
        {
            Console.WriteLine("{0}: {1}", field.FullName, field.Value);
        }
    }
}
```

Creating a field takes a page and a rectangle given by its lower-left and upper-right corners; locking and flattening finish a submitted form:

```csharp
// Lower-left x and y, then upper-right x and y, in points.
var notesField = new TextBoxField(document.Pages[1], new Rectangle(72, 600, 300, 624));
notesField.PartialName = "notes";
document.Form.Add(notesField, 1);

// Keeps the field but blocks filling it.
notesField.ReadOnly = true;

// Or replace every field with its value.
document.Flatten();
```

### After: @sdk-pdf/react [#after-sdk-pdfreact-5]

Users fill fields in [`Viewport`](https://sdk.pdf.net/docs/v1/reference/Viewport) with nothing to build when the content policy allows interacting with widgets. For form UI outside the main view, [`useWidgets`](https://sdk.pdf.net/docs/v1/reference/useWidgets) lists the widgets and [`useWidgetValue`](https://sdk.pdf.net/docs/v1/reference/useWidgetValue) binds one widget's value like a `useState` backed by the document; [Working with forms](https://sdk.pdf.net/docs/v1/guides/working-with-forms) explains the model:

```tsx
import {
  CheckboxWidget,
  TextWidget,
  useSetWidgetProps,
  useWidgets,
  useWidgetsCreate,
  useWidgetsGetter,
  useWidgetValue,
  Widget,
} from '@sdk-pdf/react'

function isTextWidget(widget: Widget): widget is TextWidget {
  return widget.widgetType === 'text'
}

function isCheckboxWidget(widget: Widget): widget is CheckboxWidget {
  return widget.widgetType === 'checkbox'
}

// The counterpart of document.Form["customer.name"] and document.Form["terms.accepted"],
// as bound inputs.
function ContractFields() {
  const { widgets, isLoaded } = useWidgets()

  // The list is partial until every page containing widgets has been loaded.
  if (!isLoaded) return <p>Loading the form…</p>

  const nameWidget = widgets
    .filter(isTextWidget)
    .find((widget) => widget.fieldName === 'customer.name')
  const termsWidget = widgets
    .filter(isCheckboxWidget)
    .find((widget) => widget.fieldName === 'terms.accepted')

  return (
    <form>
      {nameWidget && <NameInput widget={nameWidget} />}
      {termsWidget && <TermsCheckbox widget={termsWidget} />}
    </form>
  )
}

function NameInput({ widget }: { widget: TextWidget }) {
  // Like TextBoxField.Value, kept in sync with typing in the main view and undo.
  const [value, setValue] = useWidgetValue(widget)

  return (
    <label>
      Customer name
      <input value={value} onChange={(event) => setValue(event.currentTarget.value)} />
    </label>
  )
}

function TermsCheckbox({ widget }: { widget: CheckboxWidget }) {
  const [isChecked, setIsChecked] = useWidgetValue(widget)

  return (
    <label>
      <input
        type="checkbox"
        checked={isChecked}
        onChange={(event) => setIsChecked(event.currentTarget.checked)}
      />
      I accept the terms
    </label>
  )
}

function useAddNotesField() {
  const createWidgets = useWidgetsCreate()

  return async (pageId: string) => {
    // new Rectangle(72, 600, 300, 624) as a position and a size, measured from the
    // bottom-left corner of the page.
    const [widget] = await createWidgets({
      pageId,
      widgetType: 'text',
      x: 72,
      y: 600,
      width: 300 - 72,
      height: 624 - 600,
      fieldName: 'notes',
    })
    return widget
  }
}

function useLockForm() {
  const getWidgets = useWidgetsGetter()
  const setWidgetProps = useSetWidgetProps()

  return async () => {
    // getWidgets loads every page with widgets first, so the list is complete.
    const widgets = await getWidgets()
    // Like setting ReadOnly on every field; excludeFromHistory keeps undo from unlocking
    // the form.
    // Resolves when the widgets show the new state.
    await setWidgetProps(
      widgets.map((widget) => widget.id),
      { isReadOnly: true },
      { excludeFromHistory: true }
    )
  }
}
```

### From fields to widgets [#from-fields-to-widgets]

| Concept             | Aspose.PDF                                                                     | @sdk-pdf/react                                                                                                                                         |
| ------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Lookup              | `document.Form[name]` (throws when missing), `HasField(name, true)`, `foreach` | `useWidgets()` filtered by `fieldName` (re-renders), or `await getWidgets()` from [`useWidgetsGetter`](https://sdk.pdf.net/docs/v1/reference/useWidgetsGetter) (one-time) |
| Shared value        | A field with several widgets                                                   | Every widget with the same field name shows and synchronizes the same value                                                                            |
| Text                | `TextBoxField.Value`                                                           | A `text` widget: `string`                                                                                                                              |
| Checkbox            | `CheckboxField.Checked`                                                        | A `checkbox` widget: `boolean`                                                                                                                         |
| Radio group         | `RadioButtonField`, `Options[n].Selected`                                      | `radio` widgets sharing a `fieldName`, each with a `boolean` value                                                                                     |
| Lists               | `ComboBoxField`, `ListBoxField`                                                | `choice` (`string`; `isCombo` for a dropdown) and `multiChoice` (the selected option indices)                                                          |
| Buttons, signatures | `ButtonField`, `SignatureField`                                                | `pushButton` and `signature` widgets, with no value                                                                                                    |
| Creation            | `new TextBoxField(page, rectangle)`, `Form.Add(field, pageNumber)`             | `createWidgets(props)` from [`useWidgetsCreate`](https://sdk.pdf.net/docs/v1/reference/useWidgetsCreate); a unique `fieldName` is generated when omitted                  |
| Read-only           | `field.ReadOnly = true`                                                        | `setWidgetProps(ids, { isReadOnly: true }, options?)` from [`useSetWidgetProps`](https://sdk.pdf.net/docs/v1/reference/useSetWidgetProps)                                 |
| Deletion            | `Form.Delete(name)`                                                            | `deleteBlocks(widgetIds)` from [`useBlockDelete`](https://sdk.pdf.net/docs/v1/reference/useBlockDelete); delete same-named widgets together                               |
| Flattening          | `document.Flatten()`, `Form.Flatten()`, `field.Flatten()`                      | No equivalent                                                                                                                                          |
| Form data           | `Form.ExportToJson`, `Form.ImportFromJson`                                     | No equivalent                                                                                                                                          |

### What to know [#what-to-know-5]

* **Where prefilling goes.** There is no setter by field name: a value is read and written per widget, through a bound component like `NameInput` above. Values your server knows before the user opens the form can stay a server step: fill them with Aspose.PDF, and serve the filled file.
* **A missing field isn't an exception.** Where the string indexer throws, a widget lookup simply finds nothing, so render nothing for it.
* **Coordinates.** Both measure in points from the bottom-left corner of the page. Aspose.PDF takes the two corners of a rectangle, and `createWidgets` takes `x`, `y`, `width`, and `height`; check the placement on a test document before converting stored rectangles.
* **Locking instead of flattening.** `isReadOnly` restricts filling the field, not authoring the widget. To flatten, keep `document.Flatten()` on the server and run it on the uploaded file.
* **Reading submitted values.** There is no built-in JSON export. The filled PDF from `prepareDocument()` carries the values; read them on the server with `foreach (Field field in document.Form)` as before.
* **Signatures.** `signature` widgets carry a read-only `isCryptoSigned`; the SDK never signs.
* **Date fields.** A text widget's `format` exposes Acrobat date formats as `FormatType.DateTime`, recognized without running scripts; see [`FormatType`](https://sdk.pdf.net/docs/v1/reference/FormatType).

## Text extraction and replacement [#text-extraction-and-replacement]

### Before: Aspose.PDF [#before-asposepdf-6]

Absorbers visit pages: `TextAbsorber` collects the text, and `TextFragmentAbsorber` finds every occurrence of a phrase as a `TextFragment` whose `Text` you can replace. `TextSearchOptions(true)` switches the phrase to a regular expression:

```csharp
using Aspose.Pdf;
using Aspose.Pdf.Text;

public static class DocumentText
{
    public static string ReadAll(Document document)
    {
        var textAbsorber = new TextAbsorber();
        document.Pages.Accept(textAbsorber);
        return textAbsorber.Text;
    }

    public static void ReplacePlaceholder(
        Document document, string placeholder, string value)
    {
        var absorber = new TextFragmentAbsorber(placeholder);
        document.Pages.Accept(absorber);

        foreach (var textFragment in absorber.TextFragments)
        {
            textFragment.Text = value;
        }
    }
}
```

### After: @sdk-pdf/react [#after-sdk-pdfreact-6]

Under `PDF_EDITOR`, users edit text in place in [`Viewport`](https://sdk.pdf.net/docs/v1/reference/Viewport). From code, [`useProgrammaticEditor`](https://sdk.pdf.net/docs/v1/reference/useProgrammaticEditor) reads the text as paragraphs and rewrites a paragraph through an editor mirror, as [Programmatic editing](https://sdk.pdf.net/docs/v1/guides/programmatic-editing) shows:

```tsx
import { useProgrammaticEditor } from '@sdk-pdf/react'

function useReplacePlaceholder() {
  const { getAllParagraphIds, getPlainTextParagraphs, getParagraphEditor } =
    useProgrammaticEditor()

  return async (placeholder: string, value: string) => {
    // Loads every page in turn, which takes a while on a large document;
    // getParagraphsByPage(pageId) reads a single page.
    const paragraphs = await getPlainTextParagraphs(await getAllParagraphIds())

    for (const paragraph of paragraphs) {
      if (!paragraph.content.includes(placeholder)) continue

      // The paragraph may be gone: deleted by an undo since it was read.
      const editor = getParagraphEditor(paragraph.id)
      if (!editor) continue

      editor.selectAll()
      editor.deleteAll()
      editor.insertText(paragraph.content.replaceAll(placeholder, value))
    }
  }
}
```

The counterpart of `TextAbsorber.Text` joins the same entries: `paragraphs.map((paragraph) => paragraph.content).join('\n\n')`.

### What to know [#what-to-know-6]

* **Paragraphs, not fragments.** Each entry carries `id`, `content` as a plain string, the page number from 1, its box in user space as `bounds`, and the paragraph layout, such as `textAlign` and `lineHeight`, which [`useSetParagraphProps`](https://sdk.pdf.net/docs/v1/reference/useSetParagraphProps) changes. A replacement rewrites the whole paragraph text; there is no replacement of a fragment inside it.
* **Search for users.** [`useDocumentSearch`](https://sdk.pdf.net/docs/v1/reference/useDocumentSearch) finds a query in the document, highlights the matches in `Viewport`, and scrolls to the active one; `isCaseSensitive` and `isWholeWord` are its options, and there are no regular expressions. Each match carries its `pageId` and `paragraphId`. For a `RegExp` from code, match `paragraph.content` yourself and scroll to it with `scrollToBlock(paragraph.id)` from [`useScrollToBlock`](https://sdk.pdf.net/docs/v1/reference/useScrollToBlock).
* **Style goes through the selection.** The editor mirror has `focus`, `blur`, `selectAll`, `deleteAll`, and `insertText`, and no style operations. Where your code set `TextState` font, size, or color, users style their selection with [`useSelectionTextStyle`](https://sdk.pdf.net/docs/v1/reference/useSelectionTextStyle).
* **One document.** Programmatic edits flow through the same document as user edits; `setEditorAwarenessProperties({ name, color })` gives the editor a cursor for real-time collaboration, which is coming soon.

## Add text, images, and stamps [#add-text-images-and-stamps]

### Before: Aspose.PDF [#before-asposepdf-7]

New content goes onto a page at coordinates: a `TextFragment` through a `TextBuilder`, an image into a rectangle with `AddImage`, and stamps with `AddStamp`:

```csharp
using System.IO;
using Aspose.Pdf;
using Aspose.Pdf.Text;

public static class Approval
{
    public static void Mark(Document document, Stream logoStream)
    {
        var page = document.Pages[1];

        var textFragment = new TextFragment("Approved by J. Doe");
        textFragment.Position = new Position(100, 600);
        textFragment.TextState.FontSize = 12;
        new TextBuilder(page).AppendText(textFragment);

        // Lower-left x and y, then upper-right x and y, in points.
        page.AddImage(logoStream, new Rectangle(400, 700, 550, 780));

        var stamp = new TextStamp("CONFIDENTIAL");
        // Lays the stamp under the page content.
        stamp.Background = true;
        stamp.XIndent = 100;
        stamp.YIndent = 100;
        stamp.Rotate = Rotation.on90;
        page.AddStamp(stamp);
    }
}
```

### After: @sdk-pdf/react [#after-sdk-pdfreact-7]

Users place new text and images with interaction tools, which also cover simple text and image stamps, and [`useSetInteractionMode`](https://sdk.pdf.net/docs/v1/reference/useSetInteractionMode) switches the tool together with the editing policy. From code, [`useParagraphCreate`](https://sdk.pdf.net/docs/v1/reference/useParagraphCreate) creates a paragraph at a position, and [`useStampAnnotationCreate`](https://sdk.pdf.net/docs/v1/reference/useStampAnnotationCreate) places an image:

```tsx
import {
  CONTENT_POLICY_PRESET,
  InteractionTool,
  stageImage,
  useInteractionTool,
  useParagraphCreate,
  useProgrammaticEditor,
  useSetInteractionMode,
  useStampAnnotationCreate,
} from '@sdk-pdf/react'

const selectTool: InteractionTool = { type: 'select' }
const addTextTool: InteractionTool = { type: 'addText' }

function InsertToolbar() {
  const interactionTool = useInteractionTool()
  const setInteractionMode = useSetInteractionMode()

  async function placeImage(file: File) {
    // Validates and measures the image; the next click on a page places it as a stamp
    // annotation.
    const image = await stageImage(file)
    // Placing images is editing, so switch the policy with the tool.
    setInteractionMode({ type: 'addImage', image }, CONTENT_POLICY_PRESET.PDF_EDITOR)
  }

  return (
    <div role="toolbar">
      <button
        aria-pressed={interactionTool.type === 'select'}
        onClick={() => setInteractionMode(selectTool, CONTENT_POLICY_PRESET.PDF_EDITOR)}
      >
        Select
      </button>
      {/* The next click creates a paragraph and focuses it for typing. */}
      <button
        aria-pressed={interactionTool.type === 'addText'}
        onClick={() => setInteractionMode(addTextTool, CONTENT_POLICY_PRESET.PDF_EDITOR)}
      >
        Add text
      </button>
      <label>
        Add image
        <input
          type="file"
          accept="image/*"
          onChange={(event) => {
            const file = event.currentTarget.files?.[0]
            if (file) placeImage(file).catch(showError)
          }}
        />
      </label>
    </div>
  )
}

// The counterpart of TextBuilder.AppendText at a position, with no click.
function useAddTextAt() {
  const createParagraph = useParagraphCreate()
  const { getParagraphEditor } = useProgrammaticEditor()

  return async (pageId: string, x: number, y: number, text: string) => {
    const paragraphId = await createParagraph(pageId, x, y)

    // The paragraph may be gone: deleted by an undo since it was created.
    const editor = getParagraphEditor(paragraphId)
    if (!editor) return

    editor.selectAll()
    editor.insertText(text)
  }
}

// The counterpart of page.AddImage(stream, new Rectangle(llx, lly, urx, ury)).
function useAddImageAt() {
  const createStamp = useStampAnnotationCreate()

  return async (
    pageId: string,
    blob: Blob,
    llx: number,
    lly: number,
    urx: number,
    ury: number
  ) => {
    const image = await stageImage(blob)
    // x and y are the bottom-left corner in user-space units.
    return createStamp({
      pageId,
      image,
      x: llx,
      y: lly,
      width: urx - llx,
      height: ury - lly,
    })
  }
}
```

### What to know [#what-to-know-7]

* **Switch the policy with the tool.** Pass `PDF_EDITOR` with the editing tools, as above. `textSelect` needs read-only text: with editable text, the combination is unsupported and reports a message through [`logMessages`](https://sdk.pdf.net/docs/v1/reference/logMessages). Compare the active tool from [`useInteractionTool`](https://sdk.pdf.net/docs/v1/reference/useInteractionTool) by `.type`, as above. The mode is local to each user.
* **Images become stamp annotations.** [`stageImage(blob)`](https://sdk.pdf.net/docs/v1/reference/stageImage) validates and measures any image the browser can decode. The placement size goes on the `addImage` tool (`{ type: 'addImage', image, width?, height? }`) or on the `useStampAnnotationCreate` input, in user-space units: a single dimension keeps the aspect ratio, and with none the image keeps its natural size, fit down to the page.
* **Text from code.** `createParagraph(pageId, x, y)` takes the position in user-space units; check the placement on a test document when converting the `Position` of a text fragment. Style the text through the selection with [`useSelectionTextStyle`](https://sdk.pdf.net/docs/v1/reference/useSelectionTextStyle).
* **Selected blocks.** With the `select` tool, users select, move, and resize blocks; [`useDeleteSelection`](https://sdk.pdf.net/docs/v1/reference/useDeleteSelection), [`useDuplicateSelection`](https://sdk.pdf.net/docs/v1/reference/useDuplicateSelection), and [`useMoveSelection`](https://sdk.pdf.net/docs/v1/reference/useMoveSelection) drive your toolbar buttons and shortcuts.
* **Stamps are partly covered.** An image stamp becomes a stamp annotation from the `addImage` tool or `useStampAnnotationCreate`, and a text stamp becomes a paragraph from the `addText` tool or `createParagraph`. A stamp on every page is one call per page. Rotation, background layering, and opacity have no equivalent: apply those stamps to the uploaded file on the server.
* **Annotations.** [`useInkAnnotationCreate`](https://sdk.pdf.net/docs/v1/reference/useInkAnnotationCreate) creates an ink annotation from freehand paths, the counterpart of an `InkAnnotation`; creating other annotation types has no equivalent. Existing shapes, lines, and ink display, and [`useSelectionAnnotationProps`](https://sdk.pdf.net/docs/v1/reference/useSelectionAnnotationProps) changes their border color and width.

## Saving and downloading [#saving-and-downloading]

### Before: Aspose.PDF [#before-asposepdf-8]

After the changes, `Save` writes the complete document to a stream or a file, and other overloads write other formats:

```csharp
using System.IO;
using Aspose.Pdf;

public static class Storage
{
    public static byte[] SaveToBytes(Document document)
    {
        using (var output = new MemoryStream())
        {
            document.Save(output);
            return output.ToArray();
        }
    }
}
```

### After: @sdk-pdf/react [#after-sdk-pdfreact-8]

[`useDocumentSaving`](https://sdk.pdf.net/docs/v1/reference/useDocumentSaving) applies the changes recorded during the session to the loaded document. The upload goes to your .NET backend, which stores the bytes. Saving doesn't reset any modified state: record the saved revision yourself:

```tsx
import { useRef, useState } from 'react'
import {
  useDocumentRevision,
  useDocumentRevisionGetter,
  useDocumentSaving,
} from '@sdk-pdf/react'

function SaveControls({ documentId }: { documentId: string }) {
  const revision = useDocumentRevision()
  const getRevision = useDocumentRevisionGetter()
  // The revision on the server: the loaded document's revision until the first save.
  const [savedRevision, setSavedRevision] = useState(revision)
  // One save at a time, so an overlapping save can't mark a failed upload as saved.
  const isSavingRef = useRef(false)
  const { prepareDocument, downloadDocument } = useDocumentSaving()

  // Covers the user's edits and undo/redo: undoing back to the saved state clears it.
  const hasUnsavedChanges = revision !== savedRevision

  async function save() {
    if (isSavingRef.current) return
    isSavingRef.current = true
    try {
      // Edits made after the save starts may be missing from the saved bytes, so only
      // this revision becomes saved.
      const startRevision = getRevision()
      // The buffer is transferred to fetch, so don't hand out the loaded document's
      // original buffer.
      const bytes = await prepareDocument({ returnOriginalIfUnchanged: false })
      const response = await fetch(`/api/documents/${documentId}`, {
        method: 'PUT',
        body: bytes as BodyInit,
      })
      if (!response.ok) throw new Error(`Saving failed with status ${response.status}`)

      setSavedRevision(startRevision)
    } finally {
      isSavingRef.current = false
    }
  }

  return (
    <div role="toolbar">
      <button onClick={() => save().catch(showError)}>Save</button>
      <button onClick={() => void downloadDocument()}>Download</button>
      {hasUnsavedChanges && <span>Unsaved changes</span>}
    </div>
  )
}
```

One component owns the saved revision, so the save and the indicator compare against the same value. Render it only once the document is ready, so its first revision is the loaded document's.

### What to know [#what-to-know-8]

* **The server receives a finished PDF.** `prepareDocument()` resolves with the complete document as a `Uint8Array`; your endpoint stores the request body as it is. When the stored file still needs a server step, such as `Flatten`, `Encrypt`, a PDF/A `Convert`, or a stamp, open the uploaded bytes with `new Document(stream)` and run it as before.
* **returnOriginalIfUnchanged** defaults to `true`, returning the buffer passed to `loadPdf` when nothing changed; set it to `false` when the bytes will be transferred or mutated.
* **PDF only.** Besides `returnOriginalIfUnchanged`, the only option is `pageIds`, which saves a subset of the pages: there is no output format, encryption, or download file name. Conversions such as `SaveFormat.DocX` run on the server from the uploaded PDF.
* **Unsaved changes are new.** [`useDocumentRevision`](https://sdk.pdf.net/docs/v1/reference/useDocumentRevision) changes on every edit and returns to an earlier value on undo and redo, so comparing it with the saved revision tells whether there are unsaved changes; [`useUndoRedo`](https://sdk.pdf.net/docs/v1/reference/useUndoRedo) adds undo and redo of the user's edits. Edits within 500 ms of each other merge into one undo step: `markUndoBoundary()` from [`useUndoBoundary`](https://sdk.pdf.net/docs/v1/reference/useUndoBoundary) keeps a scripted operation, such as `useReplacePlaceholder` above, a step of its own.
* **Downloads.** `downloadDocument()` builds the same bytes and saves them on the user's device as a file.

## Errors and logging [#errors-and-logging]

### Before: Aspose.PDF [#before-asposepdf-9]

Aspose.PDF throws exceptions that derive from `PdfException`, such as `InvalidPasswordException` for a wrong password and `InvalidPdfFileFormatException` for an invalid PDF:

```csharp
using System.IO;
using Aspose.Pdf;

public static class Validation
{
    public static string Check(Stream input, string password)
    {
        try
        {
            using (var document = string.IsNullOrEmpty(password)
                ? new Document(input)
                : new Document(input, password))
            {
                return $"{document.Pages.Count} pages";
            }
        }
        catch (InvalidPasswordException)
        {
            return "The password is wrong.";
        }
        catch (InvalidPdfFileFormatException)
        {
            return "The file is not a valid PDF.";
        }
        catch (PdfException exception)
        {
            return exception.Message;
        }
    }
}
```

### After: @sdk-pdf/react [#after-sdk-pdfreact-9]

Failures are typed errors from the call that failed, and problems the SDK works around are log messages you subscribe to with [`logMessages`](https://sdk.pdf.net/docs/v1/reference/logMessages). [Error handling and logging](https://sdk.pdf.net/docs/v1/guides/error-handling-and-logging) has the full rules:

```tsx
import { useEffect } from 'react'
import { InvalidPasswordError, logMessages, LogLevel, useLoadPdf } from '@sdk-pdf/react'

function useSdkDiagnostics() {
  useEffect(() => {
    // The SDK never prints to the console: subscribe, and unsubscribe on unmount.
    const unsubscribe = logMessages.on('message', (logMessage) => {
      if (logMessage.level >= LogLevel.Warning)
        reportToMonitoring(logMessage.message, logMessage.data)
    })
    return () => {
      unsubscribe()
    }
  }, [])
}

function useOpenBytes() {
  const [, loadPdf] = useLoadPdf()

  return async (data: Uint8Array, password?: string) => {
    try {
      // Pass a copy and keep `data` for a password retry.
      await loadPdf(data.slice(), { password })
      return 'ready'
    } catch (error) {
      // Like catch (InvalidPasswordException): ask for the password and try again.
      if (error instanceof InvalidPasswordError) return 'needsPassword'
      throw error
    }
  }
}
```

### Error classes [#error-classes]

Every SDK error extends `GenericError` and carries a static `message` and an optional `data` object:

| @sdk-pdf/react           | Thrown when                                                             | Closest Aspose.PDF exception    |
| ------------------------ | ----------------------------------------------------------------------- | ------------------------------- |
| `InvalidPasswordError`   | The document is encrypted, and the password is missing or wrong.        | `InvalidPasswordException`      |
| `DocumentFormatError`    | The PDF content is malformed beyond what the SDK can work around.       | `InvalidPdfFileFormatException` |
| `NotSupportedError`      | The document uses a PDF feature the SDK doesn't support.                | —                               |
| `DocumentNotLoadedError` | An operation requires a document, and none is loaded.                   | —                               |
| `InvalidInputError`      | The input passed to the API is invalid.                                 | —                               |
| `UninitializedError`     | An object is used before its asynchronous initialization has completed. | —                               |
| `GenericError`           | The base class of the classes above.                                    | `PdfException`                  |

### What to know [#what-to-know-9]

* **Errors reach the browser.** Loading, editing, and saving fail in the user's session, so error UI moves to your React components; exceptions on the server remain for the steps that stay there.
* **Nothing half-applied.** Commands validate first and throw before touching anything; readers return empty values instead of throwing; hooks never throw during render. `Viewport` and `PageCanvas` are the exception: they throw `DocumentNotLoadedError` when rendered without a document.
* **Malformed documents.** When the SDK can work around a problem in the file, it completes the operation and logs a message instead of throwing. Each message carries `level` (`LogLevel.Debug`, `Info`, `Warning`, `Error`), `reason`, a static `message`, `data`, and `stack`.

## Cleanup and lifecycle [#cleanup-and-lifecycle]

### Before: Aspose.PDF [#before-asposepdf-10]

`Document` implements `IDisposable`: `Dispose` closes the resources the document uses, and `using` disposes it at the end of the block. With `isManagedStream` set to `true`, the document closes the input stream:

```csharp
using System.IO;
using Aspose.Pdf;

public static class Lifecycle
{
    public static int CountAndClose(Stream input)
    {
        // The document closes the stream itself.
        using (var document = new Document(input, true))
        {
            return document.Pages.Count;
        }
    }
}
```

### After: @sdk-pdf/react [#after-sdk-pdfreact-10]

| Concern            | @sdk-pdf/react                                                                                                                                                                           |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Closing a document | `resetPdf()` from [`useResetPdf`](https://sdk.pdf.net/docs/v1/reference/useResetPdf) unloads it and returns the load state to `'initial'`; there is no dispose call.                                        |
| Input buffers      | `loadPdf` from [`useLoadPdf`](https://sdk.pdf.net/docs/v1/reference/useLoadPdf) reads the buffer for the document's lifetime: pass a copy when you keep the bytes, and don't transfer the original.         |
| Event listeners    | [`useViewportEvent`](https://sdk.pdf.net/docs/v1/reference/useViewportEvent) subscriptions follow the component lifecycle.                                                                                  |
| Log subscriptions  | `logMessages.on()` returns an unsubscribe function; call it in the effect cleanup, as in [Errors and logging](https://sdk.pdf.net/docs/v1/migration/from-aspose#errors-and-logging).                                                      |
| Keyboard listeners | [`useHotkeys`](https://sdk.pdf.net/docs/v1/reference/useHotkeys) installs window-level shortcuts; memoize the mapping, and pass a falsy value instead of it to disable them, such as while a modal is open. |

After `resetPdf()`, page and block ids, widget snapshots, and revisions from the old document are no longer valid: read fresh state through the hooks once the next document loads.

## Features without an equivalent [#features-without-an-equivalent]

Plan around these before you migrate; most of them stay on your server with Aspose.PDF:

| Aspose.PDF feature                                                                             | In @sdk-pdf/react                                                                                                                                                             |
| ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Conversion to other formats (`Save` with `SaveFormat.Doc`, `SaveFormat.DocX`, and more)        | No equivalent; keep it on your server and convert the PDF uploaded from `prepareDocument()`                                                                                   |
| HTML-to-PDF and other inputs (`HtmlLoadOptions` and other load options)                        | No equivalent; convert on your server and send the PDF to `loadPdf`                                                                                                           |
| Page images for use outside the app (`PngDevice`, `JpegDevice`, and the other devices)         | Keep them on your server; inside the app, `Viewport` and `PageCanvas` render the pages                                                                                        |
| Digital signatures (`PdfFileSignature`)                                                        | No equivalent; signature widgets display, `isCryptoSigned` is read-only, and the SDK never signs. Keep signing on your server                                                 |
| OCR (`Document.Convert` with a `CallBackGetHocr` callback to an OCR engine)                    | No equivalent; keep it on your server                                                                                                                                         |
| PDF/A and PDF/X (`Convert` with a `PdfFormat`)                                                 | No equivalent; keep it on your server                                                                                                                                         |
| Encryption and decryption (`Encrypt`, `Decrypt`)                                               | `loadPdf` opens encrypted files with a password, and saving has no encryption option; keep encryption on your server                                                          |
| Compression and linearization (`OptimizeResources`, `Optimize`)                                | No equivalent; keep it on your server                                                                                                                                         |
| Merging and importing pages (`Pages.Add(otherDocument.Pages)`)                                 | No equivalent; keep it on your server, see [Merge and split](https://sdk.pdf.net/docs/v1/migration/from-aspose#merge-and-split)                                                                                                |
| Resizing pages (`SetPageSize`), page boxes (`MediaBox`, `CropBox`)                             | No equivalent; `createPage` sets the size of new pages                                                                                                                        |
| Flattening (`Flatten`)                                                                         | No equivalent; `isReadOnly` locks fields in the browser, and the server flattens the uploaded file                                                                            |
| Form data in JSON (`ExportToJson`, `ImportFromJson`)                                           | No equivalent; values are read and written per widget with `useWidgetValue`                                                                                                   |
| Regular-expression search (`TextSearchOptions`)                                                | No equivalent; `useDocumentSearch` matches plain text, and code can match paragraph text from `useProgrammaticEditor` itself                                                  |
| Replacing a fragment inside a paragraph, styling text from code (`TextState`)                  | No equivalent; the programmatic editor rewrites whole paragraphs, and users style their selection                                                                             |
| Stamp rotation, background layering, and opacity (`TextStamp`, `ImageStamp`)                   | No equivalent; place images with the `addImage` tool or `useStampAnnotationCreate` and text with the `addText` tool or `createParagraph`, or apply these stamps on the server |
| Creating annotations other than ink (highlights, notes, shapes, links), XFDF import and export | No equivalent; existing shapes, lines, and ink display, and their border color and width can be changed                                                                       |
| Redaction (`RedactionAnnotation.Redact`)                                                       | No equivalent                                                                                                                                                                 |
| Bookmarks (`Outlines`)                                                                         | No equivalent                                                                                                                                                                 |
| Generating documents from scratch (`new Document()`, `Pages.Add()`, `TextBuilder`)             | No equivalent; `loadPdf` opens existing PDF bytes, so generate the document on your server                                                                                    |
| Formats other than PDF                                                                         | PDF only                                                                                                                                                                      |

