Although making a graph (or any other picture) is a nice start, it’s important to be able to save the graph in a format that can work with other Mac OS X applications. For example, you might want to put the graph in a word processor document that you are making with Microsoft Word, or paste the image into a Create drawing.
One of the most common file formats in the graphics industry today is the Encapsulated PostScript (EPS) standard. EPS contains a series of device-independent commands that can be used to draw any image on any graphics device.
EPS is a great way to move graphics between applications. EPS retains all of the information that was originally used to draw the image: fonts, line strokes, bitmaps — it’s all in there. EPS images can be scaled and displayed, and they’re beautiful and easy to work with.
But over the years, Adobe PostScript has not been a runaway success in the marketplace. Although PostScript was extremely popular on the NeXTSTEP, Unix, and Macintosh operating systems, it never really caught on in the Windows world. Throughout the 1990s, there were also a growing number of security concerns with PostScript, because it is more than an imaging model — it’s a programming language. Finally, as PostScript was extended, some of its device-independence was lost.
In the 1990s, Adobe developed a new graphics imaging format called the Portable Document Format (PDF). In many ways, PDF is a successor to PostScript. Like PostScript, it is device-independent and has provisions for embedding fonts, compressing images, and more. But PDF also has built-in security: documents can be encrypted to control access. Unlike PostScript, PDF is not a programming language, which means that PDF documents have less chance of containing viruses or hostile code. And unlike PostScript, Adobe has made a strong commitment to PDF on the Windows platform.
The Mac OS X operating system extends PDF, allowing it to be used as a general graphics file format for images and line drawings. In this way, PDF documents can be embedded directly in other documents, similar to the way that EPS documents are embedded in documents. But unlike PostScript, there is no “encapsulated” form of PDF — it’s just regular PDF.
As this book goes to press, PDF is still a relatively immature technology for embedding graphics images. We can make GraphPaper produce a PDF of the GraphView graph, but we need to “trick” it because of some issues with the 10.1 AppKit. We can embed the resulting PDF image into a variety of applications, but, as we’ll see, it scales properly in only one or two of them. A PDF of a GraphPaper graph is great for printing entire pages, but it’s still immature as a format for moving images between applications.
Cocoa’s NSView class makes it possible to generate an EPS or PDF file with a single message. Just send a dataWithEPSInsideRect: message, and the NSView will return an NSData object that contains the EPS file. Similarly, dataWithPDFInsideRect: will return an NSData object that contains a PDF file. The NSView object will invoke the appropriate drawRect: method, but instead of sending the drawing code to the Window Server, it will capture the output and send it to the stream that you designate.
Unfortunately, in Mac OS X Version 10.1, these methods do not work properly for views that are not scaled in screen coordinates. Although these bugs may be fixed in a future release, in order to make this demonstration program work with Version 10.1, we were forced to find a workaround. Specifically, to generate a PDF file, we had to remove the GraphView from the ZoomScrollView, place it in an off-screen window, and ask the off-screen window to generate the PDF file for us. Once the PDF file was created, we put the GraphView back into the ZoomScrollView. This worked, but it’s not ideal.
In the rest of this section, we’ll add a Save command to GraphPaper’s menu and then modify the Controller object to capture the PDF and write it to a file.
Back in IB, edit GraphPaper’s File menu. Remove (with the Delete key) the New, Open, Open Recent, Close, Save As, and Revert menu items, leaving only the Save, Page Setup, and Print items.
Connect the Save Graph menu cell so that it sends the saveDocumentTo: action message to the First
Responder object in the Instances pane of the
MainMenu.nib
window (see Figure 19-7).
Save MainMenu.nib
.
We need to add three methods to the Controller class to make the Save Graph menu command work:
The first method, PDFForView: , will generate an NSData object that contains the PDF representation for the view.
The second method, saveDocumentTo: , will be invoked in response to a user’s choosing the Save Graph menu command. It will display a sheet that will prompt the user for the filename under which the PDF file should be saved.
Finally, we will implement a savePanelDidEnd:returnCode:contextInfo: method that is called by the Save panel to actually save the PDF in a file.
Using this last method to generate the PDF output will make it easier to adapt the saveDocumentTo: method later to save the file as either PDF or TIFF.
Insert these three method declarations into
Controller.h
:
- (NSData *)PDFForView:(NSView *)aView; - (IBAction)saveDocumentTo:(id)sender; - (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
Insert the PDFForView: action method
into Controller.m
:
// This implementation works around an AppKit bug in Cocoa 10.1 // by placing the view in a different window and asking that // window to create the PDF for the view. - (NSData *)PDFForView:(NSView *)aView { NSRect frame = [aView frame]; NSView *oldSuperview = [aView superview]; NSWindow *tempWindow; NSData *pdf; tempWindow = [[NSWindow alloc] initWithContentRect:frame styleMask:NSBorderlessWindowMask backing:NSBackingStoreRetained defer:NO]; [[tempWindow contentView] addSubview:aView]; pdf = [tempWindow dataWithPDFInsideRect:[tempWindow frame]]; [oldSuperview addSubview:aView]; [tempWindow release]; return pdf; }
This method creates a temporary off-screen window that has the same
size as the frame of the view that is passed as an argument
(aView
). The argument view, which will be
TrackingGraphView, is ripped out of its current location and is made
a subview of the temporary window’s content view.
Then NSWindow’s dataWithPDFInsideRect: method is used to
create an NSData object that contains the PDF representation of this
window. Finally, the passed-in view is returned to its original
superview.
Both the NSView and NSWindow classes support the dataWithPDFInsideRect: method. Ideally, we should be able to invoke this method in the passed-in view without putting the view in its own NSWindow. But when we tried that, we ended up with a tiny PDF image that couldn’t be displayed by any of the standard tools. This method is less elegant, but it works.
Insert the following saveDocumentTo:
method into
Controller.m
:
- (IBAction)saveDocumentTo:(id)sender { NSSavePanel *pan = [NSSavePanel savePanel]; [pan setRequiredFileType:@"pdf"]; [pan setTitle:@"Save Graph"]; [pan beginSheetForDirectory:nil file:nil modalForWindow:[graphView window] modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:nil]; }
This saveDocumentTo: method
implements the Save Graph action (recall the connection from the menu
item we made earlier). The method creates an NSSavePanel, sets the
required file type for this panel to be
“pdf”, and then starts a modal
sheet (with one of the longest method names in Cocoa!). When the user
dismisses the sheet, the savePanelDidEnd:returnCode:contextInfo:
message is sent to the
modalDelegate
, which is
self
— the Controller object.
Insert the following savePanelDidEnd:returnCode:contextInfo: method
into Controller.m
:
- (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { NSData *graphPDF; if (returnCode==0) return; // User did not click OK // Take no action graphPDF = [self PDFForView:graphView]; if ([graphPDF writeToFile:[sheet filename] atomically:NO]==NO) { NSRunAlertPanel(nil,@"Cannot save file '%@': %s",nil,nil,nil, [sheet filename],strerror(errno)); } }
This method is the delegate method for the Save panel. It first
checks the return code to see if the user exited the sheet by hitting
the OK or Cancel buttons. If the user did not click OK, the method
returns. Otherwise, the method creates a PDF for the graphView and
writes it to a file using the writeToFile:atomically:
method from the NSData class. If the
writeToFile:atomically: method
fails, the appropriate error message is displayed by an
NSRunAlertPanel( )
function.
Click the Graph button and then choose the Save Graph menu command. You should see a standard Mac OS X document-modal sheet, as shown in Figure 19-8.
Enter a filename such sin-plot
and click Save. A
new icon like the one shown here will show up in
the Documents
folder in the Finder.
Quit GraphPaper.
Now double-click the saved sin-plot.pdf
file in
the Finder. The graph should open in the Preview application, as
shown in Figure 19-9.
You can incorporate this PDF file directly into many Mac OS X applications. In theory, PDF files should automatically scale to different display sizes. Unfortunately, some applications do not currently support PDF properly. Instead of asking the Quartz system to redisplay the PDF image at the appropriate resolution, these programs appear to simply create a TIFF image of the PDF and then scale the PDF as necessary.
For an example of a program that handles PDF files properly, consider Stone Design’s Create. If you drag the PDF file into a Create window, the image will appear as it did in Preview, as shown in Figure 19-10. If you increase the resolution to 200% (or other), you’ll see that the image is rendered again in higher detail, as it is in Figure 19-11.
Figure 19-11. Our sin-plot.pdf file scales properly in Create; the PDF is rerendered at the higher resolution
On the other hand, Microsoft Word and PowerPoint for the Mac do not properly display PDF images. Figure 19-12 shows the same PDF file dragged into a Word presentation. The image is then scaled up by a factor of 200%. Unlike Create, Word simply scales up the image, rather than rerendering the PDF, and these ugly “jaggies” appear.
This is not merely of academic interest. If you are preparing images
for publication, you need to use EPS and PDF types so that the images
are properly rendered at the resolution of your output device. Figure 19-13 shows an EPS file created from GraphPaper (by
converting every “pdf” to an
“eps” in the Controller class) that
was then directly included in this book. If you compare this image of
the GraphView with the others in this book, you’ll
see that the letters sin(3*x)
have no jaggies and
that the line itself looks smoother. That’s because
the PostScript file has been imaged at the 1200 dpi (dots per inch)
resolution of our phototypesetter. The other images of the GraphView
were captured off the screen at 92 dpi (approximately) and had their
pixels replicated to get to the 1200 dpi of the
phototypesetter.
18.116.62.168