[wip] draw_images(): Clean up pixel/character and box handling
[elinks/images.git] / src / viewer / text / draw.c
blobf0b566afabba7cc3eb91890eb1605879c865dfcb
1 /** Text mode drawing functions
2 * @file */
4 #ifdef HAVE_CONFIG_H
5 #include "config.h"
6 #endif
8 #include <stdlib.h>
9 #include <string.h>
10 #ifdef HAVE_UNISTD_H
11 #include <unistd.h>
12 #endif
14 #include "elinks.h"
16 #include "bfu/dialog.h"
17 #include "cache/cache.h"
18 #include "document/document.h"
19 #include "document/html/frames.h"
20 #include "document/options.h"
21 #include "document/refresh.h"
22 #include "document/renderer.h"
23 #include "document/view.h"
24 #include "dialogs/status.h" /* print_screen_status() */
25 #include "intl/charsets.h"
26 #include "intl/gettext/libintl.h"
27 #include "protocol/uri.h"
28 #include "session/location.h"
29 #include "session/session.h"
30 #include "terminal/draw.h"
31 #include "terminal/image.h"
32 #include "terminal/tab.h"
33 #include "terminal/terminal.h"
34 #include "util/error.h"
35 #include "util/lists.h"
36 #include "util/memory.h"
37 #include "util/string.h"
38 #include "viewer/text/draw.h"
39 #include "viewer/text/form.h"
40 #include "viewer/text/link.h"
41 #include "viewer/text/search.h"
42 #include "viewer/text/view.h" /* current_frame() */
43 #include "viewer/text/vs.h"
46 static inline int
47 check_document_fragment(struct session *ses, struct document_view *doc_view)
49 struct document *document = doc_view->document;
50 struct uri *uri = doc_view->vs->uri;
51 int vy;
52 struct string fragment;
54 assert(uri->fragmentlen);
56 if (!init_string(&fragment)) return -2;
57 if (!add_uri_to_string(&fragment, uri, URI_FRAGMENT)) {
58 done_string(&fragment);
59 return -2;
61 decode_uri_string(&fragment);
62 assert(fragment.length);
63 assert(*fragment.source);
65 /* Omit the leading '#' when calling find_tag. */
66 vy = find_tag(document, fragment.source + 1, fragment.length - 1);
67 if (vy == -1) {
68 struct cache_entry *cached = document->cached;
70 assert(cached);
71 if (cached->incomplete || cached->cache_id != document->cache_id) {
72 done_string(&fragment);
73 return -2;
76 if (get_opt_bool("document.browse.links.missing_fragment",
77 ses)) {
78 info_box(ses->tab->term, MSGBOX_FREE_TEXT,
79 N_("Missing fragment"), ALIGN_CENTER,
80 msg_text(ses->tab->term, N_("The requested fragment "
81 "\"%s\" doesn't exist."),
82 fragment.source));
84 } else {
85 int_bounds(&vy, 0, document->height - 1);
88 done_string(&fragment);
90 return vy;
93 static void
94 draw_frame_lines(struct terminal *term, struct frameset_desc *frameset_desc,
95 int xp, int yp, struct color_pair *colors)
97 int y, j;
99 assert(term && frameset_desc && frameset_desc->frame_desc);
100 if_assert_failed return;
102 y = yp - 1;
103 for (j = 0; j < frameset_desc->box.height; j++) {
104 int x, i;
105 int height = frameset_desc->frame_desc[j * frameset_desc->box.width].height;
107 x = xp - 1;
108 for (i = 0; i < frameset_desc->box.width; i++) {
109 int width = frameset_desc->frame_desc[i].width;
111 if (i) {
112 struct box box;
114 set_box(&box, x, y + 1, 1, height);
115 draw_box(term, &box, BORDER_SVLINE, SCREEN_ATTR_FRAME, colors);
117 if (j == frameset_desc->box.height - 1)
118 draw_border_cross(term, x, y + height + 1,
119 BORDER_X_UP, colors);
120 } else if (j) {
121 if (x >= 0)
122 draw_border_cross(term, x, y,
123 BORDER_X_RIGHT, colors);
126 if (j) {
127 struct box box;
129 set_box(&box, x + 1, y, width, 1);
130 draw_box(term, &box, BORDER_SHLINE, SCREEN_ATTR_FRAME, colors);
132 if (i == frameset_desc->box.width - 1
133 && x + width + 1 < term->width)
134 draw_border_cross(term, x + width + 1, y,
135 BORDER_X_LEFT, colors);
136 } else if (i) {
137 draw_border_cross(term, x, y, BORDER_X_DOWN, colors);
140 if (i && j)
141 draw_border_char(term, x, y, BORDER_SCROSS, colors);
143 x += width + 1;
145 y += height + 1;
148 y = yp - 1;
149 for (j = 0; j < frameset_desc->box.height; j++) {
150 int x, i;
151 int pj = j * frameset_desc->box.width;
152 int height = frameset_desc->frame_desc[pj].height;
154 x = xp - 1;
155 for (i = 0; i < frameset_desc->box.width; i++) {
156 int width = frameset_desc->frame_desc[i].width;
157 int p = pj + i;
159 if (frameset_desc->frame_desc[p].subframe) {
160 draw_frame_lines(term, frameset_desc->frame_desc[p].subframe,
161 x + 1, y + 1, colors);
163 x += width + 1;
165 y += height + 1;
169 static void
170 draw_images(struct session *ses, struct document_view *doc_view, struct view_state *vs, struct box *win_box)
172 struct document *document = doc_view->document;
173 struct terminal *term = ses->tab->term;
174 int chw, chh;
175 int i, cnt = 1;
176 /* @vs_pixbox is a bounding box representing the viewport in
177 * the document (coordinates relative to document top left), while
178 * @win_pixbox is a bounding box representing the screen tab window.
179 * "Pixbox" means that the dimensions aren't characters but pixels. */
180 struct box win_pixbox;
181 struct box vs_pixbox;
183 assert(document && term);
184 if_assert_failed return;
186 /* Character width, height in pixels. */
187 chw = term->xres / term->width;
188 chh = term->yres / term->height;
189 //printf("RES %d/%d->%d %d/%d->%d\n", term->xres, term->width, chw, term->yres, term->height, chh);
191 set_box(&win_pixbox, win_box->x * chw, win_box->y * chh, win_box->width * chw, win_box->height * chh);
192 set_box(&vs_pixbox, vs->x * chw, vs->y * chh, win_pixbox.width, win_pixbox.height);
194 //printf("ni %d\n", document->nimages);
196 for (i = 0; i < document->nimages; i++) {
197 struct image *img = &document->images[i];
198 struct cache_entry *cached;
199 struct box img_pixbox;
201 /* Fetch cache entry. */
202 cached = get_redirected_cache_entry(img->uri);
203 //printf("ce %p %d\n", cached, cached ? cached->incomplete : -1);
204 if (!cached || cached->incomplete)
205 continue;
207 /* Get image dimensions, if unknown. */
208 if (img->w < 0 || img->h < 0) {
209 int w, h;
211 get_image_size(cached, &w, &h);
212 if (img->w < 0) img->w = w;
213 if (img->h < 0) img->h = h;
216 set_box(&img_pixbox, img->pos.x * chw, img->pos.y * chh, img->w, img->h);
218 if (is_in_box(&vs_pixbox, img_pixbox.x, img_pixbox.y)) {
219 /* Arrange the image in the tab window bounding box. */
220 img_pixbox.x = win_pixbox.x + img_pixbox.x - vs_pixbox.x;
221 img_pixbox.y = win_pixbox.y + img_pixbox.y - vs_pixbox.y;
222 img_pixbox.width = int_min(img_pixbox.width, win_pixbox.width - img_pixbox.x);
223 img_pixbox.height = int_min(img_pixbox.height, win_pixbox.height - img_pixbox.y);
225 show_image(term, cnt++, &img_pixbox, cached);
229 sync_images(term);
232 static void
233 draw_view_status(struct session *ses, struct document_view *doc_view, int active)
235 struct terminal *term = ses->tab->term;
237 draw_forms(term, doc_view);
238 if (active) {
239 draw_searched(term, doc_view);
240 draw_current_link(ses, doc_view);
244 /** Checks if there is a link under the cursor so it can become the current
245 * highlighted link. */
246 static void
247 check_link_under_cursor(struct session *ses, struct document_view *doc_view)
249 int x = ses->tab->x;
250 int y = ses->tab->y;
251 struct box *box = &doc_view->box;
252 struct link *link;
254 link = get_link_at_coordinates(doc_view, x - box->x, y - box->y);
255 if (link && link != get_current_link(doc_view)) {
256 doc_view->vs->current_link = link - doc_view->document->links;
260 /** Puts the formatted document on the given terminal's screen.
261 * @a active indicates whether the document is focused -- i.e.,
262 * whether it is displayed in the selected frame or document. */
263 static void
264 draw_doc(struct session *ses, struct document_view *doc_view, int active)
266 struct color_pair color;
267 struct view_state *vs;
268 struct terminal *term;
269 struct box *box;
270 int vx, vy;
271 int y;
273 assert(ses && ses->tab && ses->tab->term && doc_view);
274 if_assert_failed return;
276 box = &doc_view->box;
277 term = ses->tab->term;
279 /* The code in this function assumes that both width and height are
280 * bigger than 1 so we have to bail out here. */
281 if (box->width < 2 || box->height < 2) return;
283 if (active) {
284 /* When redrawing the document after things like link menu we
285 * have to reset the cursor routing state. */
286 if (ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
287 set_cursor(term, ses->tab->x, ses->tab->y, 0);
288 } else {
289 set_cursor(term, box->x + box->width - 1, box->y + box->height - 1, 1);
290 set_window_ptr(ses->tab, box->x, box->y);
294 color.foreground = get_opt_color("document.colors.text", ses);
295 color.background = doc_view->document->height
296 ? doc_view->document->color.background
297 : get_opt_color("document.colors.background", ses);
299 vs = doc_view->vs;
300 if (!vs) {
301 draw_box(term, box, ' ', 0, &color);
302 return;
305 if (document_has_frames(doc_view->document)) {
306 draw_box(term, box, ' ', 0, &color);
307 draw_frame_lines(term, doc_view->document->frame_desc, box->x, box->y, &color);
308 if (vs->current_link == -1)
309 vs->current_link = 0;
310 return;
313 if (ses->navigate_mode == NAVIGATE_LINKWISE) {
314 check_vs(doc_view);
316 } else {
317 check_link_under_cursor(ses, doc_view);
320 if (!vs->did_fragment) {
321 vy = check_document_fragment(ses, doc_view);
323 if (vy != -2) vs->did_fragment = 1;
324 if (vy >= 0) {
325 doc_view->vs->y = vy;
326 set_link(doc_view);
329 vx = vs->x;
330 vy = vs->y;
331 if (doc_view->last_x != -1
332 && doc_view->last_x == vx
333 && doc_view->last_y == vy
334 && !has_search_word(doc_view)) {
335 clear_link(term, doc_view);
336 draw_view_status(ses, doc_view, active);
337 return;
339 doc_view->last_x = vx;
340 doc_view->last_y = vy;
341 draw_box(term, box, ' ', 0, &color);
342 if (!doc_view->document->height) return;
344 while (vs->y >= doc_view->document->height) vs->y -= box->height;
345 int_lower_bound(&vs->y, 0);
346 if (vy != vs->y) {
347 vy = vs->y;
348 if (ses->navigate_mode == NAVIGATE_LINKWISE)
349 check_vs(doc_view);
351 for (y = int_max(vy, 0);
352 y < int_min(doc_view->document->height, box->height + vy);
353 y++) {
354 int st = int_max(vx, 0);
355 int en = int_min(doc_view->document->data[y].length,
356 box->width + vx);
358 if (en - st <= 0) continue;
359 draw_line(term, box->x + st - vx, box->y + y - vy, en - st,
360 &doc_view->document->data[y].chars[st]);
362 draw_images(ses, doc_view, vs, box);
363 draw_view_status(ses, doc_view, active);
364 if (has_search_word(doc_view))
365 doc_view->last_x = doc_view->last_y = -1;
368 static void
369 draw_frames(struct session *ses)
371 struct document_view *doc_view, *current_doc_view;
372 int *l;
373 int n, d;
375 assert(ses && ses->doc_view && ses->doc_view->document);
376 if_assert_failed return;
378 if (!document_has_frames(ses->doc_view->document)) return;
380 n = 0;
381 foreach (doc_view, ses->scrn_frames) {
382 doc_view->last_x = doc_view->last_y = -1;
383 n++;
385 l = &cur_loc(ses)->vs.current_link;
386 *l = int_max(*l, 0) % int_max(n, 1);
388 current_doc_view = current_frame(ses);
389 d = 0;
390 while (1) {
391 int more = 0;
393 foreach (doc_view, ses->scrn_frames) {
394 if (doc_view->depth == d)
395 draw_doc(ses, doc_view, doc_view == current_doc_view);
396 else if (doc_view->depth > d)
397 more = 1;
400 if (!more) break;
401 d++;
405 /** @todo @a rerender is ridiciously wound-up. */
406 void
407 draw_formatted(struct session *ses, int rerender)
409 assert(ses && ses->tab);
410 if_assert_failed return;
412 if (rerender) {
413 rerender--; /* Mind this when analyzing @rerender. */
414 if (!(rerender & 2) && session_is_loading(ses))
415 rerender |= 2;
416 render_document_frames(ses, rerender);
418 /* Rerendering kills the document refreshing so restart it. */
419 start_document_refreshes(ses);
422 if (ses->tab != get_current_tab(ses->tab->term))
423 return;
425 if (!ses->doc_view || !ses->doc_view->document) {
426 /*INTERNAL("document not formatted");*/
427 struct box box;
429 set_box(&box, 0, 1,
430 ses->tab->term->width,
431 ses->tab->term->height - 2);
432 draw_box(ses->tab->term, &box, ' ', 0, NULL);
433 return;
436 if (!ses->doc_view->vs && have_location(ses))
437 ses->doc_view->vs = &cur_loc(ses)->vs;
438 ses->doc_view->last_x = ses->doc_view->last_y = -1;
440 refresh_view(ses, ses->doc_view, 1);
443 void
444 refresh_view(struct session *ses, struct document_view *doc_view, int frames)
446 /* If refresh_view() is being called because the value of a
447 * form field has changed, @ses might not be in the current
448 * tab: consider SELECT pop-ups behind which -remote loads
449 * another tab, or setTimeout in ECMAScript. */
450 if (ses->tab == get_current_tab(ses->tab->term)) {
451 draw_doc(ses, doc_view, 1);
452 if (frames) draw_frames(ses);
454 print_screen_status(ses);