| /* |
| * R : A Computer Language for Statistical Data Analysis |
| * Copyright (C) 2007-8 The R Foundation |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, a copy is available at |
| * https://www.R-project.org/Licenses/ |
| * |
| * PDF output Quartz device module |
| * |
| * This module creates PDF output using CoreGraphics. Currently |
| * supported targets are file and CFMutableData. The latter is |
| * passed as parv in parameters. |
| * |
| * This file should be compiled only if AQUA is enabled |
| */ |
| |
| |
| #include "qdPDF.h" |
| |
| //#include <R.h> |
| #include <Rinternals.h> |
| //#include <R_ext/QuartzDevice.h> |
| #define _(String) (String) |
| |
| typedef struct { |
| CGContextRef context; /* drawing context */ |
| CFURLRef url; /* destication URL (on NULL is connection or data is used) */ |
| int connection; /* destination connection (currently unsupported) */ |
| int page; /* page number (0 before first NewPage call) */ |
| CGRect bbox; /* bounding box (in points) */ |
| CFMutableDataRef data; /* destination data (if writing to CFMutableData) */ |
| } QuartzPDFDevice; |
| |
| static QuartzFunctions_t *qf; |
| |
| CGContextRef QuartzPDF_GetCGContext(QuartzDesc_t dev,void *userInfo) |
| { |
| return ((QuartzPDFDevice*)userInfo)->context; |
| } |
| |
| void QuartzPDF_NewPage(QuartzDesc_t dev, void *userInfo, int flags) |
| { |
| QuartzPDFDevice *qpd = (QuartzPDFDevice*) userInfo; |
| if (qpd->context) { /* hopefully that's true */ |
| if (qpd->page) CGContextEndPage(qpd->context); |
| CGContextBeginPage(qpd->context, &qpd->bbox); |
| } |
| qpd->page++; |
| } |
| |
| void QuartzPDF_Close(QuartzDesc_t dev, void *userInfo) |
| { |
| QuartzPDFDevice *qpd = (QuartzPDFDevice*) userInfo; |
| |
| if (qpd->context) { /* hopefully that's true */ |
| if (qpd->page) CGContextEndPage(qpd->context); |
| CGContextRelease(qpd->context); |
| } |
| /* Free ourselves */ |
| if (qpd->url) CFRelease(qpd->url); |
| if (qpd->data) CFRelease(qpd->data); |
| free(qpd); |
| } |
| |
| QuartzDesc_t |
| QuartzPDF_DeviceCreate(void *dd, QuartzFunctions_t *fn, QuartzParameters_t *par) |
| { |
| QuartzDesc_t ret = NULL; |
| double *dpi = par->dpi; |
| double mydpi[2] = { 72.0, 72.0 }; |
| double width = par->width, height = par->height; |
| /* DPI is ignored, because PDF is resolution independent. |
| More precisely 72dpi is used to guarantee that PDF and GE |
| coordinates are the same */ |
| dpi = mydpi; |
| |
| if (!qf) qf = fn; |
| |
| QuartzPDFDevice *dev = calloc(1, sizeof(QuartzPDFDevice)); |
| |
| if ((!par->file || ! *par->file)) par->file = "Rplots.pdf"; |
| |
| if (par->parv) dev->data = (CFMutableDataRef) CFRetain((CFTypeRef) par->parv); /* parv if set is CFMutableDataRef to write to */ |
| else if (par->file && *par->file) { |
| CFStringRef path = CFStringCreateWithBytes(kCFAllocatorDefault, (UInt8*) par->file, strlen(par->file), kCFStringEncodingUTF8, FALSE); |
| if (!path || !(dev->url = CFURLCreateWithFileSystemPath (NULL, path, kCFURLPOSIXPathStyle, false))) { |
| warning(_("cannot open file '%s'"), par->file); |
| free(dev); |
| return ret; |
| } |
| CFRelease(path); |
| } |
| dev->bbox = CGRectMake(0, 0, width * 72.0, height * 72.0); |
| CFDictionaryRef ai = 0; |
| { /* optional PDF auxiliary info: we add creator and title (if present) - we could support more ... */ |
| int numK = 1; |
| CFStringRef keys[2], values[2]; |
| keys[0] = kCGPDFContextCreator; |
| values[0] = CFSTR("Quartz R Device"); |
| if (par->title) { |
| keys[numK] = kCGPDFContextTitle; |
| values[numK] = CFStringCreateWithBytes(kCFAllocatorDefault, (UInt8*) par->title, strlen(par->title), kCFStringEncodingUTF8, FALSE); |
| numK++; |
| } |
| ai = CFDictionaryCreate(0, (void*) keys, (void*) values, numK, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
| while (numK) CFRelease(values[--numK]); |
| } |
| |
| if (dev->data) { |
| CGDataConsumerRef consumer = CGDataConsumerCreateWithCFData(dev->data); |
| if (consumer) { |
| dev->context = CGPDFContextCreate(consumer, &dev->bbox, ai); |
| CFRelease(consumer); |
| } |
| } else |
| dev->context = CGPDFContextCreateWithURL(dev->url, &dev->bbox, ai); |
| |
| if (dev->context == NULL) { |
| if (ai) CFRelease(ai); |
| if (dev->url) CFRelease(dev->url); |
| free(dev); |
| return ret; |
| } |
| if (ai) CFRelease(ai); |
| dev->page = 0; |
| |
| /* we need to flip the y coordinate */ |
| CGContextTranslateCTM(dev->context, 0.0, dev->bbox.size.height); |
| CGContextScaleCTM(dev->context, 1.0, -1.0); |
| |
| QuartzBackend_t qdef = { |
| sizeof(qdef), width, height, |
| dpi[0]/72.0, dpi[1]/72.0, par->pointsize, |
| par->bg, par->canvas, par->flags, |
| dev, |
| QuartzPDF_GetCGContext, |
| NULL, /* locate */ |
| QuartzPDF_Close, |
| QuartzPDF_NewPage, |
| NULL, /* state */ |
| NULL, /* par */ |
| NULL, /* sync */ |
| NULL, /* cap */ |
| }; |
| |
| if (!(ret = qf->Create(dd, &qdef))) |
| QuartzPDF_Close(NULL,dev); |
| else { |
| qf->SetSize(ret, width, height); |
| qf->ResetContext(ret); |
| } |
| |
| return ret; |
| } |
| |