Quartz direct plugin for Mac OS X
authorglenlow <devnull@localhost>
Sat, 19 Jan 2008 05:26:40 +0000 (05:26 +0000)
committerglenlow <devnull@localhost>
Sat, 19 Jan 2008 05:26:40 +0000 (05:26 +0000)
plugin/quartz/gvplugin_quartz.c [new file with mode: 0644]
plugin/quartz/gvrender_quartz.c [new file with mode: 0644]

diff --git a/plugin/quartz/gvplugin_quartz.c b/plugin/quartz/gvplugin_quartz.c
new file mode 100644 (file)
index 0000000..8faa790
--- /dev/null
@@ -0,0 +1,32 @@
+/* $Id$ $Revision$ */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+*      This software is part of the graphviz package      *
+*                http://www.graphviz.org/                 *
+*                                                         *
+*            Copyright (c) 1994-2008 AT&T Corp.           *
+*                and is licensed under the                *
+*            Common Public License, Version 1.0           *
+*                      by AT&T Corp.                      *
+*                                                         *
+*        Information and Software Systems Research        *
+*              AT&T Research, Florham Park NJ             *
+**********************************************************/
+
+#include "gvplugin.h"
+
+extern gvplugin_installed_t gvrender_quartz_types;
+// extern gvplugin_installed_t gvtextlayout_quartz_types;
+// extern gvplugin_installed_t gvloadimage_quartz_types;
+extern gvplugin_installed_t gvdevice_quartz_types;
+
+static gvplugin_api_t apis[] = {
+    {API_render, &gvrender_quartz_types},
+  //  {API_textlayout, &gvtextlayout_quartz_types},
+  //  {API_loadimage, &gvloadimage_quartz_types},
+    {API_device, &gvdevice_quartz_types},
+    {(api_t)0, 0},
+};
+
+gvplugin_library_t gvplugin_quartz_LTX_library = { "quartz", apis };
diff --git a/plugin/quartz/gvrender_quartz.c b/plugin/quartz/gvrender_quartz.c
new file mode 100644 (file)
index 0000000..5736444
--- /dev/null
@@ -0,0 +1,442 @@
+/* $Id$ $Revision$ */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+*      This software is part of the graphviz package      *
+*                http://www.graphviz.org/                 *
+*                                                         *
+*            Copyright (c) 1994-2004 AT&T Corp.           *
+*                and is licensed under the                *
+*            Common Public License, Version 1.0           *
+*                      by AT&T Corp.                      *
+*                                                         *
+*        Information and Software Systems Research        *
+*              AT&T Research, Florham Park NJ             *
+**********************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "gvplugin_device.h"
+#include "gvplugin_render.h"
+#include "graph.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+
+typedef enum {
+       FORMAT_BMP,
+       FORMAT_EXR,
+       FORMAT_GIF,
+       FORMAT_JPEG,
+       FORMAT_JPEG2000,
+       FORMAT_PDF,
+       FORMAT_PICT,
+       FORMAT_PNG,
+       FORMAT_PSD,
+       FORMAT_SGI,
+       FORMAT_TIFF,
+       FORMAT_TGA
+} format_type;
+
+static const int BYTE_ALIGN = 15;                      /* align to 16 bytes */
+static const int BITS_PER_COMPONENT = 8;       /* bits per color component */
+static const int BYTES_PER_PIXEL = 4;          /* bytes per pixel */
+
+/* Uniform Type Identifiers corresponding to each format_type */
+static CFStringRef format_uti [] = {
+       CFSTR("com.microsoft.bmp"),
+       CFSTR("com.ilm.openexr-image"),
+       CFSTR("com.compuserve.gif"),
+       CFSTR("public.jpeg"),
+       CFSTR("public.jpeg-2000"),
+       CFSTR("com.adobe.pdf"),
+       CFSTR("com.apple.pict"),
+       CFSTR("public.png"),
+       CFSTR("com.adobe.photoshop-image"),
+       CFSTR("com.sgi.sgi-image"),
+       CFSTR("public.tiff"),
+       CFSTR("com.truevision.tga-image")
+};
+
+static size_t file_data_consumer_put_bytes (void *info, const void *buffer, size_t count)
+{
+       return fwrite(buffer, 1, count, (FILE*)info);
+}
+
+static void file_data_consumer_release_info (void *info)
+{
+       fflush((FILE*)info);
+}
+
+static CGDataConsumerRef create_data_consumer(char *filename)
+{
+       static CGDataConsumerCallbacks file_data_consumer_callbacks = {
+               file_data_consumer_put_bytes,
+               file_data_consumer_release_info
+       };
+       
+       CGDataConsumerRef data_consumer;
+       if (filename) {
+               /* create a data consumer directly from the file URL */
+               CFURLRef file_url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)filename, strlen(filename), false);
+               data_consumer = CGDataConsumerCreateWithURL(file_url);
+               CFRelease(file_url);
+       }
+       else
+               /* create a data consumer that uses callbacks on a FILE* */
+               data_consumer = CGDataConsumerCreate(stdout, &file_data_consumer_callbacks);
+       return data_consumer;
+}
+
+static void quartzgen_begin_job(GVJ_t *job)
+{
+       if (!job->external_context)
+               job->context = NULL;
+}
+
+static void quartzgen_end_job(GVJ_t *job)
+{
+       if (!job->external_context) {
+               CGContextRef context = (CGContextRef)job->context;
+               switch (job->device.id) {
+               
+               case FORMAT_PDF:
+                       /* save the PDF */
+                       CGPDFContextClose(context);
+                       break;
+                       
+               default:        /* bitmap formats */
+                       {
+                               /* create an image destination */
+                               CGDataConsumerRef data_consumer = create_data_consumer(job->output_filename);
+                               CGImageDestinationRef image_destination = CGImageDestinationCreateWithDataConsumer(data_consumer, format_uti[job->device.id], 1, NULL);
+                               
+                               /* add the bitmap image to the destination and save it */
+                               CGImageRef image = CGBitmapContextCreateImage(context);
+                               CGImageDestinationAddImage(image_destination, image, NULL);
+                               CGImageDestinationFinalize(image_destination);
+                               
+                               /* clean up */
+                               CGImageRelease(image);
+                               CGDataConsumerRelease(data_consumer);
+                       }
+                       break;
+               }
+               CGContextRelease(context);
+       }
+}
+
+static void quartzgen_begin_page(GVJ_t *job)
+{
+       CGRect bounds = CGRectMake(0.0, 0.0, job->width, job->height);
+       
+       if (!job->external_context && !job->context) {
+               
+               switch (job->device.id) {
+               
+               case FORMAT_PDF:
+                       {
+                               /* create the auxiliary info for PDF content, author and title */
+                               CFStringRef auxiliaryKeys[] = {
+                                       kCGPDFContextCreator,
+                                       kCGPDFContextAuthor,
+                                       kCGPDFContextTitle
+                               };
+                               CFStringRef auxiliaryValues[] = {
+                                       CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s %s"), job->common->info[0], job->common->info[1]),
+                                       CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *)job->common->user, strlen(job->common->user), kCFStringEncodingUTF8, false, kCFAllocatorNull),
+                                       job->obj->type == ROOTGRAPH_OBJTYPE ?
+                                               CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *)job->obj->u.g->name, strlen(job->obj->u.g->name), kCFStringEncodingUTF8, false, kCFAllocatorNull)
+                                               : CFSTR("")
+                               };
+                               CFDictionaryRef auxiliaryInfo = CFDictionaryCreate(
+                                       kCFAllocatorDefault,
+                                       (const void **)&auxiliaryKeys,
+                                       (const void **)&auxiliaryValues,
+                                       sizeof(auxiliaryValues)/sizeof(auxiliaryValues[0]),
+                                       &kCFTypeDictionaryKeyCallBacks,
+                                       &kCFTypeDictionaryValueCallBacks
+                               );
+                               
+                               /* create a PDF for drawing into */
+                               CGDataConsumerRef data_consumer = create_data_consumer(job->output_filename);
+                               job->context = CGPDFContextCreate(data_consumer, &bounds, auxiliaryInfo);
+                               
+                               /* clean up */
+                               CGDataConsumerRelease(data_consumer);
+                               CFRelease(auxiliaryInfo);
+                               int i;
+                               for (i = 0; i < sizeof(auxiliaryValues)/sizeof(auxiliaryValues[0]); ++i)
+                                       CFRelease(auxiliaryValues[i]);
+                       }
+                       break;
+               
+               default: /* bitmap formats */
+                       {       
+                               /* create a true color bitmap for drawing into */
+                               CGColorSpaceRef color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
+                               job->context = CGBitmapContextCreate(
+                                       NULL,                                                                                                           /* data: let Quartz take care of memory management */
+                                       job->width,                                                                                                     /* width in pixels */
+                                       job->height,                                                                                            /* height in pixels */
+                                       BITS_PER_COMPONENT,                                                                                     /* bits per component */
+                                       (job->width * BYTES_PER_PIXEL + BYTE_ALIGN) & ~BYTE_ALIGN,      /* bytes per row: align to 16 byte boundary */
+                                       color_space,                                                                                            /* color space: SRGB */
+                                       kCGImageAlphaPremultipliedFirst                                                         /* bitmap info: premul ARGB has best support in OS X */
+                               );
+                               job->imagedata = CGBitmapContextGetData((CGContextRef)job->context);
+                       }
+                       break;
+               }
+               
+       }
+       
+       /* start the page (if this is a paged context) and graphics state */
+       CGContextRef context = (CGContextRef)job->context;
+       CGContextBeginPage(context, &bounds);
+       CGContextSaveGState(context);
+       CGContextSetMiterLimit(context, 1.0);
+       
+       /* set up the context transformation */
+       CGContextScaleCTM(context, job->scale.x, job->scale.y);
+       CGContextRotateCTM(context, -job->rotation * M_PI / 180.0);
+       CGContextTranslateCTM(context, job->translation.x, job->translation.y);
+}
+
+static void quartzgen_end_page(GVJ_t *job)
+{
+       /* end the page (if this is a paged context) and graphics state */
+       CGContextRef context = (CGContextRef)job->context;
+       CGContextRestoreGState(context);
+       CGContextEndPage(context);
+}
+
+static void quartzgen_begin_anchor(GVJ_t *job, char *url, char *tooltip, char *target)
+{
+    pointf *url_map = job->obj->url_map_p;
+       if (url && url_map) {
+               /* set up the hyperlink to the given url */
+               CGContextRef context = (CGContextRef)job->context;
+               CFURLRef uri = CFURLCreateWithBytes (kCFAllocatorDefault, (const UInt8 *)url, strlen(url), kCFStringEncodingUTF8, NULL);
+               CGPDFContextSetURLForRect(
+                       context,
+                       uri,
+                       /* need to reverse the CTM on the area to get it to work */
+                       CGRectApplyAffineTransform(CGRectMake(url_map[0].x, url_map[0].y, url_map[1].x - url_map[0].x, url_map[1].y - url_map[0].y),CGContextGetCTM(context))
+               );
+
+               /* clean up */
+               CFRelease(uri);
+       }
+}
+
+static void quartzgen_textpara(GVJ_t *job, pointf p, textpara_t *para)
+{
+       CGContextRef context = (CGContextRef)job->context;
+       
+       /* set up the Core Text line */
+       CFStringRef attribute_keys[] = {
+               kCTFontAttributeName,
+               kCTForegroundColorAttributeName
+       };
+       CFStringRef fontname = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *)para->fontname, strlen(para->fontname), kCFStringEncodingUTF8, FALSE, kCFAllocatorNull);
+       CFTypeRef attribute_values[] = {
+               CTFontCreateWithName(fontname, para->fontsize, NULL),
+               CGColorCreateGenericRGB(job->obj->pencolor.u.RGBA [0], job->obj->pencolor.u.RGBA [1], job->obj->pencolor.u.RGBA [2], job->obj->pencolor.u.RGBA [3])
+       };
+       CFDictionaryRef attributes = CFDictionaryCreate(
+               kCFAllocatorDefault,
+               (const void**)&attribute_keys,
+               (const void**)&attribute_values,
+               sizeof(attribute_values) / sizeof(attribute_values[0]),
+               &kCFTypeDictionaryKeyCallBacks,
+               &kCFTypeDictionaryValueCallBacks);
+       CFStringRef str = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *)para->str, strlen(para->str), kCFStringEncodingUTF8, FALSE, kCFAllocatorNull);
+       CFAttributedStringRef attributed_str = CFAttributedStringCreate(kCFAllocatorDefault, str, attributes);
+       CTLineRef line = CTLineCreateWithAttributedString(attributed_str);
+       
+       /* adjust text position */
+       switch (para->just) {
+    case 'r':
+        p.x -= para->width;
+        break;
+    case 'l':
+        p.x -= 0.0;
+        break;
+    case 'n':
+    default:
+        p.x -= para->width / 2.0;
+        break;
+    }
+    p.y += para->yoffset_centerline;
+       
+       /* draw it */
+       CGContextSetTextPosition(context, p.x, p.y);
+       CTLineDraw(line, context);
+       
+       /* clean up */
+       CFRelease(line);
+       CFRelease(attributed_str);
+       CFRelease(str);
+       CFRelease(attributes);
+       int i;
+       for (i = 0; i < sizeof(attribute_values) / sizeof(attribute_values[0]); ++i)
+               CFRelease(attribute_values[i]);
+       CFRelease(fontname);
+}
+
+static void quartzgen_path(GVJ_t *job, int filled)
+{
+       CGContextRef context = (CGContextRef)job->context;
+       
+       /* set up colors */
+       if (filled)
+               CGContextSetRGBFillColor(context, job->obj->fillcolor.u.RGBA [0], job->obj->fillcolor.u.RGBA [1], job->obj->fillcolor.u.RGBA [2], job->obj->fillcolor.u.RGBA [3]);
+       CGContextSetRGBStrokeColor(context, job->obj->pencolor.u.RGBA [0], job->obj->pencolor.u.RGBA [1], job->obj->pencolor.u.RGBA [2], job->obj->pencolor.u.RGBA [3]);
+       CGContextSetLineWidth(context, job->obj->penwidth); // *job->scale.x);
+       
+       /* draw the path */
+       CGContextDrawPath(context, filled ? kCGPathFillStroke : kCGPathStroke);
+}
+
+static void quartzgen_ellipse(GVJ_t *job, pointf *A, int filled)
+{
+       /* convert ellipse into the current path */
+       CGContextRef context = (CGContextRef)job->context;
+       double dx = A[1].x - A[0].x;
+       double dy = A[1].y - A[0].y;
+       CGContextAddEllipseInRect(context, CGRectMake(A[0].x - dx, A[0].y - dy, dx * 2.0, dy * 2.0));
+
+       /* draw the ellipse */
+       quartzgen_path(job, filled);
+}
+
+static void quartzgen_polygon(GVJ_t *job, pointf *A, int n, int filled)
+{
+       /* convert polygon into the current path */
+       CGContextRef context = (CGContextRef)job->context;
+       CGContextMoveToPoint(context, A[0].x, A[0].y);
+       int i;
+       for (i = 1; i < n; ++i)
+               CGContextAddLineToPoint(context, A[i].x, A[i].y);
+       CGContextClosePath(context);
+
+       /* draw the ellipse */
+       quartzgen_path(job, filled);
+}
+
+static void
+quartzgen_bezier(GVJ_t *job, pointf *A, int n, int arrow_at_start,
+            int arrow_at_end, int filled)
+{
+       /* convert bezier into the current path */
+       CGContextRef context = (CGContextRef)job->context;
+       CGContextMoveToPoint(context, A[0].x, A[0].y);
+       int i;
+       for (i = 1; i < n; i += 3)
+               CGContextAddCurveToPoint(context, A[i].x, A[i].y, A[i+1].x, A[i+1].y, A[i+2].x, A[i+2].y);
+       
+       /* draw the ellipse */
+       quartzgen_path(job, filled);
+}
+
+static void quartzgen_polyline(GVJ_t *job, pointf *A, int n)
+{
+       /* convert polyline into the current path */
+       CGContextRef context = (CGContextRef)job->context;
+       CGContextMoveToPoint(context, A[0].x, A[0].y);
+       int i;
+       for (i = 1; i < n; ++i)
+               CGContextAddLineToPoint(context, A[i].x, A[i].y);
+       
+       /* draw the ellipse */
+       quartzgen_path(job, FALSE);
+}
+
+static gvrender_engine_t quartzgen_engine = {
+    quartzgen_begin_job,
+    quartzgen_end_job,
+    0,                                                 /* quartzgen_begin_graph */
+    0,                                                 /* quartzgen_end_graph */
+    0,                                                 /* quartzgen_begin_layer */
+    0,                                                 /* quartzgen_end_layer */
+    quartzgen_begin_page,
+    quartzgen_end_page,
+    0,                                                 /* quartzgen_begin_cluster */
+    0,                                                 /* quartzgen_end_cluster */
+    0,                                                 /* quartzgen_begin_nodes */
+    0,                                                 /* quartzgen_end_nodes */
+    0,                                                 /* quartzgen_begin_edges */
+    0,                                                 /* quartzgen_end_edges */
+    0,                                                 /* quartzgen_begin_node */
+    0,                                                 /* quartzgen_end_node */
+    0,                                                 /* quartzgen_begin_edge */
+    0,                                                 /* quartzgen_end_edge */
+    quartzgen_begin_anchor,
+    0,                                                 /* quartzgen_end_anchor */
+    quartzgen_textpara,
+    0,
+    quartzgen_ellipse,
+    quartzgen_polygon,
+    quartzgen_bezier,
+    quartzgen_polyline,
+    0,                                                 /* quartzgen_comment */
+    0,                                                 /* quartzgen_library_shape */
+};
+
+static gvrender_features_t render_features_quartz = {
+    GVRENDER_DOES_MAPS
+               | GVRENDER_DOES_MAP_RECTANGLE
+               | GVRENDER_DOES_TRANSFORM
+               | GVRENDER_NO_BG,       /* flags */
+    4.,                                                        /* default pad - graph units */
+    NULL,                                              /* knowncolors */
+    0,                                                 /* sizeof knowncolors */
+    RGBA_DOUBLE                                /* color_type */
+};
+
+static gvdevice_features_t device_features_quartz = {
+    GVDEVICE_BINARY_FORMAT
+      | GVDEVICE_DOES_TRUECOLOR,/* flags */
+    {0.,0.},                    /* default margin - points */
+    {0.,0.},                    /* default page width, height - points */
+    {96.,96.}                  /* dpi */
+};
+
+static gvdevice_features_t device_features_quartz_paged = {
+       GVDEVICE_DOES_PAGES
+         | GVDEVICE_BINARY_FORMAT
+      | GVDEVICE_DOES_TRUECOLOR,/* flags */
+    {36.,36.},                    /* default margin - points */
+    {0.,0.},                    /* default page width, height - points */
+    {72.,72.}                  /* dpi */
+};
+
+gvplugin_installed_t gvrender_quartz_types[] = {
+    {0, "quartz", 1, &quartzgen_engine, &render_features_quartz},
+    {0, NULL, 0, NULL, NULL}
+};
+
+gvplugin_installed_t gvdevice_quartz_types[] = {
+       {FORMAT_BMP, "bmp:quartz", 1, NULL, &device_features_quartz},
+       {FORMAT_GIF, "gif:quartz", 1, NULL, &device_features_quartz},
+       {FORMAT_EXR, "exr:quartz", 1, NULL, &device_features_quartz},
+       {FORMAT_JPEG, "jpe:quartz", 1, NULL, &device_features_quartz},
+       {FORMAT_JPEG, "jpeg:quartz", 1, NULL, &device_features_quartz},
+       {FORMAT_JPEG, "jpg:quartz", 1, NULL, &device_features_quartz},
+       {FORMAT_JPEG2000, "jp2:quartz", 1, NULL, &device_features_quartz},
+       {FORMAT_PDF, "pdf:quartz", 1, NULL, &device_features_quartz_paged},
+       {FORMAT_PICT, "pct:quartz", 1, NULL, &device_features_quartz},
+       {FORMAT_PICT, "pict:quartz", 1, NULL, &device_features_quartz},
+       {FORMAT_PNG, "png:quartz", 1, NULL, &device_features_quartz},
+       {FORMAT_PSD, "psd:quartz", 1, NULL, &device_features_quartz},
+       {FORMAT_SGI, "sgi:quartz", 1, NULL, &device_features_quartz},
+       {FORMAT_TIFF, "tif:quartz", 1, NULL, &device_features_quartz},
+       {FORMAT_TIFF, "tiff:quartz", 1, NULL, &device_features_quartz},
+       {FORMAT_TGA, "tga:quartz", 1, NULL, &device_features_quartz},
+       {0, NULL, 0, NULL, NULL}
+};
\ No newline at end of file