A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
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}