rendered-value.c 14.9 KB
Newer Older
Jody Goldberg's avatar
Jody Goldberg committed
1
/* vim: set sw=8: */
2 3 4 5 6

/*
 * rendered-value.c: Management & utility routines for formated
 *     colored text.
 *
7
 * Copyright (C) 2000, 2001 Jody Goldberg (jody@gnome.org)
8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software
21
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
22 23
 * USA
 */
24 25
#include <gnumeric-config.h>
#include "gnumeric.h"
26
#include "rendered-value.h"
27

28 29
#include "expr.h"
#include "cell.h"
30
#include "style.h"
31
#include "style-color.h"
32
#include "style-font.h"
33
#include "style-border.h"
34
#include "sheet.h"
35
#include "sheet-merge.h"
36
#include "gnm-format.h"
37
#include "value.h"
38
#include "workbook.h"
39

Jody Goldberg's avatar
Jody Goldberg committed
40
#include <string.h>
Morten Welinder's avatar
Morten Welinder committed
41
#include <goffice/utils/go-glib-extras.h>
42
#include <goffice/utils/go-font.h>
43

44
#undef DEBUG_BOUNDING_BOX
45

46 47 48 49 50
#ifndef USE_RV_POOLS
#define USE_RV_POOLS 1
#endif

#if USE_RV_POOLS
51
/* Memory pool for GnmRenderedValue.  */
52
static GOMemChunk *rendered_value_pool;
53
static GOMemChunk *rendered_rotated_value_pool;
54 55
#define CHUNK_ALLOC(T,p) ((T*)go_mem_chunk_alloc (p))
#define CHUNK_FREE(p,v) go_mem_chunk_free ((p), (v))
56 57 58 59 60
#else
#define CHUNK_ALLOC(T,c) g_new (T,1)
#define CHUNK_FREE(p,v) g_free ((v))
#endif

Morten Welinder's avatar
Morten Welinder committed
61

62
static guint16
63
calc_indent (PangoContext *context, const GnmStyle *mstyle, double zoom)
64 65
{
	int indent = 0;
66
	if (gnm_style_is_element_set (mstyle, MSTYLE_INDENT)) {
67 68
		int n = gnm_style_get_indent (mstyle);
		if (n) {
69
			GnmFont *style_font = gnm_style_get_font (mstyle, context, zoom);
70
			indent = PANGO_PIXELS (n * style_font->go.metrics->avg_digit_width);
71 72 73 74 75
		}
	}
	return MIN (indent, 65535);
}

76

77
void
Morten Welinder's avatar
Morten Welinder committed
78
gnm_rendered_value_remeasure (GnmRenderedValue *rv)
79
{
Morten Welinder's avatar
Morten Welinder committed
80
	if (rv->rotation) {
81
		GnmRenderedRotatedValue *rrv = (GnmRenderedRotatedValue *)rv;
82
		PangoContext *context = pango_layout_get_context (rv->layout);
83 84 85
		double sin_a, abs_sin_a, cos_a;
		int sdx = 0;
		int x0 = 0, x1 = 0;
86
		PangoLayoutIter *iter;
87 88
		int l = 0;
		int lwidth;
89

90 91 92 93
		sin_a = rrv->rotmat.xy;
		abs_sin_a = fabs (sin_a);
		cos_a = rrv->rotmat.xx;
		pango_context_set_matrix (context, &rrv->rotmat);
94
		pango_layout_context_changed (rv->layout);
95

96
		rrv->linecount = pango_layout_get_line_count (rv->layout);
Morten Welinder's avatar
Morten Welinder committed
97
		rrv->lines = g_new (struct GnmRenderedRotatedValueInfo, rrv->linecount);
98 99
		pango_layout_get_size (rv->layout, &lwidth, NULL);

100 101 102 103 104
		rv->layout_natural_height = 0;

		iter = pango_layout_get_iter (rv->layout);
		do {
			PangoRectangle logical;
105 106
			int x, dx, dy, indent;
			int h, ytop, ybot, baseline;
107 108

			pango_layout_iter_get_line_extents (iter, NULL, &logical);
109 110
			pango_layout_iter_get_line_yrange (iter, &ytop, &ybot);
			baseline = pango_layout_iter_get_baseline (iter);
Morten Welinder's avatar
Morten Welinder committed
111
			indent = logical.x;
112 113 114 115 116 117 118
			if (sin_a < 0)
				indent -= lwidth;

			if (l == 0 && rv->noborders)
				sdx = (int)(baseline * sin_a - ybot / sin_a);
			dx = sdx + (int)(ybot / sin_a + indent * cos_a);
			dy = (int)((baseline - ybot) * cos_a - indent * sin_a);
119

120 121
			rrv->lines[l].dx = dx;
			rrv->lines[l].dy = dy;
122

123 124 125
			/* Left edge.  */
			x = dx - (int)((baseline - ytop) * sin_a);
			x0 = MIN (x0, x);
126

127 128 129 130 131 132 133 134 135
			/* Right edge.  */
			x = dx + (int)(logical.width * cos_a + (ybot - baseline) * sin_a);
			x1 = MAX (x1, x);

			h = logical.width * abs_sin_a + logical.height * cos_a;
			if (h > rv->layout_natural_height)
				rv->layout_natural_height = h;

			l++;
136 137
		} while (pango_layout_iter_next_line (iter));
		pango_layout_iter_free (iter);
138 139 140 141 142 143 144 145 146 147

		rv->layout_natural_width = x1 - x0;
		if (sin_a < 0) {
			int dx = rv->layout_natural_width;
			for (l = 0; l < rrv->linecount; l++)
				rrv->lines[l].dx += dx;
		}
		for (l = 0; l < rrv->linecount; l++)
			rrv->lines[l].dy += rv->layout_natural_height;

148 149 150 151
#if 0
		g_print ("Natural size: %d x %d\n", rv->layout_natural_width, rv->layout_natural_height);
#endif

152 153
		pango_context_set_matrix (context, NULL);
		pango_layout_context_changed (rv->layout);
154
	} else
155 156 157
		pango_layout_get_size (rv->layout,
				       &rv->layout_natural_width,
				       &rv->layout_natural_height);
158 159 160
}


161
/**
Morten Welinder's avatar
Morten Welinder committed
162
 * gnm_rendered_value_new:
163 164
 * @cell:   The cell
 * @mstyle: The mstyle associated with the cell
165
 * @allow_variable_width : Allow format to depend on column width.
166 167 168 169
 * @context: A pango context for layout.
 *
 * Formats the value of the cell according to the format style given in @mstyle
 *
170
 * Return value: a new GnmRenderedValue
171
 **/
172
GnmRenderedValue *
Morten Welinder's avatar
Morten Welinder committed
173
gnm_rendered_value_new (GnmCell *cell, GnmStyle const *mstyle,
174 175 176
			gboolean allow_variable_width,
			PangoContext *context,
			double zoom)
177
{
178
	GnmRenderedValue	*res;
179
	GOColor		 fore;
180 181
	PangoLayout     *layout;
	PangoAttrList   *attrs;
182
	int              rotation;
183 184
	Sheet const     *sheet;
	gboolean         displayed_formula;
185 186 187 188

	g_return_val_if_fail (cell != NULL, NULL);
	g_return_val_if_fail (cell->value != NULL, NULL);
	g_return_val_if_fail (context != NULL, NULL);
189

Morten Welinder's avatar
Morten Welinder committed
190
	/* sheet->workbook can be NULL when called from preview-grid.c  */
191 192
	sheet = cell->base.sheet;

193
	displayed_formula =
194
		gnm_cell_has_expr (cell) && sheet->display_formulas;
195

196 197 198
	/* Special handling for manual recalc.
	 * If a cell has a new expression and something tries to display it we
	 * need to recalc the value */
199 200
	if (cell->base.flags & GNM_CELL_HAS_NEW_EXPR) {
		gnm_cell_eval (cell);
201 202
	}

203
	rotation = gnm_style_get_rotation (mstyle);
204 205
	if (rotation) {
		static PangoMatrix const id = PANGO_MATRIX_INIT;
206
		GnmRenderedRotatedValue *rrv;
207 208
		GnmStyleElement e;

209
		rrv = CHUNK_ALLOC (GnmRenderedRotatedValue, rendered_rotated_value_pool);
210 211 212 213 214 215 216 217 218 219 220
		res = &rrv->rv;

		rrv->rotmat = id;
		pango_matrix_rotate (&rrv->rotmat, rotation);
		rrv->linecount = 0;
		rrv->lines = NULL;

		res->noborders = TRUE;
		/* Deliberately exclude diagonals.  */
		for (e = MSTYLE_BORDER_TOP; e <= MSTYLE_BORDER_RIGHT; e++) {
			GnmBorder *b = gnm_style_get_border (mstyle, e);
Morten Welinder's avatar
Morten Welinder committed
221
			if (!gnm_style_border_is_blank (b)) {
222 223 224 225 226
				res->noborders = FALSE;
				break;
			}
		}
	} else {
227
		res = CHUNK_ALLOC (GnmRenderedValue, rendered_value_pool);
228 229 230
		res->noborders = FALSE;
	}
	res->rotation = rotation;
231

232
	res->layout = layout = pango_layout_new (context);
233 234 235
	res->hfilled = FALSE;
	res->vfilled = FALSE;
	res->variable_width = FALSE;
236
	res->drawn = FALSE;
237 238 239 240 241

	/* ---------------------------------------- */

	attrs = gnm_style_get_pango_attrs (mstyle, context, zoom);
#ifdef DEBUG_BOUNDING_BOX
242
	/* Make the whole layout end up with a red background.  */
243 244 245 246 247 248 249 250 251 252 253 254 255
	{
		PangoAttrList *new_attrs = pango_attr_list_copy (attrs);
		PangoAttribute *attr;

		pango_attr_list_unref (attrs);
		attrs = new_attrs;
		attr = pango_attr_background_new (0xffff, 0, 0);
		attr->start_index = 0;
		attr->end_index = -1;
		pango_attr_list_insert (attrs, attr);
	}
#endif

256
	/* Add markup.  */
257 258 259 260 261
	{
		GOFormat const *fmt = VALUE_FMT (cell->value);
		if (fmt != NULL && go_format_is_markup (fmt)) {
			PangoAttrList *orig = attrs;
			attrs = pango_attr_list_copy (attrs);
262 263 264
			pango_attr_list_splice (attrs,
						go_format_get_markup (fmt),
						0, 0);
265 266 267 268 269 270 271 272
			pango_attr_list_unref (orig);
		}
	}
	pango_layout_set_attributes (res->layout, attrs);
	pango_attr_list_unref (attrs);

	/* ---------------------------------------- */

273 274 275 276 277 278 279 280 281
	/*
	 * Excel actually does something rather weird.  Just like
	 * everywhere else we just see displayed formulas as
	 * strings.
	 */
	res->wrap_text =
		(VALUE_IS_STRING (cell->value) || displayed_formula) &&
		gnm_style_get_effective_wrap_text (mstyle);

282
	res->effective_valign = gnm_style_get_align_v (mstyle);
283 284
	res->effective_halign = style_default_halign (mstyle, cell);
	res->indent_left = res->indent_right = 0;
285 286
	switch (res->effective_halign) {
	case HALIGN_LEFT:
287
		res->indent_left = calc_indent (context, mstyle, zoom);
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
		pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
		break;

	case HALIGN_JUSTIFY:
		/*
		 * The code here should work, but pango doesn't:
		 * http://bugzilla.gnome.org/show_bug.cgi?id=64538
		 */
		pango_layout_set_justify (layout, TRUE);
		pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
		break;

	case HALIGN_FILL:
		/*
		 * A bit weird, but seems to match XL.  The effect is to
		 * render newlines as visible characters.
		 */
		pango_layout_set_single_paragraph_mode (layout, TRUE);
		pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
307
		res->variable_width = TRUE;
308 309 310
		break;

	case HALIGN_RIGHT:
311
		res->indent_right = calc_indent (context, mstyle, zoom);
312 313 314
		pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
		break;

315
	case HALIGN_DISTRIBUTED:
316 317 318 319 320 321 322 323 324
	case HALIGN_CENTER:
	case HALIGN_CENTER_ACROSS_SELECTION:
		pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
		break;

	default:
		g_warning ("Line justification style not supported.");
	}

325 326 327 328
	/* ---------------------------------------- */

	res->numeric_overflow = FALSE;

329
	if (displayed_formula) {
330 331 332 333 334 335 336 337 338
		GnmParsePos pp;
		GString *str = g_string_new ("=");
		gnm_expr_top_as_gstring (str, cell->base.texpr,
					 parse_pos_init_cell (&pp, cell),
					 sheet->convs);
		pango_layout_set_text (layout, str->str, str->len);
		g_string_free (str, TRUE);
		fore = 0;
		res->might_overflow = FALSE;
339
	} else if (sheet->hide_zero && gnm_cell_is_zero (cell)) {
340 341 342 343 344 345
		pango_layout_set_text (layout, "", 0);
		fore = 0;
		res->might_overflow = FALSE;
	} else {
		int col_width = -1;
		GOFormat *format = gnm_style_get_format (mstyle);
Morten Welinder's avatar
Morten Welinder committed
346
		GODateConventions const *date_conv = sheet->workbook
347 348 349 350 351
			? workbook_date_conv (sheet->workbook)
			: NULL;
		GnmFont *font = gnm_style_get_font (mstyle, context, zoom);
		gboolean is_rotated = (rotation != 0);
		gboolean variable;
Morten Welinder's avatar
Morten Welinder committed
352
		GOFormatNumberError err;
353 354 355 356 357 358 359 360 361 362 363

		if (go_format_is_general (format) && VALUE_FMT (cell->value))
			format = VALUE_FMT (cell->value);

		res->might_overflow = !is_rotated &&
			VALUE_IS_FLOAT (cell->value);

		if (go_format_is_general (format))
			variable = !is_rotated && VALUE_IS_FLOAT (cell->value);
		else
			variable = !is_rotated && go_format_is_var_width (format);
Morten Welinder's avatar
Morten Welinder committed
364

365 366 367 368 369 370
		if (variable)
			res->variable_width = TRUE;

		if (variable && allow_variable_width) {
			int col_width_pixels;

371
			if (gnm_cell_is_merged (cell)) {
372
				GnmRange const *merged =
Morten Welinder's avatar
Morten Welinder committed
373
					gnm_sheet_merge_is_corner (sheet, &cell->pos);
374 375 376 377

				col_width_pixels = sheet_col_get_distance_pixels
					(sheet,
					 merged->start.col, merged->end.col + 1);
378 379 380 381 382
			} else {
				ColRowInfo const *ci = sheet_col_get_info (sheet, cell->pos.col);
				col_width_pixels = ci->size_pixels;
			}
			col_width_pixels -= (GNM_COL_MARGIN + GNM_COL_MARGIN + 1);
383 384 385
			col_width = col_width_pixels * PANGO_SCALE;
		}

Morten Welinder's avatar
Morten Welinder committed
386 387 388 389
		err = gnm_format_layout (layout, font->go.metrics, format,
					 cell->value,
					 &fore, col_width, date_conv, TRUE);

390 391 392 393 394 395 396 397 398
		switch (err) {
		case GO_FORMAT_NUMBER_DATE_ERROR:
			pango_layout_set_text (layout, "", -1);
			pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
			res->numeric_overflow = TRUE;
			res->effective_halign = HALIGN_LEFT;
			break;
		default:
			break;
Morten Welinder's avatar
Morten Welinder committed
399
		}
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
	}

	/* ---------------------------------------- */

	/*
	 * We store the foreground color separately because
	 * 1. It is [used to be?] slow to store it as an attribute, see
	 *    http://bugzilla.gnome.org/show_bug.cgi?id=105322
	 * 2. This way we get to share the attribute list.
	 */
	if (0 == fore) {
		GnmColor const *c = gnm_style_get_font_color (mstyle);
		res->go_fore_color = c->go_color;
	} else
		res->go_fore_color = fore;

Morten Welinder's avatar
Morten Welinder committed
416
	gnm_rendered_value_remeasure (res);
417

418 419 420 421
	return res;
}

void
Morten Welinder's avatar
Morten Welinder committed
422
gnm_rendered_value_destroy (GnmRenderedValue *rv)
423
{
424 425 426
	if (rv->layout) {
		g_object_unref (G_OBJECT (rv->layout));
		rv->layout = NULL;
427 428
	}

429
	if (rv->rotation) {
430
		GnmRenderedRotatedValue *rrv = (GnmRenderedRotatedValue *)rv;
431 432 433 434
		g_free (rrv->lines);
		CHUNK_FREE (rendered_rotated_value_pool, rrv);
	} else
		CHUNK_FREE (rendered_value_pool, rv);
435 436
}

437
GnmRenderedValue *
Morten Welinder's avatar
Morten Welinder committed
438
gnm_rendered_value_recontext (GnmRenderedValue *rv, PangoContext *context)
439
{
440
	GnmRenderedValue *res;
441 442
	PangoLayout *layout, *olayout;

443
	if (rv->rotation) {
444 445 446
		GnmRenderedRotatedValue *rres =
			CHUNK_ALLOC (GnmRenderedRotatedValue, rendered_rotated_value_pool);
		res = (GnmRenderedValue *)rres;
447

448
		*rres = *(GnmRenderedRotatedValue *)rv;
449
		rres->lines = g_memdup (rres->lines,
Morten Welinder's avatar
Morten Welinder committed
450
					rres->linecount * sizeof (struct GnmRenderedRotatedValueInfo));
451
	} else {
452
		res = CHUNK_ALLOC (GnmRenderedValue, rendered_value_pool);
453 454
		*res = *rv;
	}
455 456 457 458 459 460 461 462 463 464 465

	res->layout = layout = pango_layout_new (context);
	olayout = rv->layout;

	pango_layout_set_text (layout, pango_layout_get_text (olayout), -1);
	pango_layout_set_alignment (layout, pango_layout_get_alignment (olayout));
	pango_layout_set_attributes (layout, pango_layout_get_attributes (olayout));
	pango_layout_set_single_paragraph_mode (layout, pango_layout_get_single_paragraph_mode (olayout));
	pango_layout_set_justify (layout, pango_layout_get_justify (olayout));
	pango_layout_set_width (layout, pango_layout_get_width (olayout));
	pango_layout_set_spacing (layout, pango_layout_get_spacing (olayout));
466
	pango_layout_set_wrap (layout, pango_layout_get_wrap (olayout));
467 468 469
	pango_layout_set_indent (layout, pango_layout_get_indent (olayout));
	pango_layout_set_auto_dir (layout, pango_layout_get_auto_dir (olayout));
	pango_layout_set_ellipsize (layout, pango_layout_get_ellipsize (olayout));
470
	pango_layout_set_font_description (layout, pango_layout_get_font_description (olayout));
471 472
	// ignore tabs

473 474 475 476 477 478
	/*
	 * We really want to keep the line breaks, but currently pango
	 * does not support that.
	 */
	if (pango_layout_get_line_count (olayout) == 1) {
		if (pango_layout_get_line_count (layout) > 1) {
479
			res->wrap_text = FALSE;
480 481 482 483
			pango_layout_set_width (layout, -1);
		}
	}

Morten Welinder's avatar
Morten Welinder committed
484
	gnm_rendered_value_remeasure (res);
485 486 487 488
	return res;
}


489 490
/* Return the value as a single string without format infomation.
 */
491
char const *
Morten Welinder's avatar
Morten Welinder committed
492
gnm_rendered_value_get_text (GnmRenderedValue const *rv)
493
{
Andreas J. Guelzow's avatar
Andreas J. Guelzow committed
494
	g_return_val_if_fail (rv != NULL, "ERROR");
495
	return pango_layout_get_text (rv->layout);
496 497
}

498
void
Morten Welinder's avatar
Morten Welinder committed
499
gnm_rendered_value_init (void)
500 501 502
{
#if USE_RV_POOLS
	rendered_value_pool =
503
		go_mem_chunk_new ("rendered value pool",
504
				  sizeof (GnmRenderedValue),
Morten Welinder's avatar
Morten Welinder committed
505
				  16 * 1024 - 128);
506 507
	rendered_rotated_value_pool =
		go_mem_chunk_new ("rendered rotated value pool",
508
				  sizeof (GnmRenderedRotatedValue),
509
				  16 * 1024 - 128);
510 511 512 513 514
#endif
}

#if USE_RV_POOLS
static void
515
cb_rendered_value_pool_leak (gpointer data, G_GNUC_UNUSED gpointer user)
516
{
517
	GnmRenderedValue *rendered_value = data;
518 519
	g_printerr ("Leaking rendered value at %p [%s].\n",
		    rendered_value, pango_layout_get_text (rendered_value->layout));
520 521 522 523
}
#endif

void
Morten Welinder's avatar
Morten Welinder committed
524
gnm_rendered_value_shutdown (void)
525 526
{
#if USE_RV_POOLS
527 528
	go_mem_chunk_foreach_leak (rendered_value_pool, cb_rendered_value_pool_leak, NULL);
	go_mem_chunk_destroy (rendered_value_pool, FALSE);
529
	rendered_value_pool = NULL;
530 531 532 533

	go_mem_chunk_foreach_leak (rendered_rotated_value_pool, cb_rendered_value_pool_leak, NULL);
	go_mem_chunk_destroy (rendered_rotated_value_pool, FALSE);
	rendered_rotated_value_pool = NULL;
534 535
#endif
}