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>
> Элементы 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();
}