Подтвердить что ты не робот

Android 4.4 Печать в PDF без участия пользователя

Я работаю над приложением (Android 4.4 - API 20), где я создаю отчет в формате HTML. Я использую объект WebView для отображения отчета в своем приложении.

То, что я хотел бы сделать, - это преобразовать этот WebView в pdf-документ.

Я смог преобразовать его с помощью PdfDocument и сделать .draw на странице из объекта 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();
        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 и создаю адаптер печати из объекта WebView с помощью createPrintDocumentAdapter, я могу выбрать опцию "Сохранить как 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 и разрешить разрывы страниц.

4b9b3361

Ответ 1

Возможно, это был поздний ответ, но до сих пор мне также нужно было найти решение с Print Framework, и я разделил документ Pdf на страницы с приведенным ниже кодом.

Насколько я могу судить, вы не можете заставить WebView или Pdf Document разделить ваш pdf файл на страницы в интеллектуальном ключе (не сокращая текст или изображение). Но мы можем создать страницы в формате A4 или Letter, чтобы он мог вписываться в формат бумаги для печати. ​​

Но есть еще одна проблема, с которой я сталкиваюсь. Следующий код работает как ожидается в Android 4.4, но не в более поздних версиях. В Android-L только видимая часть WebView втягивается в файл Pdf, но белые пустые страницы для остальной части HTML в WebView.

Согласно документации,

public static void enableSlowWholeDocumentDraw()

Для приложений, ориентированных на выпуск L, WebView имеет новое поведение по умолчанию, которое уменьшает объем памяти и увеличивает производительность, разумно выбирая часть HTML-документа, который нужно нарисовать. Эти оптимизации прозрачны для разработчиков. Однако при определенных обстоятельствах разработчик приложения может отключить их:

  • Когда приложение использует onDraw (Canvas) для создания собственного чертежа и доступа к частям страницы, которые находятся вне видимой части страницы.
  • Когда приложение использует capturePicture() для захвата очень большого HTML-документа. Обратите внимание, что capturePicture - устаревший API.

Включение чертежа всего HTML-документа имеет значительную производительность. Этот метод следует вызывать до создания любых WebView.

Я создал Отчет об ошибках и прокомментировал аналогичный отчет об ошибке ЗДЕСЬ, но нет ответ до сих пор. Но до тех пор вы можете использовать код ниже.

/**
 * 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 Height depending on the LetterSize Dimensions and Given width.
 *
 * @param width
 * @return
 */
private int getLetterSizeHeight(int width) {
    return (int)((float)(11*width)/8.5);
}

Ответ 2

Не уверен, что это решит проблемы с разрывом страницы, но рассмотрели ли вы использование библиотеки wkHTMLtoPDF с открытым исходным кодом (http://wkhtmltopdf.org/) для преобразования из HTML в PDF? Мы использовали его широко, создав микросервис, которому мы передаем HTML-код, а затем передадим его в PDF файл и вернем ссылку на PDF файл или, наоборот, вернем PDF файл (в зависимости от размера). Я знаю, что использование внешней службы для преобразования может быть болью (или, возможно, у вас нет доступа к интернету с устройства), но если это не проблема, то это может быть вариант. Для этого можно использовать и другие API. Одним из таких API является Neutrino API. Есть много других - вы можете искать API-интерфейсы, используя одну из этих поисковых машин API:

Ответ 3

У меня была такая же проблема, и я решил ее вручную пережить жизненный цикл PrintDocumentAdapter (onStart, onLayout, onWrite и onFinish). Это немного сложно, поскольку вам нужны части скрытого Android API.

Я планирую создать библиотеку этого, но это займет несколько недель, пока у меня не будет достаточно времени. Я буду обновлять свой ответ, когда библиотека будет завершена.

Ответ 4

После огромного времени с этой проблемой я использовал 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();
}