Как применить web worker для рендеринга PDF с помощью makepdf

Я успешно создал PDF-файл с помощью подключаемого модуля JavaScript (pdfmake), и это было здорово. Но когда я пытаюсь отобразить распечатку инвентаризации/бухгалтерской книги примерно на 8000 строк, она зависает более чем на минуту.

Вот как я обычно объявляю свой 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' }] }

куда

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

а также

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

сгенерировано файл.

Я искал веб-работников и увидел, что это может решить эту проблему зависания пользовательского интерфейса. Поэтому я попытался создать для него веб-работника:

$('#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);

Выход из console.log():

Рабочий сказал: Объект {pageOrientation: "portrait", content: Array[7]} правильно регистрирует структуру json.

Все идет нормально. Но после того, как добавил в воркер pdfmake.min.js и vfs_font.js, получаю ошибку Uncaught TypeError: Cannot read property 'createElementNS' of undefined.

Я получаю сообщение об ошибке еще до того, как начал использовать worker.

Можно ли реализовать веб-воркеры с помощью подключаемого модуля pdfmake?


person melvnberd    schedule 21.09.2015    source источник
comment
похоже, что этому инструменту нужен DOM/документ, которого нет у рабочих. вы МОЖЕТЕ использовать виртуальную библиотеку dom для узла, который вы просматриваете, это может быть круто...   -  person dandavis    schedule 17.10.2015
comment
Большое спасибо за внимание, сэр.. есть ли шанс, что вы можете подсказать мне несколько ссылок о том, с чего начать.. это действительно мне очень поможет.. еще раз спасибо   -  person melvnberd    schedule 18.10.2015


Ответы (1)


Простой ответ

Просто предоставьте фиктивный конструктор:

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

В качестве альтернативы, если вы считаете, что это слишком грязно, импортируйте XML-JS (всего 60 КБ) и создайте виртуальный документ. для pdfmake.

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

Пояснение

Известно, что pdfmake несовместим с worker.

Сам по себе pdfmake не использует createElementNS. Однако его минимизированный скрипт pdfmake.min.js, очевидно, для создания ссылка для скачивания.

В любом случае нам не нужна ссылка для скачивания, так что просто дайте ей фиктивную, чтобы она была счастливой (пока).

Если в будущем ему понадобится настоящий DOM, плохая новость заключается в том, что документ недоступен в веб-воркере. Хорошая новость: у нас есть чистая реализация JavaScript. Загрузите XML-JS, извлеките и найдите tinyxmlw3cdom.js, импортируйте его, и вы сможете создать функциональный документ.

В дополнение к документу, поскольку vfs_fonts.js получить pdfmake через window нам нужно ввести window как псевдоним для global.

Для меня эти шаги заставляют pdfmake работать в веб-воркере.

Чтобы сохранить файл, вам нужно преобразовать данные base64, предоставленные pdfmake, в загружаемый двоичный файл. Доступен ряд сценариев, например download.js. В приведенном ниже коде я использую FileSaver.


Код

Мой воркер — это Builder, который можно кэшировать. Если вы компонуете worker на стороне сервера, вы можете избавиться от стека и функций сборки и напрямую вызвать pdfmake, что значительно упростит оба js-кода.

Основной 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>

Рабочий:

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

})();
person Sheepy    schedule 19.10.2015
comment
Большое спасибо за вашу помощь, сэр @Sheepy..! ваш код действительно работает.. теперь я просто настраиваю его, чтобы он соответствовал моему макету.. хм, можно ли сразу отправить docDefinition вместо ручного массива? а также функция открытия (gen_pdf) не работает при работе с большой таблицей, я думаю, что это проблема браузера, поэтому было бы намного лучше, если бы вместо этого была функция загрузки. Большое спасибо за помощь, сэр! это решит будущие проблемы в моем проекте.\ - person melvnberd; 19.10.2015
comment
Я попытался заменить open( 'data:application/pdf;base64,' + evt.data.base64 ); на download('General Ledger From : <?php echo $startDate; ?> to <?php echo $endDate; ?>.pdf');, но это дает мне ошибку «загрузка» не определена. - person melvnberd; 19.10.2015
comment
@melvnberd В настоящее время браузеры не имеют согласованного API загрузки, поэтому я бы рекомендовал сторонние библиотеки. Их несколько, и я обновил объяснение и код ответа на относительно кросс-платформенный. - person Sheepy; 19.10.2015
comment
Большое спасибо, сэр @sheepy, за то, что действительно помогли мне с этим. У меня нет никакого опыта использования каких-либо веб-воркеров на самом деле. людей :) потому что документация по веб-воркерам очень ограничена для makePDF - person melvnberd; 19.10.2015
comment
Еще раз здравствуйте, сэр @sheepy.. теперь я уже могу распечатать данные в свой pdf. Я делаю это pdf( 'add_table', { headerRows: 1, widths: width, layout: 'lightHorizontalLines' } ); arr.map(function(obj){ pdf( 'add', obj ); }); pdf( 'close_table' );, но есть одна вещь, которую я не могу сделать. его динамическое добавление свойства. Я пробовал pdf( 'add',fontSize = 8);, но это не помогло, я также пробовал pdf('add',{fontSize:8});`, но выдает ошибку Uncaught Unrecognized document structure: {"fontSize":8,"_margin":null} - person melvnberd; 20.10.2015
comment
@melvnberd Вы всегда указываете стиль наряду с содержанием, не так ли? бывший. pdf( 'add', { text: 'Bigger font', fontSize: 15 } ); Вы можете отложить добавление контента, пока не узнаете его свойства. - person Sheepy; 20.10.2015
comment
Обычно я добавляю общее свойство в таблицу, чтобы мне не приходилось каждый раз добавлять свойство... как этот 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' }] }; - person melvnberd; 20.10.2015
comment
так что это в основном позволяет вам установить общее свойство для контента, а затем позволяет вам добавить конкретное свойство, чтобы переопределить его, если это необходимо. Вышеприведенный код был моим исходным кодом, прежде чем я пытался преобразовать его в веб-воркер. - person melvnberd; 20.10.2015
comment
@melvnberd А. Видите ли, функции не могут быть переданы вебворкеру. Обходные пути слишком сложны и, вероятно, выходят за рамки из-за своей сложности. Я сделал все возможное, добавив команду set - она ​​позволяет вам определить весь PDF-файл, если все они являются клонируемыми объектами. (объект, массив, число, строка и т. д. Подробности ищите в структурированном алгоритме клонирования.) - person Sheepy; 20.10.2015
comment
ааа, понятно .. большое спасибо, сэр @sheepy .. вы уже помогли мне больше, чем необходимо .. мне пора внести свой вклад, более глубоко изучив эту тему .. :) ваша помощь очень ценна .. Хорошего дня, сэр..! - person melvnberd; 20.10.2015
comment
Как я могу применить это решение с Angular, используя require js? - person Gabriel; 07.06.2016