Skip to main content Skip to footer

What's New in Document Solutions v9.1

We’re excited to introduce the v9.1 release of Document Solutions, bringing major updates across Excel, PDF, Word, DataViewer, and an important new addition to the product line: Document Solutions for PDF JS (DsPdfJS). This release expands the Document Solutions ecosystem with new JavaScript PDF processing capabilities while also enhancing interactive forms, accessible document export, and viewer-side data analysis across the existing product family.

The biggest highlight of this release is DsPdfJS, a new JavaScript PDF API that enables developers to create, edit, secure, optimize, annotate, and process PDF documents using a JavaScript-first programming model. Alongside that launch, DsPdf .NET adds improvements for document-level JavaScripts and form calculation order, DsWord enhances field updating and tagged PDF export, DsExcel .NET and DsExcel Java expand support for modern Excel features with cell checkboxes, picture-in-cell, pivot table enhancements, richer template filtering, improved PDF export fidelity, and stronger SpreadJS interoperability, and DsDataViewer introduces interactive PivotTable support for richer browser-based analysis experiences. Read on to explore everything new in v9.1.

This blog will include updates on the following, including:

Download a Trial of the Latest Release of Document Solutions Today


Document Solutions for PDF JS (DsPdfJS)

One of the biggest highlights of the v9.1 release is the introduction of Document Solutions for PDF JS (DsPdfJS), a new JavaScript PDF API designed to bring core PDF document processing capabilities to JavaScript developers. With DsPdfJS, developers can create, load, edit, secure, optimize, and extract content from PDF documents using a JavaScript-first programming model, expanding the Document Solutions product line further into the JavaScript ecosystem.

This initial release focuses on the core PDF workflows developers need most, including:

As the main highlight of the v9.1 release, DsPdfJS opens the door for JavaScript developers to build modern PDF solutions for browser and server-side scenarios using the same Document Solutions approach to document APIs.

The below is just a quick overview of some of the features and functionality DsPdfJS has to offer. Discover more about all DsPdfJS has to offer by checking out our demos and documentation!

Create, Load, and Save PDFs

Document Solutions for PDF JS (DsPdfJS) gives JavaScript developers a straightforward way to create, load, and save PDF documents in code. This is one of the core capabilities of the new product line, enabling applications to generate PDFs from scratch, open existing files for processing, and save results with configurable output options.

The main API centers on the PdfDocument class. Developers can create a new document with PdfDocument.create(), optionally supplying PdfDocumentOptions to control settings such as page size, PDF/A conformance, and font embedding behavior. Existing PDFs can be opened with PdfDocument.load(), and finished documents can be written out with savePdf(), including support for save options such as linearized output for faster web viewing. DsPdfJS also supports adding embedded files to a PDF using addEmbeddedFile(), making it possible to package related resources directly inside the document.

// Create a PDF
using rm = new ResourceManager();
const doc = PdfDocument.create(rm, {
  defaultPageWidth: 595,
  defaultPageHeight: 842,
  conformanceLevel: PdfAConformanceLevel.PdfA1a,
  fontEmbedMode: FontEmbedMode.EmbedFullFont
});
// Load a PDF
using rm = new ResourceManager();
const pdfDoc = PdfDocument.load(rm, await this.loadFileAsArray("Pdfs/ProcurementLetter.pdf"));
/* Loads a binary file from the specified URL */
async loadFileAsArray(url: string): Promise<Uint8Array> {
    const response = await fetch(url);
    if (!response.ok) {
        throw new Error(`Unable to load file: ${response.statusText}`);
    }
    const arrayBuffer = await response.arrayBuffer();
    return new Uint8Array(arrayBuffer);
}
// Save a PDF
const res: Uint8Array = doc.savePdf({ saveMode: SaveMode.Linearized });

Help | Demo

Merge, Split, Insert, and Remove Pages

DsPdfJS also provides the page-level editing features developers need for common PDF workflow automation, including merging documents, splitting content, and inserting or removing pages. These capabilities are useful for assembling reports, extracting content into separate outputs, or restructuring existing PDF files entirely in JavaScript.

For merging, developers can use mergeWithDocument() to append pages from one PDF into another, with support for targeting a specific insertion point and page range. For inserting pages, DsPdfJS provides APIs such as newPageContext() for adding a new page and drawing on it immediately, along with page collection methods like insert() and insertNew() to place pages at a specific position in the document. Pages can be removed with remove() or removeAt(), making it easy to reorganize documents programmatically.

For split scenarios, DsPdfJS exposes split() and related SplitOptions support, which can be used to divide layout content across pages or carry overflow into a secondary layout. These options allow developers to control behaviors such as paragraph handling, widows and orphans, spacing, and the dimensions of the remaining layout area, giving them fine-grained control over how content is split when building multi-page PDF output.

Programmatically Split PDFs in Browser/Web Applications using the split() method exposed by DsPdfJS

const ds = window.DocSol;

async function createPdf() {
  // Load an existing PDF:
  const fn1 = "Wetlands.pdf";
  const fn2 = "WetlandsReview.pdf";
  const bytes1 = await loadFileAsArray(`pdfs/${fn1}`);
  const bytes2 = await loadFileAsArray(`pdfs/${fn2}`);

  const doc1 = ds.PdfDocument.load(bytes1);
  const doc2 = ds.PdfDocument.load(bytes2);
  // Merge doc2 into doc1:
  doc1.mergeWithDocument(doc2);
  return doc1;
}

Help | Demo

Text Extraction: Search, Replace, and Delete

DsPdfJS includes built-in support for extracting and editing PDF text, making it useful for search-driven workflows, document cleanup, and content updates directly in JavaScript. Developers can retrieve page text, search for specific words or phrases, replace matched content, or remove text entirely without leaving the PDF processing pipeline.

For extraction and search, DsPdfJS provides getText() to retrieve all text from a page and FindTextParams to define search criteria such as the target text, case sensitivity, and whole-word matching. This makes it easier to build features such as indexing, validation, content inspection, or targeted text editing across PDF pages.

For editing, DsPdfJS supports both replaceText() and deleteText() on PdfPage. Developers can replace matched text with new content, optionally specifying a font and font size, or delete text using modes such as Standard or PreserveSpace depending on whether surrounding content should reflow. Together, these APIs give JavaScript developers a practical way to implement search, replace, and delete operations for real-world PDF modification scenarios such as updating names, dates, contact information, or removing unwanted text fragments.

Programmatically Find and Replace Text in PDFs using JavaScript

const ds = window.DocSol;

async function createPdf()
{
  const doc = ds.PdfDocument.load(await loadFileAsArray('PDF/LeaseAgreementDemo.pdf'))

  // Replace:
  // "Jane Donahue" -> "John Doe"
  // "(123)098-7654" -> "(007)123-4567"
  // "janed@example.com" -> "johnd@example.com"
  doc.replaceText( {text: "Jane Donahue"}, "John Doe");
  doc.replaceText( {text: "(123)098-7654"}, "(007)123-4567");
  doc.replaceText( {text: "janed@example.com"}, "johnd@example.com");
  // "13-Dec-20 22:16:00" -> date now
  // "13-Dec-22 22:16:00" -> date now + 2 years
  const termStart = new Date();
  const termEnd = new Date(termStart);
  termEnd.setDate(termEnd.getDate() + 365 * 2);
  doc.replaceText( {text: "13-Dec-20 22:16:00"}, termStart.toLocaleString("en-US", {hour12: false}));
  doc.replaceText( {text: "13-Dec-22 22:16:00"}, termEnd.toLocaleString("en-US", {hour12: false}));
  // For reference, add time stamp page footer on all pages:
  for (i = 0; i < doc.pages.count; ++i) {
    const page = doc.pages.getAt(i);
    const ctx = page.context;
    const margin = ctx.resolution / 2;
    ctx.drawText({ text: `Modified on ${new Date().toLocaleString()}, DsPdfJS v${ds.DsPdf.instance.version}`, fontSize: 10, foreColor: 'DimGray' }, margin, ctx.height - margin);
  }
  return doc;
}

Help | Demo

Redact Annotations and Apply Redacts

DsPdfJS includes support for redact annotations and permanent redaction workflows, making it possible to mark sensitive content for removal and then apply those redactions directly to the PDF. This is especially useful for compliance, privacy, and secure document-sharing scenarios where visual masking alone is not enough and content must be actually removed from the file. The core annotation type is RedactAnnotation, which can define a redaction area and optional overlay settings such as fill color, border color, justification, and overlay text.

After annotations are added, developers use PdfDocument.redact() to apply them and permanently remove the affected content. DsPdfJS also provides RedactOptions for controlling how redaction is performed, including image handling, unreadable object behavior, precision, and render options. This makes it possible to build both simple “mark and apply” redaction workflows and more advanced scenarios such as removing targeted link areas or applying document-wide cleanup with controlled rendering behavior.

Apply Redactions to PDFs in Browser Web Applications using a JS PDF API Library

const ds = window.DocSol;

async function createPdf()
{
  // Create PDF doc object & load in PDF file
  const doc = ds.PdfDocument.load(await loadFileAsArray('pdfs/SlidePages.pdf'))
  //Create redact annotation and set redaction area
  const redact = new ds.RedactAnnotation();
  Object.assign(redact, {
    rect: { x: 16, y: 16, width: 280, height: 300 },
    page: doc.pages.getAt(0),
    overlayText: `This content has been redacted by DsPdfJS v${ds.DsPdf.instance.version} on ${new Date().toLocaleString()}`,
    overlayFillColor: "PaleGoldenrod",
  });
  // Apply redaction
  doc.redact();
  return doc;
}

Help | Demo

AcroForms

DsPdfJS includes support for AcroForms, allowing JavaScript developers to create interactive PDF forms from scratch or load existing forms and update them programmatically. This makes it possible to build workflows around form generation, form filling, and field inspection entirely in JavaScript, covering common field types such as text fields, checkboxes, radio buttons, combo boxes, list boxes, signature fields, and buttons.

The main entry point is the document’s acroForm object, which exposes the fields collection for adding, reading, and updating form fields. Developers can create fields by adding typed field definitions to doc.acroForm.fields, configure widget appearance and actions, and later load an existing PDF form and iterate through its fields to inspect or modify values. DsPdfJS also supports AcroForm.calculationOrder, giving developers control over which fields are recalculated when values change.

Programmatically Generate AcroForms using a JavaScript PDF API

const ds = window.DocSol;

async function createPdf() {
  const doc = new ds.PdfDocument();
  const page = doc.pages.addNew();
  const ctx = page.context;
  const inch = ctx.resolution;

  const font = ds.Font.getPdfFont(ds.StandardPdfFont.Times);
  const fontSize = 14;
  const tf = new ds.Format({ fontSize, foreColor: "Black" });

  const x = ctx.resolution;
  let y = ctx.resolution;

  const fldOffset = inch * 2;
  const fldHeight = fontSize * 1.2;
  const dY = inch / 2;

  // Text field:
  ctx.drawText({ text: "Text field:", format: tf }, x, y);
  {
    doc.acroForm.fields.add({
        type: "text",
        name: "fldText",
        value: "Initial text field value",
        defaultValue: "Default text field value",
        widget: {
            page,
            rect: { x: x + fldOffset, y, width: inch * 3, height: fldHeight },
            defaultAppearance: { font, fontSize }
        }
    });
  }
  y += dY;

  // Checkbox:
  ctx.drawText({ text: "Checkbox:", format: tf }, x, y);
  {
    doc.acroForm.fields.add({
        type: "checkbox",
        name: "fldCheckbox",
        checked: true,
        defaultChecked: true,
        widget: {
            page,
            rect: { x: x + fldOffset, y, width: fldHeight, height: fldHeight },
        }
    });
  }
  y += dY;

  // Radio button:
  ctx.drawText({ text: "Radio button:", format: tf }, x, y);
  {
    doc.acroForm.fields.add({
        type: "radiobutton",
        name: "fldRadio",
        value: 1,
        defaultValue: 0,
        widgets:[
          { page, rect: { x: x + fldOffset, y, width: fldHeight, height: fldHeight } },
          { page, rect: { x: x + fldOffset, y: y + fldHeight * 1.2, width: fldHeight, height: fldHeight } },
          { page, rect: { x: x + fldOffset, y: y + (fldHeight * 1.2) * 2, width: fldHeight, height: fldHeight } },
        ]
    });
  }
  y += fldHeight * 3 * 1.2 + fldHeight;

  // CombTextField:
  ctx.drawText({ text: "CombText field:", format: tf }, x, y);
  {
    doc.acroForm.fields.add({
        type: "spacedtext",
        name: "fldCombText",
        value: "123",
        defaultValue: "1234567890",
        widget: {
            page,
            rect: { x: x + fldOffset, y, width: inch * 3, height: fldHeight },
            defaultAppearance: { font, fontSize }
        }
    });
  }
  y += dY;

  // Combo-box:
  ctx.drawText({ text: "Combo box:", format: tf }, x, y);
  {
    doc.acroForm.fields.add({
        type: "combobox",
        name: "fldComboBox",
        items: [ "ComboBox Choice 1", "ComboBox Choice 2", "ComboBox Choice 3"],
        value: 2,
        defaultValue: 0,
        widget: {
            page,
            rect: { x: x + fldOffset, y, width: inch * 3, height: fldHeight },
            defaultAppearance: { font, fontSize }
        }
    });
  }
  y += dY;

  // List box:
  ctx.drawText({ text: "List box:", format: tf }, x, y);
  {
    doc.acroForm.fields.add({
        type: "listbox",
        name: "fldListBox",
        items: [ "ListBox Choice 1", "ListBox Choice 2", "ListBox Choice 3"],
        selectedIndexes: [0, 2],
        defaultSelectedIndexes: [0],
        multiSelect: true,
        widget: {
            page,
            rect: { x: x + fldOffset, y, width: ctx.resolution * 2.5, height: ctx.resolution },
            defaultAppearance: { font, fontSize }
        }
    });
  }
  y += ctx.resolution + dY;

  // Signature field:
  ctx.drawText({ text: "Signature field:", format: tf }, x, y);
  {
    doc.acroForm.fields.add({
        type: "signature",
        name: "fldSignature",
        alternateName: "All fields locked when the document is signed",
        lockedFields: { type: ds.SignatureLockedFieldsType.All },
        widget: {
            page,
            rect: { x: x + fldOffset, y: y - dY / 4, width: inch * 2, height: dY },
            defaultAppearance: { font, fontSize },
            buttonAppearance: { caption: "Click to sign", },
            border: { width: 0.5, color: "DarkSeaGreen" }
        }
    });
  }
  y += dY;

  // Buttons:
  ctx.drawText({ text: "Push buttons:", format: tf }, x, y);

  // Submit form button:
  {
    doc.acroForm.fields.add({
        type: "button",
        name: "btnSubmit",
        alternateName: "Click to submit the form",
        widget: {
            page,
            rect: { x: x + fldOffset, y, width: inch, height: fldHeight },
            defaultAppearance: { font, },
            buttonAppearance: { caption: "Submit", },
            activate: new ds.ActionSubmitForm("Sample Form Submit URI"),
        }
    });
  }

  // Reset form button:
  {
    doc.acroForm.fields.add({
        type: "button",
        name: "btnReset",
        alternateName: "Click to reset the form",
        widget: {
            page,
            rect: { x: x + fldOffset + inch * 1.5, y, width: inch, height: fldHeight },
            defaultAppearance: { font, },
            buttonAppearance: { caption: "Reset", },
            highlighting: ds.HighlightingMode.Invert,
            activate: new ds.ActionResetForm(),
        }
    });
  }

  drawFooter(ctx, ctx.resolution);
  return doc;
}

Help | Demo

Encryption and Permissions

DsPdfJS includes support for PDF encryption and document permissions, allowing JavaScript developers to secure PDF files with passwords and control what recipients are allowed to do with the document. This is essential for workflows that require protected distribution, restricted printing, limited editing, or controlled content copying.

The main APIs here are EncryptOptions for applying security settings and PdfDecryptionOptions for opening protected files. Developers can use doc.security.setEncryptOptions() to set the encryption level, choose whether metadata is encrypted, define a user password, and apply permission settings such as printing or editing restrictions. When loading encrypted files, PdfDocument.load() can use decryption options such as a password, raw password bytes, and error-handling behavior for invalid passwords or unsupported security handlers. DsPdfJS also supports lower-level security handler workflows, including handlers such as StandardSecurityHandlerRev6, for more advanced control over password and permission behavior.

JavaScript support for PDF encryption and document permissions

const ds = window.DocSol;
function createPdf()
{
  const doc = new ds.PdfDocument();

  // Set the required document restrictions (a suitable security handler revision
  // will be created automatically):
  doc.security.setEncryptOptions({
      encryptionLevel: ds.EncryptionLevel.RC128,
      permissions: {
          copyContent: false,
          printingPermissions: ds.PrintingPermissions.Disabled,
          editingPermissions: ds.EditingPermissions.Disabled
      }
  });
  /* Alternatively, you may create a specific revision of the standard security handler
   * and set its options directly:
  // Create a Rev4 security handler and specify some restrictions:
  var ssh4 = ds.StandardSecurityHandlerRev4.create();
  ssh4.copyContent = false;
  ssh4.printingPermissions = ds.PrintingPermissions.Disabled; // TBD! printingPermissions
  ssh4.editingPermissions = ds.EditingPermissions.Disabled;
  doc.security.encryptHander = ssh4;
  */

  // Add a page to the PDF with some explanatory text:
  const ctx = doc.newPageContext();
  const res = ctx.resolution;
  const tf1 = new ds.Format({ fontSize: 20, foreColor: "Brown" });
  const tf2 = new ds.Format({ fontSize: 16, foreColor: "Blue" });
  const tl = new ds.Layout({
    marginAll: ctx.resolution,
    runs: [
      {
        text: "Document Restrictions.\n\n",
        format: tf1
      },
      {
        text:
          "This document has the following restrictions imposed using StandardSecurityHandlerRev4:\n" +
          "  - content copying is not allowed;\n" +
          "  - printing is not allowed;\n" +
          "  - document assembly is not allowed.\n" + 
        "\nSee index.js for details on how to programmatically set document restrictions.",
        format: tf2
      },
    ],
    maxWidth: ctx.width,
    maxHeight: ctx.height,
  });
  ctx.drawLayout(tl, 0, 0);
  // Add page footer with time stamp and DsPdfJS version:
  ctx.drawText({ text: `Generated on ${new Date().toLocaleString()}, DsPdfJS v${ds.DsPdf.instance.version}`, fontSize: 10, foreColor: 'DimGray' }, res, ctx.height - res);
  return doc;
}

Help | Demo

Optimization and Compression

DsPdfJS includes support for PDF optimization and compression, giving developers control over file size and save behavior when generating or modifying PDF documents. This is especially useful for delivery scenarios where smaller files improve storage efficiency, upload speed, and web performance.

The main APIs here are CompressionLevel, PdfStreamHandling, and SavePdfOptions. Developers can choose whether compression should prioritize speed, balanced output, or the smallest possible file, and they can control how existing streams in a loaded document are handled when saving. For example, streams can be copied as-is, recompressed using the document’s compression settings, or minimized for the best possible size reduction. These options are applied through savePdf(), giving JavaScript developers a flexible way to tune output for performance or compactness.

Font Support and Embedding

DsPdfJS includes strong support for fonts and font embedding, allowing developers to use standard PDF fonts, load custom font files, manage families through font collections, and control how text is rendered in generated documents. This is essential for maintaining visual fidelity, supporting brand typography, and ensuring text displays correctly across different PDF viewers and environments.

The main APIs here are Font and FontCollection. Developers can load fonts from files, use built-in standard PDF fonts, register multiple fonts in a collection, and let DsPdfJS resolve the best match by family and style. FontCollection also supports fallback fonts, default fonts, and font search, which makes it easier to handle mixed typography or missing glyphs. Beyond simple font loading, DsPdfJS also supports font features, bold/italic emulation, and custom drawing workflows that use either specific font objects or family-based resolution through a collection.

JS PDF API has Strong Font Support for Generated PDFs

const ds = window.DocSol;

async function createPdf()
{
  // A FontCollection lets you register multiple fonts once and then resolve them by
  // family name + style (bold/italic) when drawing text. You can attach it to a Layout
  // (for DrawingContext.drawLayout) or to a DrawingContext (for drawText/measureText).  
  const fc = new ds.FontCollection();
  async function addFont(fontFileName) {
    fc.addFont(ds.Font.load(await loadFileAsArray(fontFileName)));
  }
  await Promise.all([
    addFont("fonts/NotoSerif-Regular.ttf"),
    addFont("fonts/NotoSerif-Bold.ttf"),
    addFont("fonts/NotoSerif-Italic.ttf"),
    addFont("fonts/NotoSerif-BoldItalic.ttf"),
    addFont("fonts/NotoSans-Regular.ttf"),
    addFont("fonts/NotoSans-Bold.ttf"),
    addFont("fonts/NotoSans-BoldItalic.ttf"),
  ]);

  const doc = new ds.PdfDocument();
  const ctx = doc.newPageContext();
  const page = ctx.page;

  const margin = ctx.resolution / 2;
  const maxWidth = ctx.width - margin * 2;
  const maxHeight = ctx.height - margin * 2;

  const tl = new ds.Layout({
    marginAll: margin,
    maxWidth,
    maxHeight,
    fontCollection: fc,
  });

  let tf = new ds.Format({ fontFamily: "noto serif", fontSize: 16, foreColor: "DarkBlue" });
  tl.append({text: "Text drawn using Noto Serif Regular font. ", format: tf});
  // Setting bold/italic tells the FontCollection to resolve the best matching font
  // in that family. If an exact variant isn't available, the renderer may emulate it.
  tf = new ds.Format(tf, { italic: true, });
  tl.append({text: "Text drawn using Noto Serif Italic font. ", format: tf});
  tf = new ds.Format(tf, { bold: true, });
  tl.append({text: "Text drawn using Noto Serif Bold Italic font. ", format: tf});
  tf = new ds.Format(tf, { italic: false, });
  tl.append({text: "Text drawn using Noto Serif Bold font. ", format: tf});
  // Switch to another font family:
  tf = new ds.Format(tf, { fontFamily: "noto sans", });
  tl.append({text: "Text drawn using Noto Sans Bold font. ", format: tf});
  tf = new ds.Format(tf, { italic: true, });
  tl.append({text: "Text drawn using Noto Sans Bold Italic font. ", format: tf});
  tf = new ds.Format(tf, { bold: false, italic: false, });
  tl.append({text: "Text drawn using Noto Sans Regular font. ", format: tf});
  ctx.drawLayout(tl, 0, 0);

  // You can also assign the FontCollection to the DrawingContext so drawText(), drawLayout(),
  // and measureText() can resolve fontFamily names from that collection
  // (comment out the next line to confirm that the correct font won't be found):
  ctx.fontCollection = fc;
  ctx.drawText("Text drawn with DrawingContext.drawText() method using Noto Serif Bold font from the collection.",
    new ds.Format({ fontFamily: "noto serif", fontSize: 16, foreColor: "darkgreen" }),
    margin,
    tl.contentRect.y + tl.contentRect.height,
    page.width - margin * 2,
  );

  // Add a page footer with time stamp and DsPdfJS version:
  drawFooter(ctx, margin * 2);
  return doc;
}

Help | Demo

Raster and Scalable Vector Image Handling

DsPdfJS includes support for both raster and scalable vector image handling, allowing JavaScript developers to load, draw, clip, and save image content as part of PDF generation workflows. In v9.1, DsPdfJS supports JPEG, PNG, and SVG image scenarios, making it possible to work with both common raster formats and scalable vector artwork when building PDF documents.

For raster images, the main API is the Image class, which can load JPEG or PNG image data, expose image dimensions and type, convert image content to a bitmap, and save image data back out when needed. Developers can then draw images onto PDF pages using the drawing context, with support for scaling, clipping, opacity, alignment, and repeated placement. This makes it easy to handle common tasks such as inserting photos, placing logos, creating cropped image views, or layering semi-transparent images into document layouts.

For SVG workflows, DsPdfJS also provides APIs for drawing SVG content through its SVG drawing support, making it possible to place vector artwork into PDF output while preserving resolution independence. Together, these image APIs give developers a flexible way to combine raster and vector graphics in JavaScript-based PDF generation.

JavaScript Dev SDK: load, draw, clip, and save image content as part of PDF generation workflows

const ds = window.DocSol;

async function createPdf() {
  const doc = new ds.PdfDocument();
  const img = ds.Image.load(await loadFileAsArray("img/wargravepink.jpg"));
  const bmpFull = img.toBitmap();
  const scale = 0.5; // draw at half pixel size

  // Note about PDF/page/image units:
  // - Page coordinates are in “points” (pt). By default, ctx.resolution is 72 pt/in (PDF points),
  //   but you can set ctx.resolution to another value if you want a different coordinate scale.
  // - Images use pixels (px).
  // - If you treat images as 96 dpi, convert with pt = px * (ctx.resolution / 96) before scaling.

  // Page 1:
  let ctx = doc.newPageContext();
  const margin = ctx.resolution / 2;
  let top = margin;
  // Draw using 0.5 points per source pixel (≈ 67% of 96-dpi size):
  ctx.drawImage(img, margin, top, img.width * scale, img.height * scale);
  top += img.height * scale + 36;
  // Draw a clipped portion of the image:
  ctx.drawImage(
    img,
    margin,
    top,
    img.width * scale,
    img.height * scale,
    { clipBounds: { x: margin + 90, y: top + 90, width: 150, height: 100 } }
  );

  drawFooter(ctx, margin);

  // Page 2:
  ctx = doc.newPageContext();
  top = margin;

  // Clip region specified in page units above; convert to bitmap pixel units:
  const clipPx = { x: 90 / scale, y: 90 / scale, width: 150 / scale, height: 100 / scale };
  const bmp = bmpFull.clip(clipPx);

  ctx.drawImage(bmp, margin, top, bmp.width / 2, bmp.height);
  top += bmp.height * 1.5;
  ctx.drawImage(bmp, margin, top, bmp.width, bmp.height / 2);

  drawFooter(ctx, margin);
  return doc;
}

Help | Demo

Annotations

DsPdfJS includes broad support for PDF annotations, allowing JavaScript developers to add comments, markup, links, attachments, shapes, stamps, multimedia, watermarks, widgets, and redaction markers to PDF documents. Supported types include common review and markup annotations such as TextAnnotation, FreeTextAnnotation, TextMarkupAnnotation, SquareAnnotation, CircleAnnotation, LineAnnotation, InkAnnotation, PolygonAnnotation, PolyLineAnnotation, StampAnnotation, LinkAnnotation, FileAttachmentAnnotation, and RedactAnnotation, along with specialized types such as RichMediaAnnotation, SoundAnnotation, WatermarkAnnotation, and WidgetAnnotation.

Many of these annotation types derive from MarkupAnnotation, which provides the shared foundation for comment-style and review-style annotations. In practice, developers can create annotation objects, set properties such as content, color, position, and related appearance settings, and then add them to a page through the page’s annotations collection. This makes DsPdfJS well suited for workflows involving document review, collaboration, commenting, visual markup, links, and interactive PDF experiences.

JavaScript developers can add comments, markup, links, attachments, shapes, stamps, multimedia, watermarks, widgets, and redaction markers to PDF documents

const ds = window.DocSol;

function createPdf()
{
  const doc = new ds.PdfDocument();
  const page = doc.pages.addNew();
  const ctx = page.context;
  const margin = ctx.resolution;
  const gap = margin / 2;
  const maxWidth = ctx.width - margin * 2;
  let ipX = margin, ipY = margin;
  const tf = new ds.Format({ fontSize: 16, font: ds.Font.getPdfFont(ds.StandardPdfFont.Times) });
  const tl = new ds.Layout({
    defaultFormat: tf,
    maxWidth,
  });
  function drawNote(text) {
    tl.clear();
    tl.appendLine({ text });
    ctx.drawLayout(tl, ipX, ipY);
    ctx.drawRect(ipX, ipY, tl.contentWidth, tl.contentHeight, {lineColor: "gray"});
  }
  drawNote("This sample demonstrates some types of annotattions that can be created with DsPdfJS.");
  ipY += tl.contentHeight + gap;

  // A text annotation (red):
  tl.clear();
  drawNote("A red text annotation is placed to the right of this note.");
  const textAnnot = new ds.TextAnnotation(); //ds.getCurrentObjectManager());
  Object.assign(textAnnot, {
    userName: "Jaime Smith",
    contents: "This is a red TextAnnotation.",
    rect: { x: ipX + tl.contentWidth, y: ipY, width: 100, height: 50 },
    color: "Red",
  });
  page.annotations.add(textAnnot);
  // A reply to the previous annotation:
  const textAnnotReply = new ds.TextAnnotation();
  Object.assign(textAnnotReply, {
    userName: "Jane Donahue",
    contents: "This is a reply to the first annotation.",
    referenceAnnotation: textAnnot,
    referenceType: "R",
  });
  page.annotations.add(textAnnotReply);
  ipY += tl.contentHeight + gap;
  // Open text annotation (green):
  drawNote("An initially open green text annotation is placed to the right of this note.");
  const textAnnotOpen = new ds.TextAnnotation();
  Object.assign(textAnnotOpen, {
    open: true,
    userName: "Jaime Smith",
    contents: "This is an initially open annotation (green).",
    rect: { x: ipX + tl.contentWidth, y: ipY, width: 100, height: 50 },
    color: "green",
  });
  page.annotations.add(textAnnotOpen);
  ipY += tl.contentHeight + gap;
  // Free text annotation:
  drawNote("A blue free text annotation is placed below and to the right, with a callout going from it to this note.");
  const calloutLine = [
    {x: ipX + tl.contentWidth / 2, y: ipY + tl.contentHeight},
    {x: ctx.width - 220, y: ipY + tl.contentHeight + gap}
  ];
  ipY += tl.contentHeight + gap;
  const freeAnnot = new ds.FreeTextAnnotation();
  Object.assign(freeAnnot, {
    contents: "This is a free text annotation with a callout line going to the note on the left.",
    rect: { x: ctx.width - 220, y: ipY, width: 200, height: 50 },
    calloutLine,
    color: "LightSkyBlue",
  });
  page.annotations.add(freeAnnot);
  ipY = freeAnnot.rect.y + freeAnnot.rect.height + gap;
  // Free text annotation with rich text:
  const freeRichTextAnnot = new ds.FreeTextAnnotation();
  Object.assign(freeRichTextAnnot, {
    richText: "<body><p>This is another <i>free text annotation</i>, with <b><i>Rich Text</i></b> content.</p>" +
              "<p>Even though a <b>free text</b> annotation displays text directly on a page, " +
              "as other annotations it can be placed outside the page's bounds.</p></body>",
    rect: { x: ipX - margin * 2, y: ipY, width: margin * 5, height: margin },
    calloutLine,
    color: "LightSalmon",
  });
  page.annotations.add(freeRichTextAnnot);
  ipY = freeRichTextAnnot.rect.y + freeRichTextAnnot.rect.height + gap;
  // Square annotation around a note:
  drawNote("A square annotation drawn with a 3pt wide orange line around this note has a rich text associated with it.");
  const sauareAnnot = new ds.SquareAnnotation();
  Object.assign(sauareAnnot, {
    userName: "Jane Donahue",
    contents: "This is a free text annotation with a callout line going to the note on the left.",
    rect: { x: ipX - 7, y: ipY - 7, width: tl.contentWidth + 14, height: tl.contentHeight + 14},
    lineWidth: 3,
    color: "Orange",
    richText: "<body><p>This <b><i>rich text</i></b> is associated with the square annotation around a text note.</p></body>"
  });
  page.annotations.add(sauareAnnot);

  // Add page footer with time stamp and DsPdfJS version:
  ctx.drawText({ text: `Generated on ${new Date().toLocaleString()}, DsPdfJS v${ds.DsPdf.instance.version}`, fontSize: 10, foreColor: 'DimGray' }, margin, ctx.height - margin);
  return doc;
}

Help | Demo


Document Solutions for PDF .NET (DsPdf .NET) 

Document Solutions for PDF .NET (DsPdf .NET) v9.1 expands support for interactive PDF workflows with improvements to document-level JavaScripts and AcroForm field calculation order. These updates help developers build more capable PDF forms by making it easier to organize shared JavaScript logic at the document level and define calculation behavior more explicitly.

Together, these enhancements improve compatibility with standard PDF form behavior and give developers more control over how form logic is stored and managed. While v9.1 adds support for defining these scripts and calculation settings, it does not add support for executing document-level JavaScript in DsPdf .NET, DsPdfViewer, or DsPdfJS in this release.

Document-Level JavaScripts

DsPdf .NET v9.1 adds support for document-level JavaScripts, a standard PDF feature that allows reusable JavaScript functions to be stored at the document level and called from other JavaScript actions. This is especially useful in interactive forms, where shared validation or calculation logic can be defined once and reused across multiple fields or buttons.

The main API addition is GcPdfDocument.JavaScripts, a dictionary whose key is a unique script name and whose value is an ActionJavaScript object containing the associated script. This makes it easier to organize document-wide scripts in a way that aligns with the PDF specification and common Acrobat workflows.

var doc = new GcPdfDocument();
var p = doc.NewPage();
var fldA = AddTextField(p, "fldA", "fldA (should be less than 10)", 10, 10, 50);
fldA.Value = 5;
var fldB = AddTextField(p, "fldB", "fldB (should be greater than 1)", 10, 35, 50);
fldB.Value = 2;
var fldSum = AddTextField(p, "fldSum", "Sum of fldA and fldB", 10, 70, 50);
var btn = new PushButtonField();
doc.AcroForm.Fields.Add(btn);
btn.Widget.Page = p;
btn.Widget.Rect = new RectangleF(30, 120, 100, 30);
btn.Widget.ButtonAppearance.Caption = "Check & Calculate";
btn.Widget.Events.MouseDown = new ActionJavaScript("checkCalculate()");
string s =
"function checkCalculate()\r\n" +
"{\r\n" +
"    var fldA = this.getField('fldA');\r\n" +
"    var fldB = this.getField('fldB');\r\n" +
"    var fldSum = this.getField('fldSum');\r\n" +
"\r\n" +
"    var a = Number(fldA.value);\r\n" +
"    if (a >= 10)\r\n" +
"    {\r\n" +
"        app.alert('Invalid fldA.');\r\n" +
"        return false;\r\n" +
"    }\r\n" +
"\r\n" +
"    var b = Number(fldB.value);\r\n" +
"    if (b <= 1)\r\n" +
"    {\r\n" +
"        app.alert('Invalid fldB.');\r\n" +
"        return false;\r\n" +
"    }\r\n" +
"\r\n" +
"    fldSum.value = a + b;\r\n" +
"}\r\n";
doc.JavaScripts.Add("checkCalculate", new ActionJavaScript(s));
doc.Save("doc.pdf");

Help | Demo

Field Calculation Order Improvements

DsPdf .NET v9.1 also improves support for AcroForm field calculation order, making it easier to define which fields should be recalculated and in what sequence. Previously, this behavior depended on each field’s CalculationIndex, which was less intuitive and did not always reflect the intended PDF behavior when a field should be excluded from recalculation.

To simplify this, DsPdf .NET now adds the AcroForm.CalculationOrder property, which exposes the calculation order directly as an array of fields. Setting this property updates the corresponding field calculation indexes automatically. This provides a more straightforward way to manage the PDF /CO entry and makes form calculation behavior easier to inspect and control.

// This sample loads a document where the "fldSum" field is listed in AcroForm.CalculationOrder
// and replaces it with the "fldMin" field.
var doc = new GcPdfDocument();
var fs = new FileStream("DOC_7290.pdf", FileMode.Open);
doc.Load(fs);
var cf = doc.AcroForm.CalculationOrder;
Console.WriteLine(cf.Length.ToString());
Console.WriteLine(cf[0].Name);
doc.AcroForm.CalculationOrder = new Field[] { doc.AcroForm.Fields["fldMin"] };
doc.Save("doc.pdf");

Help | Demo

With support for document-level JavaScripts and a more user-friendly calculation order model, DsPdf v9.1 makes it easier to build and manage interactive PDF forms with behavior that more closely matches the PDF specification.


Document Solutions for Word (DsWord)

Document Solutions for Word (DsWord) v9.1 expands support for Word field updates and improves accessible PDF export. This release adds the ability to update LISTNUM, AUTONUM, AUTONUMLGL, and AUTONUMOUT fields, while also enhancing PDF export with structure tags for better accessibility and standards compliance.

Together, these updates help developers generate more accurate numbered documents and produce tagged PDFs that more closely match Microsoft Word’s own export behavior. Whether the goal is to maintain complex numbering schemes in contracts and technical documents or create PDF/A- and PDF/UA-ready output, DsWord v9.1 makes those workflows easier to automate.

Update LISTNUM Field

DsWord v9.1 adds support for updating LISTNUM fields, which are used to continue or retrieve numbering from a specific level of a numbering definition. This is especially useful in documents such as contracts, legal files, and structured technical content where numbering must continue correctly across paragraphs, tables, and other layout elements.

The main API addition is the new ListNumFieldOptions class, which lets developers work with LISTNUM field settings such as the list name, level, and starting value. LISTNUM fields are now included in the normal field update process, so calling UpdateFields() calculates and refreshes their displayed values automatically.

// LISTNUM-fields can be used to increment the numbers of your “main” numbering scheme.
// For example, let’s assume you have a typical contract structure with a title 1., a sublevel 1.1, etc.
GcWordDocument doc = new GcWordDocument();

// create a new list template
string listName = "MyList";
ListTemplate listTemplate = doc.ListTemplates.Add(BuiltInListTemplateId.OutlineLegal, listName);
// and use it to number paragraphs in the contract
Paragraph p = doc.Body.AddParagraph("SCOPE");
p.ListFormat.Template = listTemplate;
p.ListFormat.LevelNumber = (int)OutlineLevel.Level1;
p = doc.Body.AddParagraph("The Licensor grants to the Licensee an exclusive, transferable, sublicensable, remunerated, worldwide License to exploit the Software for the Term and the specific purpose set forth in this License Agreement.");
p.ListFormat.Template = listTemplate;
p.ListFormat.LevelNumber = (int)OutlineLevel.Level2;
p = doc.Body.AddParagraph("The Licensor hereby grants to the Licensee this License to use the Licensed Products. It is understood and agreed that the License shall pertain only to the Licensed Products.");
p.ListFormat.Template = listTemplate;
p.ListFormat.LevelNumber = (int)OutlineLevel.Level2;
// For some business reason you now want to insert paragraphs inside of a table that continues the “main numbering”.
// You can do so by including LISTNUM fields in the left column of the table.
p = doc.Body.AddParagraph("If the contract is signed by the customer before January 1st, 2025, then also the following will apply:");
// create options for LISTNUM fields
ListNumFieldOptions options = new ListNumFieldOptions(doc);
options.Name = listName;
options.Level = 2;
// create a table 
Table table = doc.Body.AddTable(new string[][]
{
    new string[] { "Optional clause nr." , "Optional clause content" },
    new string[] { null, "The Licensee may grant sublicenses free of charges to customers in France." },
    new string[] { null, "If at least 500 orders are submitted, the Licensee may also grant sublicenses to customers in Denmark." },
}, doc.Styles[BuiltInStyleId.GridTable1Light]);
// format header with style
table.Format.StyleOptions |= TableStyleOptions.FirstRow;
// including LISTNUM fields in the left column
table[1, 0].GetRange().Paragraphs.First.GetRange().ComplexFields.Add(options);
table[2, 0].GetRange().Paragraphs.First.GetRange().ComplexFields.Add(options);
// continue numbering in a general way
p = doc.Body.AddParagraph("The Licensee may grant any sublicenses to any third party without the prior express written consent of the Licensor which may be withheld for any reason.");
p.ListFormat.Template = listTemplate;
p.ListFormat.LevelNumber = (int)OutlineLevel.Level2;
// calculate LISTNUM fields values
doc.UpdateFields();
doc.Save(@"listnum.docx");

Help | Demo

Update AUTONUM, AUTONUMLGL, and AUTONUMOUT Fields

DsWord v9.1 also adds support for updating the legacy AUTONUM, AUTONUMLGL, and AUTONUMOUT fields. These fields are older Word numbering features used for sequential numbering, legal-style numbering, and outline-style numbering, and are still important for compatibility with existing Word documents.

To support these scenarios, DsWord introduces the new AutoNumFieldOptions, AutoNumLglFieldOptions, and AutoNumOutFieldOptions classes. These fields are now included in the field update pipeline, so developers can insert them, call UpdateFields(), and have the numbering results calculated correctly in the document. Although these field types are considered obsolete by Word, supporting them helps preserve compatibility with older document workflows.

// AUTONUM, AUTONUMLGL, and AUTONUMOUT field examples combined into one sample.
void AddHeadingWithField(GcWordDocument doc, string text, BuiltInStyleId styleId, FieldOptionsBase options)
{
    Paragraph p = doc.Body.AddParagraph(text, doc.Styles[styleId]);
    p.GetRange().ComplexFields.Insert(options, InsertLocation.Start);
}
// AUTONUM
{
    GcWordDocument doc = new GcWordDocument();
    // Create options to insert AUTONUM fields in the document.
    AutoNumFieldOptions options = new AutoNumFieldOptions(doc);
    // Create a structure of paragraphs with different outline levels
    // and insert AUTONUM fields at the beginning of each paragraph
    // to automatically number them.
    AddHeadingWithField(doc, "Heading 1", BuiltInStyleId.Heading1, options);
    AddHeadingWithField(doc, "Heading 2", BuiltInStyleId.Heading2, options);
    AddHeadingWithField(doc, "Heading 2", BuiltInStyleId.Heading2, options);
    AddHeadingWithField(doc, "Heading 3", BuiltInStyleId.Heading3, options);
    AddHeadingWithField(doc, "Heading 3", BuiltInStyleId.Heading3, options);
    AddHeadingWithField(doc, "Heading 1", BuiltInStyleId.Heading1, options);
    AddHeadingWithField(doc, "Heading 2", BuiltInStyleId.Heading2, options);
    // Calculate AUTONUM field values.
    doc.UpdateFields();
    doc.Save(@"autonum.docx");
}
// AUTONUMLGL
{
    GcWordDocument doc = new GcWordDocument();

    // Create options to insert AUTONUMLGL fields in the document.
    AutoNumLglFieldOptions options = new AutoNumLglFieldOptions(doc);

    // Create a structure of paragraphs with different outline levels
    // and insert AUTONUMLGL fields at the beginning of each paragraph
    // to automatically number them.
    AddHeadingWithField(doc, "Heading 1", BuiltInStyleId.Heading1, options);
    AddHeadingWithField(doc, "Heading 2", BuiltInStyleId.Heading2, options);
    AddHeadingWithField(doc, "Heading 2", BuiltInStyleId.Heading2, options);
    AddHeadingWithField(doc, "Heading 3", BuiltInStyleId.Heading3, options);
    AddHeadingWithField(doc, "Heading 3", BuiltInStyleId.Heading3, options);
    AddHeadingWithField(doc, "Heading 1", BuiltInStyleId.Heading1, options);
    AddHeadingWithField(doc, "Heading 2", BuiltInStyleId.Heading2, options);
    // Calculate AUTONUMLGL field values.
    doc.UpdateFields();
    doc.Save(@"autonumlgl.docx");
}
// AUTONUMOUT
{
    GcWordDocument doc = new GcWordDocument();
    // Create options to insert AUTONUMOUT fields in the document.
    AutoNumOutFieldOptions options = new AutoNumOutFieldOptions(doc);
    // Create a structure of paragraphs with different outline levels
    // and insert AUTONUMOUT fields at the beginning of each paragraph
    // to automatically number them.
    AddHeadingWithField(doc, "Heading 1", BuiltInStyleId.Heading1, options);
    AddHeadingWithField(doc, "Heading 2", BuiltInStyleId.Heading2, options);
    AddHeadingWithField(doc, "Heading 2", BuiltInStyleId.Heading2, options);
    AddHeadingWithField(doc, "Heading 3", BuiltInStyleId.Heading3, options);
    AddHeadingWithField(doc, "Heading 3", BuiltInStyleId.Heading3, options);
    AddHeadingWithField(doc, "Heading 1", BuiltInStyleId.Heading1, options);
    AddHeadingWithField(doc, "Heading 2", BuiltInStyleId.Heading2, options);
    // Calculate AUTONUMOUT field values.
    doc.UpdateFields();
    doc.Save(@"autonumout.docx");
}

Help | Demo

Structure Tags for PDF Export

DsWord v9.1 improves PDF export by adding support for structure tags, making it easier to generate tagged PDFs with better accessibility and navigation support. Previous versions wrapped paragraphs with only basic paragraph tagging, while this update adds richer structural information similar to Word’s own PDF export, including heading recognition, image alternative text, and hyperlink screen tips.

This behavior is controlled through PdfOutputSettings, primarily with ExportStructureTags and MarkAsPdfUa. Developers can also provide placeholder values such as MissingDocumentTitle, MissingShapeAlternativeText, and MissingHyperlinkScreenTip when validating incomplete documents during development. With this enhancement, DsWord can more easily participate in workflows targeting PDF/A or PDF/UA compliance, provided the source document is prepared correctly.

using GrapeCity.Documents.Word;
using GrapeCity.Documents.Word.Layout;

DocToPdfUa("SimpleDocument");
DocToPdfA("SimpleDocument");
// mark the exported document as PDF/UA compliant
static void DocToPdfUa(string name)
{
    var doc = new GcWordDocument();
    doc.Load($"{name}.docx");
    using var wl = new GcWordLayout(doc);
    var settings = new PdfOutputSettings
    {
        ExportStructureTags = true,
        MarkAsPdfUa = true,
        MissingDocumentTitle = "(no title)",
        MissingShapeAlternativeText = "(no alternative text)",
        MissingHyperlinkScreenTip = "(no content)"
    };
    wl.SaveAsPdf($"{name}PdfUa.pdf", null, settings);
}
// mark the exported document as PDF/A compliant
static void DocToPdfA(string name)
{
    var doc = new GcWordDocument();
    doc.Load($"{name}.docx");
    using var wl = new GcWordLayout(doc);
    var settings = new PdfOutputSettings
    {
        ExportStructureTags = true,
        MissingDocumentTitle = "(no title)"
    };
    wl.SaveAsPdf($"{name}PdfA.pdf", null, settings);
}

With support for LISTNUM and legacy automatic numbering fields, plus richer structure tags in PDF export, DsWord v9.1 improves both document automation and accessible publishing workflows.

Help | Demo


Document Solutions for Excel, .NET & Java Editions (DsExcel) and Document Solutions DataViewer (DsDataViewer) 


Document Solutions v9.1 brings a major step forward for the platform with the debut of DsPdfJS and meaningful enhancements across DsPdf .NET, DsWord, DsExcel, and DsDataViewer. With new JavaScript PDF APIs for creation, editing, forms, security, redaction, annotations, and optimization, alongside improvements to interactive PDF forms, accessible Word-to-PDF export, modern Excel workflows, SpreadJS compatibility, and browser-based PivotTable analysis, this release gives developers more powerful tools to build advanced document solutions across platforms and environments

Ready to check out the release? Download Document Solutions Today!

comments powered by Disqus