ResKnife/Cocoa/Plug-Ins/Font Editor/FontWindowController.m

336 lines
12 KiB
Objective-C
Raw Blame History

#import "FontWindowController.h"
#import "NGSCategories.h"
#import <stdarg.h>
UInt32 TableChecksum(UInt32 *table, UInt32 length)
{
UInt32 sum = 0, nLongs = (length+3) >> 2;
while(nLongs-- > 0) sum += *table++;
return sum;
}
@implementation FontWindowController
- (id)initWithResource:(id <ResKnifeResourceProtocol>)inResource
{
self = [self initWithWindowNibName:@"FontDocument"];
if(!self) return nil;
resource = [(id)inResource retain];
headerTable = [[NSMutableArray alloc] init];
[self loadFontFromResource];
// load the window from the nib
[self window];
return self;
}
- (void)loadFontFromResource
{
char *start = (char *)[[resource data] bytes];
arch = *(OSType*)start;
numTables = *(UInt16*)(start+4);
searchRange = *(UInt16*)(start+6);
entrySelector = *(UInt16*)(start+8);
rangeShift = *(UInt16*)(start+10);
UInt32 *pos = (UInt32 *)(start+12);
/* printf("%s\n", [[self displayName] cString]);
printf(" architecture: %#lx '%.4s'\n", arch, &arch);
printf(" number of tables: %hu\n", numTables);
printf(" searchRange: %hu\n", searchRange);
printf(" entrySelector: %hu\n", entrySelector);
printf(" rangeShift: %hu\n\n", rangeShift);
*/ for(int i = 0; i < numTables; i++)
{
OSType name = *pos++;
UInt32 checksum = *pos++;
UInt32 offset = *pos++;
UInt32 length = *pos++;
[headerTable addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
[[[NSString alloc] initWithBytes:&name length:4 encoding:NSMacOSRomanStringEncoding] autorelease], @"name",
[NSNumber numberWithUnsignedLong: checksum], @"checksum",
[NSNumber numberWithUnsignedLong: offset], @"offset",
[NSNumber numberWithUnsignedLong: length], @"length",
[NSData dataWithBytes:start+offset length:length], @"data",
nil]];
}
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[(id)resource release];
[headerTable release];
[super dealloc];
}
- (void)windowDidLoad
{
[super windowDidLoad];
// set the window's title
if(![[resource name] isEqualToString:@""])
{
[[self window] setTitle:[resource name]];
SetWindowAlternateTitle((WindowRef) [[self window] windowRef], (CFStringRef) [NSString stringWithFormat:@"%@ %@: <20>%@<40>", [resource type], [resource resID], [resource name]]);
}
// we don't want this notification until we have a window! (Only register for notifications on the resource we're editing)
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceDataDidChange:) name:ResourceDataDidChangeNotification object:resource];
// finally, show the window
[self showWindow:self];
}
- (void)windowDidBecomeKey:(NSNotification *)notification
{
NSMenu *resourceMenu = [[[NSApp mainMenu] itemAtIndex:3] submenu];
NSMenuItem *createItem = [resourceMenu itemAtIndex:[resourceMenu indexOfItemWithTarget:nil andAction:@selector(showCreateResourceSheet:)]];
[createItem setTitle: NSLocalizedString(@"Add Font Table...", nil)];
[createItem setAction:@selector(showAddFontTableSheet:)];
}
- (void)windowDidResignKey:(NSNotification *)notification
{
NSMenu *resourceMenu = [[[NSApp mainMenu] itemAtIndex:3] submenu];
NSMenuItem *createItem = [resourceMenu itemAtIndex:[resourceMenu indexOfItemWithTarget:nil andAction:@selector(showAddFontTableSheet:)]];
[createItem setTitle: NSLocalizedString(@"Create New Resource...", nil)];
[createItem setAction:@selector(showCreateResourceSheet:)];
}
- (void)resourceDataDidChange:(NSNotification *)notification
{
[headerTable removeAllObjects];
[self loadFontFromResource];
}
- (BOOL)windowShouldClose:(id)sender
{
if([[self window] isDocumentEdited])
{
NSBeginAlertSheet(@"Do you want to keep the changes you made to this font?", @"Keep", @"Don<EFBFBD>t Keep", @"Cancel", sender, self, @selector(saveSheetDidClose:returnCode:contextInfo:), nil, nil, @"Your changes cannot be saved later if you don't keep them.");
return NO;
}
else return YES;
}
- (void)saveSheetDidClose:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
switch(returnCode)
{
case NSAlertDefaultReturn: // keep
[self saveResource:nil];
[[self window] close];
break;
case NSAlertAlternateReturn: // don't keep
[[self window] close];
break;
case NSAlertOtherReturn: // cancel
break;
}
}
- (void)saveResource:(id)sender
{
// write header fields
NSMutableData *data = [NSMutableData data];
[data appendBytes:&arch length:4];
[data appendBytes:&numTables length:2];
[data appendBytes:&searchRange length:2];
[data appendBytes:&entrySelector length:2];
[data appendBytes:&rangeShift length:2];
UInt32 offset = 12 + ([headerTable count] << 4);
// add table index
for(int i = 0; i < numTables; i++)
{
NSMutableDictionary *table = [headerTable objectAtIndex:i];
NSData *tableData = [table valueForKey:@"data"];
UInt32 length = [tableData length];
UInt32 checksum = TableChecksum((UInt32 *)[tableData bytes], length);
[table setValue:[NSNumber numberWithUnsignedLong:checksum] forKey:@"checksum"];
[table setValue:[NSNumber numberWithUnsignedLong:offset] forKey:@"offset"];
[table setValue:[NSNumber numberWithUnsignedLong:length] forKey:@"length"];
[data appendBytes:[[table valueForKey:@"name"] cStringUsingEncoding:NSMacOSRomanStringEncoding] length:4];
[data appendBytes:&checksum length:4];
[data appendBytes:&offset length:4];
[data appendBytes:&length length:4];
offset += length;
if(offset % 4)
offset += 4-(offset%4);
}
// append tables
long align = 0;
for(int i = 0; i < numTables; i++)
{
// note that this doesn't output in the order thet they were read, nor align on long boundries
[data appendData:[[headerTable objectAtIndex:i] valueForKey:@"data"]];
if([data length] % 4) // pads the last table too... oh well
[data appendBytes:&align length:4-([data length]%4)];
}
// write checksum adjustment to head table
NSDictionary *head = [headerTable firstObjectReturningValue:@"head" forKey:@"name"];
if(head)
{
UInt32 fontChecksum = 0;
NSRange csRange = NSMakeRange([[head valueForKey:@"offset"] unsignedLongValue]+8,4);
[data replaceBytesInRange:csRange withBytes:&fontChecksum length:4];
fontChecksum = TableChecksum((UInt32 *)[data bytes], [data length]);
[data replaceBytesInRange:csRange withBytes:&fontChecksum length:4];
}
// [[NSNotificationCenter defaultCenter] removeObserver:self name:ResourceDataDidChangeNotification object:backup];
[resource setData:data];
// [backup setData:[data copy]];
// [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resourceDataDidChange:) name:ResourceDataDidChangeNotification object:backup];
[self setDocumentEdited:NO];
}
- (IBAction)showAddFontTableSheet:(id)sender
{
}
- (void)addFontTable:(NSString *)name
{
NSMutableDictionary *table = [NSMutableDictionary dictionaryWithObjectsAndKeys:
name, @"name",
[NSNumber numberWithUnsignedLong: 0], @"checksum",
[NSNumber numberWithUnsignedLong: 0], @"offset",
[NSNumber numberWithUnsignedLong: 0], @"length",
[NSData data], @"data", nil];
[headerTable addObject:table];
numTables = [headerTable count];
[self openTable:table inEditor:YES];
[self setDocumentEdited:YES];
}
- (void)openTableInEditor:(NSTableView *)sender
{
if([sender action])
{
// action set in IB but swapped at first click for a doubleAction ;)
[sender setAction:nil];
[sender setDoubleAction:@selector(openTableInEditor:)];
return;
}
[self openTable:[headerTable objectAtIndex:[sender clickedRow]] inEditor:YES];
}
- (void)openTable:(NSDictionary *)table inEditor:(BOOL)editor
{
NSData *data = [table valueForKey:@"data"];
if(data)
{
id tableResource = [NSClassFromString(@"Resource") resourceOfType:[table valueForKey:@"name"] andID:[NSNumber numberWithInt:0] withName:[NSString stringWithFormat:@"%@ <20> %@", [resource name], [table valueForKey:@"name"]] andAttributes:[NSNumber numberWithUnsignedShort:0] data:[table valueForKey:@"data"]];
if(!tableResource)
{
NSLog(@"Couldn't create Resource with data for table '%@'.", [table valueForKey:@"name"]);
return;
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tableDataDidChange:) name:ResourceDataDidChangeNotification object:tableResource];
if(editor) [[resource document] openResourceUsingEditor:tableResource];
else [[resource document] openResource:tableResource usingTemplate:[NSString stringWithFormat:@"sfnt subtable '%@'", [table valueForKey:@"name"]]];
}
}
- (void)tableDataDidChange:(NSNotification *)notification
{
[self setTableData:[notification object]];
}
- (void)setTableData:(id)tableResource
{
NSDictionary *table = [headerTable firstObjectReturningValue:[tableResource type] forKey:@"name"];
if(!table)
{
NSLog(@"Couldn't retrieve table with name '%@'.", [tableResource type]);
return;
}
id undoResource = [[tableResource copy] autorelease];
[undoResource setData:[table valueForKey:@"data"]];
[[[resource document] undoManager] registerUndoWithTarget:resource selector:@selector(setTableData:) object:undoResource];
[[[resource document] undoManager] setActionName:[NSString stringWithFormat:NSLocalizedString(@"Edit of table <20>%@<40>", nil), [tableResource type]]];
[table setValue:[tableResource data] forKey:@"data"];
[self setDocumentEdited:YES];
}
+ (NSArray *)filenameExtensionsForNativeHandling
{
return [NSArray arrayWithObject:@"ttf"];
}
+ (NSString *)filenameExtensionForFileExport:(id <ResKnifeResourceProtocol>)resource
{
if([[resource type] isEqualToString:@"sfnt"]) return @"ttf";
else return [resource type];
}
@end
/* sfnt_header *header = (sfnt_header *) [[resource data] bytes];
for(int i = 0; i < header->tableCount; i++)
{
switch(header->tableInfo[i].tagname)
{
case 'name':
name_table_header *name_table = (name_table_header *) (((char *)[[resource data] bytes]) + header->tableInfo[i].offset);
NSMutableArray *nameArray = [NSMutableArray array];
for(int j = 0; j < name_table->record_count; j++)
{
// load names into array of name_record classes
NSMutableDictionary *name = [NSMutableDictionary dictionary];
NSData *stringData = [NSData dataWithBytes:((char *)name_table + name_table->names[j].offset) length:name_table->names[j].length];
NSStringEncoding stringEncoding;
switch(name_table->names[j].platform_id)
{
case 0: // unicode
stringEncoding = NSUnicodeStringEncoding;
break;
case 1: // mac - values originally were smScript values, which are equivalent to CFStringEncoding values
stringEncoding = CFStringConvertEncodingToNSStringEncoding(name_table->names[j].platform_specific_id);
break;
case 2: // ISO
switch(name_table->names[j].platform_specific_id)
{
case 0: stringEncoding = NSASCIIStringEncoding; break;
case 1: stringEncoding = NSUnicodeStringEncoding; break; // ISO 10646
case 2: stringEncoding = NSISOLatin1StringEncoding; break; // ISO 8859-1
default: stringEncoding = NSASCIIStringEncoding; break;
}
break;
case 2: // windows
switch(name_table->names[j].platform_specific_id)
{
// bug: should use correct encodings here
default: stringEncoding = NSWindowsCP1252StringEncoding; break;
}
break;
default: // undefined
stringEncoding = NSWindowsCP1250StringEncoding; // guess Win-Latin-2
break;
}
[name setValue:[NSNumber numberWithUnsignedShort:name_table->names[j].platform_id] forKey:@"platform"];
[name setValue:[NSNumber numberWithUnsignedShort:name_table->names[j].platform_specific_id] forKey:@"specific"];
[name setValue:[NSNumber numberWithUnsignedShort:name_table->names[j].language_id] forKey:@"language"];
[name setValue:[NSNumber numberWithUnsignedShort:name_table->names[j].name_id] forKey:@"name"];
[name setValue:[NSString stringWithData:stringData encoding:stringEncoding]] forKey:@"string"];
[nameArray addObject:name];
}
[tables setObject:nameArray forKey:@"name"];
break;
default:
// else just save the data of the table
[tables setObject:[NSData dataWithBytes:(((char *)[[resource data] bytes]) + header->tableInfo[i].offset) length:header->tableInfo[i].length] forKey:[NSString stringWithCString:&(header->tableInfo[i].tagname) length:4]];
break;
}
}
*/