Arhn - архитектура программирования

Заполните форму PDF с помощью javascript (только на стороне клиента)

Мне нужно автоматически заполнить форму PDF в моем веб-приложении angularjs. Форма PDF создается вне приложения, поэтому я могу настроить ее по своему усмотрению.

В моем приложении мне просто нужно загрузить PDF-файл, изменить поля формы и сгладить файл, чтобы он больше не выглядел как форма.

Вы знаете какой-нибудь способ сделать это?

Изменить: я нашел iText, но это библиотека Java, которая не будет работать. для моего проекта (приложение работает на планшетах, поэтому я ищу что-то на 100% HTML5)

24.07.2014

Ответы:


1

Теперь мне помогло другое решение.
Это пакет JS: https://github.com/phihag/pdfform.js
Также доступно в NPM: https://www.npmjs.com/package/pdfform.js

Если у вас возникли проблемы с открытием PDF-файлов, лучше всего использовать библиотеку Mozilla PDF.js, которая работает со всеми PDF-файлами, которые я пробовал.
Единственным недостатком на сегодняшний день является то, что она не работает должным образом с webpack. Вы должны импортировать файлы JS с тегом script.

26.02.2018

2

Я нашел решение... не идеальное, но оно должно соответствовать большинству требований. Он не использует какой-либо сервер (идеально подходит для требований конфиденциальности) или библиотеку! Прежде всего, PDF-файл должен быть версии 1.5 (Acrobat 6.0 или более поздней версии). Исходный PDF-файл может быть другой версией, но при создании полей вы должны сохранить его как совместимый с Acrobat 6.0 или более поздней версии. Если вы хотите убедиться, что формат правильный, вы можете проверить здесь

Допустим, у меня есть файл myform.pdf (без полей формы); Я открываю его с помощью Acrobat Pro (у меня Acrobat Pro 11, но он должен работать и с другими версиями). Я добавляю поля и предварительно заполняю значение поля (не имя поля!) «кодом» (уникальной текстовой строкой). Этот код будет найден/заменен строкой, которую вы хотите, с помощью функции javascript ниже, поэтому скажем «%address%» (вы можете добавить несколько полей, но использовать другой код для различения полей). Если вы хотите, чтобы поле выглядело плоским, установите поле только для чтения. Чтобы сохранить его, выберите «Файл» -> «Сохранить как другой...» -> «Оптимизированный PDF» и выберите «Acrobat 6.0 и более поздние версии» в разделе «Сделать совместимым с» (справа вверху всплывающего окна).

Сохранив файл, вы можете проверить правильность формата, открыв его в текстовом редакторе и найдя свои коды (в моем случае '%address%'). Подсчитайте количество вхождений, оно должно появиться три раза.

Следующая функция делает три вещи: - Изменяет содержимое полей - Пересчитывает длину содержимого - Исправляет таблицы перекрестных ссылок

Итак, теперь функция (посмотрите в конце на окончательный блок PDF):

Сертификат @param: ваша pdf-форма (формат этой переменной должен быть совместим с FileReader)

Изменения @param: поле изменяется, [{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);

    }

Так что код совсем не чистый, но работает :)

Дайте мне знать, если у вас есть какие-либо вопросы

30.07.2014
  • Я знаю, что этот ответ охватывает многое, но пример реализации был бы отличным. Я продолжаю получать TypeError: аргумент 1 FileReader.readAsBinaryString не реализует интерфейс Blob. 01.03.2016
  • @JohnathanElmore developer.mozilla.org/en-US/docs/Web/ API/FileReader говорит, что readAsBinaryString() не является стандартным... вам следует либо попробовать использовать chrome, либо, возможно, использовать readAsText() (но я никогда не пробовал) 07.03.2016
  • Не могли бы вы привести пример, как создать «сертификат»? 08.08.2016
  • @Moritur Сертификат не создается, это файл, который вы создаете вне своего приложения (обычно с помощью Adobe Pro). Сертификат — это форма с пустыми полями, которую необходимо загрузить в приложение. Убедитесь, что эта форма соответствует надлежащему стандарту (версия 1.5 - прочитайте stackoverflow.com/questions/24986609/ для более подробной информации) 08.08.2016
  • Спасибо, я имел в виду пример, как получить содержимое переменной сертификата, которую вы передаете в функцию. Например. как загрузить файл pdf с помощью js. Тем не менее, я думаю, что уже заставил его работать. 08.08.2016
  • Еще один вопрос относительно вашего кода: кажется, что часть xref у меня не работает. Многие из char[xrefstmPositions[int] + offset + 1 + firstObject.length + length] просто не определены. Вот тестовый PDF-файл, который я использую: dropbox.com/s/ cwfa4dzcoqkuje6/form4.pdf?dl=0 (может быть полезно и другим людям для тестирования) 08.08.2016
  • @Moritur Извините, но я написал этот код уже два года, и я точно не помню ... самый простой способ отладки - открыть файл в блокноте ++ и попытаться сделать то, что делает код, чтобы увидеть, где он терпит неудачу. Мой подход был очень простым, я только что создал форму и изменил ее через Adobe. Затем я сравнил оба файла (исходный и измененный) и попытался найти различия. Извините, я не могу больше помочь вам с этим 10.08.2016
  • Это скорее хак, чем решение для производства (это далеко не пуленепробиваемое). Это то, что есть, пока мы не получим настоящую библиотеку javascript для PDF (что означает никогда...) 10.08.2016
  • Неважно, пока спасибо за вашу помощь! Я уже расширил ваш код, чтобы он работал и с флажками (с помощью описанного вами метода), который работал довольно хорошо. С вашего согласия я мог бы даже сделать из него небольшую библиотеку на основе вашего кода. 10.08.2016
  • @Moritur Потрясающе, я благословляю вас делать с этим кодом все, что вы хотите, я действительно призываю вас создать библиотеку. Надеюсь, кто-то еще улучшит его для более новых версий PDF. 10.08.2016

  • 3

    Насколько я вижу, на планшетах нет клиентского приложения, которое бы это делало.

    Это означает, что вам понадобится поддержка на стороне сервера, и iText действительно является одним из таких продуктов. Другой — FDFMerge от Appligent, который выполняет заполнение и может быть настроен на выравнивание.

    26.07.2014
  • Не очень доволен ответом, но спасибо! Очень странно видеть pdf-библиотеки для создания pdf-файла с нуля (jspdf, pdfmake и т. д.), но нет ни одной для заполнения формы (что кажется гораздо более простой операцией!) 27.07.2014
  • Правильно, но вам просто нужно прочитать поля формы, а не весь документ... кроме того, pdf может интегрировать код javascript, который может помочь взаимодействовать с внешним кодом (я не профессионал, но я просто предполагаю) 27.07.2014
  • Хорошо, я написал решение... смотрите мой ответ 30.07.2014
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..