/* render.c: Rendering and sizing routines for libRUIN * Copyright (C) 2007 Julian Graham * * libRUIN is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * libRUIN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with libRUIN; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include "css.h" #include "layout.h" #include "render.h" #include "util.h" #include "window.h" void _ruin_render_set_colors(enum ruin_layout_foreground_color fg, enum ruin_layout_background_color bg) { int i; short ncurses_fg = COLOR_BLACK, ncurses_bg = COLOR_BLACK; int fg_attrs = A_NORMAL; if (!has_colors()) return; switch(fg) { case RUIN_LAYOUT_FG_COLOR_BR_BLACK: fg_attrs |= A_BOLD; case RUIN_LAYOUT_FG_COLOR_BLACK: ncurses_fg = COLOR_BLACK; break; case RUIN_LAYOUT_FG_COLOR_BR_RED: fg_attrs |= A_BOLD; case RUIN_LAYOUT_FG_COLOR_RED: ncurses_fg = COLOR_RED; break; case RUIN_LAYOUT_FG_COLOR_BR_GREEN: fg_attrs |= A_BOLD; case RUIN_LAYOUT_FG_COLOR_GREEN: ncurses_fg = COLOR_GREEN; break; case RUIN_LAYOUT_FG_COLOR_BR_YELLOW: fg_attrs |= A_BOLD; case RUIN_LAYOUT_FG_COLOR_YELLOW: ncurses_fg = COLOR_YELLOW; break; case RUIN_LAYOUT_FG_COLOR_BR_BLUE: fg_attrs |= A_BOLD; case RUIN_LAYOUT_FG_COLOR_BLUE: ncurses_fg = COLOR_BLUE; break; case RUIN_LAYOUT_FG_COLOR_BR_MAGENTA: fg_attrs |= A_BOLD; case RUIN_LAYOUT_FG_COLOR_MAGENTA: ncurses_fg = COLOR_MAGENTA; break; case RUIN_LAYOUT_FG_COLOR_BR_CYAN: fg_attrs |= A_BOLD; case RUIN_LAYOUT_FG_COLOR_CYAN: ncurses_fg = COLOR_CYAN; break; case RUIN_LAYOUT_FG_COLOR_BR_WHITE: fg_attrs |= A_BOLD; case RUIN_LAYOUT_FG_COLOR_WHITE: ncurses_fg = COLOR_WHITE; break; } switch(bg) { case RUIN_LAYOUT_BG_COLOR_BLACK: ncurses_bg = COLOR_BLACK; break; case RUIN_LAYOUT_BG_COLOR_RED: ncurses_bg = COLOR_RED; break; case RUIN_LAYOUT_BG_COLOR_GREEN: ncurses_bg = COLOR_GREEN; break; case RUIN_LAYOUT_BG_COLOR_YELLOW: ncurses_bg = COLOR_YELLOW; break; case RUIN_LAYOUT_BG_COLOR_BLUE: ncurses_bg = COLOR_BLUE; break; case RUIN_LAYOUT_BG_COLOR_MAGENTA: ncurses_bg = COLOR_MAGENTA; break; case RUIN_LAYOUT_BG_COLOR_CYAN: ncurses_bg = COLOR_CYAN; break; case RUIN_LAYOUT_BG_COLOR_WHITE: ncurses_bg = COLOR_WHITE; break; } attrset(A_NORMAL); if ((ncurses_fg == COLOR_WHITE) && (ncurses_bg == COLOR_BLACK)) { attron(COLOR_PAIR(1)); return; } attron(fg_attrs); for (i = 2; i < COLOR_PAIRS; i++) { short _fg, _bg; (void) pair_content(i, &_fg, &_bg); if ((_fg == COLOR_BLACK) && (_bg == COLOR_BLACK)) { init_pair(i, ncurses_fg, ncurses_bg); attron(COLOR_PAIR(i)); break; } else if ((_fg == ncurses_fg) && (_bg == ncurses_bg)) { attron(COLOR_PAIR(i)); break; } } } void _ruin_render_set_attrs(ruin_element_t *tree, ruin_util_list *inh) { /* If text-decoration is "none" on this element, look for inline ancestors that have a non-none text-decoration and use that... */ char *td = ruin_css_lookup(tree, "text-decoration", inh); if (strcmp(td, "none") == 0) { char *display = ruin_css_lookup(tree, "display", inh); if (strcmp(display, "inline") != 0) { display = ruin_css_lookup(tree->parent, "display", inh); if (strcmp(display, "inline") != 0) return; else _ruin_render_set_attrs(tree->parent, inh); } else _ruin_render_set_attrs(tree->parent, inh); } else { if ((strstr(td, "underline") != NULL) || (strstr(td, "overline") != NULL) || (strstr(td, "line-through") != NULL)) attron(A_UNDERLINE); if (strstr(td, "blink") != NULL) attron(A_BLINK); } } /* Finds the most immediately previous sibling that is an anonymous inline element so that we can inspect it to determine white-space collapsing information; returns NULL if it crosses a non-inline element during the search. */ static ruin_element_t *_get_previous_anon_inline_sibling (ruin_element_t *tree, ruin_util_list *inh) { char *display = ruin_css_lookup(tree, "display", inh); if (strcmp(display, "inline") != 0) return NULL; if (tree->prev_sibling != NULL) { display = ruin_css_lookup(tree->prev_sibling, "display", inh); if (strcmp(display, "inline") != 0) { ruin_element_t *elt_ptr = tree->prev_sibling; while((elt_ptr != NULL) && (scm_string_p(elt_ptr->element) != SCM_BOOL_T)) { /* This is not particularly bright -- what if there are multiple children attached to the sibling we find? */ elt_ptr = elt_ptr->first_child; } return elt_ptr; } } return _get_previous_anon_inline_sibling(tree->parent, inh); } /* Split the content into whitespace-delimited words; allow whitespace at the start and end words to make it easy to do whitespace-collapsing calculations. */ int ruin_render_get_words(char *content, char ***result, int **result_lens) { int i = 0, len = strlen(content), num_words = 0, last_was_whitespace = FALSE, cword = 0; char **words = NULL; int *word_lens = NULL; for (i = 0; i < len; i++) { if (isspace(content[i])) { last_was_whitespace = TRUE; continue; } if ((last_was_whitespace) || (num_words == 0)) { num_words++; last_was_whitespace = FALSE; } } if (num_words == 0) return 0; words = malloc(sizeof(char *) * num_words); word_lens = calloc(num_words, sizeof(int)); last_was_whitespace = -1; for (i = 0; i < len; i++) { if (isspace(content[i])) { if (last_was_whitespace == FALSE) { if (cword == num_words - 1) word_lens[cword]++; else cword++; } last_was_whitespace = TRUE; } else { if (last_was_whitespace == TRUE) { words[cword] = cword == 0 ? &content[i - 1] : &content[i]; word_lens[cword] = cword == 0 ? 2 : 1; last_was_whitespace = FALSE; } else if (last_was_whitespace == -1) { last_was_whitespace = FALSE; words[cword] = content; word_lens[cword] = 1; } else word_lens[cword]++; } } *result = words; *result_lens = word_lens; return num_words; } int _ruin_render_add_word(char *buf, int buf_len, char *word, int wordlen, int ls, int ws) { int i; int wrote = 0; for (i = 0; i < wordlen; i++) { int j = 0, offset = i * (1 + ls); buf[offset] = word[i]; wrote++; if (wrote >= buf_len) return wrote; for (j = 0; j < ls; i++) { buf[offset + j + 1] = ' '; wrote++; if (wrote >= buf_len) return wrote; } } for (i = 0; i < ws; i++) { buf[i + (wordlen * (1 + ls))] = ' '; wrote++; if (wrote >= buf_len) return wrote; } return wrote; } void ruin_render_draw_inline(ruin_element_t *tree, ruin_util_list *inh) { int i, max_line_length = tree->width.used, max_lines = tree->height.used; int mask = RUIN_LAYOUT_DISPLAY_BLOCK | RUIN_LAYOUT_DISPLAY_LIST_ITEM | RUIN_LAYOUT_DISPLAY_TABLE | RUIN_LAYOUT_DISPLAY_INLINE_BLOCK | RUIN_LAYOUT_DISPLAY_TABLE_CAPTION | RUIN_LAYOUT_DISPLAY_TABLE_CELL; ruin_element_t *container = NULL; enum ruin_layout_foreground_color temp_fg; enum ruin_layout_background_color temp_bg; char **words = NULL; int *word_lens = NULL; int num_words = 0; char **lines = NULL; int num_lines; int current_line_length = tree->first_line_start - tree->left; int current_line = 0; int ls = tree->letter_spacing.used, ws = tree->word_spacing.used; int container_width = ruin_layout_find_containing_block(inh, mask)->width.used; int prev_sib_ended_in_ws = FALSE; if ((max_line_length <= 0) || (max_lines <= 0)) return; if (tree->content != NULL && (num_words = ruin_render_get_words (tree->content, &words, &word_lens)) > 0) { char *wstype = ruin_css_lookup(tree, "white-space", inh); num_lines = 1; lines = malloc(sizeof(char *) * tree->height.used); if (strcmp(wstype, "normal") == 0) { char *buf = calloc(container_width + 1, sizeof(char)); int buf_len = container_width; int num_words = ruin_render_get_words(tree->content, &words, &word_lens); lines[current_line] = buf; if (isspace(words[0][0]) && (!tree->prev_was_inline || (tree->prev_was_inline && prev_sib_ended_in_ws))) { word_lens[0]--; words[0]++; } if ((tree->first_child == NULL) && (tree->next_sibling == NULL) && (isspace(words[num_words - 1][word_lens[num_words - 1] - 1]))) { /* words[num_words - 1][word_lens[num_words - 1] - 1] = 0; */ word_lens[num_words - 1]--; } for (i = 0; i < num_words; i++) { int wlen = word_lens[i]; int total_wlen = wlen * (ls + 1) - (ls > 0 ? 1 : 0); int addition = 0; int ws_p = i == num_words - 1 ? 0 : ws; if (current_line_length + total_wlen > container_width) { num_lines++; /* The current word won't fit on the current line... Will it fit on any line? */ if (total_wlen > container_width) { /* We just cram it in... */ int remainder = total_wlen; while (remainder > 0) { addition = _ruin_render_add_word (buf, buf_len, words[i] + total_wlen - remainder, buf_len, ls, ws_p); buf += addition; buf_len -= addition; remainder -= addition; if (buf_len == 0) { if (current_line < tree->height.used - 1) { buf = calloc(container_width + 1, sizeof(char)); lines[++current_line] = buf; buf_len = container_width; current_line_length = 0; } else break; } } } else { /* Start a new line and put the word there. */ buf = calloc(container_width + 1, sizeof(char)); lines[++current_line] = buf; buf_len = container_width; addition = _ruin_render_add_word (buf, buf_len, words[i], wlen, ls, ws_p); buf += addition; buf_len -= addition; current_line_length = addition; } } else { addition = _ruin_render_add_word(buf, buf_len, words[i], wlen, ls, ws_p); buf += addition; buf_len -= addition; current_line_length += addition; } } } else if (strcmp(wstype, "pre") == 0) { char *br = index(tree->content, '\n'); char *oldbr = tree->content; if (br == NULL) { lines[num_lines - 1] = strdup(tree->content); } else { do { int len = br - oldbr; char *buf = malloc(sizeof(char) * (len + 1)); strncpy(buf, br, len); lines[num_lines - 1] = buf; num_lines++; oldbr = br; } while ((br = index(br + sizeof(char), '\n')) != NULL); } } else if (strcmp(wstype, "nowrap") == 0) { } else if (strcmp(wstype, "pre-wrap") == 0) { } else if (strcmp(wstype, "pre-line") == 0) { } /* Here's where we're going to need to handle croppage! */ if (num_lines > max_lines) { /* When we're looking for where to insert the ellipsis, we have to find the first word longer than 3 chars or the first word after a word shorter than 3 chars that we can append 3 chars onto. */ /* char *c = ruin_css_lookup(tree, "overflow"); */ num_lines = tree->height.used; lines = realloc(lines, num_lines * sizeof(char *)); } /* This is the only time that pseudo-element matches are going to work, since we've only just obtained the necessary rendering information. TODO: Make this work for nested inline elements that share the first line. */ container = ruin_layout_find_containing_block(inh, mask); tree->parent_window->current_pseudo_elements = ruin_util_list_push_front(tree->parent_window->current_pseudo_elements, ruin_util_list_new(strdup("first-line"))); tree->parent_window->current_pseudo_elements = ruin_util_list_push_front(tree->parent_window->current_pseudo_elements, ruin_util_list_new(strdup("first-letter"))); ruin_css_clear_style_cache(container); temp_fg = ruin_css_match_foreground_color (ruin_css_lookup(tree, "color", inh)); temp_bg = ruin_css_match_background_color (ruin_css_lookup(tree, "background-color", inh), inh); _ruin_render_set_colors(temp_fg, temp_bg); _ruin_render_set_attrs(tree, inh); /* Draw the first letter */ if (tree->top > tree->parent_window->top->height.used) return; move(tree->top, tree->first_line_start + tree->parent->text_indent.used); if (strcmp(ruin_css_lookup(tree, "font-variant", inh), "small-caps") == 0) addch(toupper(lines[0][0])); else addch(lines[0][0]); /* Memory leak? */ tree->parent_window->current_pseudo_elements = tree->parent_window->current_pseudo_elements->next; ruin_css_clear_style_cache(container); temp_fg = ruin_css_match_foreground_color (ruin_css_lookup(tree, "color", inh)); temp_bg = ruin_css_match_background_color (ruin_css_lookup(tree, "background-color", inh), inh); _ruin_render_set_colors(temp_fg, temp_bg); _ruin_render_set_attrs(tree, inh); /* Draw the first line */ move(tree->top, tree->first_line_start + tree->parent->text_indent.used + 1); if (strcmp(ruin_css_lookup(tree, "font-variant", inh), "small-caps") == 0) { int len = strlen(lines[0]); for (i = 1; i < len; i++) lines[0][i] = toupper(lines[0][i]); } addstr(lines[0] + 1); /* Memory leak? */ tree->parent_window->current_pseudo_elements = tree->parent_window->current_pseudo_elements->next; ruin_css_clear_style_cache(container); _ruin_render_set_colors(tree->color, tree->background_color); _ruin_render_set_attrs(tree, inh); for (i = 1; i < num_lines; i++) { int new_top = 0, new_left = 0; char *a = ruin_css_lookup(tree, "text-align", inh); if (strcmp(a, "left") == 0) new_left = tree->left; else if (strcmp(a, "center") == 0) new_left = tree->left + ((container_width - (int) strlen(lines[i])) / 2); else if (strcmp(a, "right") == 0) new_left = tree->left + container_width - (int) strlen(lines[i]); /* Need to handle the more complicated vertical alignments. */ a = ruin_css_lookup(tree, "vertical-align", inh); if (strcmp(a, "top") == 0) new_top = tree->top + i; else if (strcmp(a, "middle") == 0) new_top = tree->top + ((tree->height.used) / 2) - (i - num_lines / 2); else if ((strcmp(a, "baseline") == 0) || (strcmp(a, "bottom") == 0)) new_top = tree->top + tree->height.used - (num_lines - i); if (new_top > tree->parent_window->top->height.used) return; move(new_top, new_left); if (strcmp(ruin_css_lookup(tree, "font-variant", inh), "small-caps") == 0) { int len = strlen(lines[i]), j = 0; for (j = 0; j < len; j++) lines[i][j] = toupper(lines[i][j]); } addstr(lines[i]); free(lines[i]); } free(lines); } else { ruin_element_t *elt_ptr = tree->first_child; inh = ruin_util_list_push_front(inh, ruin_util_list_new(tree)); while(elt_ptr != NULL) { if (strcmp(ruin_css_lookup(elt_ptr, "display", inh), "inline") == 0) ruin_render_render_tree(elt_ptr, inh); elt_ptr = elt_ptr->next_sibling; } free(inh); } return; } int _get_border_char(char *s, enum _ruin_render_border_char c) { if ((strcmp(s, "solid") == 0) || (strcmp(s, "inset") == 0) || (strcmp(s, "outset") == 0)) { switch(c) { case RUIN_RENDER_BORDER_ULCORNER: return ACS_ULCORNER; case RUIN_RENDER_BORDER_URCORNER: return ACS_URCORNER; case RUIN_RENDER_BORDER_LLCORNER: return ACS_LLCORNER; case RUIN_RENDER_BORDER_LRCORNER: return ACS_LRCORNER; case RUIN_RENDER_BORDER_HLINE: return ACS_HLINE; case RUIN_RENDER_BORDER_VLINE: return ACS_VLINE; case RUIN_RENDER_BORDER_LTEE: return ACS_LTEE; case RUIN_RENDER_BORDER_RTEE: return ACS_RTEE; case RUIN_RENDER_BORDER_BTEE: return ACS_BTEE; case RUIN_RENDER_BORDER_TTEE: return ACS_TTEE; default: return ' '; } } else if (strcmp(s, "dashed") == 0) { switch(c) { case RUIN_RENDER_BORDER_ULCORNER: case RUIN_RENDER_BORDER_URCORNER: case RUIN_RENDER_BORDER_LLCORNER: case RUIN_RENDER_BORDER_LRCORNER: case RUIN_RENDER_BORDER_LTEE: case RUIN_RENDER_BORDER_RTEE: case RUIN_RENDER_BORDER_BTEE: case RUIN_RENDER_BORDER_TTEE: return '+'; case RUIN_RENDER_BORDER_HLINE: return '-'; case RUIN_RENDER_BORDER_VLINE: return '|'; default: return ' '; } } else if (strcmp(s, "dotted") == 0) { switch(c) { case RUIN_RENDER_BORDER_ULCORNER: case RUIN_RENDER_BORDER_HLINE: case RUIN_RENDER_BORDER_URCORNER: case RUIN_RENDER_BORDER_TTEE: return '.'; case RUIN_RENDER_BORDER_LLCORNER: case RUIN_RENDER_BORDER_LRCORNER: case RUIN_RENDER_BORDER_VLINE: case RUIN_RENDER_BORDER_LTEE: case RUIN_RENDER_BORDER_RTEE: case RUIN_RENDER_BORDER_BTEE: return ':'; default: return ' '; } } else return ' '; } static void _ruin_render_draw_border (ruin_element_t *tree, ruin_util_list *inh, int top, int left) { int i = 0; int w = tree->width.used + tree->padding_left.used + tree->padding_right.used + tree->border_left_width.used + tree->border_right_width.used; int h = tree->height.used + tree->padding_top.used + tree->padding_bottom.used + tree->border_top_width.used + tree->border_bottom_width.used; char *border_style = ruin_css_lookup(tree, "border-top-style", inh); /* Draw the top... */ if ((strcmp(border_style, "none") != 0) && (tree->border_top_width.used > 0)) { if (strcmp(border_style, "inset") == 0) { } else if (strcmp(border_style, "outset") == 0) { } else _ruin_render_set_colors (tree->border_top_color, tree->background_color); for (i = 0; i < tree->border_top_width.used; i++) { int start = (tree->border_left_width.used * i) / tree->border_top_width.used; int end = (tree->border_right_width.used * i) / tree->border_top_width.used; move(top + i, left + start); hline(_get_border_char(border_style, RUIN_RENDER_BORDER_ULCORNER), 1); move(top + i, left + start + 1); hline(_get_border_char(border_style, RUIN_RENDER_BORDER_HLINE), w - end - start - 1); move(top + i, left + w - end - 1); hline(_get_border_char(border_style, RUIN_RENDER_BORDER_URCORNER), 1); } } /* Draw the bottom... */ border_style = ruin_css_lookup(tree, "border-bottom-style", inh); if ((strcmp(border_style, "none") != 0) && (tree->border_bottom_width.used > 0)) { if (strcmp(border_style, "inset") == 0) { } else if (strcmp(border_style, "outset") == 0) { } else _ruin_render_set_colors (tree->border_bottom_color, tree->background_color); for (i = tree->border_bottom_width.used; i > 0; i--) { int start = (tree->border_left_width.used * (tree->border_bottom_width.used - i)) / tree->border_bottom_width.used; int end = (tree->border_right_width.used * (tree->border_bottom_width.used - i)) / tree->border_bottom_width.used; move(top + h - tree->border_bottom_width.used + i - 1, left + start); hline(_get_border_char(border_style, RUIN_RENDER_BORDER_LLCORNER), 1); move(top + h - tree->border_bottom_width.used + i - 1, left + start + 1); hline(_get_border_char(border_style, RUIN_RENDER_BORDER_HLINE), w - end - start - 1); move(top + h - tree->border_bottom_width.used + i - 1, left + w - end - 1); hline(_get_border_char(border_style, RUIN_RENDER_BORDER_LRCORNER), 1); } } /* Draw the left... */ if ((strcmp(border_style = ruin_css_lookup(tree, "border-left-style", inh), "none") != 0) && (tree->border_left_width.used > 0)) { if (strcmp(border_style, "inset") == 0) { } else if (strcmp(border_style, "outset") == 0) { } else _ruin_render_set_colors (tree->border_left_color, tree->background_color); for (i = 0; i < tree->border_left_width.used; i++) { int start = (tree->border_top_width.used / tree->border_left_width.used) * (i + 1); int end = (tree->border_bottom_width.used / tree->border_left_width.used) * (i + 1); move(top + start, left + i); vline(_get_border_char(border_style, RUIN_RENDER_BORDER_VLINE), h - start - end); } } /* Draw the right... */ if ((strcmp(border_style = ruin_css_lookup(tree, "border-right-style", inh), "none") != 0) && (tree->border_right_width.used > 0)) { if (strcmp(border_style, "inset") == 0) { } else if (strcmp(border_style, "outset") == 0) { } else _ruin_render_set_colors (tree->border_right_color, tree->background_color); for (i = tree->border_right_width.used; i > 0; i--) { int start = (tree->border_top_width.used / tree->border_right_width.used) * (tree->border_right_width.used - i + 1); int end = (tree->border_bottom_width.used / tree->border_right_width.used) * (tree->border_right_width.used - i + 1); move(top + start, left + w - tree->border_right_width.used + i - 1); vline(_get_border_char(border_style, RUIN_RENDER_BORDER_VLINE), h - start - end); } } } void ruin_render_draw_block(ruin_element_t *tree, ruin_util_list *inh) { int render_top, render_left, full_width, full_height; if ((tree == NULL) || (!tree->visible)) return; full_width = tree->margin_left.used + tree->border_left_width.used + tree->padding_left.used + tree->width.used + tree->padding_right.used + tree->border_right_width.used + tree->margin_right.used; full_height = tree->margin_top.used + tree->border_top_width.used + tree->padding_top.used + tree->height.used + tree->padding_bottom.used + tree->border_bottom_width.used + tree->margin_bottom.used; /* We're not going to render 0-size elements. The border should be factored into the node's current_width/height value, so if it's zero, there's no border, either. */ render_top = tree->top + tree->margin_top.used + tree->border_top_width.used; render_left = tree->left + tree->margin_left.used + tree->border_left_width.used; if (((tree->max_height.computed != RUIN_LAYOUT_VALUE_NONE) && (full_height <= 0)) || ((tree->max_width.computed != RUIN_LAYOUT_VALUE_NONE) && (full_width <= 0))) { ruin_render_render_tree(tree->next_sibling, inh); return; } /* First paint the background. */ _ruin_render_set_colors(tree->color, tree->background_color); { int i; int num_lines = full_height - tree->margin_top.used - tree->margin_bottom.used; int width = full_width - tree->margin_left.used - tree->margin_right.used; char *line = calloc(width + 1, sizeof(char)); (void) memset(line, (int) ' ', width); for (i = 0; i < num_lines; i++) { int top = render_top - tree->border_top_width.used + i; if (top > tree->parent_window->top->height.used) break; move(render_top - tree->border_top_width.used + i, render_left - tree->border_left_width.used); addstr(line); } } /* First render any special content, like a radio button or checkbox. */ { char *extra_content_str; switch(tree->extra_content) { case RUIN_LAYOUT_EXTRA_CONTENT_RADIO: extra_content_str = calloc(5, sizeof(char)); extra_content_str = strcat(extra_content_str, "("); extra_content_str = strcat(extra_content_str, (tree->selected) ? "*" : " "); extra_content_str = strcat(extra_content_str, ") "); move(render_top + tree->border_top_width.used + tree->padding_top.used, render_left + tree->border_left_width.used + tree->padding_left.used); addstr(extra_content_str); /* child_render_left += 4; */ break; case RUIN_LAYOUT_EXTRA_CONTENT_CHECKBOX: extra_content_str = calloc(5, sizeof(char)); extra_content_str = strcat(extra_content_str, "["); extra_content_str = strcat(extra_content_str, (tree->selected) ? "x" : " "); extra_content_str = strcat(extra_content_str, "] "); move(render_top + tree->border_top_width.used + tree->padding_top.used, render_left + tree->border_left_width.used + tree->padding_left.used); addstr(extra_content_str); /* child_render_left += 4; */ break; case RUIN_LAYOUT_EXTRA_CONTENT_COLORPICKER: move(render_top + tree->border_top_width.used + tree->padding_top.used, render_left + tree->border_left_width.used + tree->padding_left.used); addch(ACS_BLOCK); addch(ACS_BLOCK); break; case RUIN_LAYOUT_EXTRA_CONTENT_NONE: break; } } /* Now render the children. */ if (tree->caption != NULL) ruin_render_render_tree(tree->caption, inh); { ruin_element_t *elt_ptr = tree->first_child; ruin_util_list *inh2 = ruin_util_list_new(tree); inh2->next = inh; while(elt_ptr != NULL) { ruin_render_render_tree(elt_ptr, inh2); elt_ptr = elt_ptr->next_sibling; } free(inh2); } /* The children might have changed the colors... */ _ruin_render_set_colors(tree->color, tree->background_color); /* Draw the border. */ _ruin_render_draw_border(tree, inh, render_top - tree->border_top_width.used, render_left - tree->border_left_width.used); return; } void ruin_render_draw_table(ruin_element_t *tree, ruin_util_list *inh) { ruin_element_t *elt_ptr = tree->first_child; while(elt_ptr != NULL) { char *d = ruin_css_lookup(elt_ptr, "display", NULL); if (strcmp(d, "table-row-group") == 0) { ruin_element_t *elt_ptr_backup = elt_ptr; elt_ptr = elt_ptr->first_child; while(elt_ptr != NULL) { ruin_element_t *elt_ptr_backup_backup = elt_ptr; elt_ptr = elt_ptr->first_child; while(elt_ptr != NULL) { ruin_render_render_tree(elt_ptr, inh); elt_ptr = elt_ptr->next_sibling; } elt_ptr = elt_ptr_backup_backup; elt_ptr = elt_ptr->next_sibling; } elt_ptr = elt_ptr_backup; } else if (strcmp(d, "table-row") == 0) { ruin_element_t *elt_ptr_backup = elt_ptr; elt_ptr = elt_ptr->first_child; while(elt_ptr != NULL) { ruin_render_render_tree(elt_ptr, inh); elt_ptr = elt_ptr->next_sibling; } elt_ptr = elt_ptr_backup; } elt_ptr = elt_ptr->next_sibling; } _ruin_render_draw_border(tree, inh, tree->top, tree->left); return; } void ruin_render_draw_list_item(ruin_element_t *tree, ruin_util_list *inh) { int render_top, render_left; int my_pos = 1, has_dot = TRUE; char *marker = NULL; char *style = ruin_css_lookup(tree, "list-style-type", inh); ruin_element_t *elt_ptr = tree->prev_sibling; while(elt_ptr != NULL) { my_pos++; elt_ptr = elt_ptr->prev_sibling; } if (strcmp(style, "disc") == 0) { marker = strdup("*"); has_dot = FALSE; } else if (strcmp(style, "circle") == 0) { marker = strdup("o"); has_dot = FALSE; } else if (strcmp(style, "square") == 0) { marker = strdup("+"); has_dot = FALSE; } else if (strcmp(style, "decimal") == 0) { int len = (int) floor(log(my_pos) / log(10)) + 1; marker = calloc(sizeof(char), len + 1); snprintf(marker, len + 1, "%d", my_pos); } else if (strcmp(style, "decimal-leading-zero") == 0) { int len = (int) floor(log(my_pos) / log(10)); marker = calloc(sizeof(char), 4); if (my_pos < 100) strcat(marker, "0"); if (my_pos < 10) strcat(marker, "0"); snprintf(marker + strlen(marker), len + 1, "%d", my_pos); } else if (strcmp(style, "lower-roman") == 0) marker = ruin_util_arabic_to_roman(my_pos, FALSE); else if (strcmp(style, "upper-roman") == 0) marker = ruin_util_arabic_to_roman(my_pos, TRUE); else if ((strcmp(style, "lower-latin") == 0) || (strcmp(style, "lower-alpha") == 0) || (strcmp(style, "lower-greek") == 0)) { marker = calloc(sizeof(char), 2); snprintf(marker, 2, "%c", 96 + (my_pos % 26)); } else if ((strcmp(style, "upper-latin") == 0) || (strcmp(style, "upper-alpha") == 0)) { marker = calloc(sizeof(char), 2); snprintf(marker, 2, "%c", 64 + (my_pos % 26)); } else if (strcmp(style, "none") == 0) has_dot = FALSE; else { /* Treat everything else like a decimal. */ int len = (int) floor(log(my_pos) / log(10)) + 1; marker = calloc(sizeof(char), len + 1); snprintf(marker, len + 1, "%d", my_pos); } render_top = tree->top + tree->margin_top.used + tree->border_top_width.used + tree->padding_top.used; render_left = tree->left + tree->margin_left.used + tree->border_left_width.used + tree->padding_left.used; move(render_top, render_left); /* Draw the border TK... */ /* Draw the marker... */ _ruin_render_set_colors(tree->color, tree->background_color); addstr(marker); if (has_dot) { move(render_top, render_left + strlen(marker)); addch('.'); } free(marker); /* Draw the children... */ elt_ptr = tree->first_child; inh = ruin_util_list_push_front(inh, ruin_util_list_new(tree)); while(elt_ptr != NULL) { ruin_render_render_tree(elt_ptr, inh); elt_ptr = elt_ptr->next_sibling; } free(inh); return; } void ruin_render_render_tree(ruin_element_t *tree, ruin_util_list *inh) { char *d = ruin_css_lookup(tree, "display", inh); tree->color = ruin_css_match_foreground_color (ruin_css_lookup(tree, "color", inh)); tree->background_color = ruin_css_match_background_color (ruin_css_lookup(tree, "background-color", inh), inh); tree->border_top_color = ruin_css_match_foreground_color (ruin_css_lookup(tree, "border-top-color", inh)); tree->border_left_color = ruin_css_match_foreground_color (ruin_css_lookup(tree, "border-left-color", inh)); tree->border_bottom_color = ruin_css_match_foreground_color (ruin_css_lookup(tree, "border-bottom-color", inh)); tree->border_right_color = ruin_css_match_foreground_color (ruin_css_lookup(tree, "border-right-color", inh)); if ((strcmp(d, "block") == 0) || (strcmp(d, "table-cell") == 0)) { ruin_render_draw_block(tree, inh); } else if (strcmp(d, "inline") == 0) { ruin_render_draw_inline(tree, inh); } else if (strcmp(d, "table") == 0) { ruin_render_draw_table(tree, inh); } else if (strcmp(d, "list-item") == 0) { ruin_render_draw_list_item(tree, inh); } return; }