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.
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 * relevant page edge. A negative value is measures from the right margin.
75 /* When an entry is split for line-wrap:
76 * 'split_cnt' is count of splits (total lines - 1)
77 * ' split_list' is offsets from start where split happens
78 * 'x' position of wrapped portions is wrap_marign or head_length
79 * 'y' position of wrapped portions increases line_height for each
82 struct render_item *next;
83 const char *attr safe;
84 unsigned short *split_list;
85 unsigned short start, len; // in rd->line
86 unsigned short height, width;
87 signed short x,y; /* signed so x can go negative when shifted */
89 unsigned short wrap_x; /* If this item wraps, wrap_x is the margin */
90 uint8_t split_cnt; /* Wrap happens this many times at byte
91 * positions in split_list */
92 uint8_t wrap; /* This and consecutive render_items
93 * with the same wrap number form an
94 * optional wrap point. It is only visible
95 * when not wrapped, or when cursor is in
98 uint8_t hide; /* This and consecutive render_items
99 * with the same hide nmber form a
100 * hidden extent which is visible when
101 * the cursor is in it.
106 unsigned int tab_cols:4; /* For \t char */
108 TAB_LEFT = 0, // No extra space (after tab stop)
109 TAB_RIGHT, // Add extra space here so there no space at
110 // next tab stop or margin
111 TAB_CENTRE, // Add extra space here, half of what TAB_RIGHT
115 /* A "tab" value of 0 means left margin, and negative is measured from right
116 * margin, so we need some other value to say "no value here"
118 static const short TAB_UNSET = (1<<(14-2));
121 unsigned short prefix_bytes, prefix_pixels;
123 short left_margin, right_margin;
124 short space_above, space_below;
125 unsigned short line_height, min_height;
126 unsigned short scale;
127 unsigned short width;
128 unsigned short ascent;
129 char *wrap_head, *wrap_tail, *wrap_attr;
130 int head_length, tail_length;
137 struct xy image_size;
138 /* These used to check is measuring is needed, or to record
139 * results of last measurement */
140 unsigned short measure_width, measure_height;
141 short measure_offset, measure_shift_left;
142 struct render_item *content;
144 #include "core-pane.h"
146 static void aappend(struct buf *b safe, char const *a safe)
149 while (*end >= ' ' && *end != ',')
151 buf_concat_len(b, a, end-a);
155 static void add_render(struct rline_data *rd safe,
156 struct render_item **safe*rip safe,
157 const char *start safe, const char *end safe,
159 short *tab safe, enum tab_align *align safe,
160 bool *wrap_margin safe,
161 short wrap, short hide)
163 struct render_item *ri;
164 struct render_item **riend = *rip;
167 ri->attr = strdup(attr);
168 ri->start = start - rd->line;
169 ri->len = end - start;
170 ri->tab_align = *align;
174 ri->wrap_margin = *wrap_margin;
175 ri->eol = !!strchr("\n\f\0", *start);
178 *wrap_margin = False;
184 static void parse_line(struct rline_data *rd safe)
186 /* Parse out markup in line into a renderlist with
187 * global content directly in rd.
189 struct buf attr, wrapattr;
190 struct render_item *ri = NULL, **riend = &ri;
191 const char *line = rd->line;
192 bool wrap_margin = False;
193 short tab = TAB_UNSET;
194 enum tab_align align = TAB_LEFT;
195 int hide = 0, hide_num = 0, hide_depth = 0;
196 int wrap = 0, wrap_num = 0, wrap_depth = 0;
199 rd->left_margin = rd->right_margin = 0;
200 rd->space_above = rd->space_below = 0;
202 aupdate(&rd->wrap_head, NULL);
203 aupdate(&rd->wrap_tail, NULL);
204 aupdate(&rd->wrap_attr, NULL);
210 rd->image = strstarts(line, SOH "image:");
212 rd->image_size.x = 0;
220 rd->measure_width = 0; // force re-measure
222 struct render_item *r = ri;
225 unalloc_str_safe(r->attr, pane);
230 const char *st = line;
234 (!rd->word_wrap || c != ' '))
237 if (line - 1 > st || tab != TAB_UNSET) {
238 /* All text from st to line-1 has "attr' */
239 add_render(rd, &riend, st, line-1, buf_final(&attr),
240 &tab, &align, &wrap_margin, wrap, hide);
248 /* Move 'line' over the attrs */
249 while (*line && line[-1] != stx)
252 /* A set of attrs begins and ends with ',' so that
253 * ",," separates sets of attrs
254 * An empty set will be precisely 1 ','. We strip
255 * "attr," as long as we can, then strip one more ',',
256 * which should leave either a trailing comma, or an
259 buf_append(&attr, ',');
261 foreach_attr(a, v, st, line) {
262 if (amatch(a, "centre") || amatch(a, "center") ||
267 } else if (amatch(a, "tab") && v) {
270 } else if (amatch(a, "rtab")) {
272 } else if (amatch(a, "left") && v) {
273 rd->left_margin = anum(v);
274 } else if (amatch(a, "right") && v) {
275 rd->right_margin = anum(v);
276 } else if (amatch(a, "space-above") && v) {
277 rd->space_above = anum(v);
278 } else if (amatch(a, "space-below") && v) {
279 rd->space_below = anum(v);
280 } else if (amatch(a, "height") && v) {
281 rd->min_height = anum(v);
282 } else if (amatch(a, "wrap")) {
284 wrap_depth = old_len;
285 } else if (amatch(a, "wrap-margin")) {
287 } else if (amatch(a, "wrap-head")) {
288 aupdate(&rd->wrap_head, v);
289 } else if (amatch(a, "wrap-tail")) {
290 aupdate(&rd->wrap_tail, v);
291 } else if (aprefix(a, "wrap-")) {
292 aappend(&wrapattr, a+5);
293 } else if (amatch(a, "word-wrap")) {
298 } else if (amatch(a, "hide")) {
300 hide_depth = old_len;
307 /* strip last set of attrs */
308 while (attr.len >= 2 &&
309 attr.b[attr.len-1] == ',' &&
310 attr.b[attr.len-2] != ',') {
311 /* strip this attr */
313 while (attr.len && attr.b[attr.len-1] != ',')
316 /* strip one more ',' */
319 if (wrap && attr.len <= wrap_depth)
321 if (hide && attr.len <= hide_depth)
325 /* Just ignore this */
328 /* This and following spaces are wrappable */
333 add_render(rd, &riend, st - 1, line, buf_final(&attr),
334 &tab, &align, &wrap_margin, wrap, hide);
342 /* Each tab gets an entry of its own, as does any
343 * stray control character.
344 * \f \n and even \0 do. These are needed for
345 * easy cursor placement.
347 add_render(rd, &riend, st, line, buf_final(&attr),
348 &tab, &align, &wrap_margin, wrap, hide);
355 if (buf_final(&wrapattr)[0])
356 rd->wrap_attr = buf_final(&wrapattr);
359 rd->wrap_attr = strdup(",fg:blue,underline,");
363 static inline struct call_return do_measure(struct pane *p safe,
364 struct render_item *ri safe,
365 int splitpos, int len,
368 struct rline_data *rd = p->data;
369 struct call_return cr;
371 char *str = rd->line + ri->start + splitpos;
374 if (ri->len && rd->line[ri->start] == '\t') {
377 len = ri->tab_cols - splitpos;
380 len = ri->len - splitpos;
384 cr = call_ret(all, "Draw:text-size", p,
386 rd->scale, NULL, ri->attr);
389 if (cr.ret == 1 && maxwidth >= 0 &&
391 /* All fits in maxwidth */
393 /* Report position in rd->line */
395 cr.s = rd->line + ri->start;
396 if (splitpos + cr.i >= ri->tab_cols)
403 static inline struct call_return measure_str(struct pane *p safe,
407 struct rline_data *rd = p->data;
409 return call_ret(all, "Draw:text-size", p,
411 rd->scale, NULL, attr);
414 static inline void do_draw(struct pane *p safe,
415 struct pane *focus safe,
416 struct render_item *ri safe, int split,
420 struct rline_data *rd = p->data;
426 str = rd->line + ri->start;
430 if (ri->len && strchr("\f\n\0", str[0])) {
431 /* end marker - len extends past end of string,
432 * but mustn't write there. Only need to draw if
436 home_call(focus, "Draw:text", p, offset, NULL, "",
437 rd->scale, NULL, ri->attr, x, y);
440 if (ri->len && str[0] == '\t') {
445 if (ri->split_list) {
446 if (split < ri->split_cnt)
447 len = ri->split_list[split];
449 len -= ri->split_list[split-1];
452 if (ri->len && str[0] == '\t')
453 /* Tab need a list of spaces */
456 if (split > 0 && split <= ri->split_cnt && ri->split_list) {
457 str += ri->split_list[split-1];
458 offset -= ri->split_list[split-1];
466 home_call(focus, "Draw:text", p, offset, NULL, str,
467 rd->scale, NULL, ri->attr, x, y);
471 static inline void draw_wrap(struct pane *p safe,
472 struct pane *focus safe,
476 struct rline_data *rd = p->data;
478 home_call(focus, "Draw:text", p,
480 rd->scale, NULL, rd->wrap_attr,
484 static bool add_split(struct render_item *ri safe, int split)
486 int i = ri->split_cnt;
490 ri->split_list = realloc(ri->split_list,
491 sizeof(ri->split_list[0]) * ri->split_cnt);
492 ri->split_list[i] = split;
496 static int calc_pos(int num, int margin, int width)
499 return num * width / 10;
500 if (-num * width / 10 > margin)
502 return margin + num * width / 10;
505 static int measure_line(struct pane *p safe, struct pane *focus safe, int offset)
507 /* First measure each render_item entry setting
508 * height, ascent, width.
509 * Then use that with tab information to set 'x' position for
511 * Finally identify line-break locations if needed and set 'y'
514 * Return 1 if there is an EOL ('\n')
515 * 2 if there is an end-of-page ('\f')
519 struct rline_data *rd = p->data;
520 struct render_item *ri, *wraprl;
521 int shift_left = pane_attr_get_int(focus, "render-wrap", -1);
522 bool wrap = shift_left < 0;
526 struct xy xyscale = pane_scale(focus);
529 struct call_return cr;
532 bool seen_rtab = False;
533 unsigned int offset_hide = 0;
537 if (xyscale.x == rd->scale && p->w == rd->measure_width &&
538 shift_left == rd->measure_shift_left &&
539 offset == rd->measure_offset) {
541 for (ri = rd->content ; ri ; ri = ri->next) {
544 if (ri->eol && rd->line[ri->start] == '\n')
546 if (ri->eol && rd->line[ri->start] == '\f')
549 pane_resize(p, p->x, p->y, p->w, rd->measure_height);
552 rd->scale = xyscale.x;
553 rd->measure_width = p->w;
554 rd->measure_offset = offset;
555 rd->measure_shift_left = shift_left;
557 cr = measure_str(p, "M", "");
558 rd->curs_width = cr.x;
560 rd->line_height = cr.y;
562 if (rd->min_height > 10)
563 rd->line_height = rd->line_height * rd->min_height / 10;
566 cr = measure_str(p, rd->wrap_head, rd->wrap_attr);
567 rd->head_length = cr.x;
569 cr = measure_str(p, rd->wrap_tail ?: "\\", rd->wrap_attr);
570 rd->tail_length = cr.x;
572 left_margin = calc_pos(rd->left_margin, p->w, rd->curs_width);
573 /* 0 means right edge for right_margin, and left edge for all others */
574 right_margin = p->w - calc_pos(-rd->right_margin, p->w, rd->curs_width);
577 for (ri = rd->content ; ri ; ri = ri->next)
578 if (offset < ri->start + ri->len) {
579 offset_hide = ri->hide;
584 for (ri = rd->content; ri; ri = ri->next) {
585 ri->hidden = (ri->hide && ri->hide != offset_hide);
591 (unsigned char)rd->line[ri->start] >= ' ') {
592 cr = do_measure(p, ri, 0, -1, -1);
596 /* Ensure attributes of newline add to line
597 * height. The width will be ignored. */
599 if (rd->line[ri->start] == '\n')
601 if (rd->line[ri->start] == '\f')
603 } else if (rd->line[ri->start] == '\t') {
607 tmp[1] = '@' + (rd->line[ri->start] & 31);
609 cr = measure_str(p, tmp, ri->attr);
612 if (cr.y > rd->line_height)
613 rd->line_height = cr.y;
615 if (cr.i2 > rd->ascent)
617 ri->width = ri->eol ? 0 : cr.x;
618 rd->width += ri->width;
620 if (ri->start <= offset && offset <= ri->start + ri->len) {
621 cr = measure_str(p, "M", ri->attr);
622 rd->curs_width = cr.x;
626 free(ri->split_list);
627 ri->split_list = NULL;
629 /* Set 'x' position honouring tab stops, and set length
630 * of "\t" characters. Also handle \n and \f.
632 x = left_margin - (shift_left > 0 ? shift_left : 0);
633 y = rd->space_above * curs_height / 10;
634 for (ri = rd->content; ri; ri = ri->next) {
636 struct render_item *ri2;
642 if (ri->tab != TAB_UNSET)
643 x = left_margin + calc_pos(ri->tab,
644 right_margin - left_margin,
649 x = 0; /* Don't include shift. probably not margin */
650 if (rd->line[ri->start])
651 y += rd->line_height;
654 if (ri->tab_align == TAB_LEFT) {
656 if (ri->len && rd->line[ri->start] == '\t') {
657 int col = x / ri->width;
658 int cols= 8 - (col % 8);
665 if (ri->tab_align == TAB_RIGHT)
669 ri2 && ri2->tab_align == TAB_LEFT && ri2->tab == TAB_UNSET;
672 while (ri2 && ri2->tab == TAB_UNSET)
674 margin = right_margin - left_margin;
676 margin = left_margin + calc_pos(ri2->tab,
677 right_margin - left_margin,
679 if (ri->tab_align == TAB_RIGHT)
680 x = x + margin - x - w;
682 x = x + (margin - x - w) / 2;
684 while (ri->next && ri->next->next && ri->next->tab_align == TAB_LEFT) {
692 /* Now we check to see if the line needs to be wrapped and
693 * if so, adjust some y values and reduce x. If we need to
694 * split an individual entry we create an array of split points.
696 xdiff = 0; ydiff = 0; y = 0;
698 wrap_margin = left_margin + rd->head_length;
699 for (ri = rd->content ; wrap && ri ; ri = ri->next) {
703 if (ri->wrap && (wraprl == NULL || ri->wrap != wraprl->wrap))
706 wrap_margin = ri->x + xdiff;
707 ri->wrap_x = wrap_margin;
715 if (ri->x + ri->width <= right_margin - rd->tail_length)
717 if ((ri->next == NULL || ri->next->eol) &&
718 ri->x + ri->width <= right_margin &&
720 /* Don't need any tail space for last item.
721 * This allows rtab to fully right-justify,
722 * but leaves no-where for the cursor. So
723 * only do it if rtab is present.
726 /* This doesn't fit here */
728 /* Move wraprl to next line and hide it unless it contains cursor */
730 struct render_item *wraprl2, *ri2;
732 /* Find last ritem in wrap region.*/
733 for (wraprl2 = wraprl ;
734 wraprl2->next && wraprl2->next->wrap == wraprl->wrap ;
735 wraprl2 = wraprl2->next)
737 wrap_margin = wraprl2->wrap_x;
739 xd = wraprl2->next->x - wrap_margin;
740 if (wraprl2->next->start > ri->start)
743 xd = wraprl2->x - wrap_margin;
744 if (wraprl2->start > ri->start)
748 offset >= wraprl->start &&
749 offset <= wraprl2->start + wraprl2->len) {
750 /* Cursor is here, so keep it visible.
751 * If we are still in the wrap region, pretend
752 * it didn't exist, else move first item
753 * after it to next line
755 if (ri->wrap == wraprl->wrap)
758 /* Hide the wrap region */
759 while (wraprl != wraprl2) {
760 wraprl->hidden = True;
761 wraprl = wraprl->next;
764 wraprl->hidden = True;
765 while (ri->next && ri->next->hidden)
768 for (ri2 = wraprl2->next ; ri2 && ri2 != ri->next; ri2 = ri2->next) {
769 ri2->y += rd->line_height;
771 if (ri2->wrap_margin)
772 wrap_margin = ri2->x;
773 ri2->wrap_x = wrap_margin;
776 ydiff += rd->line_height;
779 ri->x + ri->width <= right_margin - rd->tail_length)
783 /* Might need to split this ri into two or more pieces */
787 cr = do_measure(p, ri, splitpos, -1,
788 right_margin - rd->tail_length - x);
790 /* Remainder fits now */
792 if (cr.i == 0 && splitpos == 0) {
793 /* None of this fits here, move to next line */
794 xdiff -= ri->x - wrap_margin;
797 ydiff += rd->line_height;
798 ri->y += rd->line_height;
802 /* Nothing fits and we already split - give up */
804 /* re-measure the first part */
805 cr = do_measure(p, ri, splitpos,
807 right_margin - rd->tail_length - x);
808 ydiff += rd->line_height;
811 xdiff -= ri->x - wrap_margin;
815 if (!add_split(ri, splitpos))
820 (rd->space_above + rd->space_below) * curs_height / 10 +
821 ydiff + rd->line_height;
822 pane_resize(p, p->x, p->y, p->w, rd->measure_height);
823 attr_set_int(&p->attrs, "line-height", rd->line_height);
827 static void draw_line(struct pane *p safe, struct pane *focus safe, int offset)
829 struct rline_data *rd = p->data;
830 struct render_item *ri;
831 char *wrap_tail = rd->wrap_tail ?: "\\";
832 char *wrap_head = rd->wrap_head ?: "";
834 home_call(focus, "Draw:clear", p, 0, NULL, rd->background);
838 for (ri = rd->content ; ri; ri = ri->next) {
845 if (offset < 0 || offset >= ri->start + ri->len)
847 else if (offset < ri->start)
850 cpos = offset - ri->start;
852 do_draw(p, focus, ri, 0, cpos, ri->x, y);
854 while (split < ri->split_cnt ||
855 (ri->next && !ri->next->eol && ri->next->y > y)) {
857 /* don't show head/tail for wrap-regions */
858 if (*wrap_tail /*&& !ri->wrap*/)
859 draw_wrap(p, focus, wrap_tail,
860 p->w - rd->tail_length, y);
861 y += rd->line_height;
862 if (*wrap_head /*&& !ri->wrap*/)
863 draw_wrap(p, focus, wrap_head,
865 if (ri->split_list && split < ri->split_cnt) {
867 do_draw(p, focus, ri, split, cpos,
873 if (offset < ri->start + ri->len)
878 static int find_xy(struct pane *p safe, struct pane *focus safe,
879 short x, short y, const char **xyattr)
881 /* Find the location in ->line that is best match for x,y.
882 * If x,y is on the char at that location, when store attributes
883 * for the char in xyattr
884 * We always return a location, even if no xyattr.
885 * We use the last render_item that is not definitely after x,y
886 * We do not consider the eol render_item
888 struct call_return cr;
889 struct rline_data *rd = p->data;
890 struct render_item *r, *ri = NULL;
894 for (r = rd->content; r ; r = r->next) {
898 if (r->y <= y && r->x <= x) {
903 for (split = 0; split < r->split_cnt; split++) {
904 if (r->y + (split + 1) * rd->line_height <= y &&
905 r->wrap_x <= x && r->split_list) {
907 splitpos = r->split_list[split];
908 start = r->start + splitpos;
915 /* newline or similar. Can only be at start */
917 cr = do_measure(p, ri, splitpos, -1, x - ri->x);
918 if ((splitpos ? ri->wrap_x : ri->x ) + cr.x > x &&
919 ri->y + rd->line_height * (1 + splitpos) > y &&
921 /* This is a bit of a hack.
922 * We stick the x,y co-ords of the start
923 * of the current attr in front of the
924 * attrs so render-lines can provide a
925 * good location for a menu
928 struct render_item *ri2;
930 for (ri2 = rd->content; ri2 != ri; ri2 = ri2->next)
931 if (strcmp(ri2->attr, ri->attr) == 0)
933 snprintf(buf, sizeof(buf), "%dx%d,", ax, y);
934 *xyattr = strconcat(p, buf, ri->attr);
937 return cr.s - rd->line;
941 static struct xy find_curs(struct pane *p safe, int offset, const char **cursattr)
943 struct call_return cr;
944 struct xy xy = {0,0};
947 struct rline_data *rd = p->data;
948 struct render_item *r, *ri = NULL;
950 for (r = rd->content; r; r = r->next) {
953 if (offset < r->start)
958 /* This should be impossible as the eol goes past
959 * the largest offset.
963 if (offset < ri->start)
968 /* offset now from ri->start */
969 if (ri->len && rd->line[ri->start] == '\t' && offset)
970 offset = ri->tab_cols;
972 *cursattr = ri->attr;
974 for (split = 0; split < ri->split_cnt && ri->split_list; split ++) {
975 if (offset < ri->split_list[split])
977 st = ri->split_list[split];
980 cr.x = offset ? ri->width : 0;
982 cr = do_measure(p, ri, st, offset - st, -1);
985 xy.x = ri->wrap_x + cr.x;
988 xy.y = ri->y + split * rd->line_height;
989 if (ri->next == NULL && offset > ri->len) {
990 /* After the newline ? Go to next line */
992 xy.y += rd->line_height;
997 static void parse_map(const char *map safe, short *rowsp safe, short *colsp safe)
999 /* The map must be a sequence of rows, each of which is
1000 * a sequence of chars starting CAPS and continuing lower.
1001 * Each row must be the same length.
1005 short this_cols = 0;
1007 for (; *map && isalpha(*map); map += 1) {
1008 if (isupper(*map)) {
1010 if (this_cols != cols)
1011 /* Rows aren't all the same */
1018 } else if (rows == 0) {
1019 /* First row malformed */
1025 if (this_cols != cols)
1026 /* Last row is wrong length */
1032 static int render_image(struct pane *p safe, struct pane *focus safe,
1033 const char *line safe,
1035 int offset, int want_xypos, short x, short y)
1037 struct rline_data *rd = p->data;
1039 const char *orig_line = line;
1040 short width, height;
1041 short rows = -1, cols = -1;
1044 struct xy xyscale = pane_scale(focus);
1045 int scale = xyscale.x;
1046 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);
1102 pane_resize(p, (p->parent->w - width)/2, p->y, width, height);
1104 attr_set_int(&p->attrs, "line-height", p->h);
1106 /* Adjust size to be the scaled size - it must fit in
1109 if (size.x * p->h > size.y * p->w) {
1110 /* Image is wider than space */
1111 size.y = size.y * p->w / size.x;
1115 /* Image is taller than space */
1116 size.x = size.x * p->h / size.y;
1118 ioffset = (p->w - size.x) / 2;
1123 if (offset >= 0 && map_offset > 0 && rows > 0 &&
1124 offset >= map_offset && offset < map_offset + (rows*cols)) {
1125 /* Place cursor based on where 'offset' is in the map */
1126 short r = (offset - map_offset) / cols;
1127 short c = offset - map_offset - r * cols;
1128 p->cx = size.x / cols * c + ioffset;
1129 p->cy = size.y / rows * r;
1132 if (fname && dodraw)
1133 home_call(focus, "Draw:image", p, 5, NULL, fname,
1134 0, NULL, NULL, cols, rows);
1138 if (want_xypos && map_offset > 0 && rows > 0) {
1139 /* report where x,y is as a position in the map */
1140 short r = y * rows / size.y;
1141 short c = (x > ioffset ? x - ioffset : 0) * cols / size.x;
1142 if (c >= cols) c = cols - 1;
1143 /* +1 below because result must never be zero */
1144 return map_offset + r * cols + c + 1;
1149 DEF_CMD(renderline_draw)
1151 struct rline_data *rd = ci->home->data;
1156 offset = rd->prefix_bytes + ci->num;
1159 render_image(ci->home, ci->focus, rd->line, True,
1160 offset, False, 0, 0);
1162 draw_line(ci->home, ci->focus, offset);
1165 xy = find_curs(ci->home, rd->prefix_bytes + ci->num, NULL);
1166 ci->home->cx = xy.x;
1167 ci->home->cy = xy.y;
1172 DEF_CMD(renderline_refresh)
1174 struct rline_data *rd = ci->home->data;
1177 if (rd->curspos >= 0)
1178 offset = rd->prefix_bytes + rd->curspos;
1180 render_image(ci->home, ci->focus, rd->line, True,
1181 offset, False, 0, 0);
1183 measure_line(ci->home, ci->focus, offset);
1184 draw_line(ci->home, ci->focus, offset);
1189 DEF_CMD(renderline_measure)
1191 struct rline_data *rd = ci->home->data;
1195 return render_image(ci->home, ci->focus, rd->line,
1196 False, ci->num, False, 0, 0);
1198 ret = measure_line(ci->home, ci->focus,
1199 ci->num < 0 ? -1 : rd->prefix_bytes + ci->num);
1200 rd->prefix_pixels = 0;
1201 if (rd->prefix_bytes) {
1202 struct xy xy = find_curs(ci->home, rd->prefix_bytes, NULL);
1203 rd->prefix_pixels = xy.x;
1206 /* Find cursor and report x,y pos and attributes */
1207 const char *cursattr = NULL;
1209 xy = find_curs(ci->home, rd->prefix_bytes + ci->num, &cursattr);
1210 comm_call(ci->comm2, "cb", ci->focus, ret, NULL,
1212 ci->home->cx = xy.x;
1213 ci->home->cy = xy.y;
1218 DEF_CMD(renderline_findxy)
1220 struct rline_data *rd = ci->home->data;
1221 const char *xyattr = NULL;
1225 return render_image(ci->home, ci->focus, rd->line,
1229 measure_line(ci->home, ci->focus,
1230 ci->num < 0 ? -1 : rd->prefix_bytes + ci->num);
1231 pos = find_xy(ci->home, ci->focus, ci->x, ci->y, &xyattr);
1232 if (pos >= rd->prefix_bytes)
1233 pos -= rd->prefix_bytes;
1238 comm_call(ci->comm2, "cb", ci->focus, pos, NULL, xyattr);
1242 DEF_CMD(renderline_get)
1244 struct rline_data *rd = ci->home->data;
1246 const char *val = buf;
1250 if (strcmp(ci->str, "prefix_len") == 0)
1251 snprintf(buf, sizeof(buf), "%d", rd->prefix_pixels);
1252 else if (strcmp(ci->str, "curs_width") == 0)
1253 snprintf(buf, sizeof(buf), "%d", rd->curs_width);
1254 else if (strcmp(ci->str, "width") == 0)
1255 snprintf(buf, sizeof(buf), "%d", rd->width);
1259 comm_call(ci->comm2, "attr", ci->focus, 0, NULL, val);
1263 static char *cvt(char *str safe)
1266 * << to < ack (ack is a no-op)
1267 * < stuff > to soh stuff stx
1268 * </> to ack ack etx
1271 for (c = str; *c; c += 1) {
1272 if (c[0] == soh || c[0] == ack)
1274 if (c[0] == '<' && c[1] == '<') {
1282 while (*c && *c != '>')
1292 while (*c && *c != '>') {
1294 (c[1] == '\\' || c[1] == '>'))
1307 DEF_CMD(renderline_set)
1309 struct rline_data *rd = ci->home->data;
1310 const char *old = rd->line;
1311 char *prefix = pane_attr_get(ci->focus, "prefix");
1312 bool word_wrap = pane_attr_get_int(ci->focus, "word-wrap", 0) != 0;
1313 bool bg_changed = False;
1318 prefix = strconcat(ci->home, ACK SOH "bold" STX,
1319 prefix, // No mark in prefix!
1322 rd->line = strconcat(NULL, prefix, ci->str);
1324 rd->line = strdup(ci->str);
1325 rd->prefix_bytes = strlen(prefix?:"");
1326 cvt(rd->line + rd->prefix_bytes);
1328 if (ci->str2 && !rd->background) {
1329 rd->background = strdup(ci->str2);
1331 } else if (!ci->str2 && rd->background) {
1332 free(rd->background);
1333 rd->background = NULL;
1335 } else if (ci->str2 && rd->background &&
1336 strcmp(ci->str2, rd->background) != 0) {
1337 free(rd->background);
1338 rd->background = strdup(ci->str2);
1342 rd->curspos = ci->num;
1343 if (strcmp(rd->line, old) != 0 || bg_changed ||
1344 (old && word_wrap != rd->word_wrap)) {
1345 pane_damaged(ci->home, DAMAGED_REFRESH);
1346 pane_damaged(ci->home->parent, DAMAGED_REFRESH);
1347 rd->word_wrap = word_wrap;
1351 ci->home->damaged &= ~DAMAGED_VIEW;
1355 DEF_CMD_CLOSED(renderline_close)
1357 struct rline_data *rd = ci->home->data;
1358 struct render_item *ri = rd->content;
1360 free((void*)rd->line);
1362 struct render_item *r = ri;
1364 free(r->split_list);
1365 unalloc_str_safe(r->attr, pane);
1368 aupdate(&rd->wrap_head, NULL);
1369 aupdate(&rd->wrap_tail, NULL);
1370 aupdate(&rd->wrap_attr, NULL);
1371 aupdate(&rd->background, NULL);
1375 static struct map *rl_map;
1376 DEF_LOOKUP_CMD(renderline_handle, rl_map);
1378 DEF_CMD(renderline_attach)
1381 struct rline_data *rd;
1384 rl_map = key_alloc();
1385 key_add(rl_map, "render-line:draw", &renderline_draw);
1386 key_add(rl_map, "Refresh", &renderline_refresh);
1387 key_add(rl_map, "render-line:measure", &renderline_measure);
1388 key_add(rl_map, "render-line:findxy", &renderline_findxy);
1389 key_add(rl_map, "get-attr", &renderline_get);
1390 key_add(rl_map, "render-line:set", &renderline_set);
1391 key_add(rl_map, "Close", &renderline_close);
1394 p = pane_register(ci->focus, ci->num, &renderline_handle.c);
1398 rd->line = strdup(ETX); // Imposible string
1400 return comm_call(ci->comm2, "cb", p);
1403 void edlib_init(struct pane *ed safe)
1405 call_comm("global-set-command", ed, &renderline_attach, 0, NULL,
1406 "attach-renderline");