Android 4.4在没有用户参与的情况下打印到PDF

时间:2014-11-13 22:01:56

标签: android pdf printing webview

我正在开发一个应用程序(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并允许分页。

3 个答案:

答案 0 :(得分:8)

这可能是一个迟到的答案,但到目前为止我还需要与Print Framework类似的解决方案,并且我将Pdf文档拆分成下面代码的页面。

据我所见,你无法真正让WebView或Pdf文档以一种聪明的方式将你的pdf文件分割成页面(而不是剪切文本或图像)。但我们可以做的是以A4或Letter尺寸的比例创建Pages,因此它可以适合打印出来的纸张格式。

但我还面临另一个问题。下面的代码在Android 4.4中按预期工作,但在以后的版本中没有。在Android-L中,只有WebView的可见部分被绘制到Pdf文件中,但是WebView中其余HTML的空白页面都是白色的。

根据documentation

  

public static void enableSlowWholeDocumentDraw()

     

对于针对L版本的应用,WebView有一个新的默认行为,可以通过智能选择需要绘制的HTML文档部分来减少内存占用并提高性能。这些优化对开发人员来说是透明的。但是,在某些情况下,App开发人员可能希望禁用它们:

     
      
  • 当应用使用onDraw(Canvas)进行自己的绘图并访问页面中可见部分之外的部分时。
  •   
  • 当应用使用capturePicture()捕获非常大的HTML文档时。请注意,capturePicture是不推荐使用的API。
  •   
     

启用绘制整个HTML文档会产生显着的性能成本。应在创建任何WebView之前调用此方法。

我已经创建了Bug Report,并对类似的错误报告HERE发表了评论,但到目前为止还没有回复。但在此之前,您可以使用以下代码。

/**
 * 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);
}

答案 1 :(得分:2)

不确定这是否会解决您的分页问题,​​但是您是否考虑过使用开源wkHTMLtoPDF库(http://wkhtmltopdf.org/)进行从HTML到PDF的转换?我们通过创建一个我们传递HTML代码的微服务来广泛使用它,然后让服务将其转换为PDF并将链接返回到PDF,或者让它返回PDF(取决于大小)。我知道使用外部服务进行转换可能会很痛苦(或者您可能没有从设备访问互联网),但如果这不是问题,那么这可能是一个选项。可能还有其他API可用于执行此转换。一个这样的API是Neutrino API。还有很多其他的 - 您可以使用以下API搜索引擎之一搜索API:

  1. apis.io
  2. Progammable Web
  3. Public APIs

答案 2 :(得分:1)

在花费大量时间解决这个问题之后,我使用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();
}