Commit 8b1641f1 authored by Morten Welinder's avatar Morten Welinder Committed by Morten Welinder

New font metrics setup.

2006-04-26  Morten Welinder  <terra@gnome.org>

	* src/style.c (gnm_font_metrics_new, gnm_font_metrics_free,
	gnm_font_metrics_unit): New font metrics setup.

	* src/sheet.c (sheet_col_get_distance_pixels): New function.

	* src/cell.c (cell_get_render_color, cell_get_entered_text,
	cell_rendered_height, cell_rendered_width, cell_rendered_offset):
	Moved from src/rendered-value.c.

	* src/gnm-format.c (gnm_format_layout, gnm_format_general): New
	functions.
	(format_value_gstring): Use gnm_format_general.

	* src/rendered-value.c (rendered_value_render): Calculate column
	widths in pixels and use gnm_format_layout to get accurate
	rendering.  Take layout instead of string.
	(rendered_value_new): Set attributes before the call to
	rendered_value_render.
parent 1907653a
2006-04-26 Morten Welinder <terra@gnome.org>
* src/style.c (gnm_font_metrics_new, gnm_font_metrics_free,
gnm_font_metrics_unit): New font metrics setup.
* src/sheet.c (sheet_col_get_distance_pixels): New function.
* src/cell.c (cell_get_render_color, cell_get_entered_text,
cell_rendered_height, cell_rendered_width, cell_rendered_offset):
Moved from src/rendered-value.c.
* src/gnm-format.c (gnm_format_layout, gnm_format_general): New
functions.
(format_value_gstring): Use gnm_format_general.
* src/rendered-value.c (rendered_value_render): Calculate column
widths in pixels and use gnm_format_layout to get accurate
rendering. Take layout instead of string.
(rendered_value_new): Set attributes before the call to
rendered_value_render.
* src/hlink.c (gnm_hlink_cur_wb_activate): Prevent a critical.
#339793.
......
......@@ -185,6 +185,8 @@ Morten:
* Improve stf import's keyboard navigation.
* Fix applix import issue. [#339190]
* Fix critical. [#339793]
* Make "General" format pixel-perfect.
* Fix zoomed rendering issue. [#310492]
Jon Kåre:
* Hand clipboard off to clipboard manager when exiting.
......
2006-04-26 Morten Welinder <terra@gnome.org>
* src/style.c (gnm_font_metrics_new, gnm_font_metrics_free,
gnm_font_metrics_unit): New font metrics setup.
* src/sheet.c (sheet_col_get_distance_pixels): New function.
* src/cell.c (cell_get_render_color, cell_get_entered_text,
cell_rendered_height, cell_rendered_width, cell_rendered_offset):
Moved from src/rendered-value.c.
* src/gnm-format.c (gnm_format_layout, gnm_format_general): New
functions.
(format_value_gstring): Use gnm_format_general.
* src/rendered-value.c (rendered_value_render): Calculate column
widths in pixels and use gnm_format_layout to get accurate
rendering. Take layout instead of string.
(rendered_value_new): Set attributes before the call to
rendered_value_render.
* src/hlink.c (gnm_hlink_cur_wb_activate): Prevent a critical.
#339793.
......
......@@ -20,11 +20,12 @@
#include "str.h"
#include "style.h"
#include "ranges.h"
#include "gnm-format.h"
#include "number-match.h"
#include "sheet-object-cell-comment.h"
#include "sheet-style.h"
#include "parse-util.h"
#include <goffice/utils/go-glib-extras.h>
#include <goffice/utils/go-format.h>
#define USE_CELL_POOL 1
......@@ -600,6 +601,117 @@ cell_get_rendered_text (GnmCell *cell)
return g_strdup (rendered_value_get_text (cell->rendered_value));
}
/**
* cell_get_render_color:
* @cell: the cell from which we want to pull the color from
*
* The returned value is a pointer to a PangoColor describing
* the foreground colour.
*/
GOColor
cell_get_render_color (GnmCell const *cell)
{
g_return_val_if_fail (cell != NULL, 0);
/* A precursor to just in time rendering Ick! */
if (cell->rendered_value == NULL)
cell_render_value ((GnmCell *)cell, TRUE);
return cell->rendered_value->go_fore_color;
}
/**
* cell_get_entered_text:
* @cell: the cell from which we want to pull the content from
*
* This returns a g_malloc()ed region of memory with a text representation
* of the cell contents.
*
* This will return a text expression if the cell contains a formula, or
* a string representation of the value.
*/
char *
cell_get_entered_text (GnmCell const *cell)
{
g_return_val_if_fail (cell != NULL, NULL);
if (cell_has_expr (cell)) {
GnmParsePos pp;
GString *res = g_string_new ("=");
gnm_expr_top_as_gstring (res, cell->base.texpr,
parse_pos_init_cell (&pp, cell),
cell->base.sheet->convs);
return g_string_free (res, FALSE);
}
if (cell->value != NULL) {
if (VALUE_IS_STRING (cell->value)) {
/* Try to be reasonably smart about adding a leading quote */
char const *tmp = cell->value->v_str.val->str;
if (tmp[0] != '\'' && !gnm_expr_char_start_p (tmp)) {
GnmValue *val = format_match_number (tmp,
cell_get_format (cell),
workbook_date_conv (cell->base.sheet->workbook));
if (val == NULL)
return g_strdup (tmp);
value_release (val);
}
return g_strconcat ("\'", tmp, NULL);
}
return format_value (NULL, cell->value, NULL, -1,
workbook_date_conv (cell->base.sheet->workbook));
}
g_warning ("A cell with no expression, and no value ??");
return g_strdup ("<ERROR>");
}
/*
* Return the height of the rendered layout after rotation.
*/
int
cell_rendered_height (GnmCell const *cell)
{
const RenderedValue *rv;
g_return_val_if_fail (cell != NULL, 0);
rv = cell->rendered_value;
if (!rv)
return 0;
return PANGO_PIXELS (cell->rendered_value->layout_natural_height);
}
/*
* Return the width of the rendered layout after rotation.
*/
int
cell_rendered_width (GnmCell const *cell)
{
const RenderedValue *rv;
g_return_val_if_fail (cell != NULL, 0);
rv = cell->rendered_value;
if (!rv)
return 0;
return PANGO_PIXELS (cell->rendered_value->layout_natural_width);
}
int
cell_rendered_offset (GnmCell const * cell)
{
if (!cell || !cell->rendered_value)
return 0;
return (cell->rendered_value->indent_left +
cell->rendered_value->indent_right);
}
GnmStyle *
cell_get_mstyle (GnmCell const *cell)
......
......@@ -24,12 +24,15 @@
#include <gnumeric-config.h>
#include "gnm-format.h"
#include "value.h"
#include "str.h"
#include <goffice/utils/format-impl.h>
#include <goffice/utils/go-glib-extras.h>
#include <glib/gi18n.h>
#include <string.h>
#include <stdio.h>
#include <style-font.h>
#undef DEBUG_GENERAL
static gboolean
gnm_style_format_condition (GOFormatElement const *entry, GnmValue const *value)
......@@ -62,40 +65,30 @@ gnm_style_format_condition (GOFormatElement const *entry, GnmValue const *value)
}
}
/*
* Returns NULL when the value should be formated as text
*/
void
format_value_gstring (GString *result, GOFormat const *format,
GnmValue const *value, GOColor *go_color,
double col_width, GODateConventions const *date_conv)
static GOFormatElement const *
find_entry (GOFormat const *format, GnmValue const *value,
GOColor *go_color, gboolean *need_abs, gboolean *empty)
{
GOFormatElement const *entry = NULL; /* default to General */
GSList *list;
gboolean need_abs = FALSE;
GOFormatElement const *entry = NULL;
if (go_color)
*go_color = 0;
g_return_if_fail (value != NULL);
if (format == NULL)
format = VALUE_FMT (value);
/* Use top left corner of an array result. This will not work for
* ranges because we dont't have a location */
if (value->type == VALUE_ARRAY)
value = value_area_fetch_x_y (value, 0, 0, NULL);
if (format) {
for (list = format->entries; list; list = list->next)
if (gnm_style_format_condition (entry = list->data, value))
GSList *list;
for (list = format->entries; list; list = list->next) {
entry = list->data;
if (gnm_style_format_condition (entry, value))
break;
}
if (entry != NULL) {
/* Empty formats should be ignored */
if (entry->format[0] == '\0')
return;
if (entry->format[0] == '\0') {
*empty = TRUE;
return entry;
}
if (go_color && entry->go_color != 0)
*go_color = entry->go_color;
......@@ -112,61 +105,418 @@ format_value_gstring (GString *result, GOFormat const *format,
} else if (strstr (entry->format, "General") != NULL)
entry = NULL;
}
/* More than one format? -- abs the value. */
need_abs = entry && format->entries->next;
}
switch (value->type) {
case VALUE_EMPTY:
return;
/* More than one format? -- abs the value. */
*need_abs = entry && format->entries->next;
*empty = FALSE;
case VALUE_BOOLEAN:
g_string_append (result, format_boolean (value->v_bool.val));
return;
return entry;
}
case VALUE_FLOAT: {
gnm_float val = value_get_as_float (value);
if (!gnm_finite (val)) {
g_string_append (result, value_error_name (GNM_ERROR_VALUE, TRUE));
return;
}
static const char *
format_nonnumber (const GnmValue *value)
{
switch (value->type) {
case VALUE_EMPTY:
return "";
if (need_abs)
val = gnm_abs (val);
case VALUE_BOOLEAN:
return format_boolean (value->v_bool.val);
if (entry == NULL) {
if (INT_MAX >= val && val >= INT_MIN && val == gnm_floor (val))
go_fmt_general_int (result, (int)val, col_width);
else
gnm_fmt_general_float (result, val, col_width);
} else
gnm_format_number (result, val, (int)col_width, entry, date_conv);
return;
}
case VALUE_ERROR:
case VALUE_STRING:
g_string_append (result, value_peek_string (value));
return;
return value_peek_string (value);
case VALUE_CELLRANGE:
g_string_append (result, value_error_name (GNM_ERROR_VALUE, TRUE));
return;
case VALUE_ARRAY: /* Array of arrays ?? */
g_string_append (result, _("ARRAY"));
return;
return value_error_name (GNM_ERROR_VALUE, TRUE);
case VALUE_ARRAY:
case VALUE_FLOAT:
default:
g_assert_not_reached ();
return;
}
return "";
}
gchar *
format_value (GOFormat const *format, GnmValue const *value, GOColor *go_color,
double col_width, GODateConventions const *date_conv)
{
GString *result = g_string_sized_new (20);
format_value_gstring (result, format, value, go_color, col_width, date_conv);
format_value_gstring (result, format, value, go_color,
col_width, date_conv);
return g_string_free (result, FALSE);
}
static gboolean
convert_minus (GString *str, size_t i)
{
if (str->str[i] != '-')
return FALSE;
str->str[i] = 0xe2;
g_string_insert_len (str, i + 1, "\x88\x92", 2);
return TRUE;
}
#define HANDLE_MINUS(i) do { if (unicode_minus) convert_minus (str, (i)); } while (0)
#define SETUP_LAYOUT do { if (layout) pango_layout_set_text (layout, str->str, -1); } while (0)
typedef int (*GnmFormatMeasure) (const GString *str, PangoLayout *layout);
static int
zero_measure (const GString *str, PangoLayout *layout)
{
return 0;
}
/* This should go back to goffice. */
/*
* gnm_format_general:
* @layout: Optional PangoLayout, probably preseeded with font attribute.
* @str: a GString to store (not append!) the resulting string in.
* @measure: Function to measure width of string/layout.
* @metrics: Font metrics corresponding to @mesaure.
* @val: floating-point value. Must be finite.
* @col_width: intended max width of layout in pango units. -1 means
* no restriction.
* @unicode_minus: Use unicode minuses, not hyphens.
*
* Render a floating-point value into @layout in such a way that the
* layouting width does not needlessly exceed @col_width. Optionally
* use unicode minus instead of hyphen.
*/
static void
gnm_format_general (PangoLayout *layout, GString *str,
GnmFormatMeasure measure, const GnmFontMetrics *metrics,
gnm_float val,
int col_width,
gboolean unicode_minus)
{
gnm_float aval, l10;
int prec, safety, digs, maxdigits;
size_t epos;
gboolean rounds_to_0;
int sign_width;
if (col_width == -1) {
measure = zero_measure;
maxdigits = GNM_DIG;
col_width = INT_MAX;
sign_width = 0;
} else {
maxdigits = MIN (GNM_DIG, col_width / metrics->min_digit_width);
sign_width = unicode_minus
? metrics->minus_width
: metrics->hyphen_width;
}
#ifdef DEBUG_GENERAL
g_print ("Rendering %" GNM_FORMAT_g " to width %d (<=%d digits)\n",
val, col_width, maxdigits);
#endif
if (val == 0)
goto zero;
aval = gnm_abs (val);
if (aval >= GNM_const(1e15) || aval < GNM_const(1e-4))
goto e_notation;
l10 = gnm_log10 (aval);
/* Number of digits in [aval]. */
digs = (aval >= 1 ? 1 + (int)l10 : 1);
/* Check if there is room for the whole part, including sign. */
safety = metrics->avg_digit_width / 2;
if (digs * metrics->min_digit_width > col_width) {
#ifdef DEBUG_GENERAL
g_print ("No room for whole part.\n");
#endif
goto e_notation;
} else if (digs * metrics->max_digit_width + safety <
col_width - (val > 0 ? 0 : sign_width)) {
#ifdef DEBUG_GENERAL
g_print ("Room for whole part.\n");
#endif
if (val == gnm_floor (val) || digs == maxdigits) {
g_string_printf (str, "%.0" GNM_FORMAT_f, val);
HANDLE_MINUS (0);
SETUP_LAYOUT;
return;
}
} else {
int w;
#ifdef DEBUG_GENERAL
g_print ("Maybe room for whole part.\n");
#endif
g_string_printf (str, "%.0" GNM_FORMAT_f, val);
HANDLE_MINUS (0);
SETUP_LAYOUT;
w = measure (str, layout);
if (w > col_width)
goto e_notation;
if (val == gnm_floor (val) || digs == maxdigits)
return;
}
prec = maxdigits - digs;
g_string_printf (str, "%.*" GNM_FORMAT_f, prec, val);
HANDLE_MINUS (0);
while (str->str[str->len - 1] == '0') {
g_string_truncate (str, str->len - 1);
prec--;
}
if (prec == 0) {
/* We got "xxxxxx.000" and dropped the zeroes. */
const char *dot = g_utf8_prev_char (str->str + str->len);
g_string_truncate (str, dot - str->str);
SETUP_LAYOUT;
return;
}
while (prec > 0) {
int w;
SETUP_LAYOUT;
w = measure (str, layout);
if (w <= col_width)
return;
prec--;
g_string_printf (str, "%.*" GNM_FORMAT_f, prec, val);
HANDLE_MINUS (0);
}
SETUP_LAYOUT;
return;
e_notation:
rounds_to_0 = (aval < 0.5);
prec = (col_width -
(val > 0 ? 0 : sign_width) -
(aval < 1 ? sign_width : metrics->plus_width) -
metrics->E_width) / metrics->min_digit_width - 3;
if (prec <= 0) {
#ifdef DEBUG_GENERAL
if (prec == 0)
g_print ("Maybe room for E notation with no decimals.\n");
else
g_print ("No room for E notation.\n");
#endif
/* Certainly too narrow for precision. */
if (prec == 0 || !rounds_to_0) {
int w;
g_string_printf (str, "%.0" GNM_FORMAT_E, val);
HANDLE_MINUS (0);
epos = strchr (str->str, 'E') - str->str;
HANDLE_MINUS (epos + 1);
SETUP_LAYOUT;
if (!rounds_to_0)
return;
w = measure (str, layout);
if (w <= col_width)
return;
}
goto zero;
}
prec = MIN (prec, GNM_DIG - 1);
g_string_printf (str, "%.*" GNM_FORMAT_E, prec, val);
epos = strchr (str->str, 'E') - str->str;
digs = 0;
while (str->str[epos - 1 - digs] == '0')
digs++;
if (digs) {
epos -= digs;
g_string_erase (str, epos, digs);
prec -= digs;
if (prec == 0) {
int dot = 1 + (str->str[0] == '-');
g_string_erase (str, dot, epos - dot);
}
}
while (1) {
int w;
HANDLE_MINUS (0);
epos = strchr (str->str + prec + 1, 'E') - str->str;
HANDLE_MINUS (epos + 1);
SETUP_LAYOUT;
w = measure (str, layout);
if (w <= col_width)
return;
if (prec > 2 && w - metrics->max_digit_width > col_width)
prec -= 2;
else {
prec--;
if (prec < 0)
break;
}
g_string_printf (str, "%.*" GNM_FORMAT_E, prec, val);
}
if (rounds_to_0)
goto zero;
SETUP_LAYOUT;
return;
zero:
#ifdef DEBUG_GENERAL
g_print ("Zero.\n");
#endif
g_string_assign (str, "0");
SETUP_LAYOUT;
return;
}
static int
pango_measure (const GString *str, PangoLayout *layout)
{
int w;
pango_layout_get_size (layout, &w, NULL);
#ifdef DEBUG_GENERAL
g_print ("[%s] --> %d\n", str->str, w);
#endif
return w;
}
void
gnm_format_layout (PangoLayout *result,
GnmFontMetrics *metrics,
GOFormat const *format,
GnmValue const *value, GOColor *go_color,
int col_width,
GODateConventions const *date_conv,
gboolean unicode_minus)
{
GOFormatElement const *entry;
gboolean need_abs, empty;
g_return_if_fail (value != NULL);
if (!format)
format = VALUE_FMT (value);
/* Use top left corner of an array result. This will not work for
* ranges because we dont't have a location */
if (value->type == VALUE_ARRAY)
value = value_area_fetch_x_y (value, 0, 0, NULL);
entry = find_entry (format, value, go_color, &need_abs, &empty);
/* Empty formats should be ignored */
if (empty) {
pango_layout_set_text (result, "", 0);
return;
}
if (VALUE_IS_FLOAT (value)) {
gnm_float val = value_get_as_float (value);
if (!gnm_finite (val)) {
pango_layout_set_text (result, value_error_name (GNM_ERROR_VALUE, TRUE), -1);
return;
}
if (need_abs)
val = gnm_abs (val);
if (entry == NULL) {
GString *str = g_string_sized_new (G_ASCII_DTOSTR_BUF_SIZE + GNM_DIG);
gnm_format_general (result, str, pango_measure,
metrics, val,
col_width, unicode_minus);
g_string_free (str, TRUE);
} else {
GString *str = g_string_sized_new (100);
/* FIXME: -1 kills filling here. */
gnm_format_number (str, val, -1, entry, date_conv);
/* FIXME: have gnm_format_number handle minus. */
if (format->family != GO_FORMAT_DATE && val < 1.0) {
size_t i;
for (i = 0; i < str->len; i++)
if (convert_minus (str, i))
i += 2;
}
pango_layout_set_text (result, str->str, str->len);
g_string_free (str, TRUE);
}
} else
pango_layout_set_text (result, format_nonnumber (value), -1);
}
static int
strlen_measure (const GString *str, PangoLayout *layout)
{
return g_utf8_strlen (str->str, -1);
}
void
format_value_gstring (GString *str, GOFormat const *format,
GnmValue const *value, GOColor *go_color,
double col_width,
GODateConventions const *date_conv)
{
GOFormatElement const *entry;
gboolean need_abs, empty;
gboolean unicode_minus = FALSE;
g_return_if_fail (value != NULL);
if (format == NULL)
format = VALUE_FMT (value);
/* Use top left corner of an array result. This will not work for
* ranges because we dont't have a location */
if (value->type == VALUE_ARRAY)
value = value_area_fetch_x_y (value, 0, 0, NULL);
entry = find_entry (format, value, go_color, &need_abs, &empty);
/* Empty formats should be ignored */
if (empty)
return;
if (VALUE_IS_FLOAT (value)) {
gnm_float val = value_get_as_float (value);
if (!gnm_finite (val)) {
g_string_append (str, value_error_name (GNM_ERROR_VALUE, TRUE));
return;
}
if (need_abs)
val = gnm_abs (val);
if (entry == NULL) {
GString *new_str = NULL;
if (str->len)
new_str = g_string_sized_new (G_ASCII_DTOSTR_BUF_SIZE + GNM_DIG);
gnm_format_general (NULL, str->len ? new_str : str,
strlen_measure,
gnm_font_metrics_unit,
val, col_width, unicode_minus);
if (new_str) {
go_string_append_gstring (str, new_str);
g_string_free (new_str, TRUE);
}
} else
gnm_format_number (str, val, (int)col_width, entry, date_conv);
} else {
g_string_append (str, format_nonnumber (value));
}
}
......@@ -3,6 +3,7 @@
#include "gnumeric.h"
#include <goffice/utils/go-format.h>
#include <pango/pango.h>
char *format_value (GOFormat const *format,
GnmValue const *value, GOColor *go_color,
......@@ -10,7 +11,16 @@ char *format_value (GOFormat const *format,
void format_value_gstring (GString *result,
GOFormat const *format,
GnmValue const *value, GOColor *go_color,
double col_width, GODateConventions const *date_conv);
double col_width,
GODateConventions const *date_conv);
void gnm_format_layout (PangoLayout *result,
GnmFontMetrics *metrics,
GOFormat const *format,
GnmValue const *value, GOColor *go_color,
int col_width,
GODateConventions const *date_conv,
gboolean unicode_minus);
/*
* http://www.unicode.org/charts/PDF/U0080.pdf
......
......@@ -92,6 +92,7 @@ typedef GList ColRowIndexList;
typedef struct _ColRowIndexSet ColRowIndexSet;
typedef