Saving to PDF

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.

Producing PDFs from NSView

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.

Changes to MainMenu.nib

  1. 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.

  2. Rename the Save menu item "Save Graph”.

  3. 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).

Connection from the Save Graph menu item to the First Responder

Figure 19-7. Connection from the Save Graph menu item to the First Responder

  1. Save MainMenu.nib.

Changes to the Controller Class

We need to add three methods to the Controller class to make the Save Graph menu command work:

PDFForView:

The first method, PDFForView: , will generate an NSData object that contains the PDF representation for the view.

saveDocumentTo:

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.

savePanelDidEnd:returnCode:contextInfo:

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.

  1. 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;
  2. 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.

  1. 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.

  1. 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.

Testing the Save Graph Menu Command

  1. Build and run GraphPaper. Save all files first.

  2. 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.

Saving a PDF file in GraphPaper

Figure 19-8. Saving a PDF file in GraphPaper

  1. 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.

    Saving a PDF file in GraphPaper

  2. Quit GraphPaper.

  3. 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.

The PDF file created by GraphPaper is displayed in Preview

Figure 19-9. The PDF file created by GraphPaper is displayed in Preview

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.

Our sin-plot.pdf file dragged into Stone Design’s Create application

Figure 19-10. Our sin-plot.pdf file dragged into Stone Design’s Create application

Our sin-plot.pdf file scales properly in Create; the PDF is rerendered at the higher resolution

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.

The PDF file in Word does not scale properly

Figure 19-12. The PDF file in Word does not scale properly

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.

An EPS image created with GraphPaper and rendered directly in this book

Figure 19-13. An EPS image created with GraphPaper and rendered directly in this book

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.62.168