Getting Started on Electron

Integrate into an Electron Project

This guide will walk you through the steps necessary to integrate PSPDFKit for Web into your project. By the end, you’ll be able to present a PDF document in the PSPDFKit UI.

Requirements

Creating a New Project

  1. Initialize a new npm workspace in an empty directory:

yarn init -y
npm init -y
  1. Install Electron and electron-packager as dev dependencies:

yarn add --dev electron electron-packager
npm install --dev electron electron-packager
  1. Modify your package.json to add the start script:

{
	"name": "PSPDFKit Electron Example",
	"version": "1.0.0",
	"main": "index.js",
	"license": "MIT",
	"devDependencies": {
		"electron": "^19.0.0",
      "electron-packager": "^15.5.1",
	},
	"scripts": {
		"start": "electron ."
	}
}

Adding PSPDFKit

PSPDFKit for Electron is installed as an npm package. This will add a pspdfkit dependency to your application’s package.json:

yarn add pspdfkit
npm install pspdfkit

Displaying a PDF

  1. Add the PDF document you want to display to the assets directory. You can use our demo document as an example.

  2. Create an index.html file to set up a mount target in the HTML file:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
    />
    <title>PSPDFKit for Electron Example App</title>

    <style>
      html,
      body {
        margin: 0;
        padding: 0;
        background: #f6f7fa;
      }

      header {
        display: none;
      }

      #root {
        width: 100vw;
        height: 100vh;
      }

      /**
       * Offset the frameless window alternative on macOS.
       * https://electronjs.org/docs/api/frameless-window#alternatives-on-macos
       */

      body.platform-darwin header {
        -webkit-app-region: drag;
        display: block;
        height: 22px;
        background-color: rgb(252, 253, 254);
      }

      body.platform-darwin #root {
        height: calc(100vh - 22px);
      }
    </style>
  </head>

  <body>
    <header></header>
    <div id="root"></div>

    <script src="./pspdfkit.js"></script>

    <script type="module">
      import { dragAndDrop } from "./lib/drag-and-drop.js";
      import { makeToolbarItems } from "./lib/toolbar.js";

      /**
       * We append `process.platform` to `<body>` as a CSS class so we can offset
       * the frameless window alternative on macOS.
       * https://electronjs.org/docs/api/frameless-window#alternatives-on-macos
       */
      document.body.classList.add(
        `platform-${window.electron.processPlatform()}`
      );

      /**
       * We store the `PSPDFKit.Instance` in this variable so we can access it from
       * everywhere.
       */
      let instance = null;

      const { documentExport, documentImport, askUserToDiscardChanges } =
        window.electron;

      /**
       * Creates an `onAnnotationsChange` handler that keeps track of changes.
       *
       * We skip the first call since `annotations.change` fires when the PDF is
       * initialized and populated with annotations.
       */
      let hasUnsavedAnnotations = false;

      function createOnAnnotationsChange() {
        let initialized = false;

        return () => {
          if (initialized) {
            hasUnsavedAnnotations = true;
          } else {
            initialized = true;
          }
        };
      }

      /**
       * If there's an existing running instance of PSPDFKit, it's destroyed before
       * a creating a new one.
       *
       * This process will make sure the WebAssembly module is optimally reused.
       */
      async function load(document) {
        if (instance) {
          PSPDFKit.unload(instance);
          hasUnsavedAnnotations = false;
          instance = null;
        }

        // Create our custom toolbar.
        const toolbarItems = makeToolbarItems(
          PSPDFKit.defaultToolbarItems,
          function exportFile() {
            documentExport(instance, () => (hasUnsavedAnnotations = false));
          },
          function importFile() {
            if (hasUnsavedAnnotations) {
              askUserToDiscardChanges(() => documentImport(load));
            } else {
              documentImport(load);
            }
          }
        );

        // Set up the configuration object. A custom style sheet is used to customize
        // the look and feel of PSPDFKit.
        const configuration = {
          document,
          container: "#root",
          styleSheet: ["./pspdfkit.css"],
          // `electronAppName` must match the license's bundle ID.
          electronAppName: "pspdfkit-electron-example",
          // Add when using a license key.
          // `licenseKey`: "LICENSE KEY GOES HERE",
        };

        instance = await PSPDFKit.load(configuration);

        instance.setToolbarItems(toolbarItems);
        instance.addEventListener(
          "annotations.change",
          createOnAnnotationsChange()
        );

        dragAndDrop(instance, (file) => {
          if (hasUnsavedAnnotations) {
            askUserToDiscardChanges(() => load(file));
          } else {
            load(file);
          }
        });
      }

      // Open a default document when the app starts.
      window.onload = () => load("./assets/example.pdf");
    </script>
  </body>
</html>
  1. Create a preload script to run before the main renderer process:

// preload.js

const { contextBridge } = require("electron");

const {
  documentExport,
  documentImport,
  askUserToDiscardChanges,
} = require("./lib/modals");

// Use the recommended, safe way of selectively exposing the capabilities
// of the Node.js API to the browser context.

// Electron helpers exposed in the browser context as `window.electron`.
contextBridge.exposeInMainWorld("electron", {
  processPlatform: () => process.platform,
  documentExport,
  documentImport,
  askUserToDiscardChanges,
});
  1. Now, create the main JavaScript file to get access to the preload script inside the BrowserWindow contructor’s webPreferences option:

const electron = require("electron");
// Module to control application life.
const app = electron.app;
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow;

const path = require("path");
const url = require("url");

// Keep a global reference of the window object. If you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow = null;
let windowInCreation = false;

function createWindow() {
  windowInCreation = true;
  pspdfkitMain.initialize();

  // Create the browser window.
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    titleBarStyle: "hidden",
    webPreferences: {
      contextIsolation: true,
      nodeIntegration: false,
      preload: path.join(__dirname, "preload.js"),
    },
  });

  windowInCreation = false;

  // And load the `index.html` of the app.
  mainWindow.loadURL(
    url.format({
      pathname: path.join(__dirname, "index.html"),
      protocol: "file:",
      slashes: true,
    })
  );

  // Open the DevTools.
  // mainWindow.webContents.openDevTools();

  // Emitted when the window is closed.
  mainWindow.on("closed", function () {
    // Dereference the window object. Usually, you'd store windows
    // in an array. If your app supports multi-windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
    pspdfkitMain.cleanup();
  });
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", createWindow);

// Quit when all windows are closed.
app.on("window-all-closed", function () {
  // On macOS, it's common for applications and their menu bars
  // to stay active until the user quits explicitly with Command + Q.
  if (process.platform !== "darwin") {
    app.quit();
  }
});

app.on("activate", function () {
  // On macOS, it's common to recreate a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null && windowInCreation === false) {
    createWindow();
  }
});

// In this file, you can include the rest of your app's specific
// main process code. You can also put the files related to 
// the main process in separate files and require them here.
  1. Start the app:

yarn start
npm start

Next Steps

Integrate into an Electron Project

This guide will walk you through the steps necessary to integrate PSPDFKit for Web into your project. By the end, you’ll be able to present a PDF document in the PSPDFKit UI.

Adding PSPDFKit

PSPDFKit for Electron is installed as an npm package. This will add a pspdfkit dependency to your application’s package.json:

yarn add pspdfkit
npm install pspdfkit

Displaying a PDF

  1. Add the PDF document you want to display to the assets directory. You can use our demo document as an example.

  2. Set up a mount target in the HTML file:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
    />
    <title>PSPDFKit for Electron Example App</title>

    <style>
      html,
      body {
        margin: 0;
        padding: 0;
        background: #f6f7fa;
      }

      header {
        display: none;
      }

      #root {
        width: 100vw;
        height: 100vh;
      }

      /**
       * Offset the frameless window alternative on macOS.
       * https://electronjs.org/docs/api/frameless-window#alternatives-on-macos
       */

      body.platform-darwin header {
        -webkit-app-region: drag;
        display: block;
        height: 22px;
        background-color: rgb(252, 253, 254);
      }

      body.platform-darwin #root {
        height: calc(100vh - 22px);
      }
    </style>
  </head>

  <body>
    <header></header>
    <div id="root"></div>

    <script src="./pspdfkit.js"></script>

    <script type="module">
      import { dragAndDrop } from "./lib/drag-and-drop.js";
      import { makeToolbarItems } from "./lib/toolbar.js";

      /**
       * We append `process.platform` to `<body>` as a CSS class so we can offset
       * the frameless window alternative on macOS.
       * https://electronjs.org/docs/api/frameless-window#alternatives-on-macos
       */
      document.body.classList.add(
        `platform-${window.electron.processPlatform()}`
      );

      /**
       * We store the `PSPDFKit.Instance` in this variable so we can access it from
       * everywhere.
       */
      let instance = null;

      const { documentExport, documentImport, askUserToDiscardChanges } =
        window.electron;

      /**
       * Creates an `onAnnotationsChange` handler that keeps track of changes.
       *
       * We skip the first call since `annotations.change` fires when the PDF is
       * initialized and populated with annotations.
       */
      let hasUnsavedAnnotations = false;

      function createOnAnnotationsChange() {
        let initialized = false;

        return () => {
          if (initialized) {
            hasUnsavedAnnotations = true;
          } else {
            initialized = true;
          }
        };
      }

      /**
       * If there's an existing running instance of PSPDFKit, it's destroyed before
       * a creating a new one.
       *
       * This process will make sure the WebAssembly module is optimally reused.
       */
      async function load(document) {
        if (instance) {
          PSPDFKit.unload(instance);
          hasUnsavedAnnotations = false;
          instance = null;
        }

        // Create our custom toolbar.
        const toolbarItems = makeToolbarItems(
          PSPDFKit.defaultToolbarItems,
          function exportFile() {
            documentExport(instance, () => (hasUnsavedAnnotations = false));
          },
          function importFile() {
            if (hasUnsavedAnnotations) {
              askUserToDiscardChanges(() => documentImport(load));
            } else {
              documentImport(load);
            }
          }
        );

        // Set up the configuration object. A custom style sheet is used to customize
        // the look and feel of PSPDFKit.
        const configuration = {
          document,
          container: "#root",
          styleSheet: ["./pspdfkit.css"],
          // `electronAppName` must match the license's bundle ID.
          electronAppName: "pspdfkit-electron-example",
          // Add when using a license key.
          // `licenseKey`: "LICENSE KEY GOES HERE",
        };

        instance = await PSPDFKit.load(configuration);

        instance.setToolbarItems(toolbarItems);
        instance.addEventListener(
          "annotations.change",
          createOnAnnotationsChange()
        );

        dragAndDrop(instance, (file) => {
          if (hasUnsavedAnnotations) {
            askUserToDiscardChanges(() => load(file));
          } else {
            load(file);
          }
        });
      }

      // Open a default document when the app starts.
      window.onload = () => load("./assets/example.pdf");
    </script>
  </body>
</html>
  1. Make sure you’ve enabled the file protocol in your main process:

// Make sure to enable access to the local file system. This is required
// to load PDF files and PSPDFKit dependencies from the local file system.
electron.protocol.registerSchemesAsPrivileged([
  {
    scheme: "file",
    privileges: { secure: true, standard: true },
  },
]);
  1. Start the app:

yarn start
npm start

Next Steps