AVAILABLE NOW: Spring 2025 Release

Editing PDFs Programmatically in the Browser

By Matt Parizeau | 2024 Apr 17

Sanity Image
Read time

5 min

Although WebViewer is often used for document viewing and annotating, you might not realize that it also exposes a full PDF editing and manipulation API that previously would only have been possible on the server but now is available completely client side in the browser.

The full APIs sample page provides many code samples showing how to use these APIs and we'll walk through a few of them below.

Learn more about Apryse's PDF and DOCX editor functionality.

Modifying and Saving the Resulting File

Copied to clipboard

The viewer preprocessing sample shows how to process a PDF file before loading it in the viewer.

The initial document looks like this

A picture of a fish outline with two other images as stamp annotations

The original PDF

and we want to replace the images of vegetables and the butterfly with some other image. In this case we will use one called sigImg

A gray-scale aerial photograph of a factory

The image that we will insert programmatically

The entire sample code is located here and we'll go through key parts of it.

First we create the PDFDoc object from a URL:

 doc = await PDFNet.PDFDoc.createFromURL(url);

Then replace the appearance for the stamp annotations in the document with our image:

switch (annotType) {
  case PDFNet.Annot.Type.e_Stamp:
  {
    // ...
    let apElement = await apBuilder.createImageScaled(sigImg, 0, 0, w, h);
    // ...
    await annot.setAppearance(apObj);
  }
  default:
    break;
}

Then we can view the PDFDoc object that we've modified directly in WebViewer by calling loadDocument and passing in the PDFDoc:

window.addEventListener('viewerLoaded', () => {
    PDFNet.initialize()
      .then(() => runScript())
      .then(async doc => {
        instance.UI.loadDocument(doc);
        console.log('finished script');
      });
  });

You can now see the modified PDF within WebViewer.

The modified PDF shown within WebViewer. The two stamp annotations have been replaced.

The modified PDF shown within WebViewer. The two stamp annotations have been replaced.

Click the download button in WebViewer; the downloaded PDF will have all the modifications that we made.

Alternatively, if you want to get the file data as a blob from the PDFDoc, you can use the following code (which is not in the sample code):

doc.saveMemoryBuffer(PDFNet.SDFDoc.SaveOptions.e_linearized).then(function(docBuf) {
  var blob = new Blob([docBuf], { type: 'application/pdf' });
  // upload blob to server
});

Converting Office Files to PDF

Copied to clipboard

WebViewer was the first SDK in the world that could convert Office files to PDF without a server component or MS Office dependencies. Using the officeToPDFBuffer function you can easily convert and then download office files without having to upload them to a server. Note: in old versions of the SDK, the function name was office2PDFBuffer.

Full sample code is here and you can see it running here.

const convertOfficeToPDF = (inputUrl, outputName, l) =>
  Core.officeToPDFBuffer(inputUrl, { l }).then(buffer => {
    saveBufferAsPDFDoc(buffer, outputName);
    console.log(`Finished downloading ${outputName}`);
  });

PDFNet.initialize().then(() => 
  convertOfficeToPDF(inputDir + fileName, `converted_${type}.pdf`))

Adding Images and Text to a Page

Copied to clipboard

WebViewer allows adding stamp annotations to a document, but these are editable and resizable. What if you want to add an image so that it isn't editable and becomes part of the document content?

Using PDFNet.ElementBuilder this can be done directly in the browser. We'll walk through parts of the code sample found here.

const builder = await PDFNet.ElementBuilder.create(); // ElementBuilder, used to build new element Objects
// create a new page writer that allows us to add/change page elements
const writer = await PDFNet.ElementWriter.create(); // ElementWriter, used to write elements to the page

// ...

writer.beginOnPage(page, PDFNet.ElementWriter.WriteMode.e_overlay);

We've created an ElementBuilder to build new elements and an ElementWriter to actually add them to the document. We can then add a jpg and png to the page:

// Adding a JPEG image to output file
let img = await PDFNet.Image.createFromURL(doc, inputURL + 'peppers.jpg');
let matrix = await PDFNet.Matrix2D.create(200, 0, 0, 250, 50, 500);
const matrix2 = await PDFNet.Matrix2D.createZeroMatrix();
await matrix2.set(200, 0, 0, 250, 50, 500);
let element = await builder.createImageFromMatrix(img, matrix2);
writer.writePlacedElement(element);

// Add a PNG to output file
img = await PDFNet.Image.createFromURL(doc, inputURL + 'butterfly.png');
matrix = await PDFNet.Matrix2D.create(await img.getImageWidth(), 0, 0, await img.getImageHeight(), 300, 500);
element = await builder.createImageFromMatrix(img, matrix);
writer.writePlacedElement(element);
Add a jpg and png to the page

We can add a number of other image formats (for example, JPEG2000) and add text to the document as well:

img = await PDFNet.Image.createFromURL(doc, inputURL + 'palm.jp2');
matrix = await PDFNet.Matrix2D.create(await img.getImageWidth(), 0, 0, await img.getImageHeight(), 96, 80);
element = await builder.createImageFromMatrix(img, matrix);
writer.writePlacedElement(element);

// write 'JPEG2000 Sample' text under image
const timesFont = await PDFNet.Font.create(doc, PDFNet.Font.StandardType1Font.e_times_roman);
writer.writeElement(await builder.createTextBeginWithFont(timesFont, 32));
element = await builder.createTextRun('JPEG2000 Sample', timesFont, 32);
matrix = await PDFNet.Matrix2D.create(1, 0, 0, 1, 190, 30);
element.setTextMatrix(matrix); // await?
writer.writeElement(element);
const element2 = await builder.createTextEnd();
writer.writeElement(element2);
Add other image formats (for example, JPEG2000) and add text to the document

The full running sample can be found here.

Learn more about WebViewer's updated real-time WYSIWYG PDF editing.

Select Elements of the Page in a Viewer

Copied to clipboard

The ViewerDisplayPoints sample demonstrates how you can integrate with the full WebViewer APIs while viewing a document. When you click on an element of the page you'll see a bounding box and outline.

The sample code is extensive but we'll walk through a few key parts.

Setup a listener for mouse clicks:

documentViewer.getViewerElement().addEventListener('mousedown', handleMouseClick);

Extract the elements from the page, processing each in a way appropriate to its type:

const ExtractElements = async pageReader => {
  var elementArray = [];
  // Read page contents
  for (let element = await pageReader.next(); element !== null; element = await pageReader.next()) {
    // ...
    switch (elemType) {
      case PDFNet.Element.Type.e_path:
      // ...
      case PDFNet.Element.Type.e_image:
      // ...
    }
  }
  return elementArray;
}

Draws annotations at the location of the selected page element:

    const DrawPointAnnot = async (pageNumber, x, y) => {
      const p1 = docCore.getViewerCoordinates(pageNumber, x, y);
      const p2 = docCore.getViewerCoordinates(pageNumber, x, y);
      p1.x -= 2;
      p1.y -= 2;
      p2.x += 2;
      p2.y += 2;
      const displayAnnot = new Annotations.RectangleAnnotation();
      displayAnnot.setPageNumber(pageNumber);

      displayAnnot.FillColor = new Annotations.Color(255, 255, 0, 1);
      displayAnnot.StrokeColor = new Annotations.Color(255, 0, 0, 1);

      displayAnnot.setRect(new Core.Math.Rect(p1.x, Math.min(p1.y, p2.y), p2.x, Math.max(p1.y, p2.y)));
      annotationManager.addAnnotation(displayAnnot);
      prevAnnotations.push(displayAnnot);
    };

Here's a picture from the viewer where you can see the points around the selected letter "e".

A picture from the viewer where you can see the points around the selected letter "e"

The full running sample can be found here.

Conclusion

Copied to clipboard

The examples above are just scratching the surface of what's possible using WebViewer's full PDF APIs. Be sure to check out the samples and guides for more information.

If you have any questions about Apryse's PDF SDK, please feel free to get in touch or contact us Discord!

[This article was original released in 2019, but was updated to use WebViewer 10 in April 2024]

Sanity Image

Matt Parizeau

Share this post

email
linkedIn
twitter