2 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * A renderline pane will take a single line of marked-up text
6 * and draw it. The "line" may well be longer that the width
7 * of the pane, and it might then be wrapped generatinging
8 * multiple display lines.
10 * The render-lines pane will place multiple renderline panes and use
11 * them to do the drawing - resizing and moving them as necessary to fit
12 * the size of the text. Other panes can use renderline for similar
13 * purposes. messageline uses just one renderline.
15 * A renderline pane can sit in the normal stack and receive Refresh
16 * messages to trigger drawing, or can sit "beside" the stack with a negative
17 * 'z' value. In that can the owner need to explicitly request refresh.
19 * "render-line:set" will set the content of the line
20 * "render-line:measure" will determine layout and size given the available
21 * width and other parameters
22 * "render-line:draw" will send drawing commands.
23 * "Refresh" does both the measure and the draw.
26 #define _GNU_SOURCE /* for asprintf */
32 #define PANE_DATA_TYPE struct rline_data
36 /* There is one render_item entry for
37 * - each string of text with all the same attributes
38 * - each individual TAB
39 * - each unknown control character
40 * - the \n \f or \0 which ends the line
41 * When word-wrap is enabled, strings of spaces get
42 * different attributes, so a different render_item entry.
44 * attributes understood at this level are:
45 * left:nn - left margin - in "points" (10 points per char normally)
46 * right:nn - right margin
47 * tab:nn - move to nn from left margin or -nn from right margin
48 * rtab - from here to next tab or eol right-aligned
49 * center or centre - equal space inserted here and before next
50 * or ctab tab-stop or margin
51 * space-above:nn - extra space before (wrapped) line
52 * space-below:nn - extra space after (wrapped) line
53 * height:nn - override height. This effectively adds space above
54 * every individual line if the whole line is wrapped
55 * wrap - text with this attr can be hidden when used as a wrap
56 * point. Not hidden if cursor in the region.
57 * wrap-margin - remember this x offset as left margin of wrapped lines
58 * wrap-head=xx - text is inserted at start of line when wrapped
59 * wrap-tail=xx - text to include at end of line when wrapped. This
60 * determines how far before right margin the wrap is
62 * wrap-XXXX - attrs to apply to wrap head/tail. Anything not
63 * recognised has "wrap-" stripped and is used for the
64 * head and tail. Default is fg:blue,underline
65 * hide - Text is hidden if cursor is not within range.
67 * "nn" is measured in "points" which is 1/10 the nominal width of chars
68 * in the default font size, which is called "10". A positive value is
69 * measured from the left margin or, which setting margins, from the
70 * relevant page edge. A negative value is measures from the right margin.
74 /* When an entry is split for line-wrap:
75 * 'split_cnt' is count of splits (total lines - 1)
76 * ' split_list' is offsets from start where split happens
77 * 'x' position of wrapped portions is wrap_marign or head_length
78 * 'y' position of wrapped portions increases line_height for each
81 struct render_item *next;
82 const char *attr safe;
83 unsigned short *split_list;
84 unsigned short start, len; // in rd->line
85 unsigned short height, width;
86 signed short x,y; /* signed so x can go negative when shifted */
88 unsigned short wrap_x; /* If this item wraps, wrap_x is the margin */
89 uint8_t split_cnt; /* Wrap happens this many times at byte
90 * positions in split_list */
91 uint8_t wrap; /* This and consecutive render_items
92 * with the same wrap number form an
93 * optional wrap point. It is only visible
94 * when not wrapped, or when cursor is in
97 uint8_t hide; /* This and consecutive render_items
98 * with the same hide number form a
99 * hidden extent which is visible when
100 * the cursor is in it.
105 unsigned int tab_cols:4; /* For \t char */
107 TAB_LEFT = 0, // No extra space (after tab stop)
108 TAB_RIGHT, // Add extra space here so there no space at
109 // next tab stop or margin
110 TAB_CENTRE, // Add extra space here, half of what TAB_RIGHT
114 /* A "tab" value of 0 means left margin, and negative is measured from right
115 * margin, so we need some other value to say "no value here"
117 static const short TAB_UNSET = (1<<(14-2));
120 unsigned short prefix_bytes, prefix_pixels;
122 short left_margin, right_margin;
123 short space_above, space_below;
124 unsigned short line_height, min_height;
125 unsigned short scale;
126 unsigned short width;
127 unsigned short ascent;
128 char *wrap_head, *wrap_tail, *wrap_attr;
129 int head_length, tail_length;
136 struct xy image_size;
137 /* These used to check is measuring is needed, or to record
138 * results of last measurement */
139 unsigned short measure_width, measure_height;
140 short measure_offset, measure_shift_left;
141 struct render_item *content;
143 #include "core-pane.h"
145 static void aappend(struct buf *b safe, char const *a safe)
148 while (*end >= ' ' && *end != ',')
150 buf_concat_len(b, a, end-a);
154 static void add_render(struct rline_data *rd safe,
155 struct render_item **safe*rip safe,
156 const char *start safe, const char *end safe,
158 short *tab safe, enum tab_align *align safe,
159 bool *wrap_margin safe,
160 short wrap, short hide)
162 struct render_item *ri;
163 struct render_item **riend = *rip;
166 ri->attr = strdup(attr);
167 ri->start = start - rd->line;
168 ri->len = end - start;
169 ri->tab_align = *align;
173 ri->wrap_margin = *wrap_margin;
174 ri->eol = !!strchr("\n\f\0", *start);
177 *wrap_margin = False;
183 static void parse_line(struct rline_data *rd safe)
185 /* Parse out markup in line into a renderlist with
186 * global content directly in rd.
188 struct buf attr, wrapattr;
189 struct render_item *ri = NULL, **riend = &ri;
190 const char *line = rd->line;
191 bool wrap_margin = False;
192 short tab = TAB_UNSET;
193 enum tab_align align = TAB_LEFT;
194 int hide = 0, hide_num = 0, hide_depth = 0;
195 int wrap = 0, wrap_num = 0, wrap_depth = 0;
198 rd->left_margin = rd->right_margin = 0;
199 rd->space_above = rd->space_below = 0;
201 aupdate(&rd->wrap_head, NULL);
202 aupdate(&rd->wrap_tail, NULL);
203 aupdate(&rd->wrap_attr, NULL);
209 rd->image = strstarts(line, SOH "image:");
211 rd->image_size.x = 0;
219 rd->measure_width = 0; // force re-measure
221 struct render_item *r = ri;
224 unalloc_str_safe(r->attr, pane);
229 const char *st = line;
233 (!rd->word_wrap || c != ' '))
236 if (line - 1 > st || tab != TAB_UNSET) {
237 /* All text from st to line-1 has "attr' */
238 add_render(rd, &riend, st, line-1, buf_final(&attr),
239 &tab, &align, &wrap_margin, wrap, hide);
247 /* Move 'line' over the attrs */
248 while (*line && line[-1] != stx)
251 /* A set of attrs begins and ends with ',' so that
252 * ",," separates sets of attrs
253 * An empty set will be precisely 1 ','. We strip
254 * "attr," as long as we can, then strip one more ',',
255 * which should leave either a trailing comma, or an
259 buf_append(&attr, ',');
260 foreach_attr(a, v, st, line) {
261 if (amatch(a, "centre") || amatch(a, "center") ||
266 } else if (amatch(a, "tab") && v) {
269 } else if (amatch(a, "rtab")) {
271 } else if (amatch(a, "left") && v) {
272 rd->left_margin = anum(v);
273 } else if (amatch(a, "right") && v) {
274 rd->right_margin = anum(v);
275 } else if (amatch(a, "space-above") && v) {
276 rd->space_above = anum(v);
277 } else if (amatch(a, "space-below") && v) {
278 rd->space_below = anum(v);
279 } else if (amatch(a, "height") && v) {
280 rd->min_height = anum(v);
281 } else if (amatch(a, "wrap")) {
283 wrap_depth = old_len;
284 } else if (amatch(a, "wrap-margin")) {
286 } else if (amatch(a, "wrap-head")) {
287 aupdate(&rd->wrap_head, v);
288 } else if (amatch(a, "wrap-tail")) {
289 aupdate(&rd->wrap_tail, v);
290 } else if (aprefix(a, "wrap-")) {
291 aappend(&wrapattr, a+5);
292 } else if (amatch(a, "word-wrap")) {
297 } else if (amatch(a, "hide")) {
299 hide_depth = old_len;
306 /* strip last set of attrs */
307 while (attr.len >= 2 &&
308 attr.b[attr.len-1] == ',' &&
309 attr.b[attr.len-2] != ',') {
310 /* strip this attr */
312 while (attr.len && attr.b[attr.len-1] != ',')
315 /* strip one more ',' */
318 if (wrap && attr.len <= wrap_depth)
320 if (hide && attr.len <= hide_depth)
324 /* Just ignore this */
327 /* This and following spaces are wrappable */
332 add_render(rd, &riend, st - 1, line, buf_final(&attr),
333 &tab, &align, &wrap_margin, wrap, hide);
341 /* Each tab gets an entry of its own, as does any
342 * stray control character.
343 * \f \n and even \0 do. These are needed for
344 * easy cursor placement.
346 add_render(rd, &riend, st, line, buf_final(&attr),
347 &tab, &align, &wrap_margin, wrap, hide);
354 if (buf_final(&wrapattr)[0])
355 rd->wrap_attr = buf_final(&wrapattr);
358 rd->wrap_attr = strdup(",fg:blue,underline,");
362 static inline struct call_return do_measure(struct pane *p safe,
363 struct render_item *ri safe,
364 int splitpos, int len,
367 struct rline_data *rd = p->data;
368 struct call_return cr;
370 char *str = rd->line + ri->start + splitpos;
373 if (ri->len && rd->line[ri->start] == '\t') {
376 len = ri->tab_cols - splitpos;
379 len = ri->len - splitpos;
383 cr = call_ret(all, "Draw:text-size", p,
385 rd->scale, NULL, ri->attr);
388 if (cr.ret == 1 && maxwidth >= 0 &&
390 /* All fits in maxwidth */
392 /* Report position in rd->line */
394 cr.s = rd->line + ri->start;
395 if (splitpos + cr.i >= ri->tab_cols)
402 static inline struct call_return measure_str(struct pane *p safe,
406 struct rline_data *rd = p->data;
408 return call_ret(all, "Draw:text-size", p,
410 rd->scale, NULL, attr);
413 static inline void do_draw(struct pane *p safe,
414 struct pane *focus safe,
415 struct render_item *ri safe, int split,
419 struct rline_data *rd = p->data;
425 str = rd->line + ri->start;
429 if (ri->len && strchr("\f\n\0", str[0])) {
430 /* end marker - len extends past end of string,
431 * but mustn't write there. Only need to draw if
435 home_call(focus, "Draw:text", p, offset, NULL, "",
436 rd->scale, NULL, ri->attr, x, y);
439 if (ri->len && str[0] == '\t') {
444 if (ri->split_list) {
445 if (split < ri->split_cnt)
446 len = ri->split_list[split];
448 len -= ri->split_list[split-1];
451 if (ri->len && str[0] == '\t')
452 /* Tab need a list of spaces */
455 if (split > 0 && split <= ri->split_cnt && ri->split_list) {
456 str += ri->split_list[split-1];
457 offset -= ri->split_list[split-1];
465 home_call(focus, "Draw:text", p, offset, NULL, str,
466 rd->scale, NULL, ri->attr, x, y);
470 static inline void draw_wrap(struct pane *p safe,
471 struct pane *focus safe,
475 struct rline_data *rd = p->data;
477 home_call(focus, "Draw:text", p,
479 rd->scale, NULL, rd->wrap_attr,
483 static bool add_split(struct render_item *ri safe, int split)
485 int i = ri->split_cnt;
489 ri->split_list = realloc(ri->split_list,
490 sizeof(ri->split_list[0]) * ri->split_cnt);
491 ri->split_list[i] = split;
495 static int calc_pos(int num, int margin, int width)
498 return num * width / 10;
499 if (-num * width / 10 > margin)
501 return margin + num * width / 10;
504 static int measure_line(struct pane *p safe, struct pane *focus safe, int offset)
506 /* First measure each render_item entry setting
507 * height, ascent, width.
508 * Then use that with tab information to set 'x' position for
510 * Finally identify line-break locations if needed and set 'y'
513 * Return 1 if there is an EOL ('\n')
514 * 2 if there is an end-of-page ('\f')
518 struct rline_data *rd = p->data;
519 struct render_item *ri, *wraprl;
520 int shift_left = pane_attr_get_int(focus, "render-wrap", -1);
521 bool wrap = shift_left < 0;
525 struct xy xyscale = pane_scale(focus);
528 struct call_return cr;
531 bool seen_rtab = False;
532 unsigned int offset_hide = 0;
536 if (xyscale.x == rd->scale && p->w == rd->measure_width &&
537 shift_left == rd->measure_shift_left &&
538 offset == rd->measure_offset) {
540 for (ri = rd->content ; ri ; ri = ri->next) {
543 if (ri->eol && rd->line[ri->start] == '\n')
545 if (ri->eol && rd->line[ri->start] == '\f')
548 pane_resize(p, p->x, p->y, p->w, rd->measure_height);
551 rd->scale = xyscale.x;
552 rd->measure_width = p->w;
553 rd->measure_offset = offset;
554 rd->measure_shift_left = shift_left;
556 cr = measure_str(p, "M", "");
557 rd->curs_width = cr.x;
559 rd->line_height = cr.y;
561 if (rd->min_height > 10)
562 rd->line_height = rd->line_height * rd->min_height / 10;
565 cr = measure_str(p, rd->wrap_head, rd->wrap_attr);
566 rd->head_length = cr.x;
568 cr = measure_str(p, rd->wrap_tail ?: "\\", rd->wrap_attr);
569 rd->tail_length = cr.x;
571 left_margin = calc_pos(rd->left_margin, p->w, rd->curs_width);
572 /* 0 means right edge for right_margin, and left edge for all others */
573 right_margin = p->w - calc_pos(-rd->right_margin, p->w, rd->curs_width);
576 for (ri = rd->content ; ri ; ri = ri->next)
577 if (offset < ri->start + ri->len) {
578 offset_hide = ri->hide;
583 for (ri = rd->content; ri; ri = ri->next) {
584 ri->hidden = (ri->hide && ri->hide != offset_hide);
590 (unsigned char)rd->line[ri->start] >= ' ') {
591 cr = do_measure(p, ri, 0, -1, -1);
595 /* Ensure attributes of newline add to line
596 * height. The width will be ignored. */
598 if (rd->line[ri->start] == '\n')
600 if (rd->line[ri->start] == '\f')
602 } else if (rd->line[ri->start] == '\t') {
606 tmp[1] = '@' + (rd->line[ri->start] & 31);
608 cr = measure_str(p, tmp, ri->attr);
611 if (cr.y > rd->line_height)
612 rd->line_height = cr.y;
614 if (cr.i2 > rd->ascent)
616 ri->width = ri->eol ? 0 : cr.x;
617 rd->width += ri->width;
619 if (ri->start <= offset && offset <= ri->start + ri->len) {
620 cr = measure_str(p, "M", ri->attr);
621 rd->curs_width = cr.x;
625 free(ri->split_list);
626 ri->split_list = NULL;
628 /* Set 'x' position honouring tab stops, and set length
629 * of "\t" characters. Also handle \n and \f.
631 x = left_margin - (shift_left > 0 ? shift_left : 0);
632 y = rd->space_above * curs_height / 10;
633 for (ri = rd->content; ri; ri = ri->next) {
635 struct render_item *ri2;
641 if (ri->tab != TAB_UNSET)
642 x = left_margin + calc_pos(ri->tab,
643 right_margin - left_margin,
648 x = 0; /* Don't include shift. probably not margin */
649 if (rd->line[ri->start])
650 y += rd->line_height;
653 if (ri->tab_align == TAB_LEFT) {
655 if (ri->len && rd->line[ri->start] == '\t') {
656 int col = x / ri->width;
657 int cols= 8 - (col % 8);
664 if (ri->tab_align == TAB_RIGHT)
668 ri2 && ri2->tab_align == TAB_LEFT && ri2->tab == TAB_UNSET;
671 while (ri2 && ri2->tab == TAB_UNSET)
673 margin = right_margin - left_margin;
675 margin = left_margin + calc_pos(ri2->tab,
676 right_margin - left_margin,
678 if (ri->tab_align == TAB_RIGHT)
679 x = x + margin - x - w;
681 x = x + (margin - x - w) / 2;
683 while (ri->next && ri->next->next && ri->next->tab_align == TAB_LEFT) {
691 /* Now we check to see if the line needs to be wrapped and
692 * if so, adjust some y values and reduce x. If we need to
693 * split an individual entry we create an array of split points.
695 xdiff = 0; ydiff = 0; y = 0;
697 wrap_margin = left_margin + rd->head_length;
698 for (ri = rd->content ; wrap && ri ; ri = ri->next) {
702 if (ri->wrap && (wraprl == NULL || ri->wrap != wraprl->wrap))
705 wrap_margin = ri->x + xdiff;
706 ri->wrap_x = wrap_margin;
714 if (ri->x + ri->width <= right_margin - rd->tail_length)
716 if ((ri->next == NULL || ri->next->eol) &&
717 ri->x + ri->width <= right_margin &&
719 /* Don't need any tail space for last item.
720 * This allows rtab to fully right-justify,
721 * but leaves no-where for the cursor. So
722 * only do it if rtab is present.
725 /* This doesn't fit here */
727 /* Move wraprl to next line and hide it unless it contains cursor */
729 struct render_item *wraprl2, *ri2;
731 /* Find last ritem in wrap region.*/
732 for (wraprl2 = wraprl ;
733 wraprl2->next && wraprl2->next->wrap == wraprl->wrap ;
734 wraprl2 = wraprl2->next)
736 wrap_margin = wraprl2->wrap_x;
738 xd = wraprl2->next->x - wrap_margin;
739 if (wraprl2->next->start > ri->start)
742 xd = wraprl2->x - wrap_margin;
743 if (wraprl2->start > ri->start)
747 offset >= wraprl->start &&
748 offset <= wraprl2->start + wraprl2->len) {
749 /* Cursor is here, so keep it visible.
750 * If we are still in the wrap region, pretend
751 * it didn't exist, else move first item
752 * after it to next line
754 if (ri->wrap == wraprl->wrap)
757 /* Hide the wrap region */
758 while (wraprl != wraprl2) {
759 wraprl->hidden = True;
760 wraprl = wraprl->next;
763 wraprl->hidden = True;
764 while (ri->next && ri->next->hidden)
767 for (ri2 = wraprl2->next ; ri2 && ri2 != ri->next; ri2 = ri2->next) {
768 ri2->y += rd->line_height;
770 if (ri2->wrap_margin)
771 wrap_margin = ri2->x;
772 ri2->wrap_x = wrap_margin;
775 ydiff += rd->line_height;
778 ri->x + ri->width <= right_margin - rd->tail_length)
782 /* Might need to split this ri into two or more pieces */
786 cr = do_measure(p, ri, splitpos, -1,
787 right_margin - rd->tail_length - x);
789 /* Remainder fits now */
791 if (cr.i == 0 && splitpos == 0) {
792 /* None of this fits here, move to next line */
793 xdiff -= ri->x - wrap_margin;
796 ydiff += rd->line_height;
797 ri->y += rd->line_height;
801 /* Nothing fits and we already split - give up */
803 /* re-measure the first part */
804 cr = do_measure(p, ri, splitpos,
806 right_margin - rd->tail_length - x);
807 ydiff += rd->line_height;
810 xdiff -= ri->x - wrap_margin;
814 if (!add_split(ri, splitpos))
819 (rd->space_above + rd->space_below) * curs_height / 10 +
820 ydiff + rd->line_height;
821 pane_resize(p, p->x, p->y, p->w, rd->measure_height);
822 attr_set_int(&p->attrs, "line-height", rd->line_height);
826 static void draw_line(struct pane *p safe, struct pane *focus safe, int offset)
828 struct rline_data *rd = p->data;
829 struct render_item *ri;
830 char *wrap_tail = rd->wrap_tail ?: "\\";
831 char *wrap_head = rd->wrap_head ?: "";
833 home_call(focus, "Draw:clear", p, 0, NULL, rd->background);
837 for (ri = rd->content ; ri; ri = ri->next) {
844 if (offset < 0 || offset >= ri->start + ri->len)
846 else if (offset < ri->start)
849 cpos = offset - ri->start;
851 do_draw(p, focus, ri, 0, cpos, ri->x, y);
853 while (split < ri->split_cnt ||
854 (ri->next && !ri->next->eol && ri->next->y > y)) {
856 /* don't show head/tail for wrap-regions */
857 if (*wrap_tail /*&& !ri->wrap*/)
858 draw_wrap(p, focus, wrap_tail,
859 p->w - rd->tail_length, y);
860 y += rd->line_height;
861 if (*wrap_head /*&& !ri->wrap*/)
862 draw_wrap(p, focus, wrap_head,
864 if (ri->split_list && split < ri->split_cnt) {
866 do_draw(p, focus, ri, split, cpos,
872 if (offset < ri->start + ri->len)
877 static int find_xy(struct pane *p safe, struct pane *focus safe,
878 short x, short y, const char **xyattr)
880 /* Find the location in ->line that is best match for x,y.
881 * If x,y is on the char at that location, when store attributes
882 * for the char in xyattr
883 * We always return a location, even if no xyattr.
884 * We use the last render_item that is not definitely after x,y
885 * We do not consider the eol render_item
887 struct call_return cr;
888 struct rline_data *rd = p->data;
889 struct render_item *r, *ri = NULL;
893 for (r = rd->content; r ; r = r->next) {
897 if (r->y <= y && r->x <= x) {
902 for (split = 0; split < r->split_cnt; split++) {
903 if (r->y + (split + 1) * rd->line_height <= y &&
904 r->wrap_x <= x && r->split_list) {
906 splitpos = r->split_list[split];
907 start = r->start + splitpos;
914 /* newline or similar. Can only be at start */
916 cr = do_measure(p, ri, splitpos, -1, x - ri->x);
917 if ((splitpos ? ri->wrap_x : ri->x ) + cr.x > x &&
918 ri->y + rd->line_height * (1 + splitpos) > y &&
920 /* This is a bit of a hack.
921 * We stick the x,y co-ords of the start
922 * of the current attr in front of the
923 * attrs so render-lines can provide a
924 * good location for a menu
927 struct render_item *ri2;
929 for (ri2 = rd->content; ri2 != ri; ri2 = ri2->next)
930 if (strcmp(ri2->attr, ri->attr) == 0)
932 snprintf(buf, sizeof(buf), "%dx%d,", ax, y);
933 *xyattr = strconcat(p, buf, ri->attr);
936 return cr.s - rd->line;
940 static struct xy find_curs(struct pane *p safe, int offset, const char **cursattr)
942 struct call_return cr;
943 struct xy xy = {0,0};
946 struct rline_data *rd = p->data;
947 struct render_item *r, *ri = NULL;
949 for (r = rd->content; r; r = r->next) {
952 if (offset < r->start)
957 /* This should be impossible as the eol goes past
958 * the largest offset.
962 if (offset < ri->start)
967 /* offset now from ri->start */
968 if (ri->len && rd->line[ri->start] == '\t' && offset)
969 offset = ri->tab_cols;
971 *cursattr = ri->attr;
973 for (split = 0; split < ri->split_cnt && ri->split_list; split ++) {
974 if (offset < ri->split_list[split])
976 st = ri->split_list[split];
979 cr.x = offset ? ri->width : 0;
981 cr = do_measure(p, ri, st, offset - st, -1);
984 xy.x = ri->wrap_x + cr.x;
987 xy.y = ri->y + split * rd->line_height;
988 if (ri->next == NULL && offset > ri->len) {
989 /* After the newline ? Go to next line */
991 xy.y += rd->line_height;
996 static void parse_map(const char *map safe, short *rowsp safe, short *colsp safe)
998 /* The map must be a sequence of rows, each of which is
999 * a sequence of chars starting CAPS and continuing lower.
1000 * Each row must be the same length.
1004 short this_cols = 0;
1006 for (; *map && isalpha(*map); map += 1) {
1007 if (isupper(*map)) {
1009 if (this_cols != cols)
1010 /* Rows aren't all the same */
1017 } else if (rows == 0) {
1018 /* First row malformed */
1024 if (this_cols != cols)
1025 /* Last row is wrong length */
1031 static int render_image(struct pane *p safe, struct pane *focus safe,
1032 const char *line safe,
1034 int offset, int want_xypos, short x, short y)
1036 struct rline_data *rd = p->data;
1038 const char *orig_line = line;
1039 short width, height;
1040 short rows = -1, cols = -1;
1043 struct xy xyscale = pane_scale(focus);
1044 int scale = xyscale.x;
1045 struct xy size= {-1, -1};
1051 home_call(focus, "Draw:clear", p);
1053 width = p->parent->w/2;
1054 height = p->parent->h/2;
1056 while (*line == soh)
1059 end = line + strcspn(line, STX);
1060 foreach_attr(a, v, line, end) {
1061 if (amatch(a, "image") && v) {
1063 if (rd->image_size.x <= 0) {
1064 struct call_return cr =
1065 home_call_ret(all, focus,
1068 if (cr.x > 0 && cr.y > 0) {
1071 rd->image_size = size;
1074 size = rd->image_size;
1075 } else if (amatch(a, "width") && v) {
1076 width = anum(v) * scale / 1000;
1077 } else if (amatch(a, "height") && v) {
1078 height = anum(v) * scale / 1000;
1079 } else if (amatch(a, "noupscale") &&
1080 fname && size.x > 0) {
1081 if (size.x < p->parent->w)
1083 if (size.y < p->parent->h)
1085 } else if ((offset >= 0 || want_xypos) &&
1086 amatch(a, "map") && v) {
1088 * A map is map:LxxxLxxxLxxxLxxx or similar
1089 * Where each "Lxxx" recognised by a CAP followed
1090 * by lower is a row, and each char is a column.
1091 * So we count the CAPs to get row count, and
1092 * count the chars to get col count.
1093 * If want_xypos then map x,y ino that matrix
1094 * and return pos in original line of cell.
1095 * If offset is in the map, then set ->cx,->cy to
1096 * the appropriate location.
1098 map_offset = v - orig_line;
1099 parse_map(v, &rows, &cols);
1100 if (rows > 0 && cols > 0)
1101 snprintf(mode, sizeof(mode),
1102 ":%dx%d", cols, rows);
1105 pane_resize(p, (p->parent->w - width)/2, p->y, width, height);
1107 attr_set_int(&p->attrs, "line-height", p->h);
1109 /* Adjust size to be the scaled size - it must fit in
1112 if (size.x * p->h > size.y * p->w) {
1113 /* Image is wider than space */
1114 size.y = size.y * p->w / size.x;
1118 /* Image is taller than space */
1119 size.x = size.x * p->h / size.y;
1121 ioffset = (p->w - size.x) / 2;
1126 if (offset >= 0 && map_offset > 0 && rows > 0 &&
1127 offset >= map_offset && offset < map_offset + (rows*cols)) {
1128 /* Place cursor based on where 'offset' is in the map */
1129 short r = (offset - map_offset) / cols;
1130 short c = offset - map_offset - r * cols;
1131 p->cx = size.x / cols * c + ioffset;
1132 p->cy = size.y / rows * r;
1135 if (fname && dodraw)
1136 home_call(focus, "Draw:image", p, 0, NULL, fname,
1141 if (want_xypos && map_offset > 0 && rows > 0) {
1142 /* report where x,y is as a position in the map */
1143 short r = y * rows / size.y;
1144 short c = (x > ioffset ? x - ioffset : 0) * cols / size.x;
1145 if (c >= cols) c = cols - 1;
1146 /* +1 below because result must never be zero */
1147 return map_offset + r * cols + c + 1;
1152 DEF_CMD(renderline_draw)
1154 struct rline_data *rd = ci->home->data;
1159 offset = rd->prefix_bytes + ci->num;
1162 render_image(ci->home, ci->focus, rd->line, True,
1163 offset, False, 0, 0);
1165 draw_line(ci->home, ci->focus, offset);
1168 xy = find_curs(ci->home, rd->prefix_bytes + ci->num, NULL);
1169 ci->home->cx = xy.x;
1170 ci->home->cy = xy.y;
1175 DEF_CMD(renderline_refresh)
1177 struct rline_data *rd = ci->home->data;
1180 if (rd->curspos >= 0)
1181 offset = rd->prefix_bytes + rd->curspos;
1183 render_image(ci->home, ci->focus, rd->line, True,
1184 offset, False, 0, 0);
1186 measure_line(ci->home, ci->focus, offset);
1187 draw_line(ci->home, ci->focus, offset);
1192 DEF_CMD(renderline_measure)
1194 struct rline_data *rd = ci->home->data;
1198 return render_image(ci->home, ci->focus, rd->line,
1199 False, ci->num, False, 0, 0);
1201 ret = measure_line(ci->home, ci->focus,
1202 ci->num < 0 ? -1 : rd->prefix_bytes + ci->num);
1203 rd->prefix_pixels = 0;
1204 if (rd->prefix_bytes) {
1205 struct xy xy = find_curs(ci->home, rd->prefix_bytes, NULL);
1206 rd->prefix_pixels = xy.x;
1209 /* Find cursor and report x,y pos and attributes */
1210 const char *cursattr = NULL;
1212 xy = find_curs(ci->home, rd->prefix_bytes + ci->num, &cursattr);
1213 comm_call(ci->comm2, "cb", ci->focus, ret, NULL,
1215 ci->home->cx = xy.x;
1216 ci->home->cy = xy.y;
1221 DEF_CMD(renderline_findxy)
1223 struct rline_data *rd = ci->home->data;
1224 const char *xyattr = NULL;
1228 return render_image(ci->home, ci->focus, rd->line,
1232 measure_line(ci->home, ci->focus,
1233 ci->num < 0 ? -1 : rd->prefix_bytes + ci->num);
1234 pos = find_xy(ci->home, ci->focus, ci->x, ci->y, &xyattr);
1235 if (pos >= rd->prefix_bytes)
1236 pos -= rd->prefix_bytes;
1241 comm_call(ci->comm2, "cb", ci->focus, pos, NULL, xyattr);
1245 DEF_CMD(renderline_get)
1247 struct rline_data *rd = ci->home->data;
1249 const char *val = buf;
1253 if (strcmp(ci->str, "prefix_len") == 0)
1254 snprintf(buf, sizeof(buf), "%d", rd->prefix_pixels);
1255 else if (strcmp(ci->str, "curs_width") == 0)
1256 snprintf(buf, sizeof(buf), "%d", rd->curs_width);
1257 else if (strcmp(ci->str, "width") == 0)
1258 snprintf(buf, sizeof(buf), "%d", rd->width);
1262 comm_call(ci->comm2, "attr", ci->focus, 0, NULL, val);
1266 static char *cvt(char *str safe)
1269 * << to < ack (ack is a no-op)
1270 * < stuff > to soh stuff stx
1271 * </> to ack ack etx
1274 for (c = str; *c; c += 1) {
1275 if (c[0] == soh || c[0] == ack)
1277 if (c[0] == '<' && c[1] == '<') {
1285 while (*c && *c != '>')
1295 while (*c && *c != '>') {
1297 (c[1] == '\\' || c[1] == '>'))
1310 DEF_CMD(renderline_set)
1312 struct rline_data *rd = ci->home->data;
1313 const char *old = rd->line;
1314 char *prefix = pane_attr_get(ci->focus, "prefix");
1315 bool word_wrap = pane_attr_get_int(ci->focus, "word-wrap", 0) != 0;
1316 bool bg_changed = False;
1321 prefix = strconcat(ci->home, ACK SOH "bold" STX,
1322 prefix, // No mark in prefix!
1325 rd->line = strconcat(NULL, prefix, ci->str);
1327 rd->line = strdup(ci->str);
1328 rd->prefix_bytes = strlen(prefix?:"");
1329 cvt(rd->line + rd->prefix_bytes);
1331 if (ci->str2 && !rd->background) {
1332 rd->background = strdup(ci->str2);
1334 } else if (!ci->str2 && rd->background) {
1335 free(rd->background);
1336 rd->background = NULL;
1338 } else if (ci->str2 && rd->background &&
1339 strcmp(ci->str2, rd->background) != 0) {
1340 free(rd->background);
1341 rd->background = strdup(ci->str2);
1345 rd->curspos = ci->num;
1346 if (strcmp(rd->line, old) != 0 || bg_changed ||
1347 (old && word_wrap != rd->word_wrap)) {
1348 pane_damaged(ci->home, DAMAGED_REFRESH);
1349 pane_damaged(ci->home->parent, DAMAGED_REFRESH);
1350 rd->word_wrap = word_wrap;
1354 ci->home->damaged &= ~DAMAGED_VIEW;
1358 DEF_CMD_CLOSED(renderline_close)
1360 struct rline_data *rd = ci->home->data;
1361 struct render_item *ri = rd->content;
1363 free((void*)rd->line);
1365 struct render_item *r = ri;
1367 free(r->split_list);
1368 unalloc_str_safe(r->attr, pane);
1371 aupdate(&rd->wrap_head, NULL);
1372 aupdate(&rd->wrap_tail, NULL);
1373 aupdate(&rd->wrap_attr, NULL);
1374 aupdate(&rd->background, NULL);
1378 static struct map *rl_map;
1379 DEF_LOOKUP_CMD(renderline_handle, rl_map);
1381 DEF_CMD(renderline_attach)
1384 struct rline_data *rd;
1387 rl_map = key_alloc();
1388 key_add(rl_map, "render-line:draw", &renderline_draw);
1389 key_add(rl_map, "Refresh", &renderline_refresh);
1390 key_add(rl_map, "render-line:measure", &renderline_measure);
1391 key_add(rl_map, "render-line:findxy", &renderline_findxy);
1392 key_add(rl_map, "get-attr", &renderline_get);
1393 key_add(rl_map, "render-line:set", &renderline_set);
1394 key_add(rl_map, "Close", &renderline_close);
1397 p = pane_register(ci->focus, ci->num, &renderline_handle.c);
1401 rd->line = strdup(ETX); // Imposible string
1403 return comm_call(ci->comm2, "cb", p);
1406 void edlib_init(struct pane *ed safe)
1408 call_comm("global-set-command", ed, &renderline_attach, 0, NULL,
1409 "attach-renderline");