2 * print-cell.c: Printing of cell regions and cells.
5 * Jody Goldberg 2000-2006 (jody@gnome.org)
6 * Miguel de Icaza 1999 (miguel@kernel.org)
7 * Andreas J. Guelzow 2007 (aguelzow@pyrshep.ca)
8 * Copyright (C) 2007-2009 Morten Welinder (terra@gnome.org)
10 #include <gnumeric-config.h>
12 #include <print-cell.h>
14 #include <application.h>
15 #include <dependent.h>
16 #include <gnm-format.h>
17 #include <style-color.h>
18 #include <style-font.h>
19 #include <parse-util.h>
22 #include <style-border.h>
23 #include <style-conditions.h>
28 #include <sheet-style.h>
29 #include <sheet-merge.h>
30 #include <rendered-value.h>
31 #include <cell-draw.h>
32 #include <print-info.h>
38 #define MERGE_DEBUG(range, str) do { range_dump (range, str); } while (0)
40 #define MERGE_DEBUG(range, str)
44 * base_[xy] : Coordinates of the upper left corner of the cell.
45 * INCLUSIVE of the near grid line
55 print_cell_gtk (GnmCell
const *cell
,
58 double width
, double height
, double h_center
,
59 GnmPrintInformation
const *pinfo
)
61 GnmRenderedValue
*rv
, *rv100
= NULL
;
64 Sheet
*sheet
= cell
->base
.sheet
;
65 double const scale_h
= 72. / gnm_app_display_dpi_get (TRUE
);
66 double const scale_v
= 72. / gnm_app_display_dpi_get (FALSE
);
68 gboolean cell_shows_error
;
70 if (cell
->base
.flags
& GNM_CELL_HAS_NEW_EXPR
)
71 gnm_cell_eval ((GnmCell
*)cell
);
73 cell_shows_error
= (gnm_cell_is_error (cell
) != NULL
)
74 && !(gnm_cell_has_expr (cell
) && sheet
->display_formulas
);
76 if (cell_shows_error
&& pinfo
->error_display
==
77 GNM_PRINT_ERRORS_AS_BLANK
)
80 /* Get the sizes exclusive of margins and grids */
81 /* Note: +1 because size_pixels includes leading gridline. */
82 height
-= GNM_ROW_MARGIN
+ GNM_ROW_MARGIN
+ 1;
83 width
-= GNM_COL_MARGIN
+ GNM_COL_MARGIN
+ 1;
85 rv
= gnm_cell_fetch_rendered_value (cell
, TRUE
);
87 /* Create a rendered value for printing */
88 if (cell_shows_error
&&
89 (pinfo
->error_display
== GNM_PRINT_ERRORS_AS_NA
90 || pinfo
->error_display
== GNM_PRINT_ERRORS_AS_DASHES
)) {
91 GnmCell
*t_cell
= (GnmCell
*)cell
;
92 GnmValue
*old
= t_cell
->value
;
93 if (pinfo
->error_display
== GNM_PRINT_ERRORS_AS_NA
)
94 t_cell
->value
= value_new_error_NA (NULL
);
96 t_cell
->value
= value_new_error
98 /* U+2014 U+200A U+2014 */
99 "\342\200\224\342\200\212\342\200\224");
100 rv100
= gnm_rendered_value_new (t_cell
,
101 pango_layout_get_context (rv
->layout
),
105 value_release (t_cell
->value
);
107 } else if (sheet
->last_zoom_factor_used
!= 1) {
109 * We're zoomed and we don't want printing to reflect that.
112 rv100
= gnm_rendered_value_new ((GnmCell
*)cell
,
113 pango_layout_get_context (rv
->layout
),
119 /* Make sure we don't get overflow in print unless we had it in
121 rv
->might_overflow
= rv
->numeric_overflow
;
123 if (cell_calc_layout (cell
, rv
, -1,
124 (int)(width
* PANGO_SCALE
/ scale_h
),
125 (int)(height
* PANGO_SCALE
/ scale_v
),
126 (int)h_center
== -1 ? -1 : (int)(h_center
* PANGO_SCALE
),
127 &fore_color
, &x
, &y
)) {
129 /* Clip the printed rectangle */
130 cairo_save (context
);
133 /* We do not clip rotated cells. */
134 cairo_new_path (context
);
135 cairo_rectangle (context
, x1
+ GNM_COL_MARGIN
, y1
+ GNM_ROW_MARGIN
,
136 width
+ 1, height
+ 1);
137 cairo_clip (context
);
140 /* Set the font colour */
141 cairo_set_source_rgba (context
,
142 GO_COLOR_TO_CAIRO (fore_color
));
144 cairo_translate (context
, x1
+0.5, y1
);
147 GnmRenderedRotatedValue
*rrv
= (GnmRenderedRotatedValue
*)rv
;
148 struct GnmRenderedRotatedValueInfo
const *li
= rrv
->lines
;
151 cairo_scale (context
, scale_h
, scale_v
);
152 cairo_move_to (context
, 0.,0.);
153 for (lines
= pango_layout_get_lines (rv
->layout
);
155 lines
= lines
->next
, li
++) {
156 cairo_save (context
);
157 cairo_move_to (context
,
158 PANGO_PIXELS (x
+ li
->dx
),
159 PANGO_PIXELS (- y
+ li
->dy
));
160 cairo_rotate (context
, rv
->rotation
* (-M_PI
/ 180));
161 pango_cairo_show_layout_line (context
, lines
->data
);
162 cairo_restore (context
);
167 cairo_scale (context
, scale_h
, scale_v
);
168 cairo_move_to (context
, x
/ (double)PANGO_SCALE
, - y
/ (double)PANGO_SCALE
);
169 pango_cairo_show_layout (context
, rv
->layout
);
171 cairo_restore(context
);
175 gnm_rendered_value_destroy (rv100
);
179 print_rectangle_gtk (cairo_t
*context
,
180 double x
, double y
, double w
, double h
)
182 cairo_new_path (context
);
183 cairo_rectangle (context
, x
, y
, w
, h
);
184 cairo_fill (context
);
188 print_cell_background_gtk (cairo_t
*context
,
189 GnmStyle
const *style
,
190 G_GNUC_UNUSED
int col
, G_GNUC_UNUSED
int row
,
191 double x
, double y
, double w
, double h
)
193 if (gnm_pattern_background_set (style
, context
, FALSE
, NULL
))
194 /* Remember api excludes the far pixels */
195 print_rectangle_gtk (context
, x
, y
, w
+0.2, h
+0.2);
196 gnm_style_border_print_diag_gtk (style
, context
, x
, y
, x
+w
, y
+h
);
201 * print_merged_range:
203 * Handle the special drawing requirements for a 'merged cell'.
204 * First draw the entire range (clipped to the visible region) then redraw any
205 * segments that are selected.
208 print_merged_range_gtk (cairo_t
*context
,
210 double start_x
, double start_y
,
211 GnmRange
const *view
, GnmRange
const *range
,
212 GnmPrintInformation
const *pinfo
)
216 GnmCell
const *cell
= sheet_cell_get (sheet
, range
->start
.col
, range
->start
.row
);
217 int const dir
= sheet
->text_is_rtl
? -1 : 1;
218 GnmStyleConditions
*conds
;
220 /* load style from corner which may not be visible */
221 GnmStyle
const *style
= sheet_style_get (sheet
, range
->start
.col
, range
->start
.row
);
224 if (view
->start
.col
< range
->start
.col
)
225 l
+= dir
* sheet_col_get_distance_pts (sheet
,
226 view
->start
.col
, range
->start
.col
);
227 if (range
->end
.col
<= (last
= view
->end
.col
))
228 last
= range
->end
.col
;
229 r
+= dir
* sheet_col_get_distance_pts (sheet
, view
->start
.col
, last
+1);
232 if (view
->start
.row
< range
->start
.row
)
233 t
-= sheet_row_get_distance_pts (sheet
,
234 view
->start
.row
, range
->start
.row
);
235 if (range
->end
.row
<= (last
= view
->end
.row
))
236 last
= range
->end
.row
;
237 b
+= sheet_row_get_distance_pts (sheet
, view
->start
.row
, last
+1);
239 if (l
== r
|| t
== b
)
242 conds
= gnm_style_get_conditions (style
);
246 eval_pos_init (&ep
, (Sheet
*)sheet
, range
->start
.col
, range
->start
.row
);
247 if ((res
= gnm_style_conditions_eval (conds
, &ep
)) >= 0)
248 style
= gnm_style_get_cond_style (style
, res
);
251 if (gnm_pattern_background_set (style
, context
, FALSE
, NULL
))
252 print_rectangle_gtk (context
, l
, t
, r
-l
+0.2, b
-t
+0.2);
254 if (range
->start
.col
< view
->start
.col
)
255 l
-= dir
* sheet_col_get_distance_pts (sheet
,
256 range
->start
.col
, view
->start
.col
);
257 if (view
->end
.col
< range
->end
.col
)
258 r
+= dir
* sheet_col_get_distance_pts (sheet
,
259 view
->end
.col
+1, range
->end
.col
+1);
260 if (range
->start
.row
< view
->start
.row
)
261 t
-= sheet_row_get_distance_pts (sheet
,
262 range
->start
.row
, view
->start
.row
);
263 if (view
->end
.row
< range
->end
.row
)
264 b
+= sheet_row_get_distance_pts (sheet
,
265 view
->end
.row
+1, range
->end
.row
+1);
268 ColRowInfo
*ri
= sheet_row_get (sheet
, range
->start
.row
);
270 if (ri
->needs_respan
)
271 row_calc_spans (ri
, cell
->pos
.row
, sheet
);
273 if (sheet
->text_is_rtl
)
274 print_cell_gtk (cell
, context
,
275 r
, t
, l
- r
, b
- t
, -1., pinfo
);
277 print_cell_gtk (cell
, context
,
278 l
, t
, r
- l
, b
- t
, -1., pinfo
);
280 gnm_style_border_print_diag_gtk (style
, context
, l
, t
, r
, b
);
284 merged_col_cmp (GnmRange
const *a
, GnmRange
const *b
)
286 return a
->start
.col
- b
->start
.col
;
291 gnm_gtk_print_cell_range (cairo_t
*context
,
292 Sheet
const *sheet
, GnmRange
*range
,
293 double base_x
, double base_y
,
294 GnmPrintInformation
const *pinfo
)
296 ColRowInfo
const *ri
= NULL
, *next_ri
= NULL
;
297 int const dir
= sheet
->text_is_rtl
? -1 : 1;
298 double const hscale
= sheet
->display_formulas
? 2 : 1;
299 int start_row
, start_col
, end_col
, end_row
;
301 GnmStyleRow sr
, next_sr
;
302 GnmStyle
const **styles
;
303 GnmBorder
const **borders
, **prev_vert
;
304 GnmBorder
const *none
;
305 gpointer
*sr_array_data
;
310 GSList
*merged_active
, *merged_active_seen
,
311 *merged_used
, *merged_unused
, *ptr
, **lag
;
314 g_return_if_fail (IS_SHEET (sheet
));
315 g_return_if_fail (range
!= NULL
);
316 g_return_if_fail (range
->start
.col
<= range
->end
.col
);
317 g_return_if_fail (range
->start
.row
<= range
->end
.row
);
318 g_return_if_fail (pinfo
!= NULL
);
320 hide_grid
= !pinfo
->print_grid_lines
;
321 none
= hide_grid
? NULL
: gnm_style_border_none ();
323 start_col
= range
->start
.col
;
324 start_row
= range
->start
.row
;
325 end_col
= range
->end
.col
;
326 end_row
= range
->end
.row
;
328 /* Skip any hidden cols/rows at the start */
329 for (; start_col
<= end_col
; ++start_col
) {
330 ColRowInfo
const *ci
= sheet_col_get_info (sheet
, start_col
);
334 for (; start_row
<= end_row
; ++start_row
) {
335 ri
= sheet_row_get_info (sheet
, start_row
);
340 sheet_style_update_grid_color (sheet
);
342 /* Get ordered list of merged regions */
343 merged_active
= merged_active_seen
= merged_used
= NULL
;
344 merged_unused
= gnm_sheet_merge_get_overlap (sheet
,
345 range_init (&view
, start_col
, start_row
, end_col
, end_row
));
348 * allocate a single blob of memory for all 8 arrays of pointers.
349 * - 6 arrays of n GnmBorder const *
350 * - 2 arrays of n GnmStyle const *
352 * then alias the arrays for easy access so that array [col] is valid
353 * for all elements start_col-1 .. end_col+1 inclusive.
354 * Note that this means that in some cases array [-1] is legal.
356 n
= end_col
- start_col
+ 3; /* 1 before, 1 after, 1 fencepost */
357 sr_array_data
= g_new (gpointer
, n
* 8);
358 style_row_init (&prev_vert
, &sr
, &next_sr
, start_col
, end_col
,
359 sr_array_data
, hide_grid
);
361 /* load up the styles for the first row */
362 next_sr
.row
= sr
.row
= row
= start_row
;
363 sheet_style_get_row (sheet
, &sr
);
367 row
= sr
.row
= next_sr
.row
, ri
= next_ri
) {
368 /* Restore the set of ranges seen, but still active.
369 * Reinverting list to maintain the original order */
370 g_return_if_fail (merged_active
== NULL
);
372 while (merged_active_seen
!= NULL
) {
373 GSList
*tmp
= merged_active_seen
->next
;
374 merged_active_seen
->next
= merged_active
;
375 merged_active
= merged_active_seen
;
376 merged_active_seen
= tmp
;
377 MERGE_DEBUG (merged_active
->data
, " : seen -> active\n");
380 /* find the next visible row */
383 if (next_sr
.row
<= end_row
) {
384 next_ri
= sheet_row_get_info (sheet
, next_sr
.row
);
385 if (next_ri
->visible
) {
386 sheet_style_get_row (sheet
, &next_sr
);
390 for (col
= start_col
; col
<= end_col
; ++col
)
391 next_sr
.vertical
[col
] =
392 next_sr
.bottom
[col
] = none
;
397 /* it is safe to const_cast because only a non-default row
398 * will ever get flagged.
400 if (ri
->needs_respan
)
401 row_calc_spans ((ColRowInfo
*)ri
, row
, sheet
);
403 /* look for merges that start on this row, on the first painted row
404 * also check for merges that start above. */
405 view
.start
.row
= row
;
406 lag
= &merged_unused
;
407 for (ptr
= merged_unused
; ptr
!= NULL
; ) {
408 GnmRange
* const r
= ptr
->data
;
410 if (r
->start
.row
<= row
) {
412 ptr
= *lag
= tmp
->next
;
413 if (r
->end
.row
< row
) {
414 tmp
->next
= merged_used
;
416 MERGE_DEBUG (r
, " : unused -> used\n");
418 ColRowInfo
const *ci
=
419 sheet_col_get_info (sheet
, r
->start
.col
);
420 g_slist_free_1 (tmp
);
421 merged_active
= g_slist_insert_sorted (merged_active
, r
,
422 (GCompareFunc
)merged_col_cmp
);
423 MERGE_DEBUG (r
, " : unused -> active\n");
426 print_merged_range_gtk (context
, sheet
,
436 for (col
= start_col
, x
= base_x
; col
<= end_col
; col
++) {
437 GnmStyle
const *style
;
438 CellSpanInfo
const *span
;
439 ColRowInfo
const *ci
= sheet_col_get_info (sheet
, col
);
440 ColRowInfo
const *ri
= sheet_row_get_info (sheet
, row
);
443 if (merged_active
!= NULL
) {
444 GnmRange
const *r
= merged_active
->data
;
445 if (r
->end
.col
== col
) {
447 merged_active
= merged_active
->next
;
448 if (r
->end
.row
<= row
) {
449 ptr
->next
= merged_used
;
451 MERGE_DEBUG (r
, " : active2 -> used\n");
453 ptr
->next
= merged_active_seen
;
454 merged_active_seen
= ptr
;
455 MERGE_DEBUG (r
, " : active2 -> seen\n");
462 /* Skip any merged regions */
464 GnmRange
const *r
= merged_active
->data
;
465 if (r
->start
.col
<= col
) {
466 gboolean clear_top
, clear_bottom
= TRUE
;
467 int i
, first
= r
->start
.col
;
468 int last
= r
->end
.col
;
470 x
+= sheet_col_get_distance_pts (sheet
,
474 if (first
< start_col
) {
476 sr
.vertical
[first
] = NULL
;
478 if (last
> end_col
) {
480 sr
.vertical
[last
+1] = NULL
;
482 clear_top
= (r
->start
.row
!= row
);
485 merged_active
= merged_active
->next
;
486 if (r
->end
.row
<= row
) {
487 clear_bottom
= FALSE
;
488 ptr
->next
= merged_used
;
490 MERGE_DEBUG (r
, " : active -> used\n");
492 ptr
->next
= merged_active_seen
;
493 merged_active_seen
= ptr
;
494 MERGE_DEBUG (r
, " : active -> seen\n");
497 /* Clear the borders */
498 for (i
= first
; i
<= last
; i
++) {
502 sr
.bottom
[i
] = NULL
;
504 sr
.vertical
[i
] = NULL
;
511 x
-= ci
->size_pts
* hscale
;
512 style
= sr
.styles
[col
];
513 print_cell_background_gtk (context
, style
, col
, row
, x
, y
,
514 ci
->size_pts
* hscale
, ri
->size_pts
);
516 /* Is this part of a span?
517 * 1) There are cells allocated in the row
518 * (indicated by ri->spans != NULL)
519 * 2) Look in the rows hash table to see if
520 * there is a span descriptor.
522 if (NULL
== ri
->spans
|| NULL
== (span
= row_span_get (ri
, col
))) {
523 /* no need to draw blanks */
524 GnmCell
const *cell
= sheet_cell_get (sheet
, col
, row
);
525 if (!gnm_cell_is_empty (cell
))
526 print_cell_gtk (cell
, context
, x
, y
,
527 ci
->size_pts
* hscale
,
528 ri
->size_pts
, -1., pinfo
);
530 /* Only draw spaning cells after all the backgrounds
531 * that we are going to draw have been drawn. No need
532 * to draw the edit cell, or blanks.
534 } else if (col
== span
->right
|| col
== end_col
) {
535 GnmCell
const *cell
= span
->cell
;
536 int const start_span_col
= span
->left
;
537 int const end_span_col
= span
->right
;
539 ColRowInfo
const *cell_col
=
540 sheet_col_get_info (sheet
, cell
->pos
.col
);
541 double center_offset
= cell_col
->size_pts
* hscale
/ 2;
542 double tmp_width
= ci
->size_pts
* hscale
;
544 if (col
!= cell
->pos
.col
)
545 style
= sheet_style_get (sheet
,
548 /* x, y are relative to this cell origin, but the cell
549 * might be using columns to the left (if it is set to right
550 * justify or center justify) compute the pixel difference
552 if (start_span_col
!= cell
->pos
.col
)
553 center_offset
+= sheet_col_get_distance_pts (
554 sheet
, start_span_col
, cell
->pos
.col
);
556 if (start_span_col
!= col
) {
557 offset
= sheet_col_get_distance_pts (
558 sheet
, start_span_col
, col
);
562 sr
.vertical
[col
] = NULL
;
564 if (end_span_col
!= col
) {
565 offset
= sheet_col_get_distance_pts (
566 sheet
, col
+1, end_span_col
+ 1);
572 print_cell_gtk (cell
, context
,
573 real_x
, y
, tmp_width
, ri
->size_pts
,
574 center_offset
, pinfo
);
575 } else if (col
!= span
->left
)
576 sr
.vertical
[col
] = NULL
;
579 x
+= ci
->size_pts
* hscale
;
581 gnm_style_borders_row_print_gtk (prev_vert
, &sr
,
582 context
, base_x
, y
, y
+ri
->size_pts
,
585 /* In case there were hidden merges that trailed off the end */
586 while (merged_active
!= NULL
) {
587 GnmRange
const *r
= merged_active
->data
;
589 merged_active
= merged_active
->next
;
590 if (r
->end
.row
<= row
) {
591 ptr
->next
= merged_used
;
593 MERGE_DEBUG (r
, " : active3 -> used\n");
595 ptr
->next
= merged_active_seen
;
596 merged_active_seen
= ptr
;
597 MERGE_DEBUG (r
, " : active3 -> seen\n");
600 /* roll the pointers */
601 borders
= prev_vert
; prev_vert
= sr
.vertical
;
602 sr
.vertical
= next_sr
.vertical
; next_sr
.vertical
= borders
;
603 borders
= sr
.top
; sr
.top
= sr
.bottom
;
604 sr
.bottom
= next_sr
.top
= next_sr
.bottom
; next_sr
.bottom
= borders
;
605 styles
= sr
.styles
; sr
.styles
= next_sr
.styles
; next_sr
.styles
= styles
;
609 gnm_style_borders_row_print_gtk (prev_vert
, &sr
,
610 context
, base_x
, y
, y
, sheet
, FALSE
, dir
);
612 g_slist_free (merged_used
); /* merges with bottom in view */
613 g_slist_free (merged_active_seen
); /* merges with bottom the view */
614 g_slist_free (merged_unused
); /* merges in hidden rows */
615 g_free (sr_array_data
);
616 g_return_if_fail (merged_active
== NULL
);