I am working on an application (Android 4.4 -- API 20) where I am generating a report in HTML format. I use the WebView object to display the report in my app.
What I would like to be able to do is convert this WebView into a pdf document.
I have been able to convert it using PdfDocument, and doing .draw onto the page from the WebView object. I save the file, and this works, except that the result is a single page document. There are no page breaks.
View content = (View) webView;
PrintAttributes pdfPrintAttrs = new PrintAttributes.Builder().
setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME).
setMediaSize(PrintAttributes.MediaSize.NA_LETTER.asLandscape()).
setResolution(new Resolution("zooey", PRINT_SERVICE, 300, 300)).
setMinMargins(PrintAttributes.Margins.NO_MARGINS).
build();
PdfDocument document = new PrintedPdfDocument(mContext,pdfPrintAttrs);
PageInfo pageInfo = new PageInfo.Builder(webView.getMeasuredWidth(), webView.getContentHeight(), 1).create();
Page page = document.startPage(pageInfo);
content.draw(page.getCanvas());
document.finishPage(page);
If I change it so that I use the PrintedPdfDocumet and don't specify the PageInfo I only get the viewable part of the WebView object.
View content = (View) webView;
PrintAttributes pdfPrintAttrs = new PrintAttributes.Builder().
setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME).
setMediaSize(PrintAttributes.MediaSize.NA_LETTER.asLandscape()).
setResolution(new Resolution("zooey", PRINT_SERVICE, 300, 300)).
setMinMargins(PrintAttributes.Margins.NO_MARGINS).
build();
PrintedPdfDocument document = new PrintedPdfDocument(mContext,pdfPrintAttrs);
Page page = document.startPage(0);
content.draw(page.getCanvas());
document.finishPage(page);
If I use the PrintManager and create a print adapter from the WebView object with createPrintDocumentAdapter, I can select the "Save as PDF" option and the resulting pdf file has the page breaks as I specify in the CSS of the original web page.
PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
PrintDocumentAdapter printAdapter = webView.createPrintDocumentAdapter();
String jobName = getString(R.string.app_name) + " Report "
+ reportName;
PrintAttributes printAttrs = new PrintAttributes.Builder().
setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME).
setMediaSize(PrintAttributes.MediaSize.NA_LETTER.asLandscape()).
setMinMargins(PrintAttributes.Margins.NO_MARGINS).
build();
PrintJob printJob = printManager.print(jobName, printAdapter,
printAttrs);
My question is: can I specify that I want the PrintManager to perform a "Save as PDF" and provide the name and location of the resulting file so that there is no interaction with the user?
Or: Is there a way I can convert my WebView object into a PDF and allow for page breaks.
It might be a late answer but I was also in need of similar solution with Print Framework so far, and I splitted the Pdf Document into pages with the code below.
As far as I can see, you cannot really make the WebView or Pdf Document splits your pdf file into pages in a smart way (not cutting the text or image). But what we can do is to create Pages in a ratio of A4 or Letter size, so it can fit into print out paper format.
But there is another issue I'm facing. The code below works as expected in Android 4.4 but not in later versions. In Android-L, only the visible part of WebView is drawn into Pdf File, but white blank pages for the rest of the HTML in WebView.
According to documentation,
public static void enableSlowWholeDocumentDraw ()
For apps targeting the L release, WebView has a new default behavior that reduces memory footprint and increases performance by intelligently choosing the portion of the HTML document that needs to be drawn. These optimizations are transparent to the developers. However, under certain circumstances, an App developer may want to disable them:
When an app uses onDraw(Canvas) to do own drawing and accesses portions of the page that is way outside the visible portion of the page.
When an app uses capturePicture() to capture a very large HTML document. Note that capturePicture is a deprecated API.
Enabling drawing the entire HTML document has a significant performance cost. This method should be called before any WebViews are created.
I've created a Bug Report, and commented on a similar bug report HERE, but no response so far. But until then, you can use the code below.
/**
* Creates a PDF Multi Page Document depending on the Ratio of Letter Size.
* This method does not close the Document. It should be Closed after writing Pdf Document to a File.
*
* #return
*/
private PdfDocument createMultiPagePdfDocument(int webViewWidth, int webViewHeight) {
/* Find the Letter Size Height depending on the Letter Size Ratio and given Page Width */
int letterSizeHeight = getLetterSizeHeight(webViewWidth);
PdfDocument document = new PrintedPdfDocument(getActivity(), getPrintAttributes());
final int numberOfPages = (webViewHeight/letterSizeHeight) + 1;
for (int i = 0; i < numberOfPages; i++) {
int webMarginTop = i*letterSizeHeight;
PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(webViewWidth, letterSizeHeight, i+1).create();
PdfDocument.Page page = document.startPage(pageInfo);
/* Scale Canvas */
page.getCanvas().translate(0, -webMarginTop);
mWebView.draw(page.getCanvas());
document.finishPage(page);
}
return document;
}
/**
* Calculates the Letter Size Paper's Height depending on the LetterSize Dimensions and Given width.
*
* #param width
* #return
*/
private int getLetterSizeHeight(int width) {
return (int)((float)(11*width)/8.5);
}
Not sure if this will solve your page-break issues, but have you considered using the open-source wkHTMLtoPDF library (http://wkhtmltopdf.org/) for the conversion from HTML to PDF? We have used it extensively by creating a micro-service that we pass the HTML code to, then have the service convert it to PDF and return the link to the PDF, or alternatively have it return the PDF (depending on size). I know using an external service for the conversion might be a pain (or maybe you don't have internet access from the device), but if that's not an issue, then this could be an option. There may be other APIs available to do this conversion as well. One such API is Neutrino API. There are many others - you can search for APIs using one of these API search engines:
apis.io
Progammable Web
Public APIs
After spending enormous time with this problem, I used DexMaker to implement non public abstract callbacks and came up with this:
#Override
protected void onPreExecute() {
super.onPreExecute();
printAdapter = webView.createPrintDocumentAdapter();
}
#Override
protected Void doInBackground(Void... voids) {
File file = new File(pdfPath);
if (file.exists()) {
file.delete();
}
try {
file.createNewFile();
// get file descriptor
descriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
// create print attributes
PrintAttributes attributes = new PrintAttributes.Builder()
.setMediaSize(PrintAttributes.MediaSize.ISO_A4)
.setResolution(new PrintAttributes.Resolution("id", PRINT_SERVICE, 300, 300))
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setMinMargins(new PrintAttributes.Margins(0, 0, 0, 0))
.build();
ranges = new PageRange[]{new PageRange(1, numberPages)};
// dexmaker cache folder
cacheFolder = new File(context.getFilesDir() +"/etemp/");
printAdapter.onStart();
printAdapter.onLayout(attributes, attributes, new CancellationSignal(), getLayoutResultCallback(new InvocationHandler() {
#Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
if (method.getName().equals("onLayoutFinished")) {
onLayoutSuccess();
} else {
Log.e(TAG, "Layout failed");
pdfCallback.onPdfFailed();
}
return null;
}
}, cacheFolder), new Bundle());
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, e != null ? e.getMessage() : "PrintPdfTask unknown error");
}
return null;
}
private void onLayoutSuccess() throws IOException {
PrintDocumentAdapter.WriteResultCallback callback = getWriteResultCallback(new InvocationHandler() {
#Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
if (method.getName().equals("onWriteFinished")) {
pdfCallback.onPdfCreated();
} else {
Log.e(TAG, "Layout failed");
pdfCallback.onPdfFailed();
}
return null;
}
}, cacheFolder);
printAdapter.onWrite(ranges, descriptor, new CancellationSignal(), callback);
}
/**
* Implementation of non public abstract class LayoutResultCallback obtained via DexMaker
* #param invocationHandler
* #param dexCacheDir
* #return LayoutResultCallback
* #throws IOException
*/
public static PrintDocumentAdapter.LayoutResultCallback getLayoutResultCallback(InvocationHandler invocationHandler,
File dexCacheDir) throws IOException {
return ProxyBuilder.forClass(PrintDocumentAdapter.LayoutResultCallback.class)
.dexCache(dexCacheDir)
.handler(invocationHandler)
.build();
}
/**
* Implementation of non public abstract class WriteResultCallback obtained via DexMaker
* #param invocationHandler
* #param dexCacheDir
* #return LayoutResultCallback
* #throws IOException
*/
public static PrintDocumentAdapter.WriteResultCallback getWriteResultCallback(InvocationHandler invocationHandler,
File dexCacheDir) throws IOException {
return ProxyBuilder.forClass(PrintDocumentAdapter.WriteResultCallback.class)
.dexCache(dexCacheDir)
.handler(invocationHandler)
.build();
}
Related
We need to allow users of our mobile app to browse a magazine with an experience that is fast, fluid and feels native to the platform (similar to iBooks/Google Books).
Some featurs we need are being able to see Thumbnails of the whole magazine, and searching for specific text.
The problem is that our magazines are over 140 pages long and we can’t force our users to have to fully download the whole ebook/PDF beforehand. We need pages to be loaded asynchronously, that is, to let users start reading without having to fully download the content.
I studied PDFKit for iOS however I didn’t find any mention in the documentation about downloading a PDF asynchronously.
Are there any solutions/libraries to implement this functionality on iOS and Android?
What you're looking for is called linearization and according to this answer.
The first object immediately after the %PDF-1.x header line shall
contain a dictionary key indicating the /Linearized property of the
file.
This overall structure allows a conforming reader to learn the
complete list of object addresses very quickly, without needing to
download the complete file from beginning to end:
The viewer can display the first page(s) very fast, before the
complete file is downloaded.
The user can click on a thumbnail page preview (or a link in the ToC
of the file) in order to jump to, say, page 445, immediately after the
first page(s) have been displayed, and the viewer can then request all
the objects required for page 445 by asking the remote server via byte
range requests to deliver these "out of order" so the viewer can
display this page faster. (While the user reads pages out of order,
the downloading of the complete document will still go on in the
background...)
You can use this native library to linearization a PDF.
However
I wouldn't recommend made it has rendering the PDFs wont be fast, fluid or feel native. For those reasons, as far as I know there is no native mobile app that does linearization. Moreover, you have to create your own rendering engine for the PDF as most PDF viewing libraries do not support linearization . What you should do instead is convert the each individual page in the PDF to HTML on the server end and have the client only load the pages when required and cache. We will also save PDFs plan text separately in order to enable search. This way everything will be smooth as the resources will be lazy loaded. In order to achieve this you can do the following.
Firstly
On the server end, whenever you publish a PDF, the pages of the PDF should be split into HTML files as explained above. Page thumbs should also be generated from those pages. Assuming that your server is running on python with a flask microframework this is what you do.
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
Here is an explanation of what the flask app is doing.
The /publish route is responsible for the publishing of your magazine, turning very page to HTML, saving the PDFs text to an SQlite db and generating thumbnails for those pages. I've used pyPDF for splitting the PDF to individual pages, pdfToHtmlEx to convert the pages to HTML, imgkit to generate those HTML to images and PIL to generate thumbs from those images. Also, a simple Sqlite db saves the pages' text.
The /page, /thumb and /search routes are self explanatory. They simply return the HTML, thumb or search query results.
Secondly, on the client end you simply download the HTML page whenever the user scrolls to it. Let me give you an example for android OS. Firstly, you'd want to Create some Utils to handle the GET requestrs
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();
}
The helper utils above simple handle the queries to the server for you, they should be self explanatory.
Next, you simple create an RecyclerView with a WebView viewHolder or better yet an advanced webview as it will give you more power with customization.
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;
}
}
That should be about it.
I am sorry to say, But there is no any library or SDK available which provides asynchronously pages loading functionality. It is next to impossible on the mobile device to open PDF file without downloading the full pdf file.
Solution:
I have already done R&D for the same and fulfilled your requirement in the project. I am not sure iBooks and Google books used below mechanism or not. But is working fine as per your requirements.
Divide your pdf into n number of part (E.g Suppose you have 150 pages in pdf then every pdf contain 15 pages -> It will take some effort from web end.)
Once first part download successfully then display it to the user and other part downloading asynchronously.
After downloading all part of the pdf file, Use below code the merge Pdf file.
How to Merge PDF file
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();
Reference: How to merge PDF file.
Update:
Main advantage of this mechanism is Logic remain same for all device Android and iOS Device.
I am trying to create PDF file from xml layout view.
I have a listview in that layout, adding items and setting height based on child. PDF is creating but not filling the whole page.
What I have tried is,
PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(2250, 1400, 1).create();
// start a page
PdfDocument.Page page = document.startPage(pageInfo);
// draw something on the page
LayoutInflater inflater = (LayoutInflater)
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View content = inflater.inflate(R.layout.pdf_layout, null);
content.measure(2250, 1400);
content.layout(0,0, 2250, 1400);
tvName = (TextView)content.findViewById(R.id.tvName);
tvDate = (TextView)content.findViewById(R.id.tvDate);
tvAge = (TextView)content.findViewById(R.id.tvAge);
tvGender = (TextView)content.findViewById(R.id.tvGender);
tvPhone = (TextView)content.findViewById(R.id.tvPhone);
lvList = (ListView)content.findViewById(R.id.lvList);
lvList.setAdapter(adapter);
Utils.setListViewHeight(lvList, CreatePDFDemo.this);
tvName.setText(name);
tvAge.setText(age + "Y");
tvGender.setText(gender);
tvPhone.setText(phone);
content.draw(page.getCanvas());
// finish the page
document.finishPage(page);
// add more pages
// write the document content
try {
document.writeTo(output);
} catch (IOException e) {
e.printStackTrace();
}
This its output is like this image,
How can I write layout view covering full width of pdf page?
Change to this,
int measureWidth = View.MeasureSpec.makeMeasureSpec(page.getCanvas().getWidth(), View.MeasureSpec.EXACTLY);
int measuredHeight = View.MeasureSpec.makeMeasureSpec(page.getCanvas().getHeight(), View.MeasureSpec.EXACTLY);
content.measure(measureWidth, measuredHeight);
content.layout(0, 0, page.getCanvas().getWidth(), page.getCanvas().getHeight());
This will get page full height and width.
Use [PrintContent] (https://developer.android.com/reference/android/support/v4/print/PrintHelper.html)!
// Get the print manager.
PrintHelper printHelper = new PrintHelper(this);
// Set the desired scale mode.
printHelper.setScaleMode(PrintHelper.SCALE_MODE_FIT);
// Get the bitmap for the ImageView's drawable.
Bitmap bitmap = ((BitmapDrawable) mImageView.getDrawable()).getBitmap();
// Print the bitmap.
printHelper.printBitmap("Print Bitmap", bitmap);
I made a library to achieve this objective (Getting PDF from layout view).
The main code snippet is with the proper documentation -
PdfGenerator.getBuilder()
.setContext(context)
.fromLayoutXMLSource()
.fromLayoutXML(R.layout.layout_print,R.layout.layout_print)
/* "fromLayoutXML()" takes array of layout resources.
* You can also invoke "fromLayoutXMLList()" method here which takes list of layout resources instead of array. */
.setDefaultPageSize(PdfGenerator.PageSize.A4)
/* It takes default page size like A4,A5. You can also set custom page size in pixel
* by calling ".setCustomPageSize(int widthInPX, int heightInPX)" here. */
.setFileName("Test-PDF")
/* It is file name */
.setFolderName("FolderA/FolderB/FolderC")
/* It is folder name. If you set the folder name like this pattern (FolderA/FolderB/FolderC), then
* FolderA creates first.Then FolderB inside FolderB and also FolderC inside the FolderB and finally
* the pdf file named "Test-PDF.pdf" will be store inside the FolderB. */
.openPDFafterGeneration(true)
/* It true then the generated pdf will be shown after generated. */
.build(new PdfGeneratorListener() {
#Override
public void onFailure(FailureResponse failureResponse) {
super.onFailure(failureResponse);
/* If pdf is not generated by an error then you will findout the reason behind it
* from this FailureResponse. */
}
#Override
public void showLog(String log) {
super.showLog(log);
/*It shows logs of events inside the pdf generation process*/
}
#Override
public void onSuccess(SuccessResponse response) {
super.onSuccess(response);
/* If PDF is generated successfully then you will find SuccessResponse
* which holds the PdfDocument,File and path (where generated pdf is stored)*/
}
});
Try to convert your layout into image then set that image to PDF. read this, maybe you will get some idea.
Convert view to PDF
I was using following piece of code in Kitkat to generate pdf file. Some how it used to generate the pdf file name as per my given text.
but in lollipop the same piece of code is not functional.Its generating file name with "RepoDocument.pdf" where Repo is my application name
#Override
public void onLayout(PrintAttributes oldAttributes,
PrintAttributes newAttributes,
CancellationSignal cancellationSignal,
LayoutResultCallback callback,
Bundle metadata)
{
// Create a new PdfDocument with the requested page attributes
mPdfDocument = new PrintedPdfDocument(m_context, newAttributes);
// Respond to cancellation request
if (cancellationSignal.isCanceled() )
{
callback.onLayoutCancelled();
return;
}
// Compute the expected number of printed pages
int pages = computePageCount(newAttributes);
if (pages > 0)
{
// Return print information to print framework
PrintDocumentInfo info = new PrintDocumentInfo.Builder("mycustomname.pdf")
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(pages).build();
// Content layout reflow is complete
callback.onLayoutFinished(info, true);
}
else
{
// Otherwise report an error to the print framework
callback.onLayoutFailed("Page count calculation failed.");
}
}
What is the reason for this issue? let me know if you need some more input
File name can be defined here
private void doPrint() {
// Get a PrintManager instance
PrintManager printManager = (PrintManager) getActivity()
.getSystemService(Context.PRINT_SERVICE);
// Set job name, which will be displayed in the print queue
String jobName = "DocumentName.pdf";
// Start a print job, passing in a PrintDocumentAdapter implementation
// to handle the generation of a print document
printManager.print(jobName, new MyPrintDocumentAdapter(getActivity()),
null); //
}
I think the jobname is not meant to be the file name as it is only used to name the printing job.
I'm using muPDF for reading PDFs in my application. I don't like its default animation (Switching horizontally). In other side i found this brilliant library for curl effect on images, and this project for flip-flap effect on layouts.
In curl sample project, in CurlActivity, all of data are images and set in PageProvider like this:
private class PageProvider implements CurlView.PageProvider {
// Bitmap resources.
private int[] mBitmapIds = { R.drawable.image1, R.drawable.image2,
R.drawable.image3, R.drawable.image4};
And use it like this:
private CurlView mCurlView;
mCurlView = (CurlView) findViewById(R.id.curl);
mCurlView.setPageProvider(new PageProvider());
And CurlView extends from GLSurfaceView and implements View.OnTouchListener, CurlRenderer.Observer
But in muPDF if i'm not mistaken, data are in core object. core is instance of MuPDFCore. And using it like this:
MuPDFReaderView mDocView;
MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView();
mDocView.setAdapter(new MuPDFPageAdapter(this, this, core));
MuPDFReaderView extends ReaderView and ReaderView extends AdapterView<Adapter> and implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener, Runnable.
My question is where how can I using curl effect in muPDF? Where should I get pages one by one and converting them to bitmaps? and then changing aspects of the Adapter in muPDF to CurlView.
In flip-flap sample project, in FlipHorizontalLayoutActivity (I like this effect too), we have these:
private FlipViewController flipView;
flipView = new FlipViewController(this, FlipViewController.HORIZONTAL);
flipView.setAdapter(new TravelAdapter(this));
setContentView(flipView);
And FlipViewController extends AdapterView<Adapter>, and data set in TravelAdapter that extends BaseAdapter.
No one has done this before? Or can help me to do that?!
EDIT:
I found another good open source PDF reader with curl effect called fbreaderJ. its developer says "An additional module that allows to open PDF files in FBReader. Based on radaee pdf library."
I got confused! cause radaeepdf is closed source and downloadable project is just for demo and inserted username and password is for this package.
People want to change whole fbreader project such as package name.
Another issue for make me confused is where is this additional module source code?!
Anyway, if someone wants to help me, fbreader has done it very well.
EDIT:
I talked to Robin Watts, who developed muPDF (or one of developers), and he said:
Have you read platform/android/ClassStructure.txt ? MuPDF is
primarily a C library. The standard api is therefore a C one. Rather
than exposing that api exactly as is to Java (which would be the
nicest solution, and something that I've done some work on, but have
not completed due to lack of time), we've implemented MuPDFCore to
wrap up just the bits we needed. MuPDFCore handles opening a PDF file,
and getting bitmaps from it to be used in views. or rather, MuPDFCore
returns 'views', not 'bitmaps'. If you need bitmaps, then you're going
to need to make changes in MuPDFCore.
There are too many errors when changing a little part of MuPDFReaderView class. I get confused! These are related to each other.
Please answer more precisely.
EDIT:
And bounty has expired.
If the muPDF does not support rendering to a bitmap, you have no other choice than rendering to a regular view and take a screen dump to a bitmap like this:
View content = findViewById(R.id.yourPdfView);
Bitmap bitmap = content.getDrawingCache();
Then use this bitmap as input to your other library.
Where should i get pages one by one and converting them to bitmaps?
In our application (newspaper app) we use MuPDF to render PDFs.
The workflow goes like this:
Download PDF file (we have one PDF per newspaper page)
Render it with MuPDF
Save the bitmap to the filesystem
Load the Bitmap from filesystem as background image to a view
So, finally, what we use is MuPDFCore.java and its methods drawPage(...) and onDestroy()
Is this what you want to know or do i miss the point?
EDIT
1.) I think it is not necessary to post code how to download a file. But after downloading i add a RenderTask (extends from Runnable) to a Renderqueue and trigger that queue. The RenderTask needs some information for rendering:
/**
* constructs a new RenderTask instance
* #param context: you need Context for MuPdfCore instance
* #param pageNumber
* #param pathToPdf
* #param renderCallback: callback to set bitmap to the view after
* rendering
* #param heightOfRenderedBitmap: this is the target height
* #param widthOfRenderedBitmap: this is the target width
*/
public RenderTask (Context context, Integer pageNumber, String pathToPdf, IRenderCallback,
renderCallback, int heightOfRenderedBitmap,
int widthOfRenderedBitmap) {
//store things in fields
}
2.) + 3.) The Renderqueue wraps the RenderTask in a new Thread and starts it. So the run-method of the RenderTask will be invoked:
#Override
public void run () {
//do not render it if file exists
if (exists () == true) {
finish();
return;
}
Bitmap bitmap = render();
//if something went wrong, we can't store the bitmap
if (bitmap == null) {
finish();
return;
}
//now save the bitmap
// in my case i save the destination path in a String field
imagePath = save(bitmap, new File("path/to/your/destination/folder/" + pageNumber + ".jpg"));
bitmap.recycle();
finish();
}
/**
* let's trigger the callback
*/
private void finish () {
if (renderCallback != null) {
// i send the whole Rendertask to callback
// maybe in your case it is enough to send the pageNumber or path to
// renderend bitmap
renderCallback.finished(this);
}
}
/**
* renders a bitmap
* #return
*/
private Bitmap render() {
MuPDFCore core = null;
try {
core = new MuPDFCore(context, pathToPdf);
} catch (Exception e) {
return null;
}
Bitmap bm = Bitmap.createBitmap(widthOfRenderedBitmap, heightOfRenderedBitmap, Config.ARGB_8888);
// here you render the WHOLE pdf cause patch-x/-y == 0
core.drawPage(bm, 0, widthOfRenderedBitmap, heightOfRenderedBitmap, 0, 0, widthOfRenderedBitmap, heightOfRenderedBitmap, core.new Cookie());
core.onDestroy();
core = null;
return bm;
}
/**
* saves bitmap to filesystem
* #param bitmap
* #param image
* #return
*/
private String save(Bitmap bitmap, File image) {
FileOutputStream out = null;
try {
out = new FileOutputStream(image.getAbsolutePath());
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
return image.getAbsolutePath();
} catch (Exception e) {
return null;
}
finally {
try {
if (out != null) {
out.close();
}
} catch(Throwable ignore) {}
}
}
}
4.) I think it is not necessary to post code how to set a bitmap as background of a view
I am using the open source library: https://code.google.com/p/flickrj-android/ and there is an example how do I get photos from flickr. Main problem is that I get only public photos. How can I manage getting private streams/photos?
Did anyone managed to get private streams?
With Flickrj-android you'd want to use this method:
Flickr flickr = new Flickr(API_KEY,SHARED_SECRET,new REST());
Set<String> extras = new HashSet();
// A set of extra info we want Flickr to give back. Go to the API page to see the other size options available.
extras.add("url_o");
extras.add("original_format");
//A request for a list of the photos in a set. The first zero is the privacy filter,
// the second is the Pages, and the third is the Per-Page (see the Flickr API)
PhotoList<Photo> photoList = flickr.getPhotosetsInterface().getPhotos(PHOTOSET_ID, extras, 0, 0, 0);
//We'll use the direct URL to the original size of the photo in order to download it. Remember: you want to make as few requests from flickr as possible!
for(Photo photo : photoList){
//You can also get other sizes. Just ask for the info in the first request.
URL url = new URL(photo.getOriginalSize().getSource());
InputStream is = url.openStream();
OutputStream os = new FileOutputStream(PATH_OF_FOLDER + photo.getTitle() + "." + photo.getOriginalFormat());
byte[] b = new byte[2048];
int length;
while ((length = is.read(b)) != -1) {
os.write(b, 0, length);
}
is.close();
os.close();
}
Use this method for a single-photo inputstream.
InputStream inputStream = flickr.getPhotosInterface().getImageAsStream(flickr.getPhotosInterface().getPhoto(PHOTO_ID), Size.ORIGINAL);
I'm not very familiar with Java and that framework but will try to help.
I found next method name in that framework:
public class PeopleInterface {
public static final String METHOD_GET_PHOTOS = "flickr.people.getPhotos";
/**
* Returns photos from the given user's photostream. Only photos visible the
* calling user will be returned. this method must be authenticated.
*
* #param userId
* #param extras
* #param perpage
* #param page
* #return
* #throws IOException
* #throws FlickrException
* #throws JSONException
*/
public PhotoList getPhotos(String userId, Set<String> extras, int perPage,
int page)
From Flick API docs I found next:
flickr.people.getPhotos Return photos from the given user's photostream. Only photos visible to the calling user will be returned.
This method must be authenticated; to return public photos for a user,
use flickr.people.getPublicPhotos.
So, it mean that you must be authenticated with 'read' permissions to get your private pohotos (your account).
You can also get private photos of some users only in case if you are contact/friend of that user.