问题描述
我已经生成一个包含ReportLab
的包含西里尔字母(非ASCII)的PDF文件。为此,我使用了支持此类字符的“ Montserrat”字体。当我查看Django的media
文件夹中生成的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
,用with open(fs.location + "/" + filename,'rb') as 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",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();
}