Во время работы над недавним приложением 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 объекта.
Далее мы добавляем действия в
- Предварительный просмотр PDF на той же странице
- Экспорт 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
, поэтому вам, возможно, придется поиграть, чтобы получить правильную настройку, если вам нужен динамический заголовок, вы можете проверить реализацию здесь