我正在开发一个应用程序(Android 4.4 - API 20),我正在生成HTML格式的报告。我使用 WebView 对象在我的应用中显示报表。
我希望能够做的是将此WebView转换为pdf文档。
我已经能够使用PdfDocument转换它,并从WebView对象对页面进行.draw。我保存了文件,这有效,除了结果是单页文档。没有分页符。
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);
如果我更改它以便我使用PrintedPdfDocumet并且不指定PageInfo,我只会获得WebView对象的可查看部分。
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);
如果我使用 PrintManager 并使用 createPrintDocumentAdapter 从 WebView 对象创建打印适配器,则可以选择"另存为 PDF"选项,生成的 pdf 文件具有我在原始网页的 CSS 中指定的分页符。
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);
我的问题是:我可以指定我希望PrintManager执行"另存为PDF"并提供结果文件的名称和位置,以便不与用户交互吗?
或者:有没有办法将我的 WebView 对象转换为 PDF 并允许分页符。
这可能是一个迟到的答案,但到目前为止,我也需要使用打印框架的类似解决方案,并且我使用以下代码将 Pdf 文档拆分为页面。
据我所知,您无法真正使WebView或Pdf文档以智能方式将pdf文件拆分为页面(而不是剪切文本或图像)。但是我们可以做的是以A4或Letter大小的比例创建页面,因此它可以适合打印出的纸张格式。
但我面临着另一个问题。下面的代码在 Android 4.4 中按预期工作,但在更高版本中则不然。在 Android-L 中,只有 WebView 的可见部分被绘制到 Pdf 文件中,但 WebView 中其余 HTML 的空白页被绘制出来。
根据文件,
我已经创建了一个错误报告,public static void enableSlowWholeDocumentDraw ()
对于面向 L 版本的应用,WebView 具有新的默认行为,通过智能选择需要绘制的 HTML 文档部分来减少内存占用并提高性能。这些优化对开发人员是透明的。但是,在某些情况下,应用开发人员可能希望禁用它们:
- 当应用程序使用 onDraw(Canvas) 进行自己的绘图并访问页面中超出页面可见部分的部分时。
- 当应用程序使用 capturePicture() 捕获非常大的 HTML 文档时。请注意,capturePicture 是一个已弃用的 API。
启用绘制整个 HTML 文档会显著降低性能成本。应在创建任何 Web 视图之前调用此方法。
并在此处评论了类似的错误报告,但到目前为止还没有回复。但在此之前,您可以使用下面的代码。
/**
* 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);
}
不确定这是否能解决您的分页问题,但您是否考虑过使用开源的 wkHTMLtoPDF 库 (http://wkhtmltopdf.org/) 从 HTML 转换为 PDF?我们通过创建一个微服务来广泛使用它,我们将HTML代码传递给它,然后让服务将其转换为PDF并返回PDF的链接,或者让它返回PDF(取决于大小)。我知道使用外部服务进行转换可能会很痛苦(或者您可能无法从设备访问互联网),但如果这不是问题,那么这可能是一种选择。可能还有其他 API 可用于执行此转换。一个这样的API是Neutrino API。还有许多其他 - 您可以使用以下 API 搜索引擎之一搜索 API:
- apis.io
- 可编程网络
- 公共接口
在花了大量的时间解决这个问题之后,我使用 DexMaker 来实现非公共抽象回调,并提出了这个:
@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();
}