生成pdf phantomjs

註:原創文件,轉載請註明出處

使用phantomjs生成還原度比較高的pdf文件,理論上生成word也可以,因需求沒有做這塊要求,功課留給大家去做了。

下載

//phantomjs.org/download.html

根據需要選擇對應版本下載使用

使用總結

擔心有人等不及看下面內容,先貼出來總結,在貼程式碼,最後貼介紹

郵件模板可以用ftl格式來做, 使用這個類來封裝import freemarker.template.Template;
############liunx下安裝步驟:#################

1,解壓,配置引用,安裝字體防止圖片漢字亂碼
tar -jxvf phantomjs-2.1.1-liunx-x86.tar.bz2
mv phantomjs-2.1.1-liunx-x86  /usr/local/src/phantomjs
ln -sf /usr/local/src/phantomjs/bin/phantomjs /usr/local/bin/phantomjs
yum install fontconfig freetype2
phantomjs -v #測試版本號

2,配置環境變數
vim /etc/profile 
export PATH=${PATH}:/usr/local/src/phantomjs/bin/

記得刷新環境變數,生效

3,在liunx伺服器的任意文件目錄下輸入命令: phantomjs, 正常輸出,則配置成功,ctrl+c退出

###############window下使用:################

1, 解壓zip包,
2,不是全局用的話,直接在bin目錄搜索欄輸入cmd打開即可, 不用配置環境變數
3,命令: 共四段,第一,二段不用改;第三段為自己的index.html文件路徑,也即要生成pdf的html文件路徑;第四段為輸出文件路徑
在bin目錄下: phantomjs htmlToPdf.js "file:///D://temp//index.html" "D://temp/outpdf.pdf"
註: 路徑file:///  和全路徑中要用"//"來載入而不是"\\"

用到的文件名:
index.html ##html原頁面
htmlToPdf.js  ##將html轉成pdf的關鍵js
outpdf.pdf  ##最終生成的pdf文件

#############echarts圖片生成:###################

1, 生成的echarts圖片需要放到pdf中,要分兩步,

使用命令生成圖片:
phantomjs echarts-convert.js -infile echarts-options.js -outfile echarts-demo.png -scale 0.01 -width 800

用到的文件名:
echarts-all.js
echarts-options.js  ##這個內容可以從echarts官網直接copy,實際在用的時候,可以自己生成該文件
jquery-3.2.1.min.js
echarts-demo.png   ##最終生成的圖片

2,第一步先生成echart.png,  然後將圖片路徑放到index.html中,然後在去生成pdf

上目錄

上程式碼

Html2pdfUtil.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * 轉換html為pdf
 */
public class Html2pdfUtil {

    public static String parseHtml2Pdf(String url) throws IOException {
        System.out.println(url);
        Runtime rt = Runtime.getRuntime();
        Process p = rt.exec("/Users/a/Downloads/phantomjs2/bin/phantomjs /Users/a/Downloads/phantomjs2/bin/html2pdf.js "+url + "  D://tmp//outpdf.pdf");
        InputStream is = p.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        StringBuffer sbf = new StringBuffer();
        String tmp = "";
        while ((tmp = br.readLine()) != null) {
            sbf.append(tmp);
        }
        String resultstr = sbf.toString();
        System.out.println("resultstr:"+resultstr);
        String[] arr = resultstr.split("\\$");
        String result = "";
        for(String s : arr){
            if(s.endsWith("pdf"))result = s;
        }
        return result;
    }
}


public class Converter {
    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        String result = Html2pdfUtil.parseHtml2Pdf("//www.baidu.com");
        long all = System.currentTimeMillis()- start;
        System.out.println("pdf生成地址:"+result+",用時:"+all/1000+"秒");
    }
}

echarts-convert.js

(function() {
    var system = require('system');
    var fs = require('fs');
    var config = {
        // define the location of js files
        JQUERY : 'jquery-3.2.1.min.js',
        ECHARTS : 'echarts-all.js',
        // default container width and height
        DEFAULT_WIDTH : '800',
        DEFAULT_HEIGHT : '400'
    }, parseParams, render, pick, usage;

    usage = function() {
        console.log("\nUsage: phantomjs echarts-convert.js -options options -outfile filename -width width -height height"
                        + "OR"
                        + "Usage: phantomjs echarts-convert.js -infile URL -outfile filename -width width -height height\n");
    };

    pick = function() {
        var args = arguments, i, arg, length = args.length;
        for (i = 0; i < length; i += 1) {
            arg = args[i];
            if (arg !== undefined && arg !== null && arg !== 'null' && arg != '0') {
                return arg;
            }
        }
    };

    parseParams = function() {
        var map = {}, i, key;
        if (system.args.length < 2) {
            usage();
            phantom.exit();
        }
        for (i = 0; i < system.args.length; i += 1) {
            if (system.args[i].charAt(0) === '-') {
                key = system.args[i].substr(1, i.length);
                if (key === 'infile') {
                    // get string from file
                    // force translate the key from infile to options.
                    key = 'options';
                    try {
                        map[key] = fs.read(system.args[i + 1]).replace(/^\s+/, '');
                    } catch (e) {
                        console.log('Error: cannot find file, ' + system.args[i + 1]);
                        phantom.exit();
                    }
                } else {
                    map[key] = system.args[i + 1];
                }
            }
        }
        return map;
    };

    render = function(params) {
        var page = require('webpage').create(), createChart;

        page.onConsoleMessage = function(msg) {
            console.log(msg);
        };

        page.onAlert = function(msg) {
            console.log(msg);
        };

        createChart = function(inputOption, width, height) {
            var counter = 0;
            function decrementImgCounter() {
                counter -= 1;
                if (counter < 1) {
                    console.log(messages.imagesLoaded);
                }
            }

            function loadScript(varStr, codeStr) {
                var script = $('<script>').attr('type', 'text/javascript');
                script.html('var ' + varStr + ' = ' + codeStr);
                document.getElementsByTagName("head")[0].appendChild(script[0]);
                if (window[varStr] !== undefined) {
                    console.log('Echarts.' + varStr + ' has been parsed');
                }
            }

            function loadImages() {
                var images = $('image'), i, img;
                if (images.length > 0) {
                    counter = images.length;
                    for (i = 0; i < images.length; i += 1) {
                        img = new Image();
                        img.onload = img.onerror = decrementImgCounter;
                        img.src = images[i].getAttribute('href');
                    }
                } else {
                    console.log('The images have been loaded');
                }
            }
            // load opitons
            if (inputOption != 'undefined') {
                // parse the options
                loadScript('options', inputOption);
                // disable the animation
                options.animation = false;
            }

            // we render the image, so we need set background to white.
            $(document.body).css('backgroundColor', 'white');
            var container = $("<div>").appendTo(document.body);
            container.attr('id', 'container');
            container.css({
                width : width,
                height : height
            });

            // render the chart
            var myChart = echarts.init(container[0]);
            myChart.setOption(options);
            // load images
            loadImages();
        };

        // parse the params
        page.open("about:blank", function(status) {
            // inject the dependency js
            page.injectJs(config.JQUERY);
            page.injectJs(config.ECHARTS);

            var width = pick(params.width, config.DEFAULT_WIDTH);
            var height = pick(params.height, config.DEFAULT_HEIGHT);

            // create the chart
            page.evaluate(createChart, params.options, width, height);

            // define the clip-rectangle
            page.clipRect = {
                top : 0,
                left : 0,
                width : width,

                height : height
            };
            // render the image
            page.render(params.outfile);
            console.log('render complete:' + params.outfile);
            // exit
            phantom.exit();
        });
    };
    // get the args
    var params = parseParams();

    // validate the params
    if (params.options === undefined || params.options.length === 0) {
        console.log("ERROR: No options or infile found.");
        usage();
        phantom.exit();
    }
    // set the default out file
    if (params.outfile === undefined) {
        var tmpDir = fs.workingDirectory + '/tmp';
        // exists tmpDir and is it writable?
        if (!fs.exists(tmpDir)) {
            try {
                fs.makeDirectory(tmpDir);
            } catch (e) {
                console.log('ERROR: Cannot make tmp directory');
            }
        }
        params.outfile = tmpDir + "/" + new Date().getTime() + ".png";
    }

    // render the image
    render(params);
}());

echarts-options.js

該內容可以參考//echarts.apache.org/examples/zh/index.html 官網,尋找自己需要的圖表樣式

{
    title : {
        text: '未來一周氣溫變化',
        subtext: '純屬虛構'
    },
    tooltip : {
        trigger: 'axis'
    },
    legend: {
        data:['最高氣溫','最低氣溫']
    },
    toolbox: {
        show : false,
        feature : {
            mark : {show: true},
            dataView : {show: true, readOnly: false},
            magicType : {show: true, type: ['line', 'bar']},
            restore : {show: true},
            saveAsImage : {show: true}
        }
    },
    calculable : true,
    xAxis : [
        {
            type : 'category',
            boundaryGap : false,
            data : ['周一','周二','周三','周四','周五','周六','周日']
        }
    ],
    yAxis : [
        {
            type : 'value',
            axisLabel : {
                formatter: '{value} °C'
            }
        }
    ],
    series : [
        {
            name:'最高氣溫',
            type:'line',
            data:[11, 11, 15, 13, 12, 13, 10],
            markPoint : {
                data : [
                    {type : 'max', name: '最大值'},
                    {type : 'min', name: '最小值'}
                ]
            },
            markLine : {
                data : [
                    {type : 'average', name: '平均值'}
                ]
            }
        },
        {
            name:'最低氣溫',
            type:'line',
            data:[1, -2, 2, 5, 3, 2, 0],
            markPoint : {
                data : [
                    {name : '周最低', value : -2, xAxis: 1, yAxis: -1.5}
                ]
            },
            markLine : {
                data : [
                    {type : 'average', name : '平均值'}
                ]
            }
        }
    ]
}

htmlToPdf.js

var page = require('webpage').create();
var system = require('system');
 
////讀取命令行參數,也就是js文件路徑。
if (system.args.length === 1) {
  console.log('Usage: loadspeed.js <some URL>');
//這行程式碼很重要。凡是結束必須調用。否則phantomjs不會停止
  phantom.exit();
}
page.settings.loadImages = true;  //載入圖片
page.settings.resourceTimeout = 10000;//超過10秒放棄載入
//縮放比例
page.zoomFactor=1; 
// 設置瀏覽器
page.settings.userAgent='Mozilla/5.0 AppleWebKit/537.36 (KHTML,like Gecko) Chrome/33.0.1750.117 Safari/537.36';
//截圖設置,
//page.viewportSize = {
//  width: 1000,
//  height: 3000
//};
// 設置尺寸
page.paperSize={format: 'A4',orientation:'portrait',margin:'0.8cm',border:'1cm'};
// page.paperSize = { width:'1500px',height:'2000px',orientation: 'portrait',border: '1cm' };
var address = system.args[1];
var outpathstr = system.args[2];
page.open(address, function(status) {
    function checkReadyState() {//等待載入完成將頁面生成pdf
        setTimeout(function () {
            var readyState = page.evaluate(function () {
                return document.readyState;
            }); 
            if ("complete" === readyState) { 
                // var timestamp = Date.parse(new Date());
                // var pdfname = 'HT_'+timestamp + Math.floor(Math.random()*1000000);
                // var outpathstr = "/Users/zachary/Downloads/phantomjs2/bin/"+pdfname+".pdf";
                setTimeout(function () {
                    page.render(outpathstr);
                    //console.log就是傳輸回去的內容。
                    console.log("生成成功");
                    console.log("$"+outpathstr+"$");
                    phantom.exit(); 
                },200); 
            } else {
                checkReadyState();
            }
        },200);
    }
    checkReadyState();
});

index.html

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title></title>
</head>
<body>
<div></div>

這是個demo,隨便寫的,
如果要載入圖片路徑,可以使用相對路徑,也可使用絕對路徑
</body>
</html>

jquery-3.2.1.min.js 該文件自己百度下載下即可,就不貼了

echarts-all.js 該文件自己去百度下載下,這裡就不貼了

使用方式

參考最上邊貼的總結,命令行也在,自己動手試一試即可

PDF 格式設置

我們需要的設置,基本上就是頁面格式、縮放、載入圖片等,但有些例外,下面一一講解。

page.paperSize = { format: ‘A4’, orientation: ‘portrait’, margin: ‘0.8cm’ };
注釋掉了官方例子的設置程式碼,因為傳入的參數只有3個,到 .pdf 為止,如果寫成通用模式,當然可以作為外部參數傳入。

format :A4 紙,可以設置 “5in7.5in”, “10cm20cm”, “Letter” 等

orientation :紙方向是豎著的,或者 landscape

margin :與紙四邊間距,可自定義,也可詳細設置 margin : { left: ‘0.8cm’, top : ‘0.8cm’, right : ‘0.8cm’, bottom : ‘0.8cm’ }

page.zoomFactor = 1;
page.settings.loadImages = true;
zoomFactor :頁面縮放比例

loadImages :頁面載入圖片

page.settings.userAgent = ‘Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Safari/537.36’;
這個設置比較不常見,一般的示例中都沒有提及,因為發現用 chrome 和 IE 打開生成的 pdf 時格式有點不一樣(表現在分頁方面),由於偏向 Chrome 瀏覽格式,故設置此值,解決這個不一致問題。

page.open 裡面的 setTimeout 方法作用:等待頁面執行完 js ,再生成 pdf。當然對於 js 要執行多久(要等多久),這個就不知道怎麼預算了。其實我有試過 ajax 方式載入內容,但因此問題而作罷了。

PDF 分頁

分頁來說,更好控制,不需要程式碼(js)設置,頁面使用樣式即可:

style = 「page-break-after: always;」
控制每頁內容的大小,使用

content

就行。

更多選擇 style=「page-break-before: always;」 , style=”page-break-inside: avoid;” 這個可以避免內容散到兩頁中

就寫道這裡了,覺得文章不錯,留言鼓勵下哦!