Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
301 views
in Technique[技术] by (71.8m points)

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

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 file.

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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;
   }
};

})();

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...