使用 javascript 填写 pdf 表单(仅限客户端) [英] Fill pdf form with javascript (client-side only)

查看:27
本文介绍了使用 javascript 填写 pdf 表单(仅限客户端)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要在我的 angularjs webapp 中自动填写一个 pdf 表单.pdf 表单是在应用程序外部生成的,因此我可以根据需要对其进行配置.

I need to fill a pdf form automatically in my angularjs webapp. The pdf form is generated outside the app so I can configure it as I want.

在我的应用程序中,我只需要加载 pdf,修改表单字段并展平文件,使其不再像表单.

In my app, I just need to load the pdf, modify the form fields and flatten the file so it doesn't look like a form anymore.

你知道怎么做吗?

我找到了 iText 但它是一个 Java 库,它不适用于我的项目(该应用在平板电脑上运行,所以我正在寻找 100% HTML5 的内容)

I've found iText but it's a java library which won't work for my project (the app runs on tablets so I'm looking for something 100% HTML5)

推荐答案

我找到了一个解决方案……虽然不完美,但应该能满足大多数要求.它不使用任何服务器(非常适合隐私要求)或库!首先,PDF 必须是 1.5 版(Acrobat 6.0 或更高版本).原始 pdf 可以是另一个版本,但是当您创建字段时,您必须将其保存为与 Acrobat 6.0 或更高版本兼容.如果您想确保格式正确,您可以查看此处

I've found a solution...not perfect but it should fit most requirements. It doesn't use any server (perfect for privacy requirements) or library! First of all, the PDF must be version 1.5 (Acrobat 6.0 or later). The original pdf can be another version but when you create the fields you must save it as compatible for Acrobat 6.0 or later. If you want to make sure the format is right, you can check there

假设我有myform.pdf"文件(没有表单字段);我用 Acrobat Pro 打开它(我有 Acrobat Pro 11,但它应该适用于其他版本).我添加字段并使用代码"(唯一文本字符串)预填充字段的值(不是字段名称!).此代码将通过下面的 javascript 函数查找/替换为您想要的字符串,因此可以说%address%"(您可以添加多个字段,但使用不同的代码来区分字段).如果您希望该字段具有扁平外观,请将字段设置为只读.要保存它,请转至文件 -> 另存为其他... -> 优化的 PDF 并在兼容"下选择Acrobat 6.0 及更高版本"(弹出窗口的右上角).

So let's say I have 'myform.pdf' file (with no form fields); I open it with Acrobat Pro (I have Acrobat Pro 11 but it should work with other versions). I add fields and pre-fill the field's value (not the field name!) with a 'code' (unique text string). This code will be find/replace with the string you want by the javascript function below so let say '%address%' (you can add multiple fields but use a different code to differenciate fields). If you want to have a flatten look of the field, set the field to read-only. To save it, go to File -> Save as other... -> Optimized PDF and choose "Acrobat 6.0 and later" under Make compatible with (top right of the pop-up).

保存文件后,您可以通过在文本编辑器中打开文件并查找代码(在我的情况下为%address%")来检查格式是否正确.计算出现次数,应该出现3次.

Once you have your file saved, you can check that the format is right by opening it in a text editor and looking for your codes (in my case '%address%'). Count number of occurences, it should appear three times.

以下函数做了三件事:- 更改字段内容- 重新计算内容的长度- 修正交叉引用表

The following function does three things: - Changes the fields content - Recalculate the content's length - Fix the cross reference tables

现在函数(看最后的 pdf blob):

So now the function (look at the end for the final pdf blob):

@param 证书:您的 pdf 表单(此变量的格式必须与 FileReader)

@param certificate: your pdf form (format of this variable must be compatible with FileReader)

@param 更改:字段更改,[{find: '%address%', replace: '2386 5th Street, New York, USA'}, ...]

@param changes: the field changes, [{find: '%address%', replace: '2386 5th Street, New York, USA'}, ...]

// Works only for PDF 1.5 (Acrobat 6.0 and later)
    var fillCertificate = function (certificate, changes) {


        // replace a a substring at a specific position
        String.prototype.replaceBetween = function(start, end, what) {
            return this.substring(0, start) + what + this.substring(end);
        };
        // format number with zeros at the beginning (n is the number and length is the total length)
        var addLeadingZeros = function (n, length) {
            var str = (n > 0 ? n : -n) + "";
            var zeros = "";
            for (var i = length - str.length; i > 0; i--)
                zeros += "0";
            zeros += str;
            return n >= 0 ? zeros : "-" + zeros;
        }


        // Create the reader first and read the file (call after the onload method)
        var reader = new FileReader();
        // To change the content of a field, three things must be done; - change the text of the field, - change the length of the content field, - change the cross table reference
        reader.onload = function(aEvent) {
            var string = aEvent.target.result;

            // Let's first change the content and the content's length
            var arrayDiff = [];
            var char;
            for(var foo = 0; foo < changes.length; foo++) {
                // Divide the string into a table of character for finding indices
                char = new Array(string.length);
                for (var int = 0; int < string.length; int++) {
                    char[int] = string.charAt(int);
                }
                // Let's find the content's field to change and change it everywhere
                var find = changes[foo].find;
                var replace = changes[foo].replace;
                var lengthDiff = replace.length - find.length;
                var search = new RegExp(find, "g");

                var match;
                var lastElements = [];
                var int = 0;
                var objectLenPos;
                var objectLenEnd;
                // Each time you change the content, compute the offset difference (number of characters). We'll add it later for the cross tables
                while (match = search.exec(string)) {
                    arrayDiff.push({index: match.index, diff: lengthDiff});
                    lastElements.push({index: match.index, diff: lengthDiff});
                    // Find length object
                    if(int == 0){
                        var length = 0;
                        var index;
                        while(char[match.index - length] != '\r'){
                            index = match.index - length;
                            length++;
                        }
                        objectLenPos = index + 10;
                        length = 0;
                        while(char[objectLenPos + length] != ' '){
                            length++;
                            objectLenEnd = objectLenPos + length;
                        }
                    }
                    int++;
                }
                var lengthObject = string.slice(objectLenPos, objectLenEnd) + ' 0 obj';

                var objectPositionStart = string.search(new RegExp('\\D' + lengthObject, 'g')) + lengthObject.toString().length + 2;
                var length = 0;
                var objectPositionEnd;
                while(char[objectPositionStart + length] != '\r'){
                    length++;
                    objectPositionEnd = objectPositionStart + length;
                }

                // Change the length of the content's field

                var lengthString = new RegExp('Length ', "g");
                var fieldLength;
                var newLength;

                string = string.replace(lengthString, function (match, int) {
                    // The length is between the two positions calculated above
                    if (int > objectPositionStart && int < objectPositionEnd) {
                        var length = 0;
                        var end;
                        while (char[int + 7 + length] != '/') {
                            length++;
                            end = int + 7 + length;
                        }
                        fieldLength = string.slice(end - length, end);
                        newLength = parseInt(fieldLength) + lengthDiff;

                        if (fieldLength.length != newLength.toString().length) {
                            arrayDiff.push({index: int, diff: (newLength.toString().length - fieldLength.length)});
                        }
                        // Let's modify the length so it's easy to find and replace what interests us; the length number itself
                        return "Length%";
                    }
                    return match;
                });

                // Replace the length with the new one based on the length difference
                string = string.replace('Length%' + fieldLength, 'Length ' + (newLength).toString());
                string = string.replace(new RegExp(find, 'g'), replace);
            }


            // FIND xref and repair cross tables
            // Rebuild the table of character
            var char = new Array(string.length);
            for (var int = 0; int < string.length; int++) {
                char[int] = string.charAt(int);
            };
            // Find XRefStm (cross reference streams)
            var regex = /XRefStm/g, result, indices = [];
            while ( (result = regex.exec(string)) ) {
                indices.push(result.index);
            }
            // Get the position of the stream
            var xrefstmPositions = [];
            for(var int = 0; int < indices.length; int++){
                var start;
                var length = 0;
                while(char[indices[int] - 2 - length] != ' '){
                    start = indices[int] - 2 - length;
                    length++;
                }
                var index = parseInt(string.slice(start, start + length));
                var tempIndex = parseInt(string.slice(start, start + length));
                // Add the offset (consequence of the content changes) to the index
                for(var num = 0; num < arrayDiff.length; num++){
                    if(index > arrayDiff[num].index){
                        index = index + arrayDiff[num].diff;
                    }
                }
                string = string.replaceBetween(start, start + length, index);
                // If there is a difference in the string length then update what needs to be updated
                if(tempIndex.toString().length != index.toString().length){
                    arrayDiff.push({index: start, diff: (index.toString().length - tempIndex.toString().length)});
                    char = new Array(string.length);
                    for (var int = 0; int < string.length; int++) {
                        char[int] = string.charAt(int);
                    };
                }

                xrefstmPositions.push(index);
            }
            // Do the same for non-stream
            var regex = /startxref/g, result, indices = [];
            while ( (result = regex.exec(string)) ) {
                indices.push(result.index);
            }
            for(var int = 0; int < indices.length; int++){
                var end;
                var length = 0;
                while(char[indices[int] + 11 + length] != '\r'){
                    length++;
                    end = indices[int] + 11 + length;
                }
                var index = parseInt(string.slice(end - length, end));
                var tempIndex = parseInt(string.slice(end - length, end));

                for(var num = 0; num < arrayDiff.length; num++){
                    if(index > arrayDiff[num].index){
                        index = index + arrayDiff[num].diff;
                    }
                }
                string = string.replaceBetween(end - length, end, index);

                if(tempIndex.toString().length != index.toString().length){
                    arrayDiff.push({index: end - length, diff: (index.toString().length - tempIndex.toString().length)});
                    char = new Array(string.length);
                    for (var int = 0; int < string.length; int++) {
                        char[int] = string.charAt(int);
                    };
                }

                xrefstmPositions.push(index);
            }
            xrefstmPositions.reverse();
            var firstObject, objectLength, end;
            var offset;
            // Updated the cross tables
            for(var int = 0; int < xrefstmPositions.length; int++) {
                var length = 0;
                var end;
                if(char[xrefstmPositions[int]] == 'x'){
                    offset = 6;
                } else{
                    offset = 0;
                }
                // Get first object index (read pdf documentation)
                while(char[xrefstmPositions[int] + offset + length] != ' '){
                    length++;
                    end = xrefstmPositions[int] + offset + length;
                }
                firstObject = string.slice(end - length, end);

                // Get length of objects (read pdf documentation)
                length = 0;
                while(char[xrefstmPositions[int] + offset + 1 + firstObject.length + length] != '\r'){
                    length++;
                    end = xrefstmPositions[int] + offset + 1 + firstObject.length + length;
                }
                objectLength = string.slice(end - length, end);

                // Replace the offset by adding the differences from the content's field
                for(var num = 0; num < objectLength; num++){
                    if(char[xrefstmPositions[int]] == 'x'){
                        offset = 9;
                    } else{
                        offset = 3;
                    }
                    // Check if it's an available object
                    if (char[xrefstmPositions[int] + 17 + offset + firstObject.length + objectLength.length + (num * 20)] == 'n') {
                        var objectCall = (parseInt(firstObject) + num).toString() + " 0 obj";
                        var regexp = new RegExp('\\D' + objectCall, "g");
                        var m;
                        var lastIndexOf;
                        // Get the last index in case an object is created more than once. (not very accurate and can be improved)
                        while (m = regexp.exec(string)) {
                            lastIndexOf = m.index;
                        }
                        string = string.replaceBetween(xrefstmPositions[int] + offset + firstObject.length + objectLength.length + (num * 20), xrefstmPositions[int] + 10 + offset + firstObject.length + objectLength.length + (num * 20), addLeadingZeros(lastIndexOf + 1, 10));
                    }
                    if(num == objectLength - 1){
                        if (char[xrefstmPositions[int] + offset + firstObject.length + objectLength.length + ((num + 1) * 20)] != 't'){
                            xrefstmPositions.push(xrefstmPositions[int] + offset + firstObject.length + objectLength.length + ((num + 1) * 20));
                        }
                    }
                }
            }

            // create a blob from the string
            var byteNumbers = new Array(string.length);
            for (var int = 0; int < string.length; int++) {
                byteNumbers[int] = string.charCodeAt(int);
            }

            var byteArray = new Uint8Array(byteNumbers);

            var blob = new Blob([byteArray], {type : 'application/pdf'});

// Do whatever you want with the blob here

        };

        reader.readAsBinaryString(certificate);

    }

所以代码一点也不干净,但它可以工作:)

So the code is not clean at all but it works :)

如果您有任何问题,请告诉我

Let me know if you have any question

这篇关于使用 javascript 填写 pdf 表单(仅限客户端)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆