Сайт Романа ПарпалакаЗаметкиТехнологииВеб-разработкаЛатех и веб-технологии

Латех и веб-технологии

2 февраля 2014 года

В прошлый раз я рассказал о своем сервисе, который генерирует для веба картинки с математическими формулами на латехе. Теперь я расскажу, как заставить программное обеспечение (TeX Live, nginx, php-fpm, SVGO) и технологии (HTML, CSS, JS, SVG) работать вместе, чтобы получился такой результат.

Аналоги

Как я уже упоминал, источником вдохновения был сервис CodeCogs. В одном из вариантов его использования на страницу подключается специальный JS, который ищет формулы, ограниченные специальными символами, например $...$ или \[...\], и заменяет их на картинки.

У такого подхода несколько достоинств. Во-первых, скрипт определяет, поддерживает ли браузер формат SVG. Если нет, то используется растровая альтернатива. Во-вторых, замена формул происходит в браузере, и на сервере не нужна предварительная обработка html-кода. О недостатках я уже писал: у формул внутри текста нет выравнивания по базовой линии, и сам сервис CodeCogs иногда глючит и выдает картинки с ошибками (например, слишком крупные символы в индексах). Это вдохновило меня на дальнейший поиск.

Интерфейс сервиса: клиентская часть

Поиск привел к статье о решении проблемы с базовой линией в латехе. Я занялся адаптацией этого решения для веба. Его суть в том, что с помощью специальных команд латех записывает в отдельный файл размеры и положение базовой линии будущей формулы. Это положение регулируется на веб-странице численным значением css-свойства vertical-align картинки с формулой. Ключевая проблема адаптации заключалась в том, как перенести значение из файла в свойство vertical-align.

Очевидный способ — парсинг html-кода и замена формул на картинки на сервере — не позволил бы сделать универсальный сервис. И мне удалось найти обходной путь: скрипты в SVG и обмен сообщениями между веб-страницей и внутренними svg-картинками.

После генерации svg-файла я добавляю в него короткий скрипт с вызовом postMessage и передачей размеров и положения базовой линии. Вот пример:

<script type="text/ecmascript">
if (window.parent.postMessage)
    window.parent.postMessage("1.14|40.1|14.3|" + window.location, "*");
</script>

На основной веб-странице подключается скрипт с обработчиком события message. В этом скрипте помимо обработчика есть функция обхода DOM-дерева и замены попавшихся формул внутри символов $$...$$ на картинки. Обработчик находит нужную картинку и устанавливает ее свойства.

В браузерах поддержка сообщений между документами даже шире, чем поддержка SVG. Скрипты в svg-картинках не работают, если их подключать через тег img. Я выбирал между тегами embed, object и iframe, по результатам тестирования остановился на первом. В старых браузерах, показывающих растровые картинки, проблема базовой линии остается нерешенной, но с этим можно жить.

Устройство сервиса: серверная часть

При первом обращении по адресу, например, http://tex.s2cms.ru/svg/x%5E2 вызывается главный php-скрипт. Он подставляет формулу «x^2» в шаблон (tex-файл) и конвертирует получившийся tex-документ командой latex в dvi-файл. Последний утилитами dvipng и dvisvgm конвертируется в png- и svg-картинки. К svg-файлам дописывается вызов postMessage, как описано выше. Ответ в запрошенном формате возвращается клиенту и соединение с ним разрывается функцией fastcgi_finish_request(). Содержимое картинок сохраняется в кеше, а svg-файлы предварительно отправляются утилите SVGO на оптимизацию и утилите gzip для создания дополнительной сжатой копии.

Кеш — это набор файлов и папок, имена которых определяются md5-хешем запрошенного URL. Я разобрался с настройкой веб-сервера nginx, чтобы он отдавал содержимое кеша напрямую, как обычные статические файлы, без вмешательства интерпретатора PHP. Обычным способом, через директиву try_files, этого сделать не получилось, потому что длина URL может превышать допустимую длину имен файлов. Поэтому пришлось использовать хеш-функцию от URL. Вычислением md5-хеша занимается сам nginx с помощью специального модуля HttpLuaModule поддержки языка Луа.

location /svg {
    set_by_lua $file_path '
        local md5 = ngx.md5(ngx.var.request_uri);
        return "/cache/" .. md5:sub(1, 3) .. "/" .. md5:sub(4);
    ';

    try_files $file_path @latex;
}

Если в кеше нужного файла нет, тогда запрос передается интерпретатору PHP и обрабатывается описанным выше способом. Модуль HttpLuaModule не входит в nginx. Но, как выяснилось, в Дебиане можно обойтись без компиляции исходников. В пакете nginx-extras модуль уже есть.

Запуск латеха — относительно тяжелая операция для сервера: время генерации картинок сравнимо с секундой. В связи с этим кеш имеет принципиальное значение для функционирования сервиса. В обычных условиях использования повторные обращения к старым формулам происходят гораздо чаще, чем обращения к новым формулам (у текстов один автор и множество читателей). Повторные обращения обслуживаются веб-сервером с помощью кеша и не создают большой нагрузки на сервер. Даже если злоумышленник попытается вызвать отказ сервиса потоком запросов к незакешированным формулам, он загрузит работой бэкенд — интерпретатор PHP. Это приведет к трудностям с генерацией новых формул (точнее, к ошибкам 502/504), но не повлияет на отдачу сгенерированных ранее формул.

Опыт эксплуатации и доработка

Как показала практика, метод определения положения базовой линии (depth в терминах теха) и размеров формулы через команды \lrbox и \usebox небезупречен. Иногда штрихи символов могут выходить за пределы области, и тогда размеры (и положение базовой линии) определяются неточно. И dvipng, и dvisvgm умеют находить правильные границы самостоятельно. Но если положение границы корректируется, становится неточной информация о базовой линии.

На помощь пришла особенность утилиты dvisvgm. Она поддерживает набор специальных команд, которые выводят в комментариях итогового svg-файла служебную информацию. Мне подошли такие команды:

\special{dvisvgm:bbox new formula}
\special{dvisvgm:raw<!--start {?x} {?y} -->}
...
\special{dvisvgm:raw<!--bbox {?bbox formula} -->}

Они выводят координаты левого верхнего и правого нижнего угла ({?bbox}), а также координаты точки ({?x} и {?y}), которая по счастливому стечению обстоятельств оказалась на базовой линии. С такой информацией определение положения базовой линии — дело обычного вычитания. Этот метод работает безупречно.

Поделиться
Посмотрите в блоге

Комментарии

#1. 3 февраля 2014 года, 22:16. Remaire пишет:
Кстати, чаще всего я вижу на сайтах MathJax.
MathJax is an open source JavaScript display engine for mathematics that works in all browsers.
#2. 14 августа 2015 года, 08:24. Juribiyan пишет:
KaTeX (https://khan.github.io/KaTeX/) проще в использовании и быстрее MathJax, но, к сожалению поддержка формул у него еще беднее.
И еще такой вопрос: вашим сервисом можно свободно пользоваться вне вашего движка? Вы справитесь с нагрузкой, если он станет популярен?
#3. 14 августа 2015 года, 10:17. пишет:
Кроме домена сервис не связан с движком. Обращения к одним и тем же формулам он обрабатывает быстро. Выдержал хабраэффект и 10 тысяч переходов с https://news.ycombinator.com/item?id=10056464

Вообще популярность бывает разная. В случае запредельной нагрузки будущие активные клиенты могут спонсировать переезд на более мощный сервер. Или сделать свою копию сервиса, его код открыт: https://github.com/parpalak/tex.s2cms.ru
#4. 26 октября 2015 года, 12:21. Станислав пишет:
Привет!
а подскажи как ты php под nginx запускаешь pdflatex? не могу его запустить никак :(
exec('pdflatex --help') — без проблем выдает справку, но exec('pdflatex formula') молча отрабатывает.

та же команда в php но из командной строки без проблем генерит pdf

Спасибо
#5. 26 октября 2015 года, 21:09. пишет:
Я сомневаюсь, что синтаксис «pdflatex formula» работает. Я создаю временный tex-файл и передаю его имя как параметр латеху: https://github.com/parpalak/tex.s2cms.ru/b … er.php#L73
#6. 13 января 2016 года, 22:00. Константин пишет:
Возможно ли запустить этот сервис под apache, а не nginx? Я попытался переписать конфигурацию (перенеся часть команд в новый php-скрипт, из которого уже вызываю render) и запустить на denwer, однако в ходе работы получаю отсутствующий файл ../vendor/autoload.php. Это я сам где-то ошибся, или проблема в использовании специфики nginx, которую я не учёл и которой в apache просто нет?
#7. 13 января 2016 года, 22:07. пишет:
Может быть и можно, я не пробовал. Apache сразу убьет производительность.

Однако ваша проблема в другом. Нужно запустить команду composer install (или php composer.phar install).

Я не зря описал требования к системе и последовательность развертывания: https://github.com/parpalak/tex.s2cms.ru
Если их не выполнить, система не заработает.
#8. 27 апреля 2016 года, 17:24. Неъматжон пишет:
Здравствуйте!
Спасибо Вам за сервис tex.s2cms.ru. Очень выручает. Вот только кириллица в формулах и картинках {picture} не отображается. :(
#9. 27 апреля 2016 года, 18:21. пишет:
Оборачивайте кириллицу в команду \text{}. Например, Q_\text{плавления}>0 отображается правильно: $$Q_\text{плавления}>0$$.
#10. 28 апреля 2016 года, 06:44. Неъматжон пишет:
Спасибо!

Оставьте свой комментарий

Ваше имя:

Комментарий:

Для выделения используйте следующий код: [i]курсив[/i], [b]жирный[/b].
Цитату оформляйте так: [q = имя автора]цитата[/q] или [q]еще цитата[/q].
Ссылку начните с http://. Других команд или HTML-тегов здесь нет.

Сколько будет 31+1?