895f53051d50d024d98375c2eda99b1d227abb0a
[colloquium.git] / libstorycode / narrative.c
1 /*
2  * narrative.c
3  *
4  * Copyright © 2019 Thomas White <taw@bitwiz.org.uk>
5  *
6  * This file is part of Colloquium.
7  *
8  * Colloquium is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <stdlib.h>
29 #include <string.h>
30 #include <assert.h>
31 #include <stdio.h>
32 #include <gio/gio.h>
33
34 #include <libintl.h>
35 #define _(x) gettext(x)
36
37 #ifdef HAVE_PANGO
38 #include <pango/pango.h>
39 #endif
40
41 #ifdef HAVE_CAIRO
42 #include <cairo.h>
43 #endif
44
45 #include "stylesheet.h"
46 #include "slide.h"
47 #include "narrative.h"
48 #include "narrative_priv.h"
49 #include "imagestore.h"
50 #include "storycode.h"
51
52 Narrative *narrative_new()
53 {
54         Narrative *n;
55         n = malloc(sizeof(*n));
56         if ( n == NULL ) return NULL;
57         n->n_items = 0;
58         n->items = NULL;
59         n->stylesheet = stylesheet_new();
60         n->imagestore = imagestore_new("."); /* FIXME: From app config */
61         n->saved = 1;
62 #ifdef HAVE_PANGO
63         n->language = pango_language_to_string(pango_language_get_default());
64 #else
65         n->language = NULL;
66 #endif
67         return n;
68 }
69
70
71 static void narrative_item_destroy(struct narrative_item *item)
72 {
73         int i;
74         for ( i=0; i<item->n_runs; i++ ) {
75                 free(item->runs[i].text);
76         }
77         free(item->runs);
78 #ifdef HAVE_PANGO
79         if ( item->layout != NULL ) {
80                 g_object_unref(item->layout);
81         }
82 #endif
83 #ifdef HAVE_CAIRO
84         if ( item->slide_thumbnail != NULL ) {
85                 cairo_surface_destroy(item->slide_thumbnail);
86         }
87 #endif
88 }
89
90
91 /* Free the narrative and all contents, but not the stylesheet */
92 void narrative_free(Narrative *n)
93 {
94         int i;
95         for ( i=0; i<n->n_items; i++ ) {
96                 narrative_item_destroy(&n->items[i]);
97         }
98         free(n->items);
99         free(n);
100 }
101
102
103 void narrative_add_empty_item(Narrative *n)
104 {
105         struct text_run *runs;
106
107         runs = malloc(sizeof(struct text_run));
108         if ( runs == NULL ) return;
109
110         runs[0].text = strdup("");
111         runs[0].type = TEXT_RUN_NORMAL;
112         if ( runs[0].text == NULL ) {
113                 free(runs);
114                 return;
115         }
116
117         narrative_add_text(n, runs, 1);
118 }
119
120
121 Narrative *narrative_load(GFile *file)
122 {
123         GBytes *bytes;
124         const char *text;
125         size_t len;
126         Narrative *n;
127
128         bytes = g_file_load_bytes(file, NULL, NULL, NULL);
129         if ( bytes == NULL ) return NULL;
130
131         text = g_bytes_get_data(bytes, &len);
132         n = storycode_parse_presentation(text);
133         g_bytes_unref(bytes);
134         if ( n == NULL ) return NULL;
135
136         if ( n->n_items == 0 ) {
137                 /* Presentation is empty.  Add a dummy to start things off */
138                 narrative_add_empty_item(n);
139         }
140
141         imagestore_set_parent(n->imagestore, g_file_get_parent(file));
142         return n;
143 }
144
145
146 int narrative_save(Narrative *n, GFile *file)
147 {
148         GFileOutputStream *fh;
149         int r;
150         GError *error = NULL;
151
152         if ( file == NULL ) {
153                 fprintf(stderr, "Saving to NULL!\n");
154                 return 1;
155         }
156
157         fh = g_file_replace(file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
158         if ( fh == NULL ) {
159                 fprintf(stderr, _("Open failed: %s\n"), error->message);
160                 return 1;
161         }
162         r = storycode_write_presentation(n, G_OUTPUT_STREAM(fh));
163         if ( r ) {
164                 fprintf(stderr, _("Couldn't save presentation\n"));
165         }
166         g_object_unref(fh);
167
168         imagestore_set_parent(n->imagestore, g_file_get_parent(file));
169
170         n->saved = 1;
171         return 0;
172 }
173
174
175 void narrative_set_unsaved(Narrative *n)
176 {
177         n->saved = 0;
178 }
179
180
181 int narrative_get_unsaved(Narrative *n)
182 {
183         return !n->saved;
184 }
185
186
187 int narrative_item_is_text(Narrative *n, int item)
188 {
189         if ( n->items[item].type == NARRATIVE_ITEM_SLIDE ) return 0;
190         if ( n->items[item].type == NARRATIVE_ITEM_EOP ) return 0;
191         return 1;
192 }
193
194
195 void narrative_add_stylesheet(Narrative *n, Stylesheet *ss)
196 {
197         assert(n->stylesheet == NULL);
198         n->stylesheet = ss;
199 }
200
201
202 Stylesheet *narrative_get_stylesheet(Narrative *n)
203 {
204         if ( n == NULL ) return NULL;
205         return n->stylesheet;
206 }
207
208
209 const char *narrative_get_language(Narrative *n)
210 {
211         if ( n == NULL ) return NULL;
212         return n->language;
213 }
214
215
216 ImageStore *narrative_get_imagestore(Narrative *n)
217 {
218         if ( n == NULL ) return NULL;
219         return n->imagestore;
220 }
221
222
223 static void init_item(struct narrative_item *item)
224 {
225 #ifdef HAVE_PANGO
226         item->layout = NULL;
227 #endif
228         item->runs = NULL;
229         item->n_runs = 0;
230         item->slide = NULL;
231         item->slide_thumbnail = NULL;
232 }
233
234
235 static struct narrative_item *add_item(Narrative *n)
236 {
237         struct narrative_item *new_items;
238         struct narrative_item *item;
239         new_items = realloc(n->items, (n->n_items+1)*sizeof(struct narrative_item));
240         if ( new_items == NULL ) return NULL;
241         n->items = new_items;
242         item = &n->items[n->n_items++];
243         init_item(item);
244         return item;
245 }
246
247
248 /* New item will have index 'pos' */
249 static struct narrative_item *insert_item(Narrative *n, int pos)
250 {
251         add_item(n);
252         memmove(&n->items[pos+1], &n->items[pos],
253                 (n->n_items-pos-1)*sizeof(struct narrative_item));
254         init_item(&n->items[pos]);
255         return &n->items[pos];
256 }
257
258
259 /* The new text item takes ownership of the array of runs */
260 extern void add_text_item(Narrative *n, struct text_run *runs, int n_runs,
261                           enum narrative_item_type type)
262 {
263         struct narrative_item *item;
264
265         item = add_item(n);
266         if ( item == NULL ) return;
267
268         item->type = type;
269         item->align = ALIGN_INHERIT;
270         item->layout = NULL;
271
272         item->runs = runs;
273         item->n_runs = n_runs;
274 }
275
276
277 void narrative_add_text(Narrative *n, struct text_run *runs, int n_runs)
278 {
279         add_text_item(n, runs, n_runs, NARRATIVE_ITEM_TEXT);
280 }
281
282
283 void narrative_add_prestitle(Narrative *n, struct text_run *runs, int n_runs)
284 {
285         add_text_item(n, runs, n_runs, NARRATIVE_ITEM_PRESTITLE);
286 }
287
288
289 void narrative_add_bp(Narrative *n, struct text_run *runs, int n_runs)
290 {
291         add_text_item(n, runs, n_runs, NARRATIVE_ITEM_BP);
292 }
293
294
295 void narrative_add_slide(Narrative *n, Slide *slide)
296 {
297         struct narrative_item *item;
298
299         item = add_item(n);
300         if ( item == NULL ) return;
301
302         item->type = NARRATIVE_ITEM_SLIDE;
303         item->slide = slide;
304         item->slide_thumbnail = NULL;
305 }
306
307
308 void narrative_add_eop(Narrative *n)
309 {
310         struct narrative_item *item;
311
312         item = add_item(n);
313         if ( item == NULL ) return;
314
315         item->type = NARRATIVE_ITEM_EOP;
316 }
317
318
319 void narrative_insert_slide(Narrative *n, Slide *slide, int pos)
320 {
321         struct narrative_item *item = insert_item(n, pos);
322         item->type = NARRATIVE_ITEM_SLIDE;
323         item->slide = slide;
324         item->slide_thumbnail = NULL;
325 }
326
327
328 static void delete_item(Narrative *n, int del)
329 {
330         int i;
331         narrative_item_destroy(&n->items[del]);
332         for ( i=del; i<n->n_items-1; i++ ) {
333                 n->items[i] = n->items[i+1];
334         }
335         n->n_items--;
336 }
337
338
339 /* Delete from item i1 offset o1 to item i2 offset o2, inclusive */
340 void narrative_delete_block(Narrative *n, int i1, size_t o1, int i2, size_t o2)
341 {
342         /* FIXME! */
343 #if 0
344         int i;
345         int n_del = 0;
346         int merge = 1;
347         int middle;    /* This is where the "middle deletion" will begin */
348
349         /* Starting item */
350         if ( !narrative_item_is_text(n, i1) ) {
351                 delete_item(n, i1);
352                 if ( i1 == i2 ) return;  /* only one slide to delete */
353                 middle = i1; /* ... which is now the item just after the slide */
354                 i2--;
355                 merge = 0;
356         } else {
357                 if ( i1 == i2 ) {
358                         memmove(&n->items[i1].text[o1],
359                                 &n->items[i1].text[o2],
360                                 strlen(n->items[i1].text)-o2+1);
361                         return;  /* easy case */
362                 } else {
363                         n->items[i1].text[o1] = '\0';
364                         middle = i1+1;
365                 }
366         }
367
368         /* Middle items */
369         for ( i=middle; i<i2; i++ ) {
370                 /* Deleting the item moves all the subsequent items up, so the
371                  * index to be deleted doesn't change. */
372                 delete_item(n, middle);
373                 n_del++;
374         }
375         i2 -= n_del;
376
377         /* Last item */
378         if ( !narrative_item_is_text(n, i2) ) {
379                 delete_item(n, i2);
380                 return;
381         }
382
383         /* We end with a text item */
384         memmove(&n->items[i2].text[0],
385                 &n->items[i2].text[o2],
386                 strlen(&n->items[i2].text[o2])+1);
387
388         /* If the start and end points are in different paragraphs, and both
389          * of them are text (any kind), merge them.  Note that at this point,
390          * we know that i1 and i2 are different because otherwise we
391          * would've returned earlier ("easy case"). */
392         if ( merge ) {
393                 char *new_text;
394                 size_t len = strlen(n->items[i1].text);
395                 assert(i1 != i2);
396                 len += strlen(n->items[i2].text);
397                 new_text = malloc(len+1);
398                 if ( new_text == NULL ) return;
399                 strcpy(new_text, n->items[i1].text);
400                 strcat(new_text, n->items[i2].text);
401                 free(n->items[i1].text);
402                 n->items[i1].text = new_text;
403                 delete_item(n, i2);
404         }
405 #endif
406 }
407
408
409 int which_run(struct narrative_item *item, size_t item_offs, size_t *run_offs)
410 {
411         int run;
412         size_t pos = 0;
413
414         for ( run=0; run<item->n_runs; run++ ) {
415                 size_t npos = pos + strlen(item->runs[run].text);
416                 if ( npos >= item_offs ) break;
417                 pos = npos;
418         }
419         if ( run_offs != NULL ) {
420                 *run_offs = item_offs - pos;
421         }
422         return run;
423 }
424
425 void narrative_split_item(Narrative *n, int i1, size_t o1)
426 {
427         struct narrative_item *item1;
428         struct narrative_item *item2;
429
430         item2 = insert_item(n, i1+1);
431         item1 = &n->items[i1];  /* NB n->items was realloced by insert_item */
432         item2->type = NARRATIVE_ITEM_TEXT;
433
434         if ( narrative_item_is_text(n, i1) ) {
435
436                 size_t run_offs;
437                 int run = which_run(item1, o1, &run_offs);
438                 int j;
439
440                 item2->n_runs = item1->n_runs - run;
441                 item2->runs = malloc(item2->n_runs*sizeof(struct text_run));
442                 for ( j=run; j<item1->n_runs; j++ ) {
443                         item2->runs[j-run] = item1->runs[j];
444                 }
445
446                 /* Now break the run */
447                 item2->runs[0].text = strdup(item1->runs[run].text+run_offs);
448                 item1->runs[run].text[run_offs] = '\0';
449                 item1->n_runs = run + 1;
450
451         } else {
452
453                 /* Splitting a non-text run simply means creating a new
454                  * plain text item after it */
455                 item2->runs = malloc(sizeof(struct text_run));
456                 item2->n_runs = 1;
457                 item2->runs[0].text = strdup("");
458                 item2->runs[0].type = TEXT_RUN_NORMAL;;
459
460         }
461 }
462
463
464 int narrative_get_num_items(Narrative *n)
465 {
466         return n->n_items;
467 }
468
469
470 int narrative_get_num_items_to_eop(Narrative *n)
471 {
472         int i;
473         for ( i=0; i<n->n_items; i++ ) {
474                 if ( n->items[i].type == NARRATIVE_ITEM_EOP ) return i;
475         }
476         return n->n_items;
477 }
478
479
480 int narrative_get_num_slides(Narrative *n)
481 {
482         int i;
483         int ns = 0;
484         for ( i=0; i<n->n_items; i++ ) {
485                 if ( n->items[i].type == NARRATIVE_ITEM_SLIDE ) ns++;
486         }
487         return ns;
488 }
489
490
491 Slide *narrative_get_slide(Narrative *n, int para)
492 {
493         if ( para >= n->n_items ) return NULL;
494         if ( n->items[para].type != NARRATIVE_ITEM_SLIDE ) return NULL;
495         return n->items[para].slide;
496 }
497
498
499 int narrative_get_slide_number_for_para(Narrative *n, int para)
500 {
501         int i;
502         int ns = 0;
503         for ( i=0; i<para; i++ ) {
504                 if ( n->items[i].type == NARRATIVE_ITEM_SLIDE ) ns++;
505         }
506         return ns;
507 }
508
509
510 int narrative_get_slide_number_for_slide(Narrative *n, Slide *s)
511 {
512         int i;
513         int ns = 0;
514         for ( i=0; i<n->n_items; i++ ) {
515                 if ( n->items[i].type == NARRATIVE_ITEM_SLIDE ) {
516                         if ( n->items[i].slide == s ) return ns;
517                         ns++;
518                 }
519         }
520         return n->n_items;
521 }
522
523
524 Slide *narrative_get_slide_by_number(Narrative *n, int pos)
525 {
526         int i;
527         int ns = 0;
528         for ( i=0; i<n->n_items; i++ ) {
529                 if ( n->items[i].type == NARRATIVE_ITEM_SLIDE ) {
530                         if ( ns == pos ) return n->items[i].slide;
531                         ns++;
532                 }
533         }
534         return NULL;
535 }
536
537
538 static void debug_runs(struct narrative_item *item)
539 {
540         int j;
541         for ( j=0; j<item->n_runs; j++ ) {
542                 printf("Run %i: '%s'\n", j, item->runs[j].text);
543         }
544 }
545
546 void narrative_debug(Narrative *n)
547 {
548         int i;
549
550         for ( i=0; i<n->n_items; i++ ) {
551
552                 struct narrative_item *item = &n->items[i];
553                 printf("Item %i ", i);
554                 switch ( item->type ) {
555
556                         case NARRATIVE_ITEM_TEXT :
557                         printf("(text):\n");
558                         debug_runs(item);
559                         break;
560
561                         case NARRATIVE_ITEM_BP :
562                         printf("(bp):\n");
563                         debug_runs(item);
564                         break;
565
566                         case NARRATIVE_ITEM_PRESTITLE :
567                         printf("(prestitle):\n");
568                         debug_runs(item);
569                         break;
570
571                         case NARRATIVE_ITEM_EOP :
572                         printf("(EOP marker)\n");
573                         break;
574
575                         case NARRATIVE_ITEM_SLIDE :
576                         printf("Slide:\n");
577                         describe_slide(item->slide);
578                         break;
579
580                 }
581
582         }
583 }