Skip to content

读取一个带有加密印章的 PDF

之前公司在和一个做电子签章的公司合作时,他们给我们提供了一份带有加密印章的 PDF。在测试中发现,不论通过 UIWebView 或者 WKWebView 打开,PDF 中的加密印章都不能成功展示。

通过不断的调研及尝试,最终成功展示出了 PDF 中的加密印章。现在把方法分享给大家~

本文的所有代码均以上传至 GitHub,如需自取~

实现步骤

为了方便我进行代码编写,我们提前设置几个宏:

objc
#define kScreenW [UIScreen mainScreen].bounds.size.width
#define kScreenH [UIScreen mainScreen].bounds.size.height

#define DOCUMENTS_DIRECTORY [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]

详细代码

本文中的 Demo 是以 WKWebView 进行开发,如果您需要使用 UIWebView,请自行修改。

  1. 添加 WKWebView

    objc
    #import <WebKit/WebKit.h>
    
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    
    WKUserContentController *wkUController = [[WKUserContentController alloc]init];
    
    config.userContentController = wkUController;
    
    // 注入JS对象名称AppModel,当JS通过AppModel来调用时,我们可以在WKScriptMessageHandler代理中接收到 // 此处是为了得到PDF加载完成或失败的反馈 [config.userContentController addScriptMessageHandler:self name:@"AppModel"];
    
    // 改变页面内容宽度,适配屏幕大小 NSString *js = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
    
    WKUserScript *wkUserScript = [[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; [wkUController addUserScript:wkUserScript];
    
    WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, kScreenW, kScreenH - 64)
                                            configuration:config];
    webView.backgroundColor = [UIColor whiteColor];
    webView.UIDelegate = self;
    webView.navigationDelegate = self;
    [self.view addSubview:webView];
  2. 下载PDF

    objc
    NSString *urlStr = @"";
    
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlStr]];
    NSURLSession *session = [NSURLSession sharedSession];
    
    NSURLSessionDataTask *sessionDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"从服务器获取到 pdf 数据");
        //对从服务器获取到的数据 data 进行相应的处理:
        dispatch_async(dispatch_get_main_queue(), ^{
            NSString *path = [DOCUMENTS_DIRECTORY stringByAppendingPathComponent:@"contract.pdf"];
            NSFileManager *fm = [NSFileManager defaultManager];
            if ([fm fileExistsAtPath:path]) {
                [fm removeItemAtPath:path error:nil];
            }
            BOOL success =  [data writeToFile:path atomically:YES];
            if (success) {
                NSLog(@"保存成功");
                NSURL *baseURL = [NSURL fileURLWithPath:[self getHtmlBasePath]];
                NSString *path = [self getHtmlPath];
                NSString *htmlStr = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
                
                [self.webView loadHTMLString:htmlStr baseURL:baseURL];
            }
        });
    }];
    [sessionDataTask resume];
  3. 打开 PDF

    objc
    - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
        [self loadPDF];
    }
    
    - (void)loadPDF {
        NSString *path = [DOCUMENTS_DIRECTORY stringByAppendingPathComponent:@"contract.pdf"];
        NSData *data = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedAlways error:nil];
        
        NSString *paraStr = [data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn];
        NSString *js = [NSString stringWithFormat:@"loadMyJS('%@')",paraStr];
        //NSLOG(@"%@",method);
        
        [self.webView evaluateJavaScript:js completionHandler:^(id _Nullable response, NSError * _Nullable error) {
            if (error) {
                NSLog(@"%@", error);
                NSLog(@"当前手机系统版本较低,不支持查看,请升级系统或者到 PC 端查看。");
            }
        }];
    }
  4. WKWebView 的代理中,我们可以知道 PDF 是否成功打开,

    objc
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
        if ([message.name isEqualToString:@"AppModel"]) {
            //和 customview.js 文件交互,js 调 oc 的代码
            // 打印所传过来的参数,只支持 NSNumber, NSString, NSDate, NSArray, NSDictionary, and NSNull 类型
            if ([message.body[@"code"] isEqualToString:@"00000"]) {
                NSLog(@"%@", message.body[@"msg"]);
            }
        }
    }
  5. PDF 是否读取成功是在 customview.js 中通知控制器的,具体可以查看下面的代码。

    js
    function handlePages(page) {
    //create new canvas
    var viewport = page.getViewport(1);
    var canvas = document.createElement("canvas");
    canvas.style.display = "block";
    
    var context = canvas.getContext("2d");
    canvas.height = viewport.height;
    canvas.width = viewport.width;
    
    //render page
    page.render({ canvasContext: context, viewport: viewport });
    
    //add canvas to body
    document.body.appendChild(canvas);
    
    //render new page
    pageNum++;
    if (pdfDoc != null && pageNum <= numPages) {
        pdfDoc.getPage(pageNum).then(handlePages);
        // PDF 加载失败
    } else {
        console.log("pdf load complete");
        // PDF 加载完毕,body的内容可以根据具体的业务需求进行修改
        window.webkit.messageHandlers.AppModel.postMessage({
        code: "00000",
        msg: "pdf load complete",
        }); //和wkWebView交互
    }
    }

到此,我们已经可以在 iOS9 以上的系统中,成功打开带加密印章的 PDF。至于 iOS8 为什么不行呢?下面给大家讲解及提供方案。

iOS8 上,htmljs 文件的路径为 tmp 目录下,iOS9 及以上则在 bundle 里。

解决方案:

objc
- (NSString*)getHtmlBasePath {
    NSString *basePath = @"";
    if ([[[UIDevice currentDevice]systemVersion]floatValue]<9.0) {
        basePath = NSTemporaryDirectory();
    }else{
        basePath = [[NSBundle mainBundle] bundlePath];
    }
    return basePath;
}

- (NSString*)getHtmlPath{
    //在 iOS8 上,html 及 js 文件的路径为 tmp 目录下,iOS9 及以上则在 bundle 里
    NSString *path = @"";
    if ([[[UIDevice currentDevice]systemVersion] floatValue] < 9.0) {
        path = [self copyHtmlToTemp];
    } else {
        path = [[NSBundle mainBundle] pathForResource:@"index" ofType:@".html"];
    }
    return path;
}

//在 iOS8 上,html 及 js 文件要放到 tmp 目录下才能正常访问,iOS9 及以上不用
- (NSString*)copyHtmlToTemp {
    NSString* htmlPath = [[NSBundle mainBundle] pathForResource:@"index" ofType:@".html"];
    NSString *compatibilityJSPath = [[NSBundle mainBundle] pathForResource:@"compatibility" ofType:@".js"];
    NSString *customviewJSPath = [[NSBundle mainBundle] pathForResource:@"customview" ofType:@".js"];
    NSString *minimalCSSPath = [[NSBundle mainBundle] pathForResource:@"minimal" ofType:@".css"];
    NSString *pdfJSPath = [[NSBundle mainBundle] pathForResource:@"pdf" ofType:@".js"];
    NSString *pdfWorkerJSPath = [[NSBundle mainBundle] pathForResource:@"pdf.worker" ofType:@".js"];

    NSString *tempPath = NSTemporaryDirectory();

    NSString *htmlTempPath = [tempPath stringByAppendingPathComponent:@"index.html"];
    NSString *compatibilityJSTempPath = [tempPath stringByAppendingPathComponent:@"compatibility.js"];
    NSString *customviewJSTempPath = [tempPath stringByAppendingPathComponent:@"customview.js"];
    NSString *minimalCSSTempPath = [tempPath stringByAppendingPathComponent:@"minimal.css"];
    NSString *pdfJSTempPath = [tempPath stringByAppendingPathComponent:@"pdf.js"];
    NSString *pdfWorkerJSTempPath = [tempPath stringByAppendingPathComponent:@"pdf.worker.js"];

    NSFileManager *fm = [NSFileManager defaultManager];

    if (![fm fileExistsAtPath:htmlTempPath]) {
        [fm copyItemAtPath:htmlPath toPath:htmlTempPath error:nil];
    }
    if (![fm fileExistsAtPath:compatibilityJSTempPath]) {
        [fm copyItemAtPath:compatibilityJSPath toPath:compatibilityJSTempPath error:nil];
    }
    if (![fm fileExistsAtPath:customviewJSTempPath]) {
        [fm copyItemAtPath:customviewJSPath toPath:customviewJSTempPath error:nil];
    }
    if (![fm fileExistsAtPath:minimalCSSTempPath]) {
        [fm copyItemAtPath:minimalCSSPath toPath:minimalCSSTempPath error:nil];
    }
    if (![fm fileExistsAtPath:pdfJSTempPath]) {
        [fm copyItemAtPath:pdfJSPath toPath:pdfJSTempPath error:nil];
    }
    if (![fm fileExistsAtPath:pdfWorkerJSTempPath]) {
        [fm copyItemAtPath:pdfWorkerJSPath toPath:pdfWorkerJSTempPath error:nil];
    }
    return htmlTempPath;
}