A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 555 lines 15 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2002 Gilles Roux 11 * 2003 Garrett Derner 12 * 2010 Yoshihisa Uchida 13 * 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. 18 * 19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 20 * KIND, either express or implied. 21 * 22 ****************************************************************************/ 23#include "plugin.h" 24#include "ctype.h" 25#include "tv_preferences.h" 26#include "tv_text_processor.h" 27 28enum{ 29 TV_TEXT_UNKNOWN, 30 TV_TEXT_MAC, 31 TV_TEXT_UNIX, 32 TV_TEXT_WIN, 33}; 34 35/* the max characters of each blocks */ 36#define TV_MAX_CHARS_PER_BLOCK (LCD_WIDTH / 2 + 1) 37 38#define TV_MAX_BLOCKS 5 39 40static unsigned text_type = TV_TEXT_UNKNOWN; 41 42static const unsigned char *end_ptr; 43 44static ucschar_t ucsbuf[TV_MAX_BLOCKS][TV_MAX_CHARS_PER_BLOCK]; 45static unsigned char utf8buf[TV_MAX_CHARS_PER_BLOCK * (2 * 3)]; 46static unsigned char *outbuf; 47 48static int block_count; 49static int block_width; 50 51/* if this value is true, then tv_create_line_text returns a blank line. */ 52static bool expand_extra_line = false; 53 54/* when a line is divided, this value sets true. */ 55static bool is_break_line = false; 56 57static unsigned short break_chars[] = // XXX promote to ucschar_t if we get a codepoint > 0xffff 58 { 59 0, 60 /* halfwidth characters */ 61 '\t', '\n', 0x0b, 0x0c, ' ', '!', ',', '-', '.', ':', ';', '?', 0xb7, 62 /* fullwidth characters */ 63 0x2010, /* hyphen */ 64 0x3000, /* fullwidth space */ 65 0x3001, /* ideographic comma */ 66 0x3002, /* ideographic full stop */ 67 0x30fb, /* katakana middle dot */ 68 0x30fc, /* katakana-hiragana prolonged sound mark */ 69 0xff01, /* fullwidth exclamation mark */ 70 0xff0c, /* fullwidth comma */ 71 0xff0d, /* fullwidth hyphen-minus */ 72 0xff0e, /* fullwidth full stop */ 73 0xff1a, /* fullwidth colon */ 74 0xff1b, /* fullwidth semicolon */ 75 0xff1f, /* fullwidth question mark */ 76 }; 77 78/* the characters which is not judged as space with isspace() */ 79static unsigned short extra_spaces[] = { 0, 0x3000 }; // XXX promote to ucschar_t if we get a codepoint > 0xffff 80 81static int tv_glyph_width(int ch) 82{ 83 if (ch == '\n') 84 return 0; 85 86 if (ch == 0) 87 ch = ' '; 88 89 /* the width of the diacritics charcter is 0 */ 90 if (rb->is_diacritic(ch, NULL)) 91 return 0; 92 93 return rb->font_get_width(rb->font_get(preferences->font_id), ch); 94} 95 96static unsigned char *tv_get_ucs(const unsigned char *str, ucschar_t *ch) 97{ 98 int count = 1; 99 unsigned char utf8_tmp[3]; 100 101 /* distinguish the text_type */ 102 if (*str == '\r') 103 { 104 if (text_type == TV_TEXT_WIN || text_type == TV_TEXT_UNKNOWN) 105 { 106 if (str + 1 < end_ptr && *(str+1) == '\n') 107 { 108 if (text_type == TV_TEXT_UNKNOWN) 109 text_type = TV_TEXT_WIN; 110 111 *ch = '\n'; 112 return (unsigned char *)str + 2; 113 } 114 115 if (text_type == TV_TEXT_UNKNOWN) 116 text_type = TV_TEXT_MAC; 117 } 118 *ch = (text_type == TV_TEXT_MAC)? '\n' : ' '; 119 return (unsigned char *)str + 1; 120 } 121 else if (*str == '\n') 122 { 123 if (text_type == TV_TEXT_UNKNOWN) 124 text_type = TV_TEXT_UNIX; 125 126 *ch = (text_type == TV_TEXT_UNIX)? '\n' : ' '; 127 return (unsigned char *)str + 1; 128 } 129 130 if (preferences->encoding == UTF_8) 131 return (unsigned char*)rb->utf8decode(str, ch); 132 133 if ((*str >= 0x80) && 134 ((preferences->encoding > SJIS) || 135 (preferences->encoding == SJIS && (*str <= 0xa0 || *str >= 0xe0)))) 136 { 137 if (str + 1 >= end_ptr) 138 { 139 end_ptr = str; 140 *ch = 0; 141 return (unsigned char *)str; 142 } 143 count = 2; 144 } 145 146 rb->iso_decode(str, utf8_tmp, preferences->encoding, count); 147 rb->utf8decode(utf8_tmp, ch); 148 return (unsigned char *)str + count; 149} 150 151static void tv_decode2utf8(const ucschar_t *ucs, int count) 152{ 153 int i; 154 155 for (i = 0; i < count; i++) 156 outbuf = rb->utf8encode(ucs[i], outbuf); 157 158 *outbuf = '\0'; 159} 160 161static bool tv_is_line_break_char(ucschar_t ch) 162{ 163 size_t i; 164 165 /* when the word mode is CHOP, all characters does not break line. */ 166 if (preferences->word_mode == WM_CHOP) 167 return false; 168 169 for (i = 0; i < sizeof(break_chars)/sizeof(ucschar_t); i++) 170 { 171 if (break_chars[i] == ch) 172 return true; 173 } 174 return false; 175} 176 177static bool tv_isspace(ucschar_t ch) 178{ 179 size_t i; 180 181 if (ch < 128 && isspace(ch)) 182 return true; 183 184 for (i = 0; i < sizeof(extra_spaces)/sizeof(ucschar_t); i++) 185 { 186 if (extra_spaces[i] == ch) 187 return true; 188 } 189 return false; 190} 191 192static bool tv_is_break_line_join_mode(const unsigned char *next_str) 193{ 194 ucschar_t ch; 195 196 tv_get_ucs(next_str, &ch); 197 return tv_isspace(ch); 198} 199 200static int tv_form_reflow_line(ucschar_t *ucs, int chars) 201{ 202 ucschar_t new_ucs[TV_MAX_CHARS_PER_BLOCK]; 203 ucschar_t *p = new_ucs; 204 ucschar_t ch; 205 int i; 206 int k; 207 int expand_spaces; 208 int indent_chars = 0; 209 int nonspace_chars = 0; 210 int nonspace_width = 0; 211 int remain_spaces; 212 int spaces = 0; 213 int words_spaces; 214 215 if (preferences->alignment == AL_LEFT) 216 { 217 while (chars > 0 && ucs[chars-1] == ' ') 218 chars--; 219 } 220 221 if (chars == 0) 222 return 0; 223 224 while (ucs[indent_chars] == ' ') 225 indent_chars++; 226 227 for (i = indent_chars; i < chars; i++) 228 { 229 ch = ucs[i]; 230 if (ch == ' ') 231 spaces++; 232 else 233 { 234 nonspace_chars++; 235 nonspace_width += tv_glyph_width(ch); 236 } 237 } 238 239 if (spaces == 0) 240 return chars; 241 242 expand_spaces = (block_width - nonspace_width) / tv_glyph_width(' ') - indent_chars; 243 if (indent_chars + nonspace_chars + expand_spaces > TV_MAX_CHARS_PER_BLOCK) 244 expand_spaces = TV_MAX_CHARS_PER_BLOCK - indent_chars - nonspace_chars; 245 246 words_spaces = expand_spaces / spaces; 247 remain_spaces = expand_spaces - words_spaces * spaces; 248 249 for (i = 0; i < indent_chars; i++) 250 *p++ = ' '; 251 252 for ( ; i < chars; i++) 253 { 254 ch = ucs[i]; 255 *p++ = ch; 256 if (ch == ' ') 257 { 258 for (k = ((remain_spaces > 0)? 0 : 1); k < words_spaces; k++) 259 *p++ = ch; 260 261 remain_spaces--; 262 } 263 } 264 265 rb->memcpy(ucs, new_ucs, sizeof(ucschar_t) * TV_MAX_CHARS_PER_BLOCK); 266 return indent_chars + nonspace_chars + expand_spaces; 267} 268 269static void tv_align_right(int *block_chars) 270{ 271 ucschar_t *cur_text; 272 ucschar_t *prev_text; 273 ucschar_t ch; 274 int cur_block = block_count - 1; 275 int prev_block; 276 int cur_chars; 277 int prev_chars; 278 int idx; 279 int break_pos; 280 int break_width = 0; 281 int append_width; 282 int width; 283 284 while (cur_block > 0) 285 { 286 cur_text = ucsbuf[cur_block]; 287 cur_chars = block_chars[cur_block]; 288 idx = cur_chars; 289 width = 0; 290 while(--idx >= 0) 291 width += tv_glyph_width(cur_text[idx]); 292 293 width = block_width - width; 294 prev_block = cur_block - 1; 295 296 do { 297 prev_text = ucsbuf[prev_block]; 298 prev_chars = block_chars[prev_block]; 299 300 idx = prev_chars; 301 append_width = 0; 302 break_pos = prev_chars; 303 while (append_width < width && idx > 0) 304 { 305 ch = prev_text[--idx]; 306 if (tv_is_line_break_char(ch)) 307 { 308 break_pos = idx + 1; 309 break_width = append_width; 310 } 311 append_width += tv_glyph_width(ch); 312 } 313 if (append_width > width) 314 idx++; 315 316 if (idx == 0) 317 { 318 break_pos = 0; 319 break_width = append_width; 320 } 321 322 if (break_pos < prev_chars) 323 append_width = break_width; 324 /* the case of 325 * (1) when the first character of the cur_text concatenates 326 * the last character of the prev_text. 327 * (2) the length of ucsbuf[block] is short (< 0.75 * block width) 328 */ 329 else if (((!tv_isspace(*cur_text) && !tv_isspace(prev_text[prev_chars - 1])) || 330 (4 * width >= 3 * block_width))) 331 { 332 break_pos = idx; 333 } 334 335 if (break_pos < prev_chars) 336 { 337 rb->memmove(cur_text + prev_chars - break_pos, 338 cur_text, block_chars[cur_block] * sizeof(ucschar_t)); 339 rb->memcpy(cur_text, prev_text + break_pos, 340 (prev_chars - break_pos) * sizeof(ucschar_t)); 341 342 block_chars[prev_block] = break_pos; 343 block_chars[cur_block ] += prev_chars - break_pos; 344 } 345 } while ((width -= append_width) > 0 && --prev_block >= 0); 346 cur_block--; 347 } 348} 349 350static int tv_parse_text(const unsigned char *src, ucschar_t *ucs, 351 int *ucs_chars, bool is_indent) 352{ 353 const unsigned char *cur = src; 354 const unsigned char *next = src; 355 const unsigned char *line_break_ptr = NULL; 356 const unsigned char *line_end_ptr = NULL; 357 ucschar_t ch = 0; 358 ucschar_t prev_ch; 359 int chars = 0; 360 int gw; 361 int line_break_width = 0; 362 int line_end_chars = 0; 363 int width = 0; 364 bool is_space = false; 365 366 while (true) { 367 cur = next; 368 if (cur >= end_ptr) 369 { 370 line_end_ptr = cur; 371 line_end_chars = chars; 372 is_break_line = true; 373 break; 374 } 375 376 prev_ch = ch; 377 next = tv_get_ucs(cur, &ch); 378 if (ch == '\n') 379 { 380 if (preferences->line_mode != LM_JOIN || tv_is_break_line_join_mode(next)) 381 { 382 line_end_ptr = next; 383 line_end_chars = chars; 384 is_break_line = false; 385 break; 386 } 387 388 if (preferences->word_mode == WM_CHOP || tv_isspace(prev_ch)) 389 continue; 390 391 /* 392 * when the line mode is JOIN and the word mode is WRAP, 393 * the next character does not concatenate with the 394 * previous character. 395 */ 396 ch = ' '; 397 } 398 else if ((is_space = tv_isspace(ch)) == true) 399 { 400 /* 401 * when the line mode is REFLOW: 402 * (1) spacelike character convert to ' ' 403 * (2) plural spaces are collected to one 404 */ 405 if (preferences->line_mode == LM_REFLOW) 406 { 407 ch = ' '; 408 if (prev_ch == ch) 409 continue; 410 } 411 412 /* when the alignment is RIGHT, ignores indent spaces. */ 413 if (preferences->alignment == AL_RIGHT && is_indent) 414 continue; 415 } 416 else 417 is_indent = false; 418 419 if (preferences->line_mode == LM_REFLOW && is_indent) 420 gw = tv_glyph_width(ch) * preferences->indent_spaces; 421 else 422 gw = tv_glyph_width(ch); 423 424 width += gw; 425 if (width > block_width) 426 { 427 width -= gw; 428 if (is_space) 429 { 430 line_end_ptr = cur; 431 line_end_chars = chars; 432 } 433 is_break_line = true; 434 break; 435 } 436 437 if (preferences->line_mode != LM_REFLOW || !is_indent) 438 ucs[chars++] = ch; 439 else 440 { 441 unsigned char i; 442 for (i = 0; i < preferences->indent_spaces; i++) 443 ucs[chars++] = ch; 444 } 445 446 if (tv_is_line_break_char(ch)) 447 { 448 line_break_ptr = next; 449 line_break_width = width; 450 line_end_chars = chars; 451 } 452 if (chars >= TV_MAX_CHARS_PER_BLOCK) 453 { 454 is_break_line = true; 455 break; 456 } 457 } 458 459 /* set the end position and character count */ 460 if (line_end_ptr == NULL) 461 { 462 /* 463 * when the last line break position is too short (line length < 0.75 * block width), 464 * the line is cut off at the position where it is closest to the displayed width. 465 */ 466 if ((preferences->line_mode == LM_REFLOW && line_break_ptr == NULL) || 467 (4 * line_break_width < 3 * block_width)) 468 { 469 line_end_ptr = cur; 470 line_end_chars = chars; 471 } 472 else 473 line_end_ptr = line_break_ptr; 474 } 475 476 *ucs_chars = line_end_chars; 477 return line_end_ptr - src; 478} 479 480int tv_create_formed_text(const unsigned char *src, ssize_t bufsize, 481 int block, bool is_multi, const unsigned char **dst) 482{ 483 ucschar_t ch; 484 int chars[block_count]; 485 int i; 486 int size = 0; 487 bool is_indent; 488 489 outbuf = utf8buf; 490 *outbuf = '\0'; 491 492 for (i = 0; i < block_count; i++) 493 chars[i] = 0; 494 495 if (dst != NULL) 496 *dst = utf8buf; 497 498 if (preferences->line_mode == LM_EXPAND && (expand_extra_line = !expand_extra_line) == true) 499 return 0; 500 501 end_ptr = src + bufsize; 502 503 tv_get_ucs(src, &ch); 504 is_indent = (tv_isspace(ch) && !is_break_line); 505 506 if (is_indent && preferences->line_mode == LM_REFLOW && preferences->indent_spaces == 0 507 && (expand_extra_line = !expand_extra_line) == true) 508 return 0; 509 510 for (i = 0; i < block_count; i++) 511 { 512 size += tv_parse_text(src + size, ucsbuf[i], &chars[i], is_indent); 513 if (!is_break_line) 514 break; 515 516 is_indent = false; 517 } 518 519 if (dst != NULL) 520 { 521 if (preferences->alignment == AL_RIGHT) 522 tv_align_right(chars); 523 524 for (i = 0; i < block_count; i++) 525 { 526 if (i == block || (is_multi && i == block + 1)) 527 { 528 if (is_break_line && preferences->line_mode == LM_REFLOW) 529 chars[i] = tv_form_reflow_line(ucsbuf[i], chars[i]); 530 531 tv_decode2utf8(ucsbuf[i], chars[i]); 532 } 533 } 534 } 535 536 return size; 537} 538 539bool tv_init_text_processor(unsigned char **buf, size_t *size) 540{ 541 /* unused : no need for dynamic buffer yet */ 542 (void)buf; 543 (void)size; 544 545 text_type = TV_TEXT_UNKNOWN; 546 expand_extra_line = false; 547 is_break_line = false; 548 return true; 549} 550 551void tv_set_creation_conditions(int blocks, int width) 552{ 553 block_count = blocks; 554 block_width = width; 555}