Сообщений 12 Оценка 86 Оценить |
FOP nFOP NfopHelper Ссылки |
Исходный текст класса NfopHelper
Исправленная библиотека nFOP
Библиотека FOP давно и успешно используется Java-разработчиками для программной генерации отчетов в формате-PDF. FOP расшифровывается как Formated Object Processor и представляет собой компонент, на вход которого подается документ в формате xsl:fo, а на выходе получается документ в одном из поддерживаемых процессором форматов (PDF, PS, TXT, и т. д.). Я не буду углубляться в описание формата xsl:fo, в сети и так немало ресурсов, ему посвященных [см. 3. 4]. Вкратце замечу, что он является подмножеством языка xml и служит для описания как содержимого, так и представления (верстки) документа. Вот кусок документа в формате xsl:fo:
<?xml version="1.0" encoding="utf-8" ?> <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"> <fo:layout-master-set> <fo:simple-page-master master-name="first" page-height="29.7cm" page-width="21cm" margin-top="1cm" margin-bottom="2cm" margin-left="2cm" margin-right="2cm"> <fo:region-body margin-top="3cm" margin-bottom="1.5cm" /> <fo:region-before extent="3cm" /> <fo:region-after extent="1.5cm" /> </fo:simple-page-master> </fo:layout-master-set> <fo:page-sequence master-reference="first"> <fo:flow flow-name="xsl-region-body"> <fo:block font-family="Arial"> Здравствуй, мир! </fo:block> </fo:flow> </fo:page-sequence> </fo:root> |
Получить документ в формате xsl:fo можно разными способами – сгенерировать его вручную либо, если исходные данные представлены в формате XML, применить к ним xslt-преобразование.
Но вернемся к замечательной библиотеке FOP. Как и множество других open source-разработок, она была перенесена на платформу .NET (в данном случае рассматривается проект nFOP). Вот пример использования этой библиотеки (здесь и далее приведены примеры на языке C#):
java.io.FileInputStream inputStream = new java.io.FileInputStream("c:\\document.fo"); org.xml.sax.InputSource inputSource = new org.xml.sax.InputSource(inputStream); java.io.FileOutputStream outputStream = new java.io.FileOutputStream("c:\\document.pdf"); try { org.apache.fop.apps.Driver driver = new org.apache.fop.apps.Driver(inputSource, outputStream); driver.setRenderer(1); driver.run(); } finally { outputStream.close(); } |
Во время испытания этой библиотеки я сразу же столкнулся с проблемой при отображении русских букв – какие бы шрифты я не использовал, Acrobat Reader упорно показывал пустые квадраты вместо русских букв.
После непродолжительного поиска в Google стало ясно, что нужно внедрить шрифт в документ, благо FOP поддерживает такую возможность. FOP-то поддерживает, а вот в nFOP эта возможность реализована криво. Попытка программно указать шрифт для внедрения успеха не принесла – во время выполнения метода driver.run() все настройки шрифтов инициализируются заново. Второй способ, посредством конфигурационного файла, тоже с наскоку не прошел.
Файл конфигурации у меня выглядел примерно так:
<configuration> <fonts> <font metrics-file="C:\Temp\Arial.xml" embed-file="C:\WINDOWS\Fonts\Arial.ttf" kerning="yes"> <font-triplet name="Arial" style="normal" weight="normal" /> </font> <font metrics-file="C:\Temp\Arialbd.xml" embed-file="C:\WINDOWS\Fonts\Arialbd.ttf" kerning="yes"> <font-triplet name="Arial" style="normal" weight="bold" /> </font> <font metrics-file="C:\Temp\Verdana.xml" embed-file="C:\WINDOWS\Fonts\Verdana.ttf" kerning="yes"> <font-triplet name="Verdana" style="normal" weight="normal" /> </font> <font metrics-file="C:\Temp\Verdanab.xml" embed-file="C:\WINDOWS\Fonts\Verdanab.ttf" kerning="yes"> <font-triplet name="Verdana" style="normal" weight="bold" /> </font> </configuration> |
Здесь каждый тег font описывает шрифт, который следует внедрить в документ. Атрибут embed-file определяет расположение файла шрифта, а атрибут metrics-file – расположение файла с метриками шрифта. Вложенный тег font-triplet определяет начертание и наименование шрифта, для которого будут использоваться указанные файлы шрифта и метрик. Пример конфигурационного файла также можно найти в дистрибутиве оригинальной библиотеки FOP – это файл userconfig.xml.
Что представляет собой файл метрик, я разбираться не стал, однако выяснил, что его можно получить с помощью того же самого nFOP. Для этого служит класс org.apache.fop.fonts.apps.TTFReader. Но увы, метод этого класса writeFontXML в библиотеке nFOP реализован не был, а именно он отвечает за сохранение файла метрик. Пришлось писать свою реализацию этого метода:
using java.io; using org.w3c.dom; using org.apache.xml.serialize; using org.apache.fop.fonts.apps; ... publicstaticvoid CreateFontMetrics( string fontPath, string metricsPath, bool cid) { TTFReader ttfReader = new TTFReader(); TTFFile ttfFile = ttfReader.loadTTF(fontPath, null); Document doc = ttfReader.constructFontXML(ttfFile, null, null, null, null, cid, null); OutputFormat format = new OutputFormat(doc); FileWriter writer = new FileWriter(metricsPath); XHTMLSerializer serializer = new XHTMLSerializer(writer, format); serializer.asDOMSerializer(); format.setEncoding("UTF-8"); format.setOmitXMLDeclaration(false); format.setOmitDocumentType(true); writer.write("<?xml version='1.0' encoding='UTF-8'?>"); serializer.serialize(doc.getDocumentElement()); writer.close(); } |
Итак, конфигурационный файл и файлы метрик готовы, можно попробовать заставить nFOP их использовать. Чтобы заставить nFOP прочитать файл конфигурации, нужно где-нибудь при старте программы выполнить следующий код:
org.apache.fop.configuration.ConfigurationReader reader = new org.apache.fop.configuration.ConfigurationReader( new org.xml.sax.InputSource(new java.io.FileInputStream(configPath))); reader.start(); |
Однако в момент выполнения метода reader.start() библиотека выбрасывала странное исключение: "Resource Bundle Not Found". Разбор полетов показал, что библиотека пыталась сообщить мне об ошибке в конфигурационном файле, для чего ей потребовалось достать строку с сообщением об ошибке из ресурсов, но ресурсы не были обнаружены. Пришлось взяться за Google, и вскоре решение нашлось – хотя ресурсные файлы и присутствовали среди исходников nFOP (файлы XMLMessages.properties и XMLSchemaMessages.properties в папке xerces-2_0_2\src\org\apache\xerces\impl\msg), но они были в формате, непонятном J#. Нужно было достать утилитку vjsresgen.exe, сконвертировать с ее помощью ресурсные файлы в нужный формат, подключить полученный файл к проекту и перекомпилировать библиотеку, не забыв перед этим заменить в исходниках строки org.apache.xerces.impl.msg.XMLMessages и org.apache.xerces.impl.xs.XMLSchemaMessages на XMLMessages и XMLSchemaMessages соответственно.
После этой операции я попытался вновь прочитать конфигурационный файл и получил внятное сообщение об ошибке – что-то вроде "не найден соответствующий закрывающий тег для тега <fonts>". Ага, причина ошибки выяснена, ошибка – исправлена. Запускаем приложение еще раз и получаем еще одно исключение – "MalformedURLException". Где-то внутри библиотеки (метод buildURL класса org.apache.fop.tools. URLBuilder) живет следующий код – "return new URL(f.toString());", где f – это экземпляр класса File. Наверное, в исходной библиотеке этот код выглядел как "return new URL(f.toURL());", но в J# метод toURL в классе File отсутствует, и при портировании исходников этот метод, видимо, просто был заменен на toString. Пришлось переписать этот метод как "return new URL("file://" + f.toString());". Такая же проблема была обнаружена еще в одном месте – в методе buildBaseURL класса org.apache.fop.configuration. Configuration.
И наконец, после внесения в библиотеку всех этих изменений, она заработала!
Для более комфортной работы с библиотекой и ее конфигурирования я написал небольшой вспомогательный класс – NfopHelper. С его использованием генерация PDF выглядит следующим образом:
string srcPath = "c:\\temp\\fopTest"; string fontPath = Tools.GetSpecialFolder(Tools.SpecialFolders.CSIDL_FONTS, false); string tempPath = Path.GetTempPath(); //Создаем файл конфигурации XmlDocument xmlConfig = NfopHelper.CreateConfigFile(); //Создаем метрики для используемых шрифтов NfopHelper.CreateFontMetrics(Path.Combine(fontPath, "Arial.ttf"), Path.Combine(tempPath, "Arial.xml"), true); //Добавляем информацию о шрифте в файл конфигурации NfopHelper.AddFontToConfig(xmlConfig, Path.Combine(fontPath, "Arial.ttf"), Path.Combine(tempPath, "Arial.xml"), "Arial", "normal", "normal"); NfopHelper.CreateFontMetrics(Path.Combine(fontPath, "Arialbd.ttf"), Path.Combine(tempPath, "Arialbd.xml"), true); NfopHelper.AddFontToConfig(xmlConfig, Path.Combine(fontPath, "Arialbd.ttf"), Path.Combine(tempPath, "Arialbd.xml"), "Arial", "normal", "bold"); //Сохраняем конфигурационный файл xmlConfig.Save(Path.Combine(tempPath, "userconfig.xml")); //Запускаем процесс генерации PDF документа NfopHelper.CreatePdf(Path.Combine(srcPath, "report.fo"), Path.Combine(tempPath, "userconfig.xml"), Path.Combine(srcPath, "report.pdf")); |
NB. Здесь файлы метрик и конфигурационный файл создаются во временном каталоге, и после окончания работы программы их желательно удалить.
Сообщений 12 Оценка 86 Оценить |