Migration

Migrating from Aspose.PDF for .NET

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

Overview

How the two integrations differ

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 hosts the document state inside your React tree, and useLoadPdf loads a document from bytes.
  • Viewport renders the pages and handles selection, text editing, form filling, links, and zoom on its own; 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 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.
ConcernAspose.PDF for .NET@sdk-pdf/react
Where it runsYour .NET server or desktop appThe user's browser, in your React app
Who changes the documentYour code, before anyone sees the resultThe user in Viewport, and your code through hooks
Showing pagesImages from PngDevice and the other devicesViewport for the main view, PageCanvas for thumbnails
Document accessThe Document object modelState hooks that re-render, command hooks, and event hooks
PagesNumbers from 1 in document.PagesSession-scoped page ids from usePages
FormsFields by name in document.FormWidgets with a fieldName, values bound with useWidgetValue
TextAbsorbers find text fragments, and your code replaces themUsers edit paragraphs in place; useProgrammaticEditor reads and rewrites paragraphs
OutputSave to a file or stream, as PDF or other formatsprepareDocument() bytes or downloadDocument(), PDF only
SetupThe NuGet package and a license fileThe 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 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 and useUndoRedo cover them, thumbnails come from PageCanvas instead of small PNG images, and Building a PDF application shows how the pieces fit.
API map

Rows marked No equivalent are covered in Features without an equivalent.

Aspose.PDF for .NET@sdk-pdf/reactNotes
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 useLoadPdfFetch the stored bytes from your server first.
new Document(stream, password)loadPdf(data, { password })
InvalidPasswordExceptionInvalidPasswordErrorThe rejected loadPdf promise.
new Document(path, new HtmlLoadOptions()) and other load optionsNo equivalentPDF only: convert on the server.
Dispose(), usingresetPdf() from useResetPdfSee Cleanup and lifecycle.
PngDevice, JpegDevice, Resolution, Process(page, stream)ViewportRendered in the browser.
new PngDevice(width, height) for thumbnailsPageCanvasSized with CSS, rendered lazily.
document.Pages.CountusePageCount
document.Pages[pageNumber]pages[pageNumber - 1].id, with pages from usePages()Ids, not numbers.
page.GetPageRect(true)width and height of the usePages entriesBoth with the rotation applied; page.Rect ignores it.
Pages.Add(), Pages.Insert(pageNumber)createPage(width, height, insertAfterPageId?) from usePageCreateInserts after a page, or appends.
Pages.Delete(pageNumber), Pages.Delete(int[])deletePages(pageIdOrIds) from usePageDelete
Pages.Add(page) followed by Pages.Delete(pageNumber) to move a pagemoveBefore or moveAfter from usePageMove
page.Rotate = Rotation.on90setPageRotation(pageIdOrIds, 90) from useSetPageRotationDegrees instead of a Rotation value.
—duplicatePage(sourcePageId, insertAfterPageId?) from usePageDuplicate
page.SetPageSize(width, height), MediaBox, CropBoxNo equivalentcreatePage sizes new pages.
Splitting pages into a new documentprepareDocument({ pageIds }) from useDocumentSavingSaves only the listed pages, in the listed order. See Merge and split.
Pages.Add(otherDocument.Pages)No equivalentSee Merge and split.
document.Form[name], foreach (Field field in document.Form)useWidgets filtered by fieldNameWidgets, not fields.
TextBoxField.Value, CheckboxField.CheckeduseWidgetValueInside a component.
new TextBoxField(page, rectangle), Form.Add(field, pageNumber)createWidgets(props) from useWidgetsCreateA corner and a size instead of two corners.
field.ReadOnly = truesetWidgetProps(ids, { isReadOnly: true }) from useSetWidgetProps
Form.Delete(name)deleteBlocks(widgetIds) from useBlockDelete
document.Flatten(), Form.Flatten()No equivalentisReadOnly locks the fields instead.
Form.ExportToJson, Form.ImportFromJsonNo equivalent
TextAbsorbergetParagraphsByPage, or getAllParagraphIds with getPlainTextParagraphs, from useProgrammaticEditorParagraphs, not one string.
TextFragmentAbsorber to find textuseDocumentSearchHighlights the matches in the main view; no regular expressions.
textFragment.Text = valuegetParagraphEditor(paragraphId), then selectAll, deleteAll, and insertTextRewrites the whole paragraph.
TextBuilder.AppendText(textFragment)createParagraph(pageId, x, y) from useParagraphCreateOr the addText tool.
TextState font, size, and coloruseSelectionTextStyleApplies to the user's selection.
page.AddImage(stream, rectangle)useStampAnnotationCreate with stageImageA 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, useStampAnnotationCreatePartly: no rotation, background layering, or opacity.
page.Annotations.Add(new InkAnnotation(...))useInkAnnotationCreateFreehand paths with a color, a width, and a blend mode.
page.Annotations.Add(annotation) for other typesNo equivalentExisting shapes, lines, and ink display and can be restyled through the selection with useSelectionAnnotationProps.
document.Save(stream)prepareDocument() from useDocumentSavingResolves with a Uint8Array.
—downloadDocument()Saves the PDF on the user's device.
document.Save(path, SaveFormat.DocX) and other formatsNo equivalent
InvalidPdfFileFormatExceptionDocumentFormatErrorSee Errors and logging.
PdfExceptionGenericError, the base of every SDK error
—logMessages, LogLevelProblems the SDK worked around.
Encrypt, Decrypt, Optimize, OptimizeResources, ConvertNo equivalentKeep them on your server.
PdfFileSignature.SignNo equivalentThe SDK never signs.
RedactionAnnotation.Redact()No equivalent
document.OutlinesNo equivalentNo bookmarks.

Install and set up

Before: Aspose.PDF

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

dotnet add package Aspose.PDF
// 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

Add the @sdk-pdf scope to an .npmrc next to your package.json, keep the token in an environment variable, and install; Getting started has the details:

@sdk-pdf:registry=https://registry.sdk.pdf.net
//registry.sdk.pdf.net/:_authToken=${PDF_NET_REGISTRY_TOKEN}
npm install @sdk-pdf/react

Set up the project as Getting started 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. Its props must keep their identity between renders, so define static values outside the component and memoize the configuration:

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

  • 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, and PdfProviders defaults to CONTENT_POLICY_PRESET.PDF_EDITOR, 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, as in Add text, images, and stamps.
  • React only. Every SDK hook and component renders inside PdfProviders.

Load and reset a document

Before: Aspose.PDF

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:

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

The browser fetches the stored PDF from your .NET backend and passes the bytes to loadPdf from useLoadPdf; the password prompt runs in the browser:

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, which returns the load state to 'initial', and the next open() fetches the new file.

What to know

  • 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. 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

Before: Aspose.PDF

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:

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

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 draws the thumbnails in Pages and page operations:

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

  • 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 and 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 describes.

Pages and page operations

Before: Aspose.PDF

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:

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

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, usePageDelete, usePageMove, and useSetPageRotation. A page panel with thumbnails for everyone and page operations for editors:

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

OperationAspose.PDF@sdk-pdf/react
Countdocument.Pages.CountusePageCount, which re-renders only when the count changes
InsertPages.Insert(pageNumber) at a position, Pages.Add()createPage(width, height, insertAfterPageId?): after a page, or appended; width and height required
DeletePages.Delete(pageNumber), Pages.Delete(int[])deletePages(pageIdOrIds): several pages in one call are one undo step
MovePages.Add(page) then Pages.Delete(pageNumber)moveBefore(ids, beforePageId?) (start when omitted) or moveAfter(ids, afterPageId?) (end when omitted)
Rotatepage.Rotate = Rotation.on90setPageRotation(ids, degrees): absolute quarter turns; -90 and 450 are normalized, angles off a quarter turn throw
Duplicate—duplicatePage(sourcePageId, insertAfterPageId?) from usePageDuplicate, async
SizeGetPageRect(true), SetPageSize(width, height), boxeswidth 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 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 scrolls the viewport to a page, and useCurrentPageId re-renders with the page in the middle of the viewport.

Merge and split

Before: Aspose.PDF

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

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

Extracting pages moves to the browser: prepareDocument({ pageIds }) from useDocumentSaving builds a PDF with only the listed pages, in the listed order, with the user's edits:

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, then loadPdf from useLoadPdf with the new bytes.

What to know

  • 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.
  • Numbers at the boundary. Page ids mean nothing to the server. When a request carries pages, getPageIndex(pageId) from 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

Before: Aspose.PDF

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:

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:

// 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

Users fill fields in Viewport with nothing to build when the content policy allows interacting with widgets. For form UI outside the main view, useWidgets lists the widgets and useWidgetValue binds one widget's value like a useState backed by the document; Working with forms explains the model:

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

ConceptAspose.PDF@sdk-pdf/react
Lookupdocument.Form[name] (throws when missing), HasField(name, true), foreachuseWidgets() filtered by fieldName (re-renders), or await getWidgets() from useWidgetsGetter (one-time)
Shared valueA field with several widgetsEvery widget with the same field name shows and synchronizes the same value
TextTextBoxField.ValueA text widget: string
CheckboxCheckboxField.CheckedA checkbox widget: boolean
Radio groupRadioButtonField, Options[n].Selectedradio widgets sharing a fieldName, each with a boolean value
ListsComboBoxField, ListBoxFieldchoice (string; isCombo for a dropdown) and multiChoice (the selected option indices)
Buttons, signaturesButtonField, SignatureFieldpushButton and signature widgets, with no value
Creationnew TextBoxField(page, rectangle), Form.Add(field, pageNumber)createWidgets(props) from useWidgetsCreate; a unique fieldName is generated when omitted
Read-onlyfield.ReadOnly = truesetWidgetProps(ids, { isReadOnly: true }, options?) from useSetWidgetProps
DeletionForm.Delete(name)deleteBlocks(widgetIds) from useBlockDelete; delete same-named widgets together
Flatteningdocument.Flatten(), Form.Flatten(), field.Flatten()No equivalent
Form dataForm.ExportToJson, Form.ImportFromJsonNo equivalent

What to know

  • 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.

Text extraction and replacement

Before: Aspose.PDF

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:

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

Under PDF_EDITOR, users edit text in place in Viewport. From code, useProgrammaticEditor reads the text as paragraphs and rewrites a paragraph through an editor mirror, as Programmatic editing shows:

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

  • 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 changes. A replacement rewrites the whole paragraph text; there is no replacement of a fragment inside it.
  • Search for users. 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.
  • 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.
  • 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

Before: Aspose.PDF

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

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

Users place new text and images with interaction tools, which also cover simple text and image stamps, and useSetInteractionMode switches the tool together with the editing policy. From code, useParagraphCreate creates a paragraph at a position, and useStampAnnotationCreate places an image:

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

  • 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. Compare the active tool from useInteractionTool by .type, as above. The mode is local to each user.
  • Images become stamp annotations. stageImage(blob) 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.
  • Selected blocks. With the select tool, users select, move, and resize blocks; useDeleteSelection, useDuplicateSelection, and 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 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 changes their border color and width.

Saving and downloading

Before: Aspose.PDF

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

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

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:

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

  • 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 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 adds undo and redo of the user's edits. Edits within 500 ms of each other merge into one undo step: markUndoBoundary() from 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

Before: Aspose.PDF

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

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

Failures are typed errors from the call that failed, and problems the SDK works around are log messages you subscribe to with logMessages. Error handling and logging has the full rules:

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

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

@sdk-pdf/reactThrown whenClosest Aspose.PDF exception
InvalidPasswordErrorThe document is encrypted, and the password is missing or wrong.InvalidPasswordException
DocumentFormatErrorThe PDF content is malformed beyond what the SDK can work around.InvalidPdfFileFormatException
NotSupportedErrorThe document uses a PDF feature the SDK doesn't support.—
DocumentNotLoadedErrorAn operation requires a document, and none is loaded.—
InvalidInputErrorThe input passed to the API is invalid.—
UninitializedErrorAn object is used before its asynchronous initialization has completed.—
GenericErrorThe base class of the classes above.PdfException

What to know

  • 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

Before: Aspose.PDF

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:

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

Concern@sdk-pdf/react
Closing a documentresetPdf() from useResetPdf unloads it and returns the load state to 'initial'; there is no dispose call.
Input buffersloadPdf from useLoadPdf reads the buffer for the document's lifetime: pass a copy when you keep the bytes, and don't transfer the original.
Event listenersuseViewportEvent subscriptions follow the component lifecycle.
Log subscriptionslogMessages.on() returns an unsubscribe function; call it in the effect cleanup, as in Errors and logging.
Keyboard listenersuseHotkeys 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

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

Aspose.PDF featureIn @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
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 exportNo 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 PDFPDF only

On this page