mirror of
https://github.com/bzotto/ClassicMacTypography.git
synced 2024-11-27 06:50:26 +00:00
284 lines
9.2 KiB
Objective-C
284 lines
9.2 KiB
Objective-C
//
|
|
// BitmapFont.m
|
|
// Copyright © 2018 Ben Zotto. All rights reserved.
|
|
//
|
|
|
|
#import "BitmapFont.h"
|
|
|
|
//
|
|
// The FONT resource format is from the 1985 edition of Apple's Inside Macintosh
|
|
// documentation Volume I (Font Manager).
|
|
//
|
|
|
|
@interface BitmapFont ()
|
|
// Private accessors for public properties
|
|
@property (copy) NSString * name;
|
|
@property (assign) NSInteger size;
|
|
@property (assign) BOOL isProportional;
|
|
@property (assign) NSInteger firstChar;
|
|
@property (assign) NSInteger lastChar;
|
|
@property (assign) NSInteger widMax;
|
|
@property (assign) NSInteger kernMax;
|
|
@property (assign) NSInteger nDescent;
|
|
@property (assign) NSInteger fRectWidth;
|
|
@property (assign) NSInteger fRectHeight;
|
|
@property (assign) NSInteger ascent;
|
|
@property (assign) NSInteger descent;
|
|
@property (assign) NSInteger leading;
|
|
|
|
// Private data
|
|
@property (strong) NSDictionary * charImageMap;
|
|
@property (strong) CharacterImage * missingCharImage;
|
|
@end
|
|
|
|
// Declarations of utility functions at the end of this file.
|
|
static NSString * FontNameFromResourceId(uint16 resourceId);
|
|
static NSInteger FontSizeFromResourceId(uint16 resourceId);
|
|
|
|
@implementation BitmapFont
|
|
|
|
+ (instancetype)fontFromResourceData:(NSData *)data withResourceIdString:(NSString *)resourceIdStr
|
|
{
|
|
if (!data || !resourceIdStr) {
|
|
return nil;
|
|
}
|
|
|
|
BitmapFont * font = [[BitmapFont alloc] init];
|
|
|
|
// Extract the name and size from the resource ID.
|
|
NSInteger resourceId = [resourceIdStr integerValue];
|
|
if (resourceId == 0 || resourceId > 0xFFFF) {
|
|
// 0 would be invalid and is also the sentinel from -integerValue that it
|
|
// couldn't parse, so safe to bail.
|
|
NSLog(@"Failed to parse resource ID %@", resourceIdStr);
|
|
return nil;
|
|
}
|
|
uint16 resourceIdShort = (uint16)resourceId;
|
|
// If the high bit is nonzero, it's invalid.
|
|
if ((resourceIdShort & 0x8000) != 0) {
|
|
NSLog(@"Failed to parse resource ID %@", resourceIdStr);
|
|
return nil;
|
|
}
|
|
|
|
font.name = FontNameFromResourceId(resourceIdShort);
|
|
font.size = FontSizeFromResourceId(resourceIdShort);
|
|
if (font.size < 0) {
|
|
return nil;
|
|
}
|
|
|
|
// Parse the resource data
|
|
if (data.length < 26) {
|
|
return nil;
|
|
}
|
|
|
|
uint8 * resourceData = (uint8 *)data.bytes;
|
|
|
|
uint16 fontType = OSReadBigInt16(resourceData, 0);
|
|
if (fontType == 0x9000) {
|
|
font.isProportional = YES;
|
|
} else if (fontType == 0xB000) {
|
|
font.isProportional = NO;
|
|
} else if (fontType == 0xACB0) {
|
|
NSLog(@"FWID width-only resources not supported.");
|
|
return nil;
|
|
} else {
|
|
NSLog(@"Unknown fontType record value");
|
|
return nil;
|
|
}
|
|
|
|
font.firstChar = OSReadBigInt16(resourceData, 2);
|
|
font.lastChar = OSReadBigInt16(resourceData, 4);
|
|
if (font.lastChar > 0xFF) {
|
|
NSLog(@"lastChar is > 255");
|
|
return nil;
|
|
}
|
|
|
|
NSUInteger charactersInFont = font.lastChar - font.firstChar + 1;
|
|
|
|
// The 16-bit words values are generally signed. Make sure we don't drop the sign
|
|
// in the expansion to native intgeters.
|
|
#define ReadBigSignedInt16(base, byteOffset) ((NSInteger)(int16_t)OSReadBigInt16(base, byteOffset))
|
|
|
|
font.widMax = ReadBigSignedInt16(resourceData, 6);
|
|
font.kernMax = ReadBigSignedInt16(resourceData, 8);
|
|
if (font.kernMax > 0) {
|
|
NSLog(@"kernMax is not negative (or zero)");
|
|
return nil;
|
|
}
|
|
font.nDescent = ReadBigSignedInt16(resourceData, 10);
|
|
font.fRectWidth = ReadBigSignedInt16(resourceData, 12);
|
|
font.fRectHeight = ReadBigSignedInt16(resourceData, 14);
|
|
if (font.fRectHeight > 127 || font.fRectWidth > 254) {
|
|
NSLog(@"font rectangle exceeds max dimension");
|
|
return nil;
|
|
}
|
|
|
|
font.ascent = ReadBigSignedInt16(resourceData, 18);
|
|
font.descent = ReadBigSignedInt16(resourceData, 20);
|
|
font.leading = ReadBigSignedInt16(resourceData, 22);
|
|
NSUInteger rowWords = OSReadBigInt16(resourceData, 24);
|
|
|
|
// Sanity check that we're not going to run off the end of the data.
|
|
// if (data.length != 26 + (rowWords * 2 * font.fRectHeight) + ((charactersInFont + 2) * 2 * 2)) {
|
|
// return nil;
|
|
// }
|
|
|
|
uint8 * bitImagePtr = &resourceData[26];
|
|
uint16 * locTablePtr = ((uint16 *)bitImagePtr) + (rowWords * font.fRectHeight);
|
|
uint16 * owTablePtr = locTablePtr + (charactersInFont + 2);
|
|
|
|
NSUInteger locTable[255 + 2] = {0};
|
|
uint16 owTable[255 + 2] = {0};
|
|
for (int i = 0; i < (charactersInFont + 2); i++) {
|
|
locTable[i] = OSReadBigInt16(locTablePtr, i * 2);
|
|
owTable[i] = OSReadBigInt16(owTablePtr, i * 2);
|
|
}
|
|
|
|
#undef ReadBigSignedInt16
|
|
|
|
// Find and extract the special missing character image so we can use it when needed.
|
|
uint16 missingLoc = locTable[charactersInFont];
|
|
uint16 missingImageWidth = locTable[charactersInFont + 1] - missingLoc;
|
|
uint16 missingOffsetWidth = owTable[charactersInFont];
|
|
font.missingCharImage = [CharacterImage imageWithWidth:(missingOffsetWidth & 0x00FF)
|
|
offset:((missingOffsetWidth >> 8) & 0x00FF)
|
|
rectSize:UIntSizeMake(missingImageWidth, font.fRectHeight)
|
|
fromBitmap:bitImagePtr
|
|
bitmapStartLocation:missingLoc
|
|
bitmapStride:(rowWords * 2)];
|
|
|
|
NSMutableDictionary * charImageMap = [[NSMutableDictionary alloc] init];
|
|
for (NSInteger i = 0; i < 256; i++) {
|
|
// Are we out of range?
|
|
if (i < font.firstChar || i > font.lastChar) {
|
|
continue;
|
|
}
|
|
|
|
// Does the character not exist?
|
|
uint16 ow = owTable[i - font.firstChar];
|
|
if (ow == 0xFFFF) {
|
|
// Doesn't exist
|
|
continue;
|
|
}
|
|
|
|
NSUInteger offset = (ow >> 8) & 0x00FF;
|
|
NSUInteger width = ow & 0x00FF;
|
|
NSUInteger location = locTable[i - font.firstChar];
|
|
NSUInteger nextLocation = locTable[i - font.firstChar + 1];
|
|
|
|
CharacterImage * charImage = [CharacterImage imageWithWidth:width
|
|
offset:offset
|
|
rectSize:UIntSizeMake(nextLocation - location, font.fRectHeight)
|
|
fromBitmap:bitImagePtr
|
|
bitmapStartLocation:location
|
|
bitmapStride:(rowWords * 2)];
|
|
charImageMap[[NSNumber numberWithInteger:i]] = charImage;
|
|
}
|
|
font.charImageMap = charImageMap;
|
|
|
|
return font;
|
|
}
|
|
|
|
- (id)init
|
|
{
|
|
if ((self = [super init])) {
|
|
self.charImageMap = @{};
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (NSUInteger)countOfPresentCharacters
|
|
{
|
|
return self.charImageMap.count;
|
|
}
|
|
|
|
- (BOOL)characterIsPresent:(NSUInteger)character
|
|
{
|
|
return [self.charImageMap objectForKey:[NSNumber numberWithInteger:character]] != nil;
|
|
}
|
|
|
|
- (CharacterImage *)imageForCharacter:(NSUInteger)character
|
|
{
|
|
CharacterImage * charImage = [self.charImageMap objectForKey:[NSNumber numberWithInteger:character]];
|
|
if (!charImage) {
|
|
charImage = self.missingCharImage;
|
|
}
|
|
return charImage;
|
|
}
|
|
|
|
- (CharacterImage *)missingCharacterImage
|
|
{
|
|
return self.missingCharImage;
|
|
}
|
|
@end
|
|
|
|
//
|
|
// Utility functions
|
|
//
|
|
|
|
static NSString * FontNameFromResourceId(uint16 resourceId)
|
|
{
|
|
uint16 maskedNumber = (resourceId >> 7) & 0x00FF;
|
|
NSInteger fontNumber = (NSInteger)maskedNumber;
|
|
switch (fontNumber) {
|
|
case 0:
|
|
return @"Chicago";
|
|
case 1:
|
|
NSLog(@"Resource ID has unexpected font number 1 (application font)");
|
|
// Default to Geneva, which was the Mac's default for this.
|
|
return @"Geneva";
|
|
case 2:
|
|
return @"New York";
|
|
case 3:
|
|
return @"Geneva";
|
|
case 4:
|
|
return @"Monaco";
|
|
case 5:
|
|
return @"Venice";
|
|
case 6:
|
|
return @"London";
|
|
case 7:
|
|
return @"Athens";
|
|
case 8:
|
|
return @"San Francisco";
|
|
case 9:
|
|
return @"Toronto";
|
|
case 10:
|
|
// There was no apparent documented font number 10.
|
|
NSLog(@"Resource ID has unexpected font number 10");
|
|
return @"Unknown (Apple) #10";
|
|
case 11:
|
|
return @"Cairo";
|
|
case 12:
|
|
return @"Los Angeles";
|
|
case 20:
|
|
return @"Times";
|
|
case 21:
|
|
return @"Helvetica";
|
|
case 22:
|
|
return @"Courier";
|
|
case 23:
|
|
return @"Symbol";
|
|
case 24:
|
|
return @"Taliesin";
|
|
|
|
default:
|
|
if (fontNumber <= 127) {
|
|
return [NSString stringWithFormat:@"Unknown (Apple) #%ld", fontNumber];
|
|
} else {
|
|
return [NSString stringWithFormat:@"Unknown (3rd Party) #%ld", fontNumber];
|
|
}
|
|
}
|
|
}
|
|
|
|
static NSInteger FontSizeFromResourceId(uint16 resourceId)
|
|
{
|
|
uint16 maskedSize = resourceId & 0x007F;
|
|
if (maskedSize == 0) {
|
|
NSLog(@"Resource ID has invalid font size 0");
|
|
return -1;
|
|
}
|
|
return (NSInteger)maskedSize;
|
|
}
|
|
|