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
Related
I am trying to make a giant PDF that will contain all information on one page as there can be no breaks between the information in the document. it probably wont ever be printed so the size of the PDF is not an issue. Using Itext the only way I have found possible is to create a page that is 14400px long "or 5M in A4 pages, but this leaves a trailing white space if the document is shorter than expected (I dont ever see the document being longer than 14400px) this is my code so far
private void pdfSave() {
float pageWidth = 200f;
float pageHeight = 1440f;
Rectangle pageSize = new Rectangle(pageWidth, pageHeight);
Document mDoc =new Document(pageSize);
String mFileName = new SimpleDateFormat("ddMMyyyy_HHmmss",
Locale.getDefault()).format(System.currentTimeMillis());
String mFilePath = Environment.getExternalStorageDirectory()+"/"+"pdf_viewer"+"/"+mFileName+".pdf";
File dir = new File(mFilePath);
if(!dir.exists()){
dir.getParentFile().mkdir();
}
try{
PdfWriter.getInstance(mDoc, new FileOutputStream(mFilePath));
mDoc.setMargins(10,10,10,10);
mDoc.open();
String mText = mTextEt.getText().toString();
mDoc.add(new Paragraph(mText,FontFactory.getFont(FontFactory.HELVETICA, 4, Font.BOLDITALIC)));
mDoc.close();
}
Edit: I have tried using a crop box and a second pass as stated in a comment, but my app crashes on this line if I debugging it
Rectangle rect = getOutputPageSize(pageSize, reader, i);
I created a plugin using Picasso and it uses the android.widget.ImageView to load the cached image into.
The plugin works fine if using a Repeater but whenever i try using it with a ListView after scrolling past about the 7th item the ListView begins to reuse old images even if the image source is different
The reason why is because list views reuse the entire fragment; so what happens is that your img being reused gets the old image shown unless you clear it.
I actually use Picasso myself; and this is my current picasso library.
So if you look in my code below, when I set the new .url, I clear the existing image. (I made a comment on the specific line) -- This way the image now show blank, and then picasso loads it from either memory, disk or a remote url (in my case a remote url) and it will assign the proper image.
"use strict";
var Img = require('ui/image').Image;
var application = require("application");
var PT = com.squareup.picasso.Target.extend("Target",{
_owner: null,
_url: null,
onBitmapLoaded: function(bitmap, from) {
// Since the actual image / target is cached; it is possible that the
// target will not match so we don't replace the image already seen
if (this._url !== this._owner._url) {
return;
}
this._owner.src = bitmap;
},
onBitmapFailed: function(ed) {
console.log("Failed File", this._url);
},
onPrepareLoad: function(ed) {
}
});
Object.defineProperty(Img.prototype, "url", {
get: function () {
return this._url;
},
set: function(src) {
if (src == null || src === "") {
this._url = "";
this.src = null;
return;
}
var dest = src;
this._url = dest;
this.src = null; // -- THIS IS THE LINE TO CLEAR THE IMAGE
try {
var target = new PT();
target._owner = this;
target._url = dest;
var x = com.squareup.picasso.Picasso.with(application.android.context).load(dest).into(target);
} catch (e) {
console.log("Exception",e);
}
},
enumerable: true,
configurable: true
});
Please note you only need to require this class once, then it attaches itself to the <Image> component and adds the new .url property; this allows me to use this in the Declarative XML in all the rest of the screens and when I need picasso, I just use the .url property to have picasso take over the loading of that image.
I need to load Bitmaps into ArrayList, then convert it to Bitmap[] and pass into ArrayAdapter to inflate ListView. I use UniversalImageLoader library and here is my code:
final ArrayList<Bitmap> imgArray = new ArrayList<>(); //before the method scope, as a class field
//...some code...
File cacheDir = StorageUtils.getOwnCacheDirectory(
getApplicationContext(),
"/sdcard/Android/data/random_folder_for_cache");
DisplayImageOptions options = new DisplayImageOptions.Builder()
.cacheInMemory(true).cacheOnDisc(true).build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(
getApplicationContext()).defaultDisplayImageOptions(options)
//.discCache(new FileCounterLimitedCache(cacheDir, 100)) - I commented it 'cause FileCounterLimitedCache isn't recognized for some reason
.build();
ImageLoader.getInstance().init(config);
for (int num=0;num<4;num++) {
ImageLoader imageLoader = ImageLoader.getInstance();
final int constNum = num;
imageLoader.loadImage("http://example.com/sample.jpg", new SimpleImageLoadingListener()
{
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage)
{
imgArray.add(constNum, loadedImage);
}
});
}
But I have some issues to struggle. Firstly, it often returns error (IndexOutOfBoundsException - current index exceeds size of ArrsyList) when first run. Then after some time (about a minute) the size of ArrayList is 1 (I check it with Toast), and then when run again right at once, it's already 4 (as it needs to be). Strange. But the main thing I need that the ArrayList is first filled and then all the other actions are done (that they be delayed and I don't have errors on the first run). How to do it?
And what to do if somebody doesn't have SD card? Btw, I couldn't find the created cache folder on my SD...
You can't add the elements in the way you're trying to. If for example the 2nd image finishes loading first, your code will correctly throw an IndexOutOfBoundsException as the location you're trying to add at is beyond the current size - see the documentation
You might be better off using an array initialised to the number of elements seeing as you know it's 4 elements - e.g.
final Bitmap[] imgArray = new Bitmap[4];
Then add the elements in your onLoadingComplete() using
imgArray[constNum] = loadedImage;
You can use a SparseArray instead of an ArrayList to avoid the IndexOutOfBoundsException.
I assume this is what you are after:
final SparseArray<Bitmap> imgArray = new SparseArray<>(); //before the method scope, as a class field
int numberOfImages;
int numberOfLoadedImages;
//...some code...
File cacheDir = StorageUtils.getOwnCacheDirectory(
getApplicationContext(),
"/sdcard/Android/data/random_folder_for_cache");
DisplayImageOptions options = new DisplayImageOptions.Builder()
.cacheInMemory(true).cacheOnDisc(true).build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(
getApplicationContext()).defaultDisplayImageOptions(options)
//.discCache(new FileCounterLimitedCache(cacheDir, 100)) - I commented it 'cause FileCounterLimitedCache isn't recognized for some reason
.build();
ImageLoader.getInstance().init(config);
numberOfImages = 4;
numberOfLoadedImages = 0;
for (int num=0;num<4;num++) {
ImageLoader imageLoader = ImageLoader.getInstance();
final int constNum = num;
imageLoader.loadImage("http://example.com/sample.jpg", new SimpleImageLoadingListener()
{
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage)
{
imgArray.put(constNum, loadedImage);
numberOfLoadedImages++;
if(numberOfImages == numberOfLoadedImages)
{
//Do all the other actions
}
}
});
}
Universal Image Loader library contains the following method:
public void displayImage(java.lang.String uri, android.widget.ImageView imageView)
Which can be passes the image url and the ImageView to display the image on it.
plus to that, if later the same ImageView passed to the method but with a different url two thing can happen:
if old image already downloaded, normally the new image will be set to the ImageView.
if old image still downloading the library will cancel the http connection that is downloading the old image. and start downloading
the new image. (can be observed in the LogCat). this behaviour happens
when using an adapter.
Method call:
ImageLoader.getInstance().displayImage("http://hydra-media.cursecdn.com/dota2.gamepedia.com/b/bd/Earthshaker.png", imageView1);
Configuration:
Caching will not work if the defaultDisplayImageOptions not specified. which tells the library where to save those image. because this library has options to load images from Assets, Drawables or Internet:
DisplayImageOptions opts = new DisplayImageOptions.Builder().cacheInMemory(true).cacheOnDisk(true).build();
With this option the images will be saved in app. internal memory.
don't worry weather the device have an external memory or not.
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this)
.defaultDisplayImageOptions(opts)
.memoryCache(new LruMemoryCache(2 * 1024 * 1024))
.memoryCacheSize(2 * 1024 * 1024)
.diskCacheSize(50 * 1024 * 1024)
.diskCacheFileCount(100)
.writeDebugLogs()
.build();
ImageLoader.getInstance().init(config);
I created a working github repository for it. check it out.
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 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();
}