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