© 2001-2003
Алексей Алишевских
alexeya at gmail-dot-com;

Last Modified:
01/24/2006 14:00:10

Архив: русифицируем 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 и именно к этой версии относится все нижесказанное.
  2. Для того чтобы билдить и запускать FOP, нужны также Ant, Xalan и Xerces (к счастью, все они включены в один дистрибутив с FOP'ом).
  3. Java SDK (1.2 как минимум)
  4. Некоторое количество ваших любимых русских шрифтов (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, нам необходимо будет попытаться совершить следующие действия:

  1. Настроить мэппинг символов кириллицы
  2. Вытащить метрики из шрифтов (попутно залечив некоторые врожденные баги в FOP'e) и скомпилировать его с этими шрифтами
  3. Настроить русские переносы с помощью некоторых шаманских методик.

Эти этапы одновременно являются частями данного HOWTO. На каждом из этапов мы должны будем получить некоторое улучшенное состояние нашего FOP'a, постепенно приближаясь к условному идеалу. Это улучшенное состояние будет отображаться в объективной реальности в виде различных конфигурационных файлов и исходников кода, поправленных в соответствии с нашими задачами. В конце описания каждого этапа будет находиться секция Workshop, в которой вы сможете скачать примеры результатов, которые должны появиться на данном этапе, а также некоторые вспомогательные материалы. Поскольку моей целью не являлось научить читающих этот документ людей увлеченно ковыряться в чужом коде и отлавливать чужие баги (что обязательно приводит к неврастении и преждевременному полысению), можно просто пропускать некоторые шаги и пользоваться готовыми материалами из Workshop.

Настраиваем мэппинг кириллицы

В каталоге 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

Создаем метрики шрифтов

На предыдущем этапе мы научили наш FOP понимать кириллицу. Сейчас мы научим его ее показывать.

Процесс добавления шрифтов в FOP довольно подробно описан в документации (fonts.html). Согласно этому документу, для добавления шрифта необходимо:

  1. Создать XML-файл с описанием метрик шрифта (ширины всех глифов и если повезет, информацию о кернинге пар). Для этого в дистрибутив FOP входят утилиты PFMReader (org.apache.fop.fonts.apps.PFMReader) и TTFReader (org.apache.fop.fonts.apps.TTFReader).
  2. Прописать новые шрифты в классе org.apache.fop.render.pdf.FontSetup
  3. Добавить вызовы XSL-преобразования метрик шрифтов в Java-классы в Ant'овском конфиге build.xml (в домашнем каталоге FOP)
  4. Перекомпилировать 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 (для "я", например, в большинстве шрифтов подходит ширина буквы "к").

Еще несколько нюансов:

  • PFMReader выбрасывает исключение
    javax.xml.transform.TransformerException: No scheme found in URI: charlist.xml
    На самом деле, исключение возникает в XSL-процессоре (Xalan), который делает пост-обработку полученного XML-файла. Попросту говоря, XSL-процессор требует чтобы для имени файла указывался протокол (file, http, ftp, etc.) Чтобы его утихомирить, нужно в файле org/apache/fop/fonts/apps/FontPostProcess.xsl, стр. 16 вместо просто имени файла 'charlist.xml' указать 'file:charlist.xml'.
  • Для Type1 шрифтов нужно изменить значение XML-элемента /font-metrics/encoding с WinAnsiEncoding на UnknownEncoding. Для TrueType же, напротив нужно оставить WinAnsiEncoding. Чем обусловлено такое требование, для меня осталось загадкой; просто - так работает, а иначе - нет.

В результате этих манипуляций вы должны получить 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>&quot;pfb/times.pfb&quot;</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

Настраиваем русские переносы

Для того, чтобы 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!