From 182ed04eaef3db9d0e07d466b19307e6c1b4aeb9 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 8 Feb 2015 13:38:35 +0000 Subject: Add support for exporting canvas as PDF or PS. git-svn-id: http://svn.drobilla.net/lad/trunk/ganv@5543 a436a847-0d15-0410-975c-d299462d15a1 --- NEWS | 6 ++-- ganv/Canvas.hpp | 1 + ganv/canvas.h | 13 ++++++++ ganv/wrap.hpp | 5 ++++ src/Canvas.cpp | 65 ++++++++++++++++++++++++++++++++++++++++ src/ganv-private.h | 2 +- src/group.c | 5 ---- src/text.c | 87 +++++++++++++++++++++--------------------------------- wscript | 2 +- 9 files changed, 124 insertions(+), 62 deletions(-) diff --git a/NEWS b/NEWS index e6ea402..60df0eb 100644 --- a/NEWS +++ b/NEWS @@ -1,11 +1,13 @@ -ganv (1.4.3) unstable; +ganv (1.5.0) unstable; * Fix positioning of embedded widgets when changing layout. * Fix unexpected node jumping when dragging new connections. + * Add support for PDF and PS export. + * Improve text rendering at high zoom. * Fix compilation with --no-fdgl (patch from Vlad Glagolev). * Fix crash when destroying canvas. - -- David Robillard Thu, 05 Feb 2015 18:32:13 -0500 + -- David Robillard Sun, 08 Feb 2015 08:32:08 -0500 ganv (1.4.2) stable; diff --git a/ganv/Canvas.hpp b/ganv/Canvas.hpp index 88211d4..4a83de2 100644 --- a/ganv/Canvas.hpp +++ b/ganv/Canvas.hpp @@ -84,6 +84,7 @@ public: void remove_edge(Edge* edge); METHOD0(ganv_canvas, arrange); + METHODRET2(ganv_canvas, int, export_image, const char*, filename, bool, draw_background); METHOD1(ganv_canvas, export_dot, const char*, filename); METHODRET0(ganv_canvas, gboolean, supports_sprung_layout); METHODRET1(ganv_canvas, gboolean, set_sprung_layout, gboolean, sprung_layout); diff --git a/ganv/canvas.h b/ganv/canvas.h index 83e3f8a..e923f65 100644 --- a/ganv/canvas.h +++ b/ganv/canvas.h @@ -377,6 +377,19 @@ ganv_canvas_set_direction(GanvCanvas* canvas, GanvDirection dir); void ganv_canvas_arrange(GanvCanvas* canvas); +/** + * ganv_canvas_export_image: + * + * Draw the canvas to an image file. The file type is determined by extension, + * currently supported: pdf, ps, svg, dot. + * + * Returns: 0 on success. + */ +int +ganv_canvas_export_image(GanvCanvas* canvas, + const char* filename, + gboolean draw_background); + /** * ganv_canvas_export_dot: * diff --git a/ganv/wrap.hpp b/ganv/wrap.hpp index f4f2e9c..f907dea 100644 --- a/ganv/wrap.hpp +++ b/ganv/wrap.hpp @@ -76,6 +76,11 @@ private: \ return prefix##_##name(gobj(), a1); \ } +#define METHODRET2(prefix, ret, name, t1, a1, t2, a2) \ + virtual ret name(t1 a1, t2 a2) { \ + return prefix##_##name(gobj(), a1, a2); \ + } + #define METHODRETWRAP0(prefix, ret, name) \ virtual ret name() const { \ if (gobj()) { \ diff --git a/src/Canvas.cpp b/src/Canvas.cpp index 02bf4f6..c3ed2f6 100644 --- a/src/Canvas.cpp +++ b/src/Canvas.cpp @@ -2586,6 +2586,64 @@ ganv_canvas_arrange(GanvCanvas* canvas) #endif } +int +ganv_canvas_export_image(GanvCanvas* canvas, + const char* filename, + gboolean draw_background) +{ + const char* ext = strrchr(filename, '.'); + if (!ext) { + return 1; + } else if (!strcmp(ext, ".dot")) { + ganv_canvas_export_dot(canvas, filename); + return 0; + } + + cairo_surface_t* rec_surface = cairo_recording_surface_create( + CAIRO_CONTENT_COLOR_ALPHA, NULL); + + // Draw to recording surface + cairo_t* cr = cairo_create(rec_surface); + (*GANV_ITEM_GET_CLASS(canvas->impl->root)->draw)( + canvas->impl->root, cr, + 0, 0, canvas->impl->width, canvas->impl->height); + cairo_destroy(cr); + + // Get draw extent + double x, y, w, h; + cairo_recording_surface_ink_extents(rec_surface, &x, &y, &w, &h); + + // Create image surface with the appropriate size + const double pad = GANV_CANVAS_PAD; + const double img_w = w + pad * 2; + const double img_h = h + pad * 2; + cairo_surface_t* img = NULL; + if (!strcmp(ext, ".svg")) { + img = cairo_svg_surface_create(filename, img_w, img_h); + } else if (!strcmp(ext, ".pdf")) { + img = cairo_pdf_surface_create(filename, img_w, img_h); + } else if (!strcmp(ext, ".ps")) { + img = cairo_ps_surface_create(filename, img_w, img_h); + } else { + cairo_surface_destroy(rec_surface); + return 1; + } + + // Draw recording to image surface + cr = cairo_create(img); + if (draw_background) { + cairo_set_source_rgba(cr, 0, 0, 0, 1.0); + cairo_rectangle(cr, 0, 0, w + 2 * pad, h + 2 * pad); + cairo_fill(cr); + } + cairo_set_source_surface(cr, rec_surface, -x + pad, -y + pad); + cairo_paint(cr); + cairo_destroy(cr); + cairo_surface_destroy(rec_surface); + cairo_surface_destroy(img); + return 0; +} + void ganv_canvas_export_dot(GanvCanvas* canvas, const char* filename) { @@ -3590,6 +3648,13 @@ ganv_canvas_paint_rect(GanvCanvas* canvas, gint x0, gint y0, gint x1, gint y1) double wx1, wy1, ww, wh; ganv_canvas_c2w(canvas, draw_x1, draw_y1, &wx1, &wy1); ganv_canvas_c2w(canvas, draw_width, draw_height, &ww, &wh); + + // Draw background + cairo_set_source_rgba(cr, 0, 0, 0, 1.0); + cairo_rectangle(cr, wx1, wy1, ww, wh); + cairo_fill(cr); + + // Draw root group (*GANV_ITEM_GET_CLASS(canvas->impl->root)->draw)( canvas->impl->root, cr, wx1, wy1, ww, wh); diff --git a/src/ganv-private.h b/src/ganv-private.h index 8f32480..2fc6c00 100644 --- a/src/ganv-private.h +++ b/src/ganv-private.h @@ -237,7 +237,7 @@ typedef struct struct _GanvTextImpl { - cairo_surface_t* surface; + PangoLayout* layout; char* text; GanvTextCoords coords; GanvTextCoords old_coords; diff --git a/src/group.c b/src/group.c index 20ef354..e867cef 100644 --- a/src/group.c +++ b/src/group.c @@ -203,11 +203,6 @@ ganv_group_draw(GanvItem* item, { GanvGroup* group = GANV_GROUP(item); - // Draw background - cairo_set_source_rgba(cr, 0, 0, 0, 1.0); - cairo_rectangle(cr, cx, cy, cw, ch); - cairo_fill(cr); - // TODO: Layered drawing for (GList* list = group->impl->item_list; list; list = list->next) { diff --git a/src/text.c b/src/text.c index 39df696..6dda2e1 100644 --- a/src/text.c +++ b/src/text.c @@ -55,7 +55,7 @@ ganv_text_init(GanvText* text) impl->coords.height = 1.0; impl->old_coords = impl->coords; - impl->surface = NULL; + impl->layout = NULL; impl->text = NULL; impl->color = 0xFFFFFFFF; impl->needs_layout = FALSE; @@ -75,9 +75,9 @@ ganv_text_destroy(GtkObject* object) impl->text = NULL; } - if (impl->surface) { - cairo_surface_destroy(impl->surface); - impl->surface = NULL; + if (impl->layout) { + g_object_unref(impl->layout); + impl->layout = NULL; } if (GTK_OBJECT_CLASS(parent_class)->destroy) { @@ -88,57 +88,36 @@ ganv_text_destroy(GtkObject* object) void ganv_text_layout(GanvText* text) { - GanvTextImpl* impl = text->impl; - GanvItem* item = GANV_ITEM(text); - GanvCanvas* canvas = ganv_item_get_canvas(item); - GtkWidget* widget = GTK_WIDGET(canvas); - double font_size = ganv_canvas_get_font_size(canvas); - guint color = 0xFFFFFFFF; - - GtkStyle* style = gtk_rc_get_style(widget); - PangoFontDescription* font = pango_font_description_copy(style->font_desc); - PangoLayout* layout = gtk_widget_create_pango_layout(widget, impl->text); - PangoContext* context = pango_layout_get_context(layout); - cairo_font_options_t* options = cairo_font_options_copy( - pango_cairo_context_get_font_options(context)); - - pango_font_description_set_size(font, font_size * (double)PANGO_SCALE); - pango_layout_set_font_description(layout, font); - - if (cairo_font_options_get_antialias(options) == CAIRO_ANTIALIAS_SUBPIXEL) { - cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_GRAY); + GanvTextImpl* impl = text->impl; + GanvItem* item = GANV_ITEM(text); + GanvCanvas* canvas = ganv_item_get_canvas(item); + GtkWidget* widget = GTK_WIDGET(canvas); + double points = ganv_canvas_get_font_size(canvas); + GtkStyle* style = gtk_rc_get_style(widget); + + if (impl->layout) { + g_object_unref(impl->layout); } + impl->layout = gtk_widget_create_pango_layout(widget, impl->text); - pango_cairo_context_set_font_options(context, options); - cairo_font_options_destroy(options); + PangoFontDescription* font = pango_font_description_copy(style->font_desc); + PangoContext* ctx = pango_layout_get_context(impl->layout); + cairo_font_options_t* opt = cairo_font_options_copy( + pango_cairo_context_get_font_options(ctx)); + + pango_font_description_set_size(font, points * (double)PANGO_SCALE); + pango_layout_set_font_description(impl->layout, font); + pango_cairo_context_set_font_options(ctx, opt); + cairo_font_options_destroy(opt); + pango_font_description_free(font); int width, height; - pango_layout_get_pixel_size(layout, &width, &height); + pango_layout_get_pixel_size(impl->layout, &width, &height); - impl->coords.width = width; + impl->coords.width = width; impl->coords.height = height; + impl->needs_layout = FALSE; - if (impl->surface) { - cairo_surface_destroy(impl->surface); - } - - impl->surface = cairo_image_surface_create( - CAIRO_FORMAT_ARGB32, width, height); - - cairo_t* cr = cairo_create(impl->surface); - - double r, g, b, a; - color_to_rgba(color, &r, &g, &b, &a); - - cairo_set_source_rgba(cr, r, g, b, a); - cairo_move_to(cr, 0, 0); - pango_cairo_show_layout(cr, layout); - - cairo_destroy(cr); - g_object_unref(layout); - pango_font_description_free(font); - - impl->needs_layout = FALSE; ganv_item_request_update(GANV_ITEM(text)); } @@ -293,12 +272,14 @@ ganv_text_draw(GanvItem* item, ganv_text_layout(text); } - // Round to the nearest pixel so text isn't blurry - wx = lrint(wx); - wy = lrint(wy); + guint color = 0xFFFFFFFF; - cairo_set_source_surface(cr, impl->surface, wx, wy); - cairo_paint(cr); + double r, g, b, a; + color_to_rgba(color, &r, &g, &b, &a); + + cairo_set_source_rgba(cr, r, g, b, a); + cairo_move_to(cr, wx, wy); + pango_cairo_show_layout(cr, impl->layout); } static void diff --git a/wscript b/wscript index 894bbee..d1c0686 100644 --- a/wscript +++ b/wscript @@ -8,7 +8,7 @@ import waflib.extras.autowaf as autowaf # major increment <=> incompatible changes # minor increment <=> compatible changes (additions) # micro increment <=> no interface changes -GANV_VERSION = '1.4.3' +GANV_VERSION = '1.5.0' GANV_MAJOR_VERSION = '1' # Mandatory waf variables -- cgit v1.2.1