1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Copyright (C) 2002 Gilles Roux
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 ****************************************************************************/
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
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];
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
);
75 rb
->splash(HZ
, "buffer does not allocate !!");
78 block_size
= buffer_size
/ 3;
79 buffer_size
= 3 * block_size
;
82 static unsigned char* crop_at_width(const unsigned char* p
)
86 const unsigned char *oldp
= p
;
90 while (LINE_IS_NOT_FULL
) {
98 return (unsigned char*)oldp
;
101 static unsigned char* find_first_feed(const unsigned char* p
, int size
)
105 for (i
=0; i
< size
; i
++)
107 return (unsigned char*) p
+i
;
112 static unsigned char* find_last_feed(const unsigned char* p
, int size
)
116 for (i
=size
-1; i
>=0; i
--)
118 return (unsigned char*) p
+i
;
123 static unsigned char* find_last_space(const unsigned char* p
, int size
)
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
;
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
;
152 if (is_short
!= NULL
)
155 if BUFFER_OOB(cur_line
)
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. */
172 for (j
=k
=width
=spaces
=newlines
=0; ; j
++) {
173 if (BUFFER_OOB(cur_line
+j
))
176 size
= search_len
= j
;
183 if (prefs
.line_mode
== REFLOW
) {
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
++;
199 next_line
= cur_line
+ size
- spaces
;
200 if (next_line
!= cur_line
)
201 return (unsigned char*) next_line
;
207 if (BUFFER_OOB(cur_line
+size
) || size
> 2*search_len
)
210 spaces
= first_chars
? 0:1;
214 if (prefs
.line_mode
==JOIN
|| newlines
>0) {
217 ADVANCE_COUNTERS(' ');
219 size
= search_len
= j
;
225 /* REFLOW, multiple spaces between words: count only
226 * one. If more are needed, they will be added
230 ADVANCE_COUNTERS(' ');
232 size
= search_len
= j
;
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
);
255 if (prefs
.word_mode
== WRAP
)
257 i
<WRAP_TRIM
&& isspace(next_line
[0]) && !BUFFER_OOB(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)) */
278 if (BUFFER_OOB(next_line
))
280 if (BUFFER_EOF() && next_line
!= cur_line
)
281 return (unsigned char*) next_line
;
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
)
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
;
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
) {
327 p
= find_next_line(prev_line
, NULL
);
330 if (BUFFER_OOB(prev_line
))
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
);
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 */
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
);
364 rb
->button_clear_queue(); /* clear button queue */
366 for(i
= 0; i
< numread
; i
++) {
383 case 0: /* No break between case 0 and default, intentionally */
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
;
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
);
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
);
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
);
434 static void get_next_line_position(unsigned char **line_begin
,
435 unsigned char **line_end
,
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
)
461 rb
->strlcpy(file_name
, fname
, MAX_PATH
+1);
462 fd
= rb
->open(fname
, O_RDONLY
);
466 void viewer_close(void)
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
);
481 if (prefs
.encoding
== UTF_8
&& is_bom
)
482 file_size
-= BOM_SIZE
;