2007-01-29

I have been thinking to myself for a couple of years now that SVG would catch on and I could start using it for diagrams and technical sketches and icons, etc. It has never happened though. It is much easier to create and edit SVG than it was two years ago: look at Inkscape and Lineform. What you cannot do easily is view SVG in most browsers or embed it in programs without additional software. (Mozilla has had intermittent support for it, and my preferred browser Camino has it enabled by default, but SVG is invisible in the vast Internet Explorer wasteland without a third party plugin like the recently discontinued Adobe SVG Viewer.) If applicable to your situation, you could keep all your graphics in SVG on the server and rewrite them to bitmaps on the fly with Apache's Batik module, but that doesn't help someone who needs to distribute content on static media like a CD-ROM.

Given that Mac developers must plan for a variably-high resolution future, you cannot just generate bitmaps in advance with Inkscape or Batik. Batik is pretty large itself, far larger than any program I would be likely to distribute, so I cannot see embedding it in my downloads either. So where does that leave me now? I see three choices.

First, I could write my own SVG renderer. That... has a certain attraction. I would learn a lot by doing it. It's feasible. I could start with a reasonable subset for my current needs and expand it incrementally. On the other hand, why hasn't anybody else done it yet? Is it so much more work than it looks? (It doesn't look that hard for the Mac. Much of what you need is already built into the OpenGL + PDF rendering system.) Is it just politics that has kept everyone else away from it? On the other hand, I don't really have much enthusiasm for it right now. There are at least six other projects in various states I would like to finish before adopting a potential monster like this one.

Second, I could generate all my diagrams in C/ObjC code and render them to bitmaps through Quartz. That is straightforward for a programming geek like me, but I can see where it would be completely out of reach for others. It is hard to believe that could be the best option.

Third, I could follow the "Mac way" and do it in PostScript. Even though OS X has wholeheartedly adopted PDF (leaving behind the Display PostScript of the NeXT era), I have always avoided PostScript. When I first read about it, it was the exclusive domain of printers way out of my price range. When I learned more about it, I always saw its deficiencies compared to what I knew. First, it was Forth with the words arbitrarily changed. Later, I saw it was Lisp without the macros or the Common Lisp library. Now I see it as an SVG without the programmatic accessibility of XML. Of course, PostScript has technical advantages over Forth and SVG and much wider acceptance than Lisp, but those old prejudices kept me away from it. After thinking about it this weekend, I have decided it is my best current choice.

OS X prefers PDF over PS or EPS. In one sense, PDF is a declarative version of PostScript. It is a kindler, simpler PostScript that will not drag the window server to its knees. If you are going to use PostScript images in a Mac application, you first convert them to PDF. You can configure Xcode to convert all your PS graphics at build time. This way, you get the best of both flavors: the full imperative power of PostScript and the runtime convenience of PDF.

What about the web, though? If you want to embed an image into a web page, PostScript is even less supported than SVG! You need to render it to a bitmap. Last July, I had posted source code to a command-line program which generated PNG files from Windows BMP files using CoreGraphics. I tried it today and that code does not work for PDF inputs. The Mac can do it, though, you just have to find a different door. Here is an even shorter program in Cocoa that should convert almost any graphics file!

/* Compile with:
	gcc -o imageToPNG2 imageToPNG2.m -framework AppKit
*/
#import <Cocoa/Cocoa.h>

int main( int argc, char *argv[] ) {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSApplicationLoad();	// initializes state before calling AppKit functions from non-AppKit code
	int i;
	for (i = 1; i < argc; i++) {
		NSString *spath = [[NSString stringWithUTF8String: argv[i]] stringByExpandingTildeInPath];
		NSImage *image = [[NSImage alloc] initByReferencingFile: spath];

		NSSize size = [image size];
		[image lockFocus];
		NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:
			NSMakeRect(0,0,size.width,size.height)];
		[image unlockFocus];
		NSData *data = [rep representationUsingType: NSPNGFileType properties: nil];

		NSString *dpath = [[spath stringByDeletingPathExtension] stringByAppendingPathExtension: @"png"];
		[data writeToFile: dpath atomically: NO];

		[rep release];
		[image release];
		printf( "Completed %s -> %s\n", argv[i], [dpath UTF8String]);
	}
	[pool release];
	return 0;
}

Isn't this pretty? The hardest part was discovering the NSApplicationLoad() function. Without it, the program crashes before processing the second item. A mention on CocoaDev led me to Apple's Carbon-Cocoa integration document. Although I am not using Carbon, I guess the rule of thumb is to call NSApplicationLoad() if you touch any AppKit routine outside NSApplicationMain().

If you feed it a document with a bounding box, the -[initWithFocusedViewRect:] method will use that box as the pixel dimensions of the output. Save the following text as an EPS file, and imageToPNG2 will convert it to a 250x400 PNG:

%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: 0 0 250 400
/Optima findfont 18 scalefont setfont
0 1 0 setrgbcolor
50 350 [ (Hello) (world) (from) (Houston,) (Texas!) ]
{ % stack: x y string
  3 copy pop moveto show
  % stack: x y
  20 sub exch 12 add exch
  % stack: x+12 y-20
} forall
pop pop

If I do not specify a bounding box, my system defaults to 692x792 pixels, the point size of an 8.5 inch x 11 inch sheet of paper. I wonder if it defaults to A4 in Europe? Also, be careful of your backgrounds. PostScript assumes the background is white while most bitmap viewers display their contents over a black background. If it matters, explicitly fill your viewable rectangle with your desired background before doing any other output.

One great thing about jumping into PostScript now is that it has been around so long, it has defined the way the current generation thinks about graphics. If you have ever programmed any graphics system above the pixel level (Quartz, OpenGL, etc.), you are familiar with PostScript concepts. Also, there is an abundance of freely available resources. Adobe's PostScript Language Reference is fairly readable but can easily bog you down with the sheer volume of information. Thinking in Postscript is a much lighter introduction. Bill Casselman's Mathematical Illustrations is an online textbook devoted to some of the computationally heavier applications of PostScript, but also check the links at the bottom of the page. (Blue and Green books.) Wow, I remember reading columns by Don Lancaster twenty years ago!