Ответ 1
То, что вы ищете, называется 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
Вот объяснение того, что делает приложение для флэков.
- Маршрут
/publish
несет ответственность за публикацию вашего журнала, превращение страницы в HTML, сохранение текста PDF файлов в базу данных SQlite и создание эскизов для этих страниц. Я использовал pyPDF для разделения PDF на отдельные страницы, pdfToHtmlEx для преобразования страниц в HTML, imgkit для генерации этих HTML-изображений и PIL для создания больших пальцев с этих изображений. Кроме того, простойSqlite db
сохраняет текст страниц. - Пути
/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
с помощью WebView viewHolder или еще лучше продвинутого веб-браузера, так как это даст вам больше возможностей с настройкой.
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;
}
}
Это должно быть об этом.