Web developer
10,4
рейтинг
16 марта 2014 в 20:41

Разработка → SpeedReader — Qt библиотека для скорочтения

image

Предисловие


Некоторое время назад на Хабре была новость о Spritz — программной реализации техники скорочтения, основанной на быстрой смене слов в виджете с определенным центрированием самого слова внутри виджета, а чуть позже и другая новость. Так как тема довольно актуальная я, недолго думая, решил реализовать нечто подобное и универсальное, с возможностью встраивания такого виджета для скорочтения в программы на различных платформах (win, linux, mac, android). Исходя из этого условия был выбран Qt фрейморк с его широкой поддержкой различных платформ.

То, что получилось и как с этим работать описано ниже. Кому интересно, добро пожаловать.

Как это использовать?


1. Подключить заголовочные файлы:
#include "SpeedReader/speedreader.h"
#include "SpeedReader/txtreader.h" // или другой наследник TextFormatReader


2. Создать объекты:
TxtReader *pTxtReader = new TxtReader("path_to_file", "UTF-8"); // параметры: путь к файлу и название текстового кодека, в котором сохранен файл
SpeedReader *pSpeedReader = new SpeedReader(pTxtReader);


3. Установить настройки ридера:
pSpeedReader->setReadingSpeed(300); // скорость в словах в минуту
pSpeedReader->setCommaPauseTime(150); // пауза при достижении запятой (в мс)
pSpeedReader->setDotPauseTime(200); // пауза при достижении конца предложения (в мс)
pSpeedReader->setCurrentPosition(0); // позиция начала чтения


4. Положить SpeedReaderLabel виджет на форму (можно положить QLabel, а затем преобразовать в SpeedReaderLabel). Установить цвет подсвечиваемого символа:
ui->speedReaderLabel->setSymbolColor("red"); 


5. Подсоединить сигналы объекта класса SpeedReader к слотам виджета SpeedReaderLabel:
connect(pSpeedReader, SIGNAL(nextWordAvailable(QString, int)), ui->speedReaderLabel, SLOT(processNextWordAvailable(QString, int)));
connect(pSpeedReader, SIGNAL(wordOffset(int)), ui->speedReaderLabel, SLOT(processWordOffset(int)));


6. Старт/стоп чтения:
pSpeedReader->startReading();
pSpeedReader->stopReading();


7. Так же есть возможность перехватить некоторые необязательные сигналы объекта класса SpeedReader, такие как:
SIGNAL(error(SpeedReaderError)) // ошибка
SIGNAL(endOfBook()) // высылается при достижении конца файла
SIGNAL(readingProgress(double)) // прогресс чтения (от 0 до 1)


Немного о том, как это все работает. Реализация ядра библиотеки


Выделив в выходной день несколько часов, сел за продумывание идеи и ее программирование. Была выбрана такая схема: класс для переключения слов (SpeedReader) + базовый абстрактный класс для считывания текстовых файлов в разных форматах (TextFormatReader), от которого необходимо наследовать конкретные «читалки форматов» (например, для примера реализован простейший класс-читалка *.txt формата — TxtReader).

Центральный класс SpeedReader

Основной задачей объектов этого класса является переключение слова с определенной скоростью и вообще, вести себя в соответствии с заданными настройками. Посмотрим на публичный интерфейс класса:
class SpeedReader : public QObject
{
   ...

    void setCurrentPosition(const int ¤tPosition); // установка начальной позиции для чтения
    void setCommaPauseTime(const int &comaPauseTime); // установка паузы при достижении запятой (в мс)
    void setDotPauseTime(const int &dotPauseTime); // установка паузы при достижении конца предложения (в мс)
    void setReadingSpeed(int wordPerMinute); // установка скорости (слов в минуту)
    void setEnableShifting(const bool &enableShifting); // установка сдвига слова (по умолчанию включено)

// соответствующие get методы
    QStringList getWordsToRead() const; // возвращает загруженный из файла список слов
    int getCurrentPosition() const;
    int getComaPauseTime() const;
    int getDotPauseTime() const;
    int getReadingSpeed() const;
    int getWordsCount() const; // возвращает количество слов, загруженных из файла
    bool isEnableShifting() const;

    void startReading(); // инициализация переключения слов
    void stopReading(); // остановка переключения слов

    ...
}


Само переключение слов происходит за счет высылки сигнала объектом класса SpeedReader, содержащим слово для отображения в виджете. Но о виджете позже.

Доступные сигналы SpeedReader для обработки:
signals:
    // сигналы для виджета (в принципе не нужно знать в рамках этой библиотеки для чего они нужны. просто опишу, что они есть)
    void nextWordAvailable(QString, int); // сигнал, высылающий слово и сдвиг - количество символов
    void wordOffset(int); // сигнал, высылающий максимальную длину из всех слов

    // сигналы необязательные для перехвата
    void readingProgress(double); // сигнал, высылающий прогресс прочтенных слов (от 0 до 1)
    void error(SpeedReaderError); // сигнал, высылающий ошибку в случае ее возникновения
    void endOfBook(); // сигнал, сигнализирующий о завершении чтения файла


TextFormatReader — базовый абстрактный класс для реализации чтения различных текстовых форматов

Все, что нужно знать об этом классе, это то, что в нем есть метод
virtual QStringList getWords() = 0;
, который нужно переопределить в классах наследниках. В этом методе как раз и происходит парсинг содержимого текстового формата. Например, в классе TxtReader в этом методе происходит простейший парсинг, а именно замена двух пробелов на один и удаление символов переноса строк.

class TextFormatReader: public QObject
{
    ...
    public:
         TextFormatReader(const QString &fileName, const QString &textCodecName);
         virtual QStringList getWords() = 0; // тот самый метод для переопределения
         void openBook(const QString &filePath);
    ...
};


Собственно, это и есть ядро библиотеки.

Реализация виджета SpeedReaderLabel


Виджет для отображения слов — наследник QLable, на котором рисуются линии разметки (как на рисунке в начале статьи). Основная задача виджета — отображать слова, присланные объектом класса SpeedReader. Давайте посмотрим на публичный интерфейс виджета:

class SpeedReaderLabel : public QLabel
{
    ...

    public slots:
        void processWordOffset(const int &verticalPointerOffset); // помните сигнал nextWordAvailable класса SpeedReader? Этот слот обрабатывает тот сигнал. В этом слоте устанавливается смещение вертикального указателя в виджете (см. картинку вначале поста)
        void processNextWordAvailable(QString w, int shift); // та же история. Слот устанавливает слово в виджет и сдвигает его на shift * ширина символа текущего шрифта

    ...
};


Поддержка других текстовых форматов (fb2, html и других)


Для того, чтобы добавить поддержку других текстовых форматов, необходимо создать класс и унаследовать его от TextFormatReader:

1. Необходимо унаследоваться от TextFormatReader, вызвать конструктор предка и переопределить один метод:
#ifndef SOMEREADER_H
#define SOMEREADER_H
 
#include "textformatreader.h"
 
class SomeReader : public TextFormatReader 
{
     public:
         SomeReader(const QString &fileName, const QString &textCodecName) : TextFormatReader(fileName, textCodecName) {} // вызов конструктора родительского класса
         QStringList getWords(); // этот метод необходимо переопределить
};
 
#endif // SOMEREADER_H


2. Переопределение getWords() метода:
QStringList SomeReader::getWords()
{
    QString textToParse = this->text; // this->text - "сырой" текст из открытого файла
 
    // здесь парсим текст (удаляем все теги и прочее прочее)
   textToParse = textToParse.replace("someTag", "");

   return countWords(textToParse); // необходимо возвратить результат countWords(QString text) метода, передав в него распарсенный текст
}


3. Теперь можно открывать файлы определенного в нашем классе формата:
SomeReader *pSomeReader = new SomeReader("path_to_file", "UTF-8");
SpeedReader *pSpeedReader = new SpeedReader(pSomeReader);
...


Ну а далее все, как в начале статьи в разделе «Как это использовать?».

Заключение


В итоге была разработана небольшая библиотека, которая позволит встраивать данную технологию скорочтения в программы на различных платформах. Позволю себе поместить здесь ссылку на проект в Google Code: SpeedReader on google code. В репозитории две папки: SpeedReader (в ней файлы библиотеки) и SpeedReaderTest (минимальный работающий проект с использованием библиотеки. *.txt файл необходимо поместить в папку с исполняемым скомпилированным файлом программы либо прописать свой путь к файлу). С удовольствием отвечу на все вопросы в комментариях, если возникнут.
Павел @FluffyMan
карма
85,0
рейтинг 10,4
Web developer
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

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

  • +3
    Спасибо за ваш труд, как увидел эту штуку тоже была идея написать себе самопал для тренировок, а у вас прям красивый виджет на Qt вышел.
    PS И вот банальное любопытство — есть ли у них патент на этот метод и насколько легально распространять свои модули с его реализацией?
    • +4
      Тоже интересен ответ на этот вопрос. Может быть знающие люди подскажут здесь. А вообще, данная технология не нова. И ничего нелегального в реализации этой технологии не должно быть. Тем более данная библиотека некоммерческая.
      • +1
        На сайте об этом пишут:
        Spritzing is reading text with Spritz Inc.’s patent-pending technology.
        • +4
          Насколько я помню, «patent pending» означает, что патенты поданы или ожидаются. Это не означает что оно уже запатентовано (не говоря о том, что не указаны страны). Так что строго говоря, подобные надписи равнозначны устрашающим текстам на воротах гаражей вида «не парковать! штраф 100500 рублей».
          • 0
            У них на сайте пишут «patent-pending» — гугл переводит это как «запатентованная». А «patent pending» (без дефиса) гугл переводит как «патент заявлен». Так это разные понятия (с дефисом и без)?
            • +3
              Таких тонкостей, к сожалению, не знаю. Но замечал, что когда изделие или технология действительно запатентованы, то всегда указываются либо номера патентов, либо это однозначно указывается текстом.
          • 0
            держатель патента
        • 0
          Вот такая еще есть статья про Spritz: www.computerra.ru/96220/

          Что же такого грандиозного изобрели в Spritz Technology? Вы не поверите: технологию быстрого последовательного визуального предъявления (Rapid serial visual presentation, RSVP)! Да-да, ту самую, которую профессор Майкл Поттер досконально изучил и описал в далёком 1976 году в своей работе «Краткосрочная концептуальная память на изображения»!

          И вот теперь приходят молодые да ушлые и ничтоже сумняшеся заявляют, что изобрели «новый способ скоростного чтения», не имеющий аналога! Способ, основанный на RSVP, но без аналога: можете себе представить моё недоумение?!

          • +1
            … ребята патентовали все, что только могли запатентовать вокруг технологии RSVP. Я так понял, что эта технология имела несчастье быть открытой и развитой той романтической когортой учёных и энтузиастов, которые жили в те романтические времена, когда патентование каждого своего пука как-то не приходило в голову. В результате практически все наработки в этой области находятся в свободном доступе. Вернее — находились, потому что Spritz Technology, надо полагать, исправила недоразумение и теперь прикрыла всё своими патентами.
      • 0
        Сегодня получили лицензию от Spritz'а, в ближайшем времени реализуем у себя на сайте и выложим пост на Хабр. И посмотрим, насколько удобно читать книги при помощи этой технологии, а то разговоров много, вроде бы интересно, но как оно будет на деле для рядовых пользователей непонятно…
  • +2
    А как центруются слова типа «синхрофазотрон» и прочие?
    Не нашел простого онлайн примера, где бы глянуть свой текст, старый добрый «С точки зрения банальной эрудиции»
    • 0
      Алгоритм центрирования простой. Количество символов самого длинного слова в тексте минус треть длины текущего слова. А вертикальная линия находится над символом в позиции «количество символов самого длинного слова в открытом тексте». Красный символ там же.
      • +1
        хм. а не будет ли эффективнее для чтения подсвечивать букву-ударение?
        • 0
          Не думаю. Идея (не моя) подсвечивания символа в том, что мол глазу видно где оптимальная точка концентрации внимания, чтобы распознать слово максимально быстро. Если честно, достаточно вертикального указателя, как по мне. Хотя спорно. Может оно работает на высоких скоростях чтения. Для меня более 300 уже некомфортно
          • –2
            хмхммм… я вполне комфортно читаю 500 спритцем, но при этом слоги приходится «додумывать» — я их просто не вижу вообще.
            может их и нет? но понимать совершенно не мешает
          • –2
            кстати, если говорить о спритце, то они длинные слова разбивают на два с переносом на больших скрорстях («продол-» и "-жительная"). почему и спрашиваю про синхрофазотрон :)

            и, опять же, опечатки на больших скоростях вообще сбивают…
            в тексте на 550 есть опечатка «к сожаления нет»
            • –2
              Ого, еще веселее — они его на три разбили куска:
              1372,0,«продол-»,1396,0,«житель-»,1420,30,«ное»
              2584,0,«соответ-»,2612,0,«ствую-»,2632,30,«щей»

              однако слово «распознавания» оставили одним словом.

              интересно, где логика?
              • 0
                Наверное, просто лимит — какое-то количество символов. А может все намного сложнее. Скорее второе, так как у них поддержка только нескольких языков, а это может быть обусловлено только тем, что по разному переносят части слов в разных языках.
          • НЛО прилетело и опубликовало эту надпись здесь
            • 0
              а чем читали?
              • НЛО прилетело и опубликовало эту надпись здесь
  • +1
    Возвращение QStringList по значению хоть и оптимизируется в Qt'e с помощью Implicit Sharing'a, но выглядит сомнительным подходом к дизайну класса. Может быть, стоит реализовать в этом классе итератор или что-нибудь подобное?

    P.S. Добавьте немного const!
    • 0
      Но там необходимо возвратить именно список строк — это и есть список слов для чтения. Этот метод используется внутри объекта класса SpeedReader.

      По поводу Вашего p.s. — это ирония или Вы о том, что возвращать нужно const переменные?
      • +1
        Никакой иронии, вот этот метод
        int getCurrentPosition();
        

        очень даже ничего бы смотрелся вот так
        int getCurrentPosition() const;
        

        И в таком же духе другие места. Это пожелание по стилю программирования, поэтому и в «P.S.»
        • +1
          Понял, спасибо.
  • +2
    Кто бы хорошую читалку под андроид на базе чего-то подобного запилил. Чтоб с поддержкой fb2 и всякими фичами, типа «на секунду назад», читаю с скоростью 500+ и часто бывают проблемы с именами и т.п., слова воспринимаются сразу целиком, прочитать слово по буквам нереально и когда появляется новое имя или название это всегда проблема. Читаю уже третью книгу в серии до сих пор не знаю как зовут некоторых действующих лиц, про себя называю Ланселот, потому что первые буквы вроде лн или лан, но подозреваю, что совсем не угадал.
    Было бы здорово чтоб была кнопка, которая бы отматывала назад на секунду и эту секунду проходила с уменьшенной скоростью, потом возвращаясь к нормальной скорости.
    • +1
      Вашу идею вполне можно реализовать на базе представленной в статье библиотеке. В ней есть весь необходимый функционал для этого. Разрабатывал ее с мыслью, чтобы можно было такие фичи делать. То есть это просто «кирпичик», который можно заложить" в какой-то крупный проект.
    • НЛО прилетело и опубликовало эту надпись здесь
  • +1
    Мы тоже подумывали реализовать что-то похожее, но большие вопросы касаемо того, будут ли посетители этим пользоваться… Интересно мнение обитателей Хабра. Вы бы стали читать книгу (худ.литературу в частности) при помощи данной технологии?
    • +1
      По моему для отдельного веб-сайта это не критично, кому надо используют расширения для браузера, если только вы не сделаете лучше, чем эти расширения, но это может быть сложно с тех. точки зрения.
    • НЛО прилетело и опубликовало эту надпись здесь
  • +1
    Расширения браузера для чтения книг...? Поделитесь ссылкой, если не трудно… А то не очень понимаю, о чем Вы…
    • +1
      вроде тут есть
  • –2
    Не нравится, что эту технику невозможно применить для чтения обычных книг, имея перед глазами только текст. Кто-нибудь знает иную простую технику скорочтения, не требующую долгих тренировок?
  • +1
    Можно было бы регулировать скорость не в словах, а в символах, т.е. время показа слова будет зависеть от его длины в символах. Потому что длинные слова и, особенно, имена и названия, довольно сложно разобрать даже на невысокой скорости. Также можно было бы дополнительно увеличивать время для имен собственных (с заглавной буквы в середине предложения).
    • 0
      В таком случае невозможно будет задать скорость чтения в словах в минуту, так как она будет зависеть от длин слов.
      • 0
        Разумеется, но так ли это важно для конечного пользователя? Он ведь сможет регулировать скорость в символах в минуту, и эта скорость будет лучше соотносится с его возможностями, чем скорость в словах в минуту. Я вот могу спокойно читать где-то на 350 словах в минуту, но длинные слова и названия прочесть нереально, придется либо снижать скорость до 250 и застревать на всяких односложных предлогах, либо смириться с тем, что запомнил название только по его длине и первой букве ;)
        • 0
          Знакомые слова воспринимаются целиком, не зависимо от их длины, прыгающая от слова к слову скорость будет только раздражать.
          Имена и названия тема отдельная, не факт, что эту проблему можно решить автоматически.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.