Использование русских шрифтов (кириллицы) с Dompdf 
Персональный сайт Егора Зайцева
Egor Zaisev personal site
В начало
Обо мне Моё резюме Светодиодное освещение Мои проекты QDictionary GPS Разное

Использование русских шрифтов с Dompdf

Написать данную статью меня заставила недавняя ситуация, когда на своём рабочем сайте потребовалось динамически генерировать страницы каталога в PDF. Поскольку из меня web-программист мягко скажем «не очень» и зарабатываю отнюдь не сайтами, в прошлом году задача была отдана стороннему исполнителю, в результате чего был получен относительно рабочий результат в виде связки MODX и упомянутого Dompdf. Но, к сожалению (или счастью?), ничто не вечно в этом мире и в один не самый прекрасный день Dompdf стал выбрасывать ошибку вида «Fatal error: Uncaught exception 'Dompdf\Exception' with message 'No block-level parent found. Not good.' in /home...». Попытка решить вопрос с изначальным исполнителем вылилось только в ответ: XXX денег. Встречный закономерный вопрос: а собственно по чьей вине всё упало так быстро повис в воздухе. Забегая вперёд скажу, что причина была в смене версии PHP с 5 на 7 на хостинге, т.е. вопросов к исполнителю скажем так не возникло бы, ХХХ+ денег заслужено могли быть его. Но… подождав месяц пояснений пришлось разбираться самому. Благо «в программирование» в принципе умею и изначальный шаблон генерации не сильно устраивал: по ТЗ мы просили разместить на 1 страницу информацию, а нам сделали 2 и говорили что уложить на 1 невозможно. Хм… странно, мы же как то вручную укладывали до этого :)

Причина самой ошибки была найдена в интернете очень быстро: на сайте стояла версия 5c98652b, которая не сильно дружит с PHP 7+. Глубоко рыть не стал, а просто аккуратно обновил на текущую 8f49b3b0 с официального репозитария. Закономерно всё «волшебным» образом восстановилось. Зачем было использовать в начале 2019 года столь древнюю версию оставим на совести исполнителя.

Далее началась самая интересная часть, а именно замена шаблона генерации. Первоначальный шаблон в Word’е на одну страницу был такой. Немного поковыряв начинку Dompdf и поисковики на предмет тонкостей родился вот такой шаблон в HTML:

<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <style type="text/css"> @page{margin: 9mm 9mm 9mm 9mm;} body{ font-family: arial; font-size: 13px; line-height: 100%; } table{ width: 100%; border-spacing: 1px; } td, th{ vertical-align: top; } p{ padding:0; margin:0; } .visibleBoard{ border: 1px solid black; } .visibleBoard td,tr{ border: 1px solid black; vertical-align: center; } </style> </head> <body> <table> <tr> <td width="59%"> <!-- таблица 1 левая часть --> <table> <tr> <td style="border-bottom: 2px solid black; text-align: left;"> <div style="font-family: impact; font-size: 35px;">{$pagetitle}</div><br> <div style="font-family: verdana; font-size: 14px;">{$longtitle}</div> </td> </tr> <tr> <td style="text-align: left;"> <div><span style="font-weight: bolder;">Область применения</span></div> <br><div>{$content}</div><br> <div><span style="font-weight: bolder;">Конструкция светильников</span></div> <br><div>{$construction}</div> </td> </tr> </table> </td> <td> <!-- таблица 2 --> <table style="text-align: center;"> <tr> <td> <!-- <img width="240px" max-height="240px" src="{$image}"></br> --> <img style="width: auto; max-height: 240px;" src="{$image}"></br> <span style="font-weight: bolder;">Технические характеристики</span><br> <div class="visibleBoard">{$nTableHTML}</div> </td> </tr> <tr> <td> <div style="{$hideGabarit}"> <br><span style="font-weight: bolder;">Габаритные размеры</span><br> <img width="280px" max-height="115px" src="{$dimensions}"> </div> </td> </tr> </table> </td> </tr> <tr> <td align="right" colspan="2"> <div style="font-size:10px; color:gray;"><i>©2011-{$curyear} Мастер ЛЕД. Документ создан {$curdata} в {$curtime}</i></div> </td> </tr> </body> </html>

Откатав шаблон в браузере, получил список «установленных» шрифтов, радостно накатил его на свой Dompdf и… крепко обломался: получил кучу знаков вопроса вместо русских букв. Мда, методом перебора выяснил, что кириллицу отображает только один доступный шрифт DejaVuSans. В принципе с ним всё работало неплохо, но из за своеобразности этого шрифта были нюансы в заголовке. Плюс душа просила максимального соответствия шаблону (как бы «фирменный стиль»). Кстати на этом месте мне стало понятно, почему исполнитель так и не смог уложить всё на одну страницу :)

На вопрос «Dompdf и кириллица/русские буквы/Unicode» интернет давал просто огромную кучу советов по формированию шрифтов из TTF в AFM. Но… убив один вечер результат был один и тот же: шрифты в целом прописывались (было видно по английским буквам), только на сайте упорно вместо русских букв отображались знаки вопроса. Не вариант.

От отчаяния начал крепко думать «а может заменить Dompdf на что то другое?». Но вдумчивый поиск показал что «из коробки» с кириллицей мало кто дружит из подобных библиотек. Т.е. менять «шило на мыло» глобально смысла не было. Надо «отшлифовывать» то, что имею, благо оно работает плюс-минус. Первоначально решил разобраться как вообще Dompdf понимает кодировки и как надо сформировать файл нужного шрифта. На этом моменте удивило что рабочий шрифт DejaVuSans имел расширение UFM, описание формата которого сходу не нашёл. Зато очень быстро нашёл интересный конвертер… ttf2ufm.exe. Первый попавшийся в интернете экземпляр радостно сказал, что в x64 он работать не будет. Ну ок, я не гордый :) Эмулятор x86 запускать не сильно хотелось и пока искал рабочий вариант exe’шника неожиданно наткнулся на интересную страничку. Дословно процитирую что зацепило:

Если долго биться головой об стену, то есть вероятность ее пробить. (ц) Я
Все таки переборол эту ботву. Кароче, рецепт такой:
1. Вытаскиваем из директории C:\windows\fonts файло arialuni.ttf и кладем его рядом с ttf2ufm.exe
2. Запускаем следующую ботву:
ExpandedWrap disabled
ttf2ufm.exe -A -F -l russian arialuni.ttf arialuni
3. Дальше действуем так, как указано в README.
Вопрос решен. 8-) 8-) 8-)

Ну и следующее дополнение:

Хы. Кстати, подойдут и обычные шрифты. Не обязательно Unicode. Главное в командной строке указать -l russian

Попробовал и… УРА! Получил свой желаемый результат. А именно файлы шрифтов с расширением UFM, которые великолепно понимаются Dompdf и не менее великолепно отображают русские буквы.

Я не знаю как тебя зовут johen с сайта sources.ru, но тебе от меня самый низкий поклон за такую чёткую и рабочую инструкцию!

Кстати при таком «ручном» вводе шрифтов не забываем прописать их правильно в dompdf_font_family_cache.dist.php по шаблону:

'verdana' => array( 'bold' => $distFontDir . 'verdanab', 'bold_italic' => $distFontDir . 'verdanaz', 'italic' => $distFontDir . 'verdanai', 'normal' => $distFontDir . 'verdana' )

Ещё если шрифт не содержит нужного написания в курсиве и жирном шрифте, то прописывать надо примерно так:

'impact' => array( 'bold' => $distFontDir . 'impact', 'bold_italic' => $distFontDir . 'impact', 'italic' => $distFontDir . 'impact', 'normal' => $distFontDir . 'impact' ),

Ссылки по статье:

  1. Шаблон документа в Word;
  2. Шрифт Arial;
  3. Шрифт Impact;
  4. Шрифт Verdana;
  5. Рабочий комплект ttf2ufm (проверено на Windows 10).

Остальные шрифты я думаю Вы легко получите самостоятельно :)

Кстати ещё немного пришлось повозиться с неразрывным пробелом (& nbsp;) в разных кодировках, ибо Dompdf его категорически не понимает и выводит «квадратики» в файл. Решилось всё такой функцией на PHP:

function removeHTML($string) { $string = html_entity_decode($string); $string = strip_tags($string); $string = str_replace(html_entity_decode('&nbsp;'),' ',$string); return $string; } $content=removeHTML($content);

Функция попутно убирает все лишние теги HTML, чтобы исправить возможные косяки в базе и получить красивый pdf-документ. Ключевое в функции str_replace это как подаём код неразрывного пробела:

html_entity_decode('&nbsp;')Остальное по учебнику :)

Для особо ленивых общий файл работы с Dompdf получился такой:

<?php function removeHTML($string) { $string = html_entity_decode($string); $string = strip_tags($string); $string = str_replace(html_entity_decode('&nbsp;'),' ',$string); return $string; } use Dompdf\Dompdf; require_once dirname(__FILE__) . '/autoload.inc.php'; define('MODX_API_MODE', true); require '../index.php'; $id = $_GET['id']; $object = $modx->getObject('modResource', array('id' => $id)); $parent = $modx->getObject('modResource', array('id' => $object->get('parent'))); $curyear = date('Y'); $curdata = date('m.d.y'); $curtime = date('H:i'); $pagetitle = $object->get('pagetitle'); $pagetitle = removeHTML($pagetitle); $longtitle = $object->get('longtitle'); $longtitle = removeHTML($longtitle); $longtitle = mb_strtoupper($longtitle); $content = $object->get('content'); $content_pos = strpos($content,'Для определения модели'); if ($content_pos !== false){ $content = substr($content,0,$content_pos); } $content=removeHTML($content); $image = $object->getTVValue('image'); $construction = $object->getTVValue('construction'); $construction=removeHTML($construction); $dimensions = $object->getTVValue('dimensions'); $hideGabarit = empty($dimensions) ? 'display: none;' : ''; $hideConstruction = empty($construction) ? 'display: none;' : ''; if(strpos($dimensions,"rgb-ctr-2k")!==false)$hideGabarit = "display: none;"; /* add fix pic */ $table = trim($object->getTVValue('table')); $nTableHTML = empty($table) ? '' : <<<HTML {$table} HTML; $nPageHTML = <<<HTML <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <style type="text/css"> @page{margin: 9mm 9mm 9mm 9mm;} body{ font-family: arial; font-size: 13px; line-height: 100%; } table{ width: 100%; border-spacing: 1px; /* border: 1px solid black; */ } td, th{ /* padding: 1px; */ /* border: 1px solid black; */ vertical-align: top; } p{ padding:0; margin:0; } .visibleBoard{ border: 1px solid black; } .visibleBoard td,tr{ border: 1px solid black; vertical-align: center; } </style> </head> <body> <table> <tr> <td width="59%"> <!-- таблица 1 левая часть --> <table> <tr> <td style="border-bottom: 2px solid black; text-align: left;"> <div style="font-family: impact; font-size: 35px;">{$pagetitle}</div><br> <div style="font-family: verdana; font-size: 14px;">{$longtitle}</div> </td> </tr> <tr> <td style="text-align: left;"> <div><span style="font-weight: bolder;">Область применения</span></div> <br><div>{$content}</div><br> <div><span style="font-weight: bolder;">Конструкция светильников</span></div> <br><div>{$construction}</div> </td> </tr> </table> </td> <td> <!-- таблица 2 --> <table style="text-align: center;"> <tr> <td> <!-- <img width="240px" max-height="240px" src="{$image}"></br> --> <img style="width: auto; max-height: 240px;" src="{$image}"></br> <span style="font-weight: bolder;">Технические характеристики</span><br> <div class="visibleBoard">{$nTableHTML}</div> </td> </tr> <tr> <td> <div style="{$hideGabarit}"> <br><span style="font-weight: bolder;">Габаритные размеры</span><br> <img width="280px" max-height="115px" src="{$dimensions}"> </div> </td> </tr> </table> </td> </tr> <tr> <td align="right" colspan="2"> <div style="font-size:10px; color:gray;"><i>©2011-{$curyear} Мастер ЛЕД. Документ создан {$curdata} в {$curtime}</i></div> </td> </tr> </body> </html> HTML; $dompdf = new Dompdf(); $dompdf->set_base_path(realpath(dirname(__FILE__).'/../')); $dompdf->load_html($nPageHTML); $dompdf->setPaper('a4', 'portrait'); $dompdf->render(); $dompdf->stream($pagetitle.".pdf", ["Attachment" => 0]); ?>

Для скачивания доступны следующие файлы:

  1. Шаблон документа в Word;
  2. Шрифт Arial;
  3. Шрифт Impact;
  4. Шрифт Verdana;
  5. Рабочий комплект ttf2ufm (проверено на Windows 10).

Последнее изменение страницы 04.02.2020 18:00 MSK


Полезные программы
 Электронный словарь NeoDic
 Файловый менеджер Unreal Commander
 Архиватор 7-Zip
 Антивирусная утилита AVZ
 ClamWin Свободный Антивирус
 Утилиты от Sysinternals
 PDFCreator
 foobar2000
Полезные ссылки
 Floating Point Math
 Основные нормы освещённости
 Power Electronics Systems Laboratory (PES)
 Форум разработчиков электроники
 Проект OpenNet
 Светотехника, светодизайн, светодиоды, лампы, светильники
Интересные статьи
 Pi to one MILLION decimal places
 Советую для прочтения!
 Scratch
 Сообщество EasyElectronics.ru