mirror of
https://github.com/nickshanks/ResKnife.git
synced 2024-08-30 13:28:58 +00:00
336 lines
12 KiB
Objective-C
336 lines
12 KiB
Objective-C
#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;
|
||
}
|
||
}
|
||
*/ |