Я хочу создать отчет в формате PDF из веб-приложения. PDF должен содержать диаграммы (pie, bar), таблицы, разные шрифты и цвета.
Серверная сторона приложения - Java, клиентская сторона - AngularJS (и, конечно, CSS3 и HTML).
Два основных варианта:
В мире Java я нашел, например, iText и JFreeChart, как здесь. Проблема здесь в том, что дизайн диаграмм выглядит плохо в этом примере, и я не знаю, можно ли его изменить, чтобы быть разработанным стилем-руководством, которое у меня есть (дизайн, который можно легко сделать с помощью CSS).
В мире JS я нашел, например, html2canvas и pdfMake, как здесь. Проблема здесь в том, что я не уверен, что преобразование из HTML в холст, а затем в PDF будет работать в угловом приложении. И я не уверен, что он преобразует хорошо сложные элементы DOM, такие как диаграммы в svg или canvas.
Есть ли у вас опыт работы с этими пакетами? Знаете ли вы другие рекомендуемые пакеты для этой задачи, клиента или сервера?
Хотите поделиться своим решением... Я выбрал клиентское решение.
Я начал с jsPDF, но имел некоторые проблемы. Например, было сложно преобразовать таблицы со стилем, который я хочу.
Я выбрал pdfMake для генерации PDF, html2canvas для создания скриншотов сложных компонентов и canvg для преобразования диаграмм d3js (svg-диаграммы) в холст (pdfMake может добавлять холст в качестве изображения в документ).
Я написал функцию, которая получает класс CSS корня HTML части, которую я хочу преобразовать в PDF (помните, что это одностраничное приложение), а также получает метаданные, из которых узлы HTML (опять же, по их классам CSS) должен быть добавлен в PDF (и какой тип является node-table/text/image/svg).
Затем, пройдя DOM, я прошел через элементы, которые я хочу добавить в PDF, и обрабатывал каждый по типу. Часть кода (перемещение и случай переключения по типу):
$(htmlRootSelector).contents().each(function processNodes(index, element) {
var classMeta = getMetaByClass(element.className);
if (!classMeta) {
$(element).contents().each(processNodes);
return;
}
var pdfObj = {};
pdfObj.width = classMeta.width || angular.undefined;
pdfObj.height = classMeta.height || angular.undefined;
pdfObj.style = classMeta.style || angular.undefined;
pdfObj.pageBreak = classMeta.pageBreak || angular.undefined;
switch (classMeta.type) {
case 'text':
pdfObj.text = element.innerText;
pdfDefinition.content.push(pdfObj);
break;
case 'table':
var tableArray = [];
var headerArray = [];
var headers = $(element).find('th');
var rows = $(element).find('tr');
$.each(headers, function (i, header) {
headerArray.push({text: header.innerHTML, style: classMeta.style + '-header'});
});
tableArray.push(headerArray);
$.each(rows, function (i, row) {
var rowArray = [];
var cells = $(row).find('td');
if (cells.length) {
$.each(cells, function (j, cell) {
rowArray.push(i % 2 === 1 ? {text: cell.innerText, style: classMeta.style + '-odd-row'} : cell.innerText);
});
tableArray.push(rowArray);
}
});
pdfObj.table = {
widths: $.map(headers, function (d, i) {
return i === 0 ? 80 : '*';
}),
body: tableArray
};
pdfDefinition.content.push(pdfObj);
break;
case 'image':
html2CanvasCount++;
htmlToCanvas(element, pdfObj);
pdfDefinition.content.push(pdfObj);
break;
case 'svg':
svgToCanvas(element, pdfObj);
pdfDefinition.content.push(pdfObj);
break;
default:
break;
}
$(element).contents().each(processNodes);
});
Это решение в целом. Надеюсь, это поможет кому-то.