如何实现异步加载页面的PDF查看器

时间:2018-05-06 02:50:27

标签: android ios pdf

我们需要允许我们的移动应用程序的用户浏览具有快速,流畅和平台原生体验的杂志(类似于iBooks / Google Books)。

我们需要的一些功能是能够看到整个杂志的缩略图,并搜索特定的文本。

问题是我们的杂志超过140页,我们不能强迫我们的用户必须事先完全下载整个电子书/ PDF。我们需要异步加载页面,即让用户开始阅读而不必完全下载内容。

我研究了适用于iOS的PDFKit但是我没有在文档中找到任何关于异步下载PDF的内容。

在iOS和Android上是否有任何解决方案/库可以实现此功能?

2 个答案:

答案 0 :(得分:4)

您正在寻找的内容称为linearization并且根据this answer

  

%PDF-1.x标题行后面的第一个对象应该是   包含一个字典键,表示/的Linearized属性   文件。

     

这种整体结构允许一致的读者学习   完整的对象地址列表非常快速,无需   从头到尾下载完整的文件:

     
      
  • 观众可以非常快速地显示第一页   完整文件已下载。

  •   
  • 用户可以点击缩略图页面预览(或ToC中的链接)   (例如,文件)为了跳转到第445页,紧接着之后   已显示第一页,然后查看者可以请求所有页面   通过字节询问远程服务器,第445页所需的对象   范围要求提供这些"故障"所以观众可以   更快地显示此页面。 (当用户不按顺序阅读页面时,   整个文件的下载仍将继续下载   背景...)

  •   

您可以使用this native librarylinearization PDF。

<强>然而 我不建议让它显示PDF 不会快速,流畅或感觉原生。出于这些原因,据我所知,没有linearization的原生移动应用程序。此外,您必须为PDF创建自己的渲染引擎,因为大多数PDF查看库不支持linearization。您应该做的是将PDF中的每个单独页面转换为服务器端的HTML,并让客户端仅在需要时加载页面并缓存。我们还将单独保存PDF计划文本以启用搜索。这样一切都将顺利,因为资源将被延迟加载。为了实现这一目标,您可以执行以下操作。

<强>首先 在服务器端,无论何时发布PDF,PDF的页面都应拆分为HTML文件,如上所述。还应从这些页面生成页面大拇指。假设您的服务器在python上运行flask microframework,这就是您所做的。

from flask import Flask,request
from werkzeug import secure_filename
import os
from pyPdf import PdfFileWriter, PdfFileReader
import imgkit
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.pdfpage import PDFPage
from pdfminer.converter import XMLConverter, HTMLConverter, TextConverter
from pdfminer.layout import LAParams
import io
import sqlite3
import Image

app = Flask(__name__)


@app.route('/publish',methods=['GET','POST'])
def upload_file():
     if request.method == 'POST':
        f = request.files['file']
        filePath = "pdfs/"+secure_filename(f.filename)
        f.save(filePath)
        savePdfText(filePath)
        inputpdf = PdfFileReader(open(filePath, "rb"))

        for i in xrange(inputpdf.numPages):
            output = PdfFileWriter()
            output.addPage(inputpdf.getPage(i))
            with open("document-page%s.pdf" % i, "wb") as outputStream:
                output.write(outputStream)
                imgkit.from_file("document-page%s.pdf" % i, "document-page%s.jpg" % i)
                saveThum("document-page%s.jpg" % i)
                os.system("pdf2htmlEX --zoom 1.3  pdf/"+"document-page%s.pdf" % i) 

    def saveThum(infile):
        save = 124,124
        outfile = os.path.splitext(infile)[0] + ".thumbnail"
        if infile != outfile:
            try:
                im = Image.open(infile)
                im.thumbnail(size, Image.ANTIALIAS)
                im.save(outfile, "JPEG")
            except IOError:
                print("cannot create thumbnail for '%s'" % infile)

    def savePdfText(data):
        fp = open(data, 'rb')
        rsrcmgr = PDFResourceManager()
        retstr = io.StringIO()
        codec = 'utf-8'
        laparams = LAParams()
        device = TextConverter(rsrcmgr, retstr, codec=codec, laparams=laparams)
        # Create a PDF interpreter object.
        interpreter = PDFPageInterpreter(rsrcmgr, device)
        # Process each page contained in the document.
        db = sqlite3.connect("pdfText.db")
        cursor = db.cursor()
        cursor.execute('create table if not exists pagesTextTables(id INTEGER PRIMARY KEY,pageNum TEXT,pageText TEXT)')
        db.commit()
        pageNum = 1
        for page in PDFPage.get_pages(fp):
            interpreter.process_page(page)
            data =  retstr.getvalue()
            cursor.execute('INSERT INTO pagesTextTables(pageNum,pageText) values(?,?) ',(str(pageNum),data ))
            db.commit()
            pageNum = pageNum+1

    @app.route('/page',methods=['GET','POST'])
    def getPage():
        if request.method == 'GET':
            page_num = request.files['page_num']
            return send_file("document-page%s.html" % page_num, as_attachment=True)

    @app.route('/thumb',methods=['GET','POST'])
    def getThum():
        if request.method == 'GET':
            page_num = request.files['page_num']
            return send_file("document-page%s.thumbnail" % page_num, as_attachment=True)

    @app.route('/search',methods=['GET','POST'])
    def search():
        if request.method == 'GET':
            query = request.files['query ']       
            db = sqlite3.connect("pdfText.db")
            cursor = db.cursor()
           cursor.execute("SELECT * from pagesTextTables Where pageText LIKE '%"+query +"%'")
           result = cursor.fetchone()
           response = Response()
           response.headers['queryResults'] = result 
           return response

以下是烧瓶应用正在做什么的解释。

  1. /publish路由负责发布您的杂志,将页面转换为HTML,将PDF文本保存到SQlite数据库并为这些页面生成缩略图。我已使用pyPDF将PDF拆分为单个页面,pdfToHtmlEx将页面转换为HTML,imgkit生成HTML到图像,PIL生成那些图像的拇指。另外,一个简单的Sqlite db可以保存页面&#39;文本。
  2. /page/thumb/search路线不言自明。他们只返回HTML,拇指或搜索查询结果。
  3. 其次,在客户端,您只需在用户滚动时下载HTML页面即可。让我举一个Android OS的例子。首先,您要创建一些Utils来处理GET请求者

    public static byte[] GetPage(int mPageNum){
    return CallServer("page","page_num",Integer.toString(mPageNum))
    }
    
    public static byte[] GetThum(int mPageNum){
    return CallServer("thumb","page_num",Integer.toString(mPageNum))
    }
    
    private  static byte[] CallServer(String route,String requestName,String requestValue) throws IOException{
    
            OkHttpClient client = new OkHttpClient.Builder().connectTimeout(30, TimeUnit.SECONDS).writeTimeout(30, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS).build();
            MultipartBody.Builder mMultipartBody = new MultipartBody.Builder().setType(MultipartBody.FORM).addFormDataPart(requestName,requestValue);
    
            RequestBody mRequestBody = mMultipartBody.build();
            Request request = new Request.Builder()
                    .url("yourUrl/"+route).post(mRequestBody)
                    .build();
            Response response = client.newCall(request).execute();
            return response.body().bytes();
        }
    

    上面的helper util简单处理对服务器的查询,它们应该是自解释的。 接下来,您只需创建一个带有WebView viewHolder的RecyclerView或更好的advanced webview,因为它可以为您提供更多的自定义功能。

        public static class ViewHolder extends RecyclerView.ViewHolder {
            private AdvancedWebView mWebView;
            public ViewHolder(View itemView) {
                super(itemView);
             mWebView = (AdvancedWebView)itemView;}
        }
        private class ContentAdapter extends RecyclerView.Adapter<YourFrament.ViewHolder>{
            @Override
            public ViewHolder onCreateViewHolder(ViewGroup container, int viewType) {
    
                return new ViewHolder(new AdvancedWebView(container.getContext()));
            }
    
            @Override
            public int getItemViewType(int position) {
    
                return 0;
            }
    
            @Override
            public void onBindViewHolder( ViewHolder holder, int position) {
    handlePageDownload(holder.mWebView);
            }
           private void handlePageDownload(AdvancedWebView mWebView){....}
    
            @Override
            public int getItemCount() {
                return numberOfPages;
            }
        }
    

    那应该是关于它的。

答案 1 :(得分:4)

我很遗憾地说,但没有任何librarySDK可用,它们提供asynchronously页面加载功能。在没有下载完整的pdf文件的情况下,移动设备上几乎不可能打开PDF文件。

<强>解决方案:

我已经完成了R&amp; D并且在项目中满足了您的要求。我不确定iBooksGoogle books是否使用了以下机制。但是根据您的要求工作正常。

  • 将您的PDF格式分为n个部分(例如,假设您有150页PDF格式,然后每个pdf包含15个页面 - &gt;这将需要一些来自Web端的工作。)
  • 成功完成第一部分下载后,将其显示给用户,并以异步方式下载其他部分。
  • 下载pdf文件的所有部分后,请使用以下代码合并Pdf文件。

如何合并PDF文件

UIGraphicsBeginPDFContextToFile(oldFile,paperSize,nil);

for (pageNumber = 1; pageNumber <= count; pageNumber++)
{
    UIGraphicsBeginPDFPageWithInfo(paperSize, nil);

    //Get graphics context to draw the page
    CGContextRef currentContext = UIGraphicsGetCurrentContext();

    //Flip and scale context to draw the pdf correctly
    CGContextTranslateCTM(currentContext, 0, paperSize.size.height);
    CGContextScaleCTM(currentContext, 1.0, -1.0);

    //Get document access of the pdf from which you want a page
    CGPDFDocumentRef newDocument = CGPDFDocumentCreateWithURL ((CFURLRef) newUrl);

    //Get the page you want
    CGPDFPageRef newPage = CGPDFDocumentGetPage (newDocument, pageNumber);

    //Drawing the page
    CGContextDrawPDFPage (currentContext, newPage);

    //Clean up
    newPage = nil;
    CGPDFDocumentRelease(newDocument);
    newDocument = nil;
    newUrl = nil;

}

UIGraphicsEndPDFContext();

参考:How to merge PDF file.

<强>更新 这种机制的主要优点是所有设备Android和iOS设备的逻辑保持相同。