Javascript 通过HttpResponse和AJAX提供服务时,非ASCII字符不能正确显示在PDF中

Javascript 通过HttpResponse和AJAX提供服务时,非ASCII字符不能正确显示在PDF中,javascript,python,django,utf-8,reportlab,Javascript,Python,Django,Utf 8,Reportlab,我已经生成了一个PDF文件,其中包含带有ReportLab的西里尔字符(非ASCII)。为此,我使用了支持此类字符的“蒙特塞拉特”字体。当我查看Django的媒体文件夹中生成的PDF文件时,字符正确显示: 我在生成PDF的函数中使用以下代码嵌入了字体: from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import A4 from reportlab.pdfbase import pdfmetrics from r

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

我在生成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时,西里尔字母没有正确显示,尽管显示为蒙特塞拉特字体:

为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
,使用open(fs.location+“/”+filename,'rb')以PDF的形式打开PDF,但没有成功。实际上,我不明白为什么,如果
ReportLab
正确嵌入字体(在
media
文件夹中的本地文件),提供给浏览器的文件没有嵌入字体

还值得注意的是,我通过Chrome或Edge使用Foxit Reader阅读PDF。当我使用Firefox的默认PDF查看器时,会显示不同的错误字符。实际上,在这种情况下,字体似乎也是错误的:

编辑 多亏了@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",
    url: window.location.href,
    xhrFields:{
        responseType: 'blob'
    },
    data: { trigger: 'print_pdf', orientation: orientation, size: size},
    success: function (data) {
        if (data.error === undefined) {
            var link = document.createElement('a');
            link.href = window.URL.createObjectURL(data);
            link.download = filename + '.pdf';
            link.click();
        }
    }
 });
返回响应时处理错误 您可以从Python返回错误(即捕获异常),如下所示:

except Exception as err:
    response = JsonResponse({'msg': "Error"})
    error = err.args[0]
    if error is not None:
        response.status_code = 403 # To announce that the user isn't allowed to publish
        if error==13:
            error = "Access denied to the PDF file."
        response.reason_phrase = error
        return response
然后,您只需使用来自AJAX的本机错误处理(在
success
部分之后):

有关详细信息,请参阅


我希望这篇文章能帮助那些在生成非ASCII(西里尔字母)字符的PDF时遇到同样问题的人。我花了好几天时间…

您正在进行一些编码/重新编码,因为如果您查看文件之间的差异,就会发现其中充斥着:

你说你没有设置编码和字符集就试过了,但我认为测试不正确-很可能你看到了一个浏览器缓存版本

正确的方法是使用FileResponse,传入文件名并让Django找出正确的内容类型

以下是工作环境的可再现性试验:

首先,将西里尔文(Cyrillic_good.pdf)放入媒体根目录中

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

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

urlpatterns = [
    path("pdf/<str:filename>", pdf_serve),
]
现在启动runserver并请求
http://localhost:8000/pdf/Cyrillic_good.pdf

如果这不能复制一个有效的pdf,这是一个本地问题,您应该查看中间件、操作系统或小绿人,而不是代码。我在本地处理您的文件,没有发生任何损坏

事实上,现在获得损坏pdf的唯一方法是在Django发送后修改浏览器缓存或响应,因为内容长度检查将阻止发送与磁盘上文件大小不同的文件

JS部分 我希望转换发生在blob构造函数中,因为可以将blob转换为类型。我不确定默认值是否是二进制安全的。 同样奇怪的是,你的数据有一个错误属性,你将整个数据传递给blob,但我们看不出你在对什么承诺做出反应。
成功:函数(数据){
if(data.error==未定义){
console.log(data)//这将提供信息
var blob=新blob([数据]);
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, textStatus, jqXHR) {
    $('#my_form').click();
}  

确保该字体嵌入到PDF中,而不仅仅是假设客户端将拥有该字体。请显示生成PDF.Hi@AntoinePinsard的代码。我添加了与reportlab一起使用的行,以嵌入字体。我想这就是你的意思,对吧?问题出在httpresponse中,在媒体内部生成的文件中,一切正常……我已在没有字体的计算机中检查了媒体中的PDF文件,并且该文件也正确显示。@Sunil,我通过以下链接解决了此问题。基本上,您必须在Python中引发一个错误,然后使用AJAX本机错误处理。我通过添加下面的内容而不仅仅是返回类型来解决这个问题<代码>xhr:function(){var xhr=new XMLHttpRequest();xhr.onreadystatechange=function(){if(xhr.readyState==2){if(xhr.status==200){xhr.responseType=“blob”;}}};return xhr;}使用
FileResponse
这种方式会导致以下错误
ValueError:read of closed file
。显然,
fileresponse
不能与上下文管理器一起使用(请参阅)。无论如何,我已经清除了所有缓存,并且直接使用了
返回FileResponse(open(filename))
。这在
lib/encodings/cp1252.py
的第23行中产生了以下错误:
字符映射代码无法解码位置561中的字节0x8d:character map to
。因此,它似乎无法正确地执行t
#urls.py
from django.urls import path
from .views import pdf_serve

urlpatterns = [
    path("pdf/<str:filename>", pdf_serve),
]
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, textStatus, jqXHR) {
    $('#my_form').click();
}