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