How to apply web worker to rendering of a PDF using makepdf

為{幸葍}努か 提交于 2019-11-27 18:59:32

问题


I successfully created a PDF using a JavaScript plug-in (pdfmake) and it was great. But when I try to render an ~8,000-row inventory/ledger printout, it freeze for over a minute.

This is how I usually declare my docDefinition

var docDefinition = { 
pageOrientation: orientation, 
footer: function(currentPage, pageCount) { return {text: currentPage.toString() + ' / ' + pageCount, fontSize:8, alignment:'center'}; }, 
content:[ 
   printHeader, 
  { fontSize: 8, alignment: 'right', style: 'tableExample', 
   table: { 
       widths: width, 
       headerRows: 1, body: arr }, 
   layout: 'lightHorizontalLines' }] }

where

var printHeader =   [ { text: 'COMPANY NAME',alignment:'center' },
{ text: 'Address 1',alignment:'center' },
{ text: 'Address 2',alignment:'center' },
{ text: 'Additional Details,alignment:'center' },
{ text: 'document title',alignment:'center' }];

and

 var arr = [[{"text":"","alignment":"left"},"text":"Date","alignment":"left"},
{"text":"Trans #","alignment":"left"},{"text":"Description","alignment":"left"},
{"text":"Ref #","alignment":"left"},{"text":"Debit","alignment":"left"},
{"text":"Credit","alignment":"left"},{"text":"Amount","alignment":"left"},
{"text":"Balance","alignment":"left"}],[{"text":"ACCOUNT : Merchandise Inventory","alignment":"left","colSpan":8},"","","","","","","",
{"text":"1,646,101.06"}],["","10/13/2015","ST#0094",{"text":"","alignment":"left"},{"text":"","alignment":"left"},"546.94","0.00","546.94","1,646,648.00"],[{"text":"Total","alignment":"left","bold":true},"","","","",
{"text":"546.94","alignment":"right","bold":true},
{"text":"0.00","alignment":"right","bold":true},
{"text":"","alignment":"right","bold":true},
{"text":"546.94","alignment":"right","bold":true}],[{"text":"ACCOUNT : Accounts Payable-Main","alignment":"left","colSpan":8},"","","","","","","",
{"text":"-1,741,953.62"}],["","10/13/2015","ST#0094",
{"text":"","alignment":"left"},
{"text":"","alignment":"left"},"0.00","546.94","-546.94","-1,742,500.56"],
[{"text":"Total","alignment":"left","bold":true},"","","","",
{"text":"0.00","alignment":"right","bold":true},
{"text":"546.94","alignment":"right","bold":true},
{"text":"","alignment":"right","bold":true},
{"text":"-546.94","alignment":"right","bold":true}]

generated .

I searched about web workers and see that it can solve this UI freezing problem. So I tried to create a web worker for it:

$('#makepdf').click(function(){
    var worker = new Worker("<?php echo URL::to('/'); ?>/js/worker.js");
  worker.addEventListener('message',function(e){
  console.log('Worker said: ',e.data);
},false);

worker.postMessage(docDefinition);

//worker.js

self.addEventListener('message', function(e) {
  self.postMessage(e.data);
}, false);

Output from console.log():

Worker said: Object {pageOrientation: "portrait", content: Array[7]} its logging correctly the json structure.

So far so good. But after I added pdfmake.min.js and vfs_font.js to the worker, I get the error Uncaught TypeError: Cannot read property 'createElementNS' of undefined.

I get the error before I even started using the worker.

Is it possible to implement web workers with the pdfmake plug-in?


回答1:


Simple answer

Just provide a dummy constructor:

var document = { 'createElementNS': function(){ return {} } };
var window = this;
importScripts( 'pdfmake.min.js', 'vfs_fonts.js' );

Alternatively, if you think it is too dirty, import XML-JS (only 60k) and create a virtual document for pdfmake.

importScripts( 'tinyxmlw3cdom.js' );
var window = this;
var document = new DOMDocument( new DOMImplementation() );
importScripts( 'pdfmake.min.js', 'vfs_fonts.js' );

Explanation

pdfmake is known to be incompatible with worker.

By itself, pdfmake does not use createElementNS. However its minified script pdfmake.min.js do, apparently to create a download link.

We don't need download link anyway, so just give it a dummy to keep it happy (for now).

If in the future it needs a real DOM, bad news is document is unavailable in web worker. Good news is we have a pure javascript implementation. Download XML-JS, extract and find tinyxmlw3cdom.js, import it and you can create a functional document.

In addition to the document, since vfs_fonts.js get pdfmake through the window variable, we need to introduce window as an alias for global.

For me, these steps make pdfmake works in web worker.

To save the file, you need to convert the base64 data provided by pdfmake into a binary download. A number of scripts are available, such as download.js. In the code below I am using FileSaver.


Code

My worker is a Builder that can be cached. If you compose the worker on server side, you can get rid of the stack and build functions and directly call pdfmake, making both js code much simpler.

Main HTML:

<script src='FileSaver.min.js'></script>
<script>
function base64ToBlob( base64, type ) {
    var bytes = atob( base64 ), len = bytes.length;
    var buffer = new ArrayBuffer( len ), view = new Uint8Array( buffer );
    for ( var i=0 ; i < len ; i++ )
      view[i] = bytes.charCodeAt(i) & 0xff;
    return new Blob( [ buffer ], { type: type } );
}
//////////////////////////////////////////////////////////

var pdfworker = new Worker( 'worker.js' );

pdfworker.onmessage = function( evt ) {
   // open( 'data:application/pdf;base64,' + evt.data.base64 ); // Popup PDF
   saveAs( base64ToBlob( evt.data.base64, 'application/pdf' ), 'General Ledger.pdf' );
};

function pdf( action, data ) {
   pdfworker.postMessage( { action: action, data: data } );
}

pdf( 'add', 'Hello WebWorker' );
pdf( 'add_table', { headerRows: 1 } );
pdf( 'add', [ 'First', 'Second', 'Third', 'The last one' ] );
pdf( 'add', [ { text: 'Bold value', bold: true }, 'Val 2', 'Val 3', 'Val 4' ] );
pdf( 'close_table' );
pdf( 'add', { text: 'This paragraph will have a bigger font', fontSize: 15 } );
pdf( 'gen_pdf' ); // Triggers onmessage when it is done

// Alternative, one-size-fit-all usage
pdf( 'set', { pageOrientation: 'landscape', footer: { text: 'copyright 2015', fontSize: 8, alignment:'center'}, content:[ "header", { fontSize: 8, alignment: 'right', table: { headerRows: 1, body: [[1,2,3],[4,5,6]] } }] } );
pdf( 'gen_pdf' );

</script>

Worker:

//importScripts( 'tinyxmlw3cdom.js' );
//var document = new DOMDocument( new DOMImplementation() );
var document = { 'createElementNS': function(){ return {} } };
var window = this;
importScripts( 'pdfmake.min.js', 'vfs_fonts.js' );

(function() { 'use strict';

var doc, current, context_stack;

function set ( data ) {
   doc = data;
   if ( ! doc.content ) doc.content = [];
   current = doc.content;
   context_stack = [ current ];
}
set( {} );

function add ( data ) {
   current.push( data );
}

function add_table ( template ) {
   if ( ! template ) template = {};
   if ( ! template.table ) template = { table: template };
   if ( ! template.table.body ) template.table.body = [];
   current.push( template ); // Append table
   push( template.table.body ); // Switch context to table body
}

function push ( data ) {
   context_stack.push( current );
   return current = data;
}

function pop () {
   if ( context_stack.length <= 1 ) return console.warn( "Cannot close pdf root" );
   context_stack.length -= 1;
   return current = context_stack[ context_stack.length-1 ];
}

function gen_pdf() {
   pdfMake.createPdf( doc ).getBase64( function( base64 ) {
      postMessage( { action: 'gen_pdf', base64: base64 } );
   } );
}

onmessage = function( evt ) {
   var action = evt.data.action, data = evt.data.data;
   switch ( action ) {
      case 'set': set( data ); break;
      case 'add': add( data ); break;
      case 'add_table'  : add_table( data ); break;
      case 'close_table': pop(); break;
      case 'gen_pdf': gen_pdf(); break;
   }
};

})();


来源:https://stackoverflow.com/questions/32689932/how-to-apply-web-worker-to-rendering-of-a-pdf-using-makepdf

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!