Files
banban/docs/banban-mini-design-atlas/render-overview.m
T

157 lines
5.9 KiB
Objective-C

#import <Cocoa/Cocoa.h>
#import <CoreImage/CoreImage.h>
#import <WebKit/WebKit.h>
static CGFloat PageWidth = 1440.0;
static CGFloat OutputScale = 1.0;
static CGFloat SharpenIntensity = 0.0;
@interface PageRenderer : NSObject <WKNavigationDelegate>
@property(nonatomic, strong) WKWebView *webView;
@property(nonatomic, strong) NSURL *inputURL;
@property(nonatomic, strong) NSURL *outputURL;
@end
@implementation PageRenderer
- (instancetype)initWithInputURL:(NSURL *)inputURL outputURL:(NSURL *)outputURL {
self = [super init];
if (self) {
_inputURL = inputURL;
_outputURL = outputURL;
_webView = [[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, PageWidth, 900)];
_webView.navigationDelegate = self;
}
return self;
}
- (void)start {
NSURL *repositoryRoot = self.inputURL.URLByDeletingLastPathComponent
.URLByDeletingLastPathComponent.URLByDeletingLastPathComponent;
[self.webView loadFileURL:self.inputURL allowingReadAccessToURL:repositoryRoot];
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
NSString *heightScript = @"Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)";
[webView evaluateJavaScript:heightScript completionHandler:^(id result, NSError *error) {
if (error || ![result isKindOfClass:NSNumber.class]) {
[self fail:[NSString stringWithFormat:@"Could not read page height: %@", error.localizedDescription ?: @"unknown error"]];
return;
}
CGFloat pageHeight = [(NSNumber *)result doubleValue];
[webView setFrameSize:NSMakeSize(PageWidth, pageHeight)];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
WKSnapshotConfiguration *configuration = [[WKSnapshotConfiguration alloc] init];
configuration.rect = CGRectMake(0, 0, PageWidth, pageHeight);
configuration.snapshotWidth = @(PageWidth * OutputScale);
[webView takeSnapshotWithConfiguration:configuration completionHandler:^(NSImage *image, NSError *snapshotError) {
NSBitmapImageRep *bitmap = nil;
if (image && SharpenIntensity > 0.0) {
CIImage *inputImage = [[CIImage alloc] initWithData:image.TIFFRepresentation];
CIFilter *sharpen = [CIFilter filterWithName:@"CIUnsharpMask"];
[sharpen setValue:inputImage forKey:kCIInputImageKey];
[sharpen setValue:@(1.0) forKey:kCIInputRadiusKey];
[sharpen setValue:@(SharpenIntensity) forKey:kCIInputIntensityKey];
CIImage *outputImage = sharpen.outputImage;
CIContext *context = [CIContext contextWithOptions:nil];
CGImageRef outputCGImage = outputImage
? [context createCGImage:outputImage fromRect:inputImage.extent]
: nil;
if (outputCGImage) {
bitmap = [[NSBitmapImageRep alloc] initWithCGImage:outputCGImage];
CGImageRelease(outputCGImage);
}
} else if (image) {
bitmap = [[NSBitmapImageRep alloc] initWithData:image.TIFFRepresentation];
}
NSData *pngData = [bitmap representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
if (snapshotError || !pngData) {
[self fail:[NSString stringWithFormat:@"Could not create snapshot: %@", snapshotError.localizedDescription ?: @"unknown error"]];
return;
}
NSError *writeError = nil;
if (![pngData writeToURL:self.outputURL options:NSDataWritingAtomic error:&writeError]) {
[self fail:[NSString stringWithFormat:@"Could not write PNG: %@", writeError.localizedDescription]];
return;
}
printf("Rendered %dx%d at %.2fx, sharpen %.2f -> %s\n",
(int)PageWidth,
(int)pageHeight,
OutputScale,
SharpenIntensity,
self.outputURL.path.UTF8String);
exit(0);
}];
});
}];
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
[self fail:[NSString stringWithFormat:@"Navigation failed: %@", error.localizedDescription]];
}
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
[self fail:[NSString stringWithFormat:@"Page load failed: %@", error.localizedDescription]];
}
- (void)fail:(NSString *)message {
fprintf(stderr, "%s\n", message.UTF8String);
exit(1);
}
@end
int main(int argc, const char *argv[]) {
@autoreleasepool {
if (argc < 3 || argc > 6) {
fprintf(stderr, "Usage: render-overview <input.html> <output.png> [page-width] [output-scale] [sharpen-intensity]\n");
return 2;
}
if (argc >= 4) {
double requestedWidth = strtod(argv[3], NULL);
if (requestedWidth < 320.0 || requestedWidth > 4096.0) {
fprintf(stderr, "Page width must be between 320 and 4096 pixels\n");
return 2;
}
PageWidth = (CGFloat)requestedWidth;
}
if (argc >= 5) {
double requestedScale = strtod(argv[4], NULL);
if (requestedScale < 1.0 || requestedScale > 4.0) {
fprintf(stderr, "Output scale must be between 1 and 4\n");
return 2;
}
OutputScale = (CGFloat)requestedScale;
}
if (argc == 6) {
double requestedSharpness = strtod(argv[5], NULL);
if (requestedSharpness < 0.0 || requestedSharpness > 1.0) {
fprintf(stderr, "Sharpen intensity must be between 0 and 1\n");
return 2;
}
SharpenIntensity = (CGFloat)requestedSharpness;
}
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
NSString *inputPath = [NSString stringWithUTF8String:argv[1]].stringByStandardizingPath;
NSString *outputPath = [NSString stringWithUTF8String:argv[2]].stringByStandardizingPath;
PageRenderer *renderer = [[PageRenderer alloc]
initWithInputURL:[NSURL fileURLWithPath:inputPath]
outputURL:[NSURL fileURLWithPath:outputPath]];
[renderer start];
[NSRunLoop.mainRunLoop run];
}
return 0;
}