// // PDFPage.h // PDFKit // // Created by Dr. H. Nikolaus Schaller on Fri Nov 9 2005. // Copyright (c) 2005 DSITRI. All rights reserved. // #import "PDFKitPrivate.h" @implementation PDFPage // manage annotations // FIXME: add to PDF object hierarchy -> [_page objectForKey:@"Annots"] - (NSArray *) annotations; { return _annotations; } - (void) addAnnotation:(PDFAnnotation *) annotation; { if(![_annotations containsObject:annotation]) [_annotations addObject:annotation]; } - (void) removeAnnotation:(PDFAnnotation *) annotation; { [_annotations removeObject:annotation]; } - (PDFAnnotation *) annotationAtPoint:(NSPoint) point; { // go through annotations and check coordinates return nil; } - (BOOL) displaysAnnotations; { return _displaysAnnotations; } - (void) setDisplaysAnnotations:(BOOL) flag; { _displaysAnnotations=flag; } // get raw text and raw data - (NSAttributedString *) attributedString; { NSEnumerator *e=[[self _content] objectEnumerator]; NSMutableAttributedString *result=[[[NSMutableAttributedString alloc] init] autorelease]; // we can even set some document-wide attributes like margins, page size, ... // and even document info like NSTitleDocumentAttribute NSMutableDictionary *attrib=[NSMutableDictionary dictionaryWithObjectsAndKeys: [NSFont systemFontOfSize:12], NSFontAttributeName, [NSColor blackColor], NSForegroundColorAttributeName, [NSNumber numberWithInt:0], NSSuperscriptAttributeName, nil]; PDFStream *content; id llobj=nil; id lobj=nil; #if 1 NSLog(@"content streams: %@", [self _content]); #endif while((content=[[e nextObject] self])) { // process next content stream NSDictionary *resources; PDFParser *parser; id obj; float ts=0.0; #if 1 NSLog(@"content: %@", content); #endif #if 0 NSLog(@"decoded: %@", [content data]); #endif // NS_DURING resources=[content objectForKey:@"Resources"]; if(!resources) resources=[self _inheritedPageAttribute:@"Resources"]; parser=[PDFParser parserWithData:[content data]]; while((obj=[parser _parseObject])) { // process objects if([obj isPDFKeyword]) { // atom or keyword #if 0 NSLog(@"keyword: %@", obj); #endif if([obj isEqualToString:@"BT"]) // begin text ts=0.0; else if([obj isEqualToString:@"Tf"]) { NSDictionary *dict=[[resources objectForKey:@"Font"] self]; #if 1 NSLog(@"set Tf = %@", llobj); NSLog(@"Fonts: %@", [resources objectForKey:@"Font"]); #endif obj=[[dict objectForKey:[llobj value]] self]; NSLog(@"set Tf = %@", obj); [attrib setObject:[NSFont systemFontOfSize:[lobj floatValue]] forKey:NSFontAttributeName]; } else if([obj isEqualToString:@"Ts"]) [attrib setObject:[NSNumber numberWithInt:[lobj intValue]] forKey:NSSuperscriptAttributeName]; // process stroke/fill color to change NSForegroundColorAttributeName else if([obj isEqualToString:@"Tj"] || [obj isEqualToString:@"'"] || [obj isEqualToString:@"\""]) { NSAttributedString *str=[[NSAttributedString alloc] initWithString:[lobj stringByAppendingString:@"\n"] attributes:attrib]; [result appendAttributedString:str]; } else if([obj isEqualToString:@"TJ"]) { // array TJ - array contains strings and numbers - numbers are offsets NSEnumerator *es=[lobj objectEnumerator]; NSAttributedString *astr; while((obj=[es nextObject])) { #if 0 NSLog(@"TJ: %@", obj); #endif if([obj isKindOfClass:[NSString class]]) { // append string astr=[[NSAttributedString alloc] initWithString:obj attributes:attrib]; [result appendAttributedString:astr]; } } astr=[[NSAttributedString alloc] initWithString:@"\n" attributes:attrib]; [result appendAttributedString:astr]; } // ignore all others } llobj=lobj; lobj=obj; } // NS_HANDLER // NSLog(@"attributedString: %@", localException); // ignore // NS_ENDHANDLER } return result; } - (NSString *) string; { return [[self attributedString] string]; } - (unsigned) numberOfCharacters; { return [[self string] length]; } - (NSData *) dataRepresentation; { // create PDF-1.4 document for just this page NSLog(@"content=%@", [self _content]); // may be array of indirect objects return nil; } // handle document - (PDFDocument *) document; { return _document; } - (NSMutableDictionary *) _page; { return _page; } - (NSArray *) _content; { id content=[[_page objectForKey:@"Contents"] self]; if(!content) return [NSArray array]; // no content - empty page if([content isKindOfClass:[NSArray class]]) return content; // should be array of streams return [NSArray arrayWithObject:content]; // single stream object } - (id) _initWithDocument:(PDFDocument *) document andPageDictionary:(NSMutableDictionary *) page; { if(!document || !page) { [self release]; return nil; } if((self=[self initWithDocument:document])) { _page=[page retain]; // page attributes/values #if 1 NSLog(@"initialized: %@", self); #endif } return self; } - (id) initWithDocument:(PDFDocument *) document; { // may be overridden in subclasses if((self=[super init])) { _document=[document retain]; } return self; } - (NSString *) description; { /* Page 1; label = 2 media (0.0, 0.0) [595.0, 842.0] crop (0.0, 0.0) [595.0, 842.0] rot 0 'RŸckrufe von Personenwagen D...' */ unsigned i=[_document indexForPage:self]; return [NSString stringWithFormat:@"%@ %d: Label=%@", NSStringFromClass([self class]), i, [self label]]; } - (void) dealloc; { [_annotations release]; [_page release]; [_document release]; [super dealloc]; } - (NSString *) label; { unsigned i=[_document indexForPage:self]; id lbl; if(i == NSNotFound) return nil; lbl=[[_document _root] objectForKey:@"PageLabels"]; if(lbl) { // page lables tree exists lbl=[[lbl self] _objectAtIndexInNumberTree:i]; if(lbl) return [lbl self]; // fetch } return [NSString stringWithFormat:@"%u", i+1]; // substitute for older PDF versions } // drawing - (void) drawWithBox:(PDFDisplayBox) box; { NSEnumerator *e=[[self _content] objectEnumerator]; PDFStream *content; PDFParser *parser; NSDictionary *resources; id obj; NSMutableArray *arg=[NSMutableArray arrayWithCapacity:10]; NSMutableArray *gstack=[[NSMutableArray alloc] initWithCapacity:10]; // graphics state stack int compat=0; // compatibility mode NSBezierPath *path=[NSBezierPath bezierPath]; // current path NSFont *font=[NSFont systemFontOfSize:12]; // default font NSAffineTransform *tm=nil; // text matrix NSAffineTransform *tlm=nil; float tc=0.0; float tw=0.0; float tz=1.0; float tl=0.0; float ts=0.0; // initialize defaults (which color space??) NSColor *strokeColor=[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:1.0]; // default color NSColor *fillColor=[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:1.0]; #if 0 NSLog(@"content streams: %@", [self _content]); #endif [strokeColor setStroke]; [fillColor setFill]; // handle rotation, i.e. modify the inital transformation matrix // how to use the displayBox? initialize clipping path? while((content=[e nextObject])) { // process all content streams #if 1 NSLog(@"content: %@", content); #endif NS_DURING content=[content self]; // fetch #if 0 NSLog(@"decoded: %@", [content data]); #endif resources=[content objectForKey:@"Resources"]; if(!resources) resources=[self _inheritedPageAttribute:@"Resources"]; #if 0 NSLog(@"resources=%@", resources); #endif parser=[PDFParser parserWithData:[content data]]; while((obj=[parser _parseObject])) { // process objects unsigned argc=[arg count]; // number of arguments if([obj isPDFKeyword]) { // atom or keyword /* FIXME: the compare methods should be optimized, e.g. by - have most probable and fastest running keywords coming first - or use an NSDict to map to method calls? */ #if 0 NSLog(@"keyword: %@", obj); #endif if([obj isEqualToString:@"BX"]) compat++; else if(compat > 0 && [obj isEqualToString:@"EX"]) compat--; else if([obj isEqualToString:@"q"]) { [gstack addObject:path]; path=[[path copy] autorelease]; [path removeAllPoints]; // start a fresh path that inherits all attributes [NSGraphicsContext saveGraphicsState]; // push graphics state - fill/stroke color etc. (everything which understands 'set') #if 0 NSLog(@"q -> %d", [gstack count]); #endif } else if([obj isEqualToString:@"Q"]) { #if 0 NSLog(@"Q <- %d", [gstack count]); #endif if([gstack count] == 0) { NSLog(@"Q: empty stack!"); continue; // ignore } path=[gstack lastObject]; [gstack removeLastObject]; // pull [NSGraphicsContext restoreGraphicsState]; } else if(argc == 6 && [obj isEqualToString:@"cm"]) { NSAffineTransformStruct str= { [[arg objectAtIndex:0] floatValue], [[arg objectAtIndex:1] floatValue], [[arg objectAtIndex:2] floatValue], [[arg objectAtIndex:3] floatValue], [[arg objectAtIndex:4] floatValue], [[arg objectAtIndex:5] floatValue] }; static NSAffineTransform *ctm; if(!ctm) ctm=[[NSAffineTransform alloc] init]; #if 0 NSLog(@"set ctm=%@", arg); #endif [ctm setTransformStruct:str]; #if 1 NSLog(@"ctm=%@", ctm); #endif [ctm concat]; // combine in the NSGraphicsContext } else if(argc == 1 && [obj isEqualToString:@"w"]) [path setLineWidth:[[arg objectAtIndex:0] floatValue]]; // convert from user space coords to points else if(argc == 1 && [obj isEqualToString:@"J"]) [path setLineCapStyle:[[arg objectAtIndex:0] intValue]]; else if(argc == 1 && [obj isEqualToString:@"j"]) [path setLineJoinStyle:[[arg objectAtIndex:0] intValue]]; else if(argc == 1 && [obj isEqualToString:@"M"]) [path setMiterLimit:[[arg objectAtIndex:0] floatValue]]; else if(argc == 2 && [obj isEqualToString:@"d"]) { NSArray *pattern=[arg objectAtIndex:0]; unsigned int i, cnt=[pattern count]; // number of elements float *pat=(float *) malloc(cnt*sizeof(pat[0])); if(pat != NULL) { for(i=0; i