jsPDF + canvas: экспорт в PDF многостраничной таблицы на русском языке

от автора

Генерация PDF… Эта тема не нова, однако порой можно столкнуться с некоторыми тонкостями, в итоге став на тернистый путь велосипедостроения. Сегодня я расскажу, как разрабатывал один такой велосипед.

Мне понадобилось сделать генерацию отчетов в PDF. По ряду причин я решил сделать это на стороне клиента. Беглый поиск предоставил мне выбор между jsPDF и pdfmake. Остановился на первом. А теперь подробнее…

Для начала хочу сказать, что хотя jsPDF и великолепная штука, документация этого проекта местами невменяемая,
что у человека психически неподготовленного вызывает желание ругаться нецензурным матом. Вспоминается документация Symfony: ее читаешь, а потом идешь гуглить с вопросом «а как?» (либо идешь в исходники).

Первый подводный камень, брошенный в мою сторону этой библиотекой, было отсутствие поддержки русского языка (и UTF-8 в целом, насколько мне удалось выяснить).

(pdfmake напротив — умеет работать с UTF-8, однако от использования этой библиотеки я вскоре отказался.)

После поиска и экспериментов решил использовать canvas для отрисовки. Однако здесь обнаружился второй подводный камень: canvas захватывает только текущий экран и моя огромная таблица сохранилась, как набор пустых листов ( а че, экономно).

Пришлось разбивать таблицу скриптом, заносить во временный контейнер, делать из него canvas и по новой. Вот, что получилось в итоге:

app.factory("PDF", function() {          return {         tableToPDF: function() {             var pdf = new jsPDF('p', 'pt', 'a4');              var rows = $("table").find("tr");             var pdfContainer = $(".tmp-pdf-container");              var pdfInternals = pdf.internal;             var pdfPageSize = pdfInternals.pageSize;             var pdfPageWidth = pdfPageSize.width;             var pdfPageHeight = pdfPageSize.height;              var partialSize = 10;             var contentSize = 0;             var marginTop = 20;             var index = 0;              // создаю новую таблицу каждые partialSize строк             var generatePartial = function() {                 var partial = "";                 rows.each(function(i, row) {                     if (i >= index && i <= partialSize) {                         partial += "<tr>" + $(this).html() + "</tr>";                         index++;                         if (index >= partialSize) {                             partialSize += 10;                             return false;                         }                     }                 });                  return partial;             }              var processCanvases = function() {                 if (index >= rows.length) {                     pdfContainer.html("");                     //pdf.output("datauri");                     pdf.save("TEST.pdf");                     return;                 }                  var partial = generatePartial();                  // generate table with that rows                 var table = $(document.createElement("table"));                 table.append("<tbody>" + partial + "</tbody>");                      // insert table to temporary div                 pdfContainer.html("<table class='table table-fixed-width table-condensed'>" + table.html() + "</table>");                 // hide unnecessary columns                 pdfContainer.find(".non-printable").css("display", "none");                  // generate canvas from that table                 html2canvas(pdfContainer, {background: "white"}).then(function(canvas) {                     // contentSize подбирал экспериментально                     // на формате а4 у меня умещается несколько partial,                     // поэтому жду, пока дойдет до конца страницы и только затем создаю новую                     if (contentSize < 2) {                         contentSize ++;                         pdf.addImage(canvas, "jpeg", 20, marginTop, pdfPageWidth-40, 0);                         // формулу и коэффициент подобрал экспериментально                         // у меня 0.01 работает, тут 0.05, пока не разобрался, как правильно это вычислить                         marginTop += canvas.height/ (canvas.width / pdfPageHeight + (pdfPageWidth / pdfPageHeight) - 0.05);                     } else {                         pdf.addImage(canvas, "jpeg", 20, marginTop, pdfPageWidth-40, 0);                         // эта проверка нужна, чтобы не создать лишнюю пустую страницу в конце                         if (index < rows.length) {                             pdf.addPage();                         }                         contentSize = 0;                         marginTop = 0;                     }                     // next iteration                     processCanvases();                 });             }              processCanvases();          }     } }); 
<div class="tmp-pdf-container" style="position: absolute; left: -9999; width: 1000px"></div> 

Отдельно хочу упомянуть формат PNG. Штука хорошая (насколько я знаю, он поддерживается старыми браузерами, в отличие от image/jpeg), но PDF утяжеляет в разы. Вдобавок на больших отчетах браузер гарантированно ляжет, конкретно у меня хром выбрасывал окно ошибки, а ФФ вообще укладывал себя и систему, поэтому спасала только кувалда.

Когда стал генерировать в JPEG, у меня получался афро черный фон. Оказалось, что прозрачность JPEG делает афро черным.

также, html2canvas, не умеет генерировать canvas из кода, поэтому нужно создавать какой-нибудь временный элемент. Плюс он не отрисовывает невидимые элементы. Где-то на stackoverflow советовали iframe, лично я добавил div с position:absolute и left:-9999, чтобы не мешал (правда, на планкере не получилось загнать за экран)

Также возникает сложность при нарезке таблицы: колонки разные. Я это вылечил, добавил следующий стиль:

 .table-fixed-width {     table-layout: fixed; } 

Вот сам пример: plnkr.co/edit/r3BaDwHpK5H9giwpqrBb

Заключение: писать велосипеды — не всегда плохо. Нередко это помогает узнать что-то новое. Однако это не должно становиться привычкой, поэтому всегда нужно читать доки, хотя бы для того, чтобы слегка попсиховать.

Надеюсь, что эта статья кому-то поможет, а также надеюсь услышать ваши советы и идеи.

Какой генератор PDF вы используете?

Никто ещё не голосовал. Воздержавшихся нет.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

ссылка на оригинал статьи https://habrahabr.ru/post/278047/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *