Search code examples
javascripthtmlpdfjspdfjspdf-autotable

Issue creating pdf with tables in IE


Currently, I'm using jspdf latest version and jspdf-AutoTable 2.1.0 latest version in order to create a PDF with a very complicated table.

It works like a charm in Chrome and FireFox (big surprise!) but in IE10, it's rendering the pdf awfully (another big surprise!)

This is the output of one of the most extensive tables in pdf on chrome (Currently empty)

PDF generated on Chrome

This is the pdf that IE10 renders

enter image description here

As you can see, it's not wrapping the column header as it should, nor it's expanding and showing the first columns cells and cropping the text inside them.

In order to mantain my custom table style, with it's content correct styling, I adjusted and created my own GetTableJSON method, so I can retrieve and store each individual cell style and apply it later on the createdHeaderCell and createdCell hooks

This is the complete code used in order to create this pdf with it's custom style

function DownloadSchedulePDF() {
    var orientation = landscape ? 'l' : 'p'
    var doc = new jsPDF(orientation, 'pt', paperFormat);
    doc.text('Header', 40, 50);
    var res = GetTableJSON($(".scheduleGrid table"));
    tableCellsStyles = res.styles;
    doc.autoTable(res.columns, res.data, {
        theme: 'plain',
        startY: 60,
        pageBreak: 'auto',
        margin: 20,
        width: 'auto',
        styles: {
            lineWidth: 0.01,
            lineColor: 0,
            fillStyle: 'DF',            
            halign: 'center',
            valign: 'middle',
            columnWidth: 'auto',
            overflow: 'linebreak'
        },
        createdHeaderCell: function (cell, data) {
            ApplyCellStyle(cell, "th", data.column.dataKey);
        },
        createdCell: function (cell, data) {
            ApplyCellStyle(cell, data.row.index, data.column.dataKey);
        },
        drawHeaderCell: function (cell, data) {
            //ApplyCellStyle(cell, "th", data.column.dataKey);
            //data.table.headerRow.cells[data.column.dataKey].styles = cell.styles;
            //ApplyCellStyle(data.table.headerRow.cells[data.column.dataKey], "th", data.column.dataKey);
        },
        drawCell: function (cell, data) {
            if (cell.raw === undefined)
                return false;
            if(cell.raw.indexOf("*") > -1) {
                var text = cell.raw.split("*")[0];
                var times = cell.raw.split("*")[1];
                doc.rect(cell.x, cell.y, cell.width, cell.height * times, 'FD');
                doc.autoTableText(text, cell.x + cell.width / 2, cell.y + cell.height * times / 2, {
                    halign: 'center',
                    valign: 'middle'
                });
                return false;
            }
        }
    });
    doc.save("schedule " + selectedDate.toLocaleDateString() + ".pdf");
}

function ApplyCellStyle(cell, x, y) {
    if(!pdfInColor)
        return;
    var styles = tableCellsStyles[x + "-" + y];
    if (styles === undefined)
        return;
    cell.styles.cellPadding = styles.cellPadding
    cell.styles.fillColor = styles.fillColor;
    cell.styles.textColor = styles.textColor;
    cell.styles.font = styles.font;
    cell.styles.fontStyle = styles.fontStyle;
}

Object.vals = function (o) {
    return Object.values
    ? Object.values(o)
    : Object.keys(o).map(function (k) { return o[k]; });
}
// direct copy of the plugin method adjusted in order to retrieve and store each cell style
function GetTableJSON (tableElem, includeHiddenElements) {
    includeHiddenElements = includeHiddenElements || false;

    var columns = {}, rows = [];

    var cellsStyle = {};

    var header = tableElem.rows[0];

    for (var k = 0; k < header.cells.length; k++) {
        var cell = header.cells[k];
        var style = window.getComputedStyle(cell);
        cellsStyle["th-" + k] = AdjustStyleProperties(style);
        if (includeHiddenElements || style.display !== 'none') {
            columns[k] = cell ? cell.textContent.trim() : '';
        }
    }

    for (var i = 1; i < tableElem.rows.length; i++) {
        var tableRow = tableElem.rows[i];
        var style = window.getComputedStyle(tableRow);
        if (includeHiddenElements || style.display !== 'none') {
            var rowData = [];
            for (var j in Object.keys(columns)) {
                var cell = tableRow.cells[j];
                style = window.getComputedStyle(cell);
                if (includeHiddenElements || style.display !== 'none') {
                    var val = cell
                        ? cell.hasChildNodes() && cell.childNodes[0].tagName !== undefined
                        ? cell.childNodes[0].textContent + (cell.getAttribute("rowSpan") ? "*" + cell.getAttribute("rowSpan") : '')
                        : cell.textContent.trim() 
                        : '';
                    cellsStyle[(i-1) + "-" + j] = cell 
                        ? cell.hasChildNodes() && cell.childNodes[0].tagName !== undefined 
                        ? AdjustStyleProperties(window.getComputedStyle(cell.childNodes[0]))
                        : AdjustStyleProperties(window.getComputedStyle(cell))
                        : {};
                    rowData.push(val);
                }
            }
            rows.push(rowData);
        }
    }

    return {columns: Object.vals(columns), rows: rows, data: rows, styles: cellsStyle}; // data prop deprecated
};

function AdjustStyleProperties(style) {
    return {
        cellPadding: parseInt(style.padding),
        fontSize: parseInt(style.fontSize),
        font: style.fontFamily, // helvetica, times, courier
        lineColor: ConvertToRGB(style.borderColor),
        lineWidth: parseInt(style.borderWidth) / 10,
        fontStyle: style.fontStyle, // normal, bold, italic, bolditalic
        overflow: 'linebreak', // visible, hidden, ellipsize or linebreak
        fillColor: ConvertToRGB(style.backgroundColor),
        textColor: ConvertToRGB(style.color),
        halign: 'center', // left, center, right
        valign: 'middle', // top, middle, bottom
        fillStyle: 'DF', // 'S', 'F' or 'DF' (stroke, fill or fill then stroke)
        rowHeight: parseInt(style.height),
        columnWidth: parseInt(style.width) // 'auto', 'wrap' or a number
        //columnWidth: 'auto'
    };
}

function ConvertToRGB(value) {
    if (value === undefined || value === '' || value === "transparent")
        value = [255, 255, 255];
    else if (value.indexOf("rgb") > -1)
        value = value.replace(/[^\d,]/g, '').split(',').map(function (x) { return parseInt(x) });
    else if (value.indexOf("#") > -1)
        value = value.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i
                         , function (m, r, g, b) { return '#' + r + r + g + g + b + b })
                .substring(1).match(/.{2}/g)
                .map(function (x) { return parseInt(x, 16) });
    else if (Array.isArray(value))
        return value;
    else {
        var canvas, context;
        canvas = document.createElement('canvas');
        canvas.height = 1;
        canvas.width = 1;
        context = canvas.getContext('2d');
        context.fillStyle = 'rgba(0, 0, 0, 0)';
        // We're reusing the canvas, so fill it with something predictable
        context.clearRect(0, 0, 1, 1);
        context.fillStyle = value;
        context.fillRect(0, 0, 1, 1);
        var imgData = context.getImageData(0, 0, 1, 1);
        value = imgData.data.slice
        ? imgData.data.slice(0, 3)
        : [imgData.data[0], imgData.data[1], imgData.data[2]];
    }
    return value;        
}

Edit:

As requested, this is the table HTML and the JSON for the tableCellStyles variable

https://jsfiddle.net/6ewqnwty/

Due to the size of the table and the amount of characters for the HTML and the JSON, I set them in a separate fiddle.

Edit 2:

I just made the fiddle runnable, being able to reproduce the issue.

https://jsfiddle.net/6ewqnwty/1/

Not perfectly as I have it in my application, I'm able to retrieve the pdf with the styling, only missing the columns headers text, but at least the issue when downloading the pdf on IE is still present


Solution

  • Running your example I get NaN for cellPadding. This is also probably the reason it does not work with the latest version. You could do something simple such as adding:

    cell.styles.cellPadding = styles.cellPadding || 5;
    

    in ApplyCellStyle. The reason you get NaN is that IE apparantly returns empty string instead of '0px' which chrome etc does.

    Also note that you would not need to parse the html table yourself if you upgrade since cell.raw is set to the html element in the latest version. This means that you could parse the style with something like window.getComputedStyle(cell.raw).