new text viewer
[kugel-rb.git] / apps / plugins / textviewer / tv_readtext.c
blob988ee637eba63354c5d4c38df868667c1b4a8b87
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 Gilles Roux
11 * 2003 Garrett Derner
12 * 2010 Yoshihisa Uchida
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version 2
17 * of the License, or (at your option) any later version.
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
22 ****************************************************************************/
23 #include "plugin.h"
24 #include "tv_readtext.h"
26 #define WRAP_TRIM 44 /* Max number of spaces to trim (arbitrary) */
27 #define NARROW_MAX_COLUMNS 64 /* Max displayable string len [narrow] (over-estimate) */
28 #define WIDE_MAX_COLUMNS 128 /* Max displayable string len [wide] (over-estimate) */
29 #define MAX_WIDTH 910 /* Max line length in WIDE mode */
30 #define READ_PREV_ZONE (block_size*9/10) /* Arbitrary number less than SMALL_BLOCK_SIZE */
31 #define SMALL_BLOCK_SIZE block_size /* Smallest file chunk we will read */
32 #define LARGE_BLOCK_SIZE (block_size << 1) /* Preferable size of file chunk to read */
33 #define TOP_SECTOR buffer
34 #define MID_SECTOR (buffer + SMALL_BLOCK_SIZE)
35 #define BOTTOM_SECTOR (buffer + (SMALL_BLOCK_SIZE << 1))
36 #undef SCROLLBAR_WIDTH
37 #define SCROLLBAR_WIDTH rb->global_settings->scrollbar_width
38 #define MAX_PAGE 9999
40 /* Out-Of-Bounds test for any pointer to data in the buffer */
41 #define BUFFER_OOB(p) ((p) < buffer || (p) >= buffer_end)
43 /* Does the buffer contain the beginning of the file? */
44 #define BUFFER_BOF() (file_pos==0)
46 /* Does the buffer contain the end of the file? */
47 #define BUFFER_EOF() (file_size-file_pos <= buffer_size)
49 /* Formula for the endpoint address outside of buffer data */
50 #define BUFFER_END() \
51 ((BUFFER_EOF()) ? (file_size-file_pos+buffer) : (buffer+buffer_size))
53 /* Is the entire file being shown in one screen? */
54 #define ONE_SCREEN_FITS_ALL() \
55 (next_screen_ptr==NULL && screen_top_ptr==buffer && BUFFER_BOF())
57 #define ADVANCE_COUNTERS(c) { width += glyph_width(c); k++; }
58 #define LINE_IS_FULL ((k>=max_columns-1) ||( width >= max_width))
59 #define LINE_IS_NOT_FULL ((k<max_columns-1) &&( width < max_width))
62 static unsigned char file_name[MAX_PARH+1];
63 static int fd = -1;
65 static unsigned char *buffer;
66 static long buffer_size;
67 static long block_size = 0x1000;
69 void viewer_init_buffer(void)
71 /* get the plugin buffer */
72 buffer = rb->plugin_get_buffer((size_t *)&buffer_size);
73 if (buffer_size == 0)
75 rb->splash(HZ, "buffer does not allocate !!");
76 return PLUGIN_ERROR;
78 block_size = buffer_size / 3;
79 buffer_size = 3 * block_size;
82 static unsigned char* crop_at_width(const unsigned char* p)
84 int k,width;
85 unsigned short ch;
86 const unsigned char *oldp = p;
88 k=width=0;
90 while (LINE_IS_NOT_FULL) {
91 oldp = p;
92 if (BUFFER_OOB(p))
93 break;
94 p = get_ucs(p, &ch);
95 ADVANCE_COUNTERS(ch);
98 return (unsigned char*)oldp;
101 static unsigned char* find_first_feed(const unsigned char* p, int size)
103 int i;
105 for (i=0; i < size; i++)
106 if (p[i] == 0)
107 return (unsigned char*) p+i;
109 return NULL;
112 static unsigned char* find_last_feed(const unsigned char* p, int size)
114 int i;
116 for (i=size-1; i>=0; i--)
117 if (p[i] == 0)
118 return (unsigned char*) p+i;
120 return NULL;
123 static unsigned char* find_last_space(const unsigned char* p, int size)
125 int i, j, k;
127 k = (prefs.line_mode==JOIN) || (prefs.line_mode==REFLOW) ? 0:1;
129 if (!BUFFER_OOB(&p[size]))
130 for (j=k; j < ((int) sizeof(line_break)) - 1; j++)
131 if (p[size] == line_break[j])
132 return (unsigned char*) p+size;
134 for (i=size-1; i>=0; i--)
135 for (j=k; j < (int) sizeof(line_break); j++)
137 if (!((p[i] == '-') && (prefs.word_mode == WRAP)))
138 if (p[i] == line_break[j])
139 return (unsigned char*) p+i;
142 return NULL;
145 static unsigned char* find_next_line(const unsigned char* cur_line, bool *is_short)
147 const unsigned char *next_line = NULL;
148 int size, i, j, k, width, search_len, spaces, newlines;
149 bool first_chars;
150 unsigned char c;
152 if (is_short != NULL)
153 *is_short = true;
155 if BUFFER_OOB(cur_line)
156 return NULL;
158 if (prefs.view_mode == WIDE) {
159 search_len = MAX_WIDTH;
161 else { /* prefs.view_mode == NARROW */
162 search_len = crop_at_width(cur_line) - cur_line;
165 size = BUFFER_OOB(cur_line+search_len) ? buffer_end-cur_line : search_len;
167 if ((prefs.line_mode == JOIN) || (prefs.line_mode == REFLOW)) {
168 /* Need to scan ahead and possibly increase search_len and size,
169 or possibly set next_line at second hard return in a row. */
170 next_line = NULL;
171 first_chars=true;
172 for (j=k=width=spaces=newlines=0; ; j++) {
173 if (BUFFER_OOB(cur_line+j))
174 return NULL;
175 if (LINE_IS_FULL) {
176 size = search_len = j;
177 break;
180 c = cur_line[j];
181 switch (c) {
182 case ' ':
183 if (prefs.line_mode == REFLOW) {
184 if (newlines > 0) {
185 size = j;
186 next_line = cur_line + size;
187 return (unsigned char*) next_line;
189 if (j==0) /* i=1 is intentional */
190 for (i=0; i<par_indent_spaces; i++)
191 ADVANCE_COUNTERS(' ');
193 if (!first_chars) spaces++;
194 break;
196 case 0:
197 if (newlines > 0) {
198 size = j;
199 next_line = cur_line + size - spaces;
200 if (next_line != cur_line)
201 return (unsigned char*) next_line;
202 break;
205 newlines++;
206 size += spaces -1;
207 if (BUFFER_OOB(cur_line+size) || size > 2*search_len)
208 return NULL;
209 search_len = size;
210 spaces = first_chars? 0:1;
211 break;
213 default:
214 if (prefs.line_mode==JOIN || newlines>0) {
215 while (spaces) {
216 spaces--;
217 ADVANCE_COUNTERS(' ');
218 if (LINE_IS_FULL) {
219 size = search_len = j;
220 break;
223 newlines=0;
224 } else if (spaces) {
225 /* REFLOW, multiple spaces between words: count only
226 * one. If more are needed, they will be added
227 * while drawing. */
228 search_len = size;
229 spaces=0;
230 ADVANCE_COUNTERS(' ');
231 if (LINE_IS_FULL) {
232 size = search_len = j;
233 break;
236 first_chars = false;
237 ADVANCE_COUNTERS(c);
238 break;
242 else {
243 /* find first hard return */
244 next_line = find_first_feed(cur_line, size);
247 if (next_line == NULL)
248 if (size == search_len) {
249 if (prefs.word_mode == WRAP) /* Find last space */
250 next_line = find_last_space(cur_line, size);
252 if (next_line == NULL)
253 next_line = crop_at_width(cur_line);
254 else
255 if (prefs.word_mode == WRAP)
256 for (i=0;
257 i<WRAP_TRIM && isspace(next_line[0]) && !BUFFER_OOB(next_line);
258 i++)
259 next_line++;
262 if (prefs.line_mode == EXPAND)
263 if (!BUFFER_OOB(next_line)) /* Not Null & not out of bounds */
264 if (next_line[0] == 0)
265 if (next_line != cur_line)
266 return (unsigned char*) next_line;
268 /* If next_line is pointing to a zero, increment it; i.e.,
269 leave the terminator at the end of cur_line. If pointing
270 to a hyphen, increment only if there is room to display
271 the hyphen on current line (won't apply in WIDE mode,
272 since it's guarenteed there won't be room). */
273 if (!BUFFER_OOB(next_line)) /* Not Null & not out of bounds */
274 if (next_line[0] == 0)/* ||
275 (next_line[0] == '-' && next_line-cur_line < draw_columns)) */
276 next_line++;
278 if (BUFFER_OOB(next_line))
280 if (BUFFER_EOF() && next_line != cur_line)
281 return (unsigned char*) next_line;
282 return NULL;
285 if (is_short)
286 *is_short = false;
288 return (unsigned char*) next_line;
291 static unsigned char* find_prev_line(const unsigned char* cur_line)
293 const unsigned char *prev_line = NULL;
294 const unsigned char *p;
296 if BUFFER_OOB(cur_line)
297 return NULL;
299 /* To wrap consistently at the same places, we must
300 start with a known hard return, then work downwards.
301 We can either search backwards for a hard return,
302 or simply start wrapping downwards from top of buffer.
303 If current line is not near top of buffer, this is
304 a file with long lines (paragraphs). We would need to
305 read earlier sectors before we could decide how to
306 properly wrap the lines above the current line, but
307 it probably is not worth the disk access. Instead,
308 start with top of buffer and wrap down from there.
309 This may result in some lines wrapping at different
310 points from where they wrap when scrolling down.
311 If buffer is at top of file, start at top of buffer. */
313 if ((prefs.line_mode == JOIN) || (prefs.line_mode == REFLOW))
314 prev_line = p = NULL;
315 else
316 prev_line = p = find_last_feed(buffer, cur_line-buffer-1);
317 /* Null means no line feeds in buffer above current line. */
319 if (prev_line == NULL)
320 if (BUFFER_BOF() || cur_line - buffer > READ_PREV_ZONE)
321 prev_line = p = buffer;
322 /* (else return NULL and read previous block) */
324 /* Wrap downwards until too far, then use the one before. */
325 while (p != NULL && p < cur_line) {
326 prev_line = p;
327 p = find_next_line(prev_line, NULL);
330 if (BUFFER_OOB(prev_line))
331 return NULL;
333 return (unsigned char*) prev_line;
336 static void check_bom(void)
338 unsigned char bom[BOM_SIZE];
339 off_t orig = rb->lseek(fd, 0, SEEK_CUR);
341 is_bom = false;
343 rb->lseek(fd, 0, SEEK_SET);
345 if (rb->read(fd, bom, BOM_SIZE) == BOM_SIZE)
346 is_bom = !memcmp(bom, BOM, BOM_SIZE);
348 rb->lseek(fd, orig, SEEK_SET);
351 static void fill_buffer(long pos, unsigned char* buf, unsigned size)
353 /* Read from file and preprocess the data */
354 /* To minimize disk access, always read on sector boundaries */
355 unsigned numread, i;
356 bool found_CR = false;
357 off_t offset = rb->lseek(fd, pos, SEEK_SET);
359 if (offset == 0 && prefs.encoding == UTF_8 && is_bom)
360 rb->lseek(fd, BOM_SIZE, SEEK_SET);
362 numread = rb->read(fd, buf, size);
363 buf[numread] = 0;
364 rb->button_clear_queue(); /* clear button queue */
366 for(i = 0; i < numread; i++) {
367 switch(buf[i]) {
368 case '\r':
369 if (mac_text) {
370 buf[i] = 0;
372 else {
373 buf[i] = ' ';
374 found_CR = true;
376 break;
378 case '\n':
379 buf[i] = 0;
380 found_CR = false;
381 break;
383 case 0: /* No break between case 0 and default, intentionally */
384 buf[i] = ' ';
385 default:
386 if (found_CR) {
387 buf[i - 1] = 0;
388 found_CR = false;
389 mac_text = true;
391 break;
396 static int read_and_synch(int direction)
398 /* Read next (or prev) block, and reposition global pointers. */
399 /* direction: 1 for down (i.e., further into file), -1 for up */
400 int move_size, move_vector, offset;
401 unsigned char *fill_buf;
403 if (direction == -1) /* up */ {
404 move_size = SMALL_BLOCK_SIZE;
405 offset = 0;
406 fill_buf = TOP_SECTOR;
407 rb->memcpy(BOTTOM_SECTOR, MID_SECTOR, SMALL_BLOCK_SIZE);
408 rb->memcpy(MID_SECTOR, TOP_SECTOR, SMALL_BLOCK_SIZE);
410 else /* down */ {
411 if (prefs.view_mode == WIDE) {
412 /* WIDE mode needs more buffer so we have to read smaller blocks */
413 move_size = SMALL_BLOCK_SIZE;
414 offset = LARGE_BLOCK_SIZE;
415 fill_buf = BOTTOM_SECTOR;
416 rb->memcpy(TOP_SECTOR, MID_SECTOR, SMALL_BLOCK_SIZE);
417 rb->memcpy(MID_SECTOR, BOTTOM_SECTOR, SMALL_BLOCK_SIZE);
419 else {
420 move_size = LARGE_BLOCK_SIZE;
421 offset = SMALL_BLOCK_SIZE;
422 fill_buf = MID_SECTOR;
423 rb->memcpy(TOP_SECTOR, BOTTOM_SECTOR, SMALL_BLOCK_SIZE);
426 move_vector = direction * move_size;
427 screen_top_ptr -= move_vector;
428 file_pos += move_vector;
429 buffer_end = BUFFER_END(); /* Update whenever file_pos changes */
430 fill_buffer(file_pos + offset, fill_buf, move_size);
431 return move_vector;
434 static void get_next_line_position(unsigned char **line_begin,
435 unsigned char **line_end,
436 bool *is_short)
438 int resynch_move;
440 *line_begin = *line_end;
441 *line_end = find_next_line(*line_begin, is_short);
443 if (*line_end == NULL && !BUFFER_EOF())
445 resynch_move = read_and_synch(1); /* Read block & move ptrs */
446 *line_begin -= resynch_move;
447 if (next_line_ptr > buffer)
448 next_line_ptr -= resynch_move;
450 *line_end = find_next_line(*line_begin, is_short);
454 /* open, close, get file size functions */
456 bool viewer_open(const unsigned char *fname)
458 if (fname == 0)
459 return false;
461 rb->strlcpy(file_name, fname, MAX_PATH+1);
462 fd = rb->open(fname, O_RDONLY);
463 return (fd >= 0);
466 void viewer_close(void)
468 if (fd >= 0)
469 rb->close(fd);
472 /* When a file is UTF-8 file with BOM, if prefs.encoding is UTF-8,
473 * then file size decreases only BOM_SIZE.
475 static void get_filesize(void)
477 file_size = rb->filesize(fd);
478 if (file_size == -1)
479 return;
481 if (prefs.encoding == UTF_8 && is_bom)
482 file_size -= BOM_SIZE;