【问题标题】:Android - Drawing to a PDF canvas from WebViewAndroid - 从 WebView 绘制到 PDF 画布
【发布时间】:2015-01-05 20:14:46
【问题描述】:

我一直无法在 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>

这是手动添加黑色边框以显示比例的结果:

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

【问题讨论】:

标签: android pdf webview android-canvas


【解决方案1】:

我遇到了同样的问题。

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

只需MediaSize 设置为PrintAttributes.MediaSize.ISO_A1

此解决方案的缺点是 pdf 大小:即使是带有简单文本的一页 pdf 也大约有 5MB。

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

【讨论】:

    【解决方案2】:

    这就是有趣的地方。 画布的那些硬编码值呢?:-

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

    在加载 HTML 之后,您需要 WebView CONTENTSwidthheight。 是的,但没有 getContentWidth 方法(只有视口值),并且 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】:

      总结: 不要修改密度,(应该在您的设备上设置,可能设置为中等 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);
      

      这很好用(当然超链接不能工作)。 更复杂的例子:

      【讨论】:

        【解决方案4】:

        基本上对我来说,这一切都归结为 (X)HTML 到 PDF(类)的超链接和外部 .css 支持(级联样式表)。

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

        div 颜色是的,那又怎样。 Pdf 文档。 (API 19 或更高版本)。 也许是一个更好的 lib itextg(API16 可能更少)(itext 子集省略了 android 框架不允许的类)。 (使用 XMLWorkerHelper 类) (div 边框 == 否)但 td == 是 yippi ! (pdf中支持的边框元素)。 itextg的时间,也许。 一致性: http://demo.itextsupport.com/xmlworker/itextdoc/CSS-conformance-list.htm 挺可怜的。 继续... 不知道你想要什么, 如果它只是一个边框,我可以在 td 元素上做到这一点。 很确定你想要的不止这些。 继续... 我可以使用支持的元素读取外部 .css 文件(请参阅链接),非常酷。 (飞碟……不适合我“JAVA Library”不支持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>
        

        > 注意 td 元素有边框!

        【讨论】:

        • 如果这是您想要的,请给我发消息了解更多详细信息,否则祝您好运,难题(不支持,到目前为止我发现的任何内容)您的 DIV 和边框非常具体。
        猜你喜欢
        • 2012-02-02
        • 2019-12-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-08
        • 1970-01-01
        • 2015-06-22
        相关资源
        最近更新 更多