From 045f61f3d1dbc3632669e53cef5280652dc4119a Mon Sep 17 00:00:00 2001 From: Morgan Harris Date: Tue, 17 Jan 2017 21:16:02 +1100 Subject: [PATCH] Fix to work with latest iOS and macOS The current version doesn't work with latest .car files. This version does. It doesn't deal with the CUICatalog at all, gets rid of all the guessing at image names, and just reads assets straight from the storage and then interprets file names from the rendition keys. --- cartool/main.m | 295 +++++++++++++++++++++++++++++-------------------- 1 file changed, 173 insertions(+), 122 deletions(-) diff --git a/cartool/main.m b/cartool/main.m index 6aaf27b..908e4e6 100644 --- a/cartool/main.m +++ b/cartool/main.m @@ -24,53 +24,68 @@ typedef NS_ENUM(NSInteger, UIUserInterfaceSizeClass) { UIUserInterfaceSizeClassRegular = 2, }; +CGDataProviderRef CGPDFDocumentGetDataProvider(CGPDFDocumentRef); + +typedef struct _renditionkeytoken { + unsigned short k; + unsigned short v; +} CUIRenditionKeyToken; + @interface CUICommonAssetStorage : NSObject -(NSArray *)allAssetKeys; -(NSArray *)allRenditionNames; - -(id)initWithPath:(NSString *)p; - -(NSString *)versionString; +- (NSString*)renditionNameForKeyList:(CUIRenditionKeyToken*) keyList; @end -@interface CUINamedImage : NSObject - -@property(readonly) CGSize size; -@property(readonly) CGFloat scale; -@property(readonly) kCoreThemeIdiom idiom; -@property(readonly) UIUserInterfaceSizeClass sizeClassHorizontal; -@property(readonly) UIUserInterfaceSizeClass sizeClassVertical; - --(CGImageRef)image; - -@end +typedef unsigned long long CUITheme; +typedef long long CUIRenditionType; @interface CUIRenditionKey : NSObject +- (CUIRenditionKeyToken*) keyList; +- (NSString*) nameOfAttributeName: (int) attributeName; +- (NSUInteger) themeScale; +- (kCoreThemeIdiom) themeIdiom; +- (UIUserInterfaceSizeClass) themeSizeClassHorizontal; +- (UIUserInterfaceSizeClass) themeSizeClassVertical; @end -@interface CUIThemeFacet : NSObject +@interface CUIImage : NSObject +- (CGImageRef) image; +@end -+(CUIThemeFacet *)themeWithContentsOfURL:(NSURL *)u error:(NSError **)e; +@interface CUIThemeRendition : NSObject ++ (NSString*) displayNameForRenditionType:(CUIRenditionType)type; +- (CUIRenditionType) type; +- (NSString*) name; +- (CGPDFDocumentRef) pdfDocument; +- (CGImageRef) unslicedImage; +- (CGImageRef) uncroppedImage; +- (id) packedContents; +- (NSData*) data; +- (id) utiType; @end -@interface CUICatalog : NSObject +@interface CUIThemeFacet : NSObject -@property(readonly) bool isVectorBased; ++(CUITheme) themeWithContentsOfURL:(NSURL *)u error:(NSError **)e; ++(CUITheme) themeWithBytes:(const void *) bytes length:(size_t) len error:(NSError**) e; ++(CUIThemeFacet *)facetWithRenditionKey:(CUIRenditionKey*) key fromTheme:(CUITheme)theme; --(id)initWithName:(NSString *)n fromBundle:(NSBundle *)b; --(id)allKeys; --(id)allImageNames; --(CUINamedImage *)imageWithName:(NSString *)n scaleFactor:(CGFloat)s; --(CUINamedImage *)imageWithName:(NSString *)n scaleFactor:(CGFloat)s deviceIdiom:(int)idiom; --(NSArray *)imagesWithName:(NSString *)n; +- (instancetype) initWithRenditionKey:(CUIRenditionKey*) key fromTheme:(CUITheme)theme; +- (CGSize) imageSize; +- (CUIImage*) image; +- (NSString*) displayName; +- (CUIRenditionType) renditionType; +- (CUIThemeRendition*) themeRendition; @end - void CGImageWriteToFile(CGImageRef image, NSString *path) { CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:path]; @@ -84,64 +99,115 @@ void CGImageWriteToFile(CGImageRef image, NSString *path) CFRelease(destination); } +void createSubdirectories(NSString* path, NSString* outputDirectoryPath) { + NSArray* pathComponents = [path pathComponents]; + if (pathComponents.count > 1) + { + // Create subdirectories for namespaced assets (those with names like "some/namespace/image-name") + NSArray* subdirectoryComponents = [pathComponents subarrayWithRange:NSMakeRange(0, pathComponents.count - 1)]; + + NSString* subdirectoryPath = [outputDirectoryPath copy]; + for (NSString* pathComponent in subdirectoryComponents) + { + subdirectoryPath = [subdirectoryPath stringByAppendingPathComponent:pathComponent]; + } + + [[NSFileManager defaultManager] createDirectoryAtPath:subdirectoryPath + withIntermediateDirectories:YES + attributes:nil + error:nil]; + } +} + NSString *idiomSuffixForCoreThemeIdiom(kCoreThemeIdiom idiom) { - switch (idiom) { - case kCoreThemeIdiomUniversal: - return @""; - break; - case kCoreThemeIdiomPhone: - return @"~iphone"; - break; - case kCoreThemeIdiomPad: - return @"~ipad"; - break; - case kCoreThemeIdiomTV: - return @"~tv"; - break; - case kCoreThemeIdiomCar: - return @"~carplay"; - break; - case kCoreThemeIdiomWatch: - return @"~watch"; - break; - case kCoreThemeIdiomMarketing: - return @"~marketing"; - break; - default: - break; - } - - return @""; + switch (idiom) { + case kCoreThemeIdiomUniversal: + return @""; + break; + case kCoreThemeIdiomPhone: + return @"~iphone"; + break; + case kCoreThemeIdiomPad: + return @"~ipad"; + break; + case kCoreThemeIdiomTV: + return @"~tv"; + break; + case kCoreThemeIdiomCar: + return @"~carplay"; + break; + case kCoreThemeIdiomWatch: + return @"~watch"; + break; + case kCoreThemeIdiomMarketing: + return @"~marketing"; + break; + default: + break; + } + + return @""; } NSString *sizeClassSuffixForSizeClass(UIUserInterfaceSizeClass sizeClass) { - switch (sizeClass) - { - case UIUserInterfaceSizeClassCompact: - return @"C"; - break; - case UIUserInterfaceSizeClassRegular: - return @"R"; - break; - default: - return @"A"; - } + switch (sizeClass) + { + case UIUserInterfaceSizeClassCompact: + return @"C"; + break; + case UIUserInterfaceSizeClassRegular: + return @"R"; + break; + default: + return @"A"; + } } -NSMutableArray *getImagesArray(CUICatalog *catalog, NSString *key) -{ - NSMutableArray *images = [[NSMutableArray alloc] initWithCapacity:5]; +NSString* fileExtension(CUIThemeRendition* rendition) { + // try to use the UTI + if (rendition.utiType) { + NSArray* extensions = CFBridgingRelease(UTTypeCopyAllTagsWithClass((__bridge CFStringRef _Nonnull)(rendition.utiType), kUTTagClassFilenameExtension)); + if (extensions.count > 0) { + return extensions.firstObject; + } + } + + if (rendition.type == 9) { + return @"pdf"; + } + + if (rendition.type == 1000) { + // use the existing extension + NSString* extension = rendition.name.pathExtension; + if (extension.length > 0) { + return extension; + } + + // unknown UTI, just use it directly + return rendition.utiType; + } + + // everything else should probably be a PNG + return @"png"; +} - for (NSNumber *scaleFactor in @[@1, @2, @3]) +NSString* typedFilename(NSString* renditionName, CUIRenditionKey* key, CUIThemeRendition* rendition) { + NSString *idiomSuffix = idiomSuffixForCoreThemeIdiom(key.themeIdiom); + NSString *sizeClassSuffix = @""; + + if (key.themeSizeClassHorizontal || key.themeSizeClassVertical) { - CUINamedImage *image = [catalog imageWithName:key scaleFactor:scaleFactor.doubleValue]; - - if (image && image.scale == scaleFactor.floatValue) [images addObject:image]; + sizeClassSuffix = [NSString stringWithFormat:@"-%@x%@", sizeClassSuffixForSizeClass(key.themeSizeClassHorizontal), sizeClassSuffixForSizeClass(key.themeSizeClassVertical)]; } - - return images; + + NSString *scale = key.themeScale > 1 ? [NSString stringWithFormat:@"@%lux", key.themeScale] : @""; + + NSString* extension = fileExtension(rendition); + + NSString *name = [NSString stringWithFormat:@"%@%@%@%@.%@", renditionName, idiomSuffix, sizeClassSuffix, scale, extension]; + return name; } void exportCarFileAtPath(NSString * carPath, NSString *outputDirectoryPath) @@ -149,64 +215,49 @@ void exportCarFileAtPath(NSString * carPath, NSString *outputDirectoryPath) NSError *error = nil; outputDirectoryPath = [outputDirectoryPath stringByExpandingTildeInPath]; - - CUIThemeFacet *facet = [CUIThemeFacet themeWithContentsOfURL:[NSURL fileURLWithPath:carPath] error:&error]; - - CUICatalog *catalog = [[CUICatalog alloc] init]; - - /* Override CUICatalog to point to a file rather than a bundle */ - [catalog setValue:facet forKey:@"_storageRef"]; + + CUITheme theme = [CUIThemeFacet themeWithContentsOfURL:[NSURL fileURLWithPath:carPath] error:&error]; + + if (error != nil) { + NSLog(@"Error: %@", error); + exit(1); + } /* CUICommonAssetStorage won't link */ CUICommonAssetStorage *storage = [[NSClassFromString(@"CUICommonAssetStorage") alloc] initWithPath:carPath]; - - for (NSString *key in [storage allRenditionNames]) - { - printf("%s\n", [key UTF8String]); + + for (CUIRenditionKey *key in [storage allAssetKeys]) { + CUIThemeFacet* aFacet = [[CUIThemeFacet alloc] initWithRenditionKey:key fromTheme:theme]; - NSArray* pathComponents = [key pathComponents]; - if (pathComponents.count > 1) - { - // Create subdirectories for namespaced assets (those with names like "some/namespace/image-name") - NSArray* subdirectoryComponents = [pathComponents subarrayWithRange:NSMakeRange(0, pathComponents.count - 1)]; - - NSString* subdirectoryPath = [outputDirectoryPath copy]; - for (NSString* pathComponent in subdirectoryComponents) - { - subdirectoryPath = [subdirectoryPath stringByAppendingPathComponent:pathComponent]; + CUIThemeRendition* rendition = [aFacet themeRendition]; + if (rendition.type > 1001) { + // this is a packed asset, skip it + continue; + } + NSString* renditionName = [storage renditionNameForKeyList:[key keyList]]; + NSString* filename = typedFilename(renditionName, key, rendition); + printf("%s\n", filename.UTF8String); + + if( outputDirectoryPath ) { + createSubdirectories( filename, outputDirectoryPath ); + NSURL* path = [NSURL fileURLWithPath:[outputDirectoryPath stringByAppendingPathComponent:filename]]; + if (rendition.pdfDocument) { + CGPDFDocumentRef doc = rendition.pdfDocument; + CGDataProviderRef provider = CGPDFDocumentGetDataProvider(doc); + NSData *data = provider ? CFBridgingRelease(CGDataProviderCopyData(provider)) : nil; + [data writeToURL:path atomically:YES]; + } else if (rendition.data) { + [rendition.data writeToURL:path atomically:YES]; + } else if (rendition.uncroppedImage) { + CGImageRef image = rendition.uncroppedImage; + CGImageWriteToFile(image, [outputDirectoryPath stringByAppendingPathComponent:filename]); + } else { + printf(" Unknown file type!\n"); } - - [[NSFileManager defaultManager] createDirectoryAtPath:subdirectoryPath - withIntermediateDirectories:YES - attributes:nil - error:&error]; } - NSMutableArray *images = getImagesArray(catalog, key); - for( CUINamedImage *image in images ) - { - if( CGSizeEqualToSize(image.size, CGSizeZero) ) - printf("\tnil image?\n"); - else - { - CGImageRef cgImage = [image image]; - NSString *idiomSuffix = idiomSuffixForCoreThemeIdiom(image.idiom); - - NSString *sizeClassSuffix = @""; - - if (image.sizeClassHorizontal || image.sizeClassVertical) - { - sizeClassSuffix = [NSString stringWithFormat:@"-%@x%@", sizeClassSuffixForSizeClass(image.sizeClassHorizontal), sizeClassSuffixForSizeClass(image.sizeClassVertical)]; - } - - NSString *scale = image.scale > 1.0 ? [NSString stringWithFormat:@"@%dx", (int)floor(image.scale)] : @""; - NSString *name = [NSString stringWithFormat:@"%@%@%@%@.png", key, idiomSuffix, sizeClassSuffix, scale]; - printf("\t%s\n", [name UTF8String]); - if( outputDirectoryPath ) - CGImageWriteToFile(cgImage, [outputDirectoryPath stringByAppendingPathComponent:name]); - } - } - } + } + } int main(int argc, const char * argv[])