PDF это первое, что мне приходит на ум, когда я слышу "печать документов", Поэтому я перевел все шаблоны из DOC в PDF, при помощи замечательной кнопочки в OpenOffice. Теперь по заполнению. На базе PDF можно создавать формы, которые потом заполняются либо руками, либо программно.
Только что узнал, что OpenOffice и сам умеет создавать PDF формы, достаточно добавить поля форм на страницу и экспортировать в PDF. Но в тот момент я об этом не подумал (мне должно быть стыдно!), поэтому пошел в обход и воспользовался Scribus. Импортировал в него созданные ранее PDF, как фоновые изображения и наложил сверху слой с элементами формы, потом опять экпортировал в PDF.
Вывод из этой истории: можете попробовать создать форму прямо в OpenOffice, а если не получится, или результат не будет Вас устраивать, воспользуйтесь Scribus.
Формы готовы, теперь о том, как их все таки заполнять. Моя программа написана на базе Django, поэтому я искал какое-нибудь Python ориентированное решение. Многим известный Reportlab надежд не оправдал, так как полноценная работа с существующими PDF в open source версии отсутсвует. После затяжных поисков я начал посматривать на консольные программки, которыми можно вопользоватся через os.system. Не красивое решение, но нужно было хоть что-то. Как оказалось pdftk умеет заполнять PDF формы! Ура! однако радость была не долгой ... сервер с програмкой работает под управлением 64 битной FreeBSD, но собрать pdftk под эту платформу за разумное время мне так и не удалось, а тесты на Linux показали, что с кодировками и шрифтами тоже не все гладко.
Небольшое лирическое отсупление, о том как все таки заполняют PDF формы. Алгоритм такой:
1. Сначало создается XFDF или FDF файл содержащий имена полей и что в них должно быть (от себя добавлю, забудьте про FDF это жуткий формат, пользуйтесь XFDF, который по сути обычный XML)
Формат XFDF выглядит так:
<?xml version="1.0" encoding="UTF-8"?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<fields>
<field name="ИМЯ_ПОЛЯ">
<value>ЗНАЧЕНИЕ_ПОЛЯ</value>
</field>
</fields>
</xfdf>
2. Потом XFDF/FDF файл и PDF с формой передаются программе, которая читает данные из первого и заносит их во второй.
После возобновления поисков был найден iText (к Apple отношения не иммет), который, судя по описанию, умел все что нужно и его разработка шла полным ходом (в отличии от заброшенного pdftk). Однако, у этого чудо техники есть две важные особенности:
1. Это не полноценная программа, а библиотека для работы с PDF.
2. Она написана на Java ...
В связи с тем, что на очередные поиски времени не было, решил написать свою первую программу на Java.
Вот что получилось:
Скомпилированная программа: xfdffill.jar
Исходный код: XFdfFill.java
package org.fillpdf;
import java.io.*;
import com.itextpdf.text.pdf.*;
public class XFdfFill {
public static void main(String[] args) {
if (args.length != 3){
System.out.println("usage:");
System.out.println("xfdffill <out.pdf> <form.pdf> <data.xpdf>");
return;
}
try {
PdfReader pdfreader = new PdfReader(args[1]);
PdfStamper stamp = new PdfStamper(pdfreader, new FileOutputStream(args[0]));
XfdfReader fdfreader = new XfdfReader(args[2]);
AcroFields form = stamp.getAcroFields();
form.setFields(fdfreader);
stamp.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
пользоваться так:
java -jar xfdffill.jar filledform.pdf pdfform.pdf data.xfdf
Где:
filledform.pdf - Файл в котором будет заполненая PDF форма.
pdfform.pdf - Оригинальная PDF форма.
data.xfdf - XFDF файл с данными.
Далее, после успешного заполнения формы отправляю получившийся файл оператору.
Но это еще не все. Некоторые документы должны содержать внутри себя много копий одного шаблона с несколькими измененными значениями полей. Есть шаблон его нужно заполнить N количество раз и каждый N раз меняется только одно - два поля, при этом оператору нужно вывести на печать все эти документы сразу. Заставлять оператора загружать несколько файлов - это не удобно, поэтому нужно объединить все эти почти одинаковые документы в один документ и отдать оператору один PDF.
Писать больше ничего не хотелось, поэтому попробовал программы, которые объединяют несколько PDF в один. К сожалению ни одна из них не справилась с PDF формой (В моем случае нужно было, что бы форма отставалась редактируемой для оператора). Поэтому опять взялся за IText.
Получилось следующее:
Скомпилированная программа: pdfmerge.jar
Исходный код: PdfMerge.java
package org.pdfmerge;
import java.io.*;
import java.util.*;
import com.itextpdf.text.pdf.*;
public class PdfMerge {
public static void main(String[] args) {
if (args.length < 2){
System.out.println("usage:");
System.out.println("pdfmerge <out.pdf> <in1.pdf> [ ... <inN.pdf> ]");
return;
}
try{
PdfCopyFields copy = new PdfCopyFields( new FileOutputStream(args[0]));
PdfReader reader;
for( int i = 1; i < args.length; i++){
reader = new PdfReader(args[i]);
List<Integer> pages = new ArrayList<Integer>();
int allpages = reader.getNumberOfPages() + 1;
for (int p = 0; p < allpages; p++){
pages.add((Integer)p);
}
copy.addDocument(reader, pages);
}
copy.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
Пользоваться так:
java -jar pdfmerge.jar out.pdf in1.pdf in2.pdf ...
Где:
out.pdf - Файл в котором будут все PDF.
in1.pdf in2.pdf ... inN.pdf - Файлы которые нужно объединить
Эта программа хорошо справляется с файлами, которые сгенерированы IText (в частности получеными от xfdffill). Если в форме были заполненые поле не через IText (я пробовал через Scribus) то вместо значений будут кракозябры.
Принцип работы такой, заполняю одну и ту же форму с помощью xfdffill разными данными полученные pdf файлы объединяю в один с помощью pdfmerge и то что получилось отправляю оператору.
В получившемся документе есть поля с одинаковыми именами и при редактировании одного из полей данные меняются во всех полях (в моем случае это не критично, а иногда даже полезно).
И напоследок класс на Python для генирации XFDF файлов.
Исходный код: xfdfwriter.py
# -*- coding: utf-8 -*-
from xml.sax.saxutils import escape, quoteattr
class XFDFWriter():
def __init__(self, xfdf_name=None):
'''
xfdf_name имя файла, куда нужно
поместить результат
'''
self.fields = {}
self.xfdf_name = xfdf_name
def setField(self, name, value):
'''
Устанавливаем значение поля
Эту функцию можно вызывать много раз.
после каждого вызова старое значение поля заменяется новым
'''
self.fields[name] = value
def setFields(self, names, value):
'''
Устанавливаем одно и то же значение многим полям.
names должен быть типа list.
Эту функцию можно вызывать много раз.
после каждого вызова старые значения полей заменяются новыми
'''
for name in names:
self.setField(name, value)
def write(self, xfdf_name=None):
'''
Записываем результат в файл.
Если xfdf_name не задан, то используем значение
установленное в конструкторе.
Указывать xfdf_name в этой функции удобно,
если нужно создать несколько xfdf файлов, в которых
отличаются значения нескольких полей, а в основном значения
полей одинаковы.
'''
data = u'<?xml version="1.0" encoding="UTF-8"?>\n'
data += u'<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">\n'
data += u' <fields>'
for field in self.fields:
data += u'\n <field name=%s><value>%s</value></field>'%(quoteattr(field), escape(self.fields[field]))
data += '\n</fields>'
data += '\n</xfdf>\n'
if not xfdf_name:
xfdf_name = self.xfdf_name
xfdf_file = open(xfdf_name, 'w')
xfdf_file.write(data.encode("utf-8"))
xfdf_file.close()
Ниже приведу пример использования этого класса.
# -*- coding: utf-8 -*-
#Подключаем XFDFWriter
from xfdfwriter import XFDFWriter
# Создаем экземпляр XFDFWriter который будет писать
# данные в 'file.xfdf'
xfdf = XFDFWriter('file.xfdf')
# Устанавливаем значения полей 'name', 'year_start', и 'year_end'
xfdf.setField('name', u'Проект №1')
xfdf.setField('year_start', u'2000 г.')
xfdf.setField('year_end', u'2003 г.')
# Записываем xfdf в файл 'file.xfdf' указанный при
# создании экземпляра этого класса
xfdf.write()
# Переопределяем значение поля 'year_start'
# (значение полей 'name' и 'year_end' остается прежним)
xfdf.setField('year_start', u'2001 г.')
# Записываем новый вариант в файл file1.xfdf'
xfdf.write('file1.xfdf')
# Переопределяем значение полей 'year_start' и 'year_end'
# (значение поля 'name' остается прежним)
xfdf.setFields( ['year_start','year_end' ], u'2002 г.')
# Записываем новый вариант в файл file2.xfdf'
xfdf.write('file2.xfdf')
P.S. Лицензия на Java программки такая же как и на IText т.е. AGPL.
Лицензия на Python программки public domain т.е. какая хотите :)
P.P.S. Использовать java через os.system очень неправильно, когда будет минутка объединю эти две программки в одного демона, который будет слушать pipe, а пока пользуюсь тем, что оператор выводящий документы только один, и в моем распоряжении мощный сервер.
может это то что мне нужно- только вот я не программист
ОтветитьУдалитьа нет примера- посмотреть?
Примера чего? pdf формы и xfdf файла?
ОтветитьУдалитьА как у вас дела обстоят с русским текстом, у меня так и не получилось заставить pdf отображать русский, когда открываешь ячейку на редактирование - текст видно, убираешь фокус - текст пропадает... Причем если заполнить pdf из xfdf средствами foxit или adobe reader то все ок.
ОтветитьУдалитьС русским все было отлично. Текст не пропадал и редактировался.
ОтветитьУдалитьРедактировали заполненные формы Acrobat Reader'ом. Другие PDF просмотерщики (Evince, Okular) с редактирование заполненного русского текста не справились.
Вы заполняли PDF формы приведенными мною программами и у Вас не получилось, или своим способом?
Огромное спасибо! Имеется абсолютно такаяже задача, воспользуюсь вашим решением
ОтветитьУдалитьДима!!! У меня есть похожая проблема. Я заполняю поле формы в pdf из php файла. Но есть одна маленькая загвоздка, когда данные приходят в pdf они не форматируются построчно. Т.е если текст длиннее ширины динамического поля, то его просто не видно(ту часть, которая выходит за эти границы). Может быть вы имеете решение этой просто задачи?
ОтветитьУдалитьПри подготовке формы нужно не забыть в настройках текстового поля указать, что оно многострочное иначе все будет в одну строку.
ОтветитьУдалитьЕсли форма подготавливается в Scribus нужно дважды щелкнуть по полю для изменения специфичных для PDF настроек и поставить соответствующую галочку.
В моем случае, так как многострострочные поля в PDF нормально не форматируются (нельзя задать межстрочный интервал), а документ должен был быть "красивым" в программе, которая готовит данные для форм приходилось высчитывать длину текста и бить на несколько строк если текст был настолько длинный, что не помещался в поле и использовать несколько однострочных полей.
Высчитывать длину можно просто считая символы (очень приблизительно если шрифт не моноширинный) или воспользоваться какой-нибудь библиотекой, которая умеет делать world wrap (например Pango) или реализовать алгоритм разбиение на строки самостоятельно.
В том то и дело что у меня установлена галочка многострочное поле в форме pdf. Все равно выводит в одну строку. В чем может быть причина, я уже не знаню. Если я после заполнения шаблона открываю его, захожу в настройки формы, убираю и ставлю галочку многострочие, тогда все нормально становится. Но мне нужно, чтобы сразу когда заполнялась форма было многострочие текста. Множество однострочных полей не подойдет
ОтветитьУдалитьпроблему не решил, но пошел другим путём:) Библиотека PCPDF!!! Делает все что нужно! Спасибо за статью.
ОтветитьУдалитьОтлично, спасибо.
ОтветитьУдалитьЗдравствуйте, не могли бы вы привести пример вашего pdf файла?
ОтветитьУдалитьПросто сгенерируйте pdf форму через scribus, это и будет моя форма ... могу дать простой пример .sla файла с настройками.
УдалитьЧто-то ну никак... даже с последней версией itext:
ОтветитьУдалитьjava.lang.RuntimeException: Root element is not xfdf: topmostSubform.
В других случаях - Root element is not Bookmark.
Форма взята отсюда: http://www.fms.gov.ru/useful/details/uvedomlenie.pdf
Самодельная (в акробате) форма - аналогично.
А вот если форму делать в OOo - то всё нормально.
>java.lang.RuntimeException: Root element is not xfdf
УдалитьТакое ощущение, что pdf и xfdf перепутаны местами
>В других случаях - Root element is not Bookmark
Не знаю, что может быть
>Форма взята отсюда: http://www.fms.gov.ru/useful/details/uvedomlenie.pdf
>Самодельная (в акробате) форма - аналогично.
Видимо форму по ссылке делали в акробате :)
Посмотрю на нее чуть позже
>Форма взята отсюда: http://www.fms.gov.ru/useful/details/uvedomlenie.pdf
УдалитьЯ проверил, c этой формой все работает
команда:
java -jar xfdffill.jar output.pdf uvedomlenie.pdf data.xfdf
содержимое data.xfdf:
<?xml version="1.0" encoding="UTF-8"?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<fields>
<field name="topmostSubform[0].Page1[0].comb_1[0]"><value>Иванов</value></field>
</fields>
</xfdf>
В output.pdf форма с заполненой фамилией
Название полей узнал с помощью:
pdftk uvedomlenie.pdf dump_data_fields_utf8
Да, действительно...
УдалитьBTW - хотелось бы объединиться с кем-то для допила программы. Я уже сделал отдельные части - но не хватает знаний Java (точнее - их практически нет).
Предполагается расширить функциональность и корректно обрабатывать XFA:
* вывод типов и названий полей (как коротких - так и длинных (как в данном случае) - с указанием вариантов для полей типа choice;`
* генерация XFDF;
* заполнение форм.
Кто может помочь?
Да, действительно...
УдалитьBTW - хотелось бы объединиться с кем-то для допила программы. Я уже сделал отдельные части - но не хватает знаний Java (точнее - их практически нет).
Предполагается расширить функциональность и корректно обрабатывать XFA:
* вывод типов и названий полей (как коротких - так и длинных (как в данном случае) - с указанием вариантов для полей типа choice;`
* генерация XFDF;
* заполнение форм.
Кто может помочь?
Спасибо за статью, воспользовался вашей программой, все работает, только русский текст не воспринимает, в файле, который заполняем, заместо русских букв выводит "������". Не подскажете в чем может быть проблема
ОтветитьУдалить1. XFDF не в UTF-8?
Удалить2. В pdf не внедрен русский шрифт (не уверен, что это обязательно но в качестве варианта).
3. Просмотерщик не понимает рускийх букв (если используется не Acrobat или старый акробат)
Спасибо, кодирование файла в UTF-8 помогла! С теперь другая проблема, после того как проект компилируется, захожу в фаил, который заполняю, поля в которых должен быть русский текст пустые, если нажать на них текст появится!
ОтветитьУдалитьКажется такое случается, когда поле слишком маленькое по высоте. Надо или увеличить поле, или уменьшить шрифт.
УдалитьПроблема решилась! Поля создавал через Adobe Acrobat X Pro, попробовал создавать форму через OpenOffice, все получилось! Такой вопрос, если используется не текстовое поле а метка, то как к ней обращаться через xfdf?
Удалить>Такой вопрос, если используется не текстовое поле а метка, то как к ней обращаться через xfdf?
Удалитьhttp://stackoverflow.com/questions/9590654/how-to-trigger-checkboxes-with-xfdf
Спасибо большое, очень помогло!
УдалитьМожно ли задать через xfdf настройки поля(длина,ширина поля)?
ОтветитьУдалитьНет
УдалитьЗдравствуйте, вы выше писали, что получили список полей через pdftk uvedomlenie.pdf dump_data_fields_utf8!Не могли бы вы подробней написать про этот метод?
ОтветитьУдалитьКуда уж подробней :)
УдалитьУстанавливаете pdftk
потом в консоли пишете команду:
pdftk < имя pdf файла > dump_data_fields_utf8
на выходе информация о полях формы включая их имена
можно перенаправить в файл при желании или отфильтровать по grep
P.S. Если все это проделываться не на "unix like" системе, то после установки еще нужно поколдовать с переменными окружения и путями, ну и возможно писать не pdftk а pdftk.exe, но точно не знаю
Спасибо! Извините, а нет ли способа получать список полей программно, т.е. через туже java, без установки программ?
ОтветитьУдалитьЕсть называется google! ;)
Удалитьможно через тот же IText http://itextpdf.com/examples/iia.php?id=121
Все понял извините, день тяжелый был! Спасибо за подробные ответы!Очень помогли!
ОтветитьУдалитьЗдравствуйте, натолкнулся на вашу статью. Попробовал воспользоваться OpenOffice, сделал форму запустил xfdffill.jar на выходе получился нормальный pdf(русский воспринимал). Попробовал объединить все в один при помощи pdfmerge.jar, получился pdf документ, только заместо русских букв нет, а так все нормально!Не подскажете в чем может быть проблема?
ОтветитьУдалитьpdfmerge.jar нормально работает только с теми файлами, которые были заполнены через xfdffill.jar
УдалитьТ.Е. Если в одной из форм были поля заполнены другим способом или вбитые по умолчанию в программе в которой подготавливалась форма, то на выгоде получится сломанный документ.
P.S. можно задавать значения полей форм по умолчанию но нужно будет их переопределять через pdfmerge.jar если они содержаткирилицу
заполнял через xfdffill.jar, вспомогательный текст(не значение полей) нормально воспринимает, а вот именно значение полей, в которых русский текст не отображает
ОтветитьУдалитьКодировка не UTF-8?
УдалитьВозможно проблема с тем как Офис создаёт формы (я свои делал через scribus), но точно не знаю, не пробовал.
Можете куда-нибудь выложить свои формы я на них посмотрю, когда будет минутка.
http://narod.ru/disk/51776029001.4f14dbbb1b0ffa41cc338ebfdc62a9b4/data.zip.html
УдалитьКакой-то кривой PDF... Название - в акроридере - кракозябрами.
УдалитьТо ли экстремальные шрифты применяются - то ли [возможно] не указан язык документа OOo.
Ну или OOo старый.
В файле который Вы дали видимо нет внедренных шрифтов или они обрезаны только до используемых символов. При склейке таких PDF шрифты теряются совсем их нужно либо внедрять потом (что я НЕ делаю в pdfmerge) или иметь все PDF c уже внедренными и не обрезанными шрифтами.
УдалитьЯ сделал тестовую форму в Scribus 1.4 экспортировал в pdf со стандартными настройками, с Вашим xfdf файлом все корректно заполнилось а потом склеилось.
=> проблема в OOo
Спасибо за ответ! Версия 3.2.1, не такая же старая!Попробую сделать все через Scribus 1.4!
УдалитьЗдравствуйте, по вашему совету начал делать формы через Scribus, только ни как не могу найти как мпортировать созданные ранее PDF, как фоновое изображение
УдалитьВставить объект Image и растенуть его на всю страницу поместить внизу всего и вставить в него PDF. На экране в скрибусе будет картинка с низким разрешением, но на самом деле в reader и на печати будет все в порядке. Еще желательно залочить картинку что бы не мешалась.
УдалитьP.S. Размер файлов PDF полученные таким способом будет огромными, если важен размер, то придется рисовать все в скрибусе.
P.P.S Предвосхищая вопрос про рамку полей, нужно два раза нажать на поле и появившемся диалоге выставить размер и цвет border в 0.
Попробовал создать блок изображения и вставить pdf, получилось, только все границы блекло серые, а после конвертирования в pdf ничего не видно
ОтветитьУдалитьСпасибо с рамкой вопрос не возник, попробовал поменять настройки выходных преобразований, стало немного получше, но все равно все осталось нечетко видно!
ОтветитьУдалитьP.S. проблему решил только переконвектированием pdf в jpeg, а потом вставкой этого изображения в Scribus
Нечеткость вставленного PDF это всего лишь оптимизация предпросмотра внутри Scribus. В экспортированном PDF все должно быть в порядке.
УдалитьC JPEG эсли DPI не достаточное будут проблемы при печати.
Не сталкивались с такой проблемой: никак не могу сделать что бы текст в поле PDF Text был Times New Roman, ставил в настройках документа, настройках при форматировании, все равно не то!Если посмотреть свойство поля, в разделе текст стоит Arial и возможности выбрать Times New Roman нет!
ОтветитьУдалитьНашел способ:в Story Editor есть кнопка найти/заменить, в правой части выставляем нужные значения, ищем и заменяем!
ОтветитьУдалитьДмитрий - поделитесь, плиз, проектом ant - чтобы статически скомпилировать с новым itext (как у Вас).
ОтветитьУдалитьХочу собрать это (http://doxgen.googlecode.com/svn/branches/xfdftool/MyPkg/xfdftool.java) - пока не выходит
Я просто руками объединил jar архивы. Распаковал положил все в одну папку и запаковал.
УдалитьПримерно то же самое я и сделал :-)
УдалитьСпасибо за работу, Дмитрий.
Даю сдачи: http://doxgen.googlecode.com/svn/branches/xfdftool/
Note: itextpdf-5.2.1.jar скачать самостоятельно
PS: если что - пишите
Я попробовал экспортировать, все равно получалось нечетко!Насчет DPI, делал конверт онлаин, была возможность выбора DPI, поставил одинаковые значения все получилось!
ОтветитьУдалитьЗдравствуйте, никогда не встречались с таким багом scribus: после создания pdf и заполнения все поля отображаются нормально, в общем обычный pdf, но вот после соединения документов в один происходит странная вещь, при нажатии на поле, его значение поднимается в верх, при повторном нажатии на любое место документа все возвращается на свои места! Может это даже не бага Scribus?
ОтветитьУдалитьНет, не встречался
УдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьУважаемый автор, Вас не затруднит посмотреть мой PDF и XFDF?
ОтветитьУдалитьну никак не хотят появлятся русские буквы.
уже дня три бьюсь над проблемой с разными утилитами.
Ваша не сработала((
http://files.mail.ru/E796CAFBFB07486D8D3B7092733A199E
А у меня файл PDF создается, но поля все перестают быть редактируемыми.
ОтветитьУдалитьчто через pdftk пробовал, что через вашу jar утилиту.
в чем может быть дело? может посмотрите?
кстати с русским тоже беда.
А у меня поля перестают быть редактируемыми
ОтветитьУдалитьпользовался и вашей утилитой и pdftk
но беда не в этом. русские буквы так и не появлились(((
куда копать??
сработал следующий вариант.
ОтветитьУдалитьмногостраничный файл был разбит постранично.
страница была прогнана через pdftk
pdftk.exe original.pdf output modified.pdf
далее использовал FPDM.php (это как то модифицированная версия класса FPDF.php)
'John',
'1050' => 'Васечкин'
);
$pdf_file_url = 'D:\\www_develop\\fpdm\\modified.pdf';
$pdf = new FPDM($pdf_file_url);
$pdf->Load($fields, true);
$pdf->Merge();
$pdf->Output('doc.pdf',F);
и вуаля.. все получилось.. а именно бился над кириллицей при заполнении.
У меня получилось добиться видных надписей кириллицей по способу, найденному на сайте http://www.sql.ru/forum/766183/itext-kirillica-pri-zapolnenii-poley-formy
УдалитьЯ добавил в код, который приводит автор данной статьи в файл XFdfFill.java перед строкой form.setFields(fdfreader); следующие строки:
final BaseFont bf = BaseFont.createFont("c:/Windows/Fonts/arial.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
form.addSubstitutionFont(bf);
Этот код добавляет шрифт Arial и указывает использовать его при заполнении формы. В чем была сделана форма с полями я не знаю. Может не очень грамотно, просто я первый раз что-то делал на java. Ну у меня отлично заработало.