aa0f4a4bbe
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30589 a1c6a512-1295-4272-9138-f99709370657
564 lines
15 KiB
C
564 lines
15 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2002 Gilles Roux
|
|
* 2003 Garrett Derner
|
|
* 2010 Yoshihisa Uchida
|
|
*
|
|
* 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.
|
|
*
|
|
****************************************************************************/
|
|
#include "plugin.h"
|
|
#include "ctype.h"
|
|
#include "tv_preferences.h"
|
|
#include "tv_text_processor.h"
|
|
|
|
enum{
|
|
TV_TEXT_UNKNOWN,
|
|
TV_TEXT_MAC,
|
|
TV_TEXT_UNIX,
|
|
TV_TEXT_WIN,
|
|
};
|
|
|
|
/* the max characters of each blocks */
|
|
#ifdef HAVE_LCD_BITMAP
|
|
#define TV_MAX_CHARS_PER_BLOCK (LCD_WIDTH / 2 + 1)
|
|
#else
|
|
#define TV_MAX_CHARS_PER_BLOCK (LCD_WIDTH + 1)
|
|
#endif
|
|
|
|
#define TV_MAX_BLOCKS 5
|
|
|
|
static unsigned text_type = TV_TEXT_UNKNOWN;
|
|
|
|
static const unsigned char *end_ptr;
|
|
|
|
static unsigned short ucsbuf[TV_MAX_BLOCKS][TV_MAX_CHARS_PER_BLOCK];
|
|
static unsigned char utf8buf[TV_MAX_CHARS_PER_BLOCK * (2 * 3)];
|
|
static unsigned char *outbuf;
|
|
|
|
static int block_count;
|
|
static int block_width;
|
|
|
|
/* if this value is true, then tv_create_line_text returns a blank line. */
|
|
static bool expand_extra_line = false;
|
|
|
|
/* when a line is divided, this value sets true. */
|
|
static bool is_break_line = false;
|
|
|
|
static unsigned short break_chars[] =
|
|
{
|
|
0,
|
|
/* halfwidth characters */
|
|
'\t', '\n', 0x0b, 0x0c, ' ', '!', ',', '-', '.', ':', ';', '?', 0xb7,
|
|
/* fullwidth characters */
|
|
0x2010, /* hyphen */
|
|
0x3000, /* fullwidth space */
|
|
0x3001, /* ideographic comma */
|
|
0x3002, /* ideographic full stop */
|
|
0x30fb, /* katakana middle dot */
|
|
0x30fc, /* katakana-hiragana prolonged sound mark */
|
|
0xff01, /* fullwidth exclamation mark */
|
|
0xff0c, /* fullwidth comma */
|
|
0xff0d, /* fullwidth hyphen-minus */
|
|
0xff0e, /* fullwidth full stop */
|
|
0xff1a, /* fullwidth colon */
|
|
0xff1b, /* fullwidth semicolon */
|
|
0xff1f, /* fullwidth question mark */
|
|
};
|
|
|
|
/* the characters which is not judged as space with isspace() */
|
|
static unsigned short extra_spaces[] = { 0, 0x3000 };
|
|
|
|
static int tv_glyph_width(int ch)
|
|
{
|
|
if (ch == '\n')
|
|
return 0;
|
|
|
|
if (ch == 0)
|
|
ch = ' ';
|
|
|
|
#ifdef HAVE_LCD_BITMAP
|
|
/* the width of the diacritics charcter is 0 */
|
|
if (rb->is_diacritic(ch, NULL))
|
|
return 0;
|
|
|
|
return rb->font_get_width(rb->font_get(preferences->font_id), ch);
|
|
#else
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
static unsigned char *tv_get_ucs(const unsigned char *str, unsigned short *ch)
|
|
{
|
|
int count = 1;
|
|
unsigned char utf8_tmp[3];
|
|
|
|
/* distinguish the text_type */
|
|
if (*str == '\r')
|
|
{
|
|
if (text_type == TV_TEXT_WIN || text_type == TV_TEXT_UNKNOWN)
|
|
{
|
|
if (str + 1 < end_ptr && *(str+1) == '\n')
|
|
{
|
|
if (text_type == TV_TEXT_UNKNOWN)
|
|
text_type = TV_TEXT_WIN;
|
|
|
|
*ch = '\n';
|
|
return (unsigned char *)str + 2;
|
|
}
|
|
|
|
if (text_type == TV_TEXT_UNKNOWN)
|
|
text_type = TV_TEXT_MAC;
|
|
}
|
|
*ch = (text_type == TV_TEXT_MAC)? '\n' : ' ';
|
|
return (unsigned char *)str + 1;
|
|
}
|
|
else if (*str == '\n')
|
|
{
|
|
if (text_type == TV_TEXT_UNKNOWN)
|
|
text_type = TV_TEXT_UNIX;
|
|
|
|
*ch = (text_type == TV_TEXT_UNIX)? '\n' : ' ';
|
|
return (unsigned char *)str + 1;
|
|
}
|
|
|
|
if (preferences->encoding == UTF_8)
|
|
return (unsigned char*)rb->utf8decode(str, ch);
|
|
|
|
#ifdef HAVE_LCD_BITMAP
|
|
if ((*str >= 0x80) &&
|
|
((preferences->encoding > SJIS) ||
|
|
(preferences->encoding == SJIS && (*str <= 0xa0 || *str >= 0xe0))))
|
|
{
|
|
if (str + 1 >= end_ptr)
|
|
{
|
|
end_ptr = str;
|
|
*ch = 0;
|
|
return (unsigned char *)str;
|
|
}
|
|
count = 2;
|
|
}
|
|
#endif
|
|
rb->iso_decode(str, utf8_tmp, preferences->encoding, count);
|
|
rb->utf8decode(utf8_tmp, ch);
|
|
return (unsigned char *)str + count;
|
|
}
|
|
|
|
static void tv_decode2utf8(const unsigned short *ucs, int count)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
outbuf = rb->utf8encode(ucs[i], outbuf);
|
|
|
|
*outbuf = '\0';
|
|
}
|
|
|
|
static bool tv_is_line_break_char(unsigned short ch)
|
|
{
|
|
size_t i;
|
|
|
|
/* when the word mode is CHOP, all characters does not break line. */
|
|
if (preferences->word_mode == WM_CHOP)
|
|
return false;
|
|
|
|
for (i = 0; i < sizeof(break_chars); i++)
|
|
{
|
|
if (break_chars[i] == ch)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool tv_isspace(unsigned short ch)
|
|
{
|
|
size_t i;
|
|
|
|
if (ch < 128 && isspace(ch))
|
|
return true;
|
|
|
|
for (i = 0; i < sizeof(extra_spaces); i++)
|
|
{
|
|
if (extra_spaces[i] == ch)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool tv_is_break_line_join_mode(const unsigned char *next_str)
|
|
{
|
|
unsigned short ch;
|
|
|
|
tv_get_ucs(next_str, &ch);
|
|
return tv_isspace(ch);
|
|
}
|
|
|
|
static int tv_form_reflow_line(unsigned short *ucs, int chars)
|
|
{
|
|
unsigned short new_ucs[TV_MAX_CHARS_PER_BLOCK];
|
|
unsigned short *p = new_ucs;
|
|
unsigned short ch;
|
|
int i;
|
|
int k;
|
|
int expand_spaces;
|
|
int indent_chars = 0;
|
|
int nonspace_chars = 0;
|
|
int nonspace_width = 0;
|
|
int remain_spaces;
|
|
int spaces = 0;
|
|
int words_spaces;
|
|
|
|
if (preferences->alignment == AL_LEFT)
|
|
{
|
|
while (chars > 0 && ucs[chars-1] == ' ')
|
|
chars--;
|
|
}
|
|
|
|
if (chars == 0)
|
|
return 0;
|
|
|
|
while (ucs[indent_chars] == ' ')
|
|
indent_chars++;
|
|
|
|
for (i = indent_chars; i < chars; i++)
|
|
{
|
|
ch = ucs[i];
|
|
if (ch == ' ')
|
|
spaces++;
|
|
else
|
|
{
|
|
nonspace_chars++;
|
|
nonspace_width += tv_glyph_width(ch);
|
|
}
|
|
}
|
|
|
|
if (spaces == 0)
|
|
return chars;
|
|
|
|
expand_spaces = (block_width - nonspace_width) / tv_glyph_width(' ') - indent_chars;
|
|
if (indent_chars + nonspace_chars + expand_spaces > TV_MAX_CHARS_PER_BLOCK)
|
|
expand_spaces = TV_MAX_CHARS_PER_BLOCK - indent_chars - nonspace_chars;
|
|
|
|
words_spaces = expand_spaces / spaces;
|
|
remain_spaces = expand_spaces - words_spaces * spaces;
|
|
|
|
for (i = 0; i < indent_chars; i++)
|
|
*p++ = ' ';
|
|
|
|
for ( ; i < chars; i++)
|
|
{
|
|
ch = ucs[i];
|
|
*p++ = ch;
|
|
if (ch == ' ')
|
|
{
|
|
for (k = ((remain_spaces > 0)? 0 : 1); k < words_spaces; k++)
|
|
*p++ = ch;
|
|
|
|
remain_spaces--;
|
|
}
|
|
}
|
|
|
|
rb->memcpy(ucs, new_ucs, sizeof(unsigned short) * TV_MAX_CHARS_PER_BLOCK);
|
|
return indent_chars + nonspace_chars + expand_spaces;
|
|
}
|
|
|
|
static void tv_align_right(int *block_chars)
|
|
{
|
|
unsigned short *cur_text;
|
|
unsigned short *prev_text;
|
|
unsigned short ch;
|
|
int cur_block = block_count - 1;
|
|
int prev_block;
|
|
int cur_chars;
|
|
int prev_chars;
|
|
int idx;
|
|
int break_pos;
|
|
int break_width = 0;
|
|
int append_width;
|
|
int width;
|
|
|
|
while (cur_block > 0)
|
|
{
|
|
cur_text = ucsbuf[cur_block];
|
|
cur_chars = block_chars[cur_block];
|
|
idx = cur_chars;
|
|
width = 0;
|
|
while(--idx >= 0)
|
|
width += tv_glyph_width(cur_text[idx]);
|
|
|
|
width = block_width - width;
|
|
prev_block = cur_block - 1;
|
|
|
|
do {
|
|
prev_text = ucsbuf[prev_block];
|
|
prev_chars = block_chars[prev_block];
|
|
|
|
idx = prev_chars;
|
|
append_width = 0;
|
|
break_pos = prev_chars;
|
|
while (append_width < width && idx > 0)
|
|
{
|
|
ch = prev_text[--idx];
|
|
if (tv_is_line_break_char(ch))
|
|
{
|
|
break_pos = idx + 1;
|
|
break_width = append_width;
|
|
}
|
|
append_width += tv_glyph_width(ch);
|
|
}
|
|
if (append_width > width)
|
|
idx++;
|
|
|
|
if (idx == 0)
|
|
{
|
|
break_pos = 0;
|
|
break_width = append_width;
|
|
}
|
|
|
|
if (break_pos < prev_chars)
|
|
append_width = break_width;
|
|
/* the case of
|
|
* (1) when the first character of the cur_text concatenates
|
|
* the last character of the prev_text.
|
|
* (2) the length of ucsbuf[block] is short (< 0.75 * block width)
|
|
*/
|
|
else if (((!tv_isspace(*cur_text) && !tv_isspace(prev_text[prev_chars - 1])) ||
|
|
(4 * width >= 3 * block_width)))
|
|
{
|
|
break_pos = idx;
|
|
}
|
|
|
|
if (break_pos < prev_chars)
|
|
{
|
|
rb->memmove(cur_text + prev_chars - break_pos,
|
|
cur_text, block_chars[cur_block] * sizeof(unsigned short));
|
|
rb->memcpy(cur_text, prev_text + break_pos,
|
|
(prev_chars - break_pos) * sizeof(unsigned short));
|
|
|
|
block_chars[prev_block] = break_pos;
|
|
block_chars[cur_block ] += prev_chars - break_pos;
|
|
}
|
|
} while ((width -= append_width) > 0 && --prev_block >= 0);
|
|
cur_block--;
|
|
}
|
|
}
|
|
|
|
static int tv_parse_text(const unsigned char *src, unsigned short *ucs,
|
|
int *ucs_chars, bool is_indent)
|
|
{
|
|
const unsigned char *cur = src;
|
|
const unsigned char *next = src;
|
|
const unsigned char *line_break_ptr = NULL;
|
|
const unsigned char *line_end_ptr = NULL;
|
|
unsigned short ch = 0;
|
|
unsigned short prev_ch;
|
|
int chars = 0;
|
|
int gw;
|
|
int line_break_width = 0;
|
|
int line_end_chars = 0;
|
|
int width = 0;
|
|
bool is_space = false;
|
|
|
|
while (true) {
|
|
cur = next;
|
|
if (cur >= end_ptr)
|
|
{
|
|
line_end_ptr = cur;
|
|
line_end_chars = chars;
|
|
is_break_line = true;
|
|
break;
|
|
}
|
|
|
|
prev_ch = ch;
|
|
next = tv_get_ucs(cur, &ch);
|
|
if (ch == '\n')
|
|
{
|
|
if (preferences->line_mode != LM_JOIN || tv_is_break_line_join_mode(next))
|
|
{
|
|
line_end_ptr = next;
|
|
line_end_chars = chars;
|
|
is_break_line = false;
|
|
break;
|
|
}
|
|
|
|
if (preferences->word_mode == WM_CHOP || tv_isspace(prev_ch))
|
|
continue;
|
|
|
|
/*
|
|
* when the line mode is JOIN and the word mode is WRAP,
|
|
* the next character does not concatenate with the
|
|
* previous character.
|
|
*/
|
|
ch = ' ';
|
|
}
|
|
else if ((is_space = tv_isspace(ch)) == true)
|
|
{
|
|
/*
|
|
* when the line mode is REFLOW:
|
|
* (1) spacelike character convert to ' '
|
|
* (2) plural spaces are collected to one
|
|
*/
|
|
if (preferences->line_mode == LM_REFLOW)
|
|
{
|
|
ch = ' ';
|
|
if (prev_ch == ch)
|
|
continue;
|
|
}
|
|
|
|
/* when the alignment is RIGHT, ignores indent spaces. */
|
|
if (preferences->alignment == AL_RIGHT && is_indent)
|
|
continue;
|
|
}
|
|
else
|
|
is_indent = false;
|
|
|
|
if (preferences->line_mode == LM_REFLOW && is_indent)
|
|
gw = tv_glyph_width(ch) * preferences->indent_spaces;
|
|
else
|
|
gw = tv_glyph_width(ch);
|
|
|
|
width += gw;
|
|
if (width > block_width)
|
|
{
|
|
width -= gw;
|
|
if (is_space)
|
|
{
|
|
line_end_ptr = cur;
|
|
line_end_chars = chars;
|
|
}
|
|
is_break_line = true;
|
|
break;
|
|
}
|
|
|
|
if (preferences->line_mode != LM_REFLOW || !is_indent)
|
|
ucs[chars++] = ch;
|
|
else
|
|
{
|
|
unsigned char i;
|
|
for (i = 0; i < preferences->indent_spaces; i++)
|
|
ucs[chars++] = ch;
|
|
}
|
|
|
|
if (tv_is_line_break_char(ch))
|
|
{
|
|
line_break_ptr = next;
|
|
line_break_width = width;
|
|
line_end_chars = chars;
|
|
}
|
|
if (chars >= TV_MAX_CHARS_PER_BLOCK)
|
|
{
|
|
is_break_line = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* set the end position and character count */
|
|
if (line_end_ptr == NULL)
|
|
{
|
|
/*
|
|
* when the last line break position is too short (line length < 0.75 * block width),
|
|
* the line is cut off at the position where it is closest to the displayed width.
|
|
*/
|
|
if ((preferences->line_mode == LM_REFLOW && line_break_ptr == NULL) ||
|
|
(4 * line_break_width < 3 * block_width))
|
|
{
|
|
line_end_ptr = cur;
|
|
line_end_chars = chars;
|
|
}
|
|
else
|
|
line_end_ptr = line_break_ptr;
|
|
}
|
|
|
|
*ucs_chars = line_end_chars;
|
|
return line_end_ptr - src;
|
|
}
|
|
|
|
int tv_create_formed_text(const unsigned char *src, ssize_t bufsize,
|
|
int block, bool is_multi, const unsigned char **dst)
|
|
{
|
|
unsigned short ch;
|
|
int chars[block_count];
|
|
int i;
|
|
int size = 0;
|
|
bool is_indent;
|
|
|
|
outbuf = utf8buf;
|
|
*outbuf = '\0';
|
|
|
|
for (i = 0; i < block_count; i++)
|
|
chars[i] = 0;
|
|
|
|
if (dst != NULL)
|
|
*dst = utf8buf;
|
|
|
|
if (preferences->line_mode == LM_EXPAND && (expand_extra_line = !expand_extra_line) == true)
|
|
return 0;
|
|
|
|
end_ptr = src + bufsize;
|
|
|
|
tv_get_ucs(src, &ch);
|
|
is_indent = (tv_isspace(ch) && !is_break_line);
|
|
|
|
if (is_indent && preferences->line_mode == LM_REFLOW && preferences->indent_spaces == 0
|
|
&& (expand_extra_line = !expand_extra_line) == true)
|
|
return 0;
|
|
|
|
for (i = 0; i < block_count; i++)
|
|
{
|
|
size += tv_parse_text(src + size, ucsbuf[i], &chars[i], is_indent);
|
|
if (!is_break_line)
|
|
break;
|
|
|
|
is_indent = false;
|
|
}
|
|
|
|
if (dst != NULL)
|
|
{
|
|
if (preferences->alignment == AL_RIGHT)
|
|
tv_align_right(chars);
|
|
|
|
for (i = 0; i < block_count; i++)
|
|
{
|
|
if (i == block || (is_multi && i == block + 1))
|
|
{
|
|
if (is_break_line && preferences->line_mode == LM_REFLOW)
|
|
chars[i] = tv_form_reflow_line(ucsbuf[i], chars[i]);
|
|
|
|
tv_decode2utf8(ucsbuf[i], chars[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
bool tv_init_text_processor(unsigned char **buf, size_t *size)
|
|
{
|
|
/* unused : no need for dynamic buffer yet */
|
|
(void)buf;
|
|
(void)size;
|
|
|
|
text_type = TV_TEXT_UNKNOWN;
|
|
expand_extra_line = false;
|
|
is_break_line = false;
|
|
return true;
|
|
}
|
|
|
|
void tv_set_creation_conditions(int blocks, int width)
|
|
{
|
|
block_count = blocks;
|
|
block_width = width;
|
|
}
|