3

I want to generate a PDF report from a web application. The PDF should contain charts (pie, bar), tables, different fonts and colors.

The server-side of the application is Java, the client-side is AngularJS (and of course CSS3 and HTML).

Two main options:

  • The client side will pass some parameters to the server, and the server will generate the PDF report, using a Java package. Then the report will be sent back to the client as a downloaded file.
  • The client will generate the report, using a JS package that converts HTML and CSS to PDF.

In the Java world, I've found for example iText and JFreeChart, like here. The problem here is that the design of charts look bad in the example, and I don't know if it can be changed to be designed by the style-guide I have (a design that can be done easily with CSS).

In the JS world, I've found for example html2canvas and pdfMake, like here. The problem here is that I'm not sure the conversion from HTML to canvas and then to PDF will work good in an Angular application. And I'm not sure it converts well complicated DOM elements, like charts in svg or canvas elements.

Do you have any experience with these packages? Do you know other recommended packages for this task, client or server?

Community
  • 1
  • 1
Guy
  • 1,547
  • 1
  • 18
  • 29
  • Maybe look at http://www.cloudformatter.com/CSS2Pdf which supports direct SVG to PDF not through canvas. – Kevin Brown May 30 '16 at 21:23
  • Thanks @KevinBrown. I can't use this solution because it is a cloud-based solution. You send them a POST request, and they return the PDF. Our application must also work without internet connectivity (because of security issues in large organizations), so we need a solution that creates the PDF independently. – Guy May 31 '16 at 06:15

1 Answers1

1

Want to share my solution... I chose a client-side solution.

I started with jsPDF, but had some problems. For example, it was hard to convert tables with the style I want.

I chose pdfMake for the PDF generation, html2canvas for taking screenshots of complicated designed components, and canvg for conversion of d3js charts (svg charts) to canvas (pdfMake can add canvas as image to the document).

I wrote a function that gets the CSS class of the HTML root of the part I want to convert to PDF (remember it's a single-page application), and also gets a meta data of which HTML nodes (again, by their CSS classes) should be added to the PDF (and what type is the node - table/text/image/svg).

Then, with DOM traversing, I walked through the elements I want to add to the PDF, and handled each one by its type. Part of the code (the traversing and the switch-case by type):

$(htmlRootSelector).contents().each(function processNodes(index, element) {
    var classMeta = getMetaByClass(element.className);

    if (!classMeta) {
      $(element).contents().each(processNodes);
      return;
    }

    var pdfObj = {};
    pdfObj.width = classMeta.width || angular.undefined;
    pdfObj.height = classMeta.height || angular.undefined;
    pdfObj.style = classMeta.style || angular.undefined;
    pdfObj.pageBreak = classMeta.pageBreak || angular.undefined;

    switch (classMeta.type) {
    case 'text':
      pdfObj.text = element.innerText;
      pdfDefinition.content.push(pdfObj);
      break;
    case 'table':
      var tableArray = [];
      var headerArray = [];
      var headers = $(element).find('th');
      var rows = $(element).find('tr');
      $.each(headers, function (i, header) {
        headerArray.push({text: header.innerHTML, style: classMeta.style + '-header'});
      });
      tableArray.push(headerArray);

      $.each(rows, function (i, row) {
        var rowArray = [];
        var cells = $(row).find('td');

        if (cells.length) {
          $.each(cells, function (j, cell) {
            rowArray.push(i % 2 === 1 ? {text: cell.innerText, style: classMeta.style + '-odd-row'} : cell.innerText);
          });

          tableArray.push(rowArray);
        }
      });

      pdfObj.table = {
        widths: $.map(headers, function (d, i) {
          return i === 0 ? 80 : '*';
        }),
        body: tableArray
      };
      pdfDefinition.content.push(pdfObj);
      break;
    case 'image':
      html2CanvasCount++;
      htmlToCanvas(element, pdfObj);
      pdfDefinition.content.push(pdfObj);
      break;
    case 'svg':
      svgToCanvas(element, pdfObj);
      pdfDefinition.content.push(pdfObj);
      break;
    default:
      break;
    }

    $(element).contents().each(processNodes);
  });

This is the solution in general. Hope it will help someone.

Guy
  • 1,547
  • 1
  • 18
  • 29