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

lis*_*aro 13 pdf android ios

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

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

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

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

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

Niz*_*ale 5

linearization根据这个答案,您正在寻找的是所谓的。

紧跟在 %PDF-1.x 标题行之后的第一个对象应包含一个字典键,指示文件的 /Linearized 属性。

这种整体结构允许符合标准的读者非常快速地了解对象地址的完整列表,而无需从头到尾下载完整的文件:

  • 在下载完整文件之前,查看器可以非常快地显示第一页。

  • 用户可以单击缩略图页面预览(或文件 ToC 中的链接),以便在第一页显示后立即跳转到第 445 页,然后查看者可以请求所有445 页所需的对象,通过字节范围请求向远程服务器发送这些“乱序”,以便查看者可以更快地显示此页面。(当用户乱序阅读页面时,整个文档的下载仍然会在后台继续......)

您可以将此本机库用于linearizationPDF。

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

首先 在服务器端,每当您发布 PDF 时,应将 PDF 页面拆分为 HTML 文件,如上所述。页面缩略图也应该从这些页面生成。假设您的服务器上运行pythonflask 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
Run Code Online (Sandbox Code Playgroud)

这里是对烧瓶应用程序正在做什么的解释。

  1. /publish路由负责出版您的杂志,将页面转换为 HTML,将 PDF 文本保存到 SQLite 数据库并为这些页面生成缩略图。我使用pyPDF将 PDF 拆分为单个页面,使用pdfToHtmlEx将页面转换为 HTML,使用imgkit将 HTML 生成为图像,使用PIL从这些图像生成缩略图。此外,一个简单的Sqlite db保存页面的文本。
  2. /page/thumb/search途径是自我解释。它们只是返回 HTML、拇指或搜索查询结果。

其次,在客户端,只要用户滚动到它,您只需下载 HTML 页面。让我给你举一个安卓操作系统的例子。首先,您需要创建一些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();
    }
Run Code Online (Sandbox Code Playgroud)

上面的辅助工具简单地为您处理对服务器的查询,它们应该是不言自明的。接下来,您可以简单地创建一个RecyclerView带有 WebView viewHolder 或更好的高级 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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

那应该是关于它的。