Android - Рисование в PDF-холст из WebView

У меня возникли проблемы с работой PDF-печати на Android. То, что я пытаюсь сделать, это отобразить некоторый HTML в WebView, затем нарисовать содержимое WebView на холсте PDF и, наконец, записать PDF в файл. Проблема, с которой я сталкиваюсь, заключается в том, что когда я рисую на холст PDF, содержимое обрезается, хотя остается много холста. Я попробовал изменить размер холста с помощью .clipRect(Rect rect, Op op) и такого рода работы, но не так хорошо, как хотелось бы.

Я также не знаю, как я могу надежно перевести измерения HTML-изображений в PDF PostScript 1/72-дюймовые измерения.

Вот код, который я использую:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    WebView wv = (WebView) this.findViewById(R.id.webView1);

    wv.loadUrl("file:///android_asset/temp.html");           
}

public void button1onClick(View v)
{
    //Create PDF document
    PdfDocument doc = new PdfDocument();

    //Create A4 sized PDF page
    PageInfo pageInfo = new PageInfo.Builder(595,842,1).create();

    Page page = doc.startPage(pageInfo);

    WebView wv = (WebView) this.findViewById(R.id.webView1);

    page.getCanvas().setDensity(200);

    //Draw the webview to the canvas
    wv.draw(page.getCanvas());

    doc.finishPage(page);

    try
    {
        //Create the PDF file
        File root = Environment.getExternalStorageDirectory();          
        File file = new File(root,"webview.pdf");
        FileOutputStream out = new FileOutputStream(file);
        doc.writeTo(out);
        out.close();
        doc.close();

        //Open the PDF
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.fromFile(file), "application/pdf");
        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
        startActivity(intent);          
    }
    catch(Exception e)
    {
        throw new RuntimeException("Error generating file", e);
    }
}

В основном программа просто загружает файл temp.html в webview и предоставляет мне кнопку, которую я могу использовать для создания PDF.

Файл temp.html выглядит так:

<html>
<head>
    <style>
        div.border
        {
            width:600px;
            height:800px;
            border:1px solid black;
        }
    </style>
</head>
<body>
    <div class="border"></div>
</body>

И вот результат с добавленной вручную черной рамкой, чтобы показать масштаб:

enter image description here

Я бы очень благодарен за советы о том, как надежно конвертировать HTML в PDF на Android без использования библиотек, требующих лицензии для коммерческого использования.

Ответы

Ответ 1

Резюме: Не изменяйте плотность (она должна быть установлена ​​на вашем устройстве, возможно, на 160 dpi), используйте масштаб. Если вам просто нужны растровые изображения вашей HTML-страницы в вашем PDF файле (без функции гиперссылки), это работает. Это то, что генерирует ваш код, со следующим кодом:

    //Create PDF document

        PdfDocument doc = new PdfDocument();

        //Create A4 sized PDF page
        int my_width  = 595;
        int my_height = 842;

        PageInfo pageInfo = new PageInfo.Builder(my_width,my_height,1).create();
//      PageInfo pageInfo = new PageInfo.Builder(650,850,1).create();

        Page page = doc.startPage(pageInfo);

        WebView wv = (WebView) this.findViewById(R.id.webView1);

        Canvas canvas = page.getCanvas();
        WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
        final DisplayMetrics displayMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(displayMetrics);
        int    height  = displayMetrics.heightPixels;
        int    width   = displayMetrics.widthPixels;
        float  density = displayMetrics.density;
        int    wvWidth = wv.getWidth();
        int    wvHeight= wv.getHeight();
        float  wvScaleX= wv.getScaleX();
        float  wvScaleY= wv.getScaleY();

//      canvas.setDensity(100);//200 Bitmap.DENSITY_NONE
        int cdensity = canvas.getDensity();
        float scaleWidth = (float)width/(float)my_width;
        float scaleHeight = (float)height/(float)my_height;
        canvas.scale(scaleWidth, scaleHeight);
        Log.e("button1onClick","canvas width:" + canvas.getHeight() + " canvas height:" +  canvas.getWidth());
        Log.e("button1onClick","metrics width:" + width + " metrics height:" +  height + "metrics density:" +  density);
        Log.e("button1onClick"," wvWidth:" + wvWidth + " wvHeight:" +  wvHeight);
        Log.e("button1onClick"," scaleWidth: " + scaleWidth +
                " scaleHeight:" +  scaleHeight +" cdensity:" + cdensity);
        Paint paint = new Paint();
//      paint.setStyle(Style.FILL);
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(1);

        //Draw the webview to the canvas
        wv.draw(canvas);
        canvas.scale(1f, 1f);
        canvas.drawRect(0, 0, canvas.getWidth()-1,  canvas.getHeight()-1, paint);
        canvas.drawText("Direct drawn Red Rectangle to fill page canvas 0, 0," +
                canvas.getWidth() + "," + canvas.getHeight(), 100, 100, paint);

        doc.finishPage(page);

webview.pdf

Это хорошо работает (гиперссылки не могут работать, конечно). Более сложный пример: более сложный пример

Ответ 2

В основном для меня все сводится к поддержке Hyper-Link и внешней .css(каскадные таблицы стилей) для (X) HTML в PDF (классы).

        div border is not supported on android in anyway I have found in free to use code.

div цвет да, ну и что. PdfDocument. (API 19 или выше). возможно, лучше lib itextg (API16, возможно, меньше) (подмножество itext, опуская рамки андроида без разрешенных классов). (использует класс XMLWorkerHelper) (div border == no), но td == yes yippi! (пограничный элемент поддерживается в формате pdf). Возможно, время для itextg. соответствия:  http://demo.itextsupport.com/xmlworker/itextdoc/CSS-conformance-list.htm             Довольно жалкий.          Продолжать...      Не уверен, что вы хотите,      если это просто граница, я могу сделать это на элементе td.      Наверняка вы хотите больше, чем это.          Продолжать...      Я могу сделать внешний .css файл, прочитанный с поддерживаемыми элементами (см. Ссылку), довольно круто.      (летающая тарелка... не летает для меня "JAVA Library" НЕ поддерживается андроидом).

Итак, такого рода вещи:

public boolean createPDF(String htmlText, String absoluteFilePath) throws DocumentException, CssResolverException
    {
        try
        {
            // step 1 new doc
            Document document = new Document();

            // step 2 create PdfWriter

            PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(absoluteFilePath));

            writer.setInitialLeading(12.5f);

            // step 3 open doc
            document.open();
            document.add(new Chunk("")); //

            HtmlPipelineContext htmlContext = new HtmlPipelineContext(null);

            htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
            CSSResolver cssResolver = null;
        if(true)
        {
            // step 4 CSS
            cssResolver = new StyleAttrCSSResolver();
            java.io.InputStream csspathtest = null;
            try {
                csspathtest =  getResources().getAssets().open("itextweb.css");
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            CssFile cssfiletest = XMLWorkerHelper.getCSS(csspathtest);
            cssResolver.addCss(cssfiletest);  
            Log.i("cssfiletest",cssfiletest.toString());
            }
        else
        {
            cssResolver = XMLWorkerHelper.getInstance().getDefaultCssResolver(false);   
            cssResolver.addCss("td {border-right: white .1px solid;}", true);
            cssResolver.addCss("div {border: green 2px solid;}", true);
        }           

            Pipeline<?> pipeline =  new CssResolverPipeline(cssResolver, new HtmlPipeline(htmlContext, new PdfWriterPipeline(document, writer)));

            XMLWorker worker1 = new XMLWorker(pipeline, true);
            XMLParser p = new XMLParser(worker1);
            ByteArrayInputStream inputRawHTML = new ByteArrayInputStream(htmlText.getBytes());

            Tidy tidy = new Tidy(); // obtain a new Tidy instance
            tidy.setXHTML(true); // set desired config options using tidy setters
            ByteArrayOutputStream output = new ByteArrayOutputStream();
//          tidy.setCharEncoding(Configuration.UTF8);
            tidy.parse(inputRawHTML, output);
            String preparedText = output.toString("UTF-8");

            Log.i("CHECKING", "JTidy Out: " + preparedText);

            ByteArrayInputStream inputPREP = new ByteArrayInputStream(preparedText.getBytes());

            // step 5 parse html
            p.parse(inputPREP); 

Итак, некоторые изображения:

для HTML:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html >
<head>
    <title>My first web page</title>

    <LINK REL=StyleSheet HREF="itextweb.css" TYPE="text/css" MEDIA=screen>
</head>
<body>
<!-- The <div> tag enables you to group sections of HTML elements together and format them with CSS.-->
    <div>
    <p>helloworld with green border if style worked</p>
    </div>

        <div>
        <h1>helloworld with green border if style worked</h1>
    </div>
<div style="border: 3px yellow solid">
<p>"SHOULD be red text if p style worked, else yellow border from div style" </p>
other text div yellow inline border
</div>
<div style="color: red">red text if div style worked</div>


    <h2>unsorted list</h2>
    <ul>
        <li>To learn HTML</li>
        <li>To show off</li>
    </ul>
<table>
    <tr>
        <td>Row 1, cell 1</td>
        <td>Row 1, cell 2</td>
        <td>Row 1, cell 3</td>
    </tr>
</table>
<textarea rows="5" cols="20">A big load of text</textarea>
<a href="http://www.htmldog.com">blue HTML Dog link</a>
</body>
</html>

input html в браузере output pdf file > Элементы td имеют границы!

Ответ 3

Вот где это становится интересным. Как насчет этих жестко закодированных значений для холста?: -

 PageInfo pageInfo = new PageInfo.Builder(595,842,1).create();

Вам нужна ширина и высота WebView СОДЕРЖАНИЕ, после. ДА, но нет метода getContentWidth (только значение порта просмотра), AND getContentHeight() неточно!

Ответ: подкласс WebView:

/*
  Jon Goodwin
*/
package com.example.html2pdf;//your package

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.webkit.WebView;

class CustomWebView extends WebView
{
    public int rawContentWidth   = 0;                         //unneeded
    public int rawContentHeight  = 0;                         //unneeded
    Context    mContext          = null;                      //unneeded

    public CustomWebView(Context context)                     //unused constructor
    {
        super(context);
        mContext = this.getContext();
    }   

    public CustomWebView(Context context, AttributeSet attrs) //inflate constructor
    {
        super(context,attrs);
        mContext = context;
    }

    public int getContentWidth()
    {
        int ret = super.computeHorizontalScrollRange();//working after load of page
        rawContentWidth = ret;
        return ret;
    }

    public int getContentHeight()
    {
        int ret = super.computeVerticalScrollRange(); //working after load of page
        rawContentHeight = ret;
        return ret;
    }

    public void onPageFinished(WebView page, String url)
    {
        //never gets called, don't know why, but getContentHeight & getContentWidth function after load of page
        rawContentWidth  =  ((CustomWebView) page).getContentWidth();
        rawContentHeight =  ((CustomWebView) page).getContentHeight();

        Log.e("CustomWebView:onPageFinished","ContentWidth: " + ((CustomWebView) page).getContentWidth());
        Log.e("CustomWebView:onPageFinished","ContentHeight: " + ((CustomWebView) page).getContentHeight());
    }

//=========
}//class
//=========

В моем измененном коде (в другом ответе) измените:

private CustomWebView wv;
    wv = (CustomWebView) this.findViewById(R.id.webView1);

    int my_width  = wv.getContentWidth();
    int my_height = wv.getContentHeight();

и измените запись класса макета из WebView на com.example.html2pdf.CustomWebView.

тогда вам хорошо идти!

Ответ 4

У меня была та же проблема.

Я решил это, используя очень простой трюк.

Просто установите MediaSize в PrintAttributes.MediaSize.ISO_A1.

Недостатком этого решения является размер pdf: даже одностраничный-pdf с простым текстом имеет примерно 5 МБ.

Рабочий фрагмент кода (создает PDF файл из представления и экспортирует его в файл):

@TargetApi(19)
private void generatePdf() {
    PrintAttributes.Builder builder = new PrintAttributes.Builder();
    builder.setColorMode(PrintAttributes.COLOR_MODE_COLOR);
    builder.setMediaSize(PrintAttributes.MediaSize.ISO_A1); // or ISO_A0
    builder.setMinMargins(PrintAttributes.Margins.NO_MARGINS);
    builder.setResolution(new PrintAttributes.Resolution("1", "label", 300, 300));
    PrintedPdfDocument document = new PrintedPdfDocument(this, builder.build());
    PdfDocument.Page page = document.startPage(1);
    View content = yourView;
    content.draw(page.getCanvas());
    document.finishPage(page);
    try {
        File file = new File(getExternalFilesDir(null).getAbsolutePath(), "document.pdf");
        document.writeTo(new FileOutputStream(file));
    } catch (IOException e) {
        Log.e("cannot generate pdf", e);
    }
    document.close();
}