frameworker

May 16, 2008

A Glimpse into PDFKit

Filed under: Uncategorized — frameworker @ 12:02 am

While I don’t consider myself to be a PDFKit expert by a long shot, I have found it compelling and accessible. This is largely due to the robust collection of example programs provided.

What is PDFKit?

One way to think about PDFKit is as the APIs used to implement much of Apple’s Preview application, exposed for reuse.

Survey of PDFKit Documentation

Taking Advantage of PDF Kit in Your Cocoa Application

PDF Kit Programming Guide

PDF Kit Reference Collection

Survey of PDFKit Example Programs

PDF Calendar uses PDF Kit to show you how to subclass PDFPage and generate your own PDF content.

PDF Annotation Editor uses PDF Kit to examine, edit, and create PDF annotations.

PDFKitViewer2 (based on PDFKitViewer ) is the example program I’m working with tonight. I’ve added the ability to save PDF form data and I’ve hidden its use of PDFOutline to focus more clearly on the recipe I’ve added.

PDFView Subclasser (distributed on Leopard DVD I’m not sure where this sample originated!?  I’m guessing it was on Apple’s Leopard Development site but not merged into the regular site; possibly an oversight.  I just sent a note (6-13-08 ) about this to the PDF Kit team, so this sample may appear shortly on Apple’s website) shows you how to subclass PDFView in order to overlay content relative to the PDF content using the (Leopard) PDFView method -[drawPagePost:]

PDFKitLinker2 presents many features of (Tiger) PDFKit. Ostensibly it allows you to open PDF’s and edit existing Link annotations or create your own.

Link Snoop is a simple application using the new Quartz framework, PDFKit in Mac OS X Tiger. When a user opens a PDF with this application it scans it for Link annotations that have a URL associated with them. URL link annotations are displayed in an NSTableView. Also, the PDF is displayed with these annotations highlighted.

Advertisements

May 12, 2008

The Spreadsheet Paradigm

Filed under: Uncategorized — frameworker @ 3:01 am

Alan Kay’s brilliant article about the spreadsheet from the September 84 issue of SciAm

alan-kay-computer-software-sciam-sept-84

May 6, 2008

A Cocoa recipe to edit PDF Forms

Filed under: Uncategorized — frameworker @ 11:14 pm

This recipe, based on John Calhoun’s PDFKitViewer2 example program, lets you edit, save and restore PDFAnnotationWidget values with PDFKit 10.5. Pretty much everything is shoehorned into a category of MyPDFDocument to minimize change to the original code base

(Note that PDFAnnotationWidget isn’t itself a class; it’s a reference to the widget classes like PDFAnnotationTextWidget.)

The recipe simply calls “restorePDFWidgets” when a document is opened and “cachePDFWidgets” when the document is closed.

As you can see, I’m using a free text annotation named “widgetArchive” to hold the PDFAnnotationWidgets. It has a zero dimension, doesn’t display or print and caches the PDFAnnotationWidgets in its contents field.

1. In MyPDFDocument->windowControllerDidLoadNib call the category method [self restorePDFWidgets].

A good place to do this is just after you’ve set the delegate.

2. Complete dataRepresentationOfType to archive the PDFAnnotationWidgets:

- (NSData *) dataRepresentationOfType: (NSString *) aType
{
	// For each non-empty widget append the type, fieldName and value to theWidgetCache

	[self cachePDFWidgets]; // <-- Call the category method to archive widgets.

	NSData * pdfData = &#91;&#91;_pdfView document&#93; dataRepresentation&#93;;

	return pdfData;
}
&#91;/sourcecode&#93;

<strong>3.</strong> Add a category for the PDFAnnotationWidget persistence methods:


//  MyPDFDocument+PDFForms.m

#import "MyPDFDocument+PDFAdditions.h"
#import "NSString+PDFAdditions.h"

@implementation MyPDFDocument (PDFForms)

4. Implement a method to restore PDFAnnotationWidgets when a document is opened:

- (void) restorePDFWidgets
{	
	PDFAnnotation * widgetArchive = [self findWidgetArchive];
	
	// Don't do anything here if widgetArchive doesn't exist.
	if (widgetArchive != NULL)
	{
		// internalize widgets from archive.
		
		NSString * widgetCache = [widgetArchive contents];
		NSCharacterSet *tokenSeperatorSet = [NSCharacterSet characterSetWithCharactersInString:@"\t"];
		NSArray *fieldArray = [widgetCache tokensSeparatedByCharactersFromSet: tokenSeperatorSet];

		int i;
		int cacheCount = [fieldArray count]/3;

		for (i = 0; i/3 < cacheCount; i = i + 3)
		{
			// fieldArray&#91;i  &#93; -> fieldname
			// fieldArray[1+1] -> "Text" or "Button"
			// fieldArray[i+2] -> stringValue or state

			// Find annot with fieldName
			
			NSString * theFieldName = (NSString *) [fieldArray objectAtIndex: i];
			PDFAnnotation * annot = [self annotWithFieldname: theFieldName];
			if (annot != NULL)
			{
				NSString * widgetType =  (NSString *) [fieldArray objectAtIndex: i + 1];
				
				if ([widgetType isEqualToString: @"Text"])
				{
					NSString * theStringValue =  (NSString *) [fieldArray objectAtIndex: i + 2];
					[(PDFAnnotationTextWidget *) annot setStringValue: theStringValue];
				}
				else
				{
					NSString * buttonState =  (NSString *) [fieldArray objectAtIndex: i + 2];
					if ([buttonState isEqualToString: @"NSOnState"])
					{
						[(PDFAnnotationButtonWidget *) annot setState: NSOnState];
					}
					else
					{
						[(PDFAnnotationButtonWidget *) annot setState: NSOffState];
					}
				}				
			}
		}
	}
}

5. Implement a method to find the PDFAnnotation that contains the widget archive.

- (PDFAnnotation *) findWidgetArchive
{
	PDFAnnotation * theWidgetArchive = NULL;
	
	// Iterate the list of pages.
	int pageIndex;
	int pageCount = [[_pdfView document] pageCount];
	for (pageIndex = 0; pageIndex < pageCount; pageIndex++)
	{
		PDFPage * currentPage = &#91;&#91;_pdfView document&#93; pageAtIndex: pageIndex&#93;;

		// Iterate the array of annotations.
		
		int annotIndex;
		NSArray * annotations = &#91;currentPage annotations&#93;;
		int annotCount = &#91;annotations count&#93;;
		for (annotIndex = 0; annotIndex < annotCount; annotIndex++)
		{
			PDFAnnotation * annotation = &#91;annotations objectAtIndex: annotIndex&#93;;
			
			if (&#91;&#91;annotation userName&#93; isEqualToString: @"widgetArchive"&#93;)
			{
				theWidgetArchive = annotation;
				break;
			}
		}
	}
	
	return theWidgetArchive;
}
&#91;/sourcecode&#93;

<strong>6.</strong> Implement a method to get the PDFAnnotationWidget by name that we want to restore.


- (PDFAnnotation *) annotWithFieldname: (NSString *) fieldName
{
	PDFAnnotation * annot = NULL;

	// Iterate the list of pages.
	int pageIndex;
	int pageCount = [[_pdfView document] pageCount];
	for (pageIndex = 0; pageIndex < pageCount; pageIndex++)
	{
		PDFPage * currentPage = &#91;&#91;_pdfView document&#93; pageAtIndex: pageIndex&#93;;

		// Iterate the array of annotations.
		
		int annotIndex;
		NSArray * annotations = &#91;currentPage annotations&#93;;
		int annotCount = &#91;annotations count&#93;;
		for (annotIndex = 0; annotIndex < annotCount; annotIndex++)
		{
			PDFAnnotation * annotation = &#91;annotations objectAtIndex: annotIndex&#93;;
			
			if (&#91;annotation isKindOfClass: &#91;PDFAnnotationTextWidget class&#93;&#93;)
			{
				NSString * theFieldName = &#91;(PDFAnnotationTextWidget *) annotation fieldName&#93;;
				
				if (&#91;theFieldName isEqualToString: fieldName&#93;)
				{
					annot = annotation;
					break;
				}
			}
		}
	}
	
	return annot;
}
&#91;/sourcecode&#93;

<strong>7.</strong> Implement a method to save PDFAnnotationWidgets when a document is closed:


- (void) cachePDFWidgets
{	
	PDFAnnotation * widgetArchive = [self findWidgetArchive];
	
	if (widgetArchive != NULL)
	{
		[[_pdfView currentPage] removeAnnotation: widgetArchive];
	}
	
	NSString * theCache = [self widgetsToCache];

	// Don't do anything here if there aren't any annots to cache.
	if ([theCache length] > 0)
	{
		// Create new widgetArchive
		
		widgetArchive = [[PDFAnnotationFreeText alloc] initWithBounds: NSMakeRect(0, 0, 0, 0)];
		
		[widgetArchive setUserName: @"widgetArchive"];
		[widgetArchive setShouldDisplay:  NO];
		[widgetArchive setShouldPrint:    NO];
		[widgetArchive setContents: theCache];
		
		[[_pdfView currentPage] addAnnotation: widgetArchive];
	}
}

8. Implement a method to get the widgets to cache:

- (NSString *) widgetsToCache
{
	NSString * theWidgetCache = @"";
	
	// Iterate the list of pages.
	int pageIndex;
	int pageCount = [[_pdfView document] pageCount];
	for (pageIndex = 0; pageIndex < pageCount; pageIndex++)
	{
		PDFPage * currentPage = &#91;&#91;_pdfView document&#93; pageAtIndex: pageIndex&#93;;

		// Iterate the array of annotations.
		
		int annotIndex;
		NSArray * annotations = &#91;currentPage annotations&#93;;
		int annotCount = &#91;annotations count&#93;;
		for (annotIndex = 0; annotIndex < annotCount; annotIndex++)
		{
			PDFAnnotation * annotation = &#91;annotations objectAtIndex: annotIndex&#93;;
			
			NSString * theFieldName	    = @"";
			NSString * theWidgetString	= @"";
			
			if (&#91;annotation isKindOfClass: &#91;PDFAnnotationTextWidget class&#93;&#93;)
			{
				theFieldName = &#91;(PDFAnnotationTextWidget *) annotation fieldName&#93;;
				theWidgetString = &#91;(PDFAnnotationTextWidget *) annotation stringValue&#93;;
				
				if (&#91;theWidgetString length&#93; > 0)
				{
					theWidgetCache = [theWidgetCache stringByAppendingString: theFieldName];
					theWidgetCache = [theWidgetCache stringByAppendingString: @"\t"];
					theWidgetCache = [theWidgetCache stringByAppendingString: @"Text"];
					theWidgetCache = [theWidgetCache stringByAppendingString: @"\t"];
					theWidgetCache = [theWidgetCache stringByAppendingString: theWidgetString];
					theWidgetCache = [theWidgetCache stringByAppendingString: @"\t"];
				}
			}
			else
			if ([annotation isKindOfClass: [PDFAnnotationButtonWidget class]])
			{
				theFieldName = [(PDFAnnotationButtonWidget *) annotation fieldName];
				int theWidgetstate = [(PDFAnnotationButtonWidget *) annotation state];
				
				if (theWidgetstate ==  NSOnState)
				{
					theWidgetCache = [theWidgetCache stringByAppendingString: theFieldName];
					theWidgetCache = [theWidgetCache stringByAppendingString: @"\t"];
					theWidgetCache = [theWidgetCache stringByAppendingString: @"Button"];
					theWidgetCache = [theWidgetCache stringByAppendingString: @"\t"];
					theWidgetCache = [theWidgetCache stringByAppendingString: @"ON"];
					theWidgetCache = [theWidgetCache stringByAppendingString: @"\t"];
				}
			}
		}
	}
	
	return theWidgetCache;
}
@end // End of MyPDFDocument (PDFForms)

9. Finally, implement an NSString category containing this utility method that tokenizes the widget string.

//  NSString+PDQAdditions.m

#import "NSString+PDQAdditions.h"

@implementation NSString (PDQAdditions)

- (NSArray *)tokensSeparatedByCharactersFromSet:(NSCharacterSet *)separatorSet
{
  NSScanner      *scanner      = [NSScanner scannerWithString:self];
  NSCharacterSet *tokenSet     = [separatorSet invertedSet];
  NSMutableArray *tokens       = [NSMutableArray array];

  [scanner setCharactersToBeSkipped:separatorSet];

  while (![scanner isAtEnd])
  {
    NSString  *destination = [NSString string];

    if ([scanner scanCharactersFromSet:tokenSet intoString:&destination])
    {
      [tokens addObject:[NSString stringWithString:destination]];
    }
  }

  return [NSArray arrayWithArray:tokens];
}

@end

And one more thing…

In order to enable the Save menu command, it is necessary to change the “Role” from Viewer to Editor in the Target Info Properties panel!

Blog at WordPress.com.