Как сделать нумерацию страниц в htmls верхнего / нижнего колонтитула с помощью wkhtmltopdf?
Я занимаюсь разработкой электронной системы выставления счетов, и одной из наших функций является создание PDF-документов счетов и их отправка по почте. У нас есть несколько шаблонов для счетов-фактур, и мы создадим их позже, поэтому мы решили использовать шаблоны HTML, сгенерировать HTML-документ, а затем преобразовать его в PDF. Но мы столкнулись с проблемой с wkhtmltopdf, которая заключается в том, что, насколько я знаю (я уже несколько дней искал Google, чтобы найти решение), мы не можем просто использовать HTML в качестве верхнего / нижнего колонтитула и показывать номера страниц в них.
В отчете об ошибке (или подобном) ( http://code.google.com/p/wkhtmltopdf/issues/detail?id=140) я прочитал, что с помощью JavaScript это комбо достижимо. Но никакой другой информации о том, как это сделать, нет на этой странице или где-либо еще.
Конечно, не так важно форсировать использование JavaScript, если с wkhtmltopdf может работать какая-то магия CSS, это будет так же здорово, как и любые другие хакерские решения.
Спасибо!
9 ответов
Чтобы показать номер страницы и общее количество страниц, вы можете использовать этот фрагмент JavaScript в своем нижнем колонтитуле или коде заголовка:
var pdfInfo = {};
var x = document.location.search.substring(1).split('&');
for (var i in x) { var z = x[i].split('=',2); pdfInfo[z[0]] = unescape(z[1]); }
function getPdfInfo() {
var page = pdfInfo.page || 1;
var pageCount = pdfInfo.topage || 1;
document.getElementById('pdfkit_page_current').textContent = page;
document.getElementById('pdfkit_page_count').textContent = pageCount;
}
И вызвать getPdfInfo со страницы загрузки
Конечно, pdfkit_page_current и pdfkit_page_count будут двумя элементами, которые показывают числа.
Фрагмент, взятый отсюда
На самом деле это намного проще, чем с фрагментом кода. Вы можете добавить следующий аргумент в командной строке: --footer-center [page]/[topage]
,
Как упоминал Ричард, дополнительные переменные находятся в разделе "Нижние колонтитулы и заголовки" документации.
Среди нескольких других параметров, номер страницы и общее количество страниц передаются в нижний колонтитул HTML в виде параметров запроса, как указано в официальных документах:
... аргументы [номер страницы] отправляются в HTML-документы верхнего / нижнего колонтитула способом GET.
Источник: http://wkhtmltopdf.org/usage/wkhtmltopdf.txt
Таким образом, решение состоит в том, чтобы получить эти параметры, используя немного JS, и отобразить их в шаблон HTML. Вот полный рабочий пример HTML нижнего колонтитула:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script>
function substitutePdfVariables() {
function getParameterByName(name) {
var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}
function substitute(name) {
var value = getParameterByName(name);
var elements = document.getElementsByClassName(name);
for (var i = 0; elements && i < elements.length; i++) {
elements[i].textContent = value;
}
}
['frompage', 'topage', 'page', 'webpage', 'section', 'subsection', 'subsubsection']
.forEach(function(param) {
substitute(param);
});
}
</script>
</head>
<body onload="substitutePdfVariables()">
<p>Page <span class="page"></span> of <span class="topage"></span></p>
</body>
</html>
substitutePdfVariables()
называется в теле onload
, Затем мы получаем каждую поддерживаемую переменную из строки запроса и заменяем содержимое всех элементов соответствующим именем класса.
Из документации wkhtmltopdf ( http://madalgo.au.dk/~jakobt/wkhtmltoxdoc/wkhtmltopdf-0.9.9-doc.html) под заголовком "Нижние и нижние колонтитулы" приведен фрагмент кода для достижения нумерации страниц:
<html><head><script>
function subst() {
var vars={};
var x=document.location.search.substring(1).split('&');
for(var i in x) {var z=x[i].split('=',2);vars[z[0]] = unescape(z[1]);}
var x=['frompage','topage','page','webpage','section','subsection','subsubsection'];
for(var i in x) {
var y = document.getElementsByClassName(x[i]);
for(var j=0; j<y.length; ++j) y[j].textContent = vars[x[i]];
}
}
</script></head><body style="border:0; margin: 0;" onload="subst()">
<table style="border-bottom: 1px solid black; width: 100%">
<tr>
<td class="section"></td>
<td style="text-align:right">
Page <span class="page"></span> of <span class="topage"></span>
</td>
</tr>
</table>
</body></html>
Есть также более доступные переменные, которые можно заменить, кроме номеров страниц, для использования в верхних и нижних колонтитулах.
Безопасный подход, даже если вы используете XHTML (например, с тимилефом). Единственное отличие от решения другого - использование тегов //.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<script>
/*<![CDATA[*/
function subst() {
var vars = {};
var query_strings_from_url = document.location.search.substring(1).split('&');
for (var query_string in query_strings_from_url) {
if (query_strings_from_url.hasOwnProperty(query_string)) {
var temp_var = query_strings_from_url[query_string].split('=', 2);
vars[temp_var[0]] = decodeURI(temp_var[1]);
}
}
var css_selector_classes = ['page', 'topage'];
for (var css_class in css_selector_classes) {
if (css_selector_classes.hasOwnProperty(css_class)) {
var element = document.getElementsByClassName(css_selector_classes[css_class]);
for (var j = 0; j < element.length; ++j) {
element[j].textContent = vars[css_selector_classes[css_class]];
}
}
}
}
/*]]>*/
</script>
</head>
<body onload="subst()">
<div class="page-counter">Page <span class="page"></span> of <span class="topage"></span></div>
</body>
Последнее примечание: при использовании тимелина замените <script>
с <script th:inline="javascript">
,
В моем примере показано, как скрыть текст на определенной странице, в этом случае он показывает текст со страницы 2 и далее.
<span id='pageNumber'>{#pageNum}</span>
<span id='pageNumber2' style="float:right; font-size: 10pt; font-family: 'Myriad ProM', MyriadPro;"><strong>${siniestro.numeroReclamo}</strong></span>
<script>
var elem = document.getElementById('pageNumber');
document.getElementById("pageNumber").style.display = "none";
if (parseInt(elem.innerHTML) <= 1) {
elem.style.display = 'none';
document.getElementById("pageNumber2").style.display = "none";
}
</script>
То, как это ДОЛЖНО быть сделано (то есть, если wkhtmltopdf поддерживает это), будет использовать надлежащие CSS Paged Media: http://www.w3.org/TR/css3-gcpm/
Я смотрю на то, что это займет сейчас.
Я не понял командную строку и, наконец, я нашел решение поместить эту информацию непосредственно в контроллер без какой-либо командной строки JS en.
В моем контроллере, когда я вызываю format.pdf, я просто помещаю нижний колонтитул строки:
format.pdf do
render :pdf => "show",
page_size: 'A4',
layouts: "pdf.html",
encoding: "UTF-8",
footer: {
right: "[page]/[topage]",
center: "Qmaker",
},
margin: { top:15,
bottom: 15,
left: 10,
right: 10}
end
Прямо с <tcode id="3209745"></tcode> Документы
Обновлено до версии 0.12.6.
Нижние
и нижние колонтитулы : верхние и нижние колонтитулы могут быть добавлены в документ с помощью аргументов --header- * и --footer * соответственно. В текстовой строке верхнего и нижнего колонтитула, передаваемой, например, --header-left, будут подставлены следующие переменные.
- [page] Заменяется количеством печатаемых страниц.
- [frompage] Replaced by the number of the first page to be printed
- [topage] Replaced by the number of the last page to be printed
- [webpage] Replaced by the URL of the page being printed
- [section] Replaced by the name of the current section
- [subsection] Replaced by the name of the current subsection
- [date] Replaced by the current date in system local format
- [isodate] Replaced by the current date in ISO 8601 extended format
- [time] Replaced by the current time in system local format
- [title] Replaced by the title of the of the current page object
- [doctitle] Replaced by the title of the output document
- [sitepage] Replaced by the number of the page in the current site being converted
- [sitepages] Replaced by the number of pages in the current site being converted
As an example specifying --header-right "Page [page] of [topage]", will result in the text "Page x of y" where x is thenumber of the current page and y is the number of the last page, to appear in the upper left corner in the document.
Headers and footers can also be supplied with HTML documents. As anexample one could specify --header-html header.html, and use thefollowing content in header.html:
<!DOCTYPE html>
<html>
<head><script>
function subst() {
var vars = {};
var query_strings_from_url = document.location.search.substring(1).split('&');
for (var query_string in query_strings_from_url) {
if (query_strings_from_url.hasOwnProperty(query_string)) {
var temp_var = query_strings_from_url[query_string].split('=', 2);
vars[temp_var[0]] = decodeURI(temp_var[1]);
}
}
var css_selector_classes = ['page', 'frompage', 'topage', 'webpage', 'section', 'subsection', 'date', 'isodate', 'time', 'title', 'doctitle', 'sitepage', 'sitepages'];
for (var css_class in css_selector_classes) {
if (css_selector_classes.hasOwnProperty(css_class)) {
var element = document.getElementsByClassName(css_selector_classes[css_class]);
for (var j = 0; j < element.length; ++j) {
element[j].textContent = vars[css_selector_classes[css_class]];
}
}
}
}
</script></head>
<body style="border:0; margin: 0;" onload="subst()">
<table style="border-bottom: 1px solid black; width: 100%">
<tr>
<td class="section"></td>
<td style="text-align:right">
Page <span class="page"></span> of <span class="topage"></span>
</td>
</tr>
</table>
</body>
</html>
ProTip
If you are not using certain information like the
webpage
,
section
,
subsection
,
subsubsection
, then you should remove them. We are generating fairly large PDFs and were running into a segmentation fault at ~1,000 pages.
After a thorough investigation, it came down to removing those unused variables. No we can generate 7,000+ page PDFs without seeing the Segmentation Fault.