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
6 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
7 * May be distributed under terms of GPLv2 - see file:COPYING
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.
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.
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
28 #define _GNU_SOURCE /* for asprintf */
34 #define PANE_DATA_TYPE struct rline_data
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.
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
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
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.
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
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
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 */
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
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.
107 unsigned int tab_cols:4; /* For \t char */
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
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"
119 static const short TAB_UNSET = (1<<(14-2));
122 unsigned short prefix_bytes, prefix_pixels;
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;
137 struct render_item *content;
139 #include "core-pane.h"
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
153 #define foreach_attr(_attr, _val, _c, _end) \
154 for (_attr = _c, _val = find_val(&_c, _end); \
156 _attr = _c, _val = find_val(&_c, _end))
157 static const char *find_val(const char **cp safe, const char *end safe)
164 while (c < end && *c != ':' && *c != ',')
171 while (*c == ',' && c < end)
182 while (c < end && *c != ',')
184 while (c < end && *c == ',')
192 static bool amatch(const char *a safe, const char *m safe)
194 while (*a && *a == *m) {
199 /* Didn't match all of m */
201 if (*a != ':' && *a != ',' && *a >= ' ')
202 /* Didn't match all of a */
207 static bool aprefix(const char *a safe, const char *m safe)
209 while (*a && *a == *m) {
214 /* Didn't match all of m */
219 static long anum(const char *v safe)
222 long ret = strtol(v, &end, 10);
223 if (end == v || !end ||
224 (*end != ',' && *end >= ' '))
225 /* Not a valid number - use zero */
230 static void aupdate(char **cp safe, const char *v)
232 /* duplicate value at v and store in *cp, freeing what is there
237 while (end && *end != ',' && *end >= ' ')
242 *cp = strndup(v, end-v);
247 static void aappend(struct buf *b safe, char const *a safe)
250 while (*end >= ' ' && *end != ',')
252 buf_concat_len(b, a, end-a);
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,
259 short tab, enum tab_align align,
261 short wrap, short hide)
263 struct render_item *ri;
264 struct render_item **riend = *rlp;
267 ri->attr = strdup(attr);
268 ri->start = start - rd->line;
269 ri->len = end - start;
270 ri->tab_align = align;
274 ri->wrap_margin = wrap_margin;
275 ri->eol = !!strchr("\n\f\0", *start);
281 static inline bool is_ctrl(unsigned int c)
284 (c >= 128 && c < 128 + ' ');
287 static void parse_line(struct rline_data *rd safe)
289 /* Parse out markup in line into a renderlist with
290 * global content directly in rd.
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;
301 rd->left_margin = rd->right_margin = 0;
302 rd->space_above = rd->space_below = 0;
304 aupdate(&rd->wrap_head, NULL);
305 aupdate(&rd->wrap_tail, NULL);
306 aupdate(&rd->wrap_attr, NULL);
312 rd->image = strstarts(line, SOH "image:");
321 struct render_item *r = ri;
324 unalloc_str_safe(r->attr, pane);
329 const char *st = line;
333 (!rd->word_wrap || c != ' '))
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);
350 /* Move 'line' over the attrs */
351 while (*line && line[-1] != stx)
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
361 buf_append(&attr, ',');
363 foreach_attr(a, v, st, line) {
364 if (amatch(a, "centre") || amatch(a, "center") ||
369 } else if (amatch(a, "tab") && v) {
372 } else if (amatch(a, "rtab")) {
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")) {
386 wrap_depth = old_len;
387 } else if (amatch(a, "wrap-margin")) {
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")) {
397 hide_depth = old_len;
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 */
410 while (attr.len && attr.b[attr.len-1] != ',')
413 /* strip one more ',' */
416 if (attr.len <= wrap_depth)
418 if (attr.len <= hide_depth)
422 /* Just ignore this */
425 /* This and following spaces are wrappable */
430 add_render(rd, &riend, st - 1, line, buf_final(&attr),
431 tab, align, wrap_margin, wrap, hide);
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.
447 add_render(rd, &riend, st, line, buf_final(&attr),
448 tab, align, wrap_margin, wrap, hide);
458 if (buf_final(&wrapattr)[0])
459 rd->wrap_attr = buf_final(&wrapattr);
462 rd->wrap_attr = strdup(",fg:blue,underline,");
466 static inline struct call_return do_measure(struct pane *p safe,
467 char *str safe, int len,
468 int offset, int scale,
471 struct call_return cr;
478 cr = call_ret(all, "Draw:text-size", p,
486 static inline void do_draw(struct pane *p safe,
487 struct pane *focus safe,
488 struct render_item *ri safe, int split,
492 struct rline_data *rd = &p->data;
498 str = rd->line + ri->start;
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
508 home_call(focus, "Draw:text", p, offset, NULL, "",
509 rd->scale, NULL, ri->attr, x, y);
512 if (str[0] == '\t') {
517 if (ri->split_list && split < ri->split_cnt)
518 len = ri->split_list[split];
521 /* Tab need a list of spaces */
524 if (split > 0 && split <= ri->split_cnt && ri->split_list) {
525 str += ri->split_list[split-1];
526 offset -= ri->split_list[split];
532 home_call(focus, "Draw:text", p, offset, NULL, str,
533 rd->scale, NULL, ri->attr, x, y);
537 static inline void draw_wrap(struct pane *p safe,
538 struct pane *focus safe,
542 struct rline_data *rd = &p->data;
544 home_call(focus, "Draw:text", p,
546 rd->scale, NULL, rd->wrap_attr,
550 static void add_split(struct render_item *ri safe, int split)
552 int i = ri->split_cnt;
554 ri->split_list = realloc(ri->split_list,
555 sizeof(ri->split_list[0]) * ri->split_cnt);
556 ri->split_list[i] = split;
559 static int calc_tab(int num, int margin, int scale)
562 return num * scale / 1000;
565 return margin + num * scale / 1000;
568 static bool measure_line(struct pane *p safe, struct pane *focus safe, int offset)
570 /* First measure each render_item entry setting
571 * height, ascent, width.
572 * Then use that with tab information to set 'x' position for
574 * Finally identify line-break locations if needed and set 'y'
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;
582 int right_margin = p->w - (rd->right_margin * rd->scale / 1000);
584 struct call_return cr;
590 cr = do_measure(p, "M", -1, -1, rd->scale,"");
591 rd->curs_width = cr.x;
592 rd->line_height = cr.y;
594 if (rd->min_height * rd->scale / 1000 > rd->line_height)
595 rd->line_height = rd->min_height * rd->scale / 1000;
598 cr = do_measure(p, rd->wrap_head, -1, -1, rd->scale,
600 rd->head_length = cr.x;
602 cr = do_measure(p, rd->wrap_tail ?: "\\", -1, -1, rd->scale,
604 rd->tail_length = cr.x;
606 for (ri = rd->content; ri; ri = ri->next) {
610 if (!is_ctrl(rd->line[ri->start])) {
611 txt = rd->line + ri->start;
613 } else if (ri->eol) {
614 /* Ensure attributes of newline add to line height.
615 * The width will be ignored. */
617 if (rd->line[ri->start] == '\f')
619 } else if (rd->line[ri->start] == '\t') {
623 tmp[1] = '@' + (rd->line[ri->start] & 31);
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;
629 if (cr.i2 > rd->ascent)
631 ri->width = ri->eol ? 0 : cr.x;
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;
640 free(ri->split_list);
641 ri->split_list = NULL;
643 /* Set 'x' position honouring tab stops, and set length
644 * of "\t" characters. Also handle \n and \f.
646 x = (rd->left_margin * rd->scale / 1000) - (shift_left > 0 ? shift_left : 0);
647 y = rd->space_above * rd->scale / 1000;
649 for (ri = rd->content; ri; ri = ri->next) {
651 struct render_item *ri2;
653 if (ri->tab != TAB_UNSET)
654 x = (rd->left_margin * rd->scale / 1000) + calc_tab(ri->tab, right_margin, rd->scale);
660 x = 0; /* Don't include shift. probably not margin */
661 if (rd->line[ri->start])
662 y += rd->line_height;
665 if (ri->tab_align == TAB_LEFT) {
667 if (rd->line[ri->start] == '\t') {
668 int col = x / ri->width;
669 int cols= 8 - (col % 8);
678 ri2 && ri2->tab_align == TAB_LEFT && ri2->tab == TAB_UNSET;
681 while (ri2 && ri2->tab == TAB_UNSET)
683 margin = right_margin;
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;
690 x = x + (margin - x - w) / 2;
692 while (ri->next && ri->next->next && ri->next->tab_align == TAB_LEFT) {
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.
704 xdiff = 0; ydiff = 0; y = 0;
706 wrap_margin = rd->head_length;
707 for (ri = rd->content ; wrap && ri ; ri = ri->next) {
712 if (ri->wrap && (wraprl == NULL || ri->wrap != wraprl->wrap))
716 ri->wrap_x = wrap_margin;
724 if (ri->x + ri->width <= right_margin - rd->tail_length)
726 /* This doesn't fit here */
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;
732 /* Find last ritem in wrap region.*/
733 for (wraprl2 = wraprl ;
734 wraprl2->next && wraprl2->next->wrap == wraprl->wrap ;
735 wraprl2 = wraprl2->next)
738 xd = wraprl2->next->x - wrap_margin;
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
747 if (ri->wrap == wraprl->wrap)
750 /* Hide the wrap region */
751 while (wraprl != wraprl2) {
752 wraprl->hidden = True;
753 wraprl = wraprl->next;
756 wraprl->hidden = True;
757 while (ri->next && ri->next->hidden)
760 for (ri2 = wraprl2->next ; ri2 && ri2 != ri->next; ri2 = ri2->next) {
761 ri2->y += rd->line_height;
765 ydiff += rd->line_height;
770 if (ri->x >= right_margin - rd->tail_length) {
771 /* This ri moves completely to next line */
772 xdiff -= ri->x - wrap_margin;
774 ydiff += rd->line_height;
775 ri->y += rd->line_height;
779 /* Need to split this ri into two or more pieces */
782 str = rd->line + ri->start;
789 cr = do_measure(p, str + splitpos,
791 right_margin - rd->tail_length - x,
792 rd->scale, ri->attr);
793 if (cr.i >= len - splitpos)
794 /* Remainder fits now */
796 /* re-measure the first part */
797 cr = do_measure(p, str + splitpos,
799 right_margin - rd->tail_length - x,
800 rd->scale, ri->attr);
802 ydiff += rd->line_height;
803 xdiff -= cr.x; // fixme where does wrap_margin fit in there
808 add_split(ri, splitpos);
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 +
816 attr_set_int(&p->attrs, "line-height", rd->line_height);
820 static void draw_line(struct pane *p safe, struct pane *focus safe, int offset)
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 ?: "";
827 home_call(focus, "Draw:clear", p);
831 for (ri = rd->content ; ri; ri = ri->next) {
838 if (offset < 0 || offset >= ri->start + ri->len)
840 else if (offset < ri->start)
843 cpos = offset - ri->start;
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 */
850 draw_wrap(p, focus, wrap_tail,
851 p->w - rd->tail_length, y);
853 draw_wrap(p, focus, wrap_head,
854 0, y + rd->line_height);
857 while (split < ri->split_cnt ||
858 (ri->next && ri->next->next && ri->next->y > y)) {
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,
868 if (ri->split_list && split < ri->split_cnt) {
870 do_draw(p, focus, ri, split, cpos,
871 rd->left_margin + rd->head_length,
875 if (offset < ri->start + ri->len)
880 static int find_xy(struct pane *p safe, struct pane *focus safe,
881 short x, short y, const char **xyattr)
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
890 struct call_return cr;
891 struct rline_data *rd = &p->data;
892 struct render_item *r, *ri = NULL;
896 for (r = rd->content; r ; r = r->next) {
898 if (r->y <= y && r->x <= x)
900 for (split = 0; split < r->split_cnt; split++) {
901 if (r->y + (split + 1) * rd->line_height &&
909 /* newline or similar. Can only be at start */
911 if (ri->x + ri->width > x &&
912 ri->y + ri->height > y &&
915 if (rd->line[ri->start] == '\t')
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;
923 static struct xy find_curs(struct pane *p safe, int offset, const char **cursattr)
925 struct call_return cr;
926 struct xy xy = {0,0};
929 struct rline_data *rd = &p->data;
930 struct render_item *r, *ri = NULL;
932 for (r = rd->content; r; r = r->next) {
933 if (offset < r->start)
938 /* This should be impossible as the eol goes past
939 * the largest offset.
943 if (offset < ri->start)
948 /* offset now from ri->start */
949 if (rd->line[ri->start] == '\t' && offset)
950 offset = ri->tab_cols;
952 *cursattr = ri->attr;
954 for (split = 0; split < ri->split_cnt && ri->split_list; split ++) {
955 if (offset < ri->split_list[split])
957 st = ri->split_list[split];
960 cr.x = offset ? ri->width : 0;
962 char *str = rd->line + ri->start + st;
964 if (rd->line[ri->start] == '\t') {
967 offset = ri->tab_cols;
969 cr = do_measure(p, str, offset - st,
970 -1, rd->scale, ri->attr);
973 xy.x = cr.x; /* FIXME margin?? */
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 */
980 xy.y += rd->line_height;
985 static void parse_map(const char *map safe, short *rowsp safe, short *colsp safe)
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.
995 for (; *map && isalpha(*map); map += 1) {
998 if (this_cols != cols)
999 /* Rows aren't all the same */
1006 } else if (rows == 0) {
1007 /* First row malformed */
1013 if (this_cols != cols)
1014 /* Last row is wrong length */
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)
1026 const char *orig_line = line;
1027 short width, height;
1028 short rows = -1, cols = -1;
1031 char *ssize = attr_find(p->attrs, "cached-size");
1032 struct xy size= {-1, -1};
1034 width = p->parent->w/2;
1035 height = p->parent->h/2;
1037 while (*line == soh)
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);
1045 sscanf(ssize, "%hdx%hd", &size.x, &size.y) != 2) {
1046 struct call_return cr =
1047 home_call_ret(all, focus,
1050 if (cr.x > 0 && cr.y > 0) {
1053 asprintf(&ssize, "%hdx%hd",
1055 attr_set_str(&p->attrs,
1056 "cached-size", ssize);
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)
1069 if (size.y < p->parent->h)
1071 } else if ((offset >= 0 || want_xypos) &&
1072 strstarts(line, "map:")) {
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.
1084 map_offset = line+4 - orig_line;
1085 parse_map(line+4, &rows, &cols);
1088 line += strspn(line, ",");
1090 pane_resize(p, (p->parent->w - width)/2, p->y, width, height);
1092 attr_set_int(&p->attrs, "line-height", p->h);
1094 /* Adjust size to be the scaled size - it must fit in
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;
1103 /* Image is taller than space */
1104 size.x = size.x * p->h / size.y;
1106 ioffset = (p->w - size.x) / 2;
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;
1120 if (fname && dodraw)
1121 home_call(focus, "Draw:image", p, 5, NULL, fname,
1122 0, NULL, NULL, cols, rows);
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;
1137 DEF_CMD(renderline_draw)
1139 struct rline_data *rd = &ci->home->data;
1144 offset = rd->prefix_bytes + ci->num;
1147 render_image(ci->home, ci->focus, rd->line, True,
1148 rd->scale, offset, False, 0, 0);
1150 draw_line(ci->home, ci->focus, offset);
1153 xy = find_curs(ci->home, rd->prefix_bytes + ci->num, NULL);
1154 ci->home->cx = xy.x;
1155 ci->home->cy = xy.y;
1160 DEF_CMD(renderline_refresh)
1162 struct rline_data *rd = &ci->home->data;
1165 if (rd->curspos >= 0)
1166 offset = rd->prefix_bytes + rd->curspos;
1168 render_image(ci->home, ci->focus, rd->line, True,
1169 rd->scale, offset, False, 0, 0);
1171 measure_line(ci->home, ci->focus, offset);
1172 draw_line(ci->home, ci->focus, offset);
1177 DEF_CMD(renderline_measure)
1179 struct rline_data *rd = &ci->home->data;
1182 return render_image(ci->home, ci->focus, rd->line,
1183 False, rd->scale, ci->num, False, 0, 0);
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;
1193 /* Find cursor and report x,y pos and attributes */
1194 const char *cursattr = NULL;
1196 xy = find_curs(ci->home, rd->prefix_bytes + ci->num, &cursattr);
1197 comm_call(ci->comm2, "cb", ci->focus, end_of_page, NULL,
1199 ci->home->cx = xy.x;
1200 ci->home->cy = xy.y;
1202 return end_of_page ? 2 : 1;
1205 DEF_CMD(renderline_findxy)
1207 struct rline_data *rd = &ci->home->data;
1208 const char *xyattr = NULL;
1212 return render_image(ci->home, ci->focus, rd->line,
1213 False, rd->scale, -1, True,
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;
1225 comm_call(ci->comm2, "cb", ci->focus, pos, NULL, xyattr);
1229 DEF_CMD(renderline_get)
1231 struct rline_data *rd = &ci->home->data;
1233 const char *val = buf;
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);
1246 comm_call(ci->comm2, "attr", ci->focus, 0, NULL, val);
1250 static char *cvt(char *str safe)
1253 * << to < ack (ack is a no-op)
1254 * < stuff > to soh stuff stx
1255 * </> to ack ack etx
1258 for (c = str; *c; c += 1) {
1259 if (c[0] == soh || c[0] == ack)
1261 if (c[0] == '<' && c[1] == '<') {
1276 while (c[0] && c[1] != '>')
1283 DEF_CMD(renderline_set)
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;
1294 prefix = cvt(strconcat(ci->home, "<bold>", prefix, "</>"));
1297 rd->line = strconcat(NULL, prefix, ci->str);
1299 rd->line = strdup(ci->str);
1300 rd->prefix_bytes = strlen(prefix?:"");
1301 cvt(rd->line + rd->prefix_bytes);
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;
1314 ci->home->damaged &= ~DAMAGED_VIEW;
1318 DEF_CMD(renderline_close)
1320 struct rline_data *rd = &ci->home->data;
1322 free((void*)rd->line);
1326 static struct map *rl_map;
1327 DEF_LOOKUP_CMD(renderline_handle, rl_map);
1329 DEF_CMD(renderline_attach)
1332 struct rline_data *rd;
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);
1346 p = pane_register(ci->focus, ci->num, &renderline_handle.c);
1350 rd->line = strdup("");
1352 return comm_call(ci->comm2, "cb", p);
1355 void edlib_init(struct pane *ed safe)
1357 call_comm("global-set-command", ed, &renderline_attach, 0, NULL,
1358 "attach-renderline");