1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
3 *
4 * diacairo.c -- Cairo based export plugin for dia
5 * Copyright (C) 2004, Hans Breuer, <Hans@Breuer.Org>
6 * based on wpg.c
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */
22
23 #include <config.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <math.h>
27
28 #include <errno.h>
29 #define G_LOG_DOMAIN "DiaCairo"
30 #include <glib.h>
31 #include <glib/gstdio.h>
32
33 #include <cairo.h>
34 /* some backend headers, win32 missing in official Cairo */
35 #ifdef CAIRO_HAS_PNG_SURFACE_FEATURE
36 #include <cairo-png.h>
37 #endif
38 #ifdef CAIRO_HAS_PS_SURFACE
39 #include <cairo-ps.h>
40 #endif
41 #ifdef CAIRO_HAS_PDF_SURFACE
42 #include <cairo-pdf.h>
43 #endif
44 #ifdef CAIRO_HAS_SVG_SURFACE
45 #include <cairo-svg.h>
46 #endif
47 #ifdef CAIRO_HAS_WIN32_SURFACE
48 #include <cairo-win32.h>
49 /* avoid namespace collisions */
50 #define Rectangle RectangleWin32
51 #endif
52 #ifdef CAIRO_HAS_SCRIPT_SURFACE
53 #include <cairo-script.h>
54 #endif
55
56 #ifdef HAVE_PANGOCAIRO_H
57 #include <pango/pangocairo.h>
58 #endif
59
60 #include "intl.h"
61 #include "message.h"
62 #include "geometry.h"
63 #include "dia_image.h"
64 #include "diarenderer.h"
65 #include "filter.h"
66 #include "plug-ins.h"
67
68 #include "diacairo.h"
69 #include "diacairo-print.h"
70
71 typedef enum OutputKind
72 {
73 OUTPUT_PS = 1,
74 OUTPUT_PNG,
75 OUTPUT_PNGA,
76 OUTPUT_PDF,
77 OUTPUT_WMF,
78 OUTPUT_EMF,
79 OUTPUT_CLIPBOARD,
80 OUTPUT_SVG
81 } OutputKind;
82
83 #if defined CAIRO_HAS_WIN32_SURFACE && CAIRO_VERSION > 10510
84 #define DIA_CAIRO_CAN_EMF 1
85 #pragma message ("DiaCairo can EMF;)")
86 #endif
87
88 /* dia export funtion */
89 static void
export_data(DiagramData * data,const gchar * filename,const gchar * diafilename,void * user_data)90 export_data(DiagramData *data, const gchar *filename,
91 const gchar *diafilename, void* user_data)
92 {
93 DiaCairoRenderer *renderer;
94 FILE *file;
95 real width, height;
96 OutputKind kind = (OutputKind)user_data;
97 /* the passed in filename is in GLib's filename encoding. On Linux everything
98 * should be fine in passing it to the C-runtime (or cairo). On win32 GLib's
99 * filename encdong is always utf-8, so another conversion is needed.
100 */
101 gchar *filename_crt = (gchar *)filename;
102 #if DIA_CAIRO_CAN_EMF
103 HDC hFileDC = NULL;
104 #endif
105
106 if (kind != OUTPUT_CLIPBOARD) {
107 file = g_fopen(filename, "wb"); /* "wb" for binary! */
108
109 if (file == NULL) {
110 message_error(_("Can't open output file %s: %s\n"),
111 dia_message_filename(filename), strerror(errno));
112 return;
113 }
114 fclose (file);
115 #ifdef G_OS_WIN32
116 filename_crt = g_locale_from_utf8 (filename, -1, NULL, NULL, NULL);
117 if (!filename_crt) {
118 message_error(_("Can't convert output filename '%s' to locale encoding.\n"
119 "Please choose a different name to save with cairo.\n"),
120 dia_message_filename(filename), strerror(errno));
121 return;
122 }
123 #endif
124 } /* != CLIPBOARD */
125 renderer = g_object_new (DIA_TYPE_CAIRO_RENDERER, NULL);
126 renderer->dia = data; /* FIXME: not sure if this a good idea */
127 renderer->scale = 1.0;
128
129 switch (kind) {
130 #ifdef CAIRO_HAS_PS_SURFACE
131 case OUTPUT_PS :
132 width = data->paper.width * (72.0 / 2.54);
133 height = data->paper.height * (72.0 / 2.54);
134 renderer->scale = data->paper.scaling * (72.0 / 2.54);
135 DIAG_NOTE(g_message ("PS Surface %dx%d\n", (int)width, (int)height));
136 renderer->surface = cairo_ps_surface_create (filename_crt,
137 width, height); /* in points? */
138 /* maybe we should increase the resolution here as well */
139 break;
140 #endif
141 #if defined CAIRO_HAS_PNG_SURFACE || defined CAIRO_HAS_PNG_FUNCTIONS
142 case OUTPUT_PNGA :
143 renderer->with_alpha = TRUE;
144 /* fall through */
145 case OUTPUT_PNG :
146 /* quite arbitrary, but consistent with ../pixbuf ;-) */
147 renderer->scale = 20.0 * data->paper.scaling;
148 width = (data->extents.right - data->extents.left) * renderer->scale;
149 height = (data->extents.bottom - data->extents.top) * renderer->scale;
150
151 DIAG_NOTE(g_message ("PNG Surface %dx%d\n", (int)width, (int)height));
152 /* use case screwed by API shakeup. We need to special case */
153 renderer->surface = cairo_image_surface_create(
154 CAIRO_FORMAT_ARGB32,
155 (int)width, (int)height);
156 /* an extra refernce to make it survive end_render():cairo_surface_destroy() */
157 cairo_surface_reference(renderer->surface);
158 break;
159 #endif
160 #ifdef CAIRO_HAS_PDF_SURFACE
161 case OUTPUT_PDF :
162 #define DPI 72.0 /* 600.0? */
163 /* I just don't get how the scaling is supposed to work, dpi versus page size ? */
164 renderer->scale = data->paper.scaling * (72.0 / 2.54);
165 width = data->paper.width * (72.0 / 2.54);
166 height = data->paper.height * (72.0 / 2.54);
167 DIAG_NOTE(g_message ("PDF Surface %dx%d\n", (int)width, (int)height));
168 renderer->surface = cairo_pdf_surface_create (filename_crt,
169 width, height);
170 cairo_surface_set_fallback_resolution (renderer->surface, DPI, DPI);
171 #undef DPI
172 break;
173 #endif
174 #ifdef CAIRO_HAS_SVG_SURFACE
175 case OUTPUT_SVG :
176 /* quite arbitrary, but consistent with ../pixbuf ;-) */
177 renderer->scale = 20.0 * data->paper.scaling;
178 width = (data->extents.right - data->extents.left) * renderer->scale;
179 height = (data->extents.bottom - data->extents.top) * renderer->scale;
180 DIAG_NOTE(g_message ("SVG Surface %dx%d\n", (int)width, (int)height));
181 /* use case screwed by API shakeup. We need to special case */
182 renderer->surface = cairo_svg_surface_create(
183 filename_crt,
184 (int)width, (int)height);
185 break;
186 #endif
187 /* finally cairo can render to MetaFiles */
188 #if DIA_CAIRO_CAN_EMF
189 case OUTPUT_EMF :
190 case OUTPUT_WMF : /* different only on close/'play' */
191 case OUTPUT_CLIPBOARD :
192 /* NOT: renderer->with_alpha = TRUE; */
193 {
194 /* see wmf/wmf.cpp */
195 HDC refDC = GetDC(NULL);
196 RECT bbox = { 0, 0,
197 #if 1 /* CreateEnhMetaFile() takes 0.01 mm */
198 (int)((data->extents.right - data->extents.left) * data->paper.scaling * 1000.0),
199 (int)((data->extents.bottom - data->extents.top) * data->paper.scaling * 1000.0) };
200 #else
201 (int)((data->extents.right - data->extents.left) * renderer->scale
202 * 100 * GetDeviceCaps(refDC, HORZSIZE) / GetDeviceCaps(refDC, HORZRES)),
203 (int)((data->extents.bottom - data->extents.top) * renderer->scale
204 * 100 * GetDeviceCaps(refDC, VERTSIZE) / GetDeviceCaps(refDC, VERTRES)) };
205 #endif
206 hFileDC = CreateEnhMetaFile (refDC, NULL, &bbox, "DiaCairo\0Diagram\0");
207 renderer->surface = cairo_win32_printing_surface_create (hFileDC);
208 /* CreateEnhMetaFile() takes resolution 0.01 mm, */
209 renderer->scale = 1000.0/25.4 * data->paper.scaling;
210 }
211 break;
212 #endif
213 default :
214 /* quite arbitrary, but consistent with ../pixbuf ;-) */
215 renderer->scale = 20.0 * data->paper.scaling;
216 width = (data->extents.right - data->extents.left) * renderer->scale;
217 height = (data->extents.bottom - data->extents.top) * renderer->scale;
218 DIAG_NOTE(g_message ("Image Surface %dx%d\n", (int)width, (int)height));
219 renderer->surface = cairo_image_surface_create (CAIRO_FORMAT_A8, (int)width, (int)height);
220 }
221
222 /* use extents */
223 DIAG_NOTE(g_message("export_data extents %f,%f -> %f,%f",
224 data->extents.left, data->extents.top, data->extents.right, data->extents.bottom));
225
226 data_render(data, DIA_RENDERER(renderer), NULL, NULL, NULL);
227 #if defined CAIRO_HAS_PNG_FUNCTIONS
228 if (OUTPUT_PNGA == kind || OUTPUT_PNG == kind)
229 {
230 cairo_surface_write_to_png(renderer->surface, filename_crt);
231 cairo_surface_destroy(renderer->surface);
232 }
233 #endif
234 #if DIA_CAIRO_CAN_EMF
235 if (OUTPUT_EMF == kind) {
236 FILE* f = g_fopen(filename, "wb");
237 HENHMETAFILE hEmf = CloseEnhMetaFile(hFileDC);
238 UINT nSize = GetEnhMetaFileBits (hEmf, 0, NULL);
239 BYTE* pData = g_new(BYTE, nSize);
240 nSize = GetEnhMetaFileBits (hEmf, nSize, pData);
241 if (f) {
242 fwrite(pData,1,nSize,f);
243 fclose(f);
244 } else {
245 message_error (_("Can't write %d bytes to %s"), nSize, filename);
246 }
247 DeleteEnhMetaFile (hEmf);
248 g_free (pData);
249 } else if (OUTPUT_WMF == kind) {
250 FILE* f = g_fopen(filename, "wb");
251 HENHMETAFILE hEmf = CloseEnhMetaFile(hFileDC);
252 HDC hdc = GetDC(NULL);
253 UINT nSize = GetWinMetaFileBits (hEmf, 0, NULL, MM_ANISOTROPIC, hdc);
254 BYTE* pData = g_new(BYTE, nSize);
255 nSize = GetWinMetaFileBits (hEmf, nSize, pData, MM_ANISOTROPIC, hdc);
256 if (f) {
257 /* FIXME: write the placeable header */
258 fwrite(pData,1,nSize,f);
259 fclose(f);
260 } else {
261 message_error (_("Can't write %d bytes to %s"), nSize, filename);
262 }
263 ReleaseDC(NULL, hdc);
264 DeleteEnhMetaFile (hEmf);
265 g_free (pData);
266 } else if (OUTPUT_CLIPBOARD == kind) {
267 HENHMETAFILE hEmf = CloseEnhMetaFile(hFileDC);
268 if ( OpenClipboard(NULL)
269 && EmptyClipboard()
270 && SetClipboardData (CF_ENHMETAFILE, hEmf)
271 && CloseClipboard ()) {
272 hEmf = NULL; /* data now owned by clipboard */
273 } else {
274 message_error (_("Clipboard copy failed"));
275 DeleteEnhMetaFile (hEmf);
276 }
277 }
278 #endif
279 g_object_unref(renderer);
280 if (filename != filename_crt)
281 g_free (filename_crt);
282 }
283
284 static void
export_print_data(DiagramData * data,const gchar * filename_utf8,const gchar * diafilename,void * user_data)285 export_print_data (DiagramData *data, const gchar *filename_utf8,
286 const gchar *diafilename, void* user_data)
287 {
288 OutputKind kind = (OutputKind)user_data;
289 #if GTK_CHECK_VERSION (2,10,0)
290 GtkPrintOperation *op = create_print_operation (data, filename_utf8);
291 GtkPrintOperationResult res;
292 GError *error = NULL;
293
294 # ifdef CAIRO_HAS_PDF_SURFACE
295 /* as of this writing the only format Gtk+ supports here is PDF */
296 g_assert (OUTPUT_PDF == kind);
297 # endif
298
299 if (!data) {
300 message_error (_("Nothing to print"));
301 return;
302 }
303
304 gtk_print_operation_set_export_filename (op, filename_utf8 ? filename_utf8 : "output.pdf");
305 res = gtk_print_operation_run (op, GTK_PRINT_OPERATION_ACTION_EXPORT, NULL, &error);
306 if (GTK_PRINT_OPERATION_RESULT_ERROR == res) {
307 message_error (error->message);
308 g_error_free (error);
309 }
310 #else
311 message_error (_("Printing with Gtk+(cairo) requires at least version 2.10."));
312 #endif
313 }
314
315 #ifdef CAIRO_HAS_PS_SURFACE
316 static const gchar *ps_extensions[] = { "ps", NULL };
317 static DiaExportFilter ps_export_filter = {
318 N_("Cairo PostScript"),
319 ps_extensions,
320 export_data,
321 (void*)OUTPUT_PS,
322 "cairo-ps" /* unique name */
323 };
324 #endif
325
326 #ifdef CAIRO_HAS_PDF_SURFACE
327 static const gchar *pdf_extensions[] = { "pdf", NULL };
328 static DiaExportFilter pdf_export_filter = {
329 N_("Cairo Portable Document Format"),
330 pdf_extensions,
331 # if GTK_CHECK_VERSION (2,10,0)
332 export_print_data,
333 # else
334 export_data,
335 # endif
336 (void*)OUTPUT_PDF,
337 "cairo-pdf"
338 };
339 #endif
340
341 #ifdef CAIRO_HAS_SVG_SURFACE
342 static const gchar *svg_extensions[] = { "svg", NULL };
343 static DiaExportFilter svg_export_filter = {
344 N_("Cairo Scalable Vector Graphics"),
345 svg_extensions,
346 export_data,
347 (void*)OUTPUT_SVG,
348 "cairo-svg",
349 FILTER_DONT_GUESS /* don't use this if not asked explicit */
350 };
351 #endif
352
353 static const gchar *png_extensions[] = { "png", NULL };
354 static DiaExportFilter png_export_filter = {
355 N_("Cairo PNG"),
356 png_extensions,
357 export_data,
358 (void*)OUTPUT_PNG,
359 "cairo-png"
360 };
361
362 static DiaExportFilter pnga_export_filter = {
363 N_("Cairo PNG (with alpha)"),
364 png_extensions,
365 export_data,
366 (void*)OUTPUT_PNGA,
367 "cairo-alpha-png"
368 };
369
370 #if DIA_CAIRO_CAN_EMF
371 static const gchar *emf_extensions[] = { "emf", NULL };
372 static DiaExportFilter emf_export_filter = {
373 N_("Cairo EMF"),
374 emf_extensions,
375 export_data,
376 (void*)OUTPUT_EMF,
377 "cairo-emf",
378 FILTER_DONT_GUESS /* don't use this if not asked explicit */
379 };
380
381 static const gchar *wmf_extensions[] = { "wmf", NULL };
382 static DiaExportFilter wmf_export_filter = {
383 N_("Cairo WMF"),
384 wmf_extensions,
385 export_data,
386 (void*)OUTPUT_WMF,
387 "cairo-wmf",
388 FILTER_DONT_GUESS /* don't use this if not asked explicit */
389 };
390
391 void
cairo_clipboard_callback(DiagramData * data,const gchar * filename,guint flags,void * user_data)392 cairo_clipboard_callback (DiagramData *data,
393 const gchar *filename,
394 guint flags, /* further additions */
395 void *user_data)
396 {
397 g_return_if_fail ((OutputKind)user_data == OUTPUT_CLIPBOARD);
398 g_return_if_fail (data != NULL);
399 /* filename is not necessary */
400 export_data (data, filename, filename, user_data);
401 }
402
403 static DiaCallbackFilter cb_clipboard = {
404 "EditCopyDiagram",
405 N_("Copy _Diagram"),
406 "/DisplayMenu/Edit/CopyDiagram",
407 cairo_clipboard_callback,
408 (void*)OUTPUT_CLIPBOARD
409 };
410 #endif
411
412 #if GTK_CHECK_VERSION (2,10,0)
413 static DiaCallbackFilter cb_gtk_print = {
414 "FilePrintGTK",
415 N_("Print (GTK) ..."),
416 "/InvisibleMenu/File/FilePrint",
417 cairo_print_callback,
418 (void*)OUTPUT_PDF
419 };
420 #endif
421
422 static gboolean
_plugin_can_unload(PluginInfo * info)423 _plugin_can_unload (PluginInfo *info)
424 {
425 /* Can't unlaod as long as we are giving away our types, e.g. dia_cairo_interactive_renderer_get_type () */
426 return FALSE;
427 }
428
429 static void
_plugin_unload(PluginInfo * info)430 _plugin_unload (PluginInfo *info)
431 {
432 #ifdef CAIRO_HAS_PS_SURFACE
433 filter_unregister_export(&ps_export_filter);
434 #endif
435 #ifdef CAIRO_HAS_PDF_SURFACE
436 filter_unregister_export(&pdf_export_filter);
437 #endif
438 #ifdef CAIRO_HAS_SVG_SURFACE
439 filter_unregister_export(&svg_export_filter);
440 #endif
441 #if defined CAIRO_HAS_PNG_SURFACE || defined CAIRO_HAS_PNG_FUNCTIONS
442 filter_unregister_export(&png_export_filter);
443 filter_unregister_export(&pnga_export_filter);
444 #endif
445 #if DIA_CAIRO_CAN_EMF
446 filter_unregister_export(&emf_export_filter);
447 filter_unregister_export(&wmf_export_filter);
448 /* filter_unregister_callback (&cb_clipboard); */
449 #endif
450 }
451
452 /* --- dia plug-in interface --- */
453
454 DIA_PLUGIN_CHECK_INIT
455
456 PluginInitResult
dia_plugin_init(PluginInfo * info)457 dia_plugin_init(PluginInfo *info)
458 {
459 if (!dia_plugin_info_init(info, "Cairo",
460 _("Cairo based Rendering"),
461 _plugin_can_unload,
462 _plugin_unload))
463 return DIA_PLUGIN_INIT_ERROR;
464
465 /* FIXME: need to think about of proper way of registartion, see also app/display.c */
466 png_export_filter.renderer_type = dia_cairo_interactive_renderer_get_type ();
467
468 #ifdef CAIRO_HAS_PS_SURFACE
469 filter_register_export(&ps_export_filter);
470 #endif
471 #ifdef CAIRO_HAS_PDF_SURFACE
472 filter_register_export(&pdf_export_filter);
473 #endif
474 #ifdef CAIRO_HAS_SVG_SURFACE
475 filter_register_export(&svg_export_filter);
476 #endif
477 #if defined CAIRO_HAS_PNG_SURFACE || defined CAIRO_HAS_PNG_FUNCTIONS
478 filter_register_export(&png_export_filter);
479 filter_register_export(&pnga_export_filter);
480 #endif
481 #if DIA_CAIRO_CAN_EMF
482 filter_register_export(&emf_export_filter);
483 filter_register_export(&wmf_export_filter);
484 filter_register_callback (&cb_clipboard);
485 #endif
486 #ifdef CAIRO_HAS_WIN32X_SURFACE
487 filter_register_export(&cb_export_filter);
488 #endif
489
490 #if GTK_CHECK_VERSION (2,10,0)
491 filter_register_callback (&cb_gtk_print);
492 #endif
493
494 return DIA_PLUGIN_INIT_OK;
495 }
496