• 游客 您好:

    目前「IT人巴啦啦天地」需要數個專家協助發表文章。

    只要您願意,可以直接與我 ihstat 連絡。我將會給你「專家」身份。

    成為「專家」有什麼好處?目前暫時還沒有。我也只願意提供最多10名會員有這樣的身份。

    他可能可以成為未來非常高的權限。(除了管理) 也可以獲得由浩瀚星空站提供的資源。

  • 本站不接受任何被列入廣告發文黑名單的電子信箱。如您無法註冊,可能是您使用的電子信箱為廣告黑名單信箱。正常的信箱都是可以正常註冊。

    如果您可以証實您的信箱非廣告黑名單,請自行來信 hstaryoching#gmail.com 申請。

    申請請留下您的正統名稱及信箱,並告知從何得知及想進來的理由。

  • 浩瀚星空站已經重新整合並新增新的開發小站天地。

    採用新版的xenforo 2.2.3 做為最新的站點系統。

    中文搜尋已在本站啟用成功,歡迎多加測試看看

    有問題請再回報

教學 如何利用JS生成PDF檔

ihstar

管理員
管理成員
這是由 IT邦 的大神留下來的。
作者:淺水員
來源出處: https://ithelp.ithome.com.tw/questions/10206319

以下是對應內容


HTML:
<iframe width="800" height="600" frameborder="0"></iframe>
<script src="pdf.js"></script>
<script src="index.js"></script>

pdf.js
JavaScript:
/**
 * 產生 pdf 的基本工具
 * 先用 addObject 寫入所需的物件
 * 最後用 output 輸出
 * 注意:這個檔案必須是 utf8 編碼,且換行為 lf
 */
function Pdf() {
    this.idCount = 0;
    this.data = [];
}

/**
 * 加入一個 obj
 *
 * @param {object} dict
 * @param {string|flase} stream
 * @param {int|undefined} specId
 */
Pdf.prototype.addObject = function (dict, stream, specId) {
    if (typeof stream === 'string') {
        dict['Length'] = stream.length;
    }
    let arr = [];
    let id;
    if (specId === undefined) {
        id = ++this.idCount;
    } else {
        id = specId;
    }
    arr.push(`${id} 0 obj`);
    arr.push('<<');
    for (let key in dict) {
        arr.push(`/${key} ${dict[key]}`);
    }
    arr.push('>>');
    if (typeof stream === 'string') {
        arr.push('stream');
        arr.push(stream);
        arr.push('endstream');
    }
    arr.push('endobj');
    this.data[id - 1] = arr.join('\n');
}

/**
 * 預留 id
 *
 * @returns {int} 預留的 id
 */
Pdf.prototype.preserveId = function () {
    return ++this.idCount;
}

/**
 * 產生 pdf 的內容
 *
 * @param {int} rootId
 * @returns {string} pdf內容
 */
Pdf.prototype.output = function (rootId) {
    let header = '%PDF-1.4\n%§§';
    let xrefArr = ['0000000000 65535 f '];
    let pos = header.length + 3;
    this.data.forEach((obj) => {
        let s = pos.toString();
        s = '0'.repeat(10 - s.length) + s;
        xrefArr.push(`${s} 00000 n `);
        pos += obj.length + 1;
    });
    return `${header}
${this.data.join('\n')}
xref
0 ${this.data.length + 1}
${xrefArr.join('\n')}
trailer
<<
/Size ${this.data.length + 1}
/Root ${rootId} 0 R
>>
startxref
${pos}
%%EOF
`;
}

/**
 * 產生 pdf 的內容
 *
 * @param {int} rootId
 * @returns {string} pdf 的 dataurl
 */
Pdf.prototype.toDataUrl=function(rootId) {
    return 'data:application/pdf;base64,'+btoa_utf8(this.output(rootId));
}

/**
 * btoa 的 utf8 版本
 * @param {string} str 字串
 * @returns {string} base64字串
 */
function btoa_utf8(str) {
    const u8enc=new TextEncoder();
    return btoa(Array.from(u8enc.encode(str), x=>String.fromCodePoint(x)).join(''));
}

index.js

JavaScript:
/**
 * 產生測試圖片
 * 一個 200 x 200 中間畫圓的 jpg
 *
 * @returns {string} base64 data url
 */
function getTestJpg() {
    let cvs = document.createElement('canvas');
    cvs.width = 200;
    cvs.height = 200;
    let ctx = cvs.getContext('2d');
    ctx.fillStyle = "rgb(255,255,255)";
    ctx.fillRect(0, 0, 200, 200);
    ctx.beginPath();
    ctx.arc(100, 100, 50, 0, Math.PI * 2);
    ctx.stroke();
    return cvs.toDataURL('image/jpeg', 1.0);
}

/**
 * 把圖片畫到 pdf 上
 * 並產生 pdf 的 dataurl
 *
 * @param {string} jpegBase64 jpeg的dataurl
 * @param {object} opt 用來指定位置與長寬 {x:x座標, y:y座標, width:寬度, height:高度}
 * @returns {string} pdf 的 dataurl
 */
function drawToPdf(jpegBase64, opt) {
    let jpegbase85 = base64ToBase85(jpegBase64.split('base64,')[1]); //轉成 base85
    /**
     * pdf 的結構請參考
     * https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf
     */
    let pdf = new Pdf();
    let catlogId = pdf.preserveId();
    let pagesId = pdf.preserveId();
    let firstPageId = pdf.preserveId();
    let resourceId = pdf.preserveId();
    let imgId = pdf.preserveId();
    let contentId = pdf.preserveId();
    pdf.addObject({
        Type: '/Catalog',
        Pages: `${pagesId} 0 R`,
    }, false, catlogId);
    pdf.addObject({
        Type: '/Pages',
        Kids: `[${firstPageId} 0 R]`,
        Count: 1
    }, false, pagesId);
    pdf.addObject({
        Type: '/Page',
        Parent: `${pagesId} 0 R`,
        Resources: `${resourceId} 0 R`,
        Contents: `[${contentId} 0 R]`,
        MediaBox: '[0 0 595.27559 841.88976]' //A4 大小
    }, false, firstPageId);
    pdf.addObject({
        ProcSet: '[/PDF /ImageB ]',
        XObject: `<< /Im1 ${imgId} 0 R >>`
    }, false, resourceId);
    pdf.addObject({
        Type: '/XObject',
        Subtype: '/Image',
        Width: opt.width,
        Height: opt.height,
        ColorSpace: '/DeviceRGB',
        BitsPerComponent: '8',
        Filter: '[/ASCII85Decode /DCTDecode]'
    }, jpegbase85+'~>', imgId);
    pdf.addObject({}, `${opt.width} 0 0 ${opt.height} ${opt.x} ${opt.y} cm /Im1 Do`, contentId);
    return pdf.toDataUrl(catlogId);

    /**
     * 把 base64 轉 base85
     * @param {string} base64 base64字串
     * @returns {string} base85字串
     */
    function base64ToBase85(base64) {
        let arr = Array.from(atob(base64), x => x.codePointAt());
        let padN = arr.length % 4 > 0 ? 4 - arr.length % 4 : 0;
        while (padN-- > 0) {
            arr.push(0);
        }
        //每4個一組
        let result = '';
        for (let i = 0, n = arr.length; i + 3 < n; i += 4) {
            let H = (arr[i] << 8 | arr[i + 1]);
            let L = (arr[i + 2] << 8 | arr[i + 3]);
            let tmp = '';
            for (let j = 0; j < 5; ++j) {
                let t = (H % 85 << 16 | L % 85);
                let r = t % 85; //對 85 取餘數
                //除 85
                H = (H - H % 85) / 85;
                L = (L - L % 85) / 85 + (t - t % 85) / 85;
                H += L >>> 16;
                L&=0xffff;
                tmp = String.fromCodePoint(33 + r) + tmp;
            }
            result += tmp;
        }
        return result;
    }
}

const jpgData = getTestJpg();
const pdfDataUrl = drawToPdf(jpgData, {
    x: 100,
    y: 600,
    width: 200,
    height: 200
});

document.querySelector('iframe').src = pdfDataUrl;

這樣就可以簡單的將圖生成PDF。下次再來研究HTML轉PDF
感謝大神的程式碼提供
 
頂部