Как реализовать просмотрщик PDF, который загружает страницы асинхронно - PullRequest
0 голосов
/ 06 мая 2018

Нам нужно разрешить пользователям нашего мобильного приложения просматривать журнал с быстрым, гибким и удобным интерфейсом для платформы (аналогично iBooks / Google Books).

Некоторые из необходимых нам умений - это возможность просматривать миниатюры всего журнала и поиск конкретного текста.

Проблема в том, что длина наших журналов составляет более 140 страниц, и мы не можем заставить наших пользователей заранее загружать всю электронную книгу / PDF полностью. Нам нужно, чтобы страницы загружались асинхронно, то есть чтобы пользователи могли начать чтение без полной загрузки контента.

Я изучал PDFKit для iOS, однако не нашел упоминаний в документации по асинхронной загрузке PDF.

Существуют ли какие-либо решения / библиотеки для реализации этой функции на iOS и Android?

Ответы [ 2 ]

0 голосов
/ 21 мая 2018

Сожалею, но нет доступных library или SDK, которые бы обеспечивали asynchronously загрузку страниц. На мобильном устройстве практически невозможно открыть файл PDF без загрузки полного файла PDF.

Решение:

Я уже сделал R & D для того же и выполнил ваше требование в проекте. Я не уверен, что iBooks и Google books использовали механизм ниже или нет. Но работает нормально в соответствии с вашими требованиями.

  • Разделите ваш pdf на n номер детали. (Например, если у вас 150 страниц в pdf, тогда каждый pdf содержит 15 страниц -> Это займет некоторое усилие с веб-конца.)
  • Как только первая часть успешно загружена, затем показать ее пользователю, а другая часть загружается асинхронно.
  • После загрузки всей части файла 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();

Ссылка: Как объединить файл PDF.

Обновление: Основным преимуществом этого механизма является то, что логика остается неизменной для всех устройств Android и iOS Device.

0 голосов
/ 20 мая 2018

То, что вы ищете, называется linearization и согласно этот ответ .

Первый объект сразу после строки заголовка% PDF-1.x должен содержит ключ словаря, указывающий / Linearized свойство файл.

Эта общая структура позволяет читателю, изучающему полный список адресов объектов очень быстро, без необходимости загрузить полный файл от начала до конца:

  • Зритель может отображать первые страницы очень быстро, до полный файл загружен.

  • Пользователь может щелкнуть предварительный просмотр миниатюрной страницы (или ссылку в ToC файла), чтобы перейти, скажем, на страницу 445, сразу после первые страницы были показаны, и зритель может запросить все объекты, требуемые для страницы 445, запрашивая удаленный сервер через байт диапазон запросов, чтобы доставить их «не в порядке», чтобы зритель мог показать эту страницу быстрее. (Пока пользователь читает страницы не по порядку, загрузка полного документа все еще будет продолжаться в фон ...)

Вы можете использовать эту нативную библиотеку до linearization 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 сохраняет текст страниц.
  2. Маршруты /page, /thumb и /search говорят сами за себя. Они просто возвращают результаты HTML, большого пальца или поискового запроса.

Во-вторых , на стороне клиента вы просто загружаете HTML-страницу всякий раз, когда пользователь прокручивает ее. Позвольте привести пример с ОС Android. Во-первых, вы хотите создать 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();
    }

Вспомогательные утилиты выше просто обрабатывают запросы к серверу для вас, они должны быть самоочевидными. Затем вы просто создаете RecyclerView с помощью 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;
        }
    }

Это должно быть об этом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...