Android - 从WebView绘制到PDF画布

时间:2014-11-09 12:46:16

标签: android pdf webview android-canvas

我在Android上进行PDF打印时遇到了麻烦。我要做的是在WebView中渲染一些HTML,然后在PDF画布上绘制WebView内容,最后将PDF写入文件。我遇到的问题是,当我绘制到PDF画布时,即使有足够的画布,内容也会被剪裁。我已经尝试使用.clipRect(Rect rect, Op op)来调整画布大小,这种方式有效,但不如我喜欢的那样好。

我也不知道如何将HTML px测量值可靠地转换为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

我非常感谢有关如何在Android上可靠地将HTML转换为PDF的一些提示,而不使用需要商业用途许可的库。

4 个答案:

答案 0 :(得分:3)

要点: 不要修改密度(应该在您的设备上设置,可能是中等160 dpi),而是使用比例尺。 如果你只需要PDF中的HTML页面的位图(没有超链接功能),这是有效的。 这是您的代码生成的代码,使用以下代码:

    //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

这很有效(超链接当然不能工作)。 更复杂的例子: more complex example

答案 1 :(得分:1)

  

基本上对我来说,这一切都归结为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子集省略android框架不允许的类)。 (使用XMLWorkerHelper类)   (div border == no)但是td ==是的yippi! (pdf支持的边框元素)。   也许是itextg的时间。   一致性:    http://demo.itextsupport.com/xmlworker/itextdoc/CSS-conformance-list.htm                很可怜。            继续...        不确定你想要什么,        如果它只是一个边界我可以在td元素上做到这一点。        很确定你想要更多。            继续...        我可以使用支持的元素进行外部.css文件读取(参见链接),非常酷。        (飞碟......不适合我和#34; JAVA图书馆&#34;不支持Android)。

所以这样的事情:

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 in browser output pdf file&gt; 注意td元素有边框!

答案 2 :(得分:1)

这是有趣的地方。 那些画布的硬编码值怎么样?: -

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

您需要宽度高度的WebView 目录 加载HTML后。 是的,但是没有 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。

然后你好好去!

答案 3 :(得分:0)

我遇到了同样的问题。

我用非常简单的技巧解决了它。

MediaSize设为PrintAttributes.MediaSize.ISO_A1

这个解决方案的缺点是pdf大小:即使是一页PDF格式的文本也只有5MB左右。

工作代码段 (从视图生成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();
}