gvrender_quartz.c 17 KB

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