1 // pangotext.c
2 // text handling code
3 // (c) A. Penkov 2010
4 // (c) G. Finch 2010 - 2020
5 // pieces of code taken and modified from scribbler.c
6 // released under the GNU GPL 3 or later
7 // see file COPYING or www.gnu.org for details
8
9 #include "main.h"
10 #include "pangotext.h"
11 #include "effects-weed.h"
12
13 #ifdef GUI_GTK
14 #include <pango/pangocairo.h>
15 static int font_cmp(const void *p1, const void *p2);
16 #endif
17
18
fill_bckg(lives_painter_t * cr,double x,double y,double dx,double dy)19 static void fill_bckg(lives_painter_t *cr, double x, double y, double dx, double dy) {
20 lives_painter_new_path(cr);
21 lives_painter_rectangle(cr, x, y, dx, dy);
22 lives_painter_fill(cr);
23 lives_painter_new_path(cr);
24 }
25
26
getxypos(LingoLayout * layout,int * px,int * py,int width,int height,boolean cent,double * pw,double * ph)27 static void getxypos(LingoLayout *layout, int *px, int *py, int width, int height, boolean cent, double *pw, double *ph) {
28 // calc coords of text, text will fit so it goes to bottom. Set cent to center text.
29
30 // width and height are frame width / height in pixels
31 // py, px : return locations for x,y
32 // pw, ph : return locations for pango width, height
33
34 int w_, h_;
35 double d;
36
37 // get size of layout
38 lingo_layout_get_size(layout, &w_, &h_);
39
40 // scale width, height to pixels
41 d = ((double)h_) / (double)LINGO_SCALE;
42 if (ph) *ph = d;
43
44 // ypos (adjusted so text goes to bottom)
45 if (py) *py = height - (int) * ph;
46
47 d = ((double)w_) / (double)LINGO_SCALE;
48 if (pw) *pw = d;
49
50 if (px) *px = cent ? (width - (int)d) >> 1 : 0.;
51 }
52
53
rewrap_text(char * text)54 static char *rewrap_text(char *text) {
55 // find the longest line and move the last word to the following line
56 // if there is no following line we add one
57 // if there are no spaces in the line we truncate
58 size_t maxlen = 0;
59
60 char **lines;
61 char *jtext, *tmp;
62 #ifdef REFLOW_TEXT
63 char *first, *second;
64 register int j;
65 #endif
66 size_t ll;
67 boolean needs_nl = FALSE;
68 int numlines, maxline = -1;
69 register int i;
70
71 if (!text || !(*text)) return NULL;
72
73 jtext = lives_strdup("");
74 numlines = get_token_count(text, '\n');
75 lines = lives_strsplit(text, "\n", numlines);
76
77 for (i = 0; i < numlines; i++) {
78 if ((ll = lives_strlen(lines[i])) > maxlen) {
79 maxlen = ll;
80 maxline = i;
81 }
82 }
83 if (maxlen < 2) {
84 lives_strfreev(lines);
85 return jtext;
86 }
87 for (i = 0; i < numlines; i++) {
88 if (i == maxline) {
89 #ifdef REFLOW_TEXT
90 for (j = maxlen - 1; j > 0; j--) {
91 // skip the final character - if it's a space we aren't going to move it yet
92 // if it's not a space we aren't going to move it yet
93 if (lines[i][j - 1] == ' ') {
94 // up to and including space
95 first = lives_strndup(lines[i], j);
96 // after space
97 second = lines[i] + j;
98 tmp = lives_strdup_printf("%s%s%s\n%s", jtext, needs_nl ? "\n" : "", first, second);
99 lives_free(first);
100 lives_free(jtext);
101 jtext = tmp;
102 needs_nl = FALSE;
103 break;
104 }
105 // no space in line, truncate last char
106 lines[i][maxlen - 1] = 0;
107 if (maxlen > 3)
108 lives_snprintf(&lines[i][maxlen - 4], 4, "%s", "...");
109 needs_nl = TRUE;
110 }
111 #endif
112 //g_print("maxlen %ld for %s\n", maxlen, lines[i]);
113 lines[i][maxlen - 1] = 0;
114 if (maxlen > 5)
115 lives_snprintf(&lines[i][maxlen - 4], 4, "%s", "...");
116 //g_print("Trying with %s\n", lines[i]);
117 }
118 tmp = lives_strdup_printf("%s%s%s", jtext, needs_nl ? "\n" : "", lines[i]);
119 lives_free(jtext);
120 jtext = tmp;
121 needs_nl = TRUE;
122 }
123 lives_strfreev(lines);
124 return jtext;
125 }
126
127
remove_first_line(char * text)128 static char *remove_first_line(char *text) {
129 int i;
130 size_t tlen = lives_strlen(text);
131 for (i = 0; i < tlen; i++) {
132 if (text[i] == '\n') return lives_strdup(text + i + 1);
133 }
134 return NULL;
135 }
136
137
deparagraph(char * text)138 static char *deparagraph(char *text) {
139 char *xtext = text;
140 size_t tlen;
141 int i, j = 0, nlcnt = 0;
142
143 for (i = 0; text[i]; i++) {
144 if (text[i] == '\n') {
145 if (!text[i + 1] || text[i + 1] == '\n') nlcnt++;
146 }
147 }
148 tlen = i;
149 if (nlcnt) {
150 tlen += nlcnt;
151 xtext = lives_malloc(tlen + 1);
152 for (i = 0; text[i]; i++) {
153 xtext[j++] = text[i];
154 if (text[i] == '\n') {
155 if (!text[i + 1] || text[i + 1] == '\n') xtext[j++] = ' ';
156 }
157 }
158 xtext[j] = 0;
159 }
160 if (text != xtext) lives_free(text);
161 return xtext;
162 }
163
164
layout_to_lives_painter(LingoLayout * layout,lives_painter_t * cr,lives_text_mode_t mode,lives_colRGBA64_t * fg,lives_colRGBA64_t * bg,int dwidth,int dheight,double x_bg,double y_bg,double x_text,double y_text)165 void layout_to_lives_painter(LingoLayout *layout, lives_painter_t *cr, lives_text_mode_t mode, lives_colRGBA64_t *fg,
166 lives_colRGBA64_t *bg, int dwidth, int dheight, double x_bg, double y_bg, double x_text, double y_text) {
167 double b_alpha = 1.;
168 double f_alpha = 1.;
169
170 if (bg) b_alpha = (double)bg->alpha / 65535.;
171 if (fg) f_alpha = (double)fg->alpha / 65535.;
172
173 if (!cr) return;
174
175 switch (mode) {
176 case LIVES_TEXT_MODE_BACKGROUND_ONLY:
177 lingo_layout_set_text(layout, "", -1);
178 case LIVES_TEXT_MODE_FOREGROUND_AND_BACKGROUND:
179 lives_painter_set_source_rgba(cr, (double)bg->red / 65535., (double)bg->green / 65535., (double)bg->blue / 65535., b_alpha);
180 fill_bckg(cr, x_bg, y_bg, dwidth, dheight);
181 break;
182 default:
183 break;
184 }
185
186 lives_painter_new_path(cr);
187 lives_painter_move_to(cr, x_text, y_text);
188 lives_painter_set_source_rgba(cr, (double)fg->red / 65535., (double)fg->green / 65535., (double)fg->blue / 65535., f_alpha);
189 }
190
191
192 //#define DEBUG_MSGS
layout_nth_message_at_bottom(int n,int width,int height,LiVESWidget * widget,int * linecount)193 LingoLayout *layout_nth_message_at_bottom(int n, int width, int height, LiVESWidget *widget, int *linecount) {
194 // create a layout, using text properties for widget
195 //
196 // nth message in mainw->messages should be at the bottom
197 // or if there are insufficient messages then we render from message 0
198
199 // also we want to justify text, splitting on words so that it fits width
200
201 #ifdef GUI_GTK
202 LingoLayout *layout;
203 LingoContext *ctx;
204 weed_plant_t *msg;
205
206 char *readytext, *testtext = NULL, *newtext = NULL, *tmp, *xx;
207 size_t ll;
208 weed_error_t error;
209
210 int w = 0, h = 0, pw;
211 int totlines = 0;
212 int whint = 0;
213 int slen;
214
215 boolean heightdone = FALSE;
216 boolean needs_newline = FALSE;
217
218 if (width < 32 || height < MIN_MSGBAR_HEIGHT) return NULL;
219
220 ctx = lives_widget_create_lingo_context(widget);
221 if (!ctx || !LINGO_IS_CONTEXT(ctx)) return NULL;
222
223 layout = lingo_layout_new(ctx);
224 if (!layout || !LINGO_IS_LAYOUT(layout)) {
225 lives_widget_object_unref(ctx);
226 return NULL;
227 }
228
229 readytext = lives_strdup("");
230
231 msg = get_nth_info_message(n);
232 if (!msg) return NULL;
233 newtext = weed_get_string_value(msg, WEED_LEAF_LIVES_MESSAGE_STRING, &error);
234 if (error != WEED_SUCCESS) return NULL;
235 if (!newtext) return NULL;
236 slen = (int)lives_strlen(newtext);
237 if (slen > 0 && newtext[slen - 1] == '\n') {
238 newtext[--slen] = 0;
239 }
240 totlines = get_token_count(newtext, '\n') + 1;
241 #ifdef DEBUG_MSGS
242 g_print("Got msg:%s\ntotal is now %d lines\n", newtext, totlines);
243 #endif
244 if (msg == mainw->msg_list) msg = NULL;
245 else {
246 msg = weed_get_plantptr_value(msg, WEED_LEAF_PREVIOUS, &error);
247 if (error != WEED_SUCCESS) return NULL;
248 }
249
250 #ifdef DEBUG_MSGS
251 g_print("Want msg number %d at bottom\n%s", n, weed_get_string_value(msg, WEED_LEAF_LIVES_MESSAGE_STRING, &error));
252 #endif
253
254 while (1) {
255 if (!newtext) {
256 if (!msg) break;
257 newtext = weed_get_string_value(msg, WEED_LEAF_LIVES_MESSAGE_STRING, &error);
258 if (error != WEED_SUCCESS) break;
259 if (!newtext) break;
260 totlines += get_token_count(newtext, '\n');
261 #ifdef DEBUG_MSGS
262 g_print("Got msg:%s\ntotal is now %d lines\n", newtext, totlines);
263 #endif
264 if (msg == mainw->msg_list) msg = NULL;
265 else {
266 msg = weed_get_plantptr_value(msg, WEED_LEAF_PREVIOUS, &error);
267 if (error != WEED_SUCCESS) break;
268 }
269 }
270
271 if (testtext) lives_free(testtext);
272 testtext = lives_strdup_printf("%s%s%s", newtext, needs_newline ? "\n" : "", readytext);
273 needs_newline = TRUE;
274 lingo_layout_set_text(layout, "", -1);
275 lives_widget_object_unref(layout);
276 layout = lingo_layout_new(ctx);
277 testtext = deparagraph(testtext);
278 lingo_layout_set_text(layout, testtext, -1);
279 lingo_layout_get_size(layout, &w, &h);
280
281 h /= LINGO_SCALE;
282 w /= LINGO_SCALE;
283
284 #ifdef DEBUG_MSGS
285 g_print("Sizes %d %d window, %d %d layout ()\n", width, height, w, h);
286 #endif
287
288 if (h > height) {
289 #ifdef DEBUG_MSGS
290 g_print("Too high !\n");
291 #endif
292
293 //#ifdef MUST_FIT
294 while (h > height) {
295 // text was too high, start removing lines from the top until it fits
296 tmp = remove_first_line(newtext);
297 lives_free(newtext);
298 newtext = tmp;
299 totlines--;
300 if (!newtext) break; // no more to remove, we are done !
301 //#ifdef DEBUG_MSGS
302 g_print("Retry with (%d) |%s|\n", totlines, newtext);
303 //#endif
304 lives_free(testtext);
305 testtext = lives_strdup_printf("%s%s", newtext, readytext);
306 #ifdef DEBUG_MSGS
307 g_print("Testing with:%s:\n", testtext);
308 #endif
309 lingo_layout_set_text(layout, "", -1);
310 lives_widget_object_unref(layout);
311 layout = lingo_layout_new(ctx);
312 lingo_layout_set_width(layout, width * LINGO_SCALE);
313 testtext = deparagraph(testtext);
314 lingo_layout_set_text(layout, testtext, -1);
315 lingo_layout_get_size(layout, NULL, &h);
316 h /= LINGO_SCALE;
317 }
318 //#endif
319 heightdone = TRUE;
320 }
321
322 // height was ok, now let's check the width
323 if (0 && w > width) {
324 int jumpval = 1, dirn = -1, tjump = 0;
325 //double nscale = 2.;
326 // text was too wide
327 #ifdef DEBUG_MSGS
328 g_print("Too wide !!!\n");
329 #endif
330 //while (1) {
331 while (0) {
332 totlines -= get_token_count(newtext, '\n');
333 slen = (int)lives_strlen(newtext);
334 // for now we just truncate and elipsise lines
335 tjump = dirn * jumpval;
336 /* if (tjump >= slen && dirn == -1) { */
337 /* jumpval = slen / 2; */
338 /* tjump = dirn * jumpval; */
339 /* } */
340 /* g_print("pt b %d %d %d\n", tjump, dirn, jumpval); */
341
342 /// TODO ****
343 if (whint == 0 || whint + 4 > slen) {
344 xx = lives_strndup(newtext, slen + tjump);
345 } else {
346 xx = lives_strndup(newtext, whint + 4 + tjump);
347 }
348 tmp = rewrap_text(xx);
349 lives_free(xx);
350 #ifdef DEBUG_MSGS
351 g_print("Retry with (%d) |%s|\n", totlines, xx);
352 #endif
353 if (!tmp) break;
354 // check width again, just looking at new part
355 lingo_layout_set_text(layout, "", -1);
356 lives_widget_object_unref(layout);
357 layout = lingo_layout_new(ctx);
358 lives_widget_object_ref_sink(layout);
359 tmp = deparagraph(tmp);
360 lingo_layout_set_text(layout, tmp, -1);
361 lingo_layout_get_size(layout, &pw, NULL);
362 w = pw / LINGO_SCALE;
363 if (w >= width) {
364 //dirn = -1;
365 jumpval++;
366 if (whint <= 0 || (ll = (int)lives_strlen(tmp)) < whint) whint = ll;
367 } else {
368 break;
369 }
370 /* if (jumpval == 1) break; */
371 /* dirn = 1; */
372 /* nscale = 0.5; */
373 /* } */
374 /* if (jumpval > 1) jumpval = (int)(jumpval * nscale + .9); */
375 lives_free(newtext);
376 newtext = tmp;
377 }
378 #ifdef DEBUG_MSGS
379 g_print("Width OK now\n");
380 #endif
381 lives_free(newtext);
382 newtext = tmp;
383 totlines += get_token_count(newtext, '\n');
384 // width is OK again, need to recheck height
385 heightdone = FALSE;
386 }
387
388 lives_free(newtext);
389 newtext = NULL;
390 lives_free(readytext);
391 readytext = testtext;
392 testtext = NULL;
393 #ifdef DEBUG_MSGS
394 g_print("|%s| passed size tests\n", readytext);
395 #endif
396 if (heightdone) break;
397 /// height too small; prepend more text
398 }
399
400 // result is now in readytext
401 //lingo_layout_set_text(layout, readytext, -1);
402
403 if (linecount) *linecount = totlines;
404
405 #ifdef DEBUG_MSGS
406 g_print("|%s| FINAL !!\n", readytext);
407 #endif
408 lives_free(readytext);
409 lives_widget_object_unref(ctx);
410 return layout;
411 #endif
412 return NULL;
413 }
414
415
get_font_list(void)416 char **get_font_list(void) {
417 register int i;
418 char **font_list = NULL;
419 #ifdef GUI_GTK
420 PangoContext *ctx;
421 ctx = gdk_pango_context_get();
422 if (ctx) {
423 PangoFontMap *pfm;
424 pfm = pango_context_get_font_map(ctx);
425 if (pfm) {
426 int num = 0;
427 PangoFontFamily **pff = NULL;
428 pango_font_map_list_families(pfm, &pff, &num);
429 if (num > 0) {
430 font_list = (char **)lives_malloc((num + 1) * sizeof(char *));
431 if (font_list) {
432 for (i = 0; i < num; ++i)
433 font_list[i] = lives_strdup(pango_font_family_get_name(pff[i]));
434 font_list[num] = NULL;
435 qsort(font_list, num, sizeof(char *), font_cmp);
436 }
437 }
438 lives_free(pff);
439 }
440 }
441 #endif
442
443 #ifdef GUI_QT
444 QFontDatabase qfd;
445 QStringList qsl = qfd.families();
446 font_list = (char **)lives_malloc((qsl.size() + 1) * sizeof(char *));
447 for (i = 0; i < qsl.size(); i++) {
448 font_list[i] = lives_strdup(qsl.at(i).toUtf8().constData());
449 }
450 #endif
451
452 return font_list;
453 }
454
455
font_cmp(const void * p1,const void * p2)456 static int font_cmp(const void *p1, const void *p2) {
457 const char *s1 = (const char *)(*(char **)p1);
458 const char *s2 = (const char *)(*(char **)p2);
459 char *u1 = lives_utf8_casefold(s1, -1);
460 char *u2 = lives_utf8_casefold(s2, -1);
461 int ret = lives_strcmp_ordered(u1, u2);
462 lives_free(u1);
463 lives_free(u2);
464 return ret;
465 }
466
467
render_text_to_cr(LiVESWidget * widget,lives_painter_t * cr,const char * text,const char * fontname,double size,lives_text_mode_t mode,lives_colRGBA64_t * fg,lives_colRGBA64_t * bg,boolean center,boolean rising,double * top,int * offs_x,int dwidth,int * dheight)468 LingoLayout *render_text_to_cr(LiVESWidget *widget, lives_painter_t *cr, const char *text, const char *fontname,
469 double size, lives_text_mode_t mode, lives_colRGBA64_t *fg, lives_colRGBA64_t *bg,
470 boolean center, boolean rising, double *top, int *offs_x, int dwidth, int *dheight) {
471 // fontname may be eg. "Sans"
472
473 // size is in device units, i.e. pixels
474
475 // ypos:
476 // if "rising" is TRUE, text will be aligned to fit to bottom
477 // if "rising" is FALSE, "top" (0.0 -> 1.0) is used
478
479 // xpos:
480 // aligned to left (offs_x), unless "center" is TRUE
481
482 LingoFontDescription *font = NULL;
483 LingoLayout *layout;
484
485 int x_pos = 0, y_pos = 0;
486 double lwidth = (double)dwidth, lheight = (double)(*dheight);
487
488 if (!cr) return NULL;
489
490 #ifdef GUI_GTK
491 if (widget) {
492 LingoContext *ctx = gtk_widget_get_pango_context(widget);
493 layout = lingo_layout_new(ctx);
494 } else {
495 layout = pango_cairo_create_layout(cr);
496 if (!layout) return NULL;
497
498 font = lingo_font_description_new();
499 pango_font_description_set_family(font, fontname);
500 pango_font_description_set_absolute_size(font, size * LINGO_SCALE);
501 pango_layout_set_font_description(layout, font);
502 }
503
504 lingo_layout_set_markup(layout, text, -1);
505 #endif
506
507 if (center) lingo_layout_set_alignment(layout, LINGO_ALIGN_CENTER);
508 else lingo_layout_set_alignment(layout, LINGO_ALIGN_LEFT);
509
510 getxypos(layout, &x_pos, &y_pos, dwidth, *dheight, center, &lwidth, &lheight);
511 if (lwidth > dwidth) {
512 lingo_layout_set_width(layout, dwidth * LINGO_SCALE);
513 /// may cause text to wrap, so call this again
514 x_pos = y_pos = 0;
515 lwidth = (double)dwidth;
516 lheight = (double)(*dheight);
517 getxypos(layout, &x_pos, &y_pos, dwidth, *dheight, center, &lwidth, &lheight);
518 }
519
520 if (!rising) y_pos = (double) * dheight * *top;
521 if (!center) {
522 x_pos += *offs_x;
523 *offs_x = x_pos;
524 }
525
526 /* lives_painter_new_path(cr);
527 lives_painter_rectangle(cr,offs_x,0,width,height);
528 lives_painter_clip(cr);*/
529
530 if (font) lingo_font_description_free(font);
531
532 if (mode == LIVES_TEXT_MODE_PRECALCULATE) {
533 *dheight = lheight;
534 return layout;
535 }
536
537 layout_to_lives_painter(layout, cr, mode, fg, bg, lwidth, lheight, x_pos, y_pos, x_pos, y_pos);
538
539 return layout;
540 }
541
542
render_text_overlay(weed_layer_t * layer,const char * text)543 LIVES_GLOBAL_INLINE weed_plant_t *render_text_overlay(weed_layer_t *layer, const char *text) {
544 if (!text) return layer;
545 else {
546 lives_colRGBA64_t col_white = lives_rgba_col_new(65535, 65535, 65535, 65535);
547 lives_colRGBA64_t col_black_a = lives_rgba_col_new(0, 0, 0, SUB_OPACITY);
548 int size = weed_layer_get_width(layer) / 32;
549 const char *font = "Sans";
550 boolean fake_gamma = FALSE;
551
552 if (prefs->apply_gamma) {
553 // leave as linear gamma maybe
554 if (weed_layer_get_gamma(layer) == WEED_GAMMA_LINEAR) {
555 // stops it getting converted
556 weed_layer_set_gamma(layer, WEED_GAMMA_SRGB);
557 fake_gamma = TRUE;
558 }
559 }
560
561 layer = render_text_to_layer(layer, text, font, size,
562 LIVES_TEXT_MODE_FOREGROUND_AND_BACKGROUND, &col_white, &col_black_a, TRUE, FALSE, 0.1);
563 if (fake_gamma)
564 weed_set_int_value(layer, WEED_LEAF_GAMMA_TYPE, WEED_GAMMA_LINEAR);
565 }
566 return layer;
567 }
568
569
render_text_to_layer(weed_layer_t * layer,const char * text,const char * fontname,double size,lives_text_mode_t mode,lives_colRGBA64_t * fg_col,lives_colRGBA64_t * bg_col,boolean center,boolean rising,double top)570 weed_plant_t *render_text_to_layer(weed_layer_t *layer, const char *text, const char *fontname,
571 double size, lives_text_mode_t mode, lives_colRGBA64_t *fg_col, lives_colRGBA64_t *bg_col,
572 boolean center, boolean rising, double top) {
573 // render text to layer and return a new layer, which may have a new "rowstrides", "width" and/or "current_palette"
574 // original layer pixel_data is freed in the process and should not be re-used
575
576 lives_painter_t *cr = NULL;
577 LingoLayout *layout;
578 weed_layer_t *test_layer, *layer_slice;
579 uint8_t *src, *pd;
580 int row = weed_layer_get_rowstride(layer);
581 double ztop = 0.;
582 int pal = weed_layer_get_palette(layer), opal = pal;
583 int width = weed_layer_get_width(layer);
584 int height = weed_layer_get_height(layer);
585 int lheight = height;
586 int gamma = WEED_GAMMA_UNKNOWN, offsx = 0;
587
588 if (weed_palette_is_rgb(pal)) {
589 int ppal = LIVES_PAINTER_COLOR_PALETTE(capable->byte_order), oppal = ppal;
590 int ipsize = pixel_size(pal);
591 int opsize = pixel_size(ppal);
592 // test first to get the layout coords
593 gamma = weed_layer_get_gamma(layer);
594
595 lheight = height;
596 weed_layer_set_height(layer, 4);
597
598 test_layer = weed_layer_copy(NULL, layer);
599
600 if (ipsize == opsize) {
601 weed_layer_set_palette(test_layer, ppal);
602 } else {
603 if (consider_swapping(&pal, &ppal)) {
604 if (ppal == oppal) {
605 weed_layer_set_palette(test_layer, pal);
606 } else ppal = oppal;
607 pal = opal;
608 }
609 }
610
611 weed_layer_set_height(layer, height);
612 cr = layer_to_lives_painter(test_layer);
613 layout = render_text_to_cr(NULL, cr, text, fontname, size, LIVES_TEXT_MODE_PRECALCULATE,
614 fg_col, bg_col, center, rising, &top, &offsx, width, &lheight);
615 if (LIVES_IS_WIDGET_OBJECT(layout)) lives_widget_object_unref(layout);
616
617 weed_layer_free(test_layer);
618
619 /// if possible just render the slice which contains the text
620 if (top * height + lheight < height) {
621 uint8_t *xsrc;
622 boolean rbswapped = FALSE;
623
624 // adjust pixel_data and height, then copy-by-ref to layer_slice
625 src = weed_layer_get_pixel_data_packed(layer);
626 xsrc = src + (int)(top * height) * row;
627 weed_layer_set_pixel_data_packed(layer, xsrc);
628 weed_layer_set_height(layer, lheight);
629
630 layer_slice = weed_layer_new(WEED_LAYER_TYPE_VIDEO);
631 weed_layer_copy(layer_slice, layer);
632 weed_leaf_set_flagbits(layer_slice, WEED_LEAF_PIXEL_DATA, LIVES_FLAG_MAINTAIN_VALUE);
633
634 // restore original values
635 weed_layer_set_height(layer, height);
636 weed_layer_set_pixel_data_packed(layer, src);
637
638 if (consider_swapping(&pal, &ppal)) {
639 if (ppal == oppal) {
640 lives_colRGBA64_t col;
641 rbswapped = TRUE;
642 weed_layer_set_palette(layer_slice, pal);
643 col.red = fg_col->red;
644 fg_col->red = fg_col->blue;
645 fg_col->blue = col.red;
646 col.red = bg_col->red;
647 bg_col->red = bg_col->blue;
648 bg_col->blue = col.red;
649 }
650 }
651
652 cr = layer_to_lives_painter(layer_slice);
653 layout = render_text_to_cr(NULL, cr, text, fontname, size, mode, fg_col, bg_col, center, FALSE, &ztop, &offsx, width, &height);
654 if (layout && LINGO_IS_LAYOUT(layout)) {
655 lingo_painter_show_layout(cr, layout);
656 lives_widget_object_unref(layout);
657 }
658 // frees pd
659 lives_painter_to_layer(cr, layer_slice);
660 /// make sure our slice isnt freed, since it is actually part of the image
661 /// which we will overwrite
662
663 convert_layer_palette(layer_slice, pal, 0);
664 weed_leaf_clear_flagbits(layer_slice, WEED_LEAF_PIXEL_DATA, LIVES_FLAG_MAINTAIN_VALUE);
665
666 pd = weed_layer_get_pixel_data_packed(layer_slice);
667 if (pd != xsrc) lives_memcpy(src + (int)(top * height) * row, pd, lheight * row);
668 else weed_layer_nullify_pixel_data(layer_slice);
669 weed_layer_free(layer_slice);
670
671 if (rbswapped) {
672 lives_colRGBA64_t col;
673 col.red = fg_col->red;
674 fg_col->red = fg_col->blue;
675 fg_col->blue = col.red;
676 col.red = bg_col->red;
677 bg_col->red = bg_col->blue;
678 bg_col->blue = col.red;
679 }
680 }
681 }
682
683 if (!cr) {
684 cr = layer_to_lives_painter(layer);
685 if (!cr) return layer; ///< error occurred
686 layout = render_text_to_cr(NULL, cr, text, fontname, size, mode, fg_col, bg_col, center, rising, &top, &offsx, width, &height);
687 if (layout && LINGO_IS_LAYOUT(layout)) {
688 lingo_painter_show_layout(cr, layout);
689 if (layout) lives_widget_object_unref(layout);
690 }
691 lives_painter_to_layer(cr, layer);
692 }
693 if (gamma != WEED_GAMMA_UNKNOWN)
694 weed_layer_set_gamma(layer, gamma);
695 return layer;
696 }
697
698 static const char *cr_str = "\x0D";
699 static const char *lf_str = "\x0A";
700
701 //
702 // read appropriate text for subtitle file (.srt)
703 //
srt_read_text(int fd,lives_subtitle_t * title)704 static char *srt_read_text(int fd, lives_subtitle_t *title) {
705 char *poslf = NULL;
706 char *poscr = NULL;
707 char *ret = NULL;
708 char data[32768];
709
710 if (fd < 0 || !title) return NULL;
711
712 lives_lseek_buffered_rdonly_absolute(fd, title->textpos);
713
714 while (lives_read_buffered(fd, data, sizeof(data) - 1, TRUE) > 0) {
715 // remove \n \r
716 poslf = strstr(data, lf_str);
717 if (poslf) *poslf = '\0';
718 poscr = strstr(data, cr_str);
719 if (poscr) *poscr = '\0';
720 if (!(*data)) break;
721 if (!ret) ret = lives_strdup(data);
722 else {
723 char *tmp = lives_strconcat(ret, "\n", data, NULL);
724 ret = tmp;
725 lives_free(ret);
726 }
727 }
728
729 return ret;
730 }
731
732
sub_read_text(int fd,lives_subtitle_t * title)733 static char *sub_read_text(int fd, lives_subtitle_t *title) {
734 char *poslf = NULL;
735 char *poscr = NULL;
736 char *ret = NULL;
737 char *retmore = NULL;
738 char data[32768];
739 size_t curlen, retlen;
740
741 if (fd < 0 || !title) return NULL;
742
743 lives_lseek_buffered_rdonly_absolute(fd, title->textpos);
744
745 while (lives_read_buffered(fd, data, sizeof(data) - 1, TRUE) > 0) {
746 // remove \n \r
747 poslf = strstr(data, lf_str);
748 if (poslf) *poslf = '\0';
749 poscr = strstr(data, cr_str);
750 if (poscr) *poscr = '\0';
751 curlen = lives_strlen(data);
752 if (!curlen) break;
753 if (!ret) {
754 ret = subst(data, "[br]", "\n");
755 if (!ret) return NULL;
756 retlen = lives_strlen(ret) + 1;
757 } else {
758 retmore = subst(data, "[br]", "\n");
759 if (!retmore) return NULL;
760 curlen = lives_strlen(retmore);
761 if (!curlen) break;
762 retlen += curlen + 1;
763 ret = (char *)lives_realloc(ret, retlen);
764 if (ret) {
765 strcat(ret, "\n");
766 strcat(ret, retmore);
767 lives_free(retmore);
768 } else {
769 lives_free(retmore);
770 return NULL;
771 }
772 }
773 }
774
775 return ret;
776 }
777
778
srt_parse_file(lives_clip_t * sfile)779 static boolean srt_parse_file(lives_clip_t *sfile) {
780 lives_subtitle_t *node = NULL;
781 lives_subtitle_t *index_prev = NULL;
782 char data[32768];
783 int fd = sfile->subt->tfile;
784
785 while (!lives_read_buffered_eof(fd)) {
786 char *poslf = NULL, *poscr = NULL;
787 double starttime, endtime;
788 int hstart, mstart, sstart, fstart;
789 int hend, mend, send, fend;
790 int i;
791
792 //
793 // data contains subtitle number
794 //
795
796 if (lives_read_buffered(fd, data, 32768, TRUE) < 12) {
797 // EOF
798 //lives_freep((void **)&sfile->subt->text);
799 //sfile->subt->current = NULL;
800 //sub_get_last_time(sfile->subt);
801 return FALSE;
802 }
803 //
804 // data contains time range
805 //
806 // remove \n \r
807 poslf = strstr(data, lf_str);
808 if (poslf)
809 *poslf = '\0';
810 poscr = strstr(data, cr_str);
811 if (poscr)
812 *poscr = '\0';
813
814 // try to parse it (time range)
815 i = sscanf(data, "%d:%d:%d,%d --> %d:%d:%d,%d", \
816 &hstart, &mstart, &sstart, &fstart, \
817 &hend, &mend, &send, &fend);
818 if (i == 8) {
819 // parsing ok
820 starttime = hstart * 3600 + mstart * 60 + sstart + fstart / 1000.;
821 endtime = hend * 3600 + mend * 60 + send + fend / 1000.;
822 node = (lives_subtitle_t *)lives_malloc(sizeof(lives_subtitle_t));
823 if (node) {
824 node->start_time = starttime;
825 node->end_time = endtime;
826 node->style = NULL;
827 node->next = NULL;
828 node->prev = (lives_subtitle_t *)index_prev;
829 node->textpos = lives_buffered_offset(fd);
830 if (index_prev)
831 index_prev->next = (lives_subtitle_t *)node;
832 else
833 sfile->subt->first = node;
834 sfile->subt->last = index_prev = (lives_subtitle_t *)node;
835 }
836 while (lives_read_buffered(fd, data, 32768, TRUE) > 0) {
837 // read the text and final empty line
838 // remove \n \r
839 poslf = strstr(data, lf_str);
840 if (poslf)
841 *poslf = '\0';
842 poscr = strstr(data, cr_str);
843 if (poscr)
844 *poscr = '\0';
845
846 if (!(*data)) break;
847 } // end while
848 } else return FALSE;
849 }
850 return TRUE;
851 }
852
853
sub_parse_file(lives_clip_t * sfile)854 static boolean sub_parse_file(lives_clip_t *sfile) {
855 lives_subtitle_t *node = NULL;
856 lives_subtitle_t *index_prev = NULL;
857 char data[32768];
858 int fd = sfile->subt->tfile;
859 boolean starttext = FALSE;
860
861 while (lives_read_buffered(fd, data, 32768, TRUE) > 0) {
862 char *poslf = NULL, *poscr = NULL;
863 int hstart, mstart, sstart, fstart;
864 int hend, mend, send, fend;
865 int i;
866 double starttime, endtime;
867
868 if (!strncmp(data, "[SUBTITLE]", 10)) {
869 starttext = TRUE;
870 }
871
872 if (!starttext) {
873 if (!strncmp(data, "[DELAY]", 7)) {
874 sfile->subt->offset = atoi(data + 7);
875 }
876 continue;
877 }
878
879 //
880 // data contains time range
881 //
882 // remove \n \r
883 poslf = strstr(data, lf_str);
884 if (poslf)
885 *poslf = '\0';
886 poscr = strstr(data, cr_str);
887 if (poscr)
888 *poscr = '\0';
889
890 // try to parse it (time range)
891 i = sscanf(data, "%d:%d:%d.%d,%d:%d:%d.%d", \
892 &hstart, &mstart, &sstart, &fstart, \
893 &hend, &mend, &send, &fend);
894 if (i == 8) {
895 // parsing ok
896 starttime = hstart * 3600 + mstart * 60 + sstart + fstart / 100.;
897 endtime = hend * 3600 + mend * 60 + send + fend / 100.;
898 node = (lives_subtitle_t *)lives_malloc(sizeof(lives_subtitle_t));
899 if (node) {
900 node->start_time = starttime;
901 node->end_time = endtime;
902 node->style = NULL;
903 node->next = NULL;
904 node->prev = (lives_subtitle_t *)index_prev;
905 node->textpos = lives_buffered_offset(fd);
906 if (index_prev)
907 index_prev->next = (lives_subtitle_t *)node;
908 else
909 sfile->subt->first = node;
910 index_prev = (lives_subtitle_t *)node;
911 }
912 while (lives_read_buffered(fd, data, 32768, TRUE) > 0) {
913 // read the text and final empty line
914 // remove \n \r
915 poslf = strstr(data, lf_str);
916 if (poslf)
917 *poslf = '\0';
918 poscr = strstr(data, cr_str);
919 if (poscr)
920 *poscr = '\0';
921
922 if (!(*data)) break;
923 } // end while
924 } else return FALSE;
925 }
926 return TRUE;
927 }
928
929
get_subt_text(lives_clip_t * sfile,double xtime)930 boolean get_subt_text(lives_clip_t *sfile, double xtime) {
931 lives_subtitle_t *curr = NULL;
932
933 if (!sfile || !sfile->subt) return FALSE;
934
935 curr = sfile->subt->current;
936 if (curr) {
937 // continue showing existing text
938 if (curr->start_time <= xtime && curr->end_time >= xtime) {
939 if (!sfile->subt->text) {
940 if (sfile->subt->type == SUBTITLE_TYPE_SRT) {
941 char *tmp = srt_read_text(sfile->subt->tfile, curr);
942 sfile->subt->text = lives_charset_convert(tmp, LIVES_CHARSET_UTF8, SRT_DEF_CHARSET);
943 lives_free(tmp);
944 } else if (sfile->subt->type == SUBTITLE_TYPE_SUB) sfile->subt->text = sub_read_text(sfile->subt->tfile, curr);
945 }
946 return TRUE;
947 }
948 }
949
950 lives_freep((void **)&sfile->subt->text);
951
952 if (xtime < sfile->subt->first->start_time || xtime > sfile->subt->last->end_time) {
953 sfile->subt->current = NULL;
954 return TRUE;
955 }
956
957 if (!curr) curr = sfile->subt->first;
958
959 if (xtime > curr->end_time) while (curr->end_time < xtime) curr = curr->next;
960 if (xtime < curr->start_time && xtime <= curr->prev->end_time) while (curr->start_time > xtime) curr = curr->prev;
961
962 sfile->subt->current = curr;
963
964 if (curr->start_time <= xtime && curr->end_time >= xtime) {
965 if (sfile->subt->type == SUBTITLE_TYPE_SRT) {
966 char *tmp = srt_read_text(sfile->subt->tfile, curr);
967 sfile->subt->text = lives_charset_convert(tmp, LIVES_CHARSET_UTF8, SRT_DEF_CHARSET);
968 lives_free(tmp);
969 } else if (sfile->subt->type == SUBTITLE_TYPE_SUB) sfile->subt->text = sub_read_text(sfile->subt->tfile, curr);
970 }
971
972 return TRUE;
973 }
974
975
subtitles_free(lives_clip_t * sfile)976 void subtitles_free(lives_clip_t *sfile) {
977 if (!sfile) return;
978 if (!sfile->subt) return;
979 if (sfile->subt->tfile >= 0) lives_close_buffered(sfile->subt->tfile);
980
981 // remove subt->first entries
982 while (sfile->subt->first) {
983 lives_subtitle_t *to_delete = sfile->subt->first;
984
985 sfile->subt->first = (lives_subtitle_t *)sfile->subt->first->next;
986
987 lives_freep((void **)&to_delete->style);
988 lives_freep((void **)&to_delete);
989 }
990
991 lives_freep((void **)&sfile->subt->text);
992 lives_freep((void **)&sfile->subt);
993 }
994
995
subtitles_init(lives_clip_t * sfile,char * fname,lives_subtitle_type_t subtype)996 boolean subtitles_init(lives_clip_t *sfile, char *fname, lives_subtitle_type_t subtype) {
997 // fname is the name of the subtitle file
998 int fd;
999
1000 if (!sfile) return FALSE;
1001 if (sfile->subt) subtitles_free(sfile);
1002 sfile->subt = NULL;
1003
1004 if ((fd = lives_open_buffered_rdonly(fname)) < 0) return FALSE;
1005
1006 sfile->subt = (lives_subtitles_t *)lives_malloc(sizeof(lives_subtitles_t));
1007 sfile->subt->tfile = fd;
1008 sfile->subt->current = sfile->subt->first = NULL;
1009 sfile->subt->text = NULL;
1010 sfile->subt->last_time = -1.;
1011 sfile->subt->type = subtype;
1012 sfile->subt->offset = 0;
1013 if (subtype == SUBTITLE_TYPE_SRT) srt_parse_file(sfile);
1014 if (subtype == SUBTITLE_TYPE_SUB) sub_parse_file(sfile);
1015 return TRUE;
1016 }
1017
1018
parse_double_time(double tim,int * ph,int * pmin,int * psec,int * pmsec,int fix)1019 static void parse_double_time(double tim, int *ph, int *pmin, int *psec, int *pmsec, int fix) {
1020 int ntim = (int)tim;
1021 int h, m, s, ms;
1022
1023 h = ntim / 3600;
1024 m = (ntim - h * 3600) / 60;
1025 s = (ntim - h * 3600 - m * 60);
1026 if (fix == 3) ms = (int)((tim - ntim) * 1000.0 + .5);
1027 else ms = (int)((tim - ntim) * 100.0 + .5); // hundredths
1028 if (ph)
1029 *ph = h;
1030 if (pmin)
1031 *pmin = m;
1032 if (psec)
1033 *psec = s;
1034 if (pmsec)
1035 *pmsec = ms;
1036 }
1037
1038
save_srt_subtitles(lives_clip_t * sfile,double start_time,double end_time,double offset_time,const char * filename)1039 boolean save_srt_subtitles(lives_clip_t *sfile, double start_time, double end_time, double offset_time, const char *filename) {
1040 lives_subtitles_t *subt = NULL;
1041 int64_t savepos = 0;
1042 int fd, num_saves;
1043 lives_subtitle_t *ptr = NULL;
1044
1045 if (!sfile) return FALSE;
1046 subt = sfile->subt;
1047 if (!subt) return FALSE;
1048 if (subt->last_time <= -1.)
1049 get_subt_text(sfile, end_time);
1050 if (subt->last_time <= -1.)
1051 savepos = lives_buffered_offset(subt->tfile);
1052
1053 // save the contents
1054 fd = lives_create_buffered(filename, DEF_FILE_PERMS);
1055 if (fd < 0) return FALSE;
1056 num_saves = 0;
1057 ptr = subt->first;
1058 while (ptr) {
1059 char *text = NULL;
1060 if (ptr->start_time < end_time && ptr->end_time >= start_time) {
1061 text = srt_read_text(subt->tfile, ptr);
1062 if (text) {
1063 int h, m, s, ms;
1064 double dtim;
1065
1066 if (num_saves > 0) lives_write_buffered(fd, "\n", 1, TRUE);
1067
1068 dtim = ptr->start_time;
1069 if (dtim < start_time) dtim = start_time;
1070 dtim += offset_time;
1071
1072 parse_double_time(dtim, &h, &m, &s, &ms, 3);
1073 lives_buffered_write_printf(fd, TRUE, "%02d:%02d:%02d,%03d\n", h, m, s, ms);
1074
1075 dtim = ptr->end_time;
1076 if (dtim > end_time) dtim = end_time;
1077 dtim += offset_time;
1078
1079 parse_double_time(dtim, &h, &m, &s, &ms, 3);
1080 lives_buffered_write_printf(fd, TRUE, "%02d:%02d:%02d,%03d\n", h, m, s, ms);
1081
1082 lives_write_buffered(fd, text, lives_strlen(text), TRUE);
1083 lives_free(text);
1084 }
1085 } else if (ptr->start_time >= end_time) break;
1086 ptr = (lives_subtitle_t *)ptr->next;
1087 }
1088
1089 lives_close_buffered(fd);
1090
1091 if (!num_saves) // don't keep the empty file
1092 lives_rm(filename);
1093
1094 if (subt->last_time <= -1.)
1095 lives_lseek_buffered_rdonly_absolute(subt->tfile, savepos);
1096
1097 return TRUE;
1098 }
1099
1100
save_sub_subtitles(lives_clip_t * sfile,double start_time,double end_time,double offset_time,const char * filename)1101 boolean save_sub_subtitles(lives_clip_t *sfile, double start_time, double end_time, double offset_time, const char *filename) {
1102 lives_subtitles_t *subt = NULL;
1103 int64_t savepos = 0;
1104 int fd, num_saves;
1105 lives_subtitle_t *ptr = NULL;
1106
1107 if (!sfile)
1108 return FALSE;
1109 subt = sfile->subt;
1110 if (!subt)
1111 return FALSE;
1112 if (subt->last_time <= -1.)
1113 get_subt_text(sfile, end_time);
1114 if (subt->last_time <= -1.)
1115 savepos = lives_buffered_offset(subt->tfile);
1116
1117 // save the contents
1118 fd = lives_create_buffered(filename, DEF_FILE_PERMS);
1119 if (fd < 0) return FALSE;
1120 num_saves = 0;
1121 ptr = subt->first;
1122
1123 lives_buffered_write_printf(fd, TRUE, "[INFORMATION]\n");
1124 lives_buffered_write_printf(fd, TRUE, "[TITLE] %s\n", sfile->title);
1125 lives_buffered_write_printf(fd, TRUE, "[AUTHOR] %s\n", sfile->author);
1126 lives_buffered_write_printf(fd, TRUE, "[SOURCE]\n");
1127 lives_buffered_write_printf(fd, TRUE, "[FILEPATH]\n");
1128 lives_buffered_write_printf(fd, TRUE, "[DELAY] 0\n");
1129 lives_buffered_write_printf(fd, TRUE, "[COMMENT] %s\n", sfile->comment);
1130 lives_buffered_write_printf(fd, TRUE, "[END INFORMATION]\n");
1131 lives_buffered_write_printf(fd, TRUE, "[SUBTITLE]\n");
1132
1133 while (ptr) {
1134 char *text = NULL;
1135 char *br_text = NULL;
1136 if (ptr->start_time < end_time && ptr->end_time >= start_time) {
1137 text = sub_read_text(subt->tfile, ptr);
1138 if (text) {
1139 int h, m, s, ms;
1140 double dtim;
1141 size_t ll = lives_strlen(text) - 1;
1142 if (text[ll] == '\n') text[ll] = 0;
1143
1144 br_text = subst(text, "\n", "[br]");
1145 if (br_text) {
1146 if (num_saves > 0) lives_write_buffered(fd, "\n", 1, TRUE);
1147
1148 dtim = ptr->start_time;
1149 if (dtim < start_time) dtim = start_time;
1150 dtim += offset_time;
1151
1152 parse_double_time(dtim, &h, &m, &s, &ms, 2);
1153 lives_buffered_write_printf(fd, TRUE, "%02d:%02d:%02d.%02d,", h, m, s, ms);
1154
1155 dtim = ptr->end_time;
1156 if (dtim > end_time) dtim = end_time;
1157 dtim += offset_time;
1158
1159 parse_double_time(dtim, &h, &m, &s, &ms, 2);
1160 lives_buffered_write_printf(fd, TRUE, "%02d:%02d:%02d.%02d\n", h, m, s, ms);
1161 lives_buffered_write_printf(fd, TRUE, "%s\n", br_text);
1162 lives_free(br_text);
1163 num_saves++;
1164 }
1165 lives_free(text);
1166 }
1167 } else if (ptr->start_time >= end_time) break;
1168 ptr = (lives_subtitle_t *)ptr->next;
1169 }
1170
1171 lives_close_buffered(fd);
1172 if (!num_saves) // don't keep the empty file
1173 lives_rm(filename);
1174
1175 if (subt->last_time <= -1.)
1176 lives_lseek_buffered_rdonly_absolute(subt->tfile, savepos);
1177
1178 return TRUE;
1179 }
1180
1181
lives_parse_font_string(const char * string,char ** font,int * size,char ** stretch,char ** style,char ** weight)1182 boolean lives_parse_font_string(const char *string, char **font, int *size, char **stretch,
1183 char **style, char **weight) {
1184 if (!string) return FALSE;
1185 else {
1186 int numtok = get_token_count(string, ' ') ;
1187 char **array = lives_strsplit(string, " ", numtok);
1188 //int xsize = -1;
1189 if (font) {
1190 *font = 0;
1191 for (int i = 0; i < numtok - 1; i++) {
1192 char *tmp;
1193 if (*font) {
1194 tmp = lives_strdup_printf("%s %s", *font, array[i]);
1195 lives_free(*font);
1196 *font = tmp;
1197 } else *font = lives_strdup(array[i]);
1198 }
1199 }
1200 if (size && numtok > 1 && atoi(array[numtok - 1])) *size = atoi(array[numtok - 1]);
1201 if (font && !atoi(array[numtok - 1])) {
1202 if (*font) {
1203 char *tmp = lives_strdup_printf("%s %s", *font, array[numtok - 1]);
1204 lives_free(*font);
1205 *font = tmp;
1206 } else *font = lives_strdup(array[numtok - 1]);
1207 }
1208 lives_strfreev(array);
1209 }
1210 return TRUE;
1211 }
1212