]> git.neil.brown.name Git - edlib.git/blob - lib-renderline.c
lib-renderlines: pass split to do_draw() instead of str/len/tab_cols.
[edlib.git] / lib-renderline.c
1 // always remeasure?
2 // :xx is points: tab left_margin
3 // what exactly is left margin for wrapping
4 // wrap content with cursor should itself wrap if needed, appeared unwrapped if possible
5 /*
6  * Copyright Neil Brown ©2015-2023 <neil@brown.name>
7  * May be distributed under terms of GPLv2 - see file:COPYING
8  *
9  * A renderline pane will take a single line of marked-up text
10  * and draw it.  The "line" may well be longer that the width
11  * of the pane, and it might then be wrapped generatinging
12  * multiple display lines.
13  *
14  * The render-lines pane will place multiple renderline panes and use
15  * them to do the drawing - resizing and moving them as necessary to fit
16  * the size of the text.
17  *
18  * A renderline normally is only active when the render-lines (or other)
19  * parent pane is being refreshed - that pane hands over some of the
20  * task to the renderline pane.
21  * Specifically a "draw-markup" command provides a marked-up line of
22  * text, a scale, and other details.  The resulting image in measured
23  * and possibly drawn
24  *
25  *
26  */
27
28 #define _GNU_SOURCE /*  for asprintf */
29 #include <stdio.h>
30 #include <ctype.h>
31 #include <wctype.h>
32 #include <stdint.h>
33
34 #define PANE_DATA_TYPE struct rline_data
35 #include "core.h"
36 #include "misc.h"
37
38 /* There is one render_item entry
39  * - each string of text with all the same attributes
40  * - each individual TAB
41  * - each unknown control character
42  * - the \n \f or \0 which ends the line
43  * When word-wrap is enabled, strings of linear white-space get
44  * different attributes, so a different render_item entry.
45  *
46  * attributes understood at this level are:
47  *  center or centre    - equal space on either end of flushed line
48  *  left:nn             - left margin - in "points"
49  *  right:nn            - right margin
50  *  tab:nn              - move to nn from left margin or -nn from right margin
51  *  rtab:nn             - from here to next tab or eol right-aligned at nn
52  *  ctab:nn             - from here to next tab or eol centered at nn
53  *  space-above:nn      - extra space before (wrapped) line
54  *  space-below:nn      - extra space after (wrapped) line
55  *  height:nn           - override height.  This effectively adds space above
56  *                        every individual line if the whole line is wrapped
57  *  wrap                - text with this attr can be hidden when used as a wrap
58  *                        point.
59  *  wrap-margin         - remember this x offset as left margin of wrapped lines
60  *  wrap-head=xx        - text is inserted at start of line when wrapped
61  *  wrap-tail=xx        - text to include at end of line when wrapped.  This
62  *                        determines how far before right margin that wrapp is
63  *                        triggered.
64  *  wrap-XXXX           - attrs to apply to wrap head/tail. Anything not recognised
65  *                        has "wrap-" stripped and is used for the head and tail.
66  *  hide                - Text is hidden if cursor is not within range.
67  *
68  * "nn" is measured in "points" which is 1/10 the nominal width of chars
69  * in the default font size, which is called "10".  A positive value is
70  * measured from the left margin or, which setting margins, from the
71  * left page edge.  A negative value is measures from the right margin
72  * or right page edge.
73  *
74  */
75
76 /* When an entry is split for line-wrap:
77  *   'split_cnt' is count of splits (total lines - 1)
78  *   ' split_list' is offsets from start where split happens
79  *   'x' position of wrapped portions is wrap_marign or head_length
80  *   'y' position of wrapped portions increases line_height for each
81  */
82 struct render_item {
83         struct render_item *next;
84         const char      *attr safe;
85         unsigned short  *split_list;
86         unsigned short  start, len; // in rd->line
87         unsigned short  height, width;
88         signed short    x,y; /* signed so x can go negative when shifted */
89         signed short    tab;
90         unsigned short  wrap_x;
91         uint8_t         split_cnt; /* wrap happens this many times at byte
92                                     * positions in split_list */
93         uint8_t         wrap; /* this and consecutive render_items
94                                * with the same wrap number form an
95                                * optional wrap point.  It is only visible
96                                * when not wrapped, or when cursor is in
97                                * it.
98                                */
99         uint8_t         hide; /* This and consecutive render_items
100                                * with the same hide nmber form a
101                                * hidden extent which is visible when
102                                * the cursor is in it.
103                                */
104         bool            wrap_margin:1;
105         bool            hidden:1;
106         bool            eol:1;
107         unsigned int    tab_cols:4; /* For \t char */
108         enum tab_align {
109                 TAB_LEFT = 0,   // No extra space (after tab stop)
110                 TAB_RIGHT,      // Add extra space here so there no space at
111                                 // next tab stop or margin
112                 TAB_CENTRE,     // Add extra space here, half of what TAB_RIGHT
113                                 // would insert
114         }               tab_align:2;
115 };
116 /* A "tab" value of 0 means left margin, and negative is measured from right
117  * margin, so we need some other value to say "no value here"
118  */
119 static const short TAB_UNSET = (1<<(14-2));
120
121 struct rline_data {
122         unsigned short  prefix_bytes, prefix_pixels;
123         short           curs_width;
124         short           left_margin, right_margin;
125         short           space_above, space_below;
126         unsigned short  line_height, min_height;
127         unsigned short  scale;
128         unsigned short  width;
129         unsigned short  ascent;
130         char            *wrap_head, *wrap_tail, *wrap_attr;
131         int             head_length, tail_length;
132         char            *line safe;
133         bool            word_wrap;
134         bool            image;
135         int             curspos;
136
137         struct render_item *content;
138 };
139 #include "core-pane.h"
140
141 enum {
142         OK = 0,
143         WRAP,
144         XYPOS,
145 };
146
147 /* sequentially set _attr to the an attr name, and _val to
148  * either the val (following ":") or NULL.
149  * _attr is valid up to : or , or < space and _val is valid up to , or <space
150  * _c is the start which will be updates, and _end is the end which
151  * must point to , or nul or a control char
152  */
153 #define foreach_attr(_attr, _val, _c, _end)                     \
154         for (_attr = _c, _val = find_val(&_c, _end);            \
155              _attr;                                             \
156              _attr = _c, _val = find_val(&_c, _end))
157 static const char *find_val(const char **cp safe, const char *end safe)
158 {
159         const char *c = *cp;
160         const char *ret;
161
162         if (!c)
163                 return NULL;
164         while (c < end && *c != ':' && *c != ',')
165                 c++;
166         if (c == end) {
167                 *cp = NULL;
168                 return NULL;
169         }
170         if (*c == ',') {
171                 while (*c == ',' && c < end)
172                         c++;
173                 if (c == end) {
174                         *cp = NULL;
175                         return NULL;
176                 }
177                 *cp = c;
178                 return NULL;
179         }
180         c += 1;
181         ret = c;
182         while (c < end && *c != ',')
183                 c++;
184         while (c < end && *c == ',')
185                 c++;
186         if (c == end)
187                 c = NULL;
188         *cp = c;
189         return ret;
190 }
191
192 static bool amatch(const char *a safe, const char *m safe)
193 {
194         while (*a && *a == *m) {
195                 a += 1;
196                 m += 1;
197         }
198         if (*m)
199                 /* Didn't match all of m */
200                 return False;
201         if (*a != ':' && *a != ',' && *a >= ' ')
202                 /* Didn't match all of a */
203                 return False;
204         return True;
205 }
206
207 static bool aprefix(const char *a safe, const char *m safe)
208 {
209         while (*a && *a == *m) {
210                 a += 1;
211                 m += 1;
212         }
213         if (*m)
214                 /* Didn't match all of m */
215                 return False;
216         return True;
217 }
218
219 static long anum(const char *v safe)
220 {
221         char *end = NULL;
222         long ret = strtol(v, &end, 10);
223         if (end == v || !end ||
224             (*end != ',' && *end >= ' '))
225                 /* Not a valid number - use zero */
226                 return 0;
227         return ret;
228 }
229
230 static void aupdate(char **cp safe, const char *v)
231 {
232         /* duplicate value at v and store in *cp, freeing what is there
233          * first
234          */
235         const char *end = v;
236
237         while (end && *end != ',' && *end >= ' ')
238                 end += 1;
239
240         free(*cp);
241         if (v)
242                 *cp = strndup(v, end-v);
243         else
244                 *cp = NULL;
245 }
246
247 static void aappend(struct buf *b safe, char const *a safe)
248 {
249         const char *end = a;
250         while (*end >= ' ' && *end != ',')
251                 end++;
252         buf_concat_len(b, a, end-a);
253         buf_append(b, ',');
254 }
255
256 static void add_render(struct rline_data *rd safe, struct render_item **safe*rlp safe,
257                        const char *start safe, const char *end safe,
258                        char *attr safe,
259                        short tab, enum tab_align align,
260                        bool wrap_margin,
261                        short wrap, short hide)
262 {
263         struct render_item *ri;
264         struct render_item **riend = *rlp;
265
266         alloc(ri, pane);
267         ri->attr = strdup(attr);
268         ri->start = start - rd->line;
269         ri->len = end - start;
270         ri->tab_align = align;
271         ri->tab = tab;
272         ri->wrap = wrap;
273         ri->hide = hide;
274         ri->wrap_margin = wrap_margin;
275         ri->eol = !!strchr("\n\f\0", *start);
276         *riend = ri;
277         riend = &ri->next;
278         *rlp = riend;
279 }
280
281 static inline bool is_ctrl(unsigned int c)
282 {
283         return c < ' ' ||
284                 (c >= 128 && c < 128 + ' ');
285 }
286
287 static void parse_line(struct rline_data *rd safe)
288 {
289         /* Parse out markup in line into a renderlist with
290          * global content directly in rd.
291          */
292         struct buf attr, wrapattr;
293         struct render_item *ri = NULL, **riend = &ri;
294         const char *line = rd->line;
295         bool wrap_margin = False;
296         int tab = TAB_UNSET, align = TAB_LEFT;
297         int hide = 0, hide_num = 0, hide_depth = 0;
298         int wrap = 0, wrap_num = 0, wrap_depth = 0;
299         unsigned char c;
300
301         rd->left_margin = rd->right_margin = 0;
302         rd->space_above = rd->space_below = 0;
303         rd->min_height = 0;
304         aupdate(&rd->wrap_head, NULL);
305         aupdate(&rd->wrap_tail, NULL);
306         aupdate(&rd->wrap_attr, NULL);
307
308         if (!line) {
309                 rd->image = False;
310                 return;
311         }
312         rd->image = strstarts(line, SOH "image:");
313         if (rd->image)
314                 return;
315         buf_init(&attr);
316         buf_init(&wrapattr);
317
318         ri = rd->content;
319         rd->content = NULL;
320         while (ri) {
321                 struct render_item *r = ri;
322                 ri = r->next;
323                 free(r->split_list);
324                 unalloc_str_safe(r->attr, pane);
325                 unalloc(r, pane);
326         }
327
328         do {
329                 const char *st = line;
330                 c = *line++;
331
332                 while (c >= ' ' &&
333                        (!rd->word_wrap || c != ' '))
334                         c = *line++;
335
336                 if (line - 1 > st) {
337                         /* All text from st to line-1 has "attr' */
338                         add_render(rd, &riend, st, line-1, buf_final(&attr),
339                                    tab, align, wrap_margin, wrap, hide);
340                         align = TAB_LEFT;
341                         tab = TAB_UNSET;
342                         wrap_margin = False;
343                         st = line - 1;
344                 }
345                 switch (c) {
346                 case soh: {
347                         int old_len;
348                         const char *a, *v;
349                         st = line;
350                         /* Move 'line' over the attrs */
351                         while (*line && line[-1] != stx)
352                                 line += 1;
353
354                         /* A set of attrs begins and ends with ',' so that
355                          * ",," separates sets of attrs
356                          * An empty set will be precisely 1 ','.  We strip
357                          * "attr," as long as we can, then strip one more ',',
358                          * which should leave either a trailing comma, or an
359                          * empty string.
360                          */
361                         buf_append(&attr, ',');
362                         old_len = attr.len;
363                         foreach_attr(a, v, st, line) {
364                                 if (amatch(a, "centre") || amatch(a, "center") ||
365                                     amatch(a, "ctab")) {
366                                         if (v)
367                                                 tab = anum(v);
368                                         align = TAB_CENTRE;
369                                 } else if (amatch(a, "tab") && v) {
370                                         tab = anum(v);
371                                         align = TAB_LEFT;
372                                 } else if (amatch(a, "rtab")) {
373                                         align = TAB_RIGHT;
374                                 } else if (amatch(a, "left") && v) {
375                                         rd->left_margin = anum(v);
376                                 } else if (amatch(a, "right") && v) {
377                                         rd->right_margin = anum(v);
378                                 } else if (amatch(a, "space-above") && v) {
379                                         rd->space_above = anum(v);
380                                 } else if (amatch(a, "space-below") && v) {
381                                         rd->space_below = anum(v);
382                                 } else if (amatch(a, "height") && v) {
383                                         rd->min_height = anum(v);
384                                 } else if (amatch(a, "wrap")) {
385                                         wrap = ++wrap_num;
386                                         wrap_depth = old_len;
387                                 } else if (amatch(a, "wrap-margin")) {
388                                         wrap_margin = True;
389                                 } else if (amatch(a, "wrap-head")) {
390                                         aupdate(&rd->wrap_head, v);
391                                 } else if (amatch(a, "wrap-tail")) {
392                                         aupdate(&rd->wrap_tail, v);
393                                 } else if (aprefix(a, "wrap-")) {
394                                         aappend(&wrapattr, a+5);
395                                 } else if (amatch(a, "hide")) {
396                                         hide = ++hide_num;
397                                         hide_depth = old_len;
398                                 } else
399                                         aappend(&attr, a);
400                         }
401                         break;
402                         }
403                 case etx:
404                         /* strip last set of attrs */
405                         while (attr.len >= 2 &&
406                         attr.b[attr.len-1] == ',' &&
407                         attr.b[attr.len-2] != ',') {
408                                 /* strip this attr */
409                                 attr.len -= 2;
410                                 while (attr.len && attr.b[attr.len-1] != ',')
411                                         attr.len -= 1;
412                         }
413                         /* strip one more ',' */
414                         if (attr.len > 0)
415                                 attr.len -= 1;
416                         if (attr.len <= wrap_depth)
417                                 wrap = 0;
418                         if (attr.len <= hide_depth)
419                                 hide = 0;
420                         break;
421                 case ack:
422                         /* Just ignore this */
423                         break;
424                 case ' ':
425                         /* This and following spaces are wrappable */
426                         st = line;
427                         while (*line == ' ')
428                                 line += 1;
429                         wrap = ++wrap_num;
430                         add_render(rd, &riend, st - 1, line, buf_final(&attr),
431                                    tab, align, wrap_margin, wrap, hide);
432                         tab = TAB_UNSET;
433                         align = TAB_LEFT;
434                         wrap_margin = False;
435                         wrap = 0;
436                         break;
437                 case '\0':
438                 case '\n':
439                 case '\f':
440                 case '\t':
441                 default:
442                         /* Each tab gets an entry of its own, as does any
443                          * stray control character.
444                          * \f \n and even \0 do.  These are needed for
445                          * easy cursor placement.
446                          */
447                         add_render(rd, &riend, st, line, buf_final(&attr),
448                                    tab, align, wrap_margin, wrap, hide);
449                         tab = TAB_UNSET;
450                         align = TAB_LEFT;
451                         wrap_margin = False;
452                         break;
453                 }
454         } while (c);
455
456         rd->content = ri;
457         free(attr.b);
458         if (buf_final(&wrapattr)[0])
459                 rd->wrap_attr = buf_final(&wrapattr);
460         else {
461                 free(wrapattr.b);
462                 rd->wrap_attr = strdup(",fg:blue,underline,");
463         }
464 }
465
466 static inline struct call_return do_measure(struct pane *p safe,
467                                             char *str safe, int len,
468                                             int offset, int scale,
469                                             const char *attr)
470 {
471         struct call_return cr;
472         char tmp;
473
474         if (len >= 0) {
475                 tmp = str[len];
476                 str[len] = 0;
477         }
478         cr = call_ret(all, "Draw:text-size", p,
479                       offset, NULL, str,
480                       scale, NULL, attr);
481         if (len >= 0)
482                 str[len] = tmp;
483         return cr;
484 }
485
486 static inline void do_draw(struct pane *p safe,
487                            struct pane *focus safe,
488                            struct render_item *ri safe, int split,
489                            int offset,
490                            int x, int y)
491 {
492         struct rline_data *rd = &p->data;
493         char tmp;
494         char *str;
495         int len;
496         char tb[] = "         ";
497
498         str = rd->line + ri->start;
499         len = ri->len;
500
501         y += rd->ascent;
502         if (strchr("\f\n\0", str[0])) {
503                 /* end marker - len extends past end of string,
504                  * but mustn't write there.  Only need to draw if
505                  * cursor is here.
506                  */
507                 if (offset == 0)
508                         home_call(focus, "Draw:text", p, offset, NULL, "",
509                                   rd->scale, NULL, ri->attr, x, y);
510                 return;
511         }
512         if (str[0] == '\t') {
513                 len = ri->tab_cols;
514                 if (split)
515                         offset = -1;
516         }
517         if (ri->split_list && split < ri->split_cnt)
518                 len = ri->split_list[split];
519
520         if (str[0] == '\t')
521                 /* Tab need a list of spaces */
522                 str = tb;
523         else
524                 if (split > 0 && split <= ri->split_cnt && ri->split_list) {
525                         str += ri->split_list[split-1];
526                         offset -= ri->split_list[split];
527                 }
528
529         tmp = str[len];
530         str[len] = 0;
531
532         home_call(focus, "Draw:text", p, offset, NULL, str,
533                            rd->scale, NULL, ri->attr, x, y);
534         str[len] = tmp;
535 }
536
537 static inline void draw_wrap(struct pane *p safe,
538                              struct pane *focus safe,
539                              char *str safe,
540                              int x, int y)
541 {
542         struct rline_data *rd = &p->data;
543
544         home_call(focus, "Draw:text", p,
545                   -1, NULL, str,
546                   rd->scale, NULL, rd->wrap_attr,
547                   x, y + rd->ascent);
548 }
549
550 static void add_split(struct render_item *ri safe, int split)
551 {
552         int i = ri->split_cnt;
553         ri->split_cnt += 1;
554         ri->split_list = realloc(ri->split_list,
555                                  sizeof(ri->split_list[0]) * ri->split_cnt);
556         ri->split_list[i] = split;
557 }
558
559 static int calc_tab(int num, int margin, int scale)
560 {
561         if (num > 0)
562                 return num * scale / 1000;
563         if (-num > margin)
564                 return 0;
565         return margin + num * scale / 1000;
566 }
567
568 static bool measure_line(struct pane *p safe, struct pane *focus safe, int offset)
569 {
570         /* First measure each render_item entry setting
571          * height, ascent, width.
572          * Then use that with tab information to set 'x' position for
573          * each unit.
574          * Finally identify line-break locations if needed and set 'y'
575          * positions
576          */
577         struct rline_data *rd = &p->data;
578         struct render_item *ri, *wraprl;
579         int shift_left = pane_attr_get_int(focus, "shift_left", 0);
580         bool wrap = shift_left < 0;
581         int wrap_margin;
582         int right_margin = p->w - (rd->right_margin * rd->scale / 1000);
583         int xdiff, ydiff;
584         struct call_return cr;
585         int x, y;
586         bool eop = False;
587
588         if (!rd->content)
589                 return eop;
590         cr = do_measure(p, "M", -1, -1, rd->scale,"");
591         rd->curs_width = cr.x;
592         rd->line_height = cr.y;
593         rd->ascent = cr.i2;
594         if (rd->min_height * rd->scale / 1000 > rd->line_height)
595                 rd->line_height = rd->min_height * rd->scale / 1000;
596
597         if (rd->wrap_head) {
598                 cr = do_measure(p, rd->wrap_head, -1, -1, rd->scale,
599                                 rd->wrap_attr);
600                 rd->head_length = cr.x;
601         }
602         cr = do_measure(p, rd->wrap_tail ?: "\\", -1, -1, rd->scale,
603                         rd->wrap_attr);
604         rd->tail_length = cr.x;
605
606         for (ri = rd->content; ri; ri = ri->next) {
607                 char tmp[4] = "";
608                 char *txt = tmp;
609                 int len = -1;
610                 if (!is_ctrl(rd->line[ri->start])) {
611                         txt = rd->line + ri->start;
612                         len = ri->len;
613                 } else if (ri->eol) {
614                         /* Ensure attributes of newline add to line height.
615                          * The width will be ignored. */
616                         strcpy(tmp, "M");
617                         if (rd->line[ri->start] == '\f')
618                                 eop = True;
619                 } else if (rd->line[ri->start] == '\t') {
620                         strcpy(tmp, " ");
621                 } else {
622                         strcpy(tmp, "^x");
623                         tmp[1] = '@' + (rd->line[ri->start] & 31);
624                 }
625                 cr = do_measure(p, txt, len, -1, rd->scale, ri->attr);
626                 if (cr.y > rd->line_height)
627                         rd->line_height = cr.y;
628                 ri->height = cr.y;
629                 if (cr.i2 > rd->ascent)
630                         rd->ascent = cr.i2;
631                 ri->width = ri->eol ? 0 : cr.x;
632                 ri->hidden = False;
633
634                 if (ri->start <= offset && offset <= ri->start + ri->len) {
635                         cr = do_measure(p, "M", -1, -1, rd->scale, ri->attr);
636                         rd->curs_width = cr.x;
637                 }
638
639                 ri->split_cnt = 0;
640                 free(ri->split_list);
641                 ri->split_list = NULL;
642         }
643         /* Set 'x' position honouring tab stops, and set length
644          * of "\t" characters.  Also handle \n and \f.
645          */
646         x = (rd->left_margin * rd->scale / 1000) - (shift_left > 0 ? shift_left : 0);
647         y = rd->space_above * rd->scale / 1000;
648         rd->width = 0;
649         for (ri = rd->content; ri; ri = ri->next) {
650                 int w, margin;
651                 struct render_item *ri2;
652                 ri->y = y;
653                 if (ri->tab != TAB_UNSET)
654                         x =  (rd->left_margin * rd->scale / 1000) + calc_tab(ri->tab, right_margin, rd->scale);
655                 if (ri->eol) {
656                         /* EOL */
657                         if (x > rd->width)
658                                 rd->width = x;
659                         ri->x = x;
660                         x = 0; /* Don't include shift. probably not margin */
661                         if (rd->line[ri->start])
662                                 y += rd->line_height;
663                         continue;
664                 }
665                 if (ri->tab_align == TAB_LEFT) {
666                         ri->x = x;
667                         if (rd->line[ri->start] == '\t') {
668                                 int col = x / ri->width;
669                                 int cols= 8 - (col % 8);
670                                 ri->tab_cols = cols;
671                                 ri->width *= cols;
672                         }
673                         x += ri->width;
674                         continue;
675                 }
676                 w = ri->width;
677                 for (ri2 = ri->next;
678                      ri2 && ri2->tab_align == TAB_LEFT && ri2->tab == TAB_UNSET;
679                      ri2 = ri2->next)
680                         w += ri2->width;
681                 while (ri2 && ri2->tab == TAB_UNSET)
682                         ri2 = ri2->next;
683                 margin = right_margin;
684                 if (ri2)
685                         margin =  (rd->left_margin * rd->scale / 1000) + calc_tab(ri2->tab, right_margin, rd->scale);
686                 if (ri->tab_align == TAB_RIGHT) {
687                         margin -= rd->tail_length;// FIXME don't want this HACK
688                         x = x + margin - x - w;
689                 } else
690                         x = x + (margin - x - w) / 2;
691                 ri->x = x;
692                 while (ri->next && ri->next->next && ri->next->tab_align == TAB_LEFT) {
693                         x += ri->width;
694                         ri = ri->next;
695                         ri->x = x;
696                         ri->y = y;
697                 }
698         }
699
700         /* Now we check to see if the line needs to be wrapped and
701          * if so, adjust some y values and reduce x.  If we need to
702          * split an individual entry we create an array of split points.
703          */
704         xdiff = 0; ydiff = 0; y = 0;
705         wraprl = NULL;
706         wrap_margin = rd->head_length;
707         for (ri = rd->content ; wrap && ri ; ri = ri->next) {
708                 int splitpos;
709                 char *str;
710                 int len;
711                 char tb[] = "        ";
712                 if (ri->wrap && (wraprl == NULL || ri->wrap != wraprl->wrap))
713                         wraprl = ri;
714                 if (ri->wrap_margin)
715                         wrap_margin = ri->x;
716                 ri->wrap_x = wrap_margin;
717                 ri->x += xdiff;
718                 ri->y += ydiff;
719                 y = ri->y;
720                 if (ri->eol) {
721                         xdiff = 0;
722                         continue;
723                 }
724                 if (ri->x + ri->width <= right_margin - rd->tail_length)
725                         continue;
726                 /* This doesn't fit here */
727                 if (wraprl) {
728                         /* Move wraprl to next line and hide it unless it contains cursor */
729                         int xd = wraprl->x - wrap_margin;
730                         struct render_item *wraprl2, *ri2;
731
732                         /* Find last ritem in wrap region.*/
733                         for (wraprl2 = wraprl ;
734                              wraprl2->next && wraprl2->next->wrap == wraprl->wrap ;
735                              wraprl2 = wraprl2->next)
736                                 ;
737                         if (wraprl2->next)
738                                 xd = wraprl2->next->x - wrap_margin;
739                         if (offset >= 0 &&
740                             offset >= wraprl->start &&
741                             offset <= wraprl2->start + wraprl2->len) {
742                                 /* Cursor is here, so keep it visible.
743                                  * If we are still in the wrap region, pretend
744                                  * it didn't exist, else move first item
745                                  * after it to next line
746                                  */
747                                 if (ri->wrap == wraprl->wrap)
748                                         goto normal_wrap;
749                         } else {
750                                 /* Hide the wrap region */
751                                 while (wraprl != wraprl2) {
752                                         wraprl->hidden = True;
753                                         wraprl = wraprl->next;
754                                 }
755                                 if (wraprl)
756                                         wraprl->hidden = True;
757                                 while (ri->next && ri->next->hidden)
758                                         ri = ri->next;
759                         }
760                         for (ri2 = wraprl2->next ; ri2 && ri2 != ri->next; ri2 = ri2->next) {
761                                 ri2->y += rd->line_height;
762                                 ri2->x -= xd;
763                         }
764                         xdiff -= xd;
765                         ydiff += rd->line_height;
766                         wraprl = NULL;
767                         continue;
768                 }
769         normal_wrap:
770                 if (ri->x >= right_margin - rd->tail_length) {
771                         /* This ri moves completely to next line */
772                         xdiff -= ri->x - wrap_margin;
773                         ri->x = wrap_margin;
774                         ydiff += rd->line_height;
775                         ri->y += rd->line_height;
776                         wraprl = NULL;
777                         continue;
778                 }
779                 /* Need to split this ri into two or more pieces */
780                 x = ri->x;
781                 splitpos = 0;
782                 str = rd->line + ri->start;
783                 len = ri->len;
784                 if (*str == '\t') {
785                         str = tb;
786                         len = ri->tab_cols;
787                 }
788                 while (1) {
789                         cr = do_measure(p, str + splitpos,
790                                         len - splitpos,
791                                         right_margin - rd->tail_length - x,
792                                         rd->scale, ri->attr);
793                         if (cr.i >= len - splitpos)
794                                 /* Remainder fits now */
795                                 break;
796                         /* re-measure the first part */
797                         cr = do_measure(p, str + splitpos,
798                                         cr.i,
799                                         right_margin - rd->tail_length - x,
800                                         rd->scale, ri->attr);
801
802                         ydiff += rd->line_height;
803                         xdiff -= cr.x; // fixme where does wrap_margin fit in there
804                         if (splitpos == 0)
805                                 xdiff -= ri->x;
806                         splitpos += cr.i;
807                         x = wrap_margin;
808                         add_split(ri, splitpos);
809                 }
810         }
811         /* We add rd->line_height for the EOL, whether a NL is present of not */
812         ydiff += rd->line_height;
813         pane_resize(p, p->x, p->y, p->w,
814                     (rd->space_above + rd->space_below) * rd->scale / 1000 +
815                     ydiff);
816         attr_set_int(&p->attrs, "line-height", rd->line_height);
817         return eop;
818 }
819
820 static void draw_line(struct pane *p safe, struct pane *focus safe, int offset)
821 {
822         struct rline_data *rd = &p->data;
823         struct render_item *ri;
824         char *wrap_tail = rd->wrap_tail ?: "\\";
825         char *wrap_head = rd->wrap_head ?: "";
826
827         home_call(focus, "Draw:clear", p);
828
829         if (!rd->content)
830                 return;
831         for (ri = rd->content ; ri; ri = ri->next) {
832                 int split = 0;
833                 short y = ri->y;
834                 int cpos;
835
836                 if (ri->hidden)
837                         continue;
838                 if (offset < 0 || offset >= ri->start + ri->len)
839                         cpos = -1;
840                 else if (offset < ri->start)
841                         cpos = 0;
842                 else
843                         cpos = offset - ri->start;
844
845                 do_draw(p, focus, ri, 0, cpos, ri->x, y);
846                 if (!ri->split_cnt && ri->next &&
847                     !ri->next->eol && ri->next->y != ri->y) {
848                         /* we are about to wrap - draw the markers */
849                         if (*wrap_tail)
850                                 draw_wrap(p, focus, wrap_tail,
851                                           p->w - rd->tail_length, y);
852                         if (*wrap_head)
853                                 draw_wrap(p, focus, wrap_head,
854                                           0, y + rd->line_height);
855                 }
856
857                 while (split < ri->split_cnt ||
858                        (ri->next && ri->next->next && ri->next->y > y)) {
859                         /* line wrap here */
860                         /* don't show head/tail for wrap-regions */
861                         if (*wrap_tail /*&& !ri->wrap*/)
862                                 draw_wrap(p, focus, wrap_tail,
863                                           p->w - rd->tail_length, y);
864                         y += rd->line_height;
865                         if (*wrap_head /*&& !ri->wrap*/)
866                                 draw_wrap(p, focus, wrap_head,
867                                           0, y);
868                         if (ri->split_list && split < ri->split_cnt) {
869                                 split += 1;
870                                 do_draw(p, focus, ri, split, cpos,
871                                         rd->left_margin + rd->head_length,
872                                         y);
873                         }
874                 }
875                 if (offset < ri->start + ri->len)
876                         offset = -1;
877         }
878 }
879
880 static int find_xy(struct pane *p safe, struct pane *focus safe,
881                    short x, short y, const char **xyattr)
882 {
883         /* Find the location in ->line that is best match for x,y.
884          * If x,y is on the char at that location, when store attributes
885          * for the char in xyattr
886          * We always return a location, even if no xyattr.
887          * We use the last render_item that is not definitely after x,y
888          * We do not consider the eol render_item
889          */
890         struct call_return cr;
891         struct rline_data *rd = &p->data;
892         struct render_item *r, *ri = NULL;
893
894         if (!rd->content)
895                 return 0;
896         for (r = rd->content; r ; r = r->next) {
897                 int split;
898                 if (r->y <= y && r->x <= x)
899                         ri = r;
900                 for (split = 0; split < r->split_cnt; split++) {
901                         if (r->y + (split + 1) * rd->line_height &&
902                             r->x <= r->wrap_x)
903                                 ri = r;
904                 }
905         }
906         if (!ri)
907                 return 0;
908         if (ri->eol)
909                 /* newline or similar.  Can only be at start */
910                 return ri->start;
911         if (ri->x + ri->width > x &&
912             ri->y + ri->height > y &&
913             xyattr)
914                 *xyattr = ri->attr;
915         if (rd->line[ri->start] == '\t')
916                 cr.i = 0;
917         else
918                 cr = do_measure(p, rd->line + ri->start, ri->len,
919                                 x - ri->x, rd->scale, ri->attr);
920         return ri->start + cr.i;
921 }
922
923 static struct xy find_curs(struct pane *p safe, int offset, const char **cursattr)
924 {
925         struct call_return cr;
926         struct xy xy = {0,0};
927         int split;
928         int st;
929         struct rline_data *rd = &p->data;
930         struct render_item *r, *ri = NULL;
931
932         for (r = rd->content; r; r = r->next) {
933                 if (offset < r->start)
934                         break;
935                 ri = r;
936         }
937         if (!ri) {
938                 /* This should be impossible as the eol goes past
939                  * the largest offset.
940                  */
941                 return xy;
942         }
943         if (offset < ri->start)
944                 /* in the attrs?? */
945                 offset = 0;
946         else
947                 offset -= ri->start;
948         /* offset now from ri->start */
949         if (rd->line[ri->start] == '\t' && offset)
950                 offset = ri->tab_cols;
951         if (cursattr)
952                 *cursattr = ri->attr;
953         st = 0;
954         for (split = 0; split < ri->split_cnt && ri->split_list; split ++) {
955                 if (offset < ri->split_list[split])
956                         break;
957                 st = ri->split_list[split];
958         }
959         if (ri->eol)
960                 cr.x = offset ? ri->width : 0;
961         else {
962                 char *str = rd->line + ri->start + st;
963                 char tb[] = "        ";
964                 if (rd->line[ri->start] == '\t') {
965                         str = tb;
966                         if (offset)
967                                 offset = ri->tab_cols;
968                 }
969                 cr = do_measure(p, str, offset - st,
970                                 -1, rd->scale, ri->attr);
971         }
972         if (split)
973                 xy.x = cr.x; /* FIXME margin?? */
974         else
975                 xy.x = ri->x + cr.x;
976         xy.y = ri->y + split * rd->line_height;
977         if (ri->next == NULL && offset > ri->len) {
978                 /* After the newline ? Go to next line */
979                 xy.x = 0;
980                 xy.y += rd->line_height;
981         }
982         return xy;
983 }
984
985 static void parse_map(const char *map safe, short *rowsp safe, short *colsp safe)
986 {
987         /* The map must be a sequence of rows, each of which is
988          * a sequence of chars starting CAPS and continuing lower.
989          * Each row must be the same length.
990          */
991         short cols = -1;
992         short rows = 0;
993         short this_cols = 0;
994
995         for (; *map && isalpha(*map); map += 1) {
996                 if (isupper(*map)) {
997                         if (rows > 1)
998                                 if (this_cols != cols)
999                                         /* Rows aren't all the same */
1000                                         return;
1001                         if (rows)
1002                                 cols = this_cols;
1003
1004                         this_cols = 1;
1005                         rows += 1;
1006                 } else if (rows == 0) {
1007                         /* First row malformed */
1008                         return;
1009                 } else {
1010                         this_cols += 1;
1011                 }
1012         }
1013         if (this_cols != cols)
1014                 /* Last row is wrong length */
1015                 return;
1016         *rowsp = rows;
1017         *colsp = cols;
1018 }
1019
1020 static int render_image(struct pane *p safe, struct pane *focus safe,
1021                         const char *line safe,
1022                         int dodraw, int scale,
1023                         int offset, int want_xypos, short x, short y)
1024 {
1025         char *fname = NULL;
1026         const char *orig_line = line;
1027         short width, height;
1028         short rows = -1, cols = -1;
1029         int map_offset = 0;
1030         int ioffset;
1031         char *ssize = attr_find(p->attrs, "cached-size");
1032         struct xy size= {-1, -1};
1033
1034         width = p->parent->w/2;
1035         height = p->parent->h/2;
1036
1037         while (*line == soh)
1038                 line += 1;
1039
1040         while (*line && *line != stx && *line != etx) {
1041                 int len = strcspn(line, "," STX ETX);
1042                 if (strstarts(line, "image:")) {
1043                         fname = strndup(line+6, len-6);
1044                         if (!ssize ||
1045                             sscanf(ssize, "%hdx%hd", &size.x, &size.y) != 2) {
1046                                 struct call_return cr =
1047                                         home_call_ret(all, focus,
1048                                                       "Draw:image-size",
1049                                                       p, 0, NULL, fname);
1050                                 if (cr.x > 0 && cr.y > 0) {
1051                                         size.x = cr.x;
1052                                         size.y = cr.y;
1053                                         asprintf(&ssize, "%hdx%hd",
1054                                                  cr.x, cr.y);
1055                                         attr_set_str(&p->attrs,
1056                                                      "cached-size", ssize);
1057                                 }
1058                         }
1059                 } else if (strstarts(line, "width:")) {
1060                         width = atoi(line + 6);
1061                         width = width * scale / 1000;
1062                 } else if (strstarts(line, "height:")) {
1063                         height = atoi(line + 7);
1064                         height = height * scale / 1000;
1065                 } else if (strstarts(line, "noupscale") &&
1066                            fname && size.x > 0) {
1067                         if (size.x < p->parent->w)
1068                                 width = size.x;
1069                         if (size.y < p->parent->h)
1070                                 height = size.y;
1071                 } else if ((offset >= 0 || want_xypos) &&
1072                            strstarts(line, "map:")) {
1073                         /*
1074                          * A map is map:LxxxLxxxLxxxLxxx or similar
1075                          * Where each "Lxxx" recognised by a CAP followed
1076                          * by lower is a row, and each char is a column.
1077                          * So we count the CAPs to get row count, and
1078                          * count the chars to get col count.
1079                          * If want_xypos then map x,y ino that matrix
1080                          * and return pos in original line of cell.
1081                          * If offset is in the map, then set ->cx,->cy to
1082                          * the appropriate location.
1083                          */
1084                         map_offset = line+4 - orig_line;
1085                         parse_map(line+4, &rows, &cols);
1086                 }
1087                 line += len;
1088                 line += strspn(line, ",");
1089         }
1090         pane_resize(p, (p->parent->w - width)/2, p->y, width, height);
1091
1092         attr_set_int(&p->attrs, "line-height", p->h);
1093
1094         /* Adjust size to be the scaled size - it must fit in
1095          * p->w, p->h
1096          */
1097         if (size.x * p->h > size.y * p->w) {
1098                 /* Image is wider than space */
1099                 size.y = size.y * p->w / size.x;
1100                 size.x = p->w;
1101                 ioffset = 0;
1102         } else {
1103                 /* Image is taller than space */
1104                 size.x = size.x * p->h / size.y;
1105                 size.y = p->h;
1106                 ioffset = (p->w - size.x) / 2;
1107         }
1108
1109         p->cx = p->cy = -1;
1110
1111         if (offset >= 0 && map_offset > 0 && rows > 0 &&
1112             offset >= map_offset && offset < map_offset + (rows*cols)) {
1113                 /* Place cursor based on where 'offset' is in the map */
1114                 short r = (offset - map_offset) / cols;
1115                 short c = offset - map_offset - r * cols;
1116                 p->cx = size.x / cols * c + ioffset;
1117                 p->cy = size.y / rows * r;
1118         }
1119
1120         if (fname && dodraw)
1121                 home_call(focus, "Draw:image", p, 5, NULL, fname,
1122                           0, NULL, NULL, cols, rows);
1123
1124         free(fname);
1125
1126         if (want_xypos && map_offset > 0 && rows > 0) {
1127                 /* report where x,y is as a position in the map */
1128                 short r = y * rows / size.y;
1129                 short c = (x > ioffset ? x - ioffset : 0) * cols / size.x;
1130                 if (c >= cols) c = cols - 1;
1131                 /* +1 below because result must never be zero */
1132                 return map_offset + r * cols + c + 1;
1133         }
1134         return 1;
1135 }
1136
1137 DEF_CMD(renderline_draw)
1138 {
1139         struct rline_data *rd = &ci->home->data;
1140         struct xy xy;
1141         int offset = -1;
1142
1143         if (ci->num >= 0)
1144                 offset = rd->prefix_bytes + ci->num;
1145
1146         if (rd->image)
1147                 render_image(ci->home, ci->focus, rd->line, True,
1148                              rd->scale, offset, False, 0, 0);
1149         else
1150                 draw_line(ci->home, ci->focus, offset);
1151
1152         if (ci->num >= 0) {
1153                 xy = find_curs(ci->home, rd->prefix_bytes + ci->num, NULL);
1154                 ci->home->cx = xy.x;
1155                 ci->home->cy = xy.y;
1156         }
1157         return 1;
1158 }
1159
1160 DEF_CMD(renderline_refresh)
1161 {
1162         struct rline_data *rd = &ci->home->data;
1163         int offset = -1;
1164
1165         if (rd->curspos >= 0)
1166                 offset = rd->prefix_bytes + rd->curspos;
1167         if (rd->image)
1168                 render_image(ci->home, ci->focus, rd->line, True,
1169                              rd->scale, offset, False, 0, 0);
1170         else {
1171                 measure_line(ci->home, ci->focus, offset);
1172                 draw_line(ci->home, ci->focus, offset);
1173         }
1174         return 1;
1175 }
1176
1177 DEF_CMD(renderline_measure)
1178 {
1179         struct rline_data *rd = &ci->home->data;
1180         bool end_of_page;
1181         if (rd->image)
1182                 return render_image(ci->home, ci->focus, rd->line,
1183                                     False, rd->scale, ci->num, False, 0, 0);
1184
1185         end_of_page = measure_line(ci->home, ci->focus,
1186                                    ci->num < 0 ? -1 : rd->prefix_bytes + ci->num);
1187         rd->prefix_pixels = 0;
1188         if (rd->prefix_bytes) {
1189                 struct xy xy = find_curs(ci->home, rd->prefix_bytes, NULL);
1190                 rd->prefix_pixels = xy.x;
1191         }
1192         if (ci->num >= 0) {
1193                 /* Find cursor and report x,y pos and attributes */
1194                 const char *cursattr = NULL;
1195                 struct xy xy;
1196                 xy = find_curs(ci->home, rd->prefix_bytes + ci->num, &cursattr);
1197                 comm_call(ci->comm2, "cb", ci->focus, end_of_page, NULL,
1198                           cursattr);
1199                 ci->home->cx = xy.x;
1200                 ci->home->cy = xy.y;
1201         }
1202         return end_of_page ? 2 : 1;
1203 }
1204
1205 DEF_CMD(renderline_findxy)
1206 {
1207         struct rline_data *rd = &ci->home->data;
1208         const char *xyattr = NULL;
1209         int pos;
1210
1211         if (rd->image)
1212                 return render_image(ci->home, ci->focus, rd->line,
1213                                     False, rd->scale, -1, True,
1214                                     ci->x, ci->y);
1215
1216         measure_line(ci->home, ci->focus,
1217                      ci->num < 0 ? -1 : rd->prefix_bytes + ci->num);
1218         pos = find_xy(ci->home, ci->focus, ci->x, ci->y, &xyattr);
1219         if (pos >= rd->prefix_bytes)
1220                 pos -= rd->prefix_bytes;
1221         else {
1222                 pos = 0;
1223                 xyattr = NULL;
1224         }
1225         comm_call(ci->comm2, "cb", ci->focus, pos, NULL, xyattr);
1226         return pos+1;
1227 }
1228
1229 DEF_CMD(renderline_get)
1230 {
1231         struct rline_data *rd = &ci->home->data;
1232         char buf[20];
1233         const char *val = buf;
1234
1235         if (!ci->str)
1236                 return Enoarg;
1237         if (strcmp(ci->str, "prefix_len") == 0)
1238                 snprintf(buf, sizeof(buf), "%d", rd->prefix_pixels);
1239         else if (strcmp(ci->str, "curs_width") == 0)
1240                 snprintf(buf, sizeof(buf), "%d", rd->curs_width);
1241         else if (strcmp(ci->str, "width") == 0)
1242                 snprintf(buf, sizeof(buf), "%d", rd->width);
1243         else
1244                 return Einval;
1245
1246         comm_call(ci->comm2, "attr", ci->focus, 0, NULL, val);
1247         return 1;
1248 }
1249
1250 static char *cvt(char *str safe)
1251 {
1252         /* Convert:
1253          *    << to < ack  (ack is a no-op)
1254          *    < stuff > to soh stuff stx
1255          *    </> to ack ack etx
1256          */
1257         char *c;
1258         for (c = str; *c; c += 1) {
1259                 if (c[0] == soh || c[0] == ack)
1260                         break;
1261                 if (c[0] == '<' && c[1] == '<') {
1262                         c[1] = ack;
1263                         c++;
1264                         continue;
1265                 }
1266                 if (c[0] != '<')
1267                         continue;
1268                 if (c[1] == '/') {
1269                         c[0] = ack;
1270                         c[1] = ack;
1271                         c[2] = etx;
1272                         c += 2;
1273                         continue;
1274                 }
1275                 c[0] = soh;
1276                 while (c[0] && c[1] != '>')
1277                         c++;
1278                 c[1] = stx;
1279         }
1280         return str;
1281 }
1282
1283 DEF_CMD(renderline_set)
1284 {
1285         struct rline_data *rd = &ci->home->data;
1286         const char *old = rd->line;
1287         struct xy xyscale = pane_scale(ci->focus);
1288         char *prefix = pane_attr_get(ci->focus, "prefix");
1289         bool word_wrap = pane_attr_get_int(ci->focus, "word-wrap", 0) != 0;
1290
1291         if (!ci->str)
1292                 return -Enoarg;
1293         if (prefix)
1294                 prefix = cvt(strconcat(ci->home, "<bold>", prefix, "</>"));
1295
1296         if (prefix)
1297                 rd->line = strconcat(NULL, prefix, ci->str);
1298         else
1299                 rd->line = strdup(ci->str);
1300         rd->prefix_bytes = strlen(prefix?:"");
1301         cvt(rd->line + rd->prefix_bytes);
1302
1303         rd->curspos = ci->num;
1304         if (strcmp(rd->line, old) != 0 ||
1305             (old && xyscale.x != rd->scale) ||
1306             (old && word_wrap != rd->word_wrap)) {
1307                 pane_damaged(ci->home, DAMAGED_REFRESH);
1308                 pane_damaged(ci->home->parent, DAMAGED_REFRESH);
1309                 rd->scale = xyscale.x;
1310                 rd->word_wrap = word_wrap;
1311                 parse_line(rd);
1312         }
1313         free((void*)old);
1314         ci->home->damaged &= ~DAMAGED_VIEW;
1315         return 1;
1316 }
1317
1318 DEF_CMD(renderline_close)
1319 {
1320         struct rline_data *rd = &ci->home->data;
1321
1322         free((void*)rd->line);
1323         return 1;
1324 }
1325
1326 static struct map *rl_map;
1327 DEF_LOOKUP_CMD(renderline_handle, rl_map);
1328
1329 DEF_CMD(renderline_attach)
1330 {
1331         struct pane *p;
1332         struct rline_data *rd;
1333
1334         if (!rl_map) {
1335                 rl_map = key_alloc();
1336                 key_add(rl_map, "render-line:draw", &renderline_draw);
1337                 key_add(rl_map, "Refresh", &renderline_refresh);
1338                 key_add(rl_map, "render-line:measure", &renderline_measure);
1339                 key_add(rl_map, "render-line:findxy", &renderline_findxy);
1340                 key_add(rl_map, "get-attr", &renderline_get);
1341                 key_add(rl_map, "render-line:set", &renderline_set);
1342                 key_add(rl_map, "Close", &renderline_close);
1343                 key_add(rl_map, "Free", &edlib_do_free);
1344         }
1345
1346         p = pane_register(ci->focus, ci->num, &renderline_handle.c);
1347         if (!p)
1348                 return Efail;
1349         rd = &p->data;
1350         rd->line = strdup("");
1351
1352         return comm_call(ci->comm2, "cb", p);
1353 }
1354
1355 void edlib_init(struct pane *ed safe)
1356 {
1357         call_comm("global-set-command", ed, &renderline_attach, 0, NULL,
1358                   "attach-renderline");
1359 }