// // GlyphTable.m // // Ryutaro Kurai // // Original File Name, Author and Licence. // // FontLabelStringDrawing.m // FontLabel // // Created by Kevin Ballard on 5/5/09. // Copyright © 2009 Zynga Game Networks // // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #define kUnicodeHighSurrogateStart 0xD800 #define kUnicodeHighSurrogateEnd 0xDBFF #define kUnicodeLowSurrogateStart 0xDC00 #define kUnicodeLowSurrogateEnd 0xDFFF #define UnicharIsHighSurrogate(c) (c >= kUnicodeHighSurrogateStart && c <= kUnicodeHighSurrogateEnd) #define UnicharIsLowSurrogate(c) (c >= kUnicodeLowSurrogateStart && c <= kUnicodeLowSurrogateEnd) #define ConvertSurrogatePairToUTF32(high, low) ((UInt32)((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)) typedef enum { kFontTableFormat4 = 4, kFontTableFormat12 = 12, } FontTableFormat; typedef struct fontTable { CFDataRef cmapTable; FontTableFormat format; union { struct { UInt16 segCountX2; UInt16 *endCodes; UInt16 *startCodes; UInt16 *idDeltas; UInt16 *idRangeOffsets; } format4; struct { UInt32 nGroups; struct { UInt32 startCharCode; UInt32 endCharCode; UInt32 startGlyphCode; } *groups; } format12; } cmap; } fontTable; static FontTableFormat supportedFormats[] = { kFontTableFormat4, kFontTableFormat12 }; static size_t supportedFormatsCount = sizeof(supportedFormats) / sizeof(FontTableFormat); static fontTable *newFontTable(CFDataRef cmapTable, FontTableFormat format) { fontTable *table = (struct fontTable *)malloc(sizeof(struct fontTable)); table->cmapTable = CFRetain(cmapTable); table->format = format; return table; } static void freeFontTable(fontTable *table) { if (table != NULL) { CFRelease(table->cmapTable); free(table); } } // read the cmap table from the font // we only know how to understand some of the table formats at the moment static fontTable *readFontTableFromCGFont(CGFontRef font) { CFDataRef cmapTable = CGFontCopyTableForTag(font, 'cmap'); NSCAssert1(cmapTable != NULL, @"CGFontCopyTableForTag returned NULL for 'cmap' tag in font %@", (font ? [(id)CFCopyDescription(font) autorelease] : @"(null)")); const UInt8 * const bytes = CFDataGetBytePtr(cmapTable); NSCAssert1(OSReadBigInt16(bytes, 0) == 0, @"cmap table for font %@ has bad version number", (font ? [(id)CFCopyDescription(font) autorelease] : @"(null)")); UInt16 numberOfSubtables = OSReadBigInt16(bytes, 2); const UInt8 *unicodeSubtable = NULL; //UInt16 unicodeSubtablePlatformID; UInt16 unicodeSubtablePlatformSpecificID; FontTableFormat unicodeSubtableFormat; const UInt8 * const encodingSubtables = &bytes[4]; for (UInt16 i = 0; i < numberOfSubtables; i++) { const UInt8 * const encodingSubtable = &encodingSubtables[8 * i]; UInt16 platformID = OSReadBigInt16(encodingSubtable, 0); UInt16 platformSpecificID = OSReadBigInt16(encodingSubtable, 2); // find the best subtable // best is defined by a combination of encoding and format // At the moment we only support format 4, so ignore all other format tables // We prefer platformID == 0, but we will also accept Microsoft's unicode format if (platformID == 0 || (platformID == 3 && platformSpecificID == 1)) { BOOL preferred = NO; if (unicodeSubtable == NULL) { preferred = YES; } else if (platformID == 0 && platformSpecificID > unicodeSubtablePlatformSpecificID) { preferred = YES; } if (preferred) { UInt32 offset = OSReadBigInt32(encodingSubtable, 4); const UInt8 *subtable = &bytes[offset]; UInt16 format = OSReadBigInt16(subtable, 0); for (int i = 0; i < supportedFormatsCount; i++) { if (format == supportedFormats[i]) { if (format >= 8) { // the version is a fixed-point UInt16 formatFrac = OSReadBigInt16(subtable, 2); if (formatFrac != 0) { // all the current formats with a Fixed version are always *.0 continue; } } unicodeSubtable = subtable; //unicodeSubtablePlatformID = platformID; unicodeSubtablePlatformSpecificID = platformSpecificID; unicodeSubtableFormat = format; break; } } } } } fontTable *table = NULL; if (unicodeSubtable != NULL) { table = newFontTable(cmapTable, unicodeSubtableFormat); switch (unicodeSubtableFormat) { case kFontTableFormat4: // subtable format 4 //UInt16 length = OSReadBigInt16(unicodeSubtable, 2); //UInt16 language = OSReadBigInt16(unicodeSubtable, 4); table->cmap.format4.segCountX2 = OSReadBigInt16(unicodeSubtable, 6); //UInt16 searchRange = OSReadBigInt16(unicodeSubtable, 8); //UInt16 entrySelector = OSReadBigInt16(unicodeSubtable, 10); //UInt16 rangeShift = OSReadBigInt16(unicodeSubtable, 12); table->cmap.format4.endCodes = (UInt16*)&unicodeSubtable[14]; table->cmap.format4.startCodes = (UInt16*)&((UInt8*)table->cmap.format4.endCodes)[table->cmap.format4.segCountX2+2]; table->cmap.format4.idDeltas = (UInt16*)&((UInt8*)table->cmap.format4.startCodes)[table->cmap.format4.segCountX2]; table->cmap.format4.idRangeOffsets = (UInt16*)&((UInt8*)table->cmap.format4.idDeltas)[table->cmap.format4.segCountX2]; //UInt16 *glyphIndexArray = &idRangeOffsets[segCountX2]; break; case kFontTableFormat12: table->cmap.format12.nGroups = OSReadBigInt32(unicodeSubtable, 12); table->cmap.format12.groups = (void *)&unicodeSubtable[16]; break; default: freeFontTable(table); table = NULL; } } CFRelease(cmapTable); return table; } // outGlyphs must be at least size n static void mapCharactersToGlyphsInFont(const fontTable *table, unichar characters[], size_t charLen, CGGlyph outGlyphs[], size_t *outGlyphLen) { if (table != NULL) { NSUInteger j = 0; for (NSUInteger i = 0; i < charLen; i++, j++) { unichar c = characters[i]; switch (table->format) { case kFontTableFormat4: { UInt16 segOffset; BOOL foundSegment = NO; for (segOffset = 0; segOffset < table->cmap.format4.segCountX2; segOffset += 2) { UInt16 endCode = OSReadBigInt16(table->cmap.format4.endCodes, segOffset); if (endCode >= c) { foundSegment = YES; break; } } if (!foundSegment) { // no segment // this is an invalid font outGlyphs[j] = 0; } else { UInt16 startCode = OSReadBigInt16(table->cmap.format4.startCodes, segOffset); if (!(startCode <= c)) { // the code falls in a hole between segments outGlyphs[j] = 0; } else { UInt16 idRangeOffset = OSReadBigInt16(table->cmap.format4.idRangeOffsets, segOffset); if (idRangeOffset == 0) { UInt16 idDelta = OSReadBigInt16(table->cmap.format4.idDeltas, segOffset); outGlyphs[j] = (c + idDelta) % 65536; } else { // use the glyphIndexArray UInt16 glyphOffset = idRangeOffset + 2 * (c - startCode); outGlyphs[j] = OSReadBigInt16(&((UInt8*)table->cmap.format4.idRangeOffsets)[segOffset], glyphOffset); } } } break; } case kFontTableFormat12: { UInt32 c32 = c; if (UnicharIsHighSurrogate(c)) { if (i+1 < charLen) { // do we have another character after this one? unichar cc = characters[i+1]; if (UnicharIsLowSurrogate(cc)) { c32 = ConvertSurrogatePairToUTF32(c, cc); i++; } } } for (UInt32 idx = 0;; idx++) { if (idx >= table->cmap.format12.nGroups) { outGlyphs[j] = 0; break; } __typeof__(table->cmap.format12.groups[idx]) group = table->cmap.format12.groups[idx]; if (c32 >= OSSwapBigToHostInt32(group.startCharCode) && c32 <= OSSwapBigToHostInt32(group.endCharCode)) { outGlyphs[j] = (CGGlyph)(OSSwapBigToHostInt32(group.startGlyphCode) + (c32 - OSSwapBigToHostInt32(group.startCharCode))); break; } } break; } } } if (outGlyphLen != NULL) *outGlyphLen = j; } else { // we have no table, so just null out the glyphs bzero(outGlyphs, charLen*sizeof(CGGlyph)); if (outGlyphLen != NULL) *outGlyphLen = 0; } }