Как-то раз я читала книгу на известном литературном портале и думала, какая же крутая у них читалка. Наверное, над её созданием трудится целая команда. В тот момент я даже не подозревала, что мне вскоре предстоит сделать что-то подобное. Расскажу, как я делала читалку для сайта библиотеки на заказ.

Содержание

Как я получила заказ на онлайн-читалку

Началось всё с Хабра. В прошлом году я написала статью про разработку читалки для формата 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, потому что он намного проще и легче.

На скрине видно, как выглядела читалка в процессе работы. Это не окончательный вариант. Когда свою часть работы выполнила, я покинула проект. Работа над фронтендом продолжилась без меня.

Рис.1. Промежуточный вариант интерфейса читалки
Рис.1. Промежуточный вариант интерфейса читалки

Как обстоит дело с разными форматами

В прошлой статье я писала, что делаю десктопную читалку на 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

Комментарии (4)


  1. gsaw
    29.10.2024 12:36

    Вместо этого я сразу записывала всё в отдельную переменную.

    Не легче было бы сделать разбор формата в бэкэнде, на java, и сайту отдавать готовые страницы? Наверняка java библиотек для разных форматов больше, чем для php.


    1. catmoon Автор
      29.10.2024 12:36

      Как вариант, но это была коммерческая разработка и не хотелось связываться с лицензиями. В своей десктопной читалке тоже, возможно, откажусь от использования библиотек.


  1. FanatPHP
    29.10.2024 12:36

    Статья вызывает смешанные чувства. С одной стороны, написано красиво и гладко. С другой - как только пытаешься почерпнуть что-то для себя, текст начинает утекать, как песок сквозь пальцы, и остаётся белый шум. Информативность статьи (особенно по сравнению с предыдущей) оставляет желать лучшего. Получилось как в инфоцыганских видео: в шапке "я сделала крутую читалку" а в тексте "ну в общем фронтом занимался другой человек" и "разработка ужасно закрытая, поэтому я могу только делать загадочное лицо и пассы руками. Но ладно, названия файлов так и быть покажу!".

    По итогу получилась типичная корпоративная жвачка с накрученным рейтингом. Что, опять же, разительно контрастирует с предыдущей статьёй. Очень жаль, что корпоративная проказа добралась до такого способного автора.


    1. catmoon Автор
      29.10.2024 12:36

      Но ведь в личной переписке сегодня днем вас не интересовали подробности. Наоборот, вы сказали что все это неинтересно, а информацию о форматах можно в интернете найти. Вы просили только убрать тег PHP. Тег решила оставить, чтобы получить рекомендации по теме. В прошлый раз они мне сильно помогли☺