From 1bab5562c2498b6273916e2d0271a9deca1a97b1 Mon Sep 17 00:00:00 2001 From: Franklin Wei Date: Sat, 11 Mar 2017 17:00:47 -0500 Subject: [PATCH] Speed-reading plugin Partially based on `spread0r', an open-source ebook reader: https://github.com/xypiie/spread0r Similar to Spritz(TM): http://spritzinc.com Change-Id: I6aa54addd1910a83a266aea561406b6268449b67 --- apps/plugins/CATEGORIES | 1 + apps/plugins/SOURCES | 2 + apps/plugins/speedread.c | 732 ++++++++++++++++++ apps/plugins/viewers.config | 1 + .../images/ss-speedread-128x128x16.png | Bin 0 -> 547 bytes .../plugins/images/ss-speedread-128x128x2.png | Bin 0 -> 1324 bytes .../images/ss-speedread-128x160x16.png | Bin 0 -> 605 bytes .../plugins/images/ss-speedread-128x96x16.png | Bin 0 -> 485 bytes .../plugins/images/ss-speedread-128x96x2.png | Bin 0 -> 1319 bytes .../plugins/images/ss-speedread-132x80x16.png | Bin 0 -> 469 bytes .../plugins/images/ss-speedread-138x110x2.png | Bin 0 -> 1329 bytes .../images/ss-speedread-160x128x16.png | Bin 0 -> 626 bytes .../plugins/images/ss-speedread-160x128x2.png | Bin 0 -> 1342 bytes .../images/ss-speedread-176x132x16.png | Bin 0 -> 657 bytes .../images/ss-speedread-176x220x16.png | Bin 0 -> 844 bytes .../images/ss-speedread-220x176x16.png | Bin 0 -> 866 bytes .../images/ss-speedread-240x320x16.png | Bin 0 -> 1226 bytes .../images/ss-speedread-240x400x16.png | Bin 0 -> 1436 bytes .../images/ss-speedread-320x240x16.png | Bin 0 -> 1079 bytes .../images/ss-speedread-320x240x24.png | Bin 0 -> 1233 bytes .../images/ss-speedread-640x480x16.png | Bin 0 -> 5958 bytes .../plugins/images/ss-speedread-96x96x16.png | Bin 0 -> 452 bytes manual/plugins/main.tex | 2 + manual/plugins/speedread.tex | 7 + 24 files changed, 745 insertions(+) create mode 100644 apps/plugins/speedread.c create mode 100644 manual/plugins/images/ss-speedread-128x128x16.png create mode 100644 manual/plugins/images/ss-speedread-128x128x2.png create mode 100644 manual/plugins/images/ss-speedread-128x160x16.png create mode 100644 manual/plugins/images/ss-speedread-128x96x16.png create mode 100644 manual/plugins/images/ss-speedread-128x96x2.png create mode 100644 manual/plugins/images/ss-speedread-132x80x16.png create mode 100644 manual/plugins/images/ss-speedread-138x110x2.png create mode 100644 manual/plugins/images/ss-speedread-160x128x16.png create mode 100644 manual/plugins/images/ss-speedread-160x128x2.png create mode 100644 manual/plugins/images/ss-speedread-176x132x16.png create mode 100644 manual/plugins/images/ss-speedread-176x220x16.png create mode 100644 manual/plugins/images/ss-speedread-220x176x16.png create mode 100644 manual/plugins/images/ss-speedread-240x320x16.png create mode 100644 manual/plugins/images/ss-speedread-240x400x16.png create mode 100644 manual/plugins/images/ss-speedread-320x240x16.png create mode 100644 manual/plugins/images/ss-speedread-320x240x24.png create mode 100644 manual/plugins/images/ss-speedread-640x480x16.png create mode 100644 manual/plugins/images/ss-speedread-96x96x16.png create mode 100644 manual/plugins/speedread.tex diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index 3fa17432f0..6308065828 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES @@ -148,6 +148,7 @@ solitaire,games sort,viewers spacerocks,games splitedit,apps +spritz,viewers star,games starfield,demos stats,apps diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index a4e372fba9..a02b9cef69 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES @@ -145,6 +145,7 @@ metronome.c #ifdef HAVE_LCD_BITMAP /* Not for the Archos Player */ 2048.c + /* Lua needs at least 160 KB to work in */ #if PLUGIN_BUFFER_SIZE >= 0x80000 boomshine.lua @@ -179,6 +180,7 @@ snake.c snake2.c solitaire.c sokoban.c +speedread.c star.c starfield.c vu_meter.c diff --git a/apps/plugins/speedread.c b/apps/plugins/speedread.c new file mode 100644 index 0000000000..c80839e4a9 --- /dev/null +++ b/apps/plugins/speedread.c @@ -0,0 +1,732 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2017 Franklin Wei + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* ideas for improvement: + * hyphenation of long words */ + +#include "plugin.h" + +#include "fixedpoint.h" + +#include "lib/helper.h" +#include "lib/pluginlib_actions.h" +#include "lib/pluginlib_exit.h" + +#define LINE_LEN 1024 +#define WORD_MAX 64 + +#define MIN_WPM 100 +#define MAX_WPM 1000 +#define DEF_WPM 250 +#define WPM_INCREMENT 25 + +/* mininum bytes to skip when seeking */ +#define SEEK_INTERVAL 100 + +#define FOCUS_X (7 * LCD_WIDTH / 20) +#define FOCUS_Y 0 + +#define FRAME_COLOR LCD_BLACK +#define BACKGROUND_COLOR LCD_WHITE /* inside frame */ + +#ifdef HAVE_LCD_COLOR +#define WORD_COLOR LCD_RGBPACK(48,48,48) +#define FOCUS_COLOR LCD_RGBPACK(204,0,0) +#define OUTSIDE_COLOR LCD_RGBPACK(128,128,128) +#define BAR_COLOR LCD_RGBPACK(230,230,230) +#else +#define WORD_COLOR LCD_BLACK +#define OUTSIDE_COLOR BACKGROUND_COLOR +#endif + +#define BOOKMARK_FILE VIEWERS_DATA_DIR "/speedread.dat" +#define CONFIG_FILE VIEWERS_DATA_DIR "/speedread.cfg" + +#define ANIM_TIME (75 * HZ / 100) + +int fd = -1; /* -1 = prescripted demo */ + +int word_num; /* which word on a line */ +off_t line_offs, begin_offs; /* offsets from the "real" beginning of the file to the current line and end of BOM */ + +int line_len = -1, custom_font = FONT_UI; + +const char *last_word = NULL; + +static const char *get_next_word(void) +{ + if(fd >= 0) + { + static char line_buf[LINE_LEN]; + static char *end = NULL; + + next_line: + + if(line_len < 0) + { + line_offs = rb->lseek(fd, 0, SEEK_CUR); + line_len = rb->read_line(fd, line_buf, LINE_LEN); + if(line_len <= 0) + return NULL; + + char *word = rb->strtok_r(line_buf, " ", &end); + + word_num = 0; + + if(!word) + goto next_line; + else + { + last_word = word; + return word; + } + } + + char *word = rb->strtok_r(NULL, " ", &end); + if(!word) + { + /* end of line */ + line_len = -1; + goto next_line; + } + ++word_num; + + last_word = word; + return word; + } + else + { + /* feed the user a quick demo */ + static const char *words[] = { "This", "plugin", "is", "for", "speed-reading", "plain", "text", "files.", + "Please", "open", "a", "plain", "text", "file", "to", "read", "by", "using", "the", "context", "menu.", + "Have", "a", "nice", "day!" }; + static unsigned idx = 0; + if(idx + 1 > ARRAYLEN(words)) + return NULL; + last_word = words[idx++]; + return last_word; + } +} + +static const char *get_last_word(void) +{ + if(last_word) + return last_word; + else + { + last_word = get_next_word(); + return last_word; + } +} + +static void cleanup(void) +{ + if(custom_font != FONT_UI) + rb->font_unload(custom_font); + backlight_use_settings(); +} + +/* returns height of drawn area */ +static int reset_drawing(long proportion) /* 16.16 fixed point, goes from 0 --> 1 over time */ +{ + int h = -1, w; + if(h < 0) + rb->lcd_getstringsize("X", &w, &h); + + /* clear word area */ + rb->lcd_set_foreground(BACKGROUND_COLOR); + rb->lcd_fillrect(0, 0, LCD_WIDTH, h * 3 / 2); + + if(proportion) + { +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(BAR_COLOR); +#endif + rb->lcd_fillrect(fp_mul(proportion, FOCUS_X << 16, 16) >> 16, FOCUS_Y, fp_mul((1 << 16) - proportion, LCD_WIDTH << 16, 16) >> 16, h * 3 / 2); + } + + rb->lcd_set_foreground(FRAME_COLOR); + + /* draw frame */ + rb->lcd_fillrect(0, h * 3 / 2, LCD_WIDTH, w / 2); + rb->lcd_fillrect(FOCUS_X - w / 4, FOCUS_Y + h * 5 / 4, w / 2, h / 2); + + rb->lcd_set_foreground(WORD_COLOR); + + return h * 3 / 2 + w / 2; +} + +static void render_word(const char *word, int focus) +{ + /* focus char first */ + char buf[5] = { 0, 0, 0, 0, 0 }; + int idx = rb->utf8seek(word, focus); + rb->memcpy(buf, word + idx, MIN(rb->utf8seek(word, focus + 1) - idx, 4)); + + int focus_w; + rb->lcd_getstringsize(buf, &focus_w, NULL); + +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(FOCUS_COLOR); +#endif + + rb->lcd_putsxy(FOCUS_X - focus_w / 2, FOCUS_Y, buf); + +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(WORD_COLOR); +#endif + + /* figure out how far left to shift */ + static char half[WORD_MAX]; + rb->strlcpy(half, word, rb->utf8seek(word, focus + 1)); + int w; + rb->lcd_getstringsize(half, &w, NULL); + + int x = FOCUS_X - focus_w / 2 - w; + + /* first half */ + rb->lcd_putsxy(x, FOCUS_Y, half); + + /* second half */ + x = FOCUS_X + focus_w / 2; + rb->lcd_putsxy(x, FOCUS_Y, word + rb->utf8seek(word, focus + 1)); +} + +static int calculate_focus(const char *word) +{ +#if 0 + int len = rb->utf8length(word); + int focus = -1; + for(int i = len / 5; i < len / 2; ++i) + { + switch(tolower(word[rb->utf8seek(word, i)])) + { + case 'a': case 'e': case 'i': case 'o': case 'u': + focus = i; + break; + default: + break; + } + } + + if(focus < 0) + focus = len / 2; + return focus; +#else + int len = rb->utf8length(word); + if(rb->utf8length(word) > 13) + return 4; + else + { + int tab[] = {0,1,1,1,1,2,2,2,2,3,3,3,3}; + return tab[len - 1]; + } +#endif +} + +static int calculate_delay(const char *word, int wpm) +{ + long base = 60 * HZ / wpm; + long timeout = base; + int len = rb->utf8length(word); + + if(len > 6) + timeout += base / 5 * (len - 6); + + if(rb->strchr(word, ',') || rb->strchr(word, '-')) + timeout += base / 2; + + if(rb->strchr(word, '.') || rb->strchr(word, '!') || rb->strchr(word, '?') || rb->strchr(word, ';')) + timeout += 3 * base / 2; + return timeout; +} + +static long render_screen(const char *word, int wpm) +{ + /* significant inspiration taken from spread0r */ + long timeout = calculate_delay(word, wpm); + int focus = calculate_focus(word); + + rb->lcd_setfont(custom_font); + + int h = reset_drawing(0); + + render_word(word, focus); + + rb->lcd_setfont(FONT_UI); + + rb->lcd_update_rect(0, 0, LCD_WIDTH, h); + return timeout; +} + +static void begin_anim(void) +{ + long start = *rb->current_tick; + long end = start + ANIM_TIME; + + const char *word = get_last_word(); + + int focus = calculate_focus(word); + + rb->lcd_setfont(custom_font); + + while(*rb->current_tick < end) + { + int h = reset_drawing(fp_div((*rb->current_tick - start) << 16, ANIM_TIME << 16, 16)); + + render_word(word, focus); + rb->lcd_update_rect(0, 0, LCD_WIDTH, h); + } + + rb->lcd_setfont(FONT_UI); +} + +static void init_drawing(void) +{ + backlight_ignore_timeout(); + atexit(cleanup); + + rb->lcd_set_background(OUTSIDE_COLOR); + rb->lcd_set_backdrop(NULL); + rb->lcd_set_drawmode(DRMODE_FG); + rb->lcd_clear_display(); + + rb->lcd_update(); +} + +enum { NOTHING = 0, SLOWER, FASTER, FFWD, BACK, PAUSE, QUIT }; + +static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; + +static int get_useraction(void) +{ + int button = pluginlib_getaction(0, plugin_contexts, ARRAYLEN(plugin_contexts)); + + switch(button) + { +#ifdef HAVE_SCROLLWHEEL + case PLA_SCROLL_FWD: + case PLA_SCROLL_FWD_REPEAT: +#else + case PLA_UP: +#endif + return FASTER; +#ifdef HAVE_SCROLLWHEEL + case PLA_SCROLL_BACK: + case PLA_SCROLL_BACK_REPEAT: +#else + case PLA_DOWN: +#endif + return SLOWER; + case PLA_SELECT: + return PAUSE; + case PLA_CANCEL: + return QUIT; + case PLA_LEFT_REPEAT: + case PLA_LEFT: + return BACK; + case PLA_RIGHT_REPEAT: + case PLA_RIGHT: + return FFWD; + default: + exit_on_usb(button); /* handle poweroff and USB events */ + return 0; + } +} + +static void save_bookmark(const char *fname, int wpm) +{ + if(!fname) + return; + rb->splash(0, "Saving..."); + /* copy every line except the one to be changed */ + int bookmark_fd = rb->open(BOOKMARK_FILE, O_RDONLY); + int tmp_fd = rb->open(BOOKMARK_FILE ".tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666); + if(bookmark_fd >= 0) + { + while(1) + { + /* space for the filename, 3, integers, and a null */ + static char line[MAX_PATH + 1 + 10 + 1 + 10 + 1 + 10 + 1]; + int len = rb->read_line(bookmark_fd, line, sizeof(line)); + if(len <= 0) + break; + + char *end; + rb->strtok_r(line, " ", &end); + rb->strtok_r(NULL, " ", &end); + rb->strtok_r(NULL, " ", &end); + char *bookmark_name = rb->strtok_r(NULL, "", &end); + + if(!bookmark_name) + continue; /* avoid crash */ + if(rb->strcmp(fname, bookmark_name)) + { + /* go back and clean up after strtok */ + for(int i = 0; i < len - 1; ++i) + if(!line[i]) + line[i] = ' '; + + rb->write(tmp_fd, line, len); + rb->fdprintf(tmp_fd, "\n"); + } + } + rb->close(bookmark_fd); + } + rb->fdprintf(tmp_fd, "%ld %d %d %s\n", line_offs, word_num, wpm, fname); + rb->close(tmp_fd); + rb->rename(BOOKMARK_FILE ".tmp", BOOKMARK_FILE); +} + +static bool load_bookmark(const char *fname, int *wpm) +{ + int bookmark_fd = rb->open(BOOKMARK_FILE, O_RDONLY); + if(bookmark_fd >= 0) + { + while(1) + { + /* space for the filename, 2 integers, and a null */ + char line[MAX_PATH + 1 + 10 + 1 + 10 + 1]; + int len = rb->read_line(bookmark_fd, line, sizeof(line)); + if(len <= 0) + break; + + char *end; + char *tok = rb->strtok_r(line, " ", &end); + if(!tok) + continue; + off_t offs = rb->atoi(tok); + + tok = rb->strtok_r(NULL, " ", &end); + if(!tok) + continue; + int word = rb->atoi(tok); + + tok = rb->strtok_r(NULL, " ", &end); + if(!tok) + continue; + *wpm = rb->atoi(tok); + if(*wpm < MIN_WPM) + *wpm = MIN_WPM; + if(*wpm > MAX_WPM) + *wpm = MAX_WPM; + + char *bookmark_name = rb->strtok_r(NULL, "", &end); + + if(!bookmark_name) + continue; + + if(!rb->strcmp(fname, bookmark_name)) + { + rb->lseek(fd, offs, SEEK_SET); + for(int i = 0; i < word; ++i) + get_next_word(); + rb->close(bookmark_fd); + return true; + } + } + rb->close(bookmark_fd); + } + return false; +} + +static void new_font(const char *path) +{ + if(custom_font != FONT_UI) + rb->font_unload(custom_font); + custom_font = rb->font_load(path); + if(custom_font < 0) + custom_font = FONT_UI; +} + +static void save_font(const char *path) +{ + int font_fd = rb->open(CONFIG_FILE, O_WRONLY | O_TRUNC | O_CREAT, 0666); + rb->write(font_fd, path, rb->strlen(path)); + rb->close(font_fd); +} + +static char font_buf[MAX_PATH + 1]; + +static void load_font(void) +{ + int font_fd = rb->open(CONFIG_FILE, O_RDONLY); + if(font_fd < 0) + return; + int len = rb->read(font_fd, font_buf, MAX_PATH); + font_buf[len] = '\0'; + rb->close(font_fd); + new_font(font_buf); +} + +static void font_menu(void) +{ + /* taken from text_viewer */ + struct browse_context browse; + char font[MAX_PATH], name[MAX_FILENAME+10]; + + rb->snprintf(name, sizeof(name), "%s.fnt", rb->global_settings->font_file); + rb->browse_context_init(&browse, SHOW_FONT, + BROWSE_SELECTONLY|BROWSE_NO_CONTEXT_MENU, + "Font", Icon_Menu_setting, FONT_DIR, name); + + browse.buf = font; + browse.bufsize = sizeof(font); + + rb->rockbox_browse(&browse); + + if (browse.flags & BROWSE_SELECTED) + { + new_font(font); + save_font(font); + } +} + +static bool confirm_restart(void) +{ + const struct text_message prompt = { (const char*[]) {"Are you sure?", "This will erase your current position."}, 2}; + enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL); + if(response == YESNO_NO) + return false; + else + return true; +} + +static int config_menu(void) +{ + MENUITEM_STRINGLIST(menu, "Speedread Menu", NULL, + "Resume Reading", + "Restart from Beginning", + "Change Font", + "Quit"); + int rc = 0; + int sel = 0; + while(!rc) + { + switch(rb->do_menu(&menu, &sel, NULL, false)) + { + case 0: + rc = 1; + break; + case 1: + if(fd >= 0 && confirm_restart()) + { + rb->lseek(fd, begin_offs, SEEK_SET); + line_len = -1; + get_next_word(); + rc = 1; + } + break; + case 2: + font_menu(); + break; + case 3: + rc = 2; + break; + default: + break; + } + } + return rc - 1; +} + +enum { SKIP = -1, FINISH = -2 }; + +static int poll_input(int *wpm, long *clear, const char *fname, off_t file_size) +{ + switch(get_useraction()) + { + case FASTER: + if(*wpm + WPM_INCREMENT <= MAX_WPM) + *wpm += WPM_INCREMENT; + rb->splashf(0, "%d wpm", *wpm); + *clear = *rb->current_tick + HZ; + break; + case SLOWER: + if(*wpm - WPM_INCREMENT >= MIN_WPM) + *wpm -= WPM_INCREMENT; + rb->splashf(0, "%d wpm", *wpm); + *clear = *rb->current_tick + HZ; + break; + case FFWD: + if(fd >= 0) + { + off_t base_offs = rb->lseek(fd, 0, SEEK_CUR); + off_t offs = 0; + + do { + offs += SEEK_INTERVAL; + if(offs >= 1000 * SEEK_INTERVAL) + offs += 199 * SEEK_INTERVAL; + else if(offs >= 100 * SEEK_INTERVAL) + offs += 99 * SEEK_INTERVAL; + else if(offs >= 10 * SEEK_INTERVAL) + offs += 9 * SEEK_INTERVAL; + rb->splashf(0, "%ld/%ld bytes", offs + base_offs, file_size); + rb->sleep(HZ/20); + } while(get_useraction() == FFWD && offs + base_offs < file_size && offs + base_offs >= 0); + + *clear = *rb->current_tick + HZ; + + rb->lseek(fd, offs, SEEK_CUR); + + /* discard the next word (or more likely, portion of a word) */ + line_len = -1; + get_next_word(); + + return SKIP; + } + break; + case BACK: + if(fd >= 0) + { + off_t base_offs = rb->lseek(fd, 0, SEEK_CUR); + off_t offs = 0; + + do { + offs -= SEEK_INTERVAL; + if(offs <= -1000 * SEEK_INTERVAL) + offs -= 199 * SEEK_INTERVAL; + else if(offs <= -100 * SEEK_INTERVAL) + offs -= 99 * SEEK_INTERVAL; + else if(offs <= -10 * SEEK_INTERVAL) + offs -= 9 * SEEK_INTERVAL; + rb->splashf(0, "%ld/%ld bytes", offs + base_offs, file_size); + rb->sleep(HZ/20); + } while(get_useraction() == FFWD && offs + base_offs < file_size && offs + base_offs >= 0); + + *clear = *rb->current_tick + HZ; + + rb->lseek(fd, offs, SEEK_CUR); + + /* discard the next word (or more likely, portion of a word) */ + line_len = -1; + get_next_word(); + + return SKIP; + } + break; + case PAUSE: + case QUIT: + if(config_menu()) + { + save_bookmark(fname, *wpm); + return FINISH; + } + else + { + init_drawing(); + begin_anim(); + } + break; + case NOTHING: + default: + break; + } + return 0; +} + +enum plugin_status plugin_start(const void *param) +{ + const char *fname = param; + + off_t file_size = 0; + + load_font(); + + bool loaded = false; + + int wpm = DEF_WPM; + + if(fname) + { + fd = rb->open_utf8(fname, O_RDONLY); + + begin_offs = rb->lseek(fd, 0, SEEK_CUR); /* skip BOM */ + file_size = rb->lseek(fd, 0, SEEK_END); + rb->lseek(fd, begin_offs, SEEK_SET); + + loaded = load_bookmark(fname, &wpm); + } + + init_drawing(); + + long clear = -1; + if(loaded) + { + rb->splash(0, "Loaded bookmark."); + clear = *rb->current_tick + HZ; + } + + begin_anim(); + + /* main loop */ + while(1) + { + switch(poll_input(&wpm, &clear, fname, file_size)) + { + case SKIP: + continue; + case FINISH: + goto done; + default: + break; + } + + const char *word = get_next_word(); + if(!word) + break; + bool want_full_update = false; + if(TIME_AFTER(*rb->current_tick, clear) && clear != -1) + { + clear = -1; + rb->lcd_clear_display(); + want_full_update = true; + } + long interval = render_screen(word, wpm); + + long frame_done = *rb->current_tick + interval; + + if(want_full_update) + rb->lcd_update(); + + while(!TIME_AFTER(*rb->current_tick, frame_done)) + { + switch(poll_input(&wpm, &clear, fname, file_size)) + { + case SKIP: + goto next_word; + case FINISH: + goto done; + default: + break; + } + rb->yield(); + } + next_word: + ; + } + +done: + rb->close(fd); + + return PLUGIN_OK; +} diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config index 9eca2dab1a..c938eeb275 100644 --- a/apps/plugins/viewers.config +++ b/apps/plugins/viewers.config @@ -2,6 +2,7 @@ ch8,viewers/chip8,0 txt,viewers/text_viewer,1 txt,apps/text_editor,2 txt,viewers/sort,- +txt,viewers/speedread,1 nfo,viewers/text_viewer,1 bmp,viewers/imageviewer,2 bmp,apps/rockpaint,11 diff --git a/manual/plugins/images/ss-speedread-128x128x16.png b/manual/plugins/images/ss-speedread-128x128x16.png new file mode 100644 index 0000000000000000000000000000000000000000..5e0603175db808ac85e9d683e47285c759b74d90 GIT binary patch literal 547 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRdwroCO|{#XuSa7#UhB#ep2gtDY{7 zAr*7p-U{@)9U##XsJ}92V^Zzipn#*?O2_^s`y9(Sx4g1gD(`*ZN{`)lL~=dl7uYg9 zlw+v)`T0}f@;#ppOrK}}OO%m=T|uUyfZ-5`5hL{A{-&B+Pk5jAoAs5*Zs0la_iDaz z4ST}ZSL+W8Fx=SpH?;XM!|v_!<`#Sg_m7*;yz_7GLBoc_?KNqM4G$LwGsso_+A@!8 z!|%QSR*TCp%((MjlqZQ{bv)C7u+NYA{Nej4m>IDxb4Ysd-naqJAWGJRsP=2T_>~m zciU(8)$Kd4yuGjYmEY*F`5gOO7MDcV{j>dVtOxfE4n``&w7p8pyI2t3hAP}%EjmfX z6NuEm{rY~jiJ4(RH3LHlKSRSiMurQv3=F&2863VdFuagsV7N;o$!cbvZR>>POtzVU OqT18d&t;ucLK6Txm(*ea literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-speedread-128x128x2.png b/manual/plugins/images/ss-speedread-128x128x2.png new file mode 100644 index 0000000000000000000000000000000000000000..48402555b82a2be967bf47c35973cf7f9b8c4e0b GIT binary patch literal 1324 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&aTa()76WMzU}R{i6bEvc83KGl zT${H13a?nMV-o_A93@9XU_^yL0l(T8P@X6W@(cbC1S87xXr8TKw7(1}bj;JmF{EP7 z+iR!!niT{b0w@3cU!S)tbAsd>=M8%2A{$jJCQFsv-Z8yG>dd7>e;pba7*8`Roh@=W z;`n)ILxm)}%l?cchcC0m87GSUl@iEY{mVRD$Uz~OAeGH3E zt?YFBuZ+fa9t;h?XMNnL2(mrD{2wFVdQ I&MBb@08-F%G5`Po literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-speedread-128x160x16.png b/manual/plugins/images/ss-speedread-128x160x16.png new file mode 100644 index 0000000000000000000000000000000000000000..273482238e2c091a0514dea0e5dc535e2872d9f5 GIT binary patch literal 605 zcmeAS@N?(olHy`uVBq!ia0vp^4M4nrg9%7_fB3u#NO2Z;L>2>S5MX3zsT2otnD{+i z978JRyuB6ZcRN6$B~X85&c>wLyFmd*xs{InOZGXIac+5Ku~gpsz?B}m?}+4j$}g~G zcqqqE@$>Vi!sUBD9hg4P{+B2t2fKnyLjl7f5FFl!k=?*^;P2IZ z;~Msaudmi07GSus?{8@HVTRq?<;^Yl4DKH{pLyrs-h+k>hudq?5*r>a4rY+6`n6>q z*M{GF|E(66VVH5}y(mu-!|HgZ17V*Z^ZjQz^Iz(R+JO%%KCiYg1bT(XTKvJnV5T4U z-?RRBwdM2ui|42Q5i9x6?f=J;;lMBLzY5=08XS01+;Q8J;r8tNhj;!o(5w8tox4tE z@9(zH?yK8(UU_?8?<>F2Ve>imw=6D+uKQ>E-&has8yt*OhG~11mUporybV>jy;^jV ziYE}MfBW_QY7;ZVf@%ha5`Kn;cZ>`dY#A7Ku`@V)XJB|C$G~uR&`DM^v*`JE<*0XP PgA$3StDnm{r-UW|72o6A literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-speedread-128x96x16.png b/manual/plugins/images/ss-speedread-128x96x16.png new file mode 100644 index 0000000000000000000000000000000000000000..978535b4bfc1c8b419423fadb75d3365b9117532 GIT binary patch literal 485 zcmeAS@N?(olHy`uVBq!ia0vp^4M3d0!2~3uB;LvaDb50q$YLN30*nkTmEu4S<8)6K z$B>FSZ*K+W9WoGL2$WwLv++^Q-k^Y^{7T2}NlZ4pCHZ##7@`KB<# zJB9;KpMSClUMExW^2eoc6^1zs1xyDx8Pq`xl>_mb{=z#GYgTStT6#dD;qS%mlJ^)_ z$o;kAVQ~2UWi{JjhP#)K^B!Y-^tod4iI?wrk1@2FSEd;_6l8K8Sa|iamFR-sasOX6 zF*cm|xh|oBF(lrpA?)K@wtA*V``PZXaO9cRi3kfjFfcZqWwqGi&G_$kTtoQo-g@`> z-8I~|>K`6C&&YIT{g>t&!3GXbERUFRF0lPrJYzQh;}e(fi`B^7-3xTtu9-{y>Z4!r z8y%EBHeD(@>fwv`Kj#-B`~de13gbA#l(-Y?Ca^RJGx#xBFg}1YcDW~}L4euD*YB?~ zx-&2q^)oO%kz-^zVavoZp_)ZN;X8+d!#hEThC9j)3_A%bc+S>Y%CmZ2-91TAWO=&! KxvX)3=qBuB~75ExM*P{6PD1(YXBg8YL21Hp*$Jep_g7wsE&6D9$R@oRj41*PcQm*!ytxtgis5anI7@QF?cE{@ zn`e6d6Q0T7!NkJ-o%xDg%irGntb1w&cp1(rN7RV0ZH5bX)cH^R zP*7A-T2y=b#|@C%8`fL@R|PQ`Ce&;Y7KDJi?^*VGNw3%6o}dMi@pScbS?83{1OU*x BYQ6vf literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-speedread-132x80x16.png b/manual/plugins/images/ss-speedread-132x80x16.png new file mode 100644 index 0000000000000000000000000000000000000000..7c3d1461fffbdd22a3351fd1046a8151038e5f7f GIT binary patch literal 469 zcmeAS@N?(olHy`uVBq!ia0vp^EkGQ=!2~43JH1W zTYdT1{>7V5|2N(G|4{3E<0CKLe{qgCWpR5Fd14#KV%x>#$+>nKOFVLJxWBl|eQf%w zc1Ew91NM5X`e2tH{xJQ2Acq%EKvDy$e^7;1`1D-T{+OfL$%rZr^m=K2$4m&gIlc7n zUaJX$%qB?j5}?TOboFyt=akR{0LMGE Aga7~l literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-speedread-138x110x2.png b/manual/plugins/images/ss-speedread-138x110x2.png new file mode 100644 index 0000000000000000000000000000000000000000..4c5846af932f9bcf6f5aa7a751b9548dd3988184 GIT binary patch literal 1329 zcmeAS@N?(olHy`uVBq!ia0vp^T|k`2!3-qN_f#JOQk(@Ik;On71Q;1wD#d{uW`+Qt z5ZC)R{!g8-%G)CpA~{NqhQNpl0sYUS$3S_aB*-uLKM;&4&!c&^e$oCipwMYg7srr_ zId88WFOC% zeA`)m2m7tGwPIvos9F9k!i7t7$%IXIs(H;K?NFG0@;D33R}b#*WngZV`PqFyH5Vl2 M>FVdQ&MBb@05V5^4*&oF literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-speedread-160x128x16.png b/manual/plugins/images/ss-speedread-160x128x16.png new file mode 100644 index 0000000000000000000000000000000000000000..71b8c849ea8193b2bd976da4cf3b5d1bba240fec GIT binary patch literal 626 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Rg9%77Gk)~}Qk(@Ik;On71Q;1wD#d{uCKXQ? z$B>FSZ*M#1-8K+#OO(~zm-OKCQ?E~lq}3;#5VY-Up=7^v(MHCc5mT%w&+0a-Pp&857xcAa%?_p-}|qsf_K+`l=$#LF+G#%!Gpyj4}$%d zuaGJDUj6o>I1hvSpEPql-aWqsndYy$Bfsg<{f6SkW_Q~{aZ{m)2gaN+YcCx*EH7K5 z^5Nw(Ry(WQKj9&pFCDnMUrl!JhL4fy{@Neb-(!#a>*$^(%-~+mrDNY{$gbGxDa`LR# z`*7*?E9P^7ce8~omhQT)Qs7m(-t)nfS861+Lvj0$oE++ekzopr0M(5J_W%F@ literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-speedread-160x128x2.png b/manual/plugins/images/ss-speedread-160x128x2.png new file mode 100644 index 0000000000000000000000000000000000000000..ea1c896f83f33805f5d75b954f509d00ff7a5457 GIT binary patch literal 1342 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$RgBeKf-aAPXNO2Z;L>2>S5MX3zsT2otm>B|m zLR_1+{0gsFu45AdksKvQLtsROKmotn7f_xk3Gxg64+JC1^Jt!}U$nmrD0IWq#WAE} z&f9B_T+Ido4uOg9>Yrcoz38K)+uV8SdsrXulj%ukw|(6IDA!+x<&mN3pWLJ_4x z5}BtO1=a7rymjD*1iRbrjHHAuv&)&3b$4kQTnhVZz4ObjLz@qjT;$AIm&W?8?9P2F zmUpZ5bY$4gs`Yt&E=LysI(u2+gJR>{*7MvkzbD8XekWS7-Tsy1gLi_Fe^$NriD&V% z(fP8h?782AOOM08FE%SU)+ox*V45c5<_&RzYQdJ%Wq&N#aB8!1E2rx3VREh&ROwOJz zjv*Cu-rf$(yX7Fkkl1%6X5*=!RjSVwgQjM7?3H-6;hgy_b+K!+=T{mmh!K0hJpH{L z!#r+Bg+KQd;_CL+d{I1qr|{)RRSqUc0gfgI0hT5O0hUICNP&92>Gd)<3u-c-df2Nh z$ZYyC=TMc{ca}oaR{2RsWgORBFWB07*D>c>!8iBnh!> z{QKj<>GO>W+vMeIep=4Z?6^B!E=K5xQb)AE^*?dnQ$CJ4b#MM;9iEzfw<^l<&e6Y) zI{Ri=P5ap-@$+6)Wq`ozcXLk#8#@Yg%WvmP=~I|?Rn5#?CVp4djQPQbjRFPU_bmBP zdHCbfsmgQhTFd`D7X$lKfrG_S0oCgWq020VHy`chuMk=`bFo5S;w($9M-l{?X3u#b zF~o4-;GTbVs{ge(+#C(KngRuSni51<7Ar`w9@H3ILiuGW7wrWnx$uG>J?^0WW| literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-speedread-176x220x16.png b/manual/plugins/images/ss-speedread-176x220x16.png new file mode 100644 index 0000000000000000000000000000000000000000..ac5b72fe4913f340a0ba1eca8524b68b5703a469 GIT binary patch literal 844 zcmeAS@N?(olHy`uVBq!ia0vp^8-Vx@2NRHtf4oNnNO2Z;L>2>S5MX3zsT2otnErXX zIEGZrd3)Q@?{)x#>qVWFIUAE|?@p3b2)!fpMqb_Y=QEqRf^5hC-f?93x@ku3ji7!W~p_Y*Ibic=r0f+{6jJFjNp+f-tgHsWRIlrKF@n}LE%{Ko|7KD^Jm`>Kk}~K z=Ik@>qz{*zn|8c>W%=!mW6r+tAFqVVSPVb?wW}32W-a`x%JT7s?eE#AKAH>k^Co== z%(v+FRO@)#&tLb`5=NT^3+#@s`Y=gB%<;~zEsy^%@-z|o{pb8>BtOqo&=GD)MGFXs;ANJ=o43mCA9$Lbwd8svbR^>t-d1@o4FTBSfBmlgN0q}#phbbhQJ_hI#gU^)fJJdsfZXuN*U|XA U$?WIvNuWI8>FVdQ&MBb@0L2s`Gynhq literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-speedread-220x176x16.png b/manual/plugins/images/ss-speedread-220x176x16.png new file mode 100644 index 0000000000000000000000000000000000000000..ba68fc9f5762ed8b10eb9b83257531ecfaebdc4f GIT binary patch literal 866 zcmeAS@N?(olHy`uVBq!ia0vp^cYt^U2NRHd%{To!km4-xh%5%uAi&7bQYjAPFpGG) zIEGZrd3!4`@3I4f>qVW;Ji*f19L*Yqg<5khgw4a&FMs~B#_;WtE8m$Jr>~C8nf|_x zVe{?IOGVkPKfk?xyLfud)4u+B_M4Q|P91Fh>|S=n#d1f%zY~2kG}y(@$=?(de){0h zPi5<*MKUoK^(pQ?Q<}M-_rK}Tm3un>^wVQ!fLf&cRUX}qHam6RGH?0MsT%E;Ud7+9 z^%U(skvM(#;+lvhdh=tgPF2e5&(!=>JpK2gJAWr^+xT_y9Gfc7DgJYB?OiMO^sv44 zz28l;f#*z?pO%fbJoV7MW7GbUDc1Xn{_#&)-&^!4aQ=_TKMy*K3LYLyKl|s`lkKV<#;c3={5t*lbN*kA z^nbk{e=XA3{`T+6KRa|Ko?fj#Ve#`?&!=5CpB@H!u#JE53H`jA8vk?Ej-LmHL$1vv znez3bOK*lw5u2l-YaSStw`j{9m&~2OfD~T(RL$$O@04k?(IkLja(Z#?cjjAnb${o% zRNf<=w5=&mLjmU1|Nru7E>dxy@}y&uOyDGCuOb)EK8=%ts+OaIXaQ2sIOX1d1L>mdKI;Vst0I)ZZxc~qF literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-speedread-240x320x16.png b/manual/plugins/images/ss-speedread-240x320x16.png new file mode 100644 index 0000000000000000000000000000000000000000..f716320df02ded01dfa3f6fa557201cc2630879e GIT binary patch literal 1226 zcmeAS@N?(olHy`uVBq!ia0vp^9~c-I9XOakB3)@LK#H@#BeEDsg8(B#OQkrF!&2kv z;uumf=j|=WJYh!>*FgQw9KqDuoRd5=Ty7NBZ_kywZ{?pJHgUCQ{F06WtJ$q(R=*g} z%(HZi&dL|~a=gEP{k%_8t=G+yzu74K<;$y|6J;$N{nr!bMjO6}Uw%1WP2R$O*GG?k zOna)bS!-v0db>uR^HA%>yUfyWs&rL6D9pBe^ zRM&K~+1gZjT_~S>t2tWf#o75&_y2B^b@Z>g`sMYumT$BKeSCP!*MwX16Ax^FDJ+O>jr04)z=RhpSFCqpLloo)24fW(s#eN`*1AwyWQFa z@!M6K?0z3TWfX1l;^h0i=l!{UZQFZaboahcpzwLm?3{%Q&YRD^_vgURi&xtNKkvU8 z<@o=m%G1;QbL8(Te#peLFVn6qiLw%|4+O6I;XY;o`NQtO=_+emT=4xah$A_ m5yax#H7Yn7AcG?@oXX$I((})N{c07cMDld?b6Mw<&;$VBTIwnQ literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-speedread-240x400x16.png b/manual/plugins/images/ss-speedread-240x400x16.png new file mode 100644 index 0000000000000000000000000000000000000000..c56efdb4220f55c7aac6d7e8fd69d9f702f0638d GIT binary patch literal 1436 zcmeAS@N?(olHy`uVBq!ia0vp^9~c-ICvY$US@K(|w1E_7fk$L9kOl!phL%ckAcxh@ z)5S5QV$R!Jj(Nh4BCdh@ojHQ3wK*qwX1Lratlyq1b>GTAJ#6A?&-f)B1y-|L%dCDe zo|$Lq7@d_b@a1@a|N42KrdqF?Cx5e1_{*19KPSpsIQp+A%#AjD5x@L$yqdg){jQH5 z|CshvWwX}K{PcE>Jm;^hQ=96(igEoaI~zXxp6r*jv$N_h-qU}vbG^LL%AcEF);hkg z^Qf-rX0x@a^14ty_f~VX(u=e6r|$pVBrlhQD_f zwD(sX|9)tjT886t5C*E)y-ik32#}dmf`JtVcM((x~9MY#FV%d z!T+Cj^>t2d3p@o)G@I01FD&7hCE_?) qDIHm-D()G3T&Ye*-d=c-+a_3#zw7YL# zNE>W^E4(4HLY()<=f|IS$I9(|xb(RGOxw8)tT_)kBMyko!@~H^`Q!WBZ--9^_r*u; zP(2u5$uy7mhvD6)J?Tt!v)1*jw_)^W-4pZva+^8(o~ZS0`WB4)pZ}iRbLIDq58E2d z?=P8K&2&%q!9nlcEPt+rT7P8OWBB0e_g(AdHYh%rXlGh;mb0S0VgA0BhPU!Q2egmZ zez5-Vs1DW+VIdAaId2DLUc)PK9 zPnvpybwS_tH!L~q=RVGw+wk^)W!z@r4YC%;v&)#aF>kN>a|R3W+p|7C-eF3EqR#;V xwFC}fgBDJSLo7UrOl<~?&ND`ZhkKaZklzq-A*a$~?Q&4|_jL7hS?83{1OP|=mYVEaktG3V`V$D-FE3@#VrguGK4-)Qp8V6fjfbz1kcqt8RqiagENU1tuTQ^Rolloi7Z zNduWTzU+T)+wXlc_2}aD@lF1c8yJfXn9UOS(hjhlImmOP!Ey$pv;n(u0`s#3wm-8N z|4;n$`uQo|_3>2`Qa-#t)9{|}$Mu(X^FGdHthb6au1jaD$v)73{rAhZ*NpbD>v{Jp zGXI;m|NfIxRrPlZ%pX`ynt!$--t0m5^f;pj`LR#gjUN;p@V9?m_q8eMK=`-J!|zWv z#2Y;Dmrp$Khn?rg$-@0WWwku}EFW~QykDVjF%!uxO^nz3_f?#1_}RrMZ}wn)*82Z$ ze^l81G%?!$HNF&XUh`qi`)g7ks*Qj)$nQIG@P7m2{+t8h``mwiT*PR9ZJ+j>;|;$V z>wl!PUE}$2t-)R{@5f7^w|*~vlhtDS*C@^4d zYX*kdnTCf6Y&^g~v6#Vl_#lr2FtD&m-JhC%t?^Gv-}P9wGZP+{PG>ZJQgJu+0FPx{ zxuk(J4q^X(c}Tnh$pie~tP9qxVQz@K#^|8`nqk58Qicnsf$^3q%kX6rA463nJHy^l g>Cs>rB+0>^@ph`pvH7wVW}qbO>FVdQ&MBb@062X2dmNPwaQ{9&+R352kPz;_n?gnxK*-g%yRX6BqT$(-Hg>}W9i zi`fJM!C?Egt$PRrQaph`l+Pl86+z*SLhw&Nbld(g@E`sW38z>z@L?Wd`_}K=qSJeu zsOQ_H5A=4W=ql8rxm>;K_u+*~le3QgjGqlM-8{aGn+WNc7&T)~^{)`;wi=~h=e3XA zh<(I(D-i$EYv^1rIpZE4??t=Be_gR^d~D@6hWScTYLTst1CxHZlo;vdTM$XP-K{v7 zV=2z1?v(b)=j+%di8GrcxX%!qB)^7GW@f&>?4;|Edi%vizB00DU{q)l$KECG6R%qr zq6*eibSnxiDG;5c(HHCg)33WpdqQ7Id(PW(ER6PeK)o9Pn6%I^> zG9Eb_@fZ?zEB-9Eg=vj^y7St6ov@@BIXi!##sWAS8Ar~z2U#EiYlSw*u*chYiH=Rn z!;JQqDc>{OpEe-Z{`_~l>m?(DZ3wo#inVP>-D~$Pg1w0p(RFOc6ja@e4ZIKlYutii zjBZ;1)94V4Y%+d)NSzaFfnYDrix%qGQ2{u?t3ZNTdOnE5tMr!SduD%!8U>R~(cJA; z9A&RVDBUf4W1TQH1=0VxKh}JlWx@2M$R%w zs`cjiIS}g8wy4=P<|kJ^gK1!%pM{n<#{zt^7A(I4Z!cK zv_yV3a&OIYh_hbPuOp#`9S+2c4TyiY@Eh>~6x{mXWn}klh_rg}2_YND8S4Fo zuRbD8uZYnbLQKa$%QM_1MR1mI<0hQB+k+&cB{qCvJ0l!PK2FU0&#srq&{DWv+=Tp; z{+6a+;)eX}8?-0NP(8bT_Fg6b62Uzs9%RqIkoeh*g`onZ_<+zuT#DeU22T)I9DxbV zs$DWOJqsCbU@Q(zLWXe`a6Ka0j87r)J#VT?;_#A0y!aY9|ao#ac9l(w|u&$r<`Su$3t@Luez zvz^V(Mfwb)owzQDzkm|5TD2Sb|3_6on!=>c!aOk(;ah&&w{X-0z1I#%NpU#n9p0Ye z@y*;Q+AXwc{0@5iEW#VLoOSZ2TompD#n-3*LL8UHF>^jg64#yDw-_HiKf@4<=h)dw z@5763B3S{#26LPXJyy<7rx4Ad!9!J(h-PtZt-)37Z(YB24^hcDfk95 zz1Ec-AA_CJM$P3jv8koVz}*Y+3!f{9J5ZR3QKOrEy--0_rgZ%^e=QYnKK;?0=UU9{L8=W}VDzupe))ME5_W*%a4?tIHF!0?oQ$`y=p zvtND(2U0kel!+yq$+sjVAj6W=A^N#!k5bbH)>mX<%nzcKCC*5^=W|Cg4a1dVx$@aa z{H~6Mga)K|b8;TF4&7c<2YYiHyzj!tp2*i=LxsgjS6rmo?0`@qa_N35#lR4|RI17= z{y;8&T4lf9248dhC;KSXI5hH@N=`sb`KIm0LUec0E_SI;7Gu&HHdlpj0{8TkdESli zfK!!fqsnh^91Gb=s{1%js*j1?ZIq?pM-+V*%pWu0RCxoNMzSxeP{Z&kG4U~FDH^%3c-*{IFXhUFfKDLAMl_WMTDkpJehjD?e^ zGf=M%)K|9S_e_z;=nj#xqyM{% zdLQky>RSt3s^=Xshw#O;=<$jd%MjT7T;7#c*w-?;l?l$;PSMd}bZt=&jtN^lk^i0V zsQ$S9nWv|uA4UF1qOf6{(ux0ygYdZ+eJraBMugmUwkinSv$WCFkZZAsn?xV&Y1xVx zRX!qiB)a9P?3!u`?tmnp0bN)KlC~+6RB{jHW?2KN?MIAR>+qoil2Trh+g$MCvIy}D z%%0LPt8F`8oS*ff0g@!IsBK&DBB@DifGzXt4f*J5q(xs&_|X5vw&63Bs7H|B+%$E^ zH?PXxsUisnv}Gj~w?FDq2cLYLO8KVMClfJc^{X%VxWYE+ogccNcyTx-7KJ0;-bnGo zPptpWa>7q{FU-QtPuwyz%LuQL-So-$$@0GbK6EdT&2H@a(ECEa&ZvyQa+2>ZFvB~} z)1@#p5v3|~bLofHm+9~@6yM94+a8DF2A;X2HWbUwbguu<`FzQO({FJ(lXqD!MVBni z{mhe&ZCJ#f3x>Gyk@wz+viTn6F@IT86%mWD44}WkTW-w`;s7T1_1o~P>;t9i79rk; z?mW}Z1(t% zhgh(--g5$P+UEZaR*sYAcjvvwk)FGoUyq;41McB372Muc1-RpJ^}}1XVh8j74Z>Hn zui-f>e0a^1_AEgSk9O(%JSVJu&dpZ*Ws`j9Rj?KdezGSN_dBv_!!cXDW~$AesbnUk z;ysqCdwojNUa?r9y>c*?`)qPm)woMX=;LELD=GM0;}h#XWw|uFh@bLkoRk{Ne>6U~ za})el4PU12H}I-o;DOkWIA(OOy|P8-ZWdp8#mq3I3GL zoA8zkqIuBPX^S+D-d}Ra(I*+&ioQ}Z|48#Pzs{%zwi_E$H9pS*>%3n^{R|`-=hTeP z?IPc)d^O3ly^pC&O4Z@D~T4Vcyb^v485ZUEgm_cB`Sp zKU?`zhmFHWe=%MJ-ToXVjk8X%iDaal18!r=?Uc-n5F?w5XQ12iCZ5K}C9-kPwf{id z;6y1sUjF-KJJ6di^~5jCVe0v?rUZ(|z|_d~rlwl%dd73)Ce%--hrTrWbrsC0VQ}NPT6OziUQ7@aIFNN9?kkS%gn$CmlJP2AbGtLmA~(hjzTDq* zAkLg~z$gC4jOJX8|68F_L!jz&lcumUt^GNd9^?; zK)Lg3ysuHDzrUgsErdX?7AZXpsea)o_`2)Hj;$8{_n6}Z5V791#^ zd>-Z*_qrO2-tgEXu4nmb87eeiTCQ2a%ZzyK2+MIr;}JL7yC6@yIkU@yK$zJBX8}h6 z{r*y}Qu464FCq+nC_8DOlFAIAqh7Y-4F#{EygI9>yZG}5Az*Ki8FptrjCe%!i#K2Yge{sT@(ADAPpbFwEV-P{2*qCo2+(-QcXDQZF|!miq^s8~p0;DH|uL?}I@DEu}P9Lp~h< zh6Wq1g?j;4ufUYyoL!F~*x(ASX{1g@E-8Zec1gn$VQdBXh62-mVaqrx{A@Af63%Y=aW|%{-N!}b#h)W(;h^662>6 z9<84S_ha_AXr{s2Buk{R)8HfO;B%kgKVHoyGr$^!R_rp(GgPHv= zCqI!lYR884UW$Bw&|$Le|d&kTnrr@yTGqX5Z#hf;x;ngme3 zosgtQVVkoVkOX?u8u|q*F$= z`4N)zD<&C0rtJ|>(Y52Ov-A!DVFc{)3uQy<)eoy8h<(>VF|GeqByy6JBz_+FF!HaW zh;t5nfMqULD!MqkfQsTWGcgzJx8v0uZ>4n+Mu7qhKs2Ic8Ijz=BnYnYb2M6gV=i z-nnfN6163Fx!DFeH_MI#`jz_ay4$F6q)``YiV zlElsiBrzlE1rJ4lq&$!MJ1m`+W3MB&0?4F*@Eve#I;1X@z@(o72C0+Od2}AaK$XLFD|a_|Zrm1o0xXO+eD=5j8PI1|+GJ zq$)H}eW*PJUYC3vmP)KCATRSj&Pyk|G>RZ_mOlKLoxNBzMg{r(fA5Yr zZIm2NPg$XDRkbLA}(NB u^ghGjTzGHGt_UOl$7E%|vu&x51CF3%OaR#(NmT3z4^ zXG!(W+PM#A{866Fn!b8(fIE{T0tz?Yi~4OgQ~25RCSB1cKgL^^kN7A%w41d0e0A7( zZegIgz^fkVxlB)=uR3>c1^);?R!a_IZ4&Fm!%sqgk5$q>8n@aR90 zfC8USl2huXrC7Z3Ob>7LSMX2($}IKeVBuw&`_`=9-BCC`={GaCJ^Np;1#jbP19JsB|!+UL0A>~X<#6GH~zh!0TZXn1P3Nv8{%mbGya*&T~jYD++qrf3r|-+mvv4F FO#qcLn}z@Y literal 0 HcmV?d00001 diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex index 3c25807424..cc3d9380ab 100644 --- a/manual/plugins/main.tex +++ b/manual/plugins/main.tex @@ -228,6 +228,8 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).} {\input{plugins/sort.tex}} +\opt{lcd_non-mono}{\input{plugins/speedread.tex}} + {\input{plugins/text_viewer.tex}} {\input{plugins/theme_remove.tex}} diff --git a/manual/plugins/speedread.tex b/manual/plugins/speedread.tex new file mode 100644 index 0000000000..11ac24634f --- /dev/null +++ b/manual/plugins/speedread.tex @@ -0,0 +1,7 @@ +\subsection{Speedread} +\screenshot{plugins/images/ss-speedread}{speedread}{fig:Speedread in action} + +This plugin is designed for reading plain-text files such as +ebooks. It works by using a form of Rapid Serial Visual Presentation +(RSVP) that has optimized word placement to reduce or eliminate eye +movement (saccades) when reading.