Skip to content

Commit 75aec3c

Browse files
committed
Improve DeviceKit chrome geometry
1 parent 3e80079 commit 75aec3c

1 file changed

Lines changed: 130 additions & 13 deletions

File tree

cli/XCWChromeRenderer.m

Lines changed: 130 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ @implementation XCWChromeRenderer
1818
NSDictionary *json = chromeInfo[@"json"];
1919
NSDictionary *images = [json[@"images"] isKindOfClass:[NSDictionary class]] ? json[@"images"] : @{};
2020
NSDictionary *sizing = [images[@"sizing"] isKindOfClass:[NSDictionary class]] ? images[@"sizing"] : @{};
21+
NSDictionary *stand = [images[@"stand"] isKindOfClass:[NSDictionary class]] ? images[@"stand"] : @{};
2122

22-
CGFloat insetTop = [self numberValue:sizing[@"topHeight"]];
23-
CGFloat insetLeft = [self numberValue:sizing[@"leftWidth"]];
24-
CGFloat insetBottom = [self numberValue:sizing[@"bottomHeight"]];
25-
CGFloat insetRight = [self numberValue:sizing[@"rightWidth"]];
23+
CGFloat sizingTop = [self numberValue:sizing[@"topHeight"]];
24+
CGFloat sizingLeft = [self numberValue:sizing[@"leftWidth"]];
25+
CGFloat sizingBottom = [self numberValue:sizing[@"bottomHeight"]];
26+
CGFloat sizingRight = [self numberValue:sizing[@"rightWidth"]];
27+
CGFloat standHeight = [self numberValue:stand[@"height"]];
2628

2729
CGSize compositeSize = [self compositeSizeForChromeInfo:chromeInfo error:error];
2830
if (CGSizeEqualToSize(compositeSize, CGSizeZero)) {
@@ -31,19 +33,59 @@ @implementation XCWChromeRenderer
3133

3234
NSDictionary *paths = [json[@"paths"] isKindOfClass:[NSDictionary class]] ? json[@"paths"] : @{};
3335
NSDictionary *border = [paths[@"simpleOutsideBorder"] isKindOfClass:[NSDictionary class]] ? paths[@"simpleOutsideBorder"] : @{};
36+
NSDictionary *borderInsets = [border[@"insets"] isKindOfClass:[NSDictionary class]] ? border[@"insets"] : @{};
3437
CGFloat rawCornerRadius = [self numberValue:border[@"cornerRadiusX"]];
3538

39+
CGFloat borderTop = [self numberValue:borderInsets[@"top"]];
40+
CGFloat borderLeft = [self numberValue:borderInsets[@"left"]];
41+
CGFloat borderBottom = [self numberValue:borderInsets[@"bottom"]];
42+
CGFloat borderRight = [self numberValue:borderInsets[@"right"]];
43+
44+
CGFloat bezelTop = sizingTop + borderTop;
45+
CGFloat bezelLeft = sizingLeft + borderLeft;
46+
CGFloat bezelBottom = sizingBottom + borderBottom;
47+
CGFloat bezelRight = sizingRight + borderRight;
48+
3649
BOOL watchProfile = [self isWatchProfile:plist];
50+
BOOL hasComposite = [self compositeAssetPathForChromeInfo:chromeInfo].length > 0;
3751
CGFloat screenScale = MAX([self numberValue:plist[@"mainScreenScale"]], 1.0);
3852
CGFloat profileScreenWidth = [self numberValue:plist[@"mainScreenWidth"]];
3953
CGFloat profileScreenHeight = [self numberValue:plist[@"mainScreenHeight"]];
4054
CGFloat pointScreenWidth = watchProfile ? profileScreenWidth : profileScreenWidth / screenScale;
41-
CGFloat screenWidth = watchProfile ? profileScreenWidth : MAX(compositeSize.width - insetLeft - insetRight, 1.0);
42-
CGFloat screenHeight = watchProfile ? profileScreenHeight : MAX(compositeSize.height - insetTop - insetBottom, 1.0);
43-
CGFloat screenX = watchProfile ? MAX((compositeSize.width - screenWidth) / 2.0, 0.0) : insetLeft;
44-
CGFloat screenY = watchProfile ? MAX((compositeSize.height - screenHeight) / 2.0, 0.0) : insetTop;
45-
CGFloat bezelWidth = MAX(insetLeft, insetTop);
46-
CGFloat innerRadius = MAX(rawCornerRadius - bezelWidth, 0.0);
55+
CGFloat pointScreenHeight = watchProfile ? profileScreenHeight : profileScreenHeight / screenScale;
56+
57+
CGFloat screenWidth;
58+
CGFloat screenHeight;
59+
CGFloat screenX;
60+
CGFloat screenY;
61+
if (hasComposite && pointScreenWidth > 0.0 && pointScreenHeight > 0.0) {
62+
// The composite PDF defines authoritative chrome dimensions; the screen is the
63+
// device's point size centered horizontally inside the chrome with the bezel
64+
// insets pushing it down vertically (and stand area, if any, occupying the
65+
// bottom of the composite).
66+
screenWidth = pointScreenWidth;
67+
screenHeight = pointScreenHeight;
68+
screenX = MAX((compositeSize.width - screenWidth) / 2.0, 0.0);
69+
CGFloat usableHeight = compositeSize.height - standHeight;
70+
screenY = MAX((usableHeight - screenHeight) / 2.0, bezelTop);
71+
} else if (watchProfile) {
72+
screenWidth = profileScreenWidth;
73+
screenHeight = profileScreenHeight;
74+
screenX = MAX((compositeSize.width - screenWidth) / 2.0, 0.0);
75+
screenY = MAX((compositeSize.height - screenHeight) / 2.0, 0.0);
76+
} else {
77+
// 9-slice path: bezel insets (sizing + simpleOutsideBorder) frame the screen.
78+
// The stand, when present, sits below the chrome and outside the screen rect.
79+
screenX = bezelLeft;
80+
screenY = bezelTop;
81+
screenWidth = MAX(compositeSize.width - bezelLeft - bezelRight, 1.0);
82+
screenHeight = MAX(compositeSize.height - standHeight - bezelTop - bezelBottom, 1.0);
83+
}
84+
85+
// Inner corner radius: when the thickest bezel exceeds the outer radius, the
86+
// screen edge is past the curved region and the inner is effectively rectangular
87+
// (e.g. iPhone 6s Plus's tall top/bottom bezel collapses the screen rounding).
88+
CGFloat innerRadius = MAX(rawCornerRadius - MAX(bezelLeft, bezelTop), 0.0);
4789
CGFloat radiusScale = pointScreenWidth > 0.0 ? screenWidth / pointScreenWidth : 1.0;
4890
CGFloat cornerRadius = watchProfile ? rawCornerRadius : innerRadius * radiusScale;
4991

@@ -271,6 +313,9 @@ + (CGSize)compositeSizeForChromeInfo:(NSDictionary *)chromeInfo
271313
NSDictionary *json = chromeInfo[@"json"];
272314
NSDictionary *images = [json[@"images"] isKindOfClass:[NSDictionary class]] ? json[@"images"] : @{};
273315
NSDictionary *sizing = [images[@"sizing"] isKindOfClass:[NSDictionary class]] ? images[@"sizing"] : @{};
316+
NSDictionary *paths = [json[@"paths"] isKindOfClass:[NSDictionary class]] ? json[@"paths"] : @{};
317+
NSDictionary *bord = [paths[@"simpleOutsideBorder"] isKindOfClass:[NSDictionary class]] ? paths[@"simpleOutsideBorder"] : @{};
318+
NSDictionary *bordI = [bord[@"insets"] isKindOfClass:[NSDictionary class]] ? bord[@"insets"] : @{};
274319
CGFloat screenScale = MAX([self numberValue:plist[@"mainScreenScale"]], 1.0);
275320
BOOL watchProfile = [self isWatchProfile:plist];
276321
CGFloat screenWidth = [self numberValue:plist[@"mainScreenWidth"]];
@@ -279,8 +324,14 @@ + (CGSize)compositeSizeForChromeInfo:(NSDictionary *)chromeInfo
279324
screenWidth /= screenScale;
280325
screenHeight /= screenScale;
281326
}
282-
CGFloat totalWidth = screenWidth + [self numberValue:sizing[@"leftWidth"]] + [self numberValue:sizing[@"rightWidth"]];
283-
CGFloat totalHeight = screenHeight + [self numberValue:sizing[@"topHeight"]] + [self numberValue:sizing[@"bottomHeight"]];
327+
CGFloat bezelLeft = [self numberValue:sizing[@"leftWidth"]] + [self numberValue:bordI[@"left"]];
328+
CGFloat bezelRight = [self numberValue:sizing[@"rightWidth"]] + [self numberValue:bordI[@"right"]];
329+
CGFloat bezelTop = [self numberValue:sizing[@"topHeight"]] + [self numberValue:bordI[@"top"]];
330+
CGFloat bezelBottom = [self numberValue:sizing[@"bottomHeight"]] + [self numberValue:bordI[@"bottom"]];
331+
NSDictionary *stand = [images[@"stand"] isKindOfClass:[NSDictionary class]] ? images[@"stand"] : @{};
332+
CGFloat standHeight = [self numberValue:stand[@"height"]];
333+
CGFloat totalWidth = screenWidth + bezelLeft + bezelRight;
334+
CGFloat totalHeight = screenHeight + bezelTop + bezelBottom + standHeight;
284335
if (totalWidth > 0.0 && totalHeight > 0.0) {
285336
return CGSizeMake(totalWidth, totalHeight);
286337
}
@@ -348,7 +399,10 @@ + (BOOL)drawSlicedChromeInfo:(NSDictionary *)chromeInfo
348399
CGFloat bottomHeight = MAX(MAX(MAX(bottom, bottomSize.height), bottomLeftSize.height), bottomRightSize.height);
349400
CGFloat rightWidth = MAX(MAX(MAX(right, rightSize.width), topRightSize.width), bottomRightSize.width);
350401
CGFloat middleWidth = MAX(size.width - leftWidth - rightWidth, 1.0);
351-
CGFloat middleHeight = MAX(size.height - topHeight - bottomHeight, 1.0);
402+
NSDictionary *stand = [images[@"stand"] isKindOfClass:[NSDictionary class]] ? images[@"stand"] : @{};
403+
CGFloat standHeight = [self numberValue:stand[@"height"]];
404+
CGFloat chromeHeight = MAX(size.height - standHeight, 1.0);
405+
CGFloat middleHeight = MAX(chromeHeight - topHeight - bottomHeight, 1.0);
352406

353407
NSArray<NSDictionary *> *pieces = @[
354408
@{ @"path": topLeftPath, @"rect": [NSValue valueWithRect:NSMakeRect(0, 0, leftWidth, topHeight)] },
@@ -377,6 +431,13 @@ + (BOOL)drawSlicedChromeInfo:(NSDictionary *)chromeInfo
377431
return NO;
378432
}
379433
}
434+
if (standHeight > 0.0 && ![self drawStandImagesForChromeInfo:chromeInfo
435+
inSize:size
436+
chromeYMax:chromeHeight
437+
context:context
438+
error:error]) {
439+
return NO;
440+
}
380441
if (!drewAny && error != NULL) {
381442
*error = [NSError errorWithDomain:XCWChromeRendererErrorDomain
382443
code:13
@@ -387,6 +448,62 @@ + (BOOL)drawSlicedChromeInfo:(NSDictionary *)chromeInfo
387448
return drewAny;
388449
}
389450

451+
+ (BOOL)drawStandImagesForChromeInfo:(NSDictionary *)chromeInfo
452+
inSize:(CGSize)size
453+
chromeYMax:(CGFloat)chromeYMax
454+
context:(CGContextRef)context
455+
error:(NSError * _Nullable __autoreleasing *)error {
456+
NSDictionary *json = chromeInfo[@"json"];
457+
NSString *chromePath = chromeInfo[@"chromePath"];
458+
NSDictionary *images = [json[@"images"] isKindOfClass:[NSDictionary class]] ? json[@"images"] : @{};
459+
NSDictionary *stand = [images[@"stand"] isKindOfClass:[NSDictionary class]] ? images[@"stand"] : @{};
460+
CGFloat standWidth = [self numberValue:stand[@"width"]];
461+
CGFloat standHeight = [self numberValue:stand[@"height"]];
462+
if (standWidth <= 0.0 || standHeight <= 0.0) {
463+
return YES;
464+
}
465+
466+
NSString *leftName = [stand[@"left"] isKindOfClass:[NSString class]] ? stand[@"left"] : @"";
467+
NSString *centerName = [stand[@"center"] isKindOfClass:[NSString class]] ? stand[@"center"] : @"";
468+
NSString *rightName = [stand[@"right"] isKindOfClass:[NSString class]] ? stand[@"right"] : @"";
469+
NSString *leftPath = leftName.length > 0 ? [self resolvedChromeAssetPathForName:leftName chromePath:chromePath] : @"";
470+
NSString *centerPath = centerName.length > 0 ? [self resolvedChromeAssetPathForName:centerName chromePath:chromePath] : @"";
471+
NSString *rightPath = rightName.length > 0 ? [self resolvedChromeAssetPathForName:rightName chromePath:chromePath] : @"";
472+
CGSize leftSize = [self PDFPageSizeAtPath:leftPath];
473+
CGSize rightSize = [self PDFPageSizeAtPath:rightPath];
474+
CGFloat leftWidth = MAX(leftSize.width, 0.0);
475+
CGFloat rightWidth = MAX(rightSize.width, 0.0);
476+
CGFloat centerWidth = MAX(standWidth - leftWidth - rightWidth, 1.0);
477+
CGFloat x = MAX((size.width - standWidth) / 2.0, 0.0);
478+
CGFloat y = chromeYMax;
479+
480+
if (leftPath.length > 0 && leftWidth > 0.0) {
481+
if (![self drawPDFAtPath:leftPath
482+
inRect:CGRectMake(x, y, leftWidth, standHeight)
483+
context:context
484+
error:error]) {
485+
return NO;
486+
}
487+
}
488+
if (centerPath.length > 0) {
489+
if (![self drawPDFAtPath:centerPath
490+
inRect:CGRectMake(x + leftWidth, y, centerWidth, standHeight)
491+
context:context
492+
error:error]) {
493+
return NO;
494+
}
495+
}
496+
if (rightPath.length > 0 && rightWidth > 0.0) {
497+
if (![self drawPDFAtPath:rightPath
498+
inRect:CGRectMake(x + leftWidth + centerWidth, y, rightWidth, standHeight)
499+
context:context
500+
error:error]) {
501+
return NO;
502+
}
503+
}
504+
return YES;
505+
}
506+
390507
+ (BOOL)drawInputImagesForChromeInfo:(NSDictionary *)chromeInfo
391508
inSize:(CGSize)size
392509
context:(CGContextRef)context

0 commit comments

Comments
 (0)