X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Foutput%2Fcairo.c;h=b1a7e273686861782b471242d296115a5aa0223c;hb=dc23d73ba6a44d3b962c3cafa3b797af14b9db4a;hp=49d8f0bc885a684d426e15236cb3794d94f1d1f1;hpb=6499a5881aeaa1e53b761f624e341652015d795e;p=pspp diff --git a/src/output/cairo.c b/src/output/cairo.c index 49d8f0bc88..b1a7e27368 100644 --- a/src/output/cairo.c +++ b/src/output/cairo.c @@ -20,6 +20,7 @@ #include "libpspp/assertion.h" #include "libpspp/cast.h" +#include "libpspp/hash-functions.h" #include "libpspp/message.h" #include "libpspp/pool.h" #include "libpspp/start-date.h" @@ -39,10 +40,11 @@ #include "output/charts/scree.h" #include "output/charts/scatterplot.h" #include "output/driver-provider.h" +#include "output/group-item.h" #include "output/message-item.h" #include "output/options.h" +#include "output/page-setup-item.h" #include "output/render.h" -#include "output/tab.h" #include "output/table-item.h" #include "output/table.h" #include "output/text-item.h" @@ -51,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +61,7 @@ #include #include +#include "gl/c-ctype.h" #include "gl/c-strcase.h" #include "gl/intprops.h" #include "gl/minmax.h" @@ -70,7 +74,8 @@ #define H TABLE_HORZ #define V TABLE_VERT -/* The unit used for internal measurements is inch/(72 * XR_POINT). */ +/* The unit used for internal measurements is inch/(72 * XR_POINT). + (Thus, XR_POINT units represent one point.) */ #define XR_POINT PANGO_SCALE /* Conversions to and from points. */ @@ -87,6 +92,10 @@ px_to_xr (int x) return x * (PANGO_SCALE * 72 / 96); } +/* Dimensions for drawing lines in tables. */ +#define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */ +#define XR_LINE_SPACE XR_POINT /* Space between double lines. */ + /* Output types. */ enum xr_output_type { @@ -143,9 +152,15 @@ struct xr_driver int line_width; /* Width of lines. */ int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */ + int object_spacing; /* Space between output objects. */ + + struct cell_color bg; /* Background color */ + struct cell_color fg; /* Foreground color */ - struct xr_color bg; /* Background color */ - struct xr_color fg; /* Foreground color */ + int initial_page_number; + + struct page_heading headings[2]; /* Top and bottom headings. */ + int headings_height[2]; /* Internal state. */ struct render_params *params; @@ -154,10 +169,12 @@ struct xr_driver char *title; char *subtitle; cairo_t *cairo; + cairo_surface_t *surface; int page_number; /* Current page number. */ int x, y; struct xr_render_fsm *fsm; int nest; + struct string_map heading_vars; }; static const struct output_driver_class cairo_driver_class; @@ -172,7 +189,7 @@ static void xr_measure_cell_width (void *, const struct table_cell *, int *min, int *max); static int xr_measure_cell_height (void *, const struct table_cell *, int width); -static void xr_draw_cell (void *, const struct table_cell *, +static void xr_draw_cell (void *, const struct table_cell *, int color_idx, int bb[TABLE_N_AXES][2], int spill[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2]); @@ -198,6 +215,256 @@ opt (struct output_driver *d, struct string_map *options, const char *key, return driver_option_get (d, options, key, default_value); } +static int +lookup_color_name (const char *s) +{ + struct color + { + struct hmap_node hmap_node; + const char *name; + int code; + }; + + static struct color colors[] = + { + { .name = "aliceblue", .code = 0xf0f8ff }, + { .name = "antiquewhite", .code = 0xfaebd7 }, + { .name = "aqua", .code = 0x00ffff }, + { .name = "aquamarine", .code = 0x7fffd4 }, + { .name = "azure", .code = 0xf0ffff }, + { .name = "beige", .code = 0xf5f5dc }, + { .name = "bisque", .code = 0xffe4c4 }, + { .name = "black", .code = 0x000000 }, + { .name = "blanchedalmond", .code = 0xffebcd }, + { .name = "blue", .code = 0x0000ff }, + { .name = "blueviolet", .code = 0x8a2be2 }, + { .name = "brown", .code = 0xa52a2a }, + { .name = "burlywood", .code = 0xdeb887 }, + { .name = "cadetblue", .code = 0x5f9ea0 }, + { .name = "chartreuse", .code = 0x7fff00 }, + { .name = "chocolate", .code = 0xd2691e }, + { .name = "coral", .code = 0xff7f50 }, + { .name = "cornflowerblue", .code = 0x6495ed }, + { .name = "cornsilk", .code = 0xfff8dc }, + { .name = "crimson", .code = 0xdc143c }, + { .name = "cyan", .code = 0x00ffff }, + { .name = "darkblue", .code = 0x00008b }, + { .name = "darkcyan", .code = 0x008b8b }, + { .name = "darkgoldenrod", .code = 0xb8860b }, + { .name = "darkgray", .code = 0xa9a9a9 }, + { .name = "darkgreen", .code = 0x006400 }, + { .name = "darkgrey", .code = 0xa9a9a9 }, + { .name = "darkkhaki", .code = 0xbdb76b }, + { .name = "darkmagenta", .code = 0x8b008b }, + { .name = "darkolivegreen", .code = 0x556b2f }, + { .name = "darkorange", .code = 0xff8c00 }, + { .name = "darkorchid", .code = 0x9932cc }, + { .name = "darkred", .code = 0x8b0000 }, + { .name = "darksalmon", .code = 0xe9967a }, + { .name = "darkseagreen", .code = 0x8fbc8f }, + { .name = "darkslateblue", .code = 0x483d8b }, + { .name = "darkslategray", .code = 0x2f4f4f }, + { .name = "darkslategrey", .code = 0x2f4f4f }, + { .name = "darkturquoise", .code = 0x00ced1 }, + { .name = "darkviolet", .code = 0x9400d3 }, + { .name = "deeppink", .code = 0xff1493 }, + { .name = "deepskyblue", .code = 0x00bfff }, + { .name = "dimgray", .code = 0x696969 }, + { .name = "dimgrey", .code = 0x696969 }, + { .name = "dodgerblue", .code = 0x1e90ff }, + { .name = "firebrick", .code = 0xb22222 }, + { .name = "floralwhite", .code = 0xfffaf0 }, + { .name = "forestgreen", .code = 0x228b22 }, + { .name = "fuchsia", .code = 0xff00ff }, + { .name = "gainsboro", .code = 0xdcdcdc }, + { .name = "ghostwhite", .code = 0xf8f8ff }, + { .name = "gold", .code = 0xffd700 }, + { .name = "goldenrod", .code = 0xdaa520 }, + { .name = "gray", .code = 0x808080 }, + { .name = "green", .code = 0x008000 }, + { .name = "greenyellow", .code = 0xadff2f }, + { .name = "grey", .code = 0x808080 }, + { .name = "honeydew", .code = 0xf0fff0 }, + { .name = "hotpink", .code = 0xff69b4 }, + { .name = "indianred", .code = 0xcd5c5c }, + { .name = "indigo", .code = 0x4b0082 }, + { .name = "ivory", .code = 0xfffff0 }, + { .name = "khaki", .code = 0xf0e68c }, + { .name = "lavender", .code = 0xe6e6fa }, + { .name = "lavenderblush", .code = 0xfff0f5 }, + { .name = "lawngreen", .code = 0x7cfc00 }, + { .name = "lemonchiffon", .code = 0xfffacd }, + { .name = "lightblue", .code = 0xadd8e6 }, + { .name = "lightcoral", .code = 0xf08080 }, + { .name = "lightcyan", .code = 0xe0ffff }, + { .name = "lightgoldenrodyellow", .code = 0xfafad2 }, + { .name = "lightgray", .code = 0xd3d3d3 }, + { .name = "lightgreen", .code = 0x90ee90 }, + { .name = "lightgrey", .code = 0xd3d3d3 }, + { .name = "lightpink", .code = 0xffb6c1 }, + { .name = "lightsalmon", .code = 0xffa07a }, + { .name = "lightseagreen", .code = 0x20b2aa }, + { .name = "lightskyblue", .code = 0x87cefa }, + { .name = "lightslategray", .code = 0x778899 }, + { .name = "lightslategrey", .code = 0x778899 }, + { .name = "lightsteelblue", .code = 0xb0c4de }, + { .name = "lightyellow", .code = 0xffffe0 }, + { .name = "lime", .code = 0x00ff00 }, + { .name = "limegreen", .code = 0x32cd32 }, + { .name = "linen", .code = 0xfaf0e6 }, + { .name = "magenta", .code = 0xff00ff }, + { .name = "maroon", .code = 0x800000 }, + { .name = "mediumaquamarine", .code = 0x66cdaa }, + { .name = "mediumblue", .code = 0x0000cd }, + { .name = "mediumorchid", .code = 0xba55d3 }, + { .name = "mediumpurple", .code = 0x9370db }, + { .name = "mediumseagreen", .code = 0x3cb371 }, + { .name = "mediumslateblue", .code = 0x7b68ee }, + { .name = "mediumspringgreen", .code = 0x00fa9a }, + { .name = "mediumturquoise", .code = 0x48d1cc }, + { .name = "mediumvioletred", .code = 0xc71585 }, + { .name = "midnightblue", .code = 0x191970 }, + { .name = "mintcream", .code = 0xf5fffa }, + { .name = "mistyrose", .code = 0xffe4e1 }, + { .name = "moccasin", .code = 0xffe4b5 }, + { .name = "navajowhite", .code = 0xffdead }, + { .name = "navy", .code = 0x000080 }, + { .name = "oldlace", .code = 0xfdf5e6 }, + { .name = "olive", .code = 0x808000 }, + { .name = "olivedrab", .code = 0x6b8e23 }, + { .name = "orange", .code = 0xffa500 }, + { .name = "orangered", .code = 0xff4500 }, + { .name = "orchid", .code = 0xda70d6 }, + { .name = "palegoldenrod", .code = 0xeee8aa }, + { .name = "palegreen", .code = 0x98fb98 }, + { .name = "paleturquoise", .code = 0xafeeee }, + { .name = "palevioletred", .code = 0xdb7093 }, + { .name = "papayawhip", .code = 0xffefd5 }, + { .name = "peachpuff", .code = 0xffdab9 }, + { .name = "peru", .code = 0xcd853f }, + { .name = "pink", .code = 0xffc0cb }, + { .name = "plum", .code = 0xdda0dd }, + { .name = "powderblue", .code = 0xb0e0e6 }, + { .name = "purple", .code = 0x800080 }, + { .name = "red", .code = 0xff0000 }, + { .name = "rosybrown", .code = 0xbc8f8f }, + { .name = "royalblue", .code = 0x4169e1 }, + { .name = "saddlebrown", .code = 0x8b4513 }, + { .name = "salmon", .code = 0xfa8072 }, + { .name = "sandybrown", .code = 0xf4a460 }, + { .name = "seagreen", .code = 0x2e8b57 }, + { .name = "seashell", .code = 0xfff5ee }, + { .name = "sienna", .code = 0xa0522d }, + { .name = "silver", .code = 0xc0c0c0 }, + { .name = "skyblue", .code = 0x87ceeb }, + { .name = "slateblue", .code = 0x6a5acd }, + { .name = "slategray", .code = 0x708090 }, + { .name = "slategrey", .code = 0x708090 }, + { .name = "snow", .code = 0xfffafa }, + { .name = "springgreen", .code = 0x00ff7f }, + { .name = "steelblue", .code = 0x4682b4 }, + { .name = "tan", .code = 0xd2b48c }, + { .name = "teal", .code = 0x008080 }, + { .name = "thistle", .code = 0xd8bfd8 }, + { .name = "tomato", .code = 0xff6347 }, + { .name = "turquoise", .code = 0x40e0d0 }, + { .name = "violet", .code = 0xee82ee }, + { .name = "wheat", .code = 0xf5deb3 }, + { .name = "white", .code = 0xffffff }, + { .name = "whitesmoke", .code = 0xf5f5f5 }, + { .name = "yellow", .code = 0xffff00 }, + { .name = "yellowgreen", .code = 0x9acd32 }, + }; + + static struct hmap color_table = HMAP_INITIALIZER (color_table); + + if (hmap_is_empty (&color_table)) + for (size_t i = 0; i < sizeof colors / sizeof *colors; i++) + hmap_insert (&color_table, &colors[i].hmap_node, + hash_string (colors[i].name, 0)); + + const struct color *color; + HMAP_FOR_EACH_WITH_HASH (color, struct color, hmap_node, + hash_string (s, 0), &color_table) + if (!strcmp (color->name, s)) + return color->code; + return -1; +} + +static bool +parse_color__ (const char *s, struct cell_color *color) +{ + /* #rrrrggggbbbb */ + uint16_t r16, g16, b16; + int len; + if (sscanf (s, "#%4"SCNx16"%4"SCNx16"%4"SCNx16"%n", + &r16, &g16, &b16, &len) == 3 + && len == 13 + && !s[len]) + { + color->r = r16 >> 8; + color->g = g16 >> 8; + color->b = b16 >> 8; + return true; + } + + /* #rrggbb */ + uint8_t r, g, b; + if (sscanf (s, "#%2"SCNx8"%2"SCNx8"%2"SCNx8"%n", &r, &g, &b, &len) == 3 + && len == 7 + && !s[len]) + { + color->r = r; + color->g = g; + color->b = b; + return true; + } + + /* rrggbb */ + if (sscanf (s, "%2"SCNx8"%2"SCNx8"%2"SCNx8"%n", &r, &g, &b, &len) == 3 + && len == 6 + && !s[len]) + { + color->r = r; + color->g = g; + color->b = b; + return true; + } + + /* rgb(r,g,b) */ + if (sscanf (s, "rgb (%"SCNi8" , %"SCNi8" , %"SCNi8" ) %n", + &r, &g, &b, &len) == 3 + && !s[len]) + { + color->r = r; + color->g = g; + color->b = b; + return true; + } + + /* rgba(r,g,b,a), ignoring a. */ + if (sscanf (s, "rgba (%"SCNi8" , %"SCNi8" , %"SCNi8", %*f ) %n", + &r, &g, &b, &len) == 3 + && !s[len]) + { + color->r = r; + color->g = g; + color->b = b; + return true; + } + + int code = lookup_color_name (s); + if (code >= 0) + { + color->r = code >> 16; + color->g = code >> 8; + color->b = code; + return true; + } + + return false; +} + /* Parse color information specified by KEY into {RED,GREEN,BLUE}. Currently, the input string must be of the form "#RRRRGGGGBBBB" Future implementations might allow things like "yellow" and @@ -206,27 +473,12 @@ opt (struct output_driver *d, struct string_map *options, const char *key, void parse_color (struct output_driver *d, struct string_map *options, const char *key, const char *default_value, - struct xr_color *color) + struct cell_color *color) { - int red, green, blue; char *string = parse_string (opt (d, options, key, default_value)); - - if (3 != sscanf (string, "#%04x%04x%04x", &red, &green, &blue)) - { - /* If the parsed option string fails, then try the default value */ - if ( 3 != sscanf (default_value, "#%04x%04x%04x", &red, &green, &blue)) - { - /* ... and if that fails set everything to zero */ - red = green = blue = 0; - } - } - + if (!parse_color__ (string, color) && !parse_color__ (default_value, color)) + *color = (struct cell_color) CELL_COLOR_BLACK; free (string); - - /* Convert 16 bit ints to float */ - color->red = red / (double) 0xFFFF; - color->green = green / (double) 0xFFFF; - color->blue = blue / (double) 0xFFFF; } static PangoFontDescription * @@ -309,10 +561,6 @@ apply_options (struct xr_driver *xr, struct string_map *o) xr->fonts[XR_FONT_EMPHASIS].desc = parse_font_option ( d, o, "emph-font", "sans serif", font_size, false, true); - xr->line_space = XR_POINT; - xr->line_width = XR_POINT / 2; - xr->page_number = 0; - parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg); parse_color (d, o, "foreground-color", "#000000000000", &xr->fg); @@ -326,6 +574,9 @@ apply_options (struct xr_driver *xr, struct string_map *o) min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale; min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale; + int object_spacing = (parse_dimension (opt (d, o, "object-spacing", NULL)) + * scale); + /* Convert to inch/(XR_POINT * 72). */ xr->left_margin = left_margin * scale; xr->right_margin = right_margin * scale; @@ -335,6 +586,9 @@ apply_options (struct xr_driver *xr, struct string_map *o) xr->length = (paper_length - top_margin - bottom_margin) * scale; xr->min_break[H] = min_break[H] >= 0 ? min_break[H] : xr->width / 2; xr->min_break[V] = min_break[V] >= 0 ? min_break[V] : xr->length / 2; + xr->object_spacing = object_spacing >= 0 ? object_spacing : XR_POINT * 12; + + /* There are no headings so headings_height can stay 0. */ } static struct xr_driver * @@ -345,6 +599,8 @@ xr_allocate (const char *name, int device_type, struct string_map *o) output_driver_init (d, &cairo_driver_class, name, device_type); + string_map_init (&xr->heading_vars); + apply_options (xr, o); return xr; @@ -366,29 +622,147 @@ xr_to_pango (int xr) : xr); } +static void +xr_measure_fonts (cairo_t *cairo, const struct xr_font fonts[XR_N_FONTS], + int *char_width, int *char_height) +{ + *char_width = 0; + *char_height = 0; + for (int i = 0; i < XR_N_FONTS; i++) + { + PangoLayout *layout = pango_cairo_create_layout (cairo); + pango_layout_set_font_description (layout, fonts[i].desc); + + pango_layout_set_text (layout, "0", 1); + + int cw, ch; + pango_layout_get_size (layout, &cw, &ch); + *char_width = MAX (*char_width, pango_to_xr (cw)); + *char_height = MAX (*char_height, pango_to_xr (ch)); + + g_object_unref (G_OBJECT (layout)); + } +} + +static int +get_layout_height (PangoLayout *layout) +{ + int w, h; + pango_layout_get_size (layout, &w, &h); + return h; +} + +static int +xr_render_page_heading (cairo_t *cairo, const PangoFontDescription *font, + const struct page_heading *ph, int page_number, + int width, bool draw, int base_y) +{ + PangoLayout *layout = pango_cairo_create_layout (cairo); + pango_layout_set_font_description (layout, font); + + int y = 0; + for (size_t i = 0; i < ph->n; i++) + { + const struct page_paragraph *pp = &ph->paragraphs[i]; + + char *markup = output_driver_substitute_heading_vars (pp->markup, + page_number); + pango_layout_set_markup (layout, markup, -1); + free (markup); + + pango_layout_set_alignment ( + layout, + (pp->halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT + : pp->halign == TABLE_HALIGN_CENTER ? PANGO_ALIGN_CENTER + : pp->halign == TABLE_HALIGN_MIXED ? PANGO_ALIGN_LEFT + : PANGO_ALIGN_RIGHT)); + pango_layout_set_width (layout, xr_to_pango (width)); + if (draw) + { + cairo_save (cairo); + cairo_translate (cairo, 0, xr_to_pt (y + base_y)); + pango_cairo_show_layout (cairo, layout); + cairo_restore (cairo); + } + + y += pango_to_xr (get_layout_height (layout)); + } + + g_object_unref (G_OBJECT (layout)); + + return y; +} + +static int +xr_measure_headings (cairo_surface_t *surface, + const PangoFontDescription *font, + const struct page_heading headings[2], + int width, int object_spacing, int height[2]) +{ + cairo_t *cairo = cairo_create (surface); + int total = 0; + for (int i = 0; i < 2; i++) + { + int h = xr_render_page_heading (cairo, font, &headings[i], -1, + width, false, 0); + + /* If the top heading is nonempty, add some space below it. */ + if (h && i == 0) + h += object_spacing; + + if (height) + height[i] = h; + total += h; + } + cairo_destroy (cairo); + return total; +} + static bool -xr_set_cairo (struct xr_driver *xr, cairo_t *cairo) +xr_check_fonts (cairo_surface_t *surface, + const struct xr_font fonts[XR_N_FONTS], + int usable_width, int usable_length) { - int i; + cairo_t *cairo = cairo_create (surface); + int char_width, char_height; + xr_measure_fonts (cairo, fonts, &char_width, &char_height); + cairo_destroy (cairo); + + bool ok = true; + enum { MIN_WIDTH = 3, MIN_LENGTH = 3 }; + if (usable_width / char_width < MIN_WIDTH) + { + msg (ME, _("The defined page is not wide enough to hold at least %d " + "characters in the default font. In fact, there's only " + "room for %d characters."), + MIN_WIDTH, usable_width / char_width); + ok = false; + } + if (usable_length / char_height < MIN_LENGTH) + { + msg (ME, _("The defined page is not long enough to hold at least %d " + "lines in the default font. In fact, there's only " + "room for %d lines."), + MIN_LENGTH, usable_length / char_height); + ok = false; + } + return ok; +} +static void +xr_set_cairo (struct xr_driver *xr, cairo_t *cairo) +{ xr->cairo = cairo; - cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width)); + cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH)); - xr->char_width = 0; - xr->char_height = 0; - for (i = 0; i < XR_N_FONTS; i++) + xr_measure_fonts (xr->cairo, xr->fonts, &xr->char_width, &xr->char_height); + + for (int i = 0; i < XR_N_FONTS; i++) { struct xr_font *font = &xr->fonts[i]; - int char_width, char_height; - font->layout = pango_cairo_create_layout (cairo); pango_layout_set_font_description (font->layout, font->desc); - - pango_layout_set_text (font->layout, "0", 1); - pango_layout_get_size (font->layout, &char_width, &char_height); - xr->char_width = MAX (xr->char_width, pango_to_xr (char_width)); - xr->char_height = MAX (xr->char_height, pango_to_xr (char_height)); } if (xr->params == NULL) @@ -405,9 +779,9 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo) xr->params->font_size[H] = xr->char_width; xr->params->font_size[V] = xr->char_height; - int lw = xr->line_width; - int ls = xr->line_space; - for (i = 0; i < TABLE_N_AXES; i++) + int lw = XR_LINE_WIDTH; + int ls = XR_LINE_SPACE; + for (int i = 0; i < TABLE_N_AXES; i++) { xr->params->line_widths[i][RENDER_LINE_NONE] = 0; xr->params->line_widths[i][RENDER_LINE_SINGLE] = lw; @@ -417,23 +791,21 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo) xr->params->line_widths[i][RENDER_LINE_DOUBLE] = 2 * lw + ls; } - for (i = 0; i < TABLE_N_AXES; i++) + for (int i = 0; i < TABLE_N_AXES; i++) xr->params->min_break[i] = xr->min_break[i]; xr->params->supports_margins = true; + xr->params->rtl = render_direction_rtl (); } - cairo_set_source_rgb (xr->cairo, xr->fg.red, xr->fg.green, xr->fg.blue); - - return true; + cairo_set_source_rgb (xr->cairo, + xr->fg.r / 255.0, xr->fg.g / 255.0, xr->fg.b / 255.0); } static struct output_driver * xr_create (const char *file_name, enum settings_output_devices device_type, struct string_map *o, enum xr_output_type file_type) { - enum { MIN_WIDTH = 3, MIN_LENGTH = 3 }; struct xr_driver *xr; - cairo_surface_t *surface; cairo_status_t status; double width_pt, length_pt; @@ -442,52 +814,25 @@ xr_create (const char *file_name, enum settings_output_devices device_type, width_pt = xr_to_pt (xr->width + xr->left_margin + xr->right_margin); length_pt = xr_to_pt (xr->length + xr->top_margin + xr->bottom_margin); if (file_type == XR_PDF) - surface = cairo_pdf_surface_create (file_name, width_pt, length_pt); + xr->surface = cairo_pdf_surface_create (file_name, width_pt, length_pt); else if (file_type == XR_PS) - surface = cairo_ps_surface_create (file_name, width_pt, length_pt); + xr->surface = cairo_ps_surface_create (file_name, width_pt, length_pt); else if (file_type == XR_SVG) - surface = cairo_svg_surface_create (file_name, width_pt, length_pt); + xr->surface = cairo_svg_surface_create (file_name, width_pt, length_pt); else NOT_REACHED (); - status = cairo_surface_status (surface); + status = cairo_surface_status (xr->surface); if (status != CAIRO_STATUS_SUCCESS) { msg (ME, _("error opening output file `%s': %s"), file_name, cairo_status_to_string (status)); - cairo_surface_destroy (surface); goto error; } - xr->cairo = cairo_create (surface); - cairo_surface_destroy (surface); - - if (!xr_set_cairo (xr, xr->cairo)) + if (!xr_check_fonts (xr->surface, xr->fonts, xr->width, xr->length)) goto error; - cairo_save (xr->cairo); - xr_driver_next_page (xr, xr->cairo); - - if (xr->width / xr->char_width < MIN_WIDTH) - { - msg (ME, _("The defined page is not wide enough to hold at least %d " - "characters in the default font. In fact, there's only " - "room for %d characters."), - MIN_WIDTH, - xr->width / xr->char_width); - goto error; - } - - if (xr->length / xr->char_height < MIN_LENGTH) - { - msg (ME, _("The defined page is not long enough to hold at least %d " - "lines in the default font. In fact, there's only " - "room for %d lines."), - MIN_LENGTH, - xr->length / xr->char_height); - goto error; - } - return &xr->driver; error: @@ -532,14 +877,14 @@ xr_destroy (struct output_driver *driver) if (xr->cairo != NULL) { - cairo_status_t status; - - cairo_surface_finish (cairo_get_target (xr->cairo)); - status = cairo_status (xr->cairo); + cairo_surface_finish (xr->surface); + cairo_status_t status = cairo_status (xr->cairo); if (status != CAIRO_STATUS_SUCCESS) msg (ME, _("error drawing output for %s driver: %s"), output_driver_get_name (driver), cairo_status_to_string (status)); + cairo_surface_destroy (xr->surface); + cairo_destroy (xr->cairo); } @@ -565,11 +910,69 @@ xr_flush (struct output_driver *driver) cairo_surface_flush (cairo_get_target (xr->cairo)); } +static void +xr_update_page_setup (struct output_driver *driver, + const struct page_setup *ps) +{ + struct xr_driver *xr = xr_driver_cast (driver); + + xr->initial_page_number = ps->initial_page_number; + xr->object_spacing = ps->object_spacing * 72 * XR_POINT; + + if (xr->cairo) + return; + + int usable[TABLE_N_AXES]; + for (int i = 0; i < 2; i++) + usable[i] = (ps->paper[i] + - (ps->margins[i][0] + ps->margins[i][1])) * 72 * XR_POINT; + + int headings_height[2]; + usable[V] -= xr_measure_headings ( + xr->surface, xr->fonts[XR_FONT_PROPORTIONAL].desc, ps->headings, + usable[H], xr->object_spacing, headings_height); + + enum table_axis h = ps->orientation == PAGE_LANDSCAPE; + enum table_axis v = !h; + if (!xr_check_fonts (xr->surface, xr->fonts, usable[h], usable[v])) + return; + + for (int i = 0; i < 2; i++) + { + page_heading_uninit (&xr->headings[i]); + page_heading_copy (&xr->headings[i], &ps->headings[i]); + xr->headings_height[i] = headings_height[i]; + } + xr->width = usable[h]; + xr->length = usable[v]; + xr->left_margin = ps->margins[h][0] * 72 * XR_POINT; + xr->right_margin = ps->margins[h][1] * 72 * XR_POINT; + xr->top_margin = ps->margins[v][0] * 72 * XR_POINT; + xr->bottom_margin = ps->margins[v][1] * 72 * XR_POINT; + cairo_pdf_surface_set_size (xr->surface, + ps->paper[h] * 72.0, ps->paper[v] * 72.0); +} + static void xr_submit (struct output_driver *driver, const struct output_item *output_item) { struct xr_driver *xr = xr_driver_cast (driver); + if (is_page_setup_item (output_item)) + { + xr_update_page_setup (driver, + to_page_setup_item (output_item)->page_setup); + return; + } + + if (!xr->cairo) + { + xr->page_number = xr->initial_page_number - 1; + xr_set_cairo (xr, cairo_create (xr->surface)); + cairo_save (xr->cairo); + xr_driver_next_page (xr, xr->cairo); + } + xr_driver_output_item (xr, output_item); while (xr_driver_need_new_page (xr)) { @@ -593,18 +996,27 @@ void xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo) { cairo_save (cairo); - cairo_set_source_rgb (cairo, xr->bg.red, xr->bg.green, xr->bg.blue); + cairo_set_source_rgb (cairo, + xr->bg.r / 255.0, xr->bg.g / 255.0, xr->bg.b / 255.0); cairo_rectangle (cairo, 0, 0, xr->width, xr->length); cairo_fill (cairo); cairo_restore (cairo); cairo_translate (cairo, xr_to_pt (xr->left_margin), - xr_to_pt (xr->top_margin)); + xr_to_pt (xr->top_margin + xr->headings_height[0])); xr->page_number++; xr->cairo = cairo; xr->x = xr->y = 0; + + xr_render_page_heading (xr->cairo, xr->fonts[XR_FONT_PROPORTIONAL].desc, + &xr->headings[0], xr->page_number, xr->width, true, + -xr->headings_height[0]); + xr_render_page_heading (xr->cairo, xr->fonts[XR_FONT_PROPORTIONAL].desc, + &xr->headings[1], xr->page_number, xr->width, true, + xr->length); + xr_driver_run_fsm (xr); } @@ -658,18 +1070,25 @@ xr_layout_cell (struct xr_driver *, const struct table_cell *, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], int *width, int *height, int *brk); +static void +set_source_rgba (cairo_t *cairo, const struct cell_color *color) +{ + cairo_set_source_rgba (cairo, + color->r / 255., color->g / 255., color->b / 255., + color->alpha / 255.); +} + static void dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1, int style, const struct cell_color *color) { cairo_new_path (xr->cairo); - cairo_set_source_rgb (xr->cairo, - color->r / 255.0, color->g / 255.0, color->b / 255.0); + set_source_rgba (xr->cairo, color); cairo_set_line_width ( xr->cairo, - xr_to_pt (style == RENDER_LINE_THICK ? xr->line_width * 2 - : style == RENDER_LINE_THIN ? xr->line_width / 2 - : xr->line_width)); + xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2 + : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2 + : XR_LINE_WIDTH)); cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y)); cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y)); cairo_stroke (xr->cairo); @@ -679,7 +1098,7 @@ static void UNUSED dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1) { cairo_new_path (xr->cairo); - cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width)); + cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH)); cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y)); cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y0 + xr->y)); cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y)); @@ -692,7 +1111,7 @@ static void fill_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1) { cairo_new_path (xr->cairo); - cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width)); + cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH)); cairo_rectangle (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y), xr_to_pt (x1 - x0), xr_to_pt (y1 - y0)); @@ -798,7 +1217,7 @@ xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2], struct xr_driver *xr = xr_; /* Offset from center of each line in a pair of double lines. */ - int double_line_ofs = (xr->line_space + xr->line_width) / 2; + int double_line_ofs = (XR_LINE_SPACE + XR_LINE_WIDTH) / 2; /* Are the lines along each axis single or double? (It doesn't make sense to have different kinds of line on the @@ -890,11 +1309,11 @@ xr_measure_cell_width (void *xr_, const struct table_cell *cell, xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL); if (*min_width > 0) - *min_width += px_to_xr (cell->style->margin[H][0] - + cell->style->margin[H][1]); + *min_width += px_to_xr (cell->style->cell_style.margin[H][0] + + cell->style->cell_style.margin[H][1]); if (*max_width > 0) - *max_width += px_to_xr (cell->style->margin[H][0] - + cell->style->margin[H][1]); + *max_width += px_to_xr (cell->style->cell_style.margin[H][0] + + cell->style->cell_style.margin[H][1]); } static int @@ -906,20 +1325,21 @@ xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width) int w, h; bb[H][0] = 0; - bb[H][1] = width - px_to_xr (cell->style->margin[H][0] - + cell->style->margin[H][1]); + bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0] + + cell->style->cell_style.margin[H][1]); bb[V][0] = 0; bb[V][1] = INT_MAX; clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL); - h += px_to_xr (cell->style->margin[V][0] + cell->style->margin[V][1]); + h += px_to_xr (cell->style->cell_style.margin[V][0] + + cell->style->cell_style.margin[V][1]); return h; } static void xr_clip (struct xr_driver *, int clip[TABLE_N_AXES][2]); static void -xr_draw_cell (void *xr_, const struct table_cell *cell, +xr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx, int bb[TABLE_N_AXES][2], int spill[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2]) @@ -940,10 +1360,7 @@ xr_draw_cell (void *xr_, const struct table_cell *cell, bg_clip[axis][1] += spill[axis][1]; } xr_clip (xr, bg_clip); - cairo_set_source_rgb (xr->cairo, - cell->style->bg.r / 255., - cell->style->bg.g / 255., - cell->style->bg.b / 255.); + set_source_rgba (xr->cairo, &cell->style->font_style.bg[color_idx]); fill_rectangle (xr, bb[H][0] - spill[H][0], bb[V][0] - spill[V][0], @@ -952,15 +1369,12 @@ xr_draw_cell (void *xr_, const struct table_cell *cell, cairo_restore (xr->cairo); cairo_save (xr->cairo); - cairo_set_source_rgb (xr->cairo, - cell->style->fg.r / 255., - cell->style->fg.g / 255., - cell->style->fg.b / 255.); + set_source_rgba (xr->cairo, &cell->style->font_style.fg[color_idx]); for (int axis = 0; axis < TABLE_N_AXES; axis++) { - bb[axis][0] += px_to_xr (cell->style->margin[axis][0]); - bb[axis][1] -= px_to_xr (cell->style->margin[axis][1]); + bb[axis][0] += px_to_xr (cell->style->cell_style.margin[axis][0]); + bb[axis][1] -= px_to_xr (cell->style->cell_style.margin[axis][1]); } if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1]) xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk); @@ -980,13 +1394,13 @@ xr_adjust_break (void *xr_, const struct table_cell *cell, return -1; bb[H][0] = 0; - bb[H][1] = width - px_to_xr (cell->style->margin[H][0] - + cell->style->margin[H][1]); + bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0] + + cell->style->cell_style.margin[H][1]); if (bb[H][1] <= 0) return 0; bb[V][0] = 0; - bb[V][1] = height - px_to_xr (cell->style->margin[V][0] - + cell->style->margin[V][1]); + bb[V][1] = height - px_to_xr (cell->style->cell_style.margin[V][0] + + cell->style->cell_style.margin[V][1]); clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk); return brk; @@ -1008,33 +1422,77 @@ xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2]) } static void -add_attr_with_start (PangoAttrList *list, PangoAttribute *attr, guint start_index) +add_attr (PangoAttrList *list, PangoAttribute *attr, + guint start_index, guint end_index) { attr->start_index = start_index; + attr->end_index = end_index; pango_attr_list_insert (list, attr); } +static void +markup_escape (struct string *out, unsigned int options, + const char *in, size_t len) +{ + if (!(options & TAB_MARKUP)) + { + ds_put_substring (out, ss_buffer (in, len == -1 ? strlen (in) : len)); + return; + } + + while (len-- > 0) + { + int c = *in++; + switch (c) + { + case 0: + return; + case '&': + ds_put_cstr (out, "&"); + break; + case '<': + ds_put_cstr (out, "<"); + break; + case '>': + ds_put_cstr (out, ">"); + break; + default: + ds_put_byte (out, c); + break; + } + } +} + +static int +get_layout_dimension (PangoLayout *layout, enum table_axis axis) +{ + int size[TABLE_N_AXES]; + pango_layout_get_size (layout, &size[H], &size[V]); + return size[axis]; +} + static int -xr_layout_cell_text (struct xr_driver *xr, - const struct cell_contents *contents, - const struct cell_style *style, +xr_layout_cell_text (struct xr_driver *xr, const struct table_cell *cell, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], int *widthp, int *brk) { - unsigned int options = contents->options; - size_t length; - int w, h; + const struct font_style *font_style = &cell->style->font_style; + const struct cell_style *cell_style = &cell->style->cell_style; + unsigned int options = cell->options; + + enum table_axis X = options & TAB_ROTATE ? V : H; + enum table_axis Y = !X; + int R = options & TAB_ROTATE ? 0 : 1; struct xr_font *font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED] - : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS] : &xr->fonts[XR_FONT_PROPORTIONAL]); struct xr_font local_font; - if (style->font) + if (font_style->typeface) { PangoFontDescription *desc = parse_font ( - style->font, - style->font_size ? style->font_size * 1000 * 72 / 128 : 10000, - style->bold, style->italic); + font_style->typeface, + font_style->size ? font_style->size * 1000 * 72 / 128 : 10000, + font_style->bold, font_style->italic); if (desc) { PangoLayout *layout = pango_cairo_create_layout (xr->cairo); @@ -1046,83 +1504,193 @@ xr_layout_cell_text (struct xr_driver *xr, } } - int footnote_adjustment; - if (contents->n_footnotes == 0) - footnote_adjustment = 0; - else if (contents->n_footnotes == 1 && (options & TAB_HALIGN) == TAB_RIGHT) + const char *text = cell->text; + enum table_halign halign = table_halign_interpret ( + cell_style->halign, cell->options & TAB_NUMERIC); + if (cell_style->halign == TABLE_HALIGN_DECIMAL && !(options & TAB_ROTATE)) { - PangoAttrList *attrs; + int margin_adjustment = -px_to_xr (cell_style->decimal_offset); - const char *marker = contents->footnotes[0]->marker; - pango_layout_set_text (font->layout, marker, strlen (marker)); + const char *decimal = strrchr (text, cell_style->decimal_char); + if (decimal) + { + pango_layout_set_text (font->layout, decimal, strlen (decimal)); + pango_layout_set_width (font->layout, -1); + margin_adjustment += get_layout_dimension (font->layout, H); + } - attrs = pango_attr_list_new (); - pango_attr_list_insert (attrs, pango_attr_rise_new (7000)); - pango_layout_set_attributes (font->layout, attrs); - pango_attr_list_unref (attrs); + if (margin_adjustment < 0) + bb[H][1] += margin_adjustment; + } + + struct string tmp = DS_EMPTY_INITIALIZER; + PangoAttrList *attrs = NULL; + + /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in + Pango's implementation of it): it will break after a period or a comma + that precedes a digit, e.g. in ".000" it will break after the period. + This code looks for such a situation and inserts a U+2060 WORD JOINER + to prevent the break. - pango_layout_get_size (font->layout, &w, &h); - footnote_adjustment = MIN (w, px_to_xr (style->margin[H][1])); + This isn't necessary when the decimal point is between two digits + (e.g. "0.000" won't be broken) or when the display width is not limited so + that word wrapping won't happen. + + It isn't necessary to look for more than one period or comma, as would + happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups + are present then there will always be a digit on both sides of every + period and comma. */ + if (options & TAB_MARKUP) + { + PangoAttrList *new_attrs; + char *new_text; + if (pango_parse_markup (text, -1, 0, &new_attrs, &new_text, NULL, NULL)) + { + attrs = new_attrs; + tmp.ss = ss_cstr (new_text); + tmp.capacity = tmp.ss.length; + } + else + { + /* XXX should we report the error? */ + ds_put_cstr (&tmp, text); + } + } + else if (options & TAB_ROTATE || bb[H][1] != INT_MAX) + { + const char *decimal = text + strcspn (text, ".,"); + if (decimal[0] + && c_isdigit (decimal[1]) + && (decimal == text || !c_isdigit (decimal[-1]))) + { + ds_extend (&tmp, strlen (text) + 16); + markup_escape (&tmp, options, text, decimal - text + 1); + ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */); + markup_escape (&tmp, options, decimal + 1, -1); + } } - else - footnote_adjustment = px_to_xr (style->margin[H][1]); - length = strlen (contents->text); - if (footnote_adjustment) + if (font_style->underline) { - PangoAttrList *attrs; - struct string s; - - bb[H][1] += footnote_adjustment; - - ds_init_empty (&s); - ds_extend (&s, length + contents->n_footnotes * 10); - ds_put_cstr (&s, contents->text); - cell_contents_format_footnote_markers (contents, &s); - pango_layout_set_text (font->layout, ds_cstr (&s), ds_length (&s)); - ds_destroy (&s); - - attrs = pango_attr_list_new (); - if (style->underline) - pango_attr_list_insert (attrs, pango_attr_underline_new ( - PANGO_UNDERLINE_SINGLE)); - add_attr_with_start (attrs, pango_attr_rise_new (7000), length); - add_attr_with_start ( - attrs, pango_attr_font_desc_new (font->desc), length); - pango_layout_set_attributes (font->layout, attrs); - pango_attr_list_unref (attrs); + if (!attrs) + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_underline_new ( + PANGO_UNDERLINE_SINGLE)); } - else + + if (cell->n_footnotes || cell->n_subscripts || cell->superscript) { - pango_layout_set_text (font->layout, contents->text, -1); + /* If we haven't already put TEXT into tmp, do it now. */ + if (ds_is_empty (&tmp)) + { + ds_extend (&tmp, strlen (text) + 16); + markup_escape (&tmp, options, text, -1); + } + + size_t subscript_ofs = ds_length (&tmp); + for (size_t i = 0; i < cell->n_subscripts; i++) + { + if (i) + ds_put_byte (&tmp, ','); + ds_put_cstr (&tmp, cell->subscripts[i]); + } + + size_t superscript_ofs = ds_length (&tmp); + if (cell->superscript) + ds_put_cstr (&tmp, cell->superscript); + + size_t footnote_ofs = ds_length (&tmp); + for (size_t i = 0; i < cell->n_footnotes; i++) + { + if (i) + ds_put_byte (&tmp, ','); + ds_put_cstr (&tmp, cell->footnotes[i]->marker); + } - if (style->underline) + /* Allow footnote markers to occupy the right margin. That way, numbers + in the column are still aligned. */ + if (cell->n_footnotes && halign == TABLE_HALIGN_RIGHT) { - PangoAttrList *attrs = pango_attr_list_new (); - pango_attr_list_insert (attrs, pango_attr_underline_new ( - PANGO_UNDERLINE_SINGLE)); - pango_layout_set_attributes (font->layout, attrs); - pango_attr_list_unref (attrs); + /* Measure the width of the footnote marker, so we know how much we + need to make room for. */ + pango_layout_set_text (font->layout, ds_cstr (&tmp) + footnote_ofs, + ds_length (&tmp) - footnote_ofs); + + PangoAttrList *fn_attrs = pango_attr_list_new (); + pango_attr_list_insert ( + fn_attrs, pango_attr_scale_new (PANGO_SCALE_SMALL)); + pango_attr_list_insert (fn_attrs, pango_attr_rise_new (3000)); + pango_layout_set_attributes (font->layout, fn_attrs); + pango_attr_list_unref (fn_attrs); + int footnote_width = get_layout_dimension (font->layout, X); + + /* Bound the adjustment by the width of the right margin. */ + int right_margin = px_to_xr (cell_style->margin[X][R]); + int footnote_adjustment = MIN (footnote_width, right_margin); + + /* Adjust the bounding box. */ + if (options & TAB_ROTATE) + footnote_adjustment = -footnote_adjustment; + bb[X][R] += footnote_adjustment; + + /* Clean up. */ + pango_layout_set_attributes (font->layout, NULL); } + + /* Set attributes. */ + if (!attrs) + attrs = pango_attr_list_new (); + add_attr (attrs, pango_attr_font_desc_new (font->desc), subscript_ofs, + PANGO_ATTR_INDEX_TO_TEXT_END); + add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL), + subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END); + if (cell->n_subscripts) + add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs, + superscript_ofs - subscript_ofs); + if (cell->superscript || cell->n_footnotes) + add_attr (attrs, pango_attr_rise_new (3000), superscript_ofs, + PANGO_ATTR_INDEX_TO_TEXT_END); } - pango_layout_set_alignment ( - font->layout, - ((options & TAB_HALIGN) == TAB_RIGHT ? PANGO_ALIGN_RIGHT - : (options & TAB_HALIGN) == TAB_LEFT ? PANGO_ALIGN_LEFT - : PANGO_ALIGN_CENTER)); + /* Set the attributes, if any. */ + if (attrs) + { + pango_layout_set_attributes (font->layout, attrs); + pango_attr_list_unref (attrs); + } + + /* Set the text. */ + if (ds_is_empty (&tmp)) + pango_layout_set_text (font->layout, text, -1); + else + pango_layout_set_text (font->layout, ds_cstr (&tmp), ds_length (&tmp)); + ds_destroy (&tmp); + + pango_layout_set_alignment (font->layout, + (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT + : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT + : PANGO_ALIGN_CENTER)); pango_layout_set_width ( font->layout, - bb[H][1] == INT_MAX ? -1 : xr_to_pango (bb[H][1] - bb[H][0])); + bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0])); pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD); if (clip[H][0] != clip[H][1]) { cairo_save (xr->cairo); - xr_clip (xr, clip); - cairo_translate (xr->cairo, - xr_to_pt (bb[H][0] + xr->x), - xr_to_pt (bb[V][0] + xr->y)); + if (!(options & TAB_ROTATE)) + xr_clip (xr, clip); + if (options & TAB_ROTATE) + { + cairo_translate (xr->cairo, + xr_to_pt (bb[H][0] + xr->x), + xr_to_pt (bb[V][1] + xr->y)); + cairo_rotate (xr->cairo, -M_PI_2); + } + else + cairo_translate (xr->cairo, + xr_to_pt (bb[H][0] + xr->x), + xr_to_pt (bb[V][0] + xr->y)); pango_cairo_show_layout (xr->cairo, font->layout); /* If enabled, this draws a blue rectangle around the extents of each @@ -1153,15 +1721,16 @@ xr_layout_cell_text (struct xr_driver *xr, cairo_restore (xr->cairo); } - pango_layout_get_size (font->layout, &w, &h); - w = pango_to_xr (w); - h = pango_to_xr (h); + int size[TABLE_N_AXES]; + pango_layout_get_size (font->layout, &size[H], &size[V]); + int w = pango_to_xr (size[X]); + int h = pango_to_xr (size[Y]); if (w > *widthp) *widthp = w; - if (bb[V][0] + h >= bb[V][1]) + if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE)) { PangoLayoutIter *iter; - int best UNUSED = 0; + int best = 0; /* Choose a breakpoint between lines instead of in the middle of one. */ iter = pango_layout_get_iter (font->layout); @@ -1198,7 +1767,8 @@ xr_layout_cell_text (struct xr_driver *xr, if (best && !xr->nest) dump_line (xr, -xr->left_margin, best, xr->width + xr->right_margin, best, - RENDER_LINE_SINGLE, &CELL_COLOR (0, 255, 0)); + RENDER_LINE_SINGLE, + &(struct cell_color) CELL_COLOR (0, 255, 0)); } } @@ -1210,23 +1780,16 @@ xr_layout_cell_text (struct xr_driver *xr, pango_font_description_free (font->desc); } - return bb[V][0] + h; + return h; } static void xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell, - int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], + int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], int *width, int *height, int *brk) { - int bb[TABLE_N_AXES][2]; - size_t i; - *width = 0; *height = 0; - if (brk) - *brk = 0; - - memcpy (bb, bb_, sizeof bb); /* If enabled, draws a blue rectangle around the cell extents, which can be useful for debugging layout. */ @@ -1245,25 +1808,9 @@ xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell, } } - for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++) - { - const struct cell_contents *contents = &cell->contents[i]; - - if (brk) - *brk = bb[V][0]; - if (i > 0) - { - bb[V][0] += xr->char_height / 2; - if (bb[V][0] >= bb[V][1]) - break; - if (brk) - *brk = bb[V][0]; - } - - bb[V][0] = xr_layout_cell_text (xr, contents, cell->style, bb, clip, - width, brk); - } - *height = bb[V][0] - bb_[V][0]; + if (brk) + *brk = bb[V][0]; + *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk); } struct output_driver_factory pdf_driver_factory = @@ -1301,11 +1848,7 @@ struct xr_driver * xr_driver_create (cairo_t *cairo, struct string_map *options) { struct xr_driver *xr = xr_allocate ("cairo", 0, options); - if (!xr_set_cairo (xr, cairo)) - { - output_driver_destroy (&xr->driver); - return NULL; - } + xr_set_cairo (xr, cairo); return xr; } @@ -1327,8 +1870,7 @@ xr_rendering_create_text (struct xr_driver *xr, const char *text, cairo_t *cr) struct table_item *table_item; struct xr_rendering *r; - table_item = table_item_create (table_from_string (TAB_LEFT, text), - NULL, NULL); + table_item = table_item_create (table_from_string (text), NULL, NULL); r = xr_rendering_create (xr, &table_item->output_item, cr); table_item_unref (table_item); @@ -1354,8 +1896,7 @@ xr_rendering_create (struct xr_driver *xr, const struct output_item *item, else if (is_message_item (item)) { const struct message_item *message_item = to_message_item (item); - const struct msg *msg = message_item_get_msg (message_item); - char *s = msg_to_string (msg, NULL); + char *s = msg_to_string (message_item_get_msg (message_item)); r = xr_rendering_create_text (xr, s, cr); free (s); } @@ -1372,6 +1913,9 @@ xr_rendering_create (struct xr_driver *xr, const struct output_item *item, r = xzalloc (sizeof *r); r->item = output_item_ref (item); } + else if (is_group_open_item (item)) + r = xr_rendering_create_text (xr, to_group_open_item (item)->command_name, + cr); return r; } @@ -1388,18 +1932,25 @@ xr_rendering_destroy (struct xr_rendering *r) } void -xr_rendering_measure (struct xr_rendering *r, int *w, int *h) +xr_rendering_measure (const struct xr_rendering *r, int *wp, int *hp) { + int w, h; + if (is_table_item (r->item)) { - *w = render_pager_get_size (r->p, H) / XR_POINT; - *h = render_pager_get_size (r->p, V) / XR_POINT; + w = render_pager_get_size (r->p, H) / XR_POINT; + h = render_pager_get_size (r->p, V) / XR_POINT; } else { - *w = CHART_WIDTH; - *h = CHART_HEIGHT; + w = CHART_WIDTH; + h = CHART_HEIGHT; } + + if (wp) + *wp = w; + if (hp) + *hp = h; } static void xr_draw_chart (const struct chart_item *, cairo_t *, @@ -1407,7 +1958,8 @@ static void xr_draw_chart (const struct chart_item *, cairo_t *, /* Draws onto CR */ void -xr_rendering_draw_all (struct xr_rendering *r, cairo_t *cr) +xr_rendering_draw (struct xr_rendering *r, cairo_t *cr, + int x0, int y0, int x1, int y1) { if (is_table_item (r->item)) { @@ -1415,8 +1967,8 @@ xr_rendering_draw_all (struct xr_rendering *r, cairo_t *cr) xr_set_cairo (xr, cr); - render_pager_draw (r->p); - + render_pager_draw_region (r->p, x0 * XR_POINT, y0 * XR_POINT, + (x1 - x0) * XR_POINT, (y1 - y0) * XR_POINT); } else xr_draw_chart (to_chart_item (r->item), cr, @@ -1461,9 +2013,8 @@ xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr, char * xr_draw_png_chart (const struct chart_item *item, const char *file_name_template, int number, - const struct xr_color *fg, - const struct xr_color *bg - ) + const struct cell_color *fg, + const struct cell_color *bg) { const int width = 640; const int length = 480; @@ -1484,10 +2035,10 @@ xr_draw_png_chart (const struct chart_item *item, surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length); cr = cairo_create (surface); - cairo_set_source_rgb (cr, bg->red, bg->green, bg->blue); + cairo_set_source_rgb (cr, bg->r / 255.0, bg->g / 255.0, bg->b / 255.0); cairo_paint (cr); - cairo_set_source_rgb (cr, fg->red, fg->green, fg->blue); + cairo_set_source_rgb (cr, fg->r / 255.0, fg->g / 255.0, fg->b / 255.0); xr_draw_chart (item, cr, 0.0, 0.0, width, length); @@ -1505,7 +2056,6 @@ xr_draw_png_chart (const struct chart_item *item, struct xr_table_state { struct xr_render_fsm fsm; - struct table_item *table_item; struct render_pager *p; }; @@ -1535,25 +2085,24 @@ xr_table_destroy (struct xr_render_fsm *fsm) { struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm); - table_item_unref (ts->table_item); render_pager_destroy (ts->p); free (ts); } static struct xr_render_fsm * -xr_render_table (struct xr_driver *xr, const struct table_item *table_item) +xr_render_table (struct xr_driver *xr, struct table_item *table_item) { struct xr_table_state *ts; ts = xmalloc (sizeof *ts); ts->fsm.render = xr_table_render; ts->fsm.destroy = xr_table_destroy; - ts->table_item = table_item_ref (table_item); if (xr->y > 0) xr->y += xr->char_height; ts->p = render_pager_create (xr->params, table_item); + table_item_unref (table_item); return &ts->fsm; } @@ -1633,32 +2182,6 @@ xr_render_eject (void) return &eject_renderer; } -static struct xr_render_fsm * -xr_create_text_renderer (struct xr_driver *xr, const struct text_item *item) -{ - struct tab_table *tab = tab_create (1, 1); - - struct cell_style *style = pool_alloc (tab->container, sizeof *style); - *style = (struct cell_style) CELL_STYLE_INITIALIZER; - if (item->font) - { - puts (item->font); - style->font = pool_strdup (tab->container, item->font); - } - style->font_size = item->font_size; - style->bold = item->bold; - style->italic = item->italic; - style->underline = item->underline; - tab->styles[0] = style; - - tab_text (tab, 0, 0, TAB_LEFT, text_item_get_text (item)); - struct table_item *table_item = table_item_create (&tab->table, NULL, NULL); - struct xr_render_fsm *fsm = xr_render_table (xr, table_item); - table_item_unref (table_item); - - return fsm; -} - static struct xr_render_fsm * xr_render_text (struct xr_driver *xr, const struct text_item *text_item) { @@ -1667,22 +2190,8 @@ xr_render_text (struct xr_driver *xr, const struct text_item *text_item) switch (type) { - case TEXT_ITEM_TITLE: - free (xr->title); - xr->title = xstrdup (text); - break; - - case TEXT_ITEM_SUBTITLE: - free (xr->subtitle); - xr->subtitle = xstrdup (text); - break; - - case TEXT_ITEM_COMMAND_CLOSE: - break; - - case TEXT_ITEM_BLANK_LINE: - if (xr->y > 0) - xr->y += xr->char_height; + case TEXT_ITEM_PAGE_TITLE: + string_map_replace (&xr->heading_vars, "PageTitle", text); break; case TEXT_ITEM_EJECT_PAGE: @@ -1691,7 +2200,8 @@ xr_render_text (struct xr_driver *xr, const struct text_item *text_item) break; default: - return xr_create_text_renderer (xr, text_item); + return xr_render_table ( + xr, text_item_to_table_item (text_item_ref (text_item))); } return NULL; @@ -1701,14 +2211,10 @@ static struct xr_render_fsm * xr_render_message (struct xr_driver *xr, const struct message_item *message_item) { - const struct msg *msg = message_item_get_msg (message_item); - char *s = msg_to_string (msg, message_item->command_name); - struct text_item *item = text_item_create (TEXT_ITEM_PARAGRAPH, s); + char *s = msg_to_string (message_item_get_msg (message_item)); + struct text_item *item = text_item_create (TEXT_ITEM_LOG, s); free (s); - struct xr_render_fsm *fsm = xr_create_text_renderer (xr, item); - text_item_unref (item); - - return fsm; + return xr_render_table (xr, text_item_to_table_item (item)); } static struct xr_render_fsm * @@ -1716,7 +2222,7 @@ xr_render_output_item (struct xr_driver *xr, const struct output_item *output_item) { if (is_table_item (output_item)) - return xr_render_table (xr, to_table_item (output_item)); + return xr_render_table (xr, table_item_ref (to_table_item (output_item))); else if (is_chart_item (output_item)) return xr_render_chart (to_chart_item (output_item)); else if (is_text_item (output_item))