Архив: русифицируем FOP 0.17
Это HOWTO описывает технику патчинга FOP версии 0.17 для работы с
русскими текстами и кириллическими шрифтами, успешная попытка которого
была предпринята мной в феврале 2001 года. С тех пор FOP значительно продвинулся
в своем развитии (о количестве багов в версии 0.17 можно судить хотя бы
по этому HOWTO). Однако эта версия до сих пор представляет интерес, так
как позволяет использовать кириллические Postscript Type1 шрифты и генерировать
PDF 1.2. Кроме того, этот документ интересен тем, что дает некоторое (хотя
и довольно своеобразное) представление о механике внутренних процессов,
происходящих в FOP.
Все нижесказанное относится только к версии Apache
FOP 0.17!
Russian FOP HOWTO
Этот документ является руководством по русской локализации
FOP (Formatting Objects Processor). FOP это первый (и пока единственный,
насколько мне известно) PDF-форматтер, обрабатывающий
XSL Formatting Objects. Это добровольческий open-source проект под
эгидой Apache
Software Foundation и замечательная, на мой взгляд штука, поскольку
позволяет реализовать полноценную издательскую систему на базе XML. Подробно
о проекте можно почитать на сайте xml.apache.org/fop/
и в листе fop-dev@xml.apache.org.
Как это не прискорбно, русских разработчиков там совсем нет и поэтому
мне пришлось самостоятельно исследовать все глюки, связанные с кириллицей
в FOP'e (из чего и родился данный HOWTO). Будем надеяться, что со временем
ситуация изменится.
[... skip]
Будучи сторонником гипотезы о том, что любую задачу можно решить бесконечным
множеством способов, с радостью услышу о других, может быть более простых
и красивых методах настройки FOP для работы с русскими текстами. Если
у вас возникнут какие-то замечания, предложения, решения для оставленных
мною проблем и пр. (а также если вам просто интересны проблемы XML-паблишинга
и конвергенции форматов), пишите.
Буду крайне признателен.
1 Приступаем к локализации
1.1 Приборы и материалы
Что нам понадобится:
- Девелоперская версия дистрибутива FOP (лежит здесь,
ок. 5Mb) . В данный момент последней версией продукта была версия 0.17
и именно к этой версии относится все нижесказанное.
- Для того чтобы билдить и запускать FOP, нужны также Ant, Xalan и Xerces
(к счастью, все они включены в один дистрибутив с FOP'ом).
- Java SDK (1.2 как минимум)
- Некоторое количество ваших любимых русских шрифтов (Type1 и/или TrueType).
Кстати, некоторые из них в процессе могут перестать быть вашими любимыми
шрифтами - почему, станет ясно позднее. Сразу предупреждаю - не пытайтесь
использовать шрифты Unicode (стандартные Windows). FOP с ними работать
не будет. Шрифты должны быть в 8-битной кодировке (1251, КОИ, Mac
- не имеет значения).
1.2 Установка и компиляция FOP
Процесс установки и компиляции FOP подробно описан в родной документации
и обычно вопросов не вызывает. Обратите внимание, что весь дистрибутив
сделан как ant-овский проект. Скорее всего, билдить дистрибутив взад и
вперед нам придется часто и много, поэтому данное удобство нам будет весьма
кстати. Для получения нового билда достаточно запустить build.bat
(build.sh) в домашнем каталоге FOP'a и скопировать полученный fop.jar
из каталога build (здесь и далее пути указаны относительно
домашнего каталога FOP) в то место, которое указано у вас в CLASSPATH
(обычно, тот же домашний каталог FOP). Рекомендуется перед каждой новой
компиляцией убивать каталог build (он создается автоматически).
Все файлы, необходимые для сборки билда, хранятся в каталогах src/codegen
(настроечные XML-файлы) и src/org (собственно исходники на
Java). Именно в этих каталогах будет вестись наша основная работа.
Установив и скомпилировав FOP, попробуйте с ним поиграться - возьмите
один из родных примеров FO-файлов и попытайтесь сделать из него PDF (с
помощью класса org.apache.fop.apps.CommandLine). Получилось?
Прекрасно - теперь попробуйте заменить часть текста в тестовом FO-файле
на русский (например, что-нибудь вроде "художник-эксперт с компьютером
всего лишь яйца в объёмный низкий ящик чохом фасовал" плюс то же прописными
буквами). Повторите процедуру создания PDF. Если вы увидели на месте русского
текста стройные ряды решеток (знаков "#") - замечательно, значит надо
приниматься за локализацию.
Предполагается что русский текст в Unicode (UTF-8). В случае использования
8-битной кодировки вместо решеток вы должны увидеть буквы европейских
алфавитов (с умляутами, диерезисами и т.д.)
1.3 Что нужно сделать для локализации FOP
Итак, чтобы получить стабильный русский билд FOP'a, нам необходимо будет
попытаться совершить следующие действия:
- Настроить
мэппинг символов кириллицы
- Вытащить
метрики из шрифтов (попутно залечив некоторые врожденные баги в
FOP'e) и скомпилировать его с этими шрифтами
- Настроить
русские переносы с помощью некоторых шаманских методик.
Эти этапы одновременно являются частями данного HOWTO. На каждом из этапов
мы должны будем получить некоторое улучшенное состояние нашего FOP'a,
постепенно приближаясь к условному идеалу. Это улучшенное состояние будет
отображаться в объективной реальности в виде различных конфигурационных
файлов и исходников кода, поправленных в соответствии с нашими задачами.
В конце описания каждого этапа будет находиться секция Workshop, в которой
вы сможете скачать примеры результатов, которые должны появиться на данном
этапе, а также некоторые вспомогательные материалы. Поскольку моей целью
не являлось научить читающих этот документ людей увлеченно ковыряться
в чужом коде и отлавливать чужие баги (что обязательно приводит к неврастении
и преждевременному полысению), можно просто пропускать некоторые шаги
и пользоваться готовыми материалами из Workshop.
2 Настраиваем мэппинг кириллицы
В каталоге src/codegen лежит файл charlist.xml.
Этот очень важный файл задает мэппинг, то есть отображение кодов символов
из кодировки входного FO-текста в кодировку используемых шрифтов. В нашем
случае необходимо создать, во-первых - мэппинг кириллицы из Unicode, во-вторых
- из 8-битной кодировки (это будет необходимо для работы переносов). К
этому моменту вы должны определиться с тем, какую 8-битную кодировку текста
и кодировку шрифта вы будете использовать. Лучше если это будет одна кодировка,
хотя это и не обязательно - с помощью charlist.xml можно
реализовать мэппинг практически любой кодировки в любую другую. Например,
можно работать с текстами в КОИ, используя Windows-шрифты и наоборот -
главное правильно настроить мэппинг.
Обратите внимание, что выбор русской кодировки текста и шрифта
для FOP - исключительно дело личных предпочтений, и не зависит от кодировки,
родной для вашей ОС (я надеюсь, что вы делаете XML в Unicode). Я использовал
windows1251 просто потому, что во-первых - для этой кодировки у меня больше
русских шрифтов и во-вторых - в этой кодировке буквы идут как им полагается
- почти по алфавиту, и их коды связаны с Unicode простой зависимостью.
Мэппинг каждого символа задается с помощью XML-элемента map.
Этот элемент имеет два обязательных атрибута - unicode и
win-ansi. Атрибут unicode определяет шестнадцатеричный
код символа во входном FO-файле, а win-ansi - код сопоставляемого
с ним символа в шрифте.
Итак, создаем мэппинг для Unicode. Для этого нам нужно найти все элементы
map c атрибутами win-ansi, значения которых
совпадают с кодами русских букв в шрифте. Например, в кодировке windows-1251
русские буквы занимают диапазон 0xC0..0xFF. У всех этих элементов
нужно заменить значения атрибутов unicode на Unicode-код
соответствующей русской буквы, сохраняя значение атрибута adobe-name:
<map adobe-name="Agrave" win-ansi="0x00C0" unicode="0x00C0"/>
заменяем на
<map adobe-name="Agrave" win-ansi="0x00C0" unicode="0x0410"/> (кириллическая А)
и так далее (еще 65 раз :-). Для этой операции нужно иметь таблицу вашей
кодировки и таблицу подмножества Unicode для русских букв (см. Workshop).
Не забывайте, что значение атрибута adobe-name (внутреннее
имя глифа для данной буквы в шрифте) должно сохраняться, то есть быть
именем совпадающей европейской буквы.
Если вы пользуетесь win1251, то найти соответствующий код Unicode можно
с помощью зависимости:
unicodeChar = 0x0410 +(winChar - 0x00C0)
Исключение составляют буквы Ё и ё (:E и :е). Эти буквы лежат вне алфавитного
порядка как в win, так и в Unicode. Так, код буквы Ё (win-код 0x00A8)
в Unicode равен 0x0401, а код ё (win-код 0x00B8) - 0x0451.
Также подобное исключение составляет некоторое множество не-русских
букв кириллицы (украинские, сербские и др. специфические буквы). Если
вы используете их, не забудьте также сделать для них мэппинг.
Итак, мы сделали мэппинг из Unicode в нашу кодировку шрифта. Это довольно
утомительно, однако сейчас нам предстоит повторить то же самое, но уже
для 8-битовой кодировки. Это нужно для того, чтобы FOP принимал кириллицу
не только в Unicode, но и в 8-битовой кодировке. Это понадобится нам чтобы
обойти некоторые глюки в системе расстановки переносов, но об этом после.
Наша задача сейчас облегчается тем фактом, что здесь мы избавлены от
атрибута adobe-name. К тому же, если мы используем одну кодировку
для шрифта и для текста, наши новые элементы map будут иметь
одинаковые значения атрибутов unicode и win-ansi:
<map win-ansi="0x00C0" unicode="0x00C0"/>
<map win-ansi="0x00C1" unicode="0x00C1"/>
...
<map win-ansi="0x00FF" unicode="0x00FF"/>
Чтобы не писать все 66 элементов руками, можно написать программку, генерящую
данную последовательность (я поступил именно так). Не забудьте только
потом вручную добавить буквы Ё/ё. После этого полученную последовательность
элементов map нужно вставить куда-нибудь внутри элемента
font-mappings файла charlist.xml.
Теперь сохраните файл charlist.xml и перекомпилируйте FOP.
Попробуйте снова преобразовать ваш тестовый FO-файл в PDF. Если все сделано
правильно, вместо решеток на месте кириллицы должны появиться буквы европейских
алфавитов из набора Latin-1.
2.1 Workshop
Workshop
к ч. 2 (ZIP, 26K):
- Пример файла charlist.xml для win1251
- Таблица перекодировки win1251 -> Unicode
- Таблица перекодировки KOI-8 -> Unicode
- Таблица всех символов Unicode
3 Создаем метрики шрифтов
На предыдущем этапе мы научили наш FOP понимать кириллицу. Сейчас мы
научим его ее показывать.
Процесс добавления шрифтов в FOP довольно подробно описан в документации
(fonts.html). Согласно этому документу, для добавления шрифта необходимо:
- Создать XML-файл с описанием метрик шрифта (ширины всех глифов и если
повезет, информацию о кернинге пар). Для этого в дистрибутив FOP входят
утилиты PFMReader (org.apache.fop.fonts.apps.PFMReader)
и TTFReader (org.apache.fop.fonts.apps.TTFReader).
- Прописать новые шрифты в классе org.apache.fop.render.pdf.FontSetup
- Добавить вызовы XSL-преобразования метрик шрифтов в Java-классы в
Ant'овском конфиге build.xml (в домашнем каталоге FOP)
- Перекомпилировать FOP
Тем не менее, при попытке использовать кириллические шрифты, оказалось
что не все так просто.
Во-первых, выяснилось, что не из всех кириллических щрифтов удается
вытащить метрики для символов кириллицы. Для многих шрифтов (особенно
TrueType) возвращаются нулевые значения ширины кириллических символов.
Я не специалист в области форматов шрифтовых файлов и поэтому не стал
разбираться с этой проблемой, а просто игнорировал эти шрифты, благо было
из чего выбирать (это как раз то, что я имел в виду когда говорил что
некоторые ваши любимые шрифты перестанут быть таковыми). Есть подозрение,
что причина - во внутреннем формате самих шрифтов, хотя с другой стороны,
непонятно как эти же шрифты работают в других программах. Если кто-то
знает почему это происходит, большая просьба сообщить мне.
Так или иначе, эти метрики использовать невозможно. Конечно, если именно
этот шрифт вам особенно дорог, можно попытаться подобрать к нему метрики
соответствующих символов из какого-нибудь похожего шрифта, однако в этом
случае качество результата ложится целиком на вашу совесть. Еще можно
определять метрики каждого символа в какой-нибудь программе типа FontLab
или FontoGrapher и прописывать их вручную.
Эта проблема решается элементарно для моноширинных шрифтов - у
них ширина всех символов одинакова.
Cуществует ограничение для Windows - FOP не работает с subset'ами шрифтов.
То есть, вы не сможете использовать кириллические псевдошрифты Windows,
на самом деле являющиеся subset'ами т.н. интернациональных Unicode-шрифтов
(типа Arial Cyr, Times New Roman Cyr и т.д.). Во всех этих шрифтах FOP
будет воспринимать только ту часть, которая соответствует стандартному
ASCII (т.е. латиница и буквы европейских алфавитов).
Во-вторых - как оказалось, PFMReader и TTFReader оба игнорируют последний
символ в шрифте. То есть, ширина символа с кодом 0xFF (в Win1251 это
буква "я") остается величиной неизвестной. Буржуи этого, естественно,
не замечали по той простой причине, что в их буржуйских кодировках этот
символ зарезервирован, проще говоря, этот код не соответствует никакой
букве или символу. Это действительно тупой баг, о котором я написал на
Apache в bugzill'y и мне ответили, что он исправлен. Так что, если у вас
достаточно свежий дистрибутив, возможно вы и не столкнетесь с этой проблемой.
Если наоборот - существуют два ее решения: простой и очень простой.
Простой заключается в исправлении ошибок в исходниках:
- В файле org/apache/fop/fonts/apps/PFMReader.java, стр.
391, в условии цикла for меняем знак отношения "<" ("меньше")
на "<=" ("меньше или равно")
- В файле org/apache/fop/fonts/PFMFile.java, стр. 234,
увеличиваем на 1 размерность массива extentTable
- В файле org/apache/fop/fonts/apps/TTFReader.java, стр.
406, в условии цикла for меняем знак отношения "<" ("меньше")
на "<=" ("меньше или равно")
Очень простой способ - для каждого шрифта подбирать и вручную прописывать
ширину недостающего символа с кодом 0xFF (для "я", например, в большинстве
шрифтов подходит ширина буквы "к").
Еще несколько нюансов:
В результате этих манипуляций вы должны получить XML-файлы с описанием
метрик ваших шрифтов. Теперь добавьте эти метрики в FOP, как описано в
документации и перекомпилируйте его. Можно заменить стандартные FOP-шрифты
(Times, Helvetica, Courier) их кириллическими версиями. В в классе org.apache.fop.render.pdf.FontSetup
можно определить, какие из ваших шрифтов будут serif, sans-serif и monospace
по умолчанию.
Советую поместить ваши шрифтовые файлы (PFB и TTF) где-нибудь в каталоге
src/org/apache/fop, для включения их в fop.jar. Это несколько
утяжелит JAR-файл, зато вы получите полностью переносимый билд, работающий
независимо от наличия и расположения шрифтов в системе. Например, можно
создать в каталоге src/org/apache/fop/render/pdf/fonts подкаталоги
pfb и ttf. В XML-файлах метрик необходимо указать
путь к шрифтовым файлам в элементе /font-metrics/embedResource
или запускать PFMReader (TTFReader) с опцией -er. Путь к
шрифтовым файлам указывается относительно каталога src/org/apache/fop/render/pdf/fonts.
Например:
<embedResource>"pfb/times.pfb"</embedResource>
Если вы все делали правильно, вас ждет виктория - должен получиться PDF
с кириллицей, написанной вашим шрифтом!
3.1 Workshop
Workshop
к ч. 3 (ZIP, 19K):
- Пример файла метрик шрифта Times-Roman.xml (Type1)
- Исправленная версия PFMReader (файлы org/apache/fop/fonts/apps/PFMReader.java,
org/apache/fop/fonts/PFMFile.java)
- Исправленная версия TTFReader (org/apache/fop/fonts/apps/TTFReader.java)
- Исправленная версия FontPostProcess.xsl
4 Настраиваем русские переносы
Для того, чтобы FOP смог делать переносы в русском тексте, необходимо
создать конфигурационный XML-файл с именем ru.xml и поместить
его в каталог hyph. Этот файл содержит шаблоны переносов
в формате TeX. Файл с конфигурацией переносов должен быть размечен в соответствии
с hyph/hyphenation.dtd.
Очень важный момент: кодировка файла переносов должна совпадать с
кодировкой входного FO-текста. При этом кодировка должна быть 8-битной.
Видимо, это связано с тем, что алгоритм переносов FOP взят из TeX'a, а
там, как известно, никакой Unicode не поддерживается.
Я задал вопрос разработчикам по этому поводу (в fop-dev@xml.apache.org)
и мне вежливо ответили, что я не прав - Unicode алгоритмом переносов поддерживается,
но только для.. default subset, т.е. символов, совпадающих с WinAnsi ;-)
Следствием этого является невозможность использования текстов в Unicode
(UTF-8) - переносы там работать не будут. Именно по этой причине мы делали
мэппинг кириллицы для 8-битной кодировки (1251, KOI или Mac).
Итак, файл переносов и входные FO-файлы должны быть в одной 8-битной
кодировке - той, для которой сделан мэппинг в charlist.xml.
Однако, большинство XML-редакторов и XSL-процессоров работают с кириллицей
в Unicode (UTF-8). Поэтому наиболее привлекательным было бы решение, когда
исходный текст перекодируется из UTF-8 в нужную 8-битную кодировку непосредственно
перед обработкой в FOP (желательно автоматически). Это можно сделать разными
способами, но я счел, что наиболее эффективным было бы включить перекодировку
прямо в класс, запускающий FOP из командной строки (org.apache.fop.apps.CommandLine).
Одновременно с перекодировкой можно предусмотреть какую-то предварительную
обработку входного текста. Я, например, сделал замену обычных кавычек("
") на русские типографские "елочки"(« »).
После этого снова перекомпилируйте FOP и запустите CommandLine с вашим
тестовым FO-файлом, в который добавьте побольше русского текста, чтобы
увидеть переносы. У элемента fo:root не забудьте указать
атрибуты language="ru" hyphenation-character="-" hyphenation-push-character-count="2"
hyphenation-remain-character-count="2". Если все в порядке и переносы
успешно работают - поздравляю, вы получили полностью локализованный билд
Formatting Object Processor!
4.1 Workshop
Workshop
к ч. 4 (ZIP, 19K):
- Конфигурационный файл русских переносов ru.xml (в win1251)
- Исправленная версия org.apache.fop.apps.CommandLine (для
перекодирования в win1251 и замены кавычек)
Все вышесказанное относится только к версии
Apache FOP 0.17!
|