Honour alignment in stylesheet
[colloquium.git] / src / sc_interp.c
1 /*
2  * sc_interp.c
3  *
4  * Copyright © 2014-2018 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 <assert.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <pango/pangocairo.h>
32 #include <gdk/gdk.h>
33
34 #include "imagestore.h"
35 #include "sc_parse.h"
36 #include "sc_interp.h"
37 #include "presentation.h"
38 #include "utils.h"
39
40
41 struct sc_state
42 {
43         PangoFontDescription *fontdesc;
44         PangoFont *font;
45         PangoAlignment alignment;
46         double col[4];
47         double bgcol[4];
48         double bgcol2[4];
49         GradientType bggrad;
50         int ascent;
51         int height;
52         float paraspace[4];
53
54         int have_size;
55         double slide_width;
56         double slide_height;
57
58         struct frame *fr;  /* The current frame */
59 };
60
61 struct _scinterp
62 {
63         PangoContext *pc;
64         PangoLanguage *lang;
65         ImageStore *is;
66
67         struct slide_constants *s_constants;
68         struct presentation_constants *p_constants;
69
70         struct sc_state *state;
71         int j;  /* Index of the current state */
72         int max_state;
73
74         SCCallbackList *cbl;
75 };
76
77 struct _sccallbacklist
78 {
79         int n_callbacks;
80         int max_callbacks;
81         char **names;
82         SCCallbackBoxFunc *box_funcs;
83         SCCallbackDrawFunc *draw_funcs;
84         SCCallbackClickFunc *click_funcs;
85         void **vps;
86 };
87
88
89 static int sc_interp_add_blocks(SCInterpreter *scin, SCBlock *bl, Stylesheet *ss)
90 {
91         while ( bl != NULL ) {
92                 if ( sc_interp_add_block(scin, bl, ss) ) return 1;
93                 bl = sc_block_next(bl);
94         }
95
96         return 0;
97 }
98
99
100 SCCallbackList *sc_callback_list_new()
101 {
102         SCCallbackList *cbl;
103
104         cbl = malloc(sizeof(struct _sccallbacklist));
105         if ( cbl == NULL ) return NULL;
106
107         cbl->names = calloc(8, sizeof(char *));
108         if ( cbl->names == NULL ) return NULL;
109
110         cbl->box_funcs = calloc(8, sizeof(cbl->box_funcs[0]));
111         if ( cbl->box_funcs == NULL ) return NULL;
112
113         cbl->draw_funcs = calloc(8, sizeof(cbl->draw_funcs[0]));
114         if ( cbl->draw_funcs == NULL ) return NULL;
115
116         cbl->click_funcs = calloc(8, sizeof(cbl->click_funcs[0]));
117         if ( cbl->click_funcs == NULL ) return NULL;
118
119         cbl->vps = calloc(8, sizeof(cbl->vps[0]));
120         if ( cbl->vps == NULL ) return NULL;
121
122         cbl->max_callbacks = 8;
123         cbl->n_callbacks = 0;
124
125         return cbl;
126 }
127
128
129 void sc_callback_list_free(SCCallbackList *cbl)
130 {
131         int i;
132
133         if ( cbl == NULL ) return;
134
135         for ( i=0; i<cbl->n_callbacks; i++ ) {
136                 free(cbl->names[i]);
137         }
138
139         free(cbl->names);
140         free(cbl->box_funcs);
141         free(cbl->draw_funcs);
142         free(cbl->vps);
143         free(cbl);
144
145 }
146
147
148 void sc_callback_list_add_callback(SCCallbackList *cbl, const char *name,
149                                    SCCallbackBoxFunc box_func,
150                                    SCCallbackDrawFunc draw_func,
151                                    SCCallbackClickFunc click_func,
152                                    void *vp)
153 {
154         if ( cbl->n_callbacks == cbl->max_callbacks ) {
155
156                 SCCallbackBoxFunc *box_funcs_new;
157                 SCCallbackDrawFunc *draw_funcs_new;
158                 SCCallbackClickFunc *click_funcs_new;
159                 char **names_new;
160                 void **vps_new;
161                 int mcn = cbl->max_callbacks + 8;
162
163                 names_new = realloc(cbl->names, mcn*sizeof(char *));
164                 box_funcs_new = realloc(cbl->box_funcs,
165                                         mcn*sizeof(SCCallbackBoxFunc));
166                 draw_funcs_new = realloc(cbl->draw_funcs,
167                                         mcn*sizeof(SCCallbackDrawFunc));
168                 click_funcs_new = realloc(cbl->click_funcs,
169                                         mcn*sizeof(SCCallbackClickFunc));
170                 vps_new = realloc(cbl->vps, mcn*sizeof(void *));
171
172                 if ( (names_new == NULL) || (box_funcs_new == NULL)
173                   || (vps_new == NULL) || (draw_funcs_new == NULL)
174                   || (click_funcs_new == NULL) ) {
175                         fprintf(stderr, _("Failed to grow callback list\n"));
176                         return;
177                 }
178
179                 cbl->names = names_new;
180                 cbl->box_funcs = box_funcs_new;
181                 cbl->draw_funcs = draw_funcs_new;
182                 cbl->click_funcs = click_funcs_new;
183                 cbl->vps = vps_new;
184                 cbl->max_callbacks = mcn;
185
186         }
187
188         cbl->names[cbl->n_callbacks] = strdup(name);
189         cbl->box_funcs[cbl->n_callbacks] = box_func;
190         cbl->draw_funcs[cbl->n_callbacks] = draw_func;
191         cbl->click_funcs[cbl->n_callbacks] = click_func;
192         cbl->vps[cbl->n_callbacks] = vp;
193         cbl->n_callbacks++;
194 }
195
196
197 void sc_interp_set_callbacks(SCInterpreter *scin, SCCallbackList *cbl)
198 {
199         if ( scin->cbl != NULL ) {
200                 fprintf(stderr, _("WARNING: Interpreter already has a callback "
201                                   "list.\n"));
202         }
203         scin->cbl = cbl;
204 }
205
206
207 static int check_callback(SCInterpreter *scin, SCBlock *bl)
208 {
209         int i;
210         const char *name = sc_block_name(bl);
211         SCCallbackList *cbl = scin->cbl;
212
213         /* No callback list -> easy */
214         if ( cbl == NULL ) return 0;
215
216         /* No name -> definitely not a callback */
217         if ( name == NULL ) return 0;
218
219         for ( i=0; i<cbl->n_callbacks; i++ ) {
220
221                 double w, h;
222                 int r;
223                 void *bvp;
224
225                 if ( strcmp(cbl->names[i], name) != 0 ) continue;
226                 r = cbl->box_funcs[i](scin, bl, &w, &h, &bvp, cbl->vps[i]);
227                 if ( r )  {
228                         struct sc_state *st = &scin->state[scin->j];
229                         Paragraph *pnew;
230                         pnew = add_callback_para(sc_interp_get_frame(scin),
231                                                  bl, w, h,
232                                                  cbl->draw_funcs[i],
233                                                  cbl->click_funcs[i],
234                                                  bvp, cbl->vps[i]);
235                         if ( pnew != NULL ) {
236                                 set_para_spacing(pnew, st->paraspace);
237                         }
238
239                 }
240                 return 1;
241
242         }
243
244         return 0;
245 }
246
247
248 PangoFont *sc_interp_get_font(SCInterpreter *scin)
249 {
250         struct sc_state *st = &scin->state[scin->j];
251         return st->font;
252 }
253
254
255 PangoFontDescription *sc_interp_get_fontdesc(SCInterpreter *scin)
256 {
257         struct sc_state *st = &scin->state[scin->j];
258         return st->fontdesc;
259 }
260
261
262 double *sc_interp_get_fgcol(SCInterpreter *scin)
263 {
264         struct sc_state *st = &scin->state[scin->j];
265         return st->col;
266 }
267
268
269 static void set_frame_default_style(struct frame *fr, SCInterpreter *scin)
270 {
271         if ( fr == NULL ) return;
272
273         if ( fr->fontdesc != NULL ) {
274                 pango_font_description_free(fr->fontdesc);
275         }
276         fr->fontdesc = pango_font_description_copy(sc_interp_get_fontdesc(scin));
277         fr->col[0] = sc_interp_get_fgcol(scin)[0];
278         fr->col[1] = sc_interp_get_fgcol(scin)[1];
279         fr->col[2] = sc_interp_get_fgcol(scin)[2];
280         fr->col[3] = sc_interp_get_fgcol(scin)[3];
281 }
282
283
284 static void update_font(SCInterpreter *scin)
285 {
286         PangoFontMetrics *metrics;
287         struct sc_state *st = &scin->state[scin->j];
288
289         if ( scin->pc == NULL ) return;
290
291         st->font = pango_font_map_load_font(pango_context_get_font_map(scin->pc),
292                                             scin->pc, st->fontdesc);
293         if ( st->font == NULL ) {
294                 char *f = pango_font_description_to_string(st->fontdesc);
295                 fprintf(stderr, _("Couldn't load font '%s' (font map %p, pc %p)\n"),
296                         f, pango_context_get_font_map(scin->pc), scin->pc);
297                 g_free(f);
298                 return;
299         }
300
301         /* FIXME: Language for box */
302         metrics = pango_font_get_metrics(st->font, NULL);
303         st->ascent = pango_font_metrics_get_ascent(metrics);
304         st->height = st->ascent + pango_font_metrics_get_descent(metrics);
305         pango_font_metrics_unref(metrics);
306         set_frame_default_style(sc_interp_get_frame(scin), scin);
307 }
308
309
310 static void set_font(SCInterpreter *scin, const char *font_name)
311 {
312         struct sc_state *st = &scin->state[scin->j];
313
314         st->fontdesc = pango_font_description_from_string(font_name);
315         if ( st->fontdesc == NULL ) {
316                 fprintf(stderr, _("Couldn't describe font.\n"));
317                 return;
318         }
319
320         update_font(scin);
321 }
322
323
324 static void copy_top_fontdesc(SCInterpreter *scin)
325 {
326         struct sc_state *st = &scin->state[scin->j];
327
328         /* If this is the first stack frame, don't even check */
329         if ( scin->j == 0 ) return;
330
331         /* If the fontdesc at the top of the stack is the same as the one
332          * below, make a copy because we're about to do something to it (which
333          * should not affect the next level up). */
334         if ( st->fontdesc == scin->state[scin->j-1].fontdesc ) {
335                 st->fontdesc = pango_font_description_copy(st->fontdesc);
336         }
337 }
338
339
340 static void set_fontsize(SCInterpreter *scin, const char *size_str)
341 {
342         struct sc_state *st = &scin->state[scin->j];
343         int size;
344         char *end;
345
346         if ( size_str[0] == '\0' ) return;
347
348         size = strtoul(size_str, &end, 10);
349         if ( end[0] != '\0' ) {
350                 fprintf(stderr, _("Invalid font size '%s'\n"), size_str);
351                 return;
352         }
353
354         copy_top_fontdesc(scin);
355         pango_font_description_set_size(st->fontdesc, size*PANGO_SCALE);
356         update_font(scin);
357 }
358
359
360 static void set_bold(SCInterpreter *scin)
361 {
362         struct sc_state *st = &scin->state[scin->j];
363         copy_top_fontdesc(scin);
364         pango_font_description_set_weight(st->fontdesc, PANGO_WEIGHT_BOLD);
365         update_font(scin);
366 }
367
368
369 static void set_oblique(SCInterpreter *scin)
370 {
371         struct sc_state *st = &scin->state[scin->j];
372         copy_top_fontdesc(scin);
373         pango_font_description_set_style(st->fontdesc, PANGO_STYLE_OBLIQUE);
374         update_font(scin);
375 }
376
377
378 static void set_italic(SCInterpreter *scin)
379 {
380         struct sc_state *st = &scin->state[scin->j];
381         copy_top_fontdesc(scin);
382         pango_font_description_set_style(st->fontdesc, PANGO_STYLE_ITALIC);
383         update_font(scin);
384 }
385
386
387 static void set_alignment(SCInterpreter *scin, PangoAlignment align)
388 {
389         struct sc_state *st = &scin->state[scin->j];
390         st->alignment = align;
391 }
392
393
394 /* This sets the colour for the font at the top of the stack */
395 static void set_colour(SCInterpreter *scin, const char *colour)
396 {
397         GdkRGBA col;
398         struct sc_state *st = &scin->state[scin->j];
399
400         if ( colour == NULL ) {
401                 printf(_("Invalid colour\n"));
402                 st->col[0] = 0.0;
403                 st->col[1] = 0.0;
404                 st->col[2] = 0.0;
405                 st->col[3] = 1.0;
406                 return;
407         }
408
409         gdk_rgba_parse(&col, colour);
410
411         st->col[0] = col.red;
412         st->col[1] = col.green;
413         st->col[2] = col.blue;
414         st->col[3] = col.alpha;
415         set_frame_default_style(sc_interp_get_frame(scin), scin);
416 }
417
418
419 static void update_bg(SCInterpreter *scin)
420 {
421         struct sc_state *st = &scin->state[scin->j];
422         struct frame *fr = sc_interp_get_frame(scin);
423
424         if ( fr == NULL ) return;
425
426         fr->bgcol[0] = st->bgcol[0];
427         fr->bgcol[1] = st->bgcol[1];
428         fr->bgcol[2] = st->bgcol[2];
429         fr->bgcol[3] = st->bgcol[3];
430         fr->bgcol2[0] = st->bgcol2[0];
431         fr->bgcol2[1] = st->bgcol2[1];
432         fr->bgcol2[2] = st->bgcol2[2];
433         fr->bgcol2[3] = st->bgcol2[3];
434         fr->grad = st->bggrad;
435 }
436
437
438 static void set_bgcol(SCInterpreter *scin, const char *colour)
439 {
440         GdkRGBA col;
441         struct sc_state *st = &scin->state[scin->j];
442
443         if ( colour == NULL ) {
444                 printf(_("Invalid colour\n"));
445                 st->bgcol[0] = 0.0;
446                 st->bgcol[1] = 0.0;
447                 st->bgcol[2] = 0.0;
448                 st->bgcol[3] = 1.0;
449                 return;
450         }
451
452         gdk_rgba_parse(&col, colour);
453
454         st->bgcol[0] = col.red;
455         st->bgcol[1] = col.green;
456         st->bgcol[2] = col.blue;
457         st->bgcol[3] = col.alpha;
458         st->bggrad = GRAD_NONE;
459 }
460
461
462 static void set_bggrad(SCInterpreter *scin, const char *options,
463                        GradientType grad)
464 {
465         struct sc_state *st = &scin->state[scin->j];
466         GdkRGBA col1, col2;
467         char *n2;
468         char *optcopy = strdup(options);
469
470         if ( options == NULL ) {
471                 fprintf(stderr, _("Invalid bg gradient spec '%s'\n"), options);
472                 return;
473         }
474
475         n2 = strchr(optcopy, ',');
476         if ( n2 == NULL ) {
477                 fprintf(stderr, _("Invalid bg gradient spec '%s'\n"), options);
478                 return;
479         }
480
481         n2[0] = '\0';
482
483         gdk_rgba_parse(&col1, optcopy);
484         gdk_rgba_parse(&col2, &n2[1]);
485
486         st->bgcol[0] = col1.red;
487         st->bgcol[1] = col1.green;
488         st->bgcol[2] = col1.blue;
489         st->bgcol[3] = col1.alpha;
490
491         st->bgcol2[0] = col2.red;
492         st->bgcol2[1] = col2.green;
493         st->bgcol2[2] = col2.blue;
494         st->bgcol2[3] = col2.alpha;
495
496         st->bggrad = grad;
497
498         free(optcopy);
499 }
500
501
502 void sc_interp_save(SCInterpreter *scin)
503 {
504         if ( scin->j+1 == scin->max_state ) {
505
506                 struct sc_state *stack_new;
507
508                 stack_new = realloc(scin->state, sizeof(struct sc_state)
509                                      * (scin->max_state+8));
510                 if ( stack_new == NULL ) {
511                         fprintf(stderr, _("Failed to add to stack.\n"));
512                         return;
513                 }
514
515                 scin->state = stack_new;
516                 scin->max_state += 8;
517
518         }
519
520         /* When n_fonts=0, we leave the first font uninitialised.  This allows
521          * the stack to be "bootstrapped", but requires the first caller to do
522          * set_font and set_colour straight away. */
523         scin->state[scin->j+1] = scin->state[scin->j];
524
525         scin->j++;
526 }
527
528
529 void sc_interp_restore(SCInterpreter *scin)
530 {
531         struct sc_state *st = &scin->state[scin->j];
532
533         if ( scin->j > 0 ) {
534                 if ( st->fontdesc != scin->state[scin->j-1].fontdesc )
535                 {
536                         pango_font_description_free(st->fontdesc);
537                 } /* else the font is the same as the previous one, and we
538                    * don't need to free it just yet */
539         }
540
541         scin->j--;
542 }
543
544
545 struct frame *sc_interp_get_frame(SCInterpreter *scin)
546 {
547         struct sc_state *st = &scin->state[scin->j];
548         return st->fr;
549 }
550
551
552 static void set_frame(SCInterpreter *scin, struct frame *fr)
553 {
554         struct sc_state *st = &scin->state[scin->j];
555         st->fr = fr;
556 }
557
558
559 SCInterpreter *sc_interp_new(PangoContext *pc, PangoLanguage *lang,
560                              ImageStore *is, struct frame *top)
561 {
562         SCInterpreter *scin;
563         struct sc_state *st;
564
565         scin = malloc(sizeof(SCInterpreter));
566         if ( scin == NULL ) return NULL;
567
568         scin->state = malloc(8*sizeof(struct sc_state));
569         if ( scin->state == NULL ) {
570                 free(scin);
571                 return NULL;
572         }
573         scin->j = 0;
574         scin->max_state = 8;
575
576         scin->pc = pc;
577         scin->is = is;
578         scin->s_constants = NULL;
579         scin->p_constants = NULL;
580         scin->cbl = NULL;
581
582         st = &scin->state[0];
583         st->fr = NULL;
584         st->paraspace[0] = 0.0;
585         st->paraspace[1] = 0.0;
586         st->paraspace[2] = 0.0;
587         st->paraspace[3] = 0.0;
588         st->fontdesc = NULL;
589         st->have_size = 0;
590         st->col[0] = 0.0;
591         st->col[1] = 0.0;
592         st->col[2] = 0.0;
593         st->col[3] = 1.0;
594         st->alignment = PANGO_ALIGN_LEFT;
595         st->bgcol[0] = 1.0;
596         st->bgcol[1] = 1.0;
597         st->bgcol[2] = 1.0;
598         st->bgcol[3] = 1.0;
599         st->bgcol2[0] = 1.0;
600         st->bgcol2[1] = 1.0;
601         st->bgcol2[2] = 1.0;
602         st->bgcol2[3] = 1.0;
603         st->bggrad = GRAD_NOBG;
604         scin->lang = lang;
605
606         /* The "ultimate" default font */
607         if ( scin->pc != NULL ) {
608                 set_frame(scin, top);
609                 set_font(scin, "Cantarell Regular 14");
610                 set_colour(scin, "#000000");
611                 update_bg(scin);
612         }
613
614         return scin;
615 }
616
617
618 void sc_interp_destroy(SCInterpreter *scin)
619 {
620         /* Empty the stack */
621         while ( scin->j > 0 ) {
622                 sc_interp_restore(scin);
623         }
624
625         if ( scin->state[0].fontdesc != NULL ) {
626                 pango_font_description_free(scin->state[0].fontdesc);
627         }
628
629         free(scin->state);
630         free(scin);
631 }
632
633
634 static void set_padding(struct frame *fr, const char *opts)
635 {
636         float p[4];
637
638         if ( parse_tuple(opts, p) ) return;
639
640         if ( fr == NULL ) return;
641
642         fr->pad_l = p[0];
643         fr->pad_r = p[1];
644         fr->pad_t = p[2];
645         fr->pad_b = p[3];
646 }
647
648
649 static void set_paraspace(SCInterpreter *scin, const char *opts)
650 {
651         float p[4];
652         struct sc_state *st = &scin->state[scin->j];
653
654         if ( parse_tuple(opts, p) ) return;
655
656         st->paraspace[0] = p[0];
657         st->paraspace[1] = p[1];
658         st->paraspace[2] = p[2];
659         st->paraspace[3] = p[3];
660
661         set_para_spacing(last_para(sc_interp_get_frame(scin)), p);
662 }
663
664
665 void update_geom(struct frame *fr)
666 {
667         char geom[256];
668         snprintf(geom, 255, "%.1fux%.1fu+%.1f+%.1f",
669                  fr->w, fr->h, fr->x, fr->y);
670
671         /* FIXME: What if there are other options? */
672         sc_block_set_options(fr->scblocks, strdup(geom));
673 }
674
675
676 static LengthUnits get_units(const char *t)
677 {
678         size_t len = strlen(t);
679
680         if ( t[len-1] == 'f' ) return UNITS_FRAC;
681         if ( t[len-1] == 'u' ) return UNITS_SLIDE;
682
683         fprintf(stderr, _("Invalid units in '%s'\n"), t);
684         return UNITS_SLIDE;
685 }
686
687
688 static int parse_dims(const char *opt, struct frame *parent,
689                       double *wp, double *hp, double *xp, double *yp)
690 {
691         char *w;
692         char *h;
693         char *x;
694         char *y;
695         char *check;
696         LengthUnits h_units, w_units;
697
698         /* Looks like a dimension/position thing */
699         w = strdup(opt);
700         h = index(w, 'x');
701         h[0] = '\0';  h++;
702
703         x = index(h, '+');
704         if ( x == NULL ) goto invalid;
705         x[0] = '\0';  x++;
706
707         y = index(x, '+');
708         if ( x == NULL ) goto invalid;
709         y[0] = '\0';  y++;
710
711         *wp = strtod(w, &check);
712         if ( check == w ) goto invalid;
713         w_units = get_units(w);
714         if ( w_units == UNITS_FRAC ) {
715                 if ( parent != NULL ) {
716                         double pw = parent->w;
717                         pw -= parent->pad_l;
718                         pw -= parent->pad_r;
719                         *wp = pw * *wp;
720                 } else {
721                         *wp = -1.0;
722                 }
723
724         }
725
726         *hp = strtod(h, &check);
727         if ( check == h ) goto invalid;
728         h_units = get_units(h);
729         if ( h_units == UNITS_FRAC ) {
730                 if ( parent != NULL ) {
731                         double ph = parent->h;
732                         ph -= parent->pad_t;
733                         ph -= parent->pad_b;
734                         *hp = ph * *hp;
735                 } else {
736                         *hp = -1.0;
737                 }
738         }
739
740         *xp= strtod(x, &check);
741         if ( check == x ) goto invalid;
742         *yp = strtod(y, &check);
743         if ( check == y ) goto invalid;
744
745         return 0;
746
747 invalid:
748         fprintf(stderr, _("Invalid dimensions '%s'\n"), opt);
749         return 1;
750 }
751
752
753 static int parse_frame_option(const char *opt, struct frame *fr,
754                               struct frame *parent)
755 {
756         if ( (index(opt, 'x') != NULL) && (index(opt, '+') != NULL)
757           && (index(opt, '+') != rindex(opt, '+')) ) {
758                 return parse_dims(opt, parent, &fr->w, &fr->h, &fr->x, &fr->y);
759         }
760
761         fprintf(stderr, _("Unrecognised frame option '%s'\n"), opt);
762
763         return 1;
764 }
765
766
767 static int parse_frame_options(struct frame *fr, struct frame *parent,
768                                const char *opth)
769 {
770         int i;
771         size_t len;
772         size_t start;
773         char *opt;
774
775         if ( opth == NULL ) return 1;
776
777         opt = strdup(opth);
778
779         len = strlen(opt);
780         start = 0;
781
782         for ( i=0; i<len; i++ ) {
783
784                 /* FIXME: comma might be escaped or quoted */
785                 if ( opt[i] == ',' ) {
786                         opt[i] = '\0';
787                         if ( parse_frame_option(opt+start, fr, parent) ) {
788                                 return 1;
789                         }
790                         start = i+1;
791                 }
792
793         }
794
795         if ( start != len ) {
796                 if ( parse_frame_option(opt+start, fr, parent) ) return 1;
797         }
798
799         free(opt);
800
801         return 0;
802 }
803
804
805 static int parse_image_option(const char *opt, struct frame *parent,
806                               double *wp, double *hp, char **filenamep)
807 {
808         if ( (index(opt, 'x') != NULL) && (index(opt, '+') != NULL)
809           && (index(opt, '+') != rindex(opt, '+')) ) {
810                 double dum;
811                 return parse_dims(opt, NULL, wp, hp, &dum, &dum);
812         }
813
814         if ( strncmp(opt, "filename=\"", 10) == 0 ) {
815                 char *fn;
816                 fn = strdup(opt+10);
817                 if ( fn[strlen(fn)-1] != '\"' ) {
818                         fprintf(stderr, _("Unterminated filename?\n"));
819                         free(fn);
820                         return 1;
821                 }
822                 fn[strlen(fn)-1] = '\0';
823                 *filenamep = fn;
824                 return 0;
825         }
826
827         fprintf(stderr, _("Unrecognised image option '%s'\n"), opt);
828
829         return 1;
830 }
831
832
833 static int parse_image_options(const char *opth, struct frame *parent,
834                                double *wp, double *hp, char **filenamep)
835 {
836         int i;
837         size_t len;
838         size_t start;
839         char *opt;
840
841         if ( opth == NULL ) return 1;
842
843         opt = strdup(opth);
844
845         len = strlen(opt);
846         start = 0;
847
848         for ( i=0; i<len; i++ ) {
849
850                 /* FIXME: comma might be escaped or quoted */
851                 if ( opt[i] == ',' ) {
852                         opt[i] = '\0';
853                         if ( parse_image_option(opt+start, parent,
854                                                 wp, hp, filenamep) ) return 1;
855                         start = i+1;
856                 }
857
858         }
859
860         if ( start != len ) {
861                 if ( parse_image_option(opt+start, parent,
862                                         wp, hp, filenamep) ) return 1;
863         }
864
865         free(opt);
866
867         return 0;
868 }
869
870
871 static void maybe_recurse_before(SCInterpreter *scin, SCBlock *child)
872 {
873         if ( child == NULL ) return;
874
875         sc_interp_save(scin);
876 }
877
878
879 static void maybe_recurse_after(SCInterpreter *scin, SCBlock *child,
880                                 Stylesheet *ss)
881 {
882         if ( child == NULL ) return;
883
884         sc_interp_add_blocks(scin, child, ss);
885         sc_interp_restore(scin);
886 }
887
888
889 static void add_newpara(struct frame *fr, SCBlock *bl)
890 {
891         Paragraph *last_para;
892
893         if ( fr->paras == NULL ) return;
894         last_para = fr->paras[fr->n_paras-1];
895
896         set_newline_at_end(last_para, bl);
897
898         /* The block after the \newpara will always be the first one of the
899          * next paragraph, by definition, even if it's \f or another \newpara */
900         create_paragraph(fr, sc_block_next(bl));
901 }
902
903
904 /* Add the SCBlock to the text in 'frame', at the end */
905 static int add_text(struct frame *fr, PangoContext *pc, SCBlock *bl,
906                     PangoLanguage *lang, int editable, SCInterpreter *scin)
907 {
908         const char *text = sc_block_contents(bl);
909         PangoFontDescription *fontdesc;
910         double *col;
911         struct sc_state *st = &scin->state[scin->j];
912         Paragraph *para;
913
914         /* Empty block? */
915         if ( text == NULL ) return 1;
916
917         fontdesc = sc_interp_get_fontdesc(scin);
918         col = sc_interp_get_fgcol(scin);
919
920         para = last_para(fr);
921         if ( (para == NULL) || (para_type(para) != PARA_TYPE_TEXT) ) {
922                 /* Last paragraph is not text.
923                  *  or: no paragraphs yet.
924                  *    Either way: Create the first one */
925                 para = create_paragraph(fr, bl);
926         }
927
928         set_para_alignment(para, st->alignment);
929         add_run(para, bl, fontdesc, col);
930         set_para_spacing(para, st->paraspace);
931
932         return 0;
933 }
934
935
936 static void apply_style(SCInterpreter *scin, Stylesheet *ss, const char *path)
937 {
938         char fullpath[256];
939         size_t len;
940         char *result;
941
942         len = strlen(path);
943         if ( len > 160 ) {
944                 fprintf(stderr, "Can't apply style: path too long.\n");
945                 return;
946         }
947
948         if ( ss == NULL ) return;
949
950         /* Font */
951         strcpy(fullpath, path);
952         strcat(fullpath, ".font");
953         result = stylesheet_lookup(ss, fullpath);
954         if ( result != NULL ) set_font(scin, result);
955
956         /* Foreground colour */
957         strcpy(fullpath, path);
958         strcat(fullpath, ".fgcol");
959         result = stylesheet_lookup(ss, fullpath);
960         if ( result != NULL ) set_colour(scin, result);
961
962         /* Background (vertical gradient) */
963         strcpy(fullpath, path);
964         strcat(fullpath, ".bggradv");
965         result = stylesheet_lookup(ss, fullpath);
966         if ( result != NULL ) set_bggrad(scin, result, GRAD_VERT);
967
968         /* Background (horizontal gradient) */
969         strcpy(fullpath, path);
970         strcat(fullpath, ".bggradh");
971         result = stylesheet_lookup(ss, fullpath);
972         if ( result != NULL ) set_bggrad(scin, result, GRAD_HORIZ);
973
974         /* Background (solid colour) */
975         strcpy(fullpath, path);
976         strcat(fullpath, ".bgcol");
977         result = stylesheet_lookup(ss, fullpath);
978         if ( result != NULL ) set_bgcol(scin, result);
979
980         /* Padding */
981         strcpy(fullpath, path);
982         strcat(fullpath, ".pad");
983         result = stylesheet_lookup(ss, fullpath);
984         if ( result != NULL ) set_padding(sc_interp_get_frame(scin), result);
985
986         /* Paragraph spacing */
987         strcpy(fullpath, path);
988         strcat(fullpath, ".paraspace");
989         result = stylesheet_lookup(ss, fullpath);
990         if ( result != NULL ) set_paraspace(scin, result);
991
992         /* Alignment */
993         strcpy(fullpath, path);
994         strcat(fullpath, ".alignment");
995         result = stylesheet_lookup(ss, fullpath);
996         if ( result != NULL ) {
997                 if ( strcmp(result, "center") == 0 ) {
998                         set_alignment(scin, PANGO_ALIGN_CENTER);
999                 }
1000                 if ( strcmp(result, "left") == 0 ) {
1001                         set_alignment(scin, PANGO_ALIGN_LEFT);
1002                 }
1003                 if ( strcmp(result, "right") == 0 ) {
1004                         set_alignment(scin, PANGO_ALIGN_RIGHT);
1005                 }
1006         }
1007
1008         update_bg(scin);
1009 }
1010
1011
1012 static void output_frame(SCInterpreter *scin, SCBlock *bl, Stylesheet *ss,
1013                          const char *stylename)
1014 {
1015         struct frame *fr;
1016         SCBlock *child = sc_block_child(bl);
1017         const char *options = sc_block_options(bl);
1018         char fullpath[256];
1019         size_t len;
1020         char *result;
1021
1022         len = strlen(stylename);
1023         if ( len > 160 ) {
1024                 fprintf(stderr, "Can't apply style: path too long.\n");
1025                 return;
1026         }
1027
1028         fr = add_subframe(sc_interp_get_frame(scin));
1029         fr->scblocks = bl;
1030         fr->resizable = 1;
1031         if ( fr == NULL ) {
1032                 fprintf(stderr, _("Failed to add frame.\n"));
1033                 return;
1034         }
1035
1036         /* Lowest priority: current state of interpreter */
1037         set_frame_default_style(fr, scin);
1038
1039         /* Next priority: geometry from stylesheet */
1040         strcpy(fullpath, stylename);
1041         strcat(fullpath, ".geometry");
1042         result = stylesheet_lookup(ss, fullpath);
1043         if ( result != NULL ) {
1044                 parse_frame_options(fr, sc_interp_get_frame(scin), result);
1045         }
1046
1047         /* Highest priority: parameters to \f (or \slidetitle etc) */
1048         parse_frame_options(fr, sc_interp_get_frame(scin), options);
1049
1050         maybe_recurse_before(scin, child);
1051         set_frame(scin, fr);
1052         apply_style(scin, ss, stylename);
1053         maybe_recurse_after(scin, child, ss);
1054 }
1055
1056
1057 static int check_outputs(SCBlock *bl, SCInterpreter *scin, Stylesheet *ss)
1058 {
1059         const char *name = sc_block_name(bl);
1060         const char *options = sc_block_options(bl);
1061
1062         if ( name == NULL ) {
1063                 add_text(sc_interp_get_frame(scin),
1064                          scin->pc, bl, scin->lang, 1, scin);
1065
1066         } else if ( strcmp(name, "image")==0 ) {
1067                 double w, h;
1068                 char *filename;
1069                 if ( parse_image_options(options, sc_interp_get_frame(scin),
1070                                          &w, &h, &filename) == 0 )
1071                 {
1072                         add_image_para(sc_interp_get_frame(scin), bl,
1073                                        filename, scin->is, w, h, 1);
1074                         free(filename);
1075                 } else {
1076                         fprintf(stderr, _("Invalid image options '%s'\n"),
1077                                 options);
1078                 }
1079
1080         } else if ( strcmp(name, "f")==0 ) {
1081                 output_frame(scin, bl, ss, "$.slide.frame");
1082
1083         } else if ( strcmp(name, "slidetitle")==0 ) {
1084                 output_frame(scin, bl, ss, "$.slide.slidetitle");
1085
1086         } else if ( strcmp(name, "prestitle")==0 ) {
1087                 output_frame(scin, bl, ss, "$.slide.prestitle");
1088
1089         } else if ( strcmp(name, "author")==0 ) {
1090                 output_frame(scin, bl, ss, "$.slide.author");
1091
1092         } else if ( strcmp(name, "newpara")==0 ) {
1093                 struct frame *fr = sc_interp_get_frame(scin);
1094                 add_newpara(fr, bl);
1095
1096         } else {
1097                 return 0;
1098         }
1099
1100         return 1;  /* handled */
1101 }
1102
1103
1104 int sc_interp_add_block(SCInterpreter *scin, SCBlock *bl, Stylesheet *ss)
1105 {
1106         const char *name = sc_block_name(bl);
1107         const char *options = sc_block_options(bl);
1108         SCBlock *child = sc_block_child(bl);
1109
1110         //printf("Running this --------->\n");
1111         //show_sc_blocks(bl);
1112         //printf("<------------\n");
1113
1114         if ( check_callback(scin, bl) ) {
1115                 /* Handled in check_callback, don't do anything else */
1116
1117         } else if ((sc_interp_get_frame(scin) != NULL)
1118           && check_outputs(bl, scin, ss) ) {
1119                 /* Block handled as output thing */
1120
1121         } else if ( name == NULL ) {
1122                 /* Dummy to ensure name != NULL below */
1123
1124         } else if ( strcmp(name, "presentation") == 0 ) {
1125                 maybe_recurse_before(scin, child);
1126                 apply_style(scin, ss, "$.narrative");
1127                 maybe_recurse_after(scin, child, ss);
1128
1129         } else if ( strcmp(name, "slide") == 0 ) {
1130                 maybe_recurse_before(scin, child);
1131                 apply_style(scin, ss, "$.slide");
1132                 maybe_recurse_after(scin, child, ss);
1133
1134         } else if ( strcmp(name, "font") == 0 ) {
1135                 maybe_recurse_before(scin, child);
1136                 set_font(scin, options);
1137                 maybe_recurse_after(scin, child, ss);
1138
1139         } else if ( strcmp(name, "fontsize") == 0 ) {
1140                 maybe_recurse_before(scin, child);
1141                 set_fontsize(scin, options);
1142                 maybe_recurse_after(scin, child, ss);
1143
1144         } else if ( strcmp(name, "bold") == 0 ) {
1145                 maybe_recurse_before(scin, child);
1146                 set_bold(scin);
1147                 maybe_recurse_after(scin, child, ss);
1148
1149         } else if ( strcmp(name, "oblique") == 0 ) {
1150                 maybe_recurse_before(scin, child);
1151                 set_oblique(scin);
1152                 maybe_recurse_after(scin, child, ss);
1153
1154         } else if ( strcmp(name, "italic") == 0 ) {
1155                 maybe_recurse_before(scin, child);
1156                 set_italic(scin);
1157                 maybe_recurse_after(scin, child, ss);
1158
1159         } else if ( strcmp(name, "lalign") == 0 ) {
1160                 maybe_recurse_before(scin, child);
1161                 set_alignment(scin, PANGO_ALIGN_LEFT);
1162                 maybe_recurse_after(scin, child, ss);
1163
1164         } else if ( strcmp(name, "ralign") == 0 ) {
1165                 maybe_recurse_before(scin, child);
1166                 set_alignment(scin, PANGO_ALIGN_RIGHT);
1167                 maybe_recurse_after(scin, child, ss);
1168
1169         } else if ( strcmp(name, "center") == 0 ) {
1170                 maybe_recurse_before(scin, child);
1171                 set_alignment(scin, PANGO_ALIGN_CENTER);
1172                 maybe_recurse_after(scin, child, ss);
1173
1174         } else if ( strcmp(name, "fgcol") == 0 ) {
1175                 maybe_recurse_before(scin, child);
1176                 set_colour(scin, options);
1177                 maybe_recurse_after(scin, child, ss);
1178
1179         } else if ( strcmp(name, "pad") == 0 ) {
1180                 maybe_recurse_before(scin, child);
1181                 set_padding(sc_interp_get_frame(scin), options);
1182                 maybe_recurse_after(scin, child, ss);
1183
1184         } else if ( strcmp(name, "bgcol") == 0 ) {
1185                 maybe_recurse_before(scin, child);
1186                 set_bgcol(scin, options);
1187                 update_bg(scin);
1188                 maybe_recurse_after(scin, child, ss);
1189
1190         } else if ( strcmp(name, "bggradh") == 0 ) {
1191                 maybe_recurse_before(scin, child);
1192                 set_bggrad(scin, options, GRAD_HORIZ);
1193                 update_bg(scin);
1194                 maybe_recurse_after(scin, child, ss);
1195
1196         } else if ( strcmp(name, "bggradv") == 0 ) {
1197                 maybe_recurse_before(scin, child);
1198                 set_bggrad(scin, options, GRAD_VERT);
1199                 update_bg(scin);
1200                 maybe_recurse_after(scin, child, ss);
1201
1202         } else if ( strcmp(name, "paraspace") == 0 ) {
1203                 maybe_recurse_before(scin, child);
1204                 set_paraspace(scin, options);
1205                 maybe_recurse_after(scin, child, ss);
1206
1207         } else {
1208
1209                 fprintf(stderr, "Don't know what to do with this:\n");
1210                 show_sc_block(bl, "");
1211
1212         }
1213
1214         return 0;
1215 }
1216