Команда Wiredcraft работала над системой регистрации избирателей Мьянмы в течение последних полугода. Одной из частей разрабатываемых нами систем является родноеприложение для Windows, которое должно работать в автономном режиме и создавать PDF из списка избирателей в >Язык Мьянмы.

Есть несколько проблем, которые нам нужно решить:

  • Собственное приложение, которое необходимо запускать в Windows
  • Клиент должен работать в автономном режиме из-за плохого подключения к Интернету.
  • Создайте один PDF-файл с 1000 страниц с помощью веб-технологии
  • Текст в PDF должен быть на языке Мьянмы.

Стек, который мы используем:

  • Electron для сборки и упаковки приложения для Windows
  • Интерфейс командной строки Go lang для взаимодействия с зашифрованной базой данных sqlite.
  • Реагировать на создание пользовательского интерфейса
  • Flux + Immutablejs для работы с внешним потоком данных

Рабочий процесс

Данные + Шаблон => Визуализированный HTML => PDF

Подготовить данные

{
  "id": 1,
  "voter_name": "ဒေါ်ကြည်စိန်",
  "gender": "မ",
  "dob": "1989-04-17",
  "father_name": "ဦးအုန်းဇော်",
  "mother_name": "ဒေါ်စန်းရီ",
  "address": "ခုံကြီးကျေးရွာ"
}

Простая функция reader.js для чтения данных. В производстве это может быть спокойный API или что-то в этом роде.

var fs = require('fs');
module.exports = function(path, cb) {
  fs.readFile(path, function(err, data) {
    if (err)
      return cb(err)
    else
      try {
        cb(null, JSON.parse(data.toString()))
      } catch (e) {
        cb(e)
      }
  })
}

Подготовьте шаблон

render.js для приема пути к шаблону и отформатированных данных.

var Handlebars = require('handlebars')
var fs = require('fs')
module.exports = function(tplPath, data, cb) {
  // registe a each helper
  Handlebars.registerHelper('each', function(context, options) {
    var ret = ""
    for(var i=0, j=context.length; i<j; i++) {
      ret = ret + options.fn(context[i])
    }
    return ret
  })
  fs.readFile(tplPath, function(err, tpl) {
    if (err)
      cb(err)
    else
      var template = Handlebars.compile(tpl.toString())
      var rendered = template({pages: data})
      cb(null, rendered)
  })
}

С помощью render.js у нас теперь есть HTML-файл с визуализированными данными.

Код для создания PDF

Теперь у нас есть отрендеренный HTML. Следующим шагом является создание PDF-файла с его помощью.

Решения для создания PDF:

  • Phantomjs: безголовый WebKit-скрипт с JavaScript API. Он имеет быструю и встроенную поддержку различных веб-стандартов: обработка DOM, селектор CSS, JSON, Canvas и SVG.
  • jsPDF: клиентское решение HTML5 для создания PDF-файлов. Идеально подходит для билетов на мероприятия, отчетов, сертификатов, вы называете это!
  • wkhtmltopdf wkhtmltopdf и wkhtmltoimage — это инструменты командной строки с открытым исходным кодом (LGPLv3) для преобразования HTML в PDF и различные форматы изображений с использованием механизма рендеринга QT Webkit.
  • Pandoc: если вам нужно преобразовать файлы из одного формата разметки в другой, pandoc — ваш швейцарский армейский нож.

Так как в нашем стеке у нас где-то нет работающего сервера. Но, к счастью, у нас есть nodejs/iojs в Electron. Все вышеперечисленные инструменты имеют командный интерфейс, с помощью которого мы можем запустить дочерний_процесс для его запуска и отправить обратно ход выполнения во время функции обратного вызова.

Фантомы

Первое, что мы пробуем, это Phantomjs вместе с rasterize.js растрирует веб-страницу в изображение или PDF.

/**
 * generatePDF function
 * @param  {String}   the path of the template
 * @param  {String}   the PDF file destination
 * @param  {Function} A callback function when job is done
 */
function generatePDF(tplPath dist, cb) {
  var childArgs = [ './rasterize.js', tplPath, dist, 'A4', 1.00];
  childProcess.execFile(phantomBinPath, childArgs, function(err, stdout, stderr) {
    if (err) alert('generatae pdf err', err);
    cb(null);
  });
}

Этот подход отлично работает для нас, за исключением:

  • Бинарный файл Phantomjs занимает около 30 МБ на Mac или Windows, что немного тяжело.
  • Он отлично работает для английского языка, но не для Мьянмы, мы пробовали много шрифтов Мьянмы, но ни один из них не работает на Winodws.
  • Даже он работает на Mac, сгенерированный файл имеет общий размер 988 МБ для 1000 страниц, а текст в PDF нельзя выбрать.
  • Текст PDF, созданный в Windows, составляет 60 МБ для 1000 страниц, и текст можно выбрать, и это также одна из причин, по которой размер намного меньше, чем на Mac (с использованием rasterize.js).

Обратите внимание, что для решения проблемы со шрифтом мы попытались преобразовать файл шрифта в файл base64 и SVG, но ни один из них не работает в Windows. :(

wkhtmltopdf

Поскольку нам так и не удалось решить проблему со шрифтами в Windows, мы отказались от Phantomjs. Затем мы попробовали wkhtmltopdf.

wkhtmltopdf предоставляет простой интерфейс cli, который мы могли бы просто вызвать

$ wkhtmltopdf voters.html voters.pdf

в дочернем_процессе так же, как мы делали с Phantomjs.

Но шрифт по-прежнему сломан, даже на Mac не работает.

Пандок

Если вам нужно преобразовать файлы из одного формата разметки в другой, pandoc станет вашим швейцарским армейским ножом. Pandoc может преобразовывать документы с разметкой Markdown, reStructuredText, Textile, HTML, DocBook, LaTeX, MediaWiki, TWiki, OPML, Emacs Org-Mode, Txt2Tags, Microsoft Word docx, EPUB или Haddock в…

Это означает, что вы можете использовать его для преобразования файлов практически между любыми текстовыми файлами. Но мы даже не успели попробовать, как бросили.

Если вы хотите создать PDF-файл, у вас должен быть установлен LaTeX. (См. MacTeX в OS X, MiKTeX в Windows или установите пакет texlive в Linux.) Затем выполните

$ pandoc test1.md -s -o test1.pdf

MikTex Windows 32-bit уже 163,18 МБ, не считая самого Pandoc.

Я считаю, что для Pandoc, безусловно, есть варианты использования, такие как профессиональные публикации или система, ориентированная на сервер, но не для нашего приложения. 200 МБ - это два тяжелых размера только для создания PDF-файлов.

printToPDF из Electron

Браузер — это лучший (даже не один из) инструментов для правильного рендеринга HTML, и это мне до сих пор нравилось.

Пробуя описанные выше подходы, мы уже знаем, что в Chrome уже есть способ печати и создания PDF-файлов из браузера.

Метод window.print() открывает диалоговое окно печати для печати текущего документа.

Если вы вызовете эту функцию из консоли браузера, вы увидите следующий диалог.

На самом деле это может сгенерировать PDF с правильным шрифтом, но есть и ограничения:

  • Проблема с UX: невозможно создать PDF-файл напрямую, пользователь должен нажать кнопку serval, чтобы получить то, что он хочет.
  • Метод window.print() может печатать только текущий документ, но мы находимся внутри экземпляра окна, визуализируемого Electron, но не в HTML-окне PDF.

Первое, что мы пробуем для этого подхода, — использовать метод window.open(), чтобы открыть другое окно и внедрить скрипт для печати открытого документа после его загрузки.

<script>
  function ready(fn) {
    if (document.readyState != 'loading'){
      fn();
    } else {
      document.addEventListener('DOMContentLoaded', fn);
    }
  }
  ready(function() {
    window.print();
  })
</script>

Это может решить вторую проблему, но UX еще хуже. У нас слишком открыто два ненужных окна только для того, чтобы сгенерировать один PDF. UX совершенно неприемлем. Кроме того, пользователь может захотеть одновременно сгенерировать несколько PDF-файлов, что означает, что нам нужно открыть много окон.

Есть ли способ пропустить все окна?

Да! На помощь приходит метод printToPDF.

Печать веб-страницы Windows в формате PDF с пользовательскими настройками печати предварительного просмотра Chromium.

Ниже приведен пример кода

var BrowserWindow = require('browser-window');
var fs = require('fs');
var win = new BrowserWindow({width: 800, height: 600});
win.loadUrl("./voters.html");
win.webContents.on("did-finish-load", function() {
  // Use default printing options
  win.webContents.printToPDF({}, function(error, data) {
    if (error) throw error;
    fs.writeFile(dist, data, function(error) {
      if (err)
        alert('write pdf file error', error);
    })
  })
});

Код здесь просто открывает новый BrowserWindow в фоновом режиме, а затем отображает шаблон, как только содержимое полностью загружено, он печатает содержимое в PDF, и, наконец, мы можем записать его в локальную файловую систему.

Представляем электрон-pdf

Чтобы упростить весь рабочий процесс, я написал простой инструмент cli с nodejs под названием electron-pdf.

Чтобы использовать его, вам просто нужно запустить `npm i -g electronic-pdf electronic-prebuilt`, и если все пойдет хорошо, вы можете начать использовать его, вызвав

$ electron-pdf ~/Desktop/fraserxu.html ~/Desktop/fraserxu.pdf

Ниже скриншот того, как это работает.

Для получения дополнительной информации и использования, пожалуйста, проверьте репозиторий Github здесь electron-pdf и устраните любую проблему, которая может у вас возникнуть, и запрос на вытягивание более чем приветствуется!

Оригинальная ссылка: https://fraserxu.me/2015/08/20/pdf-generation-on-the-web/

Найдите меня в Twitter или Github @fraserxu