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/reactis 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
- Read the 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, with a content policy that matches what users may change. - Serve the stored PDF and load it with
loadPdf, including the password flow. - Replace the page images from
PngDevicewithViewportandPageCanvas. - Move page operations from page numbers to the page hooks.
- Extract pages in the browser, and keep merging on the server.
- Move form filling from
document.Formto widgets. - Move text extraction, search, and replacement to the programmatic editor and
useDocumentSearch. - Move text and images placed at coordinates to the insertion tools and hooks, and keep rotated, layered, or transparent stamps on the server.
- Replace
Saveround trips withprepareDocument. - Move error handling and logging and cleanup.
- Plan for features without an equivalent.
- Remove
Aspose.PDF, the license file, and theSetLicensecall 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
How the two integrations differ
Aspose.PDF for .NET is a document-processing library:
- It installs from NuGet as
Aspose.PDFand 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
Documentholds a whole PDF:Pages(numbered from 1),Formwith its fields,Outlines, andInfo. Your code changes it through the object model and writes it back withSave, 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.
PdfProvidershosts the document state inside your React tree, anduseLoadPdfloads a document from bytes.Viewportrenders the pages and handles selection, text editing, form filling, links, and zoom on its own;PageCanvasrenders 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;
useDocumentSavingbuilds 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:
- 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.
- Serve bytes, not images. Endpoints that render pages to PNG give way to one endpoint that returns the stored PDF;
Viewportrenders it in the browser. - 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.
- 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. - Convert page numbers at the boundary. The browser works with page ids; requests to the server carry page numbers.
- New in the browser. Zoom and undo have no counterpart in Aspose.PDF:
useViewportScaleanduseUndoRedocover them, thumbnails come fromPageCanvasinstead 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/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 | 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 | See Cleanup and lifecycle. |
PngDevice, JpegDevice, Resolution, Process(page, stream) | Viewport | Rendered in the browser. |
new PngDevice(width, height) for thumbnails | PageCanvas | Sized with CSS, rendered lazily. |
document.Pages.Count | usePageCount | |
document.Pages[pageNumber] | pages[pageNumber - 1].id, with pages from usePages() | Ids, not numbers. |
page.GetPageRect(true) | width and height of the usePages entries | Both with the rotation applied; page.Rect ignores it. |
Pages.Add(), Pages.Insert(pageNumber) | createPage(width, height, insertAfterPageId?) from usePageCreate | Inserts 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 page | moveBefore or moveAfter from usePageMove | |
page.Rotate = Rotation.on90 | setPageRotation(pageIdOrIds, 90) from useSetPageRotation | Degrees instead of a Rotation value. |
| — | duplicatePage(sourcePageId, insertAfterPageId?) from usePageDuplicate | |
page.SetPageSize(width, height), MediaBox, CropBox | No equivalent | createPage sizes new pages. |
| Splitting pages into a new document | prepareDocument({ pageIds }) from useDocumentSaving | Saves only the listed pages, in the listed order. See Merge and split. |
Pages.Add(otherDocument.Pages) | No equivalent | See Merge and split. |
document.Form[name], foreach (Field field in document.Form) | useWidgets filtered by fieldName | Widgets, not fields. |
TextBoxField.Value, CheckboxField.Checked | useWidgetValue | Inside a component. |
new TextBoxField(page, rectangle), Form.Add(field, pageNumber) | createWidgets(props) from useWidgetsCreate | A corner and a size instead of two corners. |
field.ReadOnly = true | setWidgetProps(ids, { isReadOnly: true }) from useSetWidgetProps | |
Form.Delete(name) | deleteBlocks(widgetIds) from 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 | Paragraphs, not one string. |
TextFragmentAbsorber to find text | 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 | Or the addText tool. |
TextState font, size, and color | useSelectionTextStyle | Applies to the user's selection. |
page.AddImage(stream, rectangle) | useStampAnnotationCreate with 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 | 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. |
document.Save(stream) | prepareDocument() from 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. |
PdfException | GenericError, the base of every SDK error | |
| — | 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. |
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/reactSet 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, andPdfProvidersdefaults toCONTENT_POLICY_PRESET.PDF_EDITOR, where text, annotations, links, and widgets are all editable. For users who only read and fill forms, start withPDF_VIEWERand thetextSelecttool, as above. - Initial values.
initialContentPolicyandinitialInteractionToolapply when the viewport first mounts; switch them at runtime withuseSetInteractionMode, 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
fetchreturns 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.
loadPdftakes PDF bytes. Where Aspose.PDF opens HTML withHtmlLoadOptionsor 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 ofInvalidPasswordException. The password goes toloadPdfin 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
loadPdfcall 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.
Viewportrenders the pages as users scroll and zoom, andPageCanvasfollows the canvas's displayed CSS size or an explicitscale.PageCanvasrenders 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 fromuseViewportScaleanduseViewportFitScale. - Previews show the loaded file.
PageCanvasdraws 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.
PageCanvastakes a page id of the document inPdfProviders. Images that leave the app, such as previews in a document list or an email, still come fromPngDeviceon your server. - Render only when ready.
ViewportandPageCanvasthrowDocumentNotLoadedErrorwhen no document is loaded. - Styling. The SDK renders unstyled:
theme.classNames,theme.styles, and CSS variables such as--selection-colorattach 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
| Operation | Aspose.PDF | @sdk-pdf/react |
|---|---|---|
| Count | document.Pages.Count | 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, 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)fromusePageIndexGetterresolves 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)fromuseScrollToPagescrolls the viewport to a page, anduseCurrentPageIdre-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)fromusePageIndexGetterreturns a zero-based index, so add 1 fordocument.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
| 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 (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; a unique fieldName is generated when omitted |
| Read-only | field.ReadOnly = true | setWidgetProps(ids, { isReadOnly: true }, options?) from useSetWidgetProps |
| Deletion | Form.Delete(name) | deleteBlocks(widgetIds) from 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
- Where prefilling goes. There is no setter by field name: a value is read and written per widget, through a bound component like
NameInputabove. 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
createWidgetstakesx,y,width, andheight; check the placement on a test document before converting stored rectangles. - Locking instead of flattening.
isReadOnlyrestricts filling the field, not authoring the widget. To flatten, keepdocument.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 withforeach (Field field in document.Form)as before. - Signatures.
signaturewidgets carry a read-onlyisCryptoSigned; the SDK never signs. - Date fields. A text widget's
formatexposes Acrobat date formats asFormatType.DateTime, recognized without running scripts; seeFormatType.
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,contentas a plain string, the page number from 1, its box in user space asbounds, and the paragraph layout, such astextAlignandlineHeight, whichuseSetParagraphPropschanges. A replacement rewrites the whole paragraph text; there is no replacement of a fragment inside it. - Search for users.
useDocumentSearchfinds a query in the document, highlights the matches inViewport, and scrolls to the active one;isCaseSensitiveandisWholeWordare its options, and there are no regular expressions. Each match carries itspageIdandparagraphId. For aRegExpfrom code, matchparagraph.contentyourself and scroll to it withscrollToBlock(paragraph.id)fromuseScrollToBlock. - Style goes through the selection. The editor mirror has
focus,blur,selectAll,deleteAll, andinsertText, and no style operations. Where your code setTextStatefont, size, or color, users style their selection withuseSelectionTextStyle. - 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_EDITORwith the editing tools, as above.textSelectneeds read-only text: with editable text, the combination is unsupported and reports a message throughlogMessages. Compare the active tool fromuseInteractionToolby.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 theaddImagetool ({ type: 'addImage', image, width?, height? }) or on theuseStampAnnotationCreateinput, 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 thePositionof a text fragment. Style the text through the selection withuseSelectionTextStyle. - Selected blocks. With the
selecttool, users select, move, and resize blocks;useDeleteSelection,useDuplicateSelection, anduseMoveSelectiondrive your toolbar buttons and shortcuts. - Stamps are partly covered. An image stamp becomes a stamp annotation from the
addImagetool oruseStampAnnotationCreate, and a text stamp becomes a paragraph from theaddTexttool orcreateParagraph. 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.
useInkAnnotationCreatecreates an ink annotation from freehand paths, the counterpart of anInkAnnotation; creating other annotation types has no equivalent. Existing shapes, lines, and ink display, anduseSelectionAnnotationPropschanges 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 aUint8Array; your endpoint stores the request body as it is. When the stored file still needs a server step, such asFlatten,Encrypt, a PDF/AConvert, or a stamp, open the uploaded bytes withnew Document(stream)and run it as before. - returnOriginalIfUnchanged defaults to
true, returning the buffer passed toloadPdfwhen nothing changed; set it tofalsewhen the bytes will be transferred or mutated. - PDF only. Besides
returnOriginalIfUnchanged, the only option ispageIds, which saves a subset of the pages: there is no output format, encryption, or download file name. Conversions such asSaveFormat.DocXrun on the server from the uploaded PDF. - Unsaved changes are new.
useDocumentRevisionchanges 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;useUndoRedoadds undo and redo of the user's edits. Edits within 500 ms of each other merge into one undo step:markUndoBoundary()fromuseUndoBoundarykeeps a scripted operation, such asuseReplacePlaceholderabove, 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/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
- 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.
ViewportandPageCanvasare the exception: they throwDocumentNotLoadedErrorwhen 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 staticmessage,data, andstack.
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 document | resetPdf() from useResetPdf unloads it and returns the load state to 'initial'; there is no dispose call. |
| Input buffers | loadPdf 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 listeners | 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. |
| Keyboard listeners | 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
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 |
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 |