I am trying to generate a PDF using html+css using xhtml2pdf.pisa using Django. However, I'm running into all sorts of weird issues with the CSS.
Below is my code:
from django.template.loader import render_to_string
import cStringIO as StringIO
import xhtml2pdf.pisa as pisa
import cgi, os
def fetch_resources(uri, rel):
path = os.path.join(settings.STATIC_ROOT, uri.replace(settings.STATIC_URL, ""))
return path
def test_pdf(request):
html = render_to_string('pdf/quote_sheet.html', { 'pagesize':'A4', }, context_instance=RequestContext(request))
result = StringIO.StringIO()
pdf = pisa.pisaDocument(StringIO.StringIO(html.encode("UTF-8")), dest=result, link_callback=fetch_resources)
if not pdf.err:
return HttpResponse(result.getvalue(), mimetype='application/pdf')
return HttpResponse('Gremlins ate your pdf! %s' % cgi.escape(html))
And my template:
<!DOCTYPE HTML PUBLIC "-//W3C/DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
{% load static from staticfiles %}
{% load i18n %}
<meta charset="utf-8"/>
<meta http-equiv="content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="content-language" content='{% trans "g_locale2" %}'/>
<title>{% block title %}{% endblock %}</title>
<style type="text/css">
@page {
size: A4;
margin: 1cm;
@frame footer {
-pdf-frame-content: footerContent;
bottom: 0cm;
margin-left: 9cm;
margin-right: 9cm;
height: 1cm;
font-family: "Microsoft JhengHei";
}
}
@font-face {
font-family: "Microsoft JhengHei";
src:url('{% static "ttf/msjh.ttf" %}');
}
@font-face {
font-family: "Microsoft JhengHei";
src:url('{% static "ttf/msjhbd.ttf" %}');
}
@font-face {
font-family: "Helvetica";
src:url('{% static "ttf/Helvetica_Reg.ttf" %}');
}
table.styled-table tr th {
color: gray;
background-color: blue;
font-size: 14px;
font-family:"Microsoft JhengHei";
border: 1px solid black;
}
.biz_phone, .biz_fax {
display: inline-block;
width: 100px;
line-height: 32px;
}
.biz-info {
margin-bottom: 20px;
}
</style>
<link rel="stylesheet" href='{% static "css/pdf.css" %}'/>
</head>
<body>
<div id="main-content">
<div class="container">
<div class="biz-info">
<div class="biz_name">{{ proj.biz.name }}</div>
<div class="biz_address">{{ proj.biz.address }}</div>
<div class="biz_phone">{{ proj.biz.phone }}</div>
<div class="biz_fax">{{ proj.biz.fax }}</div>
</div>
<div class="table-div">
<table class="styled-table">
<tr class="row_header">
<th class="col_order">{% trans "g_item_num" %}</th>
<th class="col_name">{% trans "g_item_name" %}</th>
<th class="col_provider">{% trans "g_provider_name" %}</th>
<th class="col_budget_quantity">{% trans "g_quantity" %}</th>
<th class="col_price">{% trans "g_item_price" %}</th>
<th class="col_total_price">{% trans "g_item_total" %}</th>
</tr>
</table>
</div>
</div>
</div>
</body>
</html>
My code is pretty basic and nothing special, they are just pretty much copied verbatim from the web. I'm running into lots of weird issues:
- font-size, background-color works in external css, but only when applied on body or html tag.
- width, line-height etc does not work whatsoever, no matter external, internal, or inlined.
- margin-bottom on a parent div gets applied to every single child div instead of the parent div...
- all sorts of other random issues...
I cannot observe a pattern from these symptoms other than just thinking the css parser and layout engine is just totally incomplete and non-functional. However I cannot find anyone online who has the same issues as me. Am I crazy? I'm not sure what is happening here... any help would be appreciated.
Under the covers,
xhtml2pdf
usesreportlab
to actually create the PDF.Doing some debugging - I followed the execution of parser.py in
xhtml2pdf
to see whether I could work out where theCSS
was 'going missing' or being applied to the wrong elements.I found that the CSS was successfully parsed and the translation into document fragments (here in the code) worked OK, with the correct CSS elements being applied to the correct elements.
I believe the problem comes with the
reportlab
pdf rendering engine. It is documented here. It does not have a 1:1 mapping between CSS and the directives you can pass into it.I first became aware of it answering this question. For instance in the table rendering section of the
reportlab
documentation (open-source User Guide, chapter 7, page 76), it is obvious that there is no analogue for theborder-style
CSS attribute - so although in theory you can specify a border style and no error will be thrown, that value will be ignored.While investigating CSS to pdf mapping, I found this software (Javascript) that also maps HTML/CSS to pdf. I tried that on the HTML from this question and the other one I linked to. This manages to render the pages correctly into the pdf document, so I believe this is not a fundamental limitation of the pdf document specification, but rather of the
reportlab
module.It seems to me this is a problem for
xhtml2pdf
. It seems particularly bad for tables, but obviously affects other types of document too. Sorry not to resolve your problem, but I hope this goes some way towards explaining it.