通过HttpResponse和AJAX提供服务时,非ASCII字符无法正确显示在PDF中 JS部分

问题描述

我已经生成一个包含ReportLab的包含西里尔字母(非ASCII)的PDF文件。为此,我使用了支持此类字符的“ Montserrat”字体。当我查看Django的media文件夹中生成的PDF文件时,正确显示了字符:

enter image description here

我通过在生成PDF的函数中使用以下代码来嵌入字体:

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

pdfmetrics.registerFont(TTFont('Montserrat','apps/Generic/static/Generic/tff/Montserrat-Regular.ttf'))
canvas_test = canvas.Canvas("media/"+filename,pagesize=A4)
canvas_test.setFont('Montserrat',18)
canvas_test.drawString(10,150,"Some text encoded in UTF-8")
canvas_test.drawString(10,100,"как поживаешь")
canvas_test.save()

但是,当我尝试通过HttpResponse提供此PDF时,尽管以蒙特塞拉特字体显示,但西里尔字母仍无法正确显示

enter image description here

为PDF提供服务的代码如下:

# Return the pdf as a response
fs = FileSystemStorage()
if fs.exists(filename):
    with fs.open(filename) as pdf:
        response = HttpResponse(
            pdf,content_type='application/pdf; encoding=utf-8; charset=utf-8')
        response['Content-disposition'] = 'inline; filename="'+filename+'"'
        return response

我几乎尝试了所有操作(使用FileResponse,用with open(fs.location + "/" + filename,'rb') as pdf打开PDF ...),但均未成功。实际上,我不明白为什么如果ReportLab正确嵌入了字体(media文件夹中的本地文件),但提供给浏览器的文件没有嵌入字体。

有趣的是,我已经通过Chrome或Edge使用Foxit Reader来读取PDF。当我使用Firefox的认PDF查看器时,会显示不同的错误字符。实际上,在这种情况下,字体似乎也是错误的:

enter image description here

编辑

由于@Melvyn,我已经意识到错误不在于直接从Python视图发送的响应中,而是在于AJAX调用中的success代码中,我将在下文中介绍:

$.ajax({
    method: "POST",url: window.location.href,data: { trigger: 'print_pdf',orientation: orientation,size: size},success: function (data) {
        if (data.error === undefined) {
            var blob = new Blob([data]);
            var link = document.createElement('a');
            link.href = window.URL.createObjectURL(blob);
            link.download = filename + '.pdf';
            link.click();
        }
    }
 });

这是代码的一部分,正在以某种方式更改编码。

解决方案中的意见

由于我收到的所有评论(特别是来自@Melvyn的评论),终于有了一个解决方案。我没有创建Blob对象,而是将AJAX的responseType设置为Blob类型。从JQuery 3开始,这是可能的:

$.ajax({
    method: "POST",xhrFields:{
        responseType: 'blob'
    },success: function (data) {
        if (data.error === undefined) {
            var link = document.createElement('a');
            link.href = window.URL.createObjectURL(data);
            link.download = filename + '.pdf';
            link.click();
        }
    }
 });

我希望这篇文章可以帮助遇到相同问题的人们生成非ASCII(西里尔字母)字符的PDF。我花了好几天...

解决方法

您正在执行一些编码/重新编码,因为如果您查看文件之间的差异,就会发现unicode replacement characters杂乱无章:

% diff -ua Cyrillic_good.pdf Cyrillic_wrong.pdf > out.diff

% hexdump out.diff|grep 'ef bf bd'|wc -l
    2659

您说您尝试了未设置编码和字符集的尝试,但是我认为该测试没有经过正确测试-很可能是您看到了一个由浏览器缓存的激进版本。

执行此操作的正确方法是使用FileResponse,传入文件名,然后让Django确定正确的内容类型。

以下是对工作情况的可重复测试:

首先,在媒体根目录中放入Cyrillic_good.pdf(没错。pdf)。

将以下内容添加到urls.py:

#urls.py
from django.urls import path
from .views import pdf_serve

urlpatterns = [
    path("pdf/<str:filename>",pdf_serve),]

和views.py在同一目录中:

#views.py
from pathlib import Path

from django.conf import settings
from django.http import (
    HttpResponseNotFound,HttpResponseServerError,FileResponse
)

def pdf_serve(request,filename: str):
    pdf = Path(settings.MEDIA_ROOT) / filename
    if pdf.exists():
        response = FileResponse(open(pdf,"rb"),filename=filename)
        filesize = pdf.stat().st_size
        cl = int(response["Content-Length"])
        if cl != filesize:
            return HttpResponseServerError(
                f"Expected {filesize} bytes but response is {cl} bytes"
            )
        return response

    return HttpResponseNotFound(f"No such file: {filename}")


现在启动运行服务器并请求http://localhost:8000/pdf/Cyrillic_good.pdf

如果无法复制有效的pdf,则说明这是一个本地问题,您应该查看中间件或操作系统或小人物,而不是代码。我已经在本地处理您的文件,并且没有进行任何处理。

实际上,现在获取损坏的pdf的唯一方法是在Django发送后修改浏览器缓存或响应 ,因为内容长度检查会阻止发送大小与一个在磁盘上。

JS部分

我希望转换会在blob构造函数中发生,因为可以将blob传递给类型。我不确定默认值是否是二进制安全的。 同样奇怪的是,您的数据具有错误属性,并且您将整个事情传递给了Blob,但是我们看不到您做出了什么承诺。
success: function (data) {
    if (data.error === undefined) {
        console.log(data) // This will be informative
        var blob = new Blob([data]);
        var link = document.createElement('a');
        link.href = window.URL.createObjectURL(blob);
        link.download = filename + '.pdf';
        link.click();
    }
}
,

对于那些在视图中进行表单验证的人,您需要在 js 文件中添加以下代码,因为返回类型应为 blob。

xhr: function() {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 2) {
            if (xhr.status == 200) {
                xhr.responseType = "blob";
            }
        }
    };
    return xhr;
},success: function (response,textStatus,jqXHR) {
    var blob = new Blob([response])
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="contract.pdf";
    link.click();
},error: function (response,jqXHR) {
    $('#my_form').click();
}