#import "FontWindowController.h" #import "NGSCategories.h" #import 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 )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:@"%@ %@: Ò%@Ó", [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Õ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:@"%@ È %@", [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 Ò%@Ó", nil), [tableResource type]]]; [table setValue:[tableResource data] forKey:@"data"]; [self setDocumentEdited:YES]; } + (NSArray *)filenameExtensionsForNativeHandling { return [NSArray arrayWithObject:@"ttf"]; } + (NSString *)filenameExtensionForFileExport:(id )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; } } */