Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
sampletopdf.c
/* |
File: sampletopdf.c |
Abstract: Sample driver to PDF test backend for CUPS. |
MOST PRINTER DRIVERS DO NOT REQUIRE A BACKEND. DO NOT USE THIS CODE IN A |
PRODUCTION PRINTER DRIVER. This backend is provided for testing/ |
experimentation with the "Sample Raster Printer" example raster printer |
driver for CUPS. |
Version: 4.0 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2011 Apple Inc. All Rights Reserved. |
*/ |
#include <ApplicationServices/ApplicationServices.h> |
#include <CoreFoundation/CoreFoundation.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <string.h> |
#include <signal.h> |
#include "sample.h" |
#include <cups/backend.h> |
/* |
* Local functions... |
*/ |
static void free_data(void *info, const void *data, size_t size); |
static void load_levels(int cmyk[4]); |
static void save_levels(int cmyk[4]); |
static void update_ink_levels(int cmyk[4], unsigned char *line, int bytes, |
int depth, int resolution); |
/* |
* 'main()' - Read sample raster data and write a PDF file. |
*/ |
int /* O - Exit status */ |
main(int argc, /* I - Number of command-line arguments */ |
char *argv[]) /* I - Command-line arguments */ |
{ |
int document; /* Looping var */ |
CGContextRef context; /* PDF context */ |
char basename[1024], /* Base filename */ |
*baseptr, /* Pointer into base filename */ |
filename[1024], /* Actual filename */ |
line[1024], /* Line from file */ |
*value; /* Value from line */ |
int linenum; /* Current line number */ |
CGRect page_box; /* Box for page size */ |
unsigned raster_width, /* Width of page image */ |
raster_height, /* Height of page image */ |
raster_depth, /* Depth of page image - 1 (grayscale) or 3 (RGB) */ |
raster_size; /* Total size of page image */ |
int resolution; /* Computed resolution */ |
unsigned char *raster_data, /* Page buffer */ |
*raster_ptr, /* Pointer into page buffer */ |
*raster_end; /* Pointer to end of page buffer */ |
int cmyk[4]; /* CMYK "ink" levels */ |
CFStringRef cf_filename; /* CoreFoundation filename */ |
CFURLRef cf_fileurl; /* CoreFoundation file URL */ |
cups_file_t *fp; /* Input file */ |
/* |
* Localize... |
*/ |
SetLocale(); |
/* |
* Validate command-line... |
*/ |
if (argc == 1) |
{ |
/* |
* List devices... |
*/ |
puts("direct sampletopdf://Acme/Sample%20Raster \"Acme Sample Raster\" \"Sample Raster Driver\" \"MFG:Acme;MODEL:Sample Raster;\""); |
return (CUPS_BACKEND_OK); |
} |
else if (argc < 6 || argc > 7) |
{ |
/* |
* Bad invocation... |
*/ |
fputs("Usage: sampletopdf job user title copies options [filename]\n", |
stderr); |
return (CUPS_BACKEND_STOP); |
} |
else if (argc == 6) |
fp = cupsFileStdin(); |
else if ((fp = cupsFileOpen(argv[6], "r")) == NULL) |
{ |
LogMessage("ERROR", CFCopyLocalizedString(CFSTR("Unable to open print file - %s"), NULL), strerror(errno)); |
return (CUPS_BACKEND_STOP); |
} |
/* |
* Prepare base output filename using job ID (argv[1]) and title (argv[3])... |
*/ |
snprintf(basename, sizeof(basename), "%s - %s", argv[1], argv[3]); |
for (baseptr = basename; *baseptr; baseptr ++) |
if ((!(*baseptr & 0x80) && *baseptr < ' ') || *baseptr == '/' || |
*baseptr == 0x7f) |
*baseptr = '_'; |
/* |
* Prepare a directory to hold the files... |
*/ |
snprintf(filename, sizeof(filename), "/Library/Caches/%s", getenv("PRINTER")); |
mkdir(filename, 0755); |
chmod(filename, 0755); |
/* |
* Read lines from file until we see end-of-file... |
*/ |
context = NULL; |
linenum = 0; |
document = 0; |
raster_data = NULL; |
resolution = 100; |
page_box.origin.x = page_box.origin.y = page_box.size.width = page_box.size.height = 0.0; |
load_levels(cmyk); |
while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) |
{ |
if (!strcmp(line, "DOCUMENT")) |
{ |
if (context) |
{ |
CGContextRelease(context); |
context = NULL; |
} |
document ++; |
snprintf(filename, sizeof(filename), "/Library/Caches/%s/%s%d.pdf", getenv("PRINTER"), basename, document); |
cf_filename = CFStringCreateWithCString(kCFAllocatorDefault, filename, kCFStringEncodingUTF8); |
if (cf_filename) |
{ |
cf_fileurl = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, cf_filename, kCFURLPOSIXPathStyle, false); |
if (cf_fileurl) |
{ |
context = CGPDFContextCreateWithURL(cf_fileurl, NULL, NULL); |
CFRelease(cf_fileurl); |
} |
CFRelease(cf_filename); |
chmod(filename, 0644); |
fprintf(stderr, "DEBUG: Writing \"%s\"...\n", filename); |
} |
} |
else if (!strcmp(line, "ENDDOCUMENT")) |
{ |
if (context) |
{ |
CGContextRelease(context); |
context = NULL; |
} |
} |
else if (!strcmp(line, "PAGE") && value && context) |
{ |
unsigned rect[4]; /* Rectangle for page */ |
/* |
* Since CGFloat is double for 64-bit builds and float for 32-bit builds, |
* we can't use a simple sscanf to read the page rectangle. Instead, we'll |
* read the rectangle into unsigned integers (that's the form sent by our |
* rastertosample filter) and then copy to the CGRect... |
*/ |
if (sscanf(value, "%u%u%u%u", rect + 0, rect + 1, rect + 2, rect + 3) == 4) |
{ |
page_box.origin.x = rect[0]; |
page_box.origin.y = rect[1]; |
page_box.size.width = rect[2]; |
page_box.size.height = rect[3]; |
fprintf(stderr, "DEBUG: Starting page - [%g %g %g %g]...\n", |
page_box.origin.x, page_box.origin.y, |
page_box.size.width, page_box.size.height); |
CGContextBeginPage(context, &page_box); |
} |
} |
else if (!strcmp(line, "ENDPAGE") && context) |
{ |
if (raster_data) |
{ |
CFStringRef colorSpaceName = NULL; |
if (raster_depth == 1) |
{ |
// kCGColorSpaceGenericGrayGamma2_2 doesn't exist in all versions of Mac OS X |
if (&kCGColorSpaceGenericGrayGamma2_2 != NULL) |
colorSpaceName = kCGColorSpaceGenericGrayGamma2_2; |
else |
colorSpaceName = kCGColorSpaceGenericGray; |
} |
else |
{ |
// kCGColorSpaceSRGB doesn't exist in all versions of Mac OS X |
if (&kCGColorSpaceSRGB != NULL) |
colorSpaceName = kCGColorSpaceSRGB; |
else |
colorSpaceName = kCGColorSpaceGenericRGB; |
} |
CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(colorSpaceName); |
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, raster_data, raster_size, free_data); |
CGImageRef image = CGImageCreate(raster_width, raster_height, 8, raster_depth * 8, raster_width * raster_depth, colorspace, kCGImageAlphaNone, provider, NULL, false, kCGRenderingIntentDefault); |
CGContextDrawImage(context, page_box, image); |
fputs("DEBUG: Drawing image on page...\n", stderr); |
CGImageRelease(image); |
CGDataProviderRelease(provider); |
CGColorSpaceRelease(colorspace); |
} |
fputs("DEBUG: Ending page...\n", stderr); |
CGPDFContextEndPage(context); |
} |
else if (!strcmp(line, "RASTER") && value && !raster_data && page_box.size.width > 0.0 && page_box.size.height > 0.0) |
{ |
/* |
* Get raster dimensions and depth... |
*/ |
if (sscanf(value, "%u%u%u", &raster_width, &raster_height, |
&raster_depth) == 3) |
{ |
if ((raster_depth == 1 || raster_depth == 3) && |
raster_width > 0 && raster_width <= 3600 && |
raster_height > 0 && raster_height <= 5400) |
{ |
/* |
* Allocate memory for up to 12x18" page at 300 DPI. |
*/ |
raster_size = raster_width * raster_height * raster_depth; |
raster_data = malloc(raster_size); |
raster_ptr = raster_data; |
raster_end = raster_data + raster_size; |
resolution = (int)(raster_width * 72.0 / page_box.size.width); |
/* |
* Clear the page to white... |
*/ |
memset(raster_data, 255, raster_size); |
} |
} |
} |
else if (!strcmp(line, "LINE") && value && raster_data) |
{ |
size_t bytes = strtol(value, NULL, 10); |
if ((raster_ptr + bytes) > raster_end) |
{ |
/* |
* Raster data too long! |
*/ |
cupsFileRead(fp, (char *)raster_ptr, raster_end - raster_ptr); |
for (bytes -= raster_end - raster_ptr; bytes > 0; bytes --) |
cupsFileGetChar(fp); |
bytes = raster_end - raster_ptr; |
} |
else |
cupsFileRead(fp, (char *)raster_ptr, bytes); |
/* |
* Update ink usage counters. Normally you'd get this information |
* from the printer itself, but since we are simulating the printer |
* we also have to simulate the ink usage counters and the affect on |
* the printed output. |
*/ |
update_ink_levels(cmyk, raster_ptr, bytes, raster_depth, resolution); |
/* |
* Advance to the next line... |
*/ |
raster_ptr += bytes; |
} |
else if (!strcmp(line, "LEVELS")) |
{ |
/* |
* Report levels... |
*/ |
char levels[255]; /* Ink levels */ |
snprintf(levels, sizeof(levels), "IL%d,%d,%d,%d\n", cmyk[0] / 10000, cmyk[1] / 10000, cmyk[2] / 10000, cmyk[3] / 10000); |
cupsBackChannelWrite(levels, strlen(levels), 1.0); |
} |
else if (!strcmp(line, "CHANGEINK")) |
{ |
/* |
* "Change" ink... |
*/ |
cmyk[0] = cmyk[1] = cmyk[2] = cmyk[3] = 1000000; |
} |
} |
if (context) |
{ |
CGPDFContextClose(context); |
CGContextRelease(context); |
context = NULL; |
} |
save_levels(cmyk); |
return (CUPS_BACKEND_OK); |
} |
/* |
* 'free_data()' - Free bitmap data when CG is done using it. |
*/ |
static void |
free_data(void *info, /* I - Context pointer (unused) */ |
const void *data, /* I - Pointer to data */ |
size_t size) /* I - Size of data buffer (unused) */ |
{ |
(void)info; |
(void)size; |
free((void *)data); |
} |
/* |
* 'load_levels()' - Load the CMYK ink levels from the cache file. |
*/ |
static void |
load_levels(int cmyk[4]) /* O - CMYK levels */ |
{ |
cups_file_t *fp; /* File to read from */ |
char filename[1024], /* Cache filename */ |
line[1024]; /* Line from file */ |
cmyk[0] = cmyk[1] = cmyk[2] = cmyk[3] = 1000000; |
snprintf(filename, sizeof(filename), "/Library/Caches/%s.cmyk", getenv("PRINTER")); |
if ((fp = cupsFileOpen(filename, "r")) != NULL) |
{ |
if (cupsFileGets(fp, line, sizeof(line))) |
sscanf(line, "%d%d%d%d", cmyk + 0, cmyk + 1, cmyk + 2, cmyk + 3); |
cupsFileClose(fp); |
} |
} |
/* |
* 'save_levels()' - Save the CMYK ink levels to the cache file. |
*/ |
static void |
save_levels(int cmyk[4]) /* I - CMYK levels */ |
{ |
cups_file_t *fp; /* File to write to */ |
char filename[1024]; /* Cache filename */ |
snprintf(filename, sizeof(filename), "/Library/Caches/%s.cmyk", getenv("PRINTER")); |
if ((fp = cupsFileOpen(filename, "w")) != NULL) |
{ |
cupsFilePrintf(fp, "%d %d %d %d\n", cmyk[0], cmyk[1], cmyk[2], cmyk[3]); |
cupsFileClose(fp); |
chmod(filename, 0644); |
} |
} |
/* |
* 'update_ink_levels()' - Update the virtual CMYK ink levels based on a line |
* from the page. |
*/ |
static void |
update_ink_levels( |
int cmyk[4], /* IO - CMYK levels */ |
unsigned char *line, /* I - Pixels on the current line */ |
int bytes, /* I - Number of bytes */ |
int depth, /* I - Bytes per pixel */ |
int resolution) /* I - Output resolution */ |
{ |
int c = 0, m = 0, y = 0, k = 0; /* Total CMYK on the line */ |
if (depth == 1) |
{ |
/* |
* Update black ink usage for grayscale output... |
*/ |
if (cmyk[3] <= 0) |
{ |
/* |
* Simulate out-of-ink condition by removing black... |
*/ |
memset(line, 255, bytes); |
} |
else |
{ |
/* |
* Otherwise count the amount of black ink used... |
*/ |
while (bytes > 0) |
{ |
k += 255 - *line; |
bytes --; |
line ++; |
} |
} |
} |
else if (cmyk[0] <= 0 && cmyk[1] <= 0 && cmyk[2] <= 0 && cmyk[3] <= 0) |
{ |
/* |
* Completely out of ink, blank the line to simulate that... |
*/ |
memset(line, 255, bytes); |
} |
else |
{ |
/* |
* Update CMYK ink usage for color output... |
*/ |
int kmin, kmax; |
while (bytes > 0) |
{ |
/* |
* NOTE: Real printers need more complex code than this! |
* |
* The classic RGB to CMYK formula calculates K using the maximum |
* RGB value, and then subtracts it from the C, M, and Y values: |
* |
* K = 1 - max(R,G,B) |
* C = 1 - R - K |
* M = 1 - G - K |
* Y = 1 - B - K |
* |
* Colors tend to look "flat" with this simple formula, so instead we |
* use a formula that calculates black based on both the minimum and |
* maximum RGB values so that the amount of black depends not only on |
* the darkness of the color but how colorful it is. Less colorful |
* colors use more black: |
* |
* (1 - max(R,G,B))^3 |
* K = ------------------ |
* (1 - min(R,G,B))^2 |
* |
* C = 1 - R - K |
* M = 1 - G - K |
* Y = 1 - B - K |
*/ |
kmin = line[0] > line[1] ? |
(line[0] > line[2] ? line[0] : line[2]) : |
(line[1] > line[2] ? line[1] : line[2]); |
kmax = line[0] < line[1] ? |
(line[0] < line[2] ? line[0] : line[2]) : |
(line[1] < line[2] ? line[1] : line[2]); |
if (kmax > kmin) |
{ |
kmin = 255 - kmin; |
kmax = 255 - kmax; |
kmin = 255 - kmin * kmin * kmin / (kmax * kmax); |
} |
/* |
* Add the current CMYK values to our color counters for the line. |
*/ |
c += kmin - line[0]; |
m += kmin - line[1]; |
y += kmin - line[2]; |
k += 255 - kmin; |
if (cmyk[0] <= 0) |
{ |
/* |
* Simulate out-of-cyan-ink condition by removing cyan... |
*/ |
line[0] = kmin; |
} |
if (cmyk[1] <= 0) |
{ |
/* |
* Simulate out-of-magenta-ink condition by removing magenta... |
*/ |
line[1] = kmin; |
} |
if (cmyk[2] <= 0) |
{ |
/* |
* Simulate out-of-yellow-ink condition by removing yellow... |
*/ |
line[2] = kmin; |
} |
if (cmyk[3] <= 0) |
{ |
/* |
* Simulate out-of-black-ink condition by removing black... |
*/ |
kmin = 255 - kmin; |
line[0] += kmin; |
line[1] += kmin; |
line[2] += kmin; |
} |
bytes -= 3; |
line += 3; |
} |
} |
/* |
* Subtract a portion of the CMYK colors used on this line from the |
* ink counters, then limit to a minimum of 0 ink left. |
*/ |
cmyk[0] -= 50 * c / resolution / resolution; |
cmyk[1] -= 50 * m / resolution / resolution; |
cmyk[2] -= 50 * y / resolution / resolution; |
cmyk[3] -= 50 * k / resolution / resolution; |
if (cmyk[0] < 0) |
cmyk[0] = 0; |
if (cmyk[1] < 0) |
cmyk[1] = 0; |
if (cmyk[2] < 0) |
cmyk[2] = 0; |
if (cmyk[3] < 0) |
cmyk[3] = 0; |
} |
Copyright © 2011 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2011-09-06