Как-то раз я читала книгу на известном литературном портале и думала, какая же крутая у них читалка. Наверное, над её созданием трудится целая команда. В тот момент я даже не подозревала, что мне вскоре предстоит сделать что-то подобное. Расскажу, как я делала читалку для сайта библиотеки на заказ.
Содержание
Как я получила заказ на онлайн-читалку
Началось всё с Хабра. В прошлом году я написала статью про разработку читалки для формата FB2 на Java. И спустя какое-то время через личные сообщения ко мне обратился заказчик.
На тот момент у него был читательский клуб с небольшой аудиторией. Проект изначально закрытый с платным доступом. И вот, у владельца этого клуба возникла идея открыть онлайн-библиотеку с читалкой. Человек он занятой, у него есть крупный бизнес в другой сфере и вот такое хобби. На Хабре он обратился ко мне не лично, а через подчинённого.
Несмотря на то что в моей статье на Хабре речь шла о читалке для десктопа на Java, заказ поступил на интерфейс для онлайн-библиотеки. Если в Java я новичок, то с PHP работаю давно, и поэтому согласилась. Таким образом, передо мной встала задача сделать почти то же самое, но теперь не для десктопа, а для веба.
Как шёл процесс работы над читалкой
Изначально была задача сделать интерфейс, в котором книги показывались бы сплошной портянкой без разбивки на страницы. Мне дали ссылку на читалку одной онлайн-библиотеки, на которую я должна была ориентироваться.
Но в процессе аппетиты, как водится, росли. И в итоге техзадание мутировало до читалки в две колонки с кучей дополнительных опций. Так, например, нужно было сделать возможность менять размер шрифта, яркость, ширину полей, высоту строк и выравнивание текста.
Заказчик захотел, чтобы на панели находились такие элементы управления: содержание книги, закладки и поиск. Всё это, разумеется, должно было работать на десктопе и смартфонах. Ещё меня попросили сделать так, чтобы на мобильных устройствах работал свайп между страницами.
Сам сайт онлайн-библиотеки был на Wordpress, поэтому я решила сделать плагин для этого движка. Дело в том, что нужно было ещё создать страницу на сайте для загрузки книги пользователями, а также раздел внутри личного кабинета с книгами, которые пользователь читает, и его закладками. Чтобы человек мог легко переключаться между сайтом и читалкой, я решила сделать всё в одном плагине.
Как выглядит плагин читалки изнутри
В главном файле myreader.php я подключила все шаблоны, файлы стилей и скриптов, создала кастомный тип поста book для каталога книг, поле для загрузки электронной книги пользователями и другие опции согласно техзаданию. На страницы постов типа book добавила кнопку для перехода в читалку.
Файловая структура плагина:
myreader/
├─ css/
├─ fonts/
├─ img/
├─ js/
controller.php
docxreader.php
epubreader.php
fictionbook.php
myreader.php
template-add-book.php
template-reader.php
Основная магия происходит в файлах docxreader.php, epubreader.php и fictionbook.php. Они получают файл электронной книги в соответствующем формате. Затем они его распаковывают, приводят текст и картинки к единообразному виду и отправляют данные в controller.php.
Файл controller.php отвечает за обработку данных, переданных из docxreader.php, epubreader.php и fictionbook.php. А поступает в него текст в формате XML с тегами и картинками в бинарном виде. Файл controller.php разбивает текст на страницы и отправляет на вывод в читалку.
Остальные файлы, template-reader.php и template-add-book.php, — это шаблоны для отображения самой читалки и страницы добавления книги, соответственно. Остальные разделы, которые создаёт плагин, используют стандартные шаблоны темы.
Что насчёт фронтенда
Я работала не одна, а вместе с парнем, который занимался фронтендом. Первый вариант с простеньким интерфейсом сделала сама. Честно предупредила заказчика, что получилось как умею, потому что я не дизайнер. Потом подключался фронтенд-разработчик и наводил красоту.
Чтобы все переходы работали плавно, а при переключении настроек страница не перезагружалась, я использовала фреймворк Alpine. Его ещё называют лайт-версией React, потому что он намного проще и легче.
На скрине видно, как выглядела читалка в процессе работы. Это не окончательный вариант. Когда свою часть работы выполнила, я покинула проект. Работа над фронтендом продолжилась без меня.
Как обстоит дело с разными форматами
В прошлой статье я писала, что делаю десктопную читалку на Java для собственных нужд. Это было больше года назад. Сейчас она уже давно готова, и я ей активно пользуюсь. Моя читалка понимает форматы FB2, EPUB, DOCX, PDF и, конечно же, TXT.
При разработке веб-версии опыт работы с форматами электронных книг мне очень пригодился. У меня уже были готовые алгоритмы. Чтобы ускорить процесс, я буквально «переводила» код с одного языка на другой. В некоторых случаях это получалось довольно быстро, а в других были сложности.
При работе над десктопной читалкой я использовала сторонние библиотеки. Но при работе над PHP-версией я не нашла многих решений, которые есть на Java. Поэтому для каких-то форматов я использовала тот же подход, а для других пришлось придумывать новый.
От формата PDF мы, посовещавшись с заказчиком, решили отказаться. Его трудно было привести к нужному виду, сохранив структуру, чтобы картинки и текст шли в правильном порядке.
Для читалки на Java я использовала специальную библиотеку для работы с PDF. Она распознаёт страницы книги как отдельные картинки. При работе над своей читалкой я не ставила задачу вывести текст непременно в две колонки, которые должны умещаться на страницу. Я просто сделала вывод сплошной портянкой, которая листается скроллингом вниз.
Для PHP-версии я не нашла решения, которое позволило бы вытащить текст с картинками из файла PDF и представить их в удобной структуре. Поэтому веб-версия читалки этот формат не поддерживает.
Далее расскажу про работу с каждым форматом электронных книг. Про TXT писать ничего не буду, потому что, думаю, с ним и так всё понятно.
FB2
Статья, которую я написала для Хабра в прошлом году, как раз была об этом формате. Поэтому не буду останавливаться на описании структуры и алгоритма работы с ним. При разработке онлайн-читалки я учла советы в комментариях и немного упростила код.
В частности, я не стала создавать временную папку для хранения текста и ресурсов, извлечённых из файла FB2. Вместо этого я сразу записывала всё в отдельную переменную.
Картинки я оставила в бинарном виде и создала отдельный массив для их сбора. Напомню, что в FB2 изображения хранятся в самом конце XML-файла. Мне нужно было поместить их код в формате Base64 внутрь тегов в тексте.
После я положила текст вместе с картинками, расположенными в правильных местах, в отдельную переменную. Данные из этой переменной как раз и передаются в файл controller.php.
EPUB
В отличие от FB2, текст книги EPUB хранится не в одном файле, а разбит на множество частей. Если распакуете EPUB, вы увидите много файлов и папок. Самые главные среди них — это файлы с расширениями OPF и NCX. В корневом каталоге будет лежать файл XML, содержащий ссылку на OPF.
Файл NCX имеет XML-формат. Он представляет собой содержание книги с названиями глав. В файле OPF, который также имеет XML-формат, находится структура книги. Она заключена между тегами, которые примерно выглядят так:
<spine toc="ncx"></spine>
Теги spine содержат перечисление частей книги, которые представляют собой файлы с текстами в формате HTML. Ссылки на них находятся в атрибутах элементов itemref. Следовательно, нужно вытащить эту структуру и по кусочкам собрать в переменную весь текст. А затем, по аналогии с предыдущим форматом, передать данные в файл плагина controller.php.
Проблема лишь в том, что картинки в EPUB после распаковки представляют собой отдельные файлы JPG, PNG или GIF. Но мне понравилось использовать формат Base64. Поэтому я перекодировала изображения в бинарный формат и вставила их в теги картинок внутри текста.
DOCX
Этот формат также представляет собой архив и успешно распаковывается. Внутри находится несколько папок. Самый важный файл с текстом книги хранится внутри папки word. Его название — document.xml. Картинки лежат в папке media.
Структура у этого формата самая непростая, разобрать и представить её в нужном виде — та ещё задача. У тегов DOCX свой синтаксис, который довольно сложно понять. Например, параграфы текста содержатся между тегами вида <w:t>, а картинки — внутри элементов вида <a:blip>.
Такие теги невозможно обработать с помощью функций PHP для извлечения информации из элементов XML. Поэтому сначала их нужно привести к тому виду, который будут понимать эти функции. А уже потом собирать текст с картинками в переменную для передачи в controller.php.
Итак, алгоритм обработки DOCX такой:
Сначала перебираем все файлы внутри всех папок и складываем их в массив. Картинки сразу переводим в бинарный формат.
Затем в массиве отыскиваем файл document.xml и разбираем его структуру.
Вытаскиваем куски текста из нагромождения тегов DOCX и складываем их в правильном порядке в отдельную переменную.
Каждый кусок текста обрамляем нужными тегами HTML, к картинкам в атрибут src тега img подставляем бинарный код.
Какие ещё были сложности
Кроме обработки картинок и трудностей с форматом DOCX, был ещё один момент, который заставил шевельнуть извилинами. Это вывод текста с картинками, таблицами и прочими элементами так, чтобы он помещался на экран без скроллинга на разных устройствах.
Код для разбиения текста на страницы я поместила в файл controller.php. Он состоит из целого ряда функций: для вычисления размера абзаца, картинки, таблицы и списка. Отдельная функция разбивает книгу на теги, которые помещаются в массив.
Затем текст разбивается на страницы с учётом величины каждого тега, а также ширины, высоты экрана и размера шрифта. Этот кусок кода представляет собой сложный алгоритм, который перебирает каждый тег в массиве, проверяет, помещается ли он, и решает, оставить его на этой странице или переместить на следующую.
Какие ещё возможности мы добавили в читалку
Какое-то время после завершения работы по основному техзаданию заказчик обращался ко мне за доработками. Мы добавили поиск по книге с выводом результатов на отдельной странице внутри читалки. Никаких специальных скриптов для сложного поиска, наподобие Sphinx, я не использовала. Сделала простейший поиск по слову без словоформ.
Ещё мы сделали страницу внутри личного кабинета пользователя, на которой сохранялись книги в процессе чтения. Прямо с этой страницы посетитель мог перейти в читалку по закладке.
Также успешно удалось воплотить все те мелкие опции, которые были в техзадании изначально. В читалке работает изменение размера шрифта и выравнивание текста. Изменение яркости фона на каком-то этапе решено было заменить на темы оформления. Этим уже занимался фронтенд-разработчик.
Содержание удалось добавить не для всех книг, а только для тех, у которых оно предусмотрено форматом. Например, для EPUB. Если у книги есть содержание, то в нём работают ссылки для перехода на соответствующую страницу.
По просьбе заказчика в качестве одной из последних доработок я добавила кнопку для разворачивания полнотекстовой версии книги. При нажатии на неё отменялась разбивка на страницы, и книга отображалась одной портянкой. Возможно, для кого-то это удобно, раз такую функцию попросили внедрить.
Мне кажется, в чём-то этот проект — уникальное решение, где несколько форматов собрано в одной читалке. По крайней мере, я не встречала аналогов. В других онлайн-библиотеках поддерживается какой-либо один или два формата. Кроме того, другие сервисы обычно предъявляют строгие требования по оформлению книг к тем, кто загружает файлы.
Я прекрасно понимаю, с чем связаны эти ограничения. Это нужно, чтобы все книги одинаково хорошо распознавались и отображались в читалке. Многие книги одного и того же формата могут иметь большие различия. Отличается их внутренняя структура, формат картинок и другие мелкие моменты. Все они влияют на корректность отображения в читалке.
Вот такой у меня был интересный опыт участия в проекте по созданию онлайн-читалки. Поскольку это был платный заказ, примерами кода в этот раз не могу поделиться. Поэтому в свой обзор постаралась включить побольше подсказок. Надеюсь, это поможет быстрее влиться в тему тем, кто будет заниматься разработкой чего-то подобного. Буду рада рекомендациям, что можно было еще добавить или сделать круче. Пригодится в работе над улучшением десктопной читалки.
НЛО прилетело и оставило здесь промокод для читателей нашего блога:
— 15% на заказ любого VDS (кроме тарифа Прогрев) — HABRFIRSTVDS
Комментарии (6)
FanatPHP
29.10.2024 12:36Статья вызывает смешанные чувства. С одной стороны, написано красиво и гладко. С другой - как только пытаешься почерпнуть что-то для себя, текст начинает утекать, как песок сквозь пальцы, и остаётся белый шум. Информативность статьи (особенно по сравнению с предыдущей) оставляет желать лучшего. Получилось как в инфоцыганских видео: в шапке "я сделала крутую читалку" а в тексте "ну в общем фронтом занимался другой человек" и "разработка ужасно закрытая, поэтому я могу только делать загадочное лицо и пассы руками. Но ладно, названия файлов так и быть покажу!".
По итогу получилась типичная корпоративная жвачка с накрученным рейтингом. Что, опять же, разительно контрастирует с предыдущей статьёй. Очень жаль, что корпоративная проказа добралась до такого способного автора.
catmoon Автор
29.10.2024 12:36Но ведь в личной переписке сегодня днем вас не интересовали подробности. Наоборот, вы сказали что все это неинтересно, а информацию о форматах можно в интернете найти. Вы просили только убрать тег PHP. Тег решила оставить, чтобы получить рекомендации по теме. В прошлый раз они мне сильно помогли☺
DrZlodberg
29.10.2024 12:36Делал как-то себе читалку fb2 для вэба. Проще всего отдавать xml и кастомный xslt. Бонусом получаешь возможность одним кликом менять стиль и содержание (можно оглавление прикрутить, например). Разве что менял ссылки на картинки так, чтобы они открывались как отдельные файлы (тут, правда, был необходим повторный просмотр файла на каждую). Из оригинального файла их вырезал при отдаче. Бонусом картинки можно просто отключить. Ну и файл отдавался без полной загрузки в память, поскольку нужен один проход и не требуется его хранить в памяти. SAX-парсер прекрасно справлялся.
gsaw
Не легче было бы сделать разбор формата в бэкэнде, на java, и сайту отдавать готовые страницы? Наверняка java библиотек для разных форматов больше, чем для php.
catmoon Автор
Как вариант, но это была коммерческая разработка и не хотелось связываться с лицензиями. В своей десктопной читалке тоже, возможно, откажусь от использования библиотек.