Во время работы над недавним приложением Ember Js мне нужно было экспортировать в pdf, и я искал что-то, что бы без проблем обрабатывало html в pdf. Нашел несколько библиотек, которые могли бы это сделать, но, похоже, они не очень хорошо обрабатывают таблицу. Так что мне пришлось довольствоваться pdfmake.

pdfmake принимает объект с определением документа как свойство объекта, чтобы реализовать html в pdf, нам нужно будет проанализировать нашу таблицу на тип объекта, который примет pdfmake. К счастью, нам не нужно много делать Антон уже пошел на шаг вперед для нас здесь, нам нужно только немного отредактировать его, чтобы добавить поддержку новой страницы с помощью тега html <br/>, а также поддержку других важных тегов html .

Следующим шагом будет создание миксина.

Миксины в Ember позволяют нам повторно использовать блок кода в разных частях нашего приложения. Вставив наш улучшенный код сверху в наш миксин, мы получили;

import Ember from 'ember';
export default Ember.Mixin.create({
pdfForElement(args) {
            function ParseContainer(cnt, e, p, styles, hd) {
                var elements = [];
                var children = e.childNodes;
                if (children.length != 0) {
                    for (var i = 0; i < children.length; i++) p = ParseElement(elements, children[i], p, styles, hd);
                }
                if (elements.length != 0) {
                    for (var i = 0; i < elements.length; i++) cnt.push(elements[i]);
                }
                return p;
            }
function ComputeStyle(o, styles) {
                for (var i = 0; i < styles.length; i++) {
                var st = styles[i].trim().toLowerCase().split(":");
                if (st.length == 2) {
                    switch (st[0]) {
                    case "font-size":
                        {
                        o.fontSize = parseInt(st[1]);
                        break;
                        }
                    case "text-align":
                        {
                        switch (st[1]) {
                            case "right":
                            o.alignment = 'right';
                            break;
                            case "center":
                            o.alignment = 'center';
                            break;
                        }
                        break;
                        }
                    case "font-weight":
                        {
                        switch (st[1]) {
                            case "bold":
                            o.bold = true;
                            break;
                        }
                        break;
                        }
                    case "text-decoration":
                        {
                        switch (st[1]) {
                            case "underline":
                            o.decoration = "underline";
                            break;
                        }
                        break;
                        }
                    case "font-style":
                        {
                        switch (st[1]) {
                            case "italic":
                            o.italics = true;
                            break;
                        }
                        break;
                        }
                    }
                }
                }
            }
            function ParseElement(cnt, e, p, styles, hd) {
                if (!styles) styles = [];
                if (e.getAttribute) {
                    var nodeStyle = e.getAttribute("style");
                    if (nodeStyle) {
                        var ns = nodeStyle.split(";");
                        for (var k = 0; k < ns.length; k++) styles.push(ns[k]);
                    }
                }
switch (e.nodeName.toLowerCase()) {
                case "#text":
                    {
                    var t = {
                        text: e.textContent.replace(/\n/g, "")
                    };
                    if (styles) ComputeStyle(t, styles);
                    p.text.push(t);
                    break;
                }
                case "h1":
                case "h2":
                case "h3":
                case "h4":
                case "h5":
                case "b":
                case "strong":
                    {
                    //styles.push("font-weight:bold");
                    ParseContainer(cnt, e, p, styles.concat(["font-weight:bold"]));
                    break;
                    }
                case "img":{
                    var maxResolution = {
                                 width: 435,
                                height: 830
                            },
                            width = parseInt(e.getAttribute("width")),
                            height = parseInt(e.getAttribute("height"));
if (width > maxResolution.width) {
                                var scaleByWidth = maxResolution.width/width;
                                width *= scaleByWidth;
                                height *= scaleByWidth;
                            }
                            if (height > maxResolution.height) {
                                    var scaleByHeight = maxResolution.height/height;
                                    width *= scaleByHeight;
                                    height *= scaleByHeight;
                            }
                            cnt.push({
                                    image: e.getAttribute("src"),
                                    width: width,
                                    alignment: e.getAttribute("alignment")
                            });
                            break;
                }
                case "u":
                    {
                    //styles.push("text-decoration:underline");
                    ParseContainer(cnt, e, p, styles.concat(["text-decoration:underline"]));
                    break;
                    }
                case "i":
                    {
                    //styles.push("font-style:italic");
                    ParseContainer(cnt, e, p, styles.concat(["font-style:italic"]));
                    //styles.pop();
                    break;
                    //cnt.push({ text: e.innerText, bold: false });
                    }
                case "span":
                    {
                    ParseContainer(cnt, e, p, styles);
                    break;
                    }
                case "br":
                    {
                    p = CreateParagraph(true);
                    cnt.push(p);
                    break;
                    }
                case "table":
                    {
                        //if(e.getAttribute("header") && hd.length > 0) break;
                    var t = {
                        table: {
                        widths: [],
                        body: [],
                        headerText:'',
                        }
                    }
                    var border = e.getAttribute("border");
                    var isBorder = false;
                    t.headerText = e.getAttribute("headerText");
                    if (border)
                        if (parseInt(border) == 1) isBorder = true;
                    if (!isBorder) t.layout = 'noBorders';
                    ParseContainer(t.table.body, e, p, styles);
var widths = e.getAttribute("widths");
                    if (!widths) {
                        if (t.table.body.length != 0) {
                        if (t.table.body[0].length != 0)
                            for (var k = 0; k < t.table.body[0].length; k++) t.table.widths.push("*");
                        }
                    } else {
                        var w = widths.split(",");
                        for (var k = 0; k < w.length; k++) t.table.widths.push(w[k]);
                    }
                   /*  if(!e.getAttribute("header")){
                        cnt.push(t);
                    } */
                    break;
                    }
                case "tbody":
                case "thead":
                    {
                    ParseContainer(cnt, e, p, styles);
                    //p = CreateParagraph();
                    break;
                    }
                case "tr":
                    {
                    var row = [];
                    ParseContainer(row, e, p, styles);
                    cnt.push(row);
                    break;
                }
                case "th":
                case "td":
                    {
                    p = CreateParagraph();
                    var st = {
                        stack: []
                    }
                    st.stack.push(p);
var rspan = e.getAttribute("rowspan");
                    if (rspan) st.rowSpan = parseInt(rspan);
                    var cspan = e.getAttribute("colspan");
                    if (cspan) st.colSpan = parseInt(cspan);
                    ParseContainer(st.stack, e, p, styles);
                    cnt.push(st);
                    break;
                    }
                case "div":
                case "a":
                case "p":
                    {
                    p = CreateParagraph();
                    var st = {
                        stack: []
                    }
                    st.stack.push(p);
                    ComputeStyle(st, styles);
                    ParseContainer(st.stack, e, p, '', hd);
                    cnt.push(st);
                    break;
                    }
                default:
                    {
                    console.log("Parsing for node " + e.nodeName + " not found");
                    break;
                    }
                }
                return p;
            }
function ParseHtml(cnt, hd, htmlText) {
                var html = $(htmlText.replace(/\t/g, "").replace(/\n/g, ""));
                var p = CreateParagraph();
                for (var i = 0; i < html.length; i++) ParseElement(cnt, html.get(i), p, '',hd);
            }
function CreateParagraph(b) {
                var p = {
                text: [],
            };
            if(b)p.pageBreak = 'after';
                return p;
            }
            var content = [];
            var header = [];
            var docDefinition = {}; 
            ParseHtml(content, header, document.getElementById(args.id).outerHTML);
            docDefinition.content = content;
            docDefinition.pageMargins = [40, 170, 40, 0] ;
            docDefinition.header = (currentPage)=>{
                //header object goes here
            }
            return docDefinition;
        }
})

Весь код был заключен в pdfForElement, и он принимает объект в качестве аргумента, который определенно будет включать id контейнера, содержащего html, который вы хотите распечатать в pdf. Первоначально код возвращает контент, но мы хотим добавить поддержку заголовка, поэтому теперь мы возвращаем docDefinition со свойствами content, pageMargins и header it объекта.

Далее мы добавляем действия в

  1. Предварительный просмотр PDF на той же странице
  2. Экспорт PDF для скачивания
...
actions:{
    exportPDF(){
      let args = {id: 'export', ...};
      let doc = this.pdfForElement(args);
      pdfMake.createPdf(doc).download(this.get('filename'));
   },
   previewPDF(){
       const targetElement =       document.querySelector('#iframe_container');
       const iframe = document.createElement('iframe');
       let args = {id: 'export',... };
      let doc = this.pdfForElement(args);
      const pdfDocGenerator = pdfMake.createPdf(doc);
      pdfDocGenerator.getBuffer( function (buffer) {
      const dataUrl =  URL.createObjectURL(new Blob([buffer], {
           type: "application/pdf"
      }));
      iframe.src = dataUrl;
      iframe.width = "100%";
      iframe.height = "600px";
      targetElement.appendChild(iframe);
    });
  }
}
...

С его помощью мы можем вызвать любое действие в представлении, в которое добавлен миксин, контроллер или компоненты.

Однако пара предостережений: отображение ваших заголовков в pdfmake зависит от pageMargins, поэтому вам, возможно, придется поиграть, чтобы получить правильную настройку, если вам нужен динамический заголовок, вы можете проверить реализацию здесь