From 235e290936796be4fbddce8f4914c9fa473834bb Mon Sep 17 00:00:00 2001 From: Airy ANDRE Date: Wed, 18 Jan 2012 15:59:12 -0800 Subject: [PATCH] Basic support for Image in PDF Context + support for printing to a PDF file --- AppKit/NSApplication.m | 1 - AppKit/NSDocument.m | 1 - AppKit/NSPrintInfo.h | 7 ++++ AppKit/NSPrintInfo.m | 7 ++++ AppKit/NSPrintOperation.m | 31 ++++++++++++---- Onyx2D/O2Image+PDF.m | 68 ++++++++++++++++++++++++++++------ Onyx2D/O2PDFContext.h | 3 +- Onyx2D/O2PDFContext.m | 77 +++++++++++++++++++++++---------------- Onyx2D/O2PDFxref.m | 2 +- 9 files changed, 141 insertions(+), 56 deletions(-) diff --git a/AppKit/NSApplication.m b/AppKit/NSApplication.m index c6dd2ac5..7c444fed 100755 --- a/AppKit/NSApplication.m +++ b/AppKit/NSApplication.m @@ -73,7 +73,6 @@ id NSApp=nil; +(void)initialize { if(self==[NSApplication class]){ - [NSClassFromString(@"Win32RunningCopyPipe") performSelector:@selector(startRunningCopyPipe)]; } } diff --git a/AppKit/NSDocument.m b/AppKit/NSDocument.m index 7d63a6af..25ec46a3 100755 --- a/AppKit/NSDocument.m +++ b/AppKit/NSDocument.m @@ -793,7 +793,6 @@ forSaveOperation:(NSSaveOperationType)operation if(operation==nil){ return; } - [operation setShowsPrintPanel:showPrintPanel]; [operation runOperation]; } diff --git a/AppKit/NSPrintInfo.h b/AppKit/NSPrintInfo.h index d963f46e..c0a26485 100755 --- a/AppKit/NSPrintInfo.h +++ b/AppKit/NSPrintInfo.h @@ -24,6 +24,13 @@ APPKIT_EXPORT NSString * const NSPrintPrinterName; APPKIT_EXPORT NSString * const NSPrintJobDisposition; APPKIT_EXPORT NSString * const NSPrintDetailedErrorReporting; +APPKIT_EXPORT NSString * const NSPrintSpoolJob; +APPKIT_EXPORT NSString * const NSPrintPreviewJob; +APPKIT_EXPORT NSString * const NSPrintSaveJob; +APPKIT_EXPORT NSString * const NSPrintCancelJob; + +APPKIT_EXPORT NSString * const NSPrintSavePath; + APPKIT_EXPORT NSString * const NSPrintCopies; APPKIT_EXPORT NSString * const NSPrintAllPages; APPKIT_EXPORT NSString * const NSPrintFirstPage; diff --git a/AppKit/NSPrintInfo.m b/AppKit/NSPrintInfo.m index ea906d26..c6297e9d 100755 --- a/AppKit/NSPrintInfo.m +++ b/AppKit/NSPrintInfo.m @@ -18,6 +18,13 @@ NSString * const NSPrintPrinterName=@"NSPrintPrinterName"; NSString * const NSPrintJobDisposition=@"NSPrintJobDisposition"; NSString * const NSPrintDetailedErrorReporting=@"NSPrintDetailedErrorReporting"; +NSString * const NSPrintSpoolJob = @"NSPrintSpoolJob"; +NSString * const NSPrintPreviewJob = @"NSPrintPreviewJob"; +NSString * const NSPrintSaveJob = @"NSPrintSaveJob"; +NSString * const NSPrintCancelJob = @"NSPrintCancelJob"; + +NSString * const NSPrintSavePath = @"NSPrintSavePath"; + NSString * const NSPrintCopies=@"NSPrintCopies"; NSString * const NSPrintAllPages=@"NSPrintAllPages"; NSString * const NSPrintFirstPage=@"NSPrintFirstPage"; diff --git a/AppKit/NSPrintOperation.m b/AppKit/NSPrintOperation.m index 0f8abbf6..0ad3bdc1 100755 --- a/AppKit/NSPrintOperation.m +++ b/AppKit/NSPrintOperation.m @@ -17,7 +17,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI enum { NSPrintOperationPDFInRect, NSPrintOperationEPSInRect, - NSPrintOperationPrinter, + NSPrintOperationPrinter, + NSPrintOperationFile, }; @implementation NSPrintOperation @@ -31,7 +32,7 @@ static NSPrintOperation *_currentOperation=nil; -initWithView:(NSView *)view printInfo:(NSPrintInfo *)printInfo insideRect:(NSRect)rect toData:(NSMutableData *)data type:(int)type { _view=[view retain]; _printInfo=[printInfo copy]; - if(type==NSPrintOperationPDFInRect || type==NSPrintOperationEPSInRect) + if(type==NSPrintOperationPDFInRect || type==NSPrintOperationEPSInRect || type==NSPrintOperationFile) [_printInfo setPaperSize:rect.size]; _printPanel=[[NSPrintPanel printPanel] retain]; _showsPrintPanel=YES; @@ -43,7 +44,12 @@ static NSPrintOperation *_currentOperation=nil; } -initWithView:(NSView *)view printInfo:(NSPrintInfo *)printInfo { - return [self initWithView:view printInfo:printInfo insideRect:[view bounds] toData:nil type:NSPrintOperationPrinter]; + int type = NSPrintOperationPrinter; + + if ([[printInfo jobDisposition] isEqualToString: NSPrintSaveJob]) { + type = NSPrintOperationFile; + } + return [self initWithView:view printInfo:printInfo insideRect:[view bounds] toData:nil type:type]; } -(void)dealloc { @@ -219,8 +225,7 @@ static NSPrintOperation *_currentOperation=nil; if((context=(CGContextRef)[[_printInfo dictionary] objectForKey:@"_KGContext"])==nil) return nil; - } - else if(_type==NSPrintOperationPDFInRect){ + } else if(_type==NSPrintOperationPDFInRect){ NSDictionary *auxiliaryInfo=[NSDictionary dictionaryWithObject:[[_view window] title] forKey:(NSString *)kCGPDFContextTitle]; CGDataConsumerRef consumer=CGDataConsumerCreateWithCFData((CFMutableDataRef)_mutableData); @@ -229,10 +234,20 @@ static NSPrintOperation *_currentOperation=nil; [(id)context autorelease]; CGDataConsumerRelease(consumer); - } - else + } else if (_type == NSPrintOperationFile) { + NSDictionary *auxiliaryInfo=[NSDictionary dictionaryWithObject:[[_view window] title] forKey:(NSString *)kCGPDFContextTitle]; + + NSString *filePath = [_printInfo.dictionary objectForKey:NSPrintSavePath]; + CGDataConsumerRef consumer=CGDataConsumerCreateWithURL((CFURLRef)[NSURL fileURLWithPath:filePath]); + + context=CGPDFContextCreate(consumer,&_insideRect,(CFDictionaryRef)auxiliaryInfo); + [(id)context autorelease]; + + CGDataConsumerRelease(consumer); + + } else { return nil; - + } return [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO]; } diff --git a/Onyx2D/O2Image+PDF.m b/Onyx2D/O2Image+PDF.m index b2c4c753..c73edb84 100644 --- a/Onyx2D/O2Image+PDF.m +++ b/Onyx2D/O2Image+PDF.m @@ -44,8 +44,10 @@ const char *O2ImageNameWithIntent(O2ColorRenderingIntent intent){ } -(O2PDFObject *)encodeReferenceWithContext:(O2PDFContext *)context { - O2PDFStream *result=[O2PDFStream pdfStream]; - O2PDFDictionary *dictionary=[O2PDFDictionary pdfDictionary]; + O2PDFStream *result=[O2PDFStream pdfStream]; + O2PDFDictionary *dictionary=[result dictionary]; + + const void *bytes = [self directBytes]; [dictionary setNameForKey:"Type" value:"XObject"]; [dictionary setNameForKey:"Subtype" value:"Image"]; @@ -60,15 +62,62 @@ const char *O2ImageNameWithIntent(O2ColorRenderingIntent intent){ [dictionary setObjectForKey:"Mask" value:[_mask encodeReferenceWithContext:context]]; if(_decode!=NULL) [dictionary setObjectForKey:"Decode" value:[O2PDFArray pdfArrayWithNumbers:_decode count:O2ColorSpaceGetNumberOfComponents(_colorSpace)*2]]; - [dictionary setBooleanForKey:"Interpolate" value:_interpolate]; - /* FIX, generate soft mask + [dictionary setBooleanForKey:"Interpolate" value:_interpolate]; + + /* FIX, generate soft mask for alpha data [dictionary setObjectForKey:"SMask" value:[softMask encodeReferenceWithContext:context]]; */ + //[dictionary setNameForKey:"Filter" value:"FlateDecode"]; // Zipped - /* FIX - */ - - return [context encodeIndirectPDFObject:result]; + // TODO : we should at least flate encode the data + // And it would be nice if jpg data would stay jpg data (instead of an uncompress stream), as it does + // with Quartz and CGImage + + // Export RGB bytes, without the alpha data, in the expected order + // TODO : support non 32 bits pixels, respect the premultiplied state, non-RGB pixels... + const uint8_t *ptr = bytes; + int alphaInfo = _bitmapInfo & kO2BitmapAlphaInfoMask; + BOOL hasAlpha = alphaInfo != kO2ImageAlphaNone; + BOOL alphaFirst = alphaInfo == kO2ImageAlphaPremultipliedFirst || alphaInfo == kO2ImageAlphaFirst || alphaInfo == kO2ImageAlphaNoneSkipFirst; + BOOL alphaLast = hasAlpha && (alphaFirst == NO); + BOOL bigEndian = _bitmapInfo & kO2BitmapByteOrder32Big; + BOOL littleEndian = bigEndian == NO; + int bytesPerPixel = _bitsPerPixel/8; + for (int i = 0; i < _height; i++, ptr += _bytesPerRow) { + const uint8_t *linePtr = ptr; + for (int j = 0; j < _width; j++, linePtr += bytesPerPixel) { + const uint8_t *pixelPtr = linePtr; + /* + AlphaFirst => The Alpha channel is next to the Red channel + (ARGB and BGRA are both Alpha First formats) + AlphaLast => The Alpha channel is next to the Blue channel + (RGBA and ABGR are both Alpha Last formats) + + LittleEndian => Blue comes before Red + (BGRA and ABGR are Little endian formats) + BigEndian => Red comes before Blue + (ARGB and RGBA are Big endian formats). + */ + uint8_t r = 0, g = 0, b = 0; + if ((alphaFirst && bigEndian) || (alphaLast && littleEndian)) { + // Skip the alpha + ++pixelPtr; + } + if (bigEndian) { + r = *pixelPtr++; + g = *pixelPtr++; + b = *pixelPtr++; + } else { + b = *pixelPtr++; + g = *pixelPtr++; + r = *pixelPtr++; + } + [[result mutableData] appendBytes:&r length: 1]; + [[result mutableData] appendBytes:&g length: 1]; + [[result mutableData] appendBytes:&b length: 1]; + } + } + return [context encodeIndirectPDFObject:result]; } @@ -91,8 +140,6 @@ const char *O2ImageNameWithIntent(O2ColorRenderingIntent intent){ O2PDFStream *softMaskStream=nil; O2Image *softMask=NULL; - NSLog(@"PDF Image=%@",dictionary); - if(![dictionary getIntegerForKey:"Width" value:&width]){ O2PDFError(__FILE__,__LINE__,@"Image has no Width"); return NULL; @@ -199,7 +246,6 @@ const char *O2ImageNameWithIntent(O2ColorRenderingIntent intent){ O2DataProviderRelease(provider); O2ColorSpaceRelease(colorSpace); - NSLog(@"image=%@",image); return image; } diff --git a/Onyx2D/O2PDFContext.h b/Onyx2D/O2PDFContext.h index 2ca5b9ca..f2e5b192 100644 --- a/Onyx2D/O2PDFContext.h +++ b/Onyx2D/O2PDFContext.h @@ -15,7 +15,6 @@ const NSString *kO2PDFContextTitle; @interface O2PDFContext : O2Context { O2DataConsumer *_dataConsumer; - NSMutableData *_mutableData; NSMutableDictionary *_fontCache; NSMapTable *_objectToRef; NSMutableArray *_indirectObjects; @@ -30,12 +29,12 @@ const NSString *kO2PDFContextTitle; NSMutableDictionary *_categoryToNext; NSMutableArray *_textStateStack; NSMutableArray *_contentStreamStack; + size_t _length; } -initWithConsumer:(O2DataConsumer *)consumer mediaBox:(const O2Rect *)mediaBox auxiliaryInfo:(NSDictionary *)auxiliaryInfo; -(unsigned)length; --(NSData *)data; -(void)appendData:(NSData *)data; -(void)appendBytes:(const void *)ptr length:(unsigned)length; diff --git a/Onyx2D/O2PDFContext.m b/Onyx2D/O2PDFContext.m index 737fb3c7..732fc131 100644 --- a/Onyx2D/O2PDFContext.m +++ b/Onyx2D/O2PDFContext.m @@ -32,16 +32,20 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI #import #import #import +#import const NSString *kO2PDFContextTitle=@"kO2PDFContextTitle"; @implementation O2PDFContext +-(void)clipToState:(O2ClipState *)clipState +{ +} + -initWithConsumer:(O2DataConsumer *)consumer mediaBox:(const O2Rect *)mediaBox auxiliaryInfo:(NSDictionary *)auxiliaryInfo { [super init]; _dataConsumer=[consumer retain]; - _mutableData=[[consumer mutableData] retain]; _fontCache=[NSMutableDictionary new]; _objectToRef=NSCreateMapTable(NSObjectMapKeyCallBacks,NSObjectMapValueCallBacks,0); _indirectObjects=[NSMutableArray new]; @@ -89,7 +93,6 @@ const NSString *kO2PDFContextTitle=@"kO2PDFContextTitle"; -(void)dealloc { [_dataConsumer release]; - [_mutableData release]; [_fontCache release]; NSFreeMapTable(_objectToRef); [_indirectObjects release]; @@ -107,29 +110,25 @@ const NSString *kO2PDFContextTitle=@"kO2PDFContextTitle"; } -(unsigned)length { - return [_mutableData length]; -} - --(NSData *)data { - return _mutableData; -} - --(void)appendData:(NSData *)data { - [_mutableData appendData:data]; + return _length; } -(void)appendBytes:(const void *)ptr length:(unsigned)length { - [_mutableData appendBytes:ptr length:length]; + _length += length; + O2DataConsumerPutBytes(_dataConsumer, ptr, length); +} + +-(void)appendData:(NSData *)data { + [self appendBytes:[data bytes] length:[data length]]; } -(void)appendCString:(const char *)cString { - [_mutableData appendBytes:cString length:strlen(cString)]; + [self appendBytes:cString length:strlen(cString)]; } -(void)appendString:(NSString *)string { NSData *data=[string dataUsingEncoding:NSASCIIStringEncoding]; - - [_mutableData appendData:data]; + [self appendData:data]; } -(void)appendFormat:(NSString *)format,... { @@ -145,7 +144,7 @@ const NSString *kO2PDFContextTitle=@"kO2PDFContextTitle"; va_end(arguments); } --(void)appendPDFStringWithBytes:(const void *)bytesV length:(unsigned)length mutableData:(NSMutableData *)data { +-(void)appendPDFStringWithBytes:(const void *)bytesV length:(unsigned)length toObject:(id)data { const unsigned char *bytes=bytesV; BOOL hex=NO; int i; @@ -183,7 +182,7 @@ const NSString *kO2PDFContextTitle=@"kO2PDFContextTitle"; } -(void)appendPDFStringWithBytes:(const void *)bytes length:(unsigned)length { - [self appendPDFStringWithBytes:bytes length:length mutableData:_mutableData]; + [self appendPDFStringWithBytes:bytes length:length toObject:self]; } -(BOOL)hasReferenceForObject:(O2PDFObject *)object { @@ -247,7 +246,7 @@ const NSString *kO2PDFContextTitle=@"kO2PDFContextTitle"; } -(void)contentPDFStringWithBytes:(const void *)bytes length:(unsigned)length { - [self appendPDFStringWithBytes:bytes length:length mutableData:[[_contentStreamStack lastObject] mutableData]]; + [self appendPDFStringWithBytes:bytes length:length toObject:[[_contentStreamStack lastObject] mutableData]]; } -(O2PDFObject *)referenceForFontWithName:(NSString *)name size:(float)size { @@ -287,8 +286,8 @@ const NSString *kO2PDFContextTitle=@"kO2PDFContextTitle"; const char *objectName=[[NSString stringWithFormat:@"%s%d",categoryName,[next intValue]] cString]; [category setObjectForKey:objectName value:pdfObject]; - - return [O2PDFObject_Name pdfObjectWithCString:objectName]; + + return [O2PDFObject_Name pdfObjectWithCString:objectName]; } -(void)emitPath:(O2PathRef)path { @@ -340,6 +339,15 @@ const NSString *kO2PDFContextTitle=@"kO2PDFContextTitle"; } } +- (void)translateCTM:(float)x:(float)y +{ + [self contentWithFormat:@"1 0 0 1 %f %f cm ", x, y]; +} +- (void)scaleCTM:(float)x:(float)y +{ + [self contentWithFormat:@"%f 0 0 %f 0 0 cm ", x, y]; +} + -(void)emitSaveGState { [self contentWithString:@"q "]; } @@ -436,7 +444,7 @@ const NSString *kO2PDFContextTitle=@"kO2PDFContextTitle"; [self contentWithFormat:@"%g i ",gState->_flatness]; O2AffineTransform matrix=gState->_userSpaceTransform; - [self contentWithFormat:@"%g %g %g %g %g %g cm ",matrix.a,matrix.b,matrix.c,matrix.d,matrix.tx,matrix.ty]; + [self contentWithFormat:@"%g %g %g %g %g %g cm ",matrix.a,matrix.b,matrix.c,matrix.d,matrix.tx,matrix.ty]; NSArray *clipPhases=[O2GStateClipState(gState) clipPhases]; @@ -457,7 +465,7 @@ const NSString *kO2PDFContextTitle=@"kO2PDFContextTitle"; } break; - case O2ClipPhaseMask: + case O2ClipPhaseMask: { #if 0 O2PDFObject *pdfObject=[image encodeReferenceWithContext:self]; O2PDFObject *name=[self nameForResource:pdfObject inCategory:"XObject"]; @@ -468,6 +476,7 @@ const NSString *kO2PDFContextTitle=@"kO2PDFContextTitle"; [self contentWithFormat:@"%@ Do ",name]; [self contentWithString:@"Q "]; #endif + } break; } } @@ -545,16 +554,17 @@ const NSString *kO2PDFContextTitle=@"kO2PDFContextTitle"; -(void)drawImage:(O2Image *)image inRect:(O2Rect)rect { [self emitSaveGState]; - [self emitCurrentGState]; - O2PDFObject *pdfObject=[image encodeReferenceWithContext:self]; + + O2PDFObject *pdfObject=[image encodeReferenceWithContext:self]; O2PDFObject *name=[self nameForResource:pdfObject inCategory:"XObject"]; - - [self contentWithString:@"q "]; -// FIXME: enable these ctm changes -// [self translateCTM:rect.origin.x:rect.origin.y]; -// [self scaleCTM:rect.size.width:rect.size.height]; + [self emitCurrentGState]; + + [self emitSaveGState]; + [self translateCTM:rect.origin.x:rect.origin.y]; + [self scaleCTM:rect.size.width:rect.size.height]; [self contentWithFormat:@"%@ Do ",name]; - [self contentWithString:@"Q "]; + [self emitRestoreGState]; + [self emitRestoreGState]; } @@ -584,10 +594,11 @@ const NSString *kO2PDFContextTitle=@"kO2PDFContextTitle"; for(i=0;i<[_indirectObjects count];i++){ // do not cache 'count', can grow during encoding O2PDFxrefEntry *entry=[_indirectEntries objectAtIndex:i]; O2PDFObject *object=[_indirectObjects objectAtIndex:i]; - unsigned position=[_mutableData length]; + unsigned position=[self length]; [entry setPosition:position]; - [self appendFormat:@"%d %d obj\n",[entry number],[entry generation]]; + + [self appendFormat:@"%d %d obj\n",[entry number],[entry generation]]; [object encodeWithPDFContext:self]; [self appendFormat:@"endobj\n"]; } @@ -616,6 +627,8 @@ const NSString *kO2PDFContextTitle=@"kO2PDFContextTitle"; -(void)close { [self internIndirectObjects]; [self encodePDFObject:(id)_xref]; + [_dataConsumer release]; + _dataConsumer=nil; } @end diff --git a/Onyx2D/O2PDFxref.m b/Onyx2D/O2PDFxref.m index c38a44a9..1101a1a6 100755 --- a/Onyx2D/O2PDFxref.m +++ b/Onyx2D/O2PDFxref.m @@ -151,7 +151,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI for(i=0;i