Printing / Printing


The jsPlumb Toolkit offers client side support for printing on the server. Given that the Toolkit operates inside a web page, any solution that is able to render a web page server side can be used to generate a PDF or screenshot from a Toolkit instance.

There are complications with server side printing, though - most notably nailing down when the page is fully loaded and ready to print. The Toolkit offers an optional package to assist with ensuring the client side is ready to print.

The package can be loaded via package.json:


In a vanilla setup:

<script src="/path/to/jsplumbtoolkit-print.js"></script>

In ES6/Typescript:

import { jsPlumbToolkitPrint } from "jsplumbtoolkit-print"

The basic setup required to print using the print helper package is:

  • import the print handler in your JS and create an instance of it that is tied to some Surface widget

  • configure an endpoint on your server that can serve the page containing the Toolkit instance without any content other than the Toolkit UI. How you do this is a matter of preference: a simple yet effective approach is simply to include a stylesheet that removes everything you do not want to see.

  • configure an endpoint on your server that can process print requests. This endpoint is where you write the code that interacts with headless Chrome, support for which is now available in many languages. On this page we'll show our endpoint code, which is written in Scala, but it uses a Java library, so it applies for Java too.

jsPlumbToolkitPrint.registerHandler(renderer, "jsplumb-demo-print");

This comes from our Flowchart Builder demonstration. renderer is an instance of the Surface widget. We also pass in the ID to use for this handler - which our server side code will look for. serves all of its demonstrations from a single controller, with the ID of the desired demonstration forming part of the URL, for instance:

We have a second endpoint which can serve each demonstration without any content other than the demonstration itself:

On our site, we achieve this by the simple expedient of including a stylesheet that hides everything we don't want to see:

.jtk-site-demonstrations {

.jtk-site-header {

.jtk-site-footer {

.jtk-demo-rhs {

.jtk-miniview {
    display:none !important;

.controls {

This demonstration-print endpoint is the URL we will pass to headless Chrome. Under the hood the page is loading exactly as it does when the rest of the content is visible. The key is that the javascript is creating a print handler, as shown above, and that print handler knows when the Surface it is associated with is ready.

The last thing you need is an endpoint to process incoming print requests, which will communicate with headless Chrome to serve them. As mentioned above, we use Scala on, and to talk to headless Chrome we currently use this library:

The print endpoint code looks like this:

def print(demoId:String, size:String) = Action.async { implicit request =>"Print request for demo $demoId and size $size")

    val method = com.jsplumb.print.PrintMethod.valueOf(size)
    val url = ... get the absolute url to the print generator
    val launcher = new ChromeLauncher
    val args = ChromeArguments.defaults(true)
      .additionalArguments("no-sandbox", null).build()
    val chromeService = launcher.launch(args)

    // Create empty tab ie about:blank.
    val tab = chromeService.createTab

    // Get DevTools service to this tab
    val devToolsService = chromeService.createDevToolsService(tab)

    // Get individual commands
    val page = devToolsService.getPage

    // load the page we want

    val listener = new PdfDataGenerator(method, devToolsService, "jsplumb-demo-print")

    val result:Future[Result] =  scala.concurrent.Future {
        val data = listener.getData
        if (data != null)
        else {
          logger.error("could not generate pdf")


A lot of the heavy lifting is done here by PdfDataGenerator, so we'll include the full code for it, and its superclasses.

This class is the base class for all content generators (this page talks about PDF output, but we're also working on PNG/JPG output using the same architecture).

The key thing to note in this code is the jsPlumbToolkit.isReadyToPrint call. This executes on the headless Chrome instance, and we pass in the ID of the handler we want to test (in this case, jsplumb-demo-print as shown above). The code repeatedly tests for readiness until either the JS reports it is ready, or we exceed the number of attempts or timeout.

package com.jsplumb.print;

import com.github.kklisura.cdt.protocol.types.runtime.Evaluate;

public abstract class AbstractListener {

    protected ChromeDevToolsService devToolsService;

    public AbstractListener(ChromeDevToolsService devToolsService) {
        this.devToolsService = devToolsService;

    protected void waitForReady(String handlerId, com.github.kklisura.cdt.protocol.commands.Runtime runtime, int attempts, int timeoutMillis) {
        int attempt = 0;
        String r_expr = "jsPlumbToolkitPrint.isReadyToPrint('" + handlerId + "');";
        Evaluate r_evaluate = runtime.evaluate(r_expr);
        boolean ready = (Boolean)r_evaluate.getResult().getValue();

        while (!ready && attempt < attempts) {
            System.out.println("Content not ready, load attempt " + attempt);
            try {
                r_evaluate = runtime.evaluate(r_expr);
                ready = (Boolean)r_evaluate.getResult().getValue();
            } catch (InterruptedException ie) {
                System.out.println("Thread sleep interrupted");
                throw new RuntimeException("Could not wait for content to be ready");

        if (!ready) {
            throw new RuntimeException("Content was not ready within timeout.");

This class generates the PDF. EventHandler is an interface from the library we use, the key being that its onEvent method will be executed once the page loads. So that's where we've got all our code for generating the PDF.

There's a bunch of setup code first, followed by one of the key pieces:

String expr = "jsPlumbToolkitPrint.scaleToPageSize('" + this.printHandlerId + "', jsPlumbToolkitPrint.PageSizes." + + ","  + margins +", jsPlumbToolkitPrint.Units.INCHES)";

This produces a string like:

jsPlumbToolkitPrint.scaleToPageSize('jsplumb-demo-print', jsPlumbToolkitPrint.PageSizes.A4,[0.25,0.25,0.55,0.25], jsPlumbToolkitPrint.Units.INCHES)

Which is then executed in the headless Chrome instance, instructing the Toolkit print handler with id jsplumb-demo-print to zoom/pan the content such that it will fit into an A4 (in this case) page, with the given margins.

package com.jsplumb.print;

import com.github.kklisura.cdt.protocol.types.runtime.Evaluate;

import java.util.Base64;

abstract class PdfListener extends AbstractListener implements EventHandler<LoadEventFired> {

    private PrintMethod printMethod;
    private byte[] data = null;
    private String printHandlerId;
    private play.Logger.ALogger logger;

    PdfListener(PrintMethod printMethod, ChromeDevToolsService devToolsService, String printHandlerId) {
        this.printMethod = printMethod;
        this.printHandlerId = printHandlerId;
        this.logger = play.Logger.of("print");

    private byte[] generateData(PrintToPDF printToPDF) {
        return Base64.getDecoder().decode(printToPDF.getData());

    public byte[] getData() {
        return data;

    abstract void dataRetrieved(byte[] data);

    public void onEvent(LoadEventFired event) {
        logger.debug("Generating PDF");

        try {

            com.github.kklisura.cdt.protocol.commands.Runtime runtime = devToolsService.getRuntime();

            Double scale = 1d;
            Double marginTop = 0.25d;
            Double marginBottom = 0.55d;
            Double marginLeft = 0.25d;
            Double marginRight = 0.25d;

            Double paperWidth = null;
            Double paperHeight = null;

            String margins = "[" + marginTop + "," + marginRight + "," + marginBottom + "," + marginLeft + "]";

            waitForReady(this.printHandlerId, runtime, 25, 200);

            String expr = "jsPlumbToolkitPrint.scaleToPageSize('" + this.printHandlerId + "', jsPlumbToolkitPrint.PageSizes." + + ","  + margins +", jsPlumbToolkitPrint.Units.INCHES)";
            Evaluate evaluate = runtime.evaluate(expr);
            com.fasterxml.jackson.databind.node.ArrayNode paperSize =  (com.fasterxml.jackson.databind.node.ArrayNode)play.libs.Json.parse((String)evaluate.getResult().getValue());
            paperWidth = paperSize.get(0).doubleValue();
            paperHeight = paperSize.get(1).doubleValue();

            data = generateData(devToolsService


  "PDF generated successfully");

        } catch (Exception e) {
            logger.error("Exception occurred generating pdf", e);
        finally {

This class exists because we also have a PdfFileWriter class, which has the same requirements in terms of generating a PDF, but which writes the output to a file instead. You don't necessarily need this extra level in the class hierarchy.

package com.jsplumb.print;


public class PdfDataGenerator extends PdfListener {

    public PdfDataGenerator(PrintMethod printMethod, ChromeDevToolsService devToolsService, String printHandleId) {
        super(printMethod, devToolsService, printHandleId);

    void dataRetrieved(byte[] data) { }

There are a few moving parts to the setup here but the end result is reliable and adaptable. Much of the code shown here is specific to the language and library we are using on, but the client side jsPlumbToolkitPrint handler is server agnostic.

If you'd like a PDF of our Flowchart Builder demonstration, click here. That one's A4 sized. If you'd like one in A3, click here. Or for a full size PDF, try this one.