Graphviz 13.1.0~dev.20250626.0830
Loading...
Searching...
No Matches
gvrender_quartz.c
Go to the documentation of this file.
1/*************************************************************************
2 * Copyright (c) 2011 AT&T Intellectual Property
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors: Details at https://graphviz.org
9 *************************************************************************/
10
11#include "config.h"
12
13#include <stdlib.h>
14#include <TargetConditionals.h>
15#include <util/gv_math.h>
16
17#if TARGET_OS_IPHONE
18#include <mach/mach_host.h>
19#include <sys/mman.h>
20#include <ImageIO/ImageIO.h>
21#endif
22
23#include <gvc/gvplugin_device.h>
24#include <gvc/gvplugin_render.h>
25#include <cgraph/cgraph.h>
26
27#include "gvplugin_quartz.h"
28
29static CGFloat dashed[] = { 6.0 };
30static CGFloat dotted[] = { 2.0, 6.0 };
31
32static void quartzgen_begin_job(GVJ_t * job)
33{
34 switch (job->device.id) {
35 case FORMAT_CGIMAGE:
36 /* save the passed-in context in the window field, so we can create a CGContext in the context field later on */
37 job->window = job->context;
38 *((CGImageRef *) job->window) = NULL;
39 }
40
41 job->context = NULL;
42}
43
44static void quartzgen_end_job(GVJ_t * job)
45{
46 CGContextRef context = job->context;
47
48#if TARGET_OS_IPHONE
49 void* context_data;
50 size_t context_datalen;
51
52 switch (job->device.id) {
53
54 case FORMAT_PDF:
55 context_data = NULL;
56 context_datalen = 0;
57 break;
58
59 default:
60 context_data = CGBitmapContextGetData(context);
61 context_datalen = CGBitmapContextGetBytesPerRow(context) * CGBitmapContextGetHeight(context);
62 break;
63 }
64#endif
65
66 switch (job->device.id) {
67
68 case FORMAT_PDF:
69 /* save the PDF */
70 CGPDFContextClose(context);
71 break;
72
73 case FORMAT_CGIMAGE:
74 /* create an image and save it where the window field is, which was set to the passed-in context at begin job */
75 *((CGImageRef *) job->window) = CGBitmapContextCreateImage(context);
76 break;
77
78 default: /* bitmap formats */
79 {
80 /* create an image destination */
81 CGDataConsumerRef data_consumer =
82 CGDataConsumerCreate(job,
84 CGImageDestinationRef image_destination =
85 CGImageDestinationCreateWithDataConsumer(data_consumer,
87
88 /* add the bitmap image to the destination and save it */
89 CGImageRef image = CGBitmapContextCreateImage(context);
90 CGImageDestinationAddImage(image_destination, image, NULL);
91 CGImageDestinationFinalize(image_destination);
92
93 /* clean up */
94 if (image_destination)
95 CFRelease(image_destination);
96 CGImageRelease(image);
97 CGDataConsumerRelease(data_consumer);
98 }
99 break;
100 }
101
102 CGContextRelease(context);
103
104#if TARGET_OS_IPHONE
105 if (context_data && context_datalen)
106 munmap(context_data, context_datalen);
107#endif
108}
109
110static void quartzgen_begin_page(GVJ_t * job)
111{
112 CGRect bounds = CGRectMake(0.0, 0.0, job->width, job->height);
113
114 if (!job->context) {
115
116 switch (job->device.id) {
117
118 case FORMAT_PDF:
119 {
120 /* create the auxiliary info for PDF content, author and title */
121 const void *auxiliaryKeys[] = {
122 kCGPDFContextCreator,
123 kCGPDFContextTitle
124 };
125 const void *auxiliaryValues[] = {
126 CFStringCreateWithFormat(NULL, NULL,
127 CFSTR("%s %s"),
128 job->common->info[0],
129 job->common->info[1]),
130 job->obj->type ==
132 CFStringCreateWithCStringNoCopy(NULL,
133 agnameof(job->obj->u.g),
134 kCFStringEncodingUTF8,
135 kCFAllocatorNull)
136 : CFSTR("")
137 };
138 CFDictionaryRef auxiliaryInfo =
139 CFDictionaryCreate(NULL,
140 auxiliaryKeys,
141 auxiliaryValues,
142 sizeof(auxiliaryValues) /
143 sizeof(auxiliaryValues[0]),
144 &kCFTypeDictionaryKeyCallBacks,
145 &kCFTypeDictionaryValueCallBacks);
146
147 /* create a PDF for drawing into */
148 CGDataConsumerRef data_consumer =
149 CGDataConsumerCreate(job,
151 job->context =
152 CGPDFContextCreate(data_consumer, &bounds,
153 auxiliaryInfo);
154
155 /* clean up */
156 CGDataConsumerRelease(data_consumer);
157 CFRelease(auxiliaryInfo);
158 for (size_t i = 0; i < sizeof(auxiliaryValues) / sizeof(auxiliaryValues[0]);
159 ++i)
160 CFRelease(auxiliaryValues[i]);
161 }
162 break;
163
164 default: /* bitmap formats */
165 {
166 size_t bytes_per_row = job->width * BYTES_PER_PIXEL;
167 // align up to a 16-byte boundary
168 if (bytes_per_row % 16 != 0) {
169 bytes_per_row += 16 - (bytes_per_row % 16);
170 }
171
172 void *buffer = NULL;
173
174#if TARGET_OS_IPHONE
175 /* iPhoneOS has no swap files for memory, so if we're short of memory we need to make our own temp scratch file to back it */
176
177 size_t buffer_size = job->height * bytes_per_row;
178 mach_msg_type_number_t vm_info_size = HOST_VM_INFO_COUNT;
179 vm_statistics_data_t vm_info;
180
181 if (host_statistics
182 (mach_host_self(), HOST_VM_INFO,
183 (host_info_t) & vm_info,
184 &vm_info_size) != KERN_SUCCESS
185 || buffer_size * 2 >
186 vm_info.free_count * vm_page_size) {
187 FILE *temp_file = tmpfile();
188 if (temp_file) {
189 int temp_file_descriptor = fileno(temp_file);
190 if (temp_file_descriptor >= 0
191 && ftruncate(temp_file_descriptor,
192 buffer_size) == 0) {
193 buffer = mmap(NULL, buffer_size, PROT_READ | PROT_WRITE,
194 MAP_FILE | MAP_PRIVATE, temp_file_descriptor, 0);
195 if (buffer == MAP_FAILED)
196 buffer = NULL;
197 }
198 fclose(temp_file);
199 }
200 }
201 if (buffer == NULL) {
202 buffer = mmap(NULL, buffer_size, PROT_READ | PROT_WRITE,
203 MAP_ANON | MAP_PRIVATE, -1, 0);
204 if (buffer == MAP_FAILED) {
205 buffer = NULL;
206 }
207 }
208#endif
209
210 /* create a true color bitmap for drawing into */
211 CGColorSpaceRef color_space =
212 CGColorSpaceCreateDeviceRGB();
213 job->context = CGBitmapContextCreate(buffer, /* data: MacOSX lets system allocate, iPhoneOS use manual memory mapping */
214 job->width, /* width in pixels */
215 job->height, /* height in pixels */
216 BITS_PER_COMPONENT, /* bits per component */
217 bytes_per_row, /* bytes per row: align to 16 byte boundary */
218 color_space, /* color space: device RGB */
219 kCGImageAlphaPremultipliedFirst /* bitmap info: premul ARGB has best support in OS X */
220 );
221 job->imagedata = CGBitmapContextGetData(job->context);
222
223 /* clean up */
224 CGColorSpaceRelease(color_space);
225 }
226 break;
227 }
228
229 }
230
231 /* start the page (if this is a paged context) and graphics state */
232 CGContextRef context = job->context;
233 CGContextBeginPage(context, &bounds);
234 CGContextSaveGState(context);
235 /* CGContextSetMiterLimit(context, 1.0); */
236 /* CGContextSetLineJoin(context, kCGLineJoinBevel); */
237
238 /* set up the context transformation */
239 CGContextScaleCTM(context, job->scale.x, job->scale.y);
240 CGContextRotateCTM(context, job->rotation * M_PI / 180.0);
241 CGContextTranslateCTM(context, job->translation.x, job->translation.y);
242}
243
244static void quartzgen_end_page(GVJ_t * job)
245{
246 /* end the page (if this is a paged context) and graphics state */
247 CGContextRef context = job->context;
248 CGContextRestoreGState(context);
249 CGContextEndPage(context);
250}
251
253static CFURLRef make_url(const char *url) {
254 assert(url != NULL);
255 CFStringRef u = CFStringCreateWithCStringNoCopy(NULL, url,
256 kCFStringEncodingUTF8,
257 kCFAllocatorNull);
258 CFURLRef res = CFURLCreateWithString(NULL, u, NULL);
259 CFRelease(u);
260 return res;
261}
262
263static void quartzgen_begin_anchor(GVJ_t * job, char *url, char *tooltip,
264 char *target, char *id)
265{
266 (void)tooltip;
267 (void)target;
268 (void)id;
269
270 pointf *url_map = job->obj->url_map_p;
271 if (url && url_map) {
272 /* set up the hyperlink to the given url */
273 CGContextRef context = job->context;
274 CFURLRef uri = make_url(url);
275 CGPDFContextSetURLForRect(context, uri,
276 /* need to reverse the CTM on the area to get it to work */
277 CGRectApplyAffineTransform(CGRectMake
278 (url_map[0].x,
279 url_map[0].y,
280 url_map[1].
281 x -
282 url_map[0].x,
283 url_map[1].
284 y -
285 url_map[0].
286 y),
287 CGContextGetCTM
288 (context))
289 );
290
291 /* clean up */
292 CFRelease(uri);
293 }
294}
295
296static void quartzgen_path(GVJ_t * job, int filled)
297{
298 CGContextRef context = job->context;
299
300 /* set up colors */
301 if (filled)
302 CGContextSetRGBFillColor(context, job->obj->fillcolor.u.RGBA[0],
303 job->obj->fillcolor.u.RGBA[1],
304 job->obj->fillcolor.u.RGBA[2],
305 job->obj->fillcolor.u.RGBA[3]);
306 CGContextSetRGBStrokeColor(context, job->obj->pencolor.u.RGBA[0],
307 job->obj->pencolor.u.RGBA[1],
308 job->obj->pencolor.u.RGBA[2],
309 job->obj->pencolor.u.RGBA[3]);
310
311 /* set up line style */
312 const CGFloat *segments;
313 size_t segment_count;
314 switch (job->obj->pen) {
315 case PEN_DASHED:
316 segments = dashed;
317 segment_count = sizeof(dashed) / sizeof(CGFloat);
318 break;
319 case PEN_DOTTED:
320 segments = dotted;
321 segment_count = sizeof(dotted) / sizeof(CGFloat);
322 break;
323 default:
324 segments = NULL;
325 segment_count = 0;
326 break;
327 }
328 CGContextSetLineDash(context, 0.0, segments, segment_count);
329
330 /* set up line width */
331 CGContextSetLineWidth(context, job->obj->penwidth); // *job->scale.x);
332
333 /* draw the path */
334 CGContextDrawPath(context, filled ? kCGPathFillStroke : kCGPathStroke);
335}
336
337static void quartzgen_textspan(GVJ_t * job, pointf p, textspan_t * span)
338{
339 CGContextRef context = job->context;
340
341 /* adjust text position */
342 switch (span->just) {
343 case 'r':
344 p.x -= span->size.x;
345 break;
346 case 'l':
347 p.x -= 0.0;
348 break;
349 case 'n':
350 default:
351 p.x -= span->size.x / 2.0;
352 break;
353 }
354 p.y += span->yoffset_centerline;
355
356 void *layout;
357 if (span->free_layout == &quartz_free_layout)
358 layout = span->layout;
359 else
360 layout =
361 quartz_new_layout(span->font->name, span->font->size, span->str);
362
363 CGContextSetRGBFillColor(context, job->obj->pencolor.u.RGBA[0],
364 job->obj->pencolor.u.RGBA[1],
365 job->obj->pencolor.u.RGBA[2],
366 job->obj->pencolor.u.RGBA[3]);
367 quartz_draw_layout(layout, context, CGPointMake(p.x, p.y));
368
369 if (span->free_layout != &quartz_free_layout)
371}
372
373static void quartzgen_ellipse(GVJ_t * job, pointf * A, int filled)
374{
375 /* convert ellipse into the current path */
376 CGContextRef context = job->context;
377 double dx = A[1].x - A[0].x;
378 double dy = A[1].y - A[0].y;
379 CGContextAddEllipseInRect(context,
380 CGRectMake(A[0].x - dx, A[0].y - dy,
381 dx * 2.0, dy * 2.0));
382
383 /* draw the ellipse */
384 quartzgen_path(job, filled);
385}
386
387static void quartzgen_polygon(GVJ_t *job, pointf *A, size_t n, int filled) {
388 /* convert polygon into the current path */
389 CGContextRef context = job->context;
390 CGContextMoveToPoint(context, A[0].x, A[0].y);
391 for (size_t i = 1; i < n; ++i)
392 CGContextAddLineToPoint(context, A[i].x, A[i].y);
393 CGContextClosePath(context);
394
395 /* draw the ellipse */
396 quartzgen_path(job, filled);
397}
398
399static void quartzgen_bezier(GVJ_t *job, pointf *A, size_t n, int filled) {
400 /* convert bezier into the current path */
401 CGContextRef context = job->context;
402 CGContextMoveToPoint(context, A[0].x, A[0].y);
403 for (size_t i = 1; i < n; i += 3)
404 CGContextAddCurveToPoint(context, A[i].x, A[i].y, A[i + 1].x,
405 A[i + 1].y, A[i + 2].x, A[i + 2].y);
406
407 /* draw the ellipse */
408 quartzgen_path(job, filled);
409}
410
411static void quartzgen_polyline(GVJ_t *job, pointf *A, size_t n) {
412 /* convert polyline into the current path */
413 CGContextRef context = job->context;
414 CGContextMoveToPoint(context, A[0].x, A[0].y);
415 for (size_t i = 1; i < n; ++i)
416 CGContextAddLineToPoint(context, A[i].x, A[i].y);
417
418 /* draw the ellipse */
419 quartzgen_path(job, 0);
420}
421
425 0, /* quartzgen_begin_graph */
426 0, /* quartzgen_end_graph */
427 0, /* quartzgen_begin_layer */
428 0, /* quartzgen_end_layer */
431 0, /* quartzgen_begin_cluster */
432 0, /* quartzgen_end_cluster */
433 0, /* quartzgen_begin_nodes */
434 0, /* quartzgen_end_nodes */
435 0, /* quartzgen_begin_edges */
436 0, /* quartzgen_end_edges */
437 0, /* quartzgen_begin_node */
438 0, /* quartzgen_end_node */
439 0, /* quartzgen_begin_edge */
440 0, /* quartzgen_end_edge */
442 0, /* quartzgen_end_anchor */
443 0, /* quartzgen_begin_label */
444 0, /* quartzgen_end_label */
446 0,
451 0, /* quartzgen_comment */
452 0, /* quartzgen_library_shape */
453};
454
457 4., /* default pad - graph units */
458 NULL, /* knowncolors */
459 0, /* sizeof knowncolors */
460 RGBA_DOUBLE /* color_type */
461};
462
465 {0., 0.}, /* default margin - points */
466 {0., 0.}, /* default page width, height - points */
467 {72., 72.} /* dpi */
468};
469
472 {36., 36.}, /* default margin - points */
473 {0., 0.}, /* default page width, height - points */
474 {72., 72.} /* dpi */
475};
476
481
483 {FORMAT_PDF, "pdf:quartz", 8, NULL, &device_features_quartz_paged},
484 {FORMAT_CGIMAGE, "cgimage:quartz", 8, NULL, &device_features_quartz},
485 {FORMAT_BMP, "bmp:quartz", 8, NULL, &device_features_quartz},
486 {FORMAT_GIF, "gif:quartz", 8, NULL, &device_features_quartz},
487 {FORMAT_ICO, "ico:quartz", 8, NULL, &device_features_quartz},
488 {FORMAT_JPEG, "jpe:quartz", 8, NULL, &device_features_quartz},
489 {FORMAT_JPEG, "jpeg:quartz", 8, NULL, &device_features_quartz},
490 {FORMAT_JPEG, "jpg:quartz", 8, NULL, &device_features_quartz},
491 {FORMAT_JPEG2000, "jp2:quartz", 8, NULL, &device_features_quartz},
492 {FORMAT_PNG, "png:quartz", 8, NULL, &device_features_quartz},
493 {FORMAT_TIFF, "tif:quartz", 8, NULL, &device_features_quartz},
494 {FORMAT_TIFF, "tiff:quartz", 8, NULL, &device_features_quartz},
495 {FORMAT_TGA, "tga:quartz", 8, NULL, &device_features_quartz},
496#if !TARGET_OS_IPHONE
497 {FORMAT_EXR, "exr:quartz", 8, NULL, &device_features_quartz},
498 {FORMAT_ICNS, "icns:quartz", 8, NULL, &device_features_quartz},
499 {FORMAT_PICT, "pct:quartz", 8, NULL, &device_features_quartz},
500 {FORMAT_PICT, "pict:quartz", 8, NULL, &device_features_quartz},
501 {FORMAT_PSD, "psd:quartz", 8, NULL, &device_features_quartz},
502 {FORMAT_SGI, "sgi:quartz", 8, NULL, &device_features_quartz},
503#endif
504 {0, NULL, 0, NULL, NULL}
505};
#define M_PI
Definition arith.h:41
abstract graph C library, Cgraph API
@ RGBA_DOUBLE
Definition color.h:27
static float dy
Definition draw.c:38
static float dx
Definition draw.c:37
#define A(n, t)
Definition expr.h:76
node NULL
Definition grammar.y:181
char * agnameof(void *)
returns a string descriptor for the object.
Definition id.c:143
Arithmetic helper functions.
@ BYTES_PER_PIXEL
Definition gv_math.h:88
@ PEN_DOTTED
Definition gvcjob.h:35
@ PEN_DASHED
Definition gvcjob.h:35
#define GVRENDER_NO_WHITE_BG
Definition gvcjob.h:106
#define GVDEVICE_DOES_PAGES
Definition gvcjob.h:87
#define GVDEVICE_DOES_TRUECOLOR
Definition gvcjob.h:90
#define GVDEVICE_BINARY_FORMAT
Definition gvcjob.h:91
#define GVRENDER_DOES_MAPS
Definition gvcjob.h:97
@ ROOTGRAPH_OBJTYPE
Definition gvcjob.h:168
#define GVRENDER_DOES_TRANSFORM
Definition gvcjob.h:95
#define GVRENDER_DOES_MAP_RECTANGLE
Definition gvcjob.h:98
@ FORMAT_ICO
@ FORMAT_TIFF
@ FORMAT_BMP
CGDataConsumerCallbacks device_data_consumer_callbacks
CFStringRef format_to_uti(format_type format)
void quartz_draw_layout(void *layout, CGContextRef context, CGPoint position)
void * quartz_new_layout(char *fontname, double fontsize, char *text)
static const int BITS_PER_COMPONENT
@ FORMAT_PICT
@ FORMAT_CGIMAGE
@ FORMAT_EXR
@ FORMAT_JPEG2000
@ FORMAT_TGA
@ FORMAT_ICNS
@ FORMAT_PSD
@ FORMAT_SGI
void quartz_free_layout(void *layout)
format_type
@ FORMAT_JPEG
Definition gvrender_gd.c:37
@ FORMAT_GIF
Definition gvrender_gd.c:36
@ FORMAT_PNG
Definition gvrender_gd.c:38
@ FORMAT_PDF
static void quartzgen_bezier(GVJ_t *job, pointf *A, size_t n, int filled)
static CGFloat dashed[]
static gvdevice_features_t device_features_quartz_paged
static CGFloat dotted[]
static gvrender_engine_t quartzgen_engine
static void quartzgen_end_page(GVJ_t *job)
static void quartzgen_polygon(GVJ_t *job, pointf *A, size_t n, int filled)
static void quartzgen_path(GVJ_t *job, int filled)
static void quartzgen_begin_job(GVJ_t *job)
static gvrender_features_t render_features_quartz
static void quartzgen_end_job(GVJ_t *job)
gvplugin_installed_t gvrender_quartz_types[]
static gvdevice_features_t device_features_quartz
static void quartzgen_ellipse(GVJ_t *job, pointf *A, int filled)
static void quartzgen_polyline(GVJ_t *job, pointf *A, size_t n)
static CFURLRef make_url(const char *url)
create a Core Foundation URL from a C string
static void quartzgen_begin_page(GVJ_t *job)
static void quartzgen_begin_anchor(GVJ_t *job, char *url, char *tooltip, char *target, char *id)
gvplugin_installed_t gvdevice_quartz_types[]
static void quartzgen_textspan(GVJ_t *job, pointf p, textspan_t *span)
T_cell image
Definition htmlparse.y:340
static int layout(graph_t *g, layout_info *infop)
Definition layout.c:814
char ** info
Definition gvcommon.h:20
int rotation
Definition gvcjob.h:319
obj_state_t * obj
Definition gvcjob.h:269
gvplugin_active_device_t device
Definition gvcjob.h:286
void * context
Definition gvcjob.h:295
unsigned char * imagedata
location of imagedata
Definition gvcjob.h:297
GVCOMMON_t * common
Definition gvcjob.h:267
pointf scale
Definition gvcjob.h:332
unsigned int width
Definition gvcjob.h:327
void * window
Definition gvcjob.h:353
pointf translation
Definition gvcjob.h:333
unsigned int height
Definition gvcjob.h:328
double RGBA[4]
Definition color.h:32
union color_s::@74 u
ingroup plugin_api
Definition gvplugin.h:35
graph_t * g
Definition gvcjob.h:186
pointf * url_map_p
Definition gvcjob.h:240
gvcolor_t fillcolor
Definition gvcjob.h:194
obj_type type
Definition gvcjob.h:184
pen_type pen
Definition gvcjob.h:197
gvcolor_t pencolor
Definition gvcjob.h:194
union obj_state_s::@94 u
double penwidth
Definition gvcjob.h:199
double x
Definition geom.h:29
double y
Definition geom.h:29
char * name
Definition textspan.h:54
double size
Definition textspan.h:57
char * str
Definition textspan.h:65
char just
'l' 'n' 'r'
Definition textspan.h:71
void * layout
Definition textspan.h:67
pointf size
Definition textspan.h:70
textfont_t * font
Definition textspan.h:66
double yoffset_centerline
Definition textspan.h:69
void(* free_layout)(void *layout)
Definition textspan.h:68