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

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

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

Введите описание изображения здесь

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

Solutions Collecting From Web of "Android – рисование в PDF-холсте из WebView"

Резюме. Не изменяйте плотность (она должна быть установлена ​​на вашем устройстве, возможно, на среднем 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

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

В основном для меня все сводится к поддержке 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 в браузере Выходной файл pdf > Обратите внимание, что элементы td имеют границы!

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

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

Вам нужна ширина и высота WebView CONTENTS после загрузки вашего HTML. ДА, но нет метода 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.

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

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

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

Просто установите 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(); }