@@ -106,7 +106,11 @@ + (nullable NSData *)PNGDataForDeviceName:(NSString *)deviceName
106106 }
107107 }
108108 CGContextTranslateCTM (context, -chromeX, -chromeY);
109- [self clearScreenAreaForProfile: profile context: context];
109+ if (![self clearScreenAreaForChromeInfo: chromeInfo profile: profile context: context error: error]) {
110+ CGContextRestoreGState (context);
111+ CGContextRelease (context);
112+ return nil ;
113+ }
110114 if (![self drawSensorBarForChromeInfo: chromeInfo profile: profile context: context error: error]) {
111115 CGContextRestoreGState (context);
112116 CGContextRelease (context);
@@ -237,8 +241,11 @@ + (nullable NSData *)screenMaskPNGDataForDeviceName:(NSString *)deviceName
237241 CGFloat screenScale = MAX ([self numberValue: plist[@" mainScreenScale" ]], 1.0 );
238242 CGFloat profileScreenWidth = [self numberValue: plist[@" mainScreenWidth" ]];
239243 CGFloat profileScreenHeight = [self numberValue: plist[@" mainScreenHeight" ]];
240- CGFloat pointScreenWidth = watchProfile ? profileScreenWidth : profileScreenWidth / screenScale;
241- CGFloat pointScreenHeight = watchProfile ? profileScreenHeight : profileScreenHeight / screenScale;
244+ CGSize profileScreenSize = [self screenSizeForChromeInfo: chromeInfo
245+ chromeSize: compositeSize
246+ screenScale: screenScale];
247+ CGFloat pointScreenWidth = profileScreenSize.width ;
248+ CGFloat pointScreenHeight = profileScreenSize.height ;
242249
243250 CGFloat screenWidth;
244251 CGFloat screenHeight;
@@ -262,15 +269,10 @@ + (nullable NSData *)screenMaskPNGDataForDeviceName:(NSString *)deviceName
262269 screenHeight = MAX (compositeSize.height - standHeight - bezelTop - bezelBottom, 1.0 );
263270 }
264271
265- CGFloat innerRadius = MAX (rawCornerRadius - MAX (bezelLeft, bezelTop ), 0.0 );
272+ CGFloat innerRadius = MAX (rawCornerRadius - MAX (screenX, screenY ), 0.0 );
266273 CGFloat radiusScale = pointScreenWidth > 0.0 ? screenWidth / pointScreenWidth : 1.0 ;
267- CGFloat chromeCornerRadius = watchProfile ? rawCornerRadius : innerRadius * radiusScale;
274+ CGFloat chromeCornerRadius = innerRadius * radiusScale;
268275 CGFloat cornerRadius = chromeCornerRadius;
269- CGFloat maskCornerRadius = [self framebufferMaskCornerRadiusForChromeInfo: chromeInfo
270- pointScreenWidth: pointScreenWidth];
271- if (!phoneProfile && maskCornerRadius > 0.0 ) {
272- cornerRadius = maskCornerRadius * radiusScale;
273- }
274276
275277 CGRect fullFrame = [self fullFrameForChromeInfo: chromeInfo chromeSize: compositeSize];
276278 CGFloat chromeX = -CGRectGetMinX (fullFrame);
@@ -388,13 +390,11 @@ + (CGSize)compositeSizeForChromeInfo:(NSDictionary *)chromeInfo
388390 NSDictionary *bord = [paths[@" simpleOutsideBorder" ] isKindOfClass: [NSDictionary class ]] ? paths[@" simpleOutsideBorder" ] : @{};
389391 NSDictionary *bordI = [bord[@" insets" ] isKindOfClass: [NSDictionary class ]] ? bord[@" insets" ] : @{};
390392 CGFloat screenScale = MAX ([self numberValue: plist[@" mainScreenScale" ]], 1.0 );
391- BOOL watchProfile = [self isWatchProfile: plist];
392- CGFloat screenWidth = [self numberValue: plist[@" mainScreenWidth" ]];
393- CGFloat screenHeight = [self numberValue: plist[@" mainScreenHeight" ]];
394- if (!watchProfile) {
395- screenWidth /= screenScale;
396- screenHeight /= screenScale;
397- }
393+ CGSize screenSize = [self screenSizeForChromeInfo: chromeInfo
394+ chromeSize: CGSizeZero
395+ screenScale: screenScale];
396+ CGFloat screenWidth = screenSize.width ;
397+ CGFloat screenHeight = screenSize.height ;
398398 CGFloat bezelLeft = [self numberValue: sizing[@" leftWidth" ]] + [self numberValue: bordI[@" left" ]];
399399 CGFloat bezelRight = [self numberValue: sizing[@" rightWidth" ]] + [self numberValue: bordI[@" right" ]];
400400 CGFloat bezelTop = [self numberValue: sizing[@" topHeight" ]] + [self numberValue: bordI[@" top" ]];
@@ -496,7 +496,7 @@ + (BOOL)drawSlicedChromeInfo:(NSDictionary *)chromeInfo
496496 if (NSWidth (nsRect) <= 0.0 || NSHeight (nsRect) <= 0.0 ) {
497497 continue ;
498498 }
499- if ([self drawPDFAtPath : assetPath inRect: NSRectToCGRect (nsRect) context: context error: error]) {
499+ if ([self drawRasterizedPDFAtPath : assetPath inRect: NSRectToCGRect (nsRect) context: context error: error]) {
500500 drewAny = YES ;
501501 } else {
502502 return NO ;
@@ -549,26 +549,26 @@ + (BOOL)drawStandImagesForChromeInfo:(NSDictionary *)chromeInfo
549549 CGFloat y = chromeYMax;
550550
551551 if (leftPath.length > 0 && leftWidth > 0.0 ) {
552- if (![self drawPDFAtPath : leftPath
553- inRect: CGRectMake (x, y, leftWidth, standHeight)
554- context: context
555- error: error]) {
552+ if (![self drawRasterizedPDFAtPath : leftPath
553+ inRect: CGRectMake (x, y, leftWidth, standHeight)
554+ context: context
555+ error: error]) {
556556 return NO ;
557557 }
558558 }
559559 if (centerPath.length > 0 ) {
560- if (![self drawPDFAtPath : centerPath
561- inRect: CGRectMake (x + leftWidth, y, centerWidth, standHeight)
562- context: context
563- error: error]) {
560+ if (![self drawRasterizedPDFAtPath : centerPath
561+ inRect: CGRectMake (x + leftWidth, y, centerWidth, standHeight)
562+ context: context
563+ error: error]) {
564564 return NO ;
565565 }
566566 }
567567 if (rightPath.length > 0 && rightWidth > 0.0 ) {
568- if (![self drawPDFAtPath : rightPath
569- inRect: CGRectMake (x + leftWidth + centerWidth, y, rightWidth, standHeight)
570- context: context
571- error: error]) {
568+ if (![self drawRasterizedPDFAtPath : rightPath
569+ inRect: CGRectMake (x + leftWidth + centerWidth, y, rightWidth, standHeight)
570+ context: context
571+ error: error]) {
572572 return NO ;
573573 }
574574 }
@@ -654,17 +654,13 @@ + (CGRect)inputFrameForInput:(NSDictionary *)input
654654 CGFloat x = offsetX;
655655 CGFloat y = offsetY;
656656 if ([anchor isEqualToString: @" left" ]) {
657- CGFloat visibleWidth = MAX (assetSize.width - MAX (offsetX, 0.0 ), 0.0 ) / 2.0 ;
658- x = -visibleWidth;
657+ x = offsetX - (assetSize.width / 2.0 );
659658 } else if ([anchor isEqualToString: @" right" ]) {
660- CGFloat visibleWidth = MAX (assetSize.width + MIN (offsetX, 0.0 ), 0.0 ) / 2.0 ;
661- x = size.width - assetSize.width + visibleWidth;
659+ x = size.width + offsetX - (assetSize.width / 2.0 );
662660 } else if ([anchor isEqualToString: @" top" ]) {
663- CGFloat visibleHeight = MAX (assetSize.height - MAX (offsetY, 0.0 ), 0.0 ) / 2.0 ;
664- y = -visibleHeight;
661+ y = offsetY;
665662 } else if ([anchor isEqualToString: @" bottom" ]) {
666- CGFloat visibleHeight = MAX (assetSize.height + MIN (offsetY, 0.0 ), 0.0 ) / 2.0 ;
667- y = size.height - assetSize.height + visibleHeight;
663+ y = size.height + offsetY;
668664 }
669665
670666 if ([anchor isEqualToString: @" left" ] || [anchor isEqualToString: @" right" ]) {
@@ -688,21 +684,162 @@ + (CGRect)inputFrameForInput:(NSDictionary *)input
688684 return CGRectMake (x, y, assetSize.width , assetSize.height );
689685}
690686
691- + (void )clearScreenAreaForProfile : (NSDictionary *)profile
692- context : (CGContextRef)context {
687+ + (CGSize)screenSizeForChromeInfo : (NSDictionary *)chromeInfo
688+ chromeSize : (CGSize)chromeSize
689+ screenScale : (CGFloat)screenScale {
690+ NSDictionary *plist = chromeInfo[@" plist" ];
691+ CGFloat rawWidth = [self numberValue: plist[@" mainScreenWidth" ]];
692+ CGFloat rawHeight = [self numberValue: plist[@" mainScreenHeight" ]];
693+ CGFloat scale = MAX (screenScale, 1.0 );
694+ if (![self isWatchProfile: plist]) {
695+ return CGSizeMake (rawWidth / scale, rawHeight / scale);
696+ }
697+
698+ if (chromeSize.width > 0.0 &&
699+ chromeSize.height > 0.0 &&
700+ rawWidth <= chromeSize.width &&
701+ rawHeight <= chromeSize.height ) {
702+ return CGSizeMake (rawWidth, rawHeight);
703+ }
704+ return CGSizeMake (rawWidth / scale, rawHeight / scale);
705+ }
706+
707+ + (BOOL )drawRasterizedPDFAtPath : (NSString *)path
708+ inRect : (CGRect)rect
709+ context : (CGContextRef)context
710+ error : (NSError * _Nullable __autoreleasing *)error {
711+ CGImageRef image = [self newImageForPDFAtPath: path error: error];
712+ if (image == NULL ) {
713+ return NO ;
714+ }
715+
716+ CGFloat imageWidth = MAX ((CGFloat)CGImageGetWidth (image), 1.0 );
717+ CGFloat imageHeight = MAX ((CGFloat)CGImageGetHeight (image), 1.0 );
718+ CGContextSaveGState (context);
719+ CGContextClipToRect (context, rect);
720+ CGContextTranslateCTM (context, rect.origin .x , rect.origin .y + rect.size .height );
721+ CGContextScaleCTM (context, rect.size .width / imageWidth, -rect.size .height / imageHeight);
722+ CGContextDrawImage (context, CGRectMake (0 , 0 , imageWidth, imageHeight), image);
723+ CGContextRestoreGState (context);
724+ CGImageRelease (image);
725+ return YES ;
726+ }
727+
728+ + (nullable CGImageRef)newImageForPDFAtPath : (NSString *)path
729+ error : (NSError * _Nullable __autoreleasing *)error CF_RETURNS_RETAINED {
730+ if (path.length == 0 ) {
731+ if (error != NULL ) {
732+ *error = [NSError errorWithDomain: XCWChromeRendererErrorDomain
733+ code: 14
734+ userInfo: @{
735+ NSLocalizedDescriptionKey : @" DeviceKit chrome asset path was empty." ,
736+ }];
737+ }
738+ return NULL ;
739+ }
740+
741+ CGPDFDocumentRef document = CGPDFDocumentCreateWithURL ((__bridge CFURLRef)[NSURL fileURLWithPath: path]);
742+ if (document == NULL ) {
743+ if (error != NULL ) {
744+ *error = [NSError errorWithDomain: XCWChromeRendererErrorDomain
745+ code: 7
746+ userInfo: @{
747+ NSLocalizedDescriptionKey : [NSString stringWithFormat: @" Unable to open DeviceKit chrome PDF %@ ." , path.lastPathComponent],
748+ }];
749+ }
750+ return NULL ;
751+ }
752+ CGPDFPageRef page = CGPDFDocumentGetPage (document, 1 );
753+ if (page == NULL ) {
754+ CGPDFDocumentRelease (document);
755+ if (error != NULL ) {
756+ *error = [NSError errorWithDomain: XCWChromeRendererErrorDomain
757+ code: 8
758+ userInfo: @{
759+ NSLocalizedDescriptionKey : [NSString stringWithFormat: @" DeviceKit chrome PDF %@ did not contain a renderable page." , path.lastPathComponent],
760+ }];
761+ }
762+ return NULL ;
763+ }
764+
765+ CGRect mediaBox = CGPDFPageGetBoxRect (page, kCGPDFCropBox );
766+ if (CGRectIsEmpty (mediaBox)) {
767+ mediaBox = CGPDFPageGetBoxRect (page, kCGPDFMediaBox );
768+ }
769+ NSInteger width = MAX ((NSInteger )ceil (mediaBox.size .width ), 1 );
770+ NSInteger height = MAX ((NSInteger )ceil (mediaBox.size .height ), 1 );
771+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB ();
772+ CGContextRef context = CGBitmapContextCreate (NULL ,
773+ width,
774+ height,
775+ 8 ,
776+ 0 ,
777+ colorSpace,
778+ kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big );
779+ CGColorSpaceRelease (colorSpace);
780+ if (context == NULL ) {
781+ CGPDFDocumentRelease (document);
782+ if (error != NULL ) {
783+ *error = [NSError errorWithDomain: XCWChromeRendererErrorDomain
784+ code: 9
785+ userInfo: @{
786+ NSLocalizedDescriptionKey : @" Unable to create a CoreGraphics bitmap context for DeviceKit chrome asset rendering." ,
787+ }];
788+ }
789+ return NULL ;
790+ }
791+
792+ CGContextClearRect (context, CGRectMake (0 , 0 , width, height));
793+ CGContextTranslateCTM (context, -mediaBox.origin .x , -mediaBox.origin .y );
794+ CGContextDrawPDFPage (context, page);
795+ CGImageRef image = CGBitmapContextCreateImage (context);
796+ CGContextRelease (context);
797+ CGPDFDocumentRelease (document);
798+ if (image == NULL && error != NULL ) {
799+ *error = [NSError errorWithDomain: XCWChromeRendererErrorDomain
800+ code: 10
801+ userInfo: @{
802+ NSLocalizedDescriptionKey : @" Unable to create a CGImage from the DeviceKit chrome asset." ,
803+ }];
804+ }
805+ return image;
806+ }
807+
808+ + (BOOL )clearScreenAreaForChromeInfo : (NSDictionary *)chromeInfo
809+ profile : (NSDictionary *)profile
810+ context : (CGContextRef)context
811+ error : (NSError * _Nullable __autoreleasing *)error {
693812 CGFloat x = [self numberValue: profile[@" screenX" ]];
694813 CGFloat y = [self numberValue: profile[@" screenY" ]];
695814 CGFloat width = [self numberValue: profile[@" screenWidth" ]];
696815 CGFloat height = [self numberValue: profile[@" screenHeight" ]];
697- CGFloat radius = [self numberValue: profile[@" chromeCornerRadius" ]];
698- if (radius <= 0.0 ) {
699- radius = [self numberValue: profile[@" cornerRadius" ]];
700- }
701816 if (width <= 0.0 || height <= 0.0 ) {
702- return ;
817+ return YES ;
703818 }
704819
705820 CGRect rect = CGRectMake (x, y, width, height);
821+ BOOL hasScreenMask = [profile[@" hasScreenMask" ] respondsToSelector: @selector (boolValue )] && [profile[@" hasScreenMask" ] boolValue ];
822+ if (hasScreenMask) {
823+ NSString *maskPath = [self screenMaskPathForChromeInfo: chromeInfo];
824+ if (maskPath.length > 0 ) {
825+ CGImageRef maskImage = [self newImageForPDFAtPath: maskPath error: error];
826+ if (maskImage == NULL ) {
827+ return NO ;
828+ }
829+ CGContextSaveGState (context);
830+ CGContextClipToMask (context, rect, maskImage);
831+ CGContextSetBlendMode (context, kCGBlendModeClear );
832+ CGContextFillRect (context, rect);
833+ CGContextRestoreGState (context);
834+ CGImageRelease (maskImage);
835+ return YES ;
836+ }
837+ }
838+
839+ CGFloat radius = [self numberValue: profile[@" cornerRadius" ]];
840+ if (radius <= 0.0 ) {
841+ radius = [self numberValue: profile[@" chromeCornerRadius" ]];
842+ }
706843 CGFloat clampedRadius = MIN (MAX (radius, 0.0 ), MIN (width, height) / 2.0 );
707844
708845 CGContextSaveGState (context);
@@ -726,6 +863,7 @@ + (void)clearScreenAreaForProfile:(NSDictionary *)profile
726863 CGPathRelease (path);
727864 }
728865 CGContextRestoreGState (context);
866+ return YES ;
729867}
730868
731869+ (BOOL )drawSensorBarForChromeInfo : (NSDictionary *)chromeInfo
0 commit comments