Dan Wood: The Eponymous Weblog (Archives)

Dan Wood Dan Wood is co-owner of Karelia Software, creating programs for the Macintosh computer. He is the father of two kids, lives in the Bay Area of California USA, and prefers bicycles to cars. This site is his older weblog, which mostly covers geeky topics like Macs and Mac Programming. Go visit the current blog here.

Useful Tidbits and Egotistical Musings from Dan Wood

Categories: Business · Mac OS X · Cocoa Programming · General · All Categories

Fri, 08 Jan 2010

Converting Rich Text to TEXT/styl resources for an SLA on a Disk Image

Update: Rainer Brockerhoff told me that you can actually put an 'RTF ' resource in the resource file, rather than TEXT/styl, so it turns out that this is not the easiest solution.

One thing that I try to accomplish each January is to update our copyright statements in all the right places. The only straggler that remained was the Software License Agreement that is embedded in our disk images.

Unfortunately, the text for this is not stored in any way that makes it easy to update things. The rich text is stored as an old-fashioned Circa 1984 resource fork, as TEXT and styl resources. (Apple's "Software License Agreements for UDIFs" SDK [Download DMG] has all the gory details.)

It's barely possible to even edit a resource file nowadays. There is the open-source ResKnife but it doesn't seem to have TEXT/styl support. There's ResFool, but at least as of this writing the website isn't even loading. There are of course options like ResEdit and Resorcerer that one can run under Classic mode from older versions of Mac OS X, but that's not particularly convenient!

I really was hoping I could just store our SLA text as rich text (like .rtf files) and have the conversion to the resources handled for me automagically!

No wonder many developers use tools like DropDMG and DMGCanvas. The trouble is, we have all of our build/package/upload process as a shell script, run from a build phase on Xcode. So I really needed a scriptable tool to produce the correct resources.

I did some digging, and I found a nice hint (at the bottom of this page) for converting rich text to TEXT/styl resources by using NSPasteboard.

So I whipped up some code that goes something like this.

int main (int argc, const char * argv[])
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

	if (2 != argc)
		NSLog(@"Usage: %@ srcpath\n", [[NSString stringWithUTF8String:argv[0]] lastPathComponent]);
		return -1;

	NSString *sourcePath = [NSString stringWithUTF8String:argv[1]];
	NSAttributedString *str = [[NSAttributedString alloc] initWithPath:sourcePath documentAttributes:nil];
	NSData *data = [str RTFFromRange:NSMakeRange(0, [str length]) documentAttributes:nil];

	NSPasteboard *pb = [NSPasteboard generalPasteboard];
	[pb declareTypes:[NSArray arrayWithObject:NSRTFPboardType] owner:nil];
	[pb setData:data forType:NSRTFPboardType];

	NSData *textData  = [pb dataForType:@"CorePasteboardFlavorType 0x54455854"];   // TEXT
	NSData *styleData = [pb dataForType:@"CorePasteboardFlavorType 0x7374796C"];   // styl

All I needed to do was to write the NSData to a ".r" file. I found a bit of code via Google, from an app called JPEGBatcherX. It looks like the author was doing something similar. I adapted his dump_rsrc method from this source file which was directly indexed by Google: rtf2r.m. (I don't want to reproduce the code here because it's not clear what the copyright on the code is.)

So the above function should be able to end like this:

	dump_rsrc("TEXT", textData);
	dump_rsrc("styl", styleData);

	[pool drain];
    return 0;

However, I hit a snag. When running on my Intel Mac, this is an invalid 'styl' resource. I had to dig up an old copy of Resorcerer and run it under Classic on my old G5 to figure out why.

A valid 'styl' resource starts something like this:

0060 0000 0000 000F 000C 0400 0100 000C

This corresponds to: 0x0060 style runs, 0x00000000 first offset, 0x000F line height, 0x000C font ascent, 0x0400 font family, 0x0100 char style, 0x000c pixel size, and so forth.

However, the style data I was getting from the pasteboard was more like this:

6000 0000 0000 0f00 0C00 0004 0001 0C00

It sure looks like a little-endian vs. big-endian issue. This perplexed me, considering I was not manipulating numbers; I was just asking the pasteboard for a bit of data that I ought to be able to write into the 'styl' resource.

Fortunately Daniel Jalkut mentioned some methods that were designed to flip resource data around. He later explained "Apple provided flippers for many common resource types, but left us to our own devices for some of the less common/modern ones."

I guess this make a small amount of sense. Rather than Apple rewriting the resource manager code to deal with endian issues, we just flip the data afterwards. Still, it's a bit tricky if you don't expect that!

So all I needed was to copy the bytes to a writeable block and call the appropriate routine to flip them around. Note my #ifdef in the code so that in case the code is big-endian, no real flipping will happen.

	int len = [styleData length];
	char *bytes = malloc(len);
	[styleData getBytes:bytes length:len];

	OSStatus status = CoreEndianFlipData (
	  kCoreEndianResourceManagerDomain, //OSType dataDomain,
	  'styl', //OSType dataType,
	  0, //SInt16 id,
	  bytes, //void *data,
	  len, //ByteCount dataLen,
#ifdef __BIG_ENDIAN__
	  false	//Boolean currentlyNative

	NSData *newStyleData = [[[NSData alloc] initWithBytesNoCopy:bytes length:len freeWhenDone:YES] autorelease];

That does the trick! Now all I have to do is to concatenate the TEXT/styl resources produced with this tool to with the other (un-changing) resources I'm storing in a common ".r" file and build them into my disk image, using the 'Rez' tool.