/*************************************************************************** * __________ __ ___. * 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)/sizeof(unsigned short); 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)/sizeof(unsigned short); 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; }