Handle *bold*, /italic/ etc
authorThomas White <taw@bitwiz.me.uk>
Sun, 18 Aug 2019 20:38:13 +0000 (22:38 +0200)
committerThomas White <taw@bitwiz.me.uk>
Sun, 18 Aug 2019 20:38:13 +0000 (22:38 +0200)
libstorycode/narrative.c
libstorycode/narrative_priv.h
libstorycode/narrative_render_cairo.c

index 4d088e4..7db31d9 100644 (file)
@@ -75,6 +75,8 @@ static void narrative_item_destroy(struct narrative_item *item)
        if ( item->layout != NULL ) {
                g_object_unref(item->layout);
        }
+       free(item->chars_removed);
+       free(item->layout_text);
 #endif
 #ifdef HAVE_CAIRO
        if ( item->slide_thumbnail != NULL ) {
@@ -200,7 +202,12 @@ ImageStore *narrative_get_imagestore(Narrative *n)
 
 static void init_item(struct narrative_item *item)
 {
+#ifdef HAVE_PANGO
        item->layout = NULL;
+       item->layout_text = NULL;
+       item->chars_removed = NULL;
+       item->n_chars_removed = 0;
+#endif
        item->text = NULL;
        item->slide = NULL;
        item->slide_thumbnail = NULL;
index 9d6e294..ec3ffb5 100644 (file)
@@ -64,6 +64,9 @@ struct narrative_item
        enum alignment align;
 #ifdef HAVE_PANGO
        PangoLayout *layout;
+       char *layout_text;
+       int *chars_removed;
+       int n_chars_removed;
 #else
        void *layout;
 #endif
index c22b7ce..17f22aa 100644 (file)
@@ -69,6 +69,199 @@ static PangoAlignment to_pangoalignment(enum alignment align)
 }
 
 
+static int find_pair(const gchar *p, gunichar c, gchar **start, gchar **end)
+{
+       gchar *s;
+       gchar *e;
+       gchar *next;
+
+       /* FIXME: Check it's not escaped */
+       s = g_utf8_strchr(p, -1, c);
+       if ( s == NULL ) return 0;
+
+       next = g_utf8_find_next_char(s, NULL);
+       if ( next == NULL ) return 0;
+
+       e = g_utf8_strchr(next, -1, c);
+       if ( e == NULL ) return 0;
+
+       *start = s;
+       *end = e;
+       return 1;
+}
+
+
+struct attr_to_add
+{
+       int start;
+       int end;
+       char type;  /* b=bold, i=italic, u=underline */
+};
+
+
+static int add_range(struct narrative_item *item, int *max_chars_removed,
+                     int start, int end, int *n_add, int *max_add,
+                     struct attr_to_add **add, char type)
+{
+       if ( item->n_chars_removed == *max_chars_removed ) {
+               (*max_chars_removed) += 256;
+               item->chars_removed = realloc(item->chars_removed, *max_chars_removed*sizeof(int));
+               if ( item->chars_removed == NULL ) return 1;
+       }
+
+       item->chars_removed[item->n_chars_removed++] = start;
+       item->chars_removed[item->n_chars_removed++] = end;
+
+       if ( *n_add == *max_add ) {
+               *max_add += 64;
+               *add = realloc(*add, *max_add*sizeof(struct attr_to_add));
+               if ( *add == NULL ) return 1;
+       }
+
+       (*add)[*n_add].start = start;
+       (*add)[*n_add].end = end;
+       (*add)[*n_add].type = type;
+       (*n_add)++;
+
+       return 0;
+}
+
+
+/* How many bytes were removed up to idx? */
+int index_before_removal(int *chars_removed, int n_chars_removed, int idx)
+{
+       int i;
+
+       for ( i=0; i<n_chars_removed; i++ ) {
+               if ( chars_removed[i] > idx ) break;
+       }
+
+       return idx + i;
+}
+
+
+/* How many bytes were removed up to idx? */
+int index_with_removal(int *chars_removed, int n_chars_removed, int idx)
+{
+       int i;
+
+       for ( i=0; i<n_chars_removed; i++ ) {
+               if ( chars_removed[i] > idx ) break;
+       }
+
+       return idx - i;
+}
+
+
+static int cmpi(const void *a, const void *b)
+{
+       return *(int *)a - *(int *)b;
+}
+
+
+static void process_tags(struct narrative_item *item, PangoAttrList *attrs)
+{
+       gchar *efstart;
+       gchar *efend;
+       gchar *text;
+       PangoAttribute *attr;
+       struct attr_to_add *add;
+       int n_add;
+       int max_add;
+       int max_chars_removed;
+       int i, j, k;
+       size_t len;
+
+       item->n_chars_removed = 0;
+       free(item->chars_removed);
+       item->chars_removed = NULL;
+       max_chars_removed = 0;
+
+       add = NULL;
+       n_add = 0;
+       max_add = 0;
+
+       /* Scan the text, identify characters to remove and character ranges
+        * (in the original text) which need attributes applied */
+       text = item->text;
+       while ( find_pair(text, '*', &efstart, &efend) ) {
+               if ( add_range(item, &max_chars_removed,
+                              efstart - item->text, efend - item->text,
+                              &n_add, &max_add, &add, 'b') ) return;
+               text = g_utf8_find_next_char(efend, NULL);
+       }
+       text = item->text;
+       while ( find_pair(text, '/', &efstart, &efend) ) {
+               if ( add_range(item, &max_chars_removed,
+                              efstart - item->text, efend - item->text,
+                              &n_add, &max_add, &add, 'i') ) return;
+               text = g_utf8_find_next_char(efend, NULL);
+       }
+       text = item->text;
+       while ( find_pair(text, '_', &efstart, &efend) ) {
+               if ( add_range(item, &max_chars_removed,
+                              efstart - item->text, efend - item->text,
+                              &n_add, &max_add, &add, 'u') ) return;
+               text = g_utf8_find_next_char(efend, NULL);
+       }
+
+       /* Sort the list of removed characters */
+       qsort(item->chars_removed, item->n_chars_removed, sizeof(int), cmpi);
+
+       /* Go through the list of attributes, and correct the character ranges
+        * so that they refer to the text with characters removed, and add them
+        * to the PangoAttrList */
+       for ( i=0; i<n_add; i++ ) {
+
+               switch ( add[i].type ) {
+
+                       case 'b' :
+                       attr = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
+                       break;
+
+                       case 'i' :
+                       attr = pango_attr_style_new(PANGO_STYLE_ITALIC);
+                       break;
+
+                       case 'u' :
+                       attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
+                       break;
+
+               }
+
+               attr->start_index = index_with_removal(item->chars_removed,
+                                                      item->n_chars_removed,
+                                                      add[i].start);
+
+               attr->end_index = index_with_removal(item->chars_removed,
+                                                    item->n_chars_removed,
+                                                    add[i].end) + 1;
+
+               pango_attr_list_insert(attrs, attr);
+
+       }
+
+       /* Create the version of the text with characters removed */
+       if ( item->n_chars_removed == 0 ) {
+                  item->layout_text = strdup(item->text);
+       } else {
+               len = strlen(item->text);
+               item->layout_text = malloc(len);
+               if ( item->layout_text == NULL ) return;
+               j = 0;
+               for ( i=0; i<len+1; i++ ) {  /* \0 terminator please */
+                       if ( item->chars_removed[k] == i ) {
+                               k++;
+                       } else {
+                               item->layout_text[j++] = item->text[i];
+                       }
+               }
+       }
+
+       free(add);
+}
+
+
 static void wrap_text(struct narrative_item *item, PangoContext *pc,
                       Stylesheet *ss, const char *stn, double w,
                       size_t sel_start, size_t sel_end)
@@ -120,14 +313,15 @@ static void wrap_text(struct narrative_item *item, PangoContext *pc,
                item->layout = pango_layout_new(pc);
        }
        pango_layout_set_width(item->layout, pango_units_from_double(wrap_w));
-       pango_layout_set_text(item->layout, item->text, -1);
        pango_layout_set_alignment(item->layout, palignment);
        pango_layout_set_font_description(item->layout, fontdesc);
+
+       /* Handle *bold*, _underline_, /italic/ etc. */
+       process_tags(item, attrs);
        pango_layout_set_attributes(item->layout, attrs);
+       pango_attr_list_unref(attrs);
 
-       /* FIXME: Handle *bold*, _underline_, /italic/ etc. */
-       //pango_layout_set_attributes(item->layout, attrs);
-       //pango_attr_list_unref(attrs);
+       pango_layout_set_text(item->layout, item->layout_text, -1);
 
        pango_layout_get_extents(item->layout, NULL, &rect);
        item->obj_w = pango_units_to_double(rect.width);