1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * print-cell.c: Printing of cell regions and cells.
6 * Jody Goldberg 2000-2006 (jody@gnome.org)
7 * Miguel de Icaza 1999 (miguel@kernel.org)
8 * Andreas J. Guelzow 2007 (aguelzow@pyrshep.ca)
9 * Copyright (C) 2007-2009 Morten Welinder (terra@gnome.org)
11 #include <gnumeric-config.h>
13 #include "print-cell.h"
15 #include "application.h"
16 #include "dependent.h"
17 #include "gnm-format.h"
18 #include "style-color.h"
19 #include "style-font.h"
20 #include "parse-util.h"
23 #include "style-border.h"
24 #include "style-conditions.h"
29 #include "sheet-style.h"
30 #include "sheet-merge.h"
31 #include "rendered-value.h"
32 #include "cell-draw.h"
33 #include "print-info.h"
39 #define MERGE_DEBUG(range, str) do { range_dump (range, str); } while (0)
41 #define MERGE_DEBUG(range, str)
45 * base_[xy] : Coordinates of the upper left corner of the cell.
46 * INCLUSIVE of the near grid line
56 print_cell_gtk (GnmCell
const *cell
,
59 double width
, double height
, double h_center
,
60 GnmPrintInformation
const *pinfo
)
62 GnmRenderedValue
*rv
, *rv100
= NULL
;
65 Sheet
*sheet
= cell
->base
.sheet
;
66 double const scale_h
= 72. / gnm_app_display_dpi_get (TRUE
);
67 double const scale_v
= 72. / gnm_app_display_dpi_get (FALSE
);
69 gboolean cell_shows_error
;
71 if (cell
->base
.flags
& GNM_CELL_HAS_NEW_EXPR
)
72 gnm_cell_eval ((GnmCell
*)cell
);
74 cell_shows_error
= (gnm_cell_is_error (cell
) != NULL
)
75 && !(gnm_cell_has_expr (cell
) && sheet
->display_formulas
);
77 if (cell_shows_error
&& pinfo
->error_display
==
78 GNM_PRINT_ERRORS_AS_BLANK
)
81 /* Get the sizes exclusive of margins and grids */
82 /* Note: +1 because size_pixels includes leading gridline. */
83 height
-= GNM_ROW_MARGIN
+ GNM_ROW_MARGIN
+ 1;
84 width
-= GNM_COL_MARGIN
+ GNM_COL_MARGIN
+ 1;
86 rv
= gnm_cell_fetch_rendered_value (cell
, TRUE
);
88 /* Create a rendered value for printing */
89 if (cell_shows_error
&&
90 (pinfo
->error_display
== GNM_PRINT_ERRORS_AS_NA
91 || pinfo
->error_display
== GNM_PRINT_ERRORS_AS_DASHES
)) {
92 GnmCell
*t_cell
= (GnmCell
*)cell
;
93 GnmValue
*old
= t_cell
->value
;
94 if (pinfo
->error_display
== GNM_PRINT_ERRORS_AS_NA
)
95 t_cell
->value
= value_new_error_NA (NULL
);
97 t_cell
->value
= value_new_error
99 /* U+2014 U+200A U+2014 */
100 "\342\200\224\342\200\212\342\200\224");
101 rv100
= gnm_rendered_value_new (t_cell
,
102 pango_layout_get_context (rv
->layout
),
106 value_release (t_cell
->value
);
108 } else if (sheet
->last_zoom_factor_used
!= 1) {
110 * We're zoomed and we don't want printing to reflect that.
113 rv100
= gnm_rendered_value_new ((GnmCell
*)cell
,
114 pango_layout_get_context (rv
->layout
),
120 /* Make sure we don't get overflow in print unless we had it in
122 rv
->might_overflow
= rv
->numeric_overflow
;
124 if (cell_calc_layout (cell
, rv
, -1,
125 (int)(width
* PANGO_SCALE
/ scale_h
),
126 (int)(height
* PANGO_SCALE
/ scale_v
),
127 (int)h_center
== -1 ? -1 : (int)(h_center
* PANGO_SCALE
),
128 &fore_color
, &x
, &y
)) {
130 /* Clip the printed rectangle */
131 cairo_save (context
);
134 /* We do not clip rotated cells. */
135 cairo_new_path (context
);
136 cairo_rectangle (context
, x1
+ GNM_COL_MARGIN
, y1
+ GNM_ROW_MARGIN
,
137 width
+ 1, height
+ 1);
138 cairo_clip (context
);
141 /* Set the font colour */
142 cairo_set_source_rgba (context
,
143 GO_COLOR_TO_CAIRO (fore_color
));
145 cairo_translate (context
, x1
+0.5, y1
);
148 GnmRenderedRotatedValue
*rrv
= (GnmRenderedRotatedValue
*)rv
;
149 struct GnmRenderedRotatedValueInfo
const *li
= rrv
->lines
;
152 cairo_scale (context
, scale_h
, scale_v
);
153 cairo_move_to (context
, 0.,0.);
154 for (lines
= pango_layout_get_lines (rv
->layout
);
156 lines
= lines
->next
, li
++) {
157 cairo_save (context
);
158 cairo_move_to (context
,
159 PANGO_PIXELS (x
+ li
->dx
),
160 PANGO_PIXELS (- y
+ li
->dy
));
161 cairo_rotate (context
, rv
->rotation
* (-M_PI
/ 180));
162 pango_cairo_show_layout_line (context
, lines
->data
);
163 cairo_restore (context
);
168 cairo_scale (context
, scale_h
, scale_v
);
169 cairo_move_to (context
, x
/ (double)PANGO_SCALE
, - y
/ (double)PANGO_SCALE
);
170 pango_cairo_show_layout (context
, rv
->layout
);
172 cairo_restore(context
);
176 gnm_rendered_value_destroy (rv100
);
180 print_rectangle_gtk (cairo_t
*context
,
181 double x
, double y
, double w
, double h
)
183 cairo_new_path (context
);
184 cairo_rectangle (context
, x
, y
, w
, h
);
185 cairo_fill (context
);
189 print_cell_background_gtk (cairo_t
*context
,
190 GnmStyle
const *style
,
191 G_GNUC_UNUSED
int col
, G_GNUC_UNUSED
int row
,
192 double x
, double y
, double w
, double h
)
194 if (gnm_pattern_background_set (style
, context
, FALSE
, NULL
))
195 /* Remember api excludes the far pixels */
196 print_rectangle_gtk (context
, x
, y
, w
+0.2, h
+0.2);
197 gnm_style_border_print_diag_gtk (style
, context
, x
, y
, x
+w
, y
+h
);
202 * print_merged_range:
204 * Handle the special drawing requirements for a 'merged cell'.
205 * First draw the entire range (clipped to the visible region) then redraw any
206 * segments that are selected.
209 print_merged_range_gtk (cairo_t
*context
,
211 double start_x
, double start_y
,
212 GnmRange
const *view
, GnmRange
const *range
,
213 GnmPrintInformation
const *pinfo
)
217 GnmCell
const *cell
= sheet_cell_get (sheet
, range
->start
.col
, range
->start
.row
);
218 int const dir
= sheet
->text_is_rtl
? -1 : 1;
219 GnmStyleConditions
*conds
;
221 /* load style from corner which may not be visible */
222 GnmStyle
const *style
= sheet_style_get (sheet
, range
->start
.col
, range
->start
.row
);
225 if (view
->start
.col
< range
->start
.col
)
226 l
+= dir
* sheet_col_get_distance_pts (sheet
,
227 view
->start
.col
, range
->start
.col
);
228 if (range
->end
.col
<= (last
= view
->end
.col
))
229 last
= range
->end
.col
;
230 r
+= dir
* sheet_col_get_distance_pts (sheet
, view
->start
.col
, last
+1);
233 if (view
->start
.row
< range
->start
.row
)
234 t
-= sheet_row_get_distance_pts (sheet
,
235 view
->start
.row
, range
->start
.row
);
236 if (range
->end
.row
<= (last
= view
->end
.row
))
237 last
= range
->end
.row
;
238 b
+= sheet_row_get_distance_pts (sheet
, view
->start
.row
, last
+1);
240 if (l
== r
|| t
== b
)
243 conds
= gnm_style_get_conditions (style
);
247 eval_pos_init (&ep
, (Sheet
*)sheet
, range
->start
.col
, range
->start
.row
);
248 if ((res
= gnm_style_conditions_eval (conds
, &ep
)) >= 0)
249 style
= gnm_style_get_cond_style (style
, res
);
252 if (gnm_pattern_background_set (style
, context
, FALSE
, NULL
))
253 print_rectangle_gtk (context
, l
, t
, r
-l
+0.2, b
-t
+0.2);
255 if (range
->start
.col
< view
->start
.col
)
256 l
-= dir
* sheet_col_get_distance_pts (sheet
,
257 range
->start
.col
, view
->start
.col
);
258 if (view
->end
.col
< range
->end
.col
)
259 r
+= dir
* sheet_col_get_distance_pts (sheet
,
260 view
->end
.col
+1, range
->end
.col
+1);
261 if (range
->start
.row
< view
->start
.row
)
262 t
-= sheet_row_get_distance_pts (sheet
,
263 range
->start
.row
, view
->start
.row
);
264 if (view
->end
.row
< range
->end
.row
)
265 b
+= sheet_row_get_distance_pts (sheet
,
266 view
->end
.row
+1, range
->end
.row
+1);
269 ColRowInfo
*ri
= sheet_row_get (sheet
, range
->start
.row
);
271 if (ri
->needs_respan
)
272 row_calc_spans (ri
, cell
->pos
.row
, sheet
);
274 if (sheet
->text_is_rtl
)
275 print_cell_gtk (cell
, context
,
276 r
, t
, l
- r
, b
- t
, -1., pinfo
);
278 print_cell_gtk (cell
, context
,
279 l
, t
, r
- l
, b
- t
, -1., pinfo
);
281 gnm_style_border_print_diag_gtk (style
, context
, l
, t
, r
, b
);
285 merged_col_cmp (GnmRange
const *a
, GnmRange
const *b
)
287 return a
->start
.col
- b
->start
.col
;
292 gnm_gtk_print_cell_range (cairo_t
*context
,
293 Sheet
const *sheet
, GnmRange
*range
,
294 double base_x
, double base_y
,
295 GnmPrintInformation
const *pinfo
)
297 ColRowInfo
const *ri
= NULL
, *next_ri
= NULL
;
298 int const dir
= sheet
->text_is_rtl
? -1 : 1;
299 double const hscale
= sheet
->display_formulas
? 2 : 1;
300 int start_row
, start_col
, end_col
, end_row
;
302 GnmStyleRow sr
, next_sr
;
303 GnmStyle
const **styles
;
304 GnmBorder
const **borders
, **prev_vert
;
305 GnmBorder
const *none
;
306 gpointer
*sr_array_data
;
311 GSList
*merged_active
, *merged_active_seen
,
312 *merged_used
, *merged_unused
, *ptr
, **lag
;
315 g_return_if_fail (IS_SHEET (sheet
));
316 g_return_if_fail (range
!= NULL
);
317 g_return_if_fail (range
->start
.col
<= range
->end
.col
);
318 g_return_if_fail (range
->start
.row
<= range
->end
.row
);
319 g_return_if_fail (pinfo
!= NULL
);
321 hide_grid
= !pinfo
->print_grid_lines
;
322 none
= hide_grid
? NULL
: gnm_style_border_none ();
324 start_col
= range
->start
.col
;
325 start_row
= range
->start
.row
;
326 end_col
= range
->end
.col
;
327 end_row
= range
->end
.row
;
329 /* Skip any hidden cols/rows at the start */
330 for (; start_col
<= end_col
; ++start_col
) {
331 ColRowInfo
const *ci
= sheet_col_get_info (sheet
, start_col
);
335 for (; start_row
<= end_row
; ++start_row
) {
336 ri
= sheet_row_get_info (sheet
, start_row
);
341 sheet_style_update_grid_color (sheet
);
343 /* Get ordered list of merged regions */
344 merged_active
= merged_active_seen
= merged_used
= NULL
;
345 merged_unused
= gnm_sheet_merge_get_overlap (sheet
,
346 range_init (&view
, start_col
, start_row
, end_col
, end_row
));
349 * allocate a single blob of memory for all 8 arrays of pointers.
350 * - 6 arrays of n GnmBorder const *
351 * - 2 arrays of n GnmStyle const *
353 * then alias the arrays for easy access so that array [col] is valid
354 * for all elements start_col-1 .. end_col+1 inclusive.
355 * Note that this means that in some cases array [-1] is legal.
357 n
= end_col
- start_col
+ 3; /* 1 before, 1 after, 1 fencepost */
358 sr_array_data
= g_new (gpointer
, n
* 8);
359 style_row_init (&prev_vert
, &sr
, &next_sr
, start_col
, end_col
,
360 sr_array_data
, hide_grid
);
362 /* load up the styles for the first row */
363 next_sr
.row
= sr
.row
= row
= start_row
;
364 sheet_style_get_row (sheet
, &sr
);
368 row
= sr
.row
= next_sr
.row
, ri
= next_ri
) {
369 /* Restore the set of ranges seen, but still active.
370 * Reinverting list to maintain the original order */
371 g_return_if_fail (merged_active
== NULL
);
373 while (merged_active_seen
!= NULL
) {
374 GSList
*tmp
= merged_active_seen
->next
;
375 merged_active_seen
->next
= merged_active
;
376 merged_active
= merged_active_seen
;
377 merged_active_seen
= tmp
;
378 MERGE_DEBUG (merged_active
->data
, " : seen -> active\n");
381 /* find the next visible row */
384 if (next_sr
.row
<= end_row
) {
385 next_ri
= sheet_row_get_info (sheet
, next_sr
.row
);
386 if (next_ri
->visible
) {
387 sheet_style_get_row (sheet
, &next_sr
);
391 for (col
= start_col
; col
<= end_col
; ++col
)
392 next_sr
.vertical
[col
] =
393 next_sr
.bottom
[col
] = none
;
398 /* it is safe to const_cast because only a non-default row
399 * will ever get flagged.
401 if (ri
->needs_respan
)
402 row_calc_spans ((ColRowInfo
*)ri
, row
, sheet
);
404 /* look for merges that start on this row, on the first painted row
405 * also check for merges that start above. */
406 view
.start
.row
= row
;
407 lag
= &merged_unused
;
408 for (ptr
= merged_unused
; ptr
!= NULL
; ) {
409 GnmRange
* const r
= ptr
->data
;
411 if (r
->start
.row
<= row
) {
413 ptr
= *lag
= tmp
->next
;
414 if (r
->end
.row
< row
) {
415 tmp
->next
= merged_used
;
417 MERGE_DEBUG (r
, " : unused -> used\n");
419 ColRowInfo
const *ci
=
420 sheet_col_get_info (sheet
, r
->start
.col
);
421 g_slist_free_1 (tmp
);
422 merged_active
= g_slist_insert_sorted (merged_active
, r
,
423 (GCompareFunc
)merged_col_cmp
);
424 MERGE_DEBUG (r
, " : unused -> active\n");
427 print_merged_range_gtk (context
, sheet
,
437 for (col
= start_col
, x
= base_x
; col
<= end_col
; col
++) {
438 GnmStyle
const *style
;
439 CellSpanInfo
const *span
;
440 ColRowInfo
const *ci
= sheet_col_get_info (sheet
, col
);
441 ColRowInfo
const *ri
= sheet_row_get_info (sheet
, row
);
444 if (merged_active
!= NULL
) {
445 GnmRange
const *r
= merged_active
->data
;
446 if (r
->end
.col
== col
) {
448 merged_active
= merged_active
->next
;
449 if (r
->end
.row
<= row
) {
450 ptr
->next
= merged_used
;
452 MERGE_DEBUG (r
, " : active2 -> used\n");
454 ptr
->next
= merged_active_seen
;
455 merged_active_seen
= ptr
;
456 MERGE_DEBUG (r
, " : active2 -> seen\n");
463 /* Skip any merged regions */
465 GnmRange
const *r
= merged_active
->data
;
466 if (r
->start
.col
<= col
) {
467 gboolean clear_top
, clear_bottom
= TRUE
;
468 int i
, first
= r
->start
.col
;
469 int last
= r
->end
.col
;
471 x
+= sheet_col_get_distance_pts (sheet
,
475 if (first
< start_col
) {
477 sr
.vertical
[first
] = NULL
;
479 if (last
> end_col
) {
481 sr
.vertical
[last
+1] = NULL
;
483 clear_top
= (r
->start
.row
!= row
);
486 merged_active
= merged_active
->next
;
487 if (r
->end
.row
<= row
) {
488 clear_bottom
= FALSE
;
489 ptr
->next
= merged_used
;
491 MERGE_DEBUG (r
, " : active -> used\n");
493 ptr
->next
= merged_active_seen
;
494 merged_active_seen
= ptr
;
495 MERGE_DEBUG (r
, " : active -> seen\n");
498 /* Clear the borders */
499 for (i
= first
; i
<= last
; i
++) {
503 sr
.bottom
[i
] = NULL
;
505 sr
.vertical
[i
] = NULL
;
512 x
-= ci
->size_pts
* hscale
;
513 style
= sr
.styles
[col
];
514 print_cell_background_gtk (context
, style
, col
, row
, x
, y
,
515 ci
->size_pts
* hscale
, ri
->size_pts
);
517 /* Is this part of a span?
518 * 1) There are cells allocated in the row
519 * (indicated by ri->spans != NULL)
520 * 2) Look in the rows hash table to see if
521 * there is a span descriptor.
523 if (NULL
== ri
->spans
|| NULL
== (span
= row_span_get (ri
, col
))) {
524 /* no need to draw blanks */
525 GnmCell
const *cell
= sheet_cell_get (sheet
, col
, row
);
526 if (!gnm_cell_is_empty (cell
))
527 print_cell_gtk (cell
, context
, x
, y
,
528 ci
->size_pts
* hscale
,
529 ri
->size_pts
, -1., pinfo
);
531 /* Only draw spaning cells after all the backgrounds
532 * that we are going to draw have been drawn. No need
533 * to draw the edit cell, or blanks.
535 } else if (col
== span
->right
|| col
== end_col
) {
536 GnmCell
const *cell
= span
->cell
;
537 int const start_span_col
= span
->left
;
538 int const end_span_col
= span
->right
;
540 ColRowInfo
const *cell_col
=
541 sheet_col_get_info (sheet
, cell
->pos
.col
);
542 double center_offset
= cell_col
->size_pts
* hscale
/ 2;
543 double tmp_width
= ci
->size_pts
* hscale
;
545 if (col
!= cell
->pos
.col
)
546 style
= sheet_style_get (sheet
,
549 /* x, y are relative to this cell origin, but the cell
550 * might be using columns to the left (if it is set to right
551 * justify or center justify) compute the pixel difference
553 if (start_span_col
!= cell
->pos
.col
)
554 center_offset
+= sheet_col_get_distance_pts (
555 sheet
, start_span_col
, cell
->pos
.col
);
557 if (start_span_col
!= col
) {
558 offset
= sheet_col_get_distance_pts (
559 sheet
, start_span_col
, col
);
563 sr
.vertical
[col
] = NULL
;
565 if (end_span_col
!= col
) {
566 offset
= sheet_col_get_distance_pts (
567 sheet
, col
+1, end_span_col
+ 1);
573 print_cell_gtk (cell
, context
,
574 real_x
, y
, tmp_width
, ri
->size_pts
,
575 center_offset
, pinfo
);
576 } else if (col
!= span
->left
)
577 sr
.vertical
[col
] = NULL
;
580 x
+= ci
->size_pts
* hscale
;
582 gnm_style_borders_row_print_gtk (prev_vert
, &sr
,
583 context
, base_x
, y
, y
+ri
->size_pts
,
586 /* In case there were hidden merges that trailed off the end */
587 while (merged_active
!= NULL
) {
588 GnmRange
const *r
= merged_active
->data
;
590 merged_active
= merged_active
->next
;
591 if (r
->end
.row
<= row
) {
592 ptr
->next
= merged_used
;
594 MERGE_DEBUG (r
, " : active3 -> used\n");
596 ptr
->next
= merged_active_seen
;
597 merged_active_seen
= ptr
;
598 MERGE_DEBUG (r
, " : active3 -> seen\n");
601 /* roll the pointers */
602 borders
= prev_vert
; prev_vert
= sr
.vertical
;
603 sr
.vertical
= next_sr
.vertical
; next_sr
.vertical
= borders
;
604 borders
= sr
.top
; sr
.top
= sr
.bottom
;
605 sr
.bottom
= next_sr
.top
= next_sr
.bottom
; next_sr
.bottom
= borders
;
606 styles
= sr
.styles
; sr
.styles
= next_sr
.styles
; next_sr
.styles
= styles
;
610 gnm_style_borders_row_print_gtk (prev_vert
, &sr
,
611 context
, base_x
, y
, y
, sheet
, FALSE
, dir
);
613 g_slist_free (merged_used
); /* merges with bottom in view */
614 g_slist_free (merged_active_seen
); /* merges with bottom the view */
615 g_slist_free (merged_unused
); /* merges in hidden rows */
616 g_free (sr_array_data
);
617 g_return_if_fail (merged_active
== NULL
);