1 /*
2  * cell-draw.c: Cell drawing on screen
3  *
4  * Author:
5  *    Miguel de Icaza 1998, 1999 (miguel@kernel.org)
6  *    Jody Goldberg 2000-2002    (jody@gnome.org)
7  *    Morten Welinder 2003       (terra@gnome.org)
8  */
9 #include <gnumeric-config.h>
10 #include <gnumeric.h>
11 #include <cell-draw.h>
12 
13 #include <style.h>
14 #include <cell.h>
15 #include <sheet.h>
16 #include <gnm-format.h>
17 #include <rendered-value.h>
18 #include <parse-util.h>
19 #include <sheet-merge.h>
20 #include <goffice/goffice.h>
21 
22 #include <gdk/gdk.h>
23 #include <string.h>
24 #include <math.h>
25 
26 static char const hashes[] =
27 "################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################";
28 
29 static gboolean
cell_draw_simplify_cb(PangoAttribute * attribute,gboolean * recalc_height)30 cell_draw_simplify_cb (PangoAttribute *attribute,
31 		       gboolean *recalc_height)
32 {
33 	if ((attribute->klass->type == PANGO_ATTR_RISE) ||
34 	    (attribute->klass->type == PANGO_ATTR_SCALE)) {
35 		*recalc_height = TRUE;
36 		return TRUE;
37 	}
38 	return (attribute->klass->type == PANGO_ATTR_SHAPE);
39 }
40 
41 static void
cell_draw_simplify_attributes(GnmRenderedValue * rv)42 cell_draw_simplify_attributes (GnmRenderedValue *rv)
43 {
44 	PangoAttrList *pal = pango_layout_get_attributes (rv->layout);
45 	gboolean recalc_height = FALSE;
46 	pango_attr_list_unref
47 		(pango_attr_list_filter
48 		 (pal, (PangoAttrFilterFunc) cell_draw_simplify_cb, &recalc_height));
49 	if (recalc_height)
50 		pango_layout_get_size (rv->layout, NULL,
51 				       &rv->layout_natural_height);
52 }
53 
54 /*
55  *             G      G
56  *             r      r
57  *             i      i
58  *             d      d
59  *
60  *  Grid line  a------+
61  *             |mmmmmm|
62  *             |m    m|
63  *             |mmmmmm|
64  *  Grid line  +------+
65  *
66  *  'm' == margin
67  *  ' ' == space for contents
68  *
69  * @h_center: The number of pango units from x1 marking the logical center
70  *             of the cell.  NOTE This can be asymmetric.  Passing
71  *             <= 0 will use width / 2
72  */
73 gboolean
cell_calc_layout(G_GNUC_UNUSED GnmCell const * cell,GnmRenderedValue * rv,int y_direction,int width,int height,int h_center,GOColor * res_color,gint * res_x,gint * res_y)74 cell_calc_layout (G_GNUC_UNUSED GnmCell const *cell, GnmRenderedValue *rv, int y_direction,
75 		  int width, int height, int h_center,
76 		  GOColor *res_color, gint *res_x, gint *res_y)
77 {
78 	int text_base;
79 	PangoLayout *layout;
80 	int indent;
81 	int hoffset;
82 	int rect_x, rect_y;
83 	gboolean was_drawn;
84 
85 	g_return_val_if_fail (rv != NULL, FALSE);
86 
87 	layout = rv->layout;
88 	indent = (rv->indent_left + rv->indent_right) * PANGO_SCALE;
89 
90 	was_drawn = rv->drawn;
91 	rv->drawn = TRUE;
92 
93 	if (width <= 0 || height <= 0)
94 		return FALSE;
95 
96 	hoffset = rv->indent_left * PANGO_SCALE;
97 
98 #if 0
99 	g_print ("%s: w=%d  h=%d\n", cell_name (cell), width, height);
100 #endif
101 
102 	/* This rectangle has the whole area used by this cell
103 	 * excluding the surrounding grid lines and margins */
104 	rect_x = PANGO_SCALE * (1 + GNM_COL_MARGIN);
105 	rect_y = PANGO_SCALE * y_direction * (1 + GNM_ROW_MARGIN);
106 
107 	/* if a number overflows, do special drawing */
108 	if (rv->layout_natural_width > width - indent &&
109 	    rv->might_overflow &&
110 	    !rv->numeric_overflow) {
111 		char const *text = pango_layout_get_text (layout);
112 		size_t textlen = strlen (text);
113 #if 0
114 		g_print ("nat=%d  w=%d  i=%d\n", rv->layout_natural_width, width, indent);
115 #endif
116 		/* This assumes that two hash marks are wider than
117 		   the characters in the number.  Probably ok.  */
118 		pango_layout_set_text (layout, hashes,
119 				       MIN (sizeof (hashes) - 1, 2 * textlen));
120 		cell_draw_simplify_attributes (rv);
121 		rv->numeric_overflow = TRUE;
122 		rv->variable_width = TRUE;
123 		rv->hfilled = TRUE;
124 	}
125 
126 	/* Special handling of error dates.  */
127 	if (!was_drawn && rv->numeric_overflow) {
128 		pango_layout_set_text (layout, hashes, -1);
129 		cell_draw_simplify_attributes (rv);
130 		rv->variable_width = TRUE;
131 		rv->hfilled = TRUE;
132 	}
133 
134 	if (rv->rotation && !rv->noborders) {
135 		GnmRenderedRotatedValue const *rrv = (GnmRenderedRotatedValue *)rv;
136 		if (rv->wrap_text) {
137 			/* quick fix for #394, may be not perfect */
138 			double rot = rv->rotation / 180. * M_PI, actual_width;
139 			actual_width = MAX (0, width - indent) * cos (rot) + height * fabs (sin (rot));
140 			if (actual_width > pango_layout_get_width (layout)) {
141 				pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
142 				pango_layout_set_width (layout, actual_width);
143 				gnm_rendered_value_remeasure (rv);
144 			}
145 		}
146 		if (rrv->sin_a_neg) {
147 			hoffset += (width - indent) - rv->layout_natural_width;
148 		}
149 	} else if (!rv->rotation && rv->wrap_text
150 		   && (rv->effective_halign != GNM_HALIGN_FILL)) {
151 		int wanted_width = MAX (0, width - indent);
152 		if (wanted_width != pango_layout_get_width (layout)) {
153 			pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
154 			pango_layout_set_width (layout, wanted_width);
155 			gnm_rendered_value_remeasure (rv);
156 		}
157 	} else {
158 		switch (rv->effective_halign) {
159 		case GNM_HALIGN_RIGHT:
160 			hoffset += (width - indent) - rv->layout_natural_width;
161 			break;
162 		case GNM_HALIGN_DISTRIBUTED:
163 		case GNM_HALIGN_CENTER:
164 			if (h_center == -1)
165 				h_center = width / 2;
166 			hoffset += h_center + (-indent - rv->layout_natural_width) / 2;
167 			break;
168 		case GNM_HALIGN_CENTER_ACROSS_SELECTION:
169 			hoffset += ((width - indent) - rv->layout_natural_width) / 2;
170 			break;
171 		case GNM_HALIGN_FILL: {
172 			PangoDirection dir = PANGO_DIRECTION_LTR;
173 			if (!rv->hfilled &&
174 			    rv->layout_natural_width > 0 &&
175 			    width - indent >= 2 * rv->layout_natural_width) {
176 				/*
177 				 * We ignore kerning between copies in calculating the number
178 				 * of copies needed.  Instead we toss in a zero-width-space.
179 				 */
180 				int copies = (width - indent) / rv->layout_natural_width;
181 				char const *copy1 = pango_layout_get_text (layout);
182 				size_t len1 = strlen (copy1);
183 				GString *multi = g_string_sized_new ((len1 + 6) * copies);
184 				int i;
185 				PangoAttrList *attr = pango_layout_get_attributes (layout);
186 
187 				dir = pango_find_base_dir (copy1, -1);
188 				for (i = 0; i < copies; i++) {
189 					if (i)
190 						g_string_append_unichar (multi, UNICODE_ZERO_WIDTH_SPACE_C);
191 					g_string_append_len (multi, copy1, len1);
192 				}
193 				pango_layout_set_text (layout, multi->str, multi->len);
194 				g_string_free (multi, TRUE);
195 
196 				if (attr != NULL && !go_pango_attr_list_is_empty (attr)) {
197 					PangoAttrList *attr_c = pango_attr_list_copy (attr);
198 					size_t len = len1 + UNICODE_ZERO_WIDTH_SPACE_C_UTF8_LENGTH;
199 					for (i = 1; i < copies;
200 					     i++, len += len1 + UNICODE_ZERO_WIDTH_SPACE_C_UTF8_LENGTH)
201 						pango_attr_list_splice (attr, attr_c, len, len1);
202 					pango_attr_list_unref (attr_c);
203 				}
204 			} else
205 				dir = pango_find_base_dir (pango_layout_get_text (layout), -1);
206 			/* right align if text is RTL */
207 			if (dir == PANGO_DIRECTION_RTL) {
208 				PangoRectangle r;
209 				pango_layout_get_extents (layout, NULL, &r);
210 				hoffset += (width - indent) - r.width;
211 			}
212 
213 			rv->hfilled = TRUE;
214 			break;
215 		}
216 
217 #ifndef DEBUG_SWITCH_ENUM
218 		default:
219 #endif
220 		case GNM_HALIGN_GENERAL:
221 			g_warning ("Unhandled horizontal alignment.");
222 		case GNM_HALIGN_LEFT:
223 			break;
224 		}
225 	}
226 
227 	/* Note that Excel always truncates the cell content only at the */
228 	/* bottom even if the request is to align it at the bottom or to */
229 	/* center it. We do the same for compatibilities sake. Also see  */
230 	/* bug #662368 */
231 	switch (rv->effective_valign) {
232 #ifndef DEBUG_SWITCH_ENUM
233 	default:
234 		g_warning ("Unhandled vertical alignment.");
235 		/* Fall through.  */
236 #endif
237 	case GNM_VALIGN_TOP:
238 		text_base = rect_y;
239 		break;
240 
241 	case GNM_VALIGN_BOTTOM: {
242 		int dh = height - rv->layout_natural_height;
243 		if (rv->rotation == 0 && dh < 0)
244 			dh = 0;
245 		text_base = rect_y + y_direction * dh;
246 		break;
247 	}
248 
249 	case GNM_VALIGN_DISTRIBUTED: /* dunno what this does yet */
250 	case GNM_VALIGN_CENTER: {
251 		int dh = (height - rv->layout_natural_height) / 2;
252 		if (rv->rotation == 0 && dh < 0)
253                         dh = 0;
254 		text_base = rect_y + y_direction * dh;
255 		break;
256 	}
257 
258 	case GNM_VALIGN_JUSTIFY:
259 		text_base = rect_y;
260 		if (!rv->vfilled && height > rv->layout_natural_height) {
261 			int line_count = pango_layout_get_line_count (layout);
262 			if (line_count > 1) {
263 				int spacing = (height - rv->layout_natural_height) /
264 					(line_count - 1);
265 				pango_layout_set_spacing (layout, spacing);
266 				gnm_rendered_value_remeasure (rv);
267 			}
268 		}
269 		rv->vfilled = TRUE;
270 		break;
271 	}
272 
273 #if 0
274 	if (rv->rotation)
275 		g_print ("hoffset=%d,  text_base=%d,  n_width=%d, n_height=%d\n",
276 			 hoffset, text_base,
277 			 rv->layout_natural_width, rv->layout_natural_height);
278 #endif
279 
280 	*res_color = gnm_rendered_value_get_color (rv);
281 	*res_x = rect_x + hoffset;
282 	*res_y = text_base;
283 
284 	return TRUE;
285 }
286 
287 /*
288  * This finishes a layout by pretending to draw it.  The effect is to
289  * handler numerical overflow, filling, etc.
290  * (Doesn't currently handle vertical fill.)
291  */
292 void
cell_finish_layout(GnmCell * cell,GnmRenderedValue * rv,int col_width,gboolean inhibit_overflow)293 cell_finish_layout (GnmCell *cell, GnmRenderedValue *rv,
294 		    int col_width,
295 		    gboolean inhibit_overflow)
296 {
297 	gint dummy_x, dummy_y;
298 	GOColor dummy_fore_color;
299 	int dummy_h_center = -1;  /* Affects position only.  */
300 	int dummy_height = 1;  /* Unhandled.  */
301 	gboolean might_overflow;
302 	GnmRenderedValue *cell_rv;
303 
304 	cell_rv = gnm_cell_get_rendered_value (cell);
305 
306 	if (!rv)
307 		rv = cell_rv;
308 
309 	if (rv->drawn)
310 		return;
311 
312 	if (rv->variable_width && rv == cell_rv &&
313 	    !go_format_is_general (gnm_cell_get_format (cell))) {
314 		/*
315 		 * We get here when entering a new value in a cell
316 		 * with a format that has a filler, for example
317 		 * one of the standard accounting formats.  We need
318 		 * to rerender such that the filler gets a chance
319 		 * to expand.
320 		 */
321 		rv = gnm_cell_render_value (cell, TRUE);
322 	}
323 
324 	might_overflow = rv->might_overflow;
325 	if (inhibit_overflow) rv->might_overflow = FALSE;
326 	cell_calc_layout (cell, rv, -1, col_width * PANGO_SCALE,
327 		dummy_height, dummy_h_center, &dummy_fore_color,
328 		&dummy_x, &dummy_y);
329 	rv->might_overflow = might_overflow;
330 }
331 
332 
333 static void
cell_draw_extension_mark_bottom(cairo_t * cr,int x1,int y1,int height,int h_center)334 cell_draw_extension_mark_bottom (cairo_t *cr, int x1, int y1, int height, int h_center)
335 {
336 	cairo_set_source_rgba (cr, 1, 0, 0, 0.7);
337 	cairo_new_path (cr);
338 	cairo_move_to (cr, x1 + h_center, y1 + height);
339 	cairo_rel_line_to (cr, -3, -3);
340 	cairo_rel_line_to (cr, 6, 0);
341 	cairo_close_path (cr);
342 	cairo_fill (cr);
343 }
344 
345 static void
cell_draw_extension_mark_left(cairo_t * cr,int x1,int y1,int height)346 cell_draw_extension_mark_left (cairo_t *cr, int x1, int y1, int height)
347 {
348 	cairo_set_source_rgba (cr, 1, 0, 0, 0.7);
349 	cairo_new_path (cr);
350 	cairo_move_to (cr, x1, y1 + height/2);
351 	cairo_rel_line_to (cr, 3, -3);
352 	cairo_rel_line_to (cr, 0, 6);
353 	cairo_close_path (cr);
354 	cairo_fill (cr);
355 }
356 
357 static void
cell_draw_extension_mark_right(cairo_t * cr,int x1,int y1,int width,int height)358 cell_draw_extension_mark_right (cairo_t *cr, int x1, int y1, int width, int height)
359 {
360 	cairo_set_source_rgba (cr, 1, 0, 0, 0.7);
361 	cairo_new_path (cr);
362 	cairo_move_to (cr, x1 + width, y1 + height/2);
363 	cairo_rel_line_to (cr, -3, -3);
364 	cairo_rel_line_to (cr, 0, 6);
365 	cairo_close_path (cr);
366 	cairo_fill (cr);
367 
368 }
369 
370 
371 static void
cell_draw_h_extension_markers(cairo_t * cr,GnmRenderedValue * rv,int x1,int y1,int width,int height)372 cell_draw_h_extension_markers (cairo_t *cr, GnmRenderedValue *rv,
373 			       int x1, int y1,
374 			       int width, int height)
375 {
376 	switch (rv->effective_halign) {
377 	case GNM_HALIGN_GENERAL:
378 	case GNM_HALIGN_LEFT:
379 		cell_draw_extension_mark_right (cr, x1, y1, width, height);
380 		break;
381 	case GNM_HALIGN_RIGHT:
382 		cell_draw_extension_mark_left (cr, x1, y1, height);
383 		break;
384 	case GNM_HALIGN_DISTRIBUTED:
385 	case GNM_HALIGN_CENTER:
386 	case GNM_HALIGN_CENTER_ACROSS_SELECTION:
387 		cell_draw_extension_mark_right (cr, x1, y1, width, height);
388 		cell_draw_extension_mark_left (cr, x1, y1, height);
389 		break;
390 	case GNM_HALIGN_FILL:
391 	default:
392 		break;
393 	}
394 }
395 
396 static void
cell_draw_v_extension_markers(cairo_t * cr,int x1,int y1,int width,int height,int h_center)397 cell_draw_v_extension_markers (cairo_t *cr,
398 			       int x1, int y1,
399 			       int width, int height,
400 			       int h_center)
401 {
402 	if (h_center == -1)
403 		h_center = width / 2;
404 	cell_draw_extension_mark_bottom (cr, x1, y1, height, h_center);
405 }
406 
407 /**
408  * cell_draw:
409  * @cell: #GnmCell const
410  * @cr: #cairo_t
411  * @x:
412  * @y:
413  * @width: including margins and leading grid line
414  * @height: including margins and leading grid line
415  * @h_center:
416  * @show_extension_markers:
417  **/
418 void
cell_draw(GnmCell const * cell,cairo_t * cr,int x1,int y1,int width,int height,int h_center,gboolean show_extension_markers)419 cell_draw (GnmCell const *cell, cairo_t *cr,
420 	   int x1, int y1, int width, int height, int h_center,
421 	   gboolean show_extension_markers)
422 {
423 	GOColor fore_color;
424 	gint x;
425 	gint y;
426 	GnmRenderedValue *rv;
427 
428 	/* Get the sizes exclusive of margins and grids */
429 	/* Note: +1 because size_pixels includes leading gridline.  */
430 	height -= GNM_ROW_MARGIN + GNM_ROW_MARGIN + 1;
431 	width  -= GNM_COL_MARGIN + GNM_COL_MARGIN + 1;
432 
433 	if (h_center > GNM_COL_MARGIN)
434 		h_center = h_center - GNM_COL_MARGIN - 1 + (h_center % 2);
435 
436 	rv = gnm_cell_fetch_rendered_value (cell, TRUE);
437 
438 	if (cell_calc_layout (cell, rv, +1,
439 			      width * PANGO_SCALE,
440 			      height * PANGO_SCALE,
441 			      h_center == -1 ? -1 : (h_center * PANGO_SCALE),
442 			      &fore_color, &x, &y)) {
443 
444 		cairo_save (cr);
445 
446 		/*
447 		 * HACK -- do not clip rotated cells.  This gives an
448 		 * approximation to the right effect.  (The right way
449 		 * would be to create a proper cellspan type.)
450 		 */
451 		if (!rv->rotation) {
452 			cairo_new_path (cr);
453 			/* +1 to get past left grid-line.  */
454 			cairo_rectangle (cr, x1 + 1 + GNM_COL_MARGIN,
455 					 y1 + 1 + GNM_ROW_MARGIN,
456 					 width, height);
457 
458 			cairo_clip (cr);
459 		}
460 
461 		/* See http://bugzilla.gnome.org/show_bug.cgi?id=105322 */
462 		cairo_set_source_rgba (cr, GO_COLOR_TO_CAIRO (fore_color));
463 
464 		if (rv->rotation) {
465 			GnmRenderedRotatedValue *rrv = (GnmRenderedRotatedValue *)rv;
466 			struct GnmRenderedRotatedValueInfo const *li = rrv->lines;
467 			GSList *lines;
468 
469 			for (lines = pango_layout_get_lines (rv->layout);
470 			     lines;
471 			     lines = lines->next, li++) {
472 				cairo_save (cr);
473 				cairo_move_to (cr, x1 + PANGO_PIXELS (x + li->dx), y1 + PANGO_PIXELS (y + li->dy));
474 				cairo_rotate (cr, rv->rotation * (-M_PI / 180));
475 				pango_cairo_show_layout_line (cr, lines->data);
476 				cairo_restore (cr);
477 			}
478 		} else {
479 			cairo_save (cr);
480 			cairo_translate (cr, x1 + PANGO_PIXELS (x), y1 + PANGO_PIXELS (y));
481 			pango_cairo_show_layout (cr, rv->layout);
482 			cairo_restore (cr);
483 
484 			if (show_extension_markers &&
485 			    width < PANGO_PIXELS (rv->layout_natural_width)) {
486 				cairo_save (cr);
487 				cell_draw_h_extension_markers
488 					(cr, rv,
489 					 x1 + 1 + GNM_COL_MARGIN,
490 					 y1 + 1 + GNM_ROW_MARGIN,
491 					 width, height);
492 				cairo_restore (cr);
493 			}
494 
495 			if (show_extension_markers &&
496 			    height < PANGO_PIXELS (rv->layout_natural_height)) {
497 				cairo_save (cr);
498 				cell_draw_v_extension_markers
499 					(cr, x1 + 1 + GNM_COL_MARGIN,
500 					 y1 + 1 + GNM_ROW_MARGIN,
501 					 width, height, h_center);
502 				cairo_restore (cr);
503 			}
504 		}
505 		cairo_restore (cr);
506 	}
507 }
508