1

I am trying to make invoices by creating html file and convert it to pdf and than send as http response. The problem is that those invoices contains polish characters which UTF-8 does not display. I have tried to use ISO-8859-2 to display them, but than I am getting error: ('charmap' codec can't encode characters in position 1159-1163: character maps to ).

utils.py:

from io import BytesIO

from django.http import HttpResponse
from django.template.loader import get_template
from xhtml2pdf import pisa


def render_to_pdf(template_src, context_dict={}):
    template = get_template(template_src)
    html = template.render(context_dict)
    result = BytesIO()
    pdf = pisa.pisaDocument(
        src=BytesIO(html.encode('ISO-8859-2')), 
        dest=result, 
        encoding='UTF-8'
        )

    if pdf.err:
        return HttpResponse('We had some errors <pre>' + html + '</pre>')
    return HttpResponse(result.getvalue(), content_type='application/pdf')

views.py:

class GeneratePDF(View):
    def get(self, request, pk=None):
        "getting data here"
                
        pdf = render_to_pdf("invoice.html", data)

        if pdf:
            response = HttpResponse(pdf, content_type='application/pdf')
            filename = "Sales_Invoice_%s.pdf" % ("name")
            content = "inline; filename=%s" % (filename)
            download = request.GET.get("download")
            if download:
                content = "attachment; filename='%s'" % (filename)
            response['Content-Disposition'] = content
            return response
        return Response(status=rest_status.HTTP_404_NOT_FOUND)

invoice.html:

<html>
<head>
<title>Sales Invoice - {{ sales_invoice.id }}</title>
<style>
    @page {
        size: A4 portrait;
        @frame header_frame {
            -pdf-frame-content: header_content;
            left: 50pt; width: 512pt; top: 50pt; height: 40pt;
        }
        @frame content_frame {
            left: 50pt; width: 512pt; top: 90pt; height: 632pt;
            {% comment %} -pdf-frame-border: 1; {% endcomment %}
        }
        @frame footer_frame {
            -pdf-frame-content: footer_content;
            left: 50pt; width: 512pt; top: 772pt; height: 20pt;
        }
    }
</style>
<head>
<body>
    <div id="header_content"><h1>Sales Invoice {{ sales_invoice.id }}</h1></div>
    <p>
        <strong>Date of issue:</strong> Krakow, {{ today | date:"d/m/Y" }}<br />
        <strong>Date of sale:</strong> {{ today | date:"d/m/Y" }}<br />
        <strong>Date of payment:</strong> {{ today | date:"d/m/Y" }}<br />
        <strong>Payment:</strong> cash<br />
    </p>
    {% if sales_invoice.parts %}
    <table id="cssTable" class="cssTdTh">
      <thead>
        <tr>
          <th>Part</th>
          <th>Code</th>
          <th>Quantity</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>
        {% for part in sales_invoice.parts %}
        <tr>
          <td>{{ part.name }}</td>
          <td>{{ part.code }}</td>
          <td>{{ part.amount }}</td>
          <td>{{ part.price_out }}</td>
        </tr>
        {% endfor %}
        <tr>
          <td colspan="3">Total</td>
          <td>{{sales_invoice.total_part_price}}</td>
        </tr>
      </tbody>
    </table>
    {% endif %}

    <br />

    {% if sales_invoice.works %}
    <table id="cssTable" class="cssTdTh">
      <thead>
        <tr>
          <th>Work</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>
        {% for work in sales_invoice.works %}
        <tr>
          <td>{{ work.name }}</td>
          <td>{{ work.price }}</td>
        </tr>
        {% endfor %}
        <td>Total</td>
        <td>{{sales_invoice.total_work_price}}</td>
      </tbody>
    </table>
    {% endif %}
    <div id="footer_content">Footer</div>
</body>
</html>
print("==============================================================")
for work in sales_invoice.works.all():
    print(work.name)

for part in sales_invoice.parts.all():
    print(part.name)
print("==============================================================")

output:
==============================================================
Wymiana łącznika elastycznego
Wymiana sprężarki klimatyzacji
Montaż radia
==============================================================
Bęben
Bagnet
Alternator
==============================================================

Result I am getting:
Using ISO-8859-2: result
Using UTF-8: result

Pip freeze:

Django==3.2.7
xhtml2pdf==0.2.5
banboniera
  • 89
  • 7
  • 2
    Can we get a a [mcve] please? It seems your problem is with creating the PDF, so the whole HTTP code is just noise and should be removed. Instead some input text that causes the problem would be helpful. Also please don't post images of errors; see [ask]. – Robert Oct 20 '21 at 17:04
  • Is the font that the PDF is using capable of displaying Polish characters? – snakecharmerb Oct 20 '21 at 17:33
  • @Robert Thank you for advices on how to ask questions. I have changed html code to one xhtml2pdf have in their documentation. – banboniera Oct 21 '21 at 08:00
  • @snakecharmerb I have removed all styles and fonts to example in documentation. It helped - that it does not cause an error in mapping charmap, but now it does not display characters correctly – banboniera Oct 21 '21 at 08:02
  • This is still no [mcve]. I want a simple HTML file, without any templating stuff. (I don't have your database!) Remove all Django stuff, unless your problem is about serving a PDF through Django, in which case you should remove all the PDF generation and just add a small PDF that shows the problem. I need to be able to just copy & paste your code and run it, and see the problem you are having. I am not setting up Django, a database and what not. Please do read [ask]. – Robert Oct 21 '21 at 17:26

1 Answers1

2

The problem was not in encoding, it is just fonts. The solution is to use Asian fonts that mhtml2pdf supports. xhtml2pdf documentation

For example client can have polish letters in name, so I use this font here

<td style="font-family: STSong-Light">{{client.full_name}}</td>

utils.py:

from io import BytesIO

from django.http import HttpResponse
from django.template.loader import get_template
from xhtml2pdf import pisa


def render_to_pdf(template_src, context_dict={}):
    template = get_template(template_src)
    html = template.render(context_dict)
    result = BytesIO()
    pdf = pisa.pisaDocument(
        src=BytesIO(html.encode('UTF-8')),
        dest=result,
        encoding='UTF-8'
    )

    if pdf.err:
        return HttpResponse('We had some errors <pre>' + html + '</pre>')
    return HttpResponse(result.getvalue(), content_type='application/pdf')
banboniera
  • 89
  • 7