PyMuPDF==1.22.5
reportlab==4.2.2
直接插入图片
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
from PIL import Image
# 创建一个 PDF 文档
c = canvas.Canvas("1.pdf", pagesize=letter)
page_width, page_height = letter
# 图片路径
image_path = r"C:\Users\Yi\Desktop\88.jpg"
image = Image.open(image_path)
img_width, img_height = image.size
img_width, img_height = img_width * 0.1, img_height * 0.1
image.close()
# 设置图片的位置(从左上角计算)
x = 0 # x 坐标
y = 0 # y 坐标(从左上角计算)
c.drawImage(image_path, x, page_height - y - img_height, width=img_width, height=img_height)
# 保存 PDF 文档
c.save()
创建pdf与插入图片
import fitz # PyMuPDF
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import SimpleDocTemplate, Paragraph, Table, TableStyle
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase import pdfmetrics
class CreateBaoXiuDan(object):
'''生成保修单'''
def __init__(self, pdf_name):
pdfmetrics.registerFont(TTFont('SimHei', 'SimHei.ttf')) # 注册中文字体
self.pdf = SimpleDocTemplate(pdf_name, pagesize=letter)
# self.pdf = SimpleDocTemplate(pdf_name, pagesize=letter, topMargin=0, bottomMargin=0) 页面顶部边距和底部边距
def table1(self, data: list):
'''顶部表格 ■ □'''
data = [
['客户姓名', '', '客户电话', '', '客户电话', ''],
['安装地址', '', '', '', '购买场所', ''],
['供暖方式', '□集中 ■独立', '管路连接方式', '□串联 ■并联 □混联', '', ''],
['墙体情况', '□混凝土 ■砖墙\n□石膏板 □其他', '进回水管材质', '□铝塑 □镀锌 □PPR\n□PB □PERT □其他', '', ''],
['工作类型', '□安装 □换线 □改造', '', '', '', ''],
]
style = TableStyle([ # 定义表格样式
('ALIGN', (0, 0), (-1, -1), 'CENTER'), # 水平居中对齐 LETF左 RIGHT右 CENTER居中
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), # 垂直居中对齐 BOTTOM下 TOP上 MIDDLE居中
('FONT', (0, 0), (-1, -1), 'SimHei'), # 字体
('BACKGROUND', (0, 0), (-1, 0), '#FFFFFF'), # 单元格背景色
('GRID', (0, 0), (-1, -1), 1, 'black'), # 加网格线 0,0开始 (-1, -1) 表示到最后一个单元格(负值表示最后的单元格) 1线条宽度
# ('GRID', (0, 0), (-1, -1), 0, colors.transparent) 不要边框
('SPAN', (1, 1), (3, 1)), # 合并第二行的第2、3、4列 (起始列, 起始行), (结束列, 结束行))
('SPAN', (3, 2), (5, 2)),
('SPAN', (3, 3), (5, 3)),
('SPAN', (1, 4), (5, 4)),
])
table = Table(data, colWidths=[80, 80, 80, 80, 80, 80]) # 创建表格 元素为列的数量
table.setStyle(style)
return table
def table2(self, content: list):
'''表格2'''
data = [
['订货清单', ],
['房间', '型号', '组数']
]
data.extend(content)
style = TableStyle([ # 定义表格样式
('ALIGN', (0, 0), (-1, -1), 'CENTER'), # 水平居中对齐
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), # 垂直居中对齐
('FONT', (0, 0), (-1, -1), 'SimHei'), # 字体
('BACKGROUND', (0, 0), (-1, 0), '#FFFFFF'), # 单元格背景色
('GRID', (0, 0), (-1, -1), 1, 'black'), # 加网格线 0,0开始 (-1, -1) 表示到最后一个单元格(负值表示最后的单元格) 1线条宽度
('SPAN', (0, 0), (2, 0)),
])
table = Table(data, colWidths=[160, 160, 160]) # 创建表格 元素为列的数量
table.setStyle(style)
return table
def table3(self, content: list):
'''表格3'''
data = [
['改管材料清单', ],
['项目', '单价', '数量', '合计']
]
data.extend(content)
dj, sl, hj = [],[],[]
for i in content:
dj.append(float(i[1]))
sl.append(int(i[2]))
hj.append(float(i[3]))
data.append(['总计', f'{sum(dj):.2f}', sum(sl), f'{sum(hj):.2f}'])
style = TableStyle([ # 定义表格样式
('ALIGN', (0, 0), (-1, -1), 'CENTER'), # 水平居中对齐
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), # 垂直居中对齐
('FONT', (0, 0), (-1, -1), 'SimHei'), # 字体
('BACKGROUND', (0, 0), (-1, 0), '#FFFFFF'), # 单元格背景色
('GRID', (0, 0), (-1, -1), 1, 'black'), # 加网格线 0,0开始 (-1, -1) 表示到最后一个单元格(负值表示最后的单元格) 1线条宽度
('SPAN', (0, 0), (3, 0)),
])
table = Table(data, colWidths=[120, 120, 120, 120]) # 创建表格 元素为列的数量
table.setStyle(style)
return table
def table4(self):
'''表格4'''
data = [
['完工状态', ],
['压力测试', '□已做 □未做'],
['阀门状态', '□关闭 □开启'],
['放气螺栓', '□已拧紧 □未拧紧'],
['所有接口', '□无漏水 □漏水'],
['位置正确', '□是 □否'],
['清理现场', '□是 □否']
]
style = TableStyle([ # 定义表格样式
('ALIGN', (0, 0), (-1, -1), 'CENTER'), # 水平居中对齐
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), # 垂直居中对齐
('FONT', (0, 0), (-1, -1), 'SimHei'), # 字体
('BACKGROUND', (0, 0), (-1, 0), '#FFFFFF'), # 单元格背景色
('GRID', (0, 0), (-1, -1), 1, 'black'), # 加网格线 0,0开始 (-1, -1) 表示到最后一个单元格(负值表示最后的单元格) 1线条宽度
('SPAN', (0, 0), (1, 0)),
])
table = Table(data, colWidths=[240, 240]) # 创建表格 元素为列的数量
table.setStyle(style)
return table
def table5(self):
'''表格5'''
data = [
['\n用户意见\n', ''],
]
style = TableStyle([ # 定义表格样式
('ALIGN', (0, 0), (-1, -1), 'CENTER'), # 水平居中对齐
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), # 垂直居中对齐
('FONT', (0, 0), (-1, -1), 'SimHei'), # 字体
('BACKGROUND', (0, 0), (-1, 0), '#FFFFFF'), # 单元格背景色
('GRID', (0, 0), (-1, -1), 1, 'black'), # 加网格线 0,0开始 (-1, -1) 表示到最后一个单元格(负值表示最后的单元格) 1线条宽度
])
table = Table(data, colWidths=[80, 400]) # 创建表格 元素为列的数量
table.setStyle(style)
return table
def qianzi(self):
'''签字区'''
text_style = ParagraphStyle(
name='TitleStyle',
fontName='SimHei',
fontSize=10,
alignment=0,
# spaceAfter=5,
fontWeight='Bold' # 设置加粗
)
nbsp = ' '
text = [
Paragraph('签字区: (签字前请阅读背后合同说明,未签字或未付安装费用本协议不生效)', text_style),
Paragraph(f'改造网点:{nbsp * 35}安装网点:', text_style),
Paragraph(f'改造时间:{nbsp * 35}安装网点24小时电话:', text_style),
Paragraph(f'用户签字:{nbsp * 35}施工人员签字:', text_style),
]
return text
def insert_img(self, yonghu_img: str, shigongyuan_img: str, input_pdf: str, output_pdf: str):
'''插入图片'''
# fitz.Rect(123, 250, 200, 1003) # 左上角x,y 和右下角的x,y 右下角必须大于左上角
pdf_document = fitz.open(input_pdf)
page = pdf_document[0] # 选择要插入图片的页面(这里选择第一页)
page.insert_image(fitz.Rect(142, 265, 219, 990), filename=shigongyuan_img) # 施工员签字
page.insert_image(fitz.Rect(402, 250, 479, 990), filename=yonghu_img) # 用户签字
page.insert_image(fitz.Rect(123, 560, 200, 640), filename=r'D:\code\nuantong\media\default\gz.png') # 公章
page_number = pdf_document.page_count # 创建一个新的页面
pdf_document.insert_page(page_number) # 在末尾插入新页面
new_page = pdf_document[page_number] # 获取新插入的页面
new_page.insert_image(fitz.Rect(0, 50, 580, 700), filename=r'C:\Users\Yi\Desktop\23.jpg')
pdf_document.save(output_pdf) # 保存修改后的 PDF 文件
pdf_document.close()
def save(self, title):
'''保存'''
title_style = ParagraphStyle(
name='TitleStyle',
fontName='SimHei',
fontSize=16,
alignment=1, # 0 - 左对齐 1 - 中间对齐 2 - 右对齐
spaceAfter=12,
fontWeight='Bold' # 设置加粗
)
title = Paragraph(title, title_style)
elements = [title,
self.table1([]),
self.table2([('11', '22', '33'), ('we', 're', 'gf')]),
self.table3([('1', '1', '1', '1'), ('2', '2', '2', '2')]),
self.table4(),
self.table5()
]
elements.extend(self.qianzi())
self.pdf.build(elements)
if __name__ == '__main__':
a = CreateBaoXiuDan('1.pdf')
a.save('散热器安装保修协议')
a.insert_img(r'C:\Users\Yi\Desktop\88.png', r'D:\code\nuantong\backstage\1.pdf', r'D:\code\nuantong\backstage\2.pdf')
边框虚线效果
from reportlab.lib import colors
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle
创建文档
doc = SimpleDocTemplate(“example.pdf”)
data = [[‘标题1’, ‘标题2’], [‘内容1’, ‘内容2’]]
定义表格样式
style = TableStyle([
('ALIGN', (0, 0), (-1, -1), 'RIGHT'), # 水平右对齐
('VALIGN', (0, 0), (-1, -1), 'BOTTOM'), # 垂直底部对齐
('FONT', (0, 0), (-1, -1), 'SimHei'), # 字体
('FONTSIZE', (0, 0), (-1, -1), 12), # 字体大小
('BACKGROUND', (0, 0), (-1, 0), colors.white), # 单元格背景色
('GRID', (0, 0), (-1, -1), 1, colors.black), # 实线边框
('LINEABOVE', (0, 0), (-1, 0), 1, colors.black), # 上边框
('LINEBELOW', (0, 0), (-1, 0), 1, colors.black), # 下边框
])
# 添加虚线效果
style.add('LINEBELOW', (0, 0), (-1, -1), 1, colors.black) # 在这里更改为虚线
# 创建表格并应用样式
table = Table(data)
table.setStyle(style)
# 添加表格到文档并生成PDF
doc.build([table])
食材案例
import os, sys, django
import fitz
from django.db.models import Sum
from reportlab.lib.pagesizes import A4, letter
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas
# 将django项目根目录加入环境变量
parent_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(parent_path)
# 引入django配置文件
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shicaigongying.settings")
# 启动django
django.setup()
from backstage.utils import create_random_file_name
from backstage.models import Order, OrderInfo, Gongzhang, User
from shicaigongying.settings import MEDIA_ROOT, FontPath, SERVER_URL
from reportlab.lib import colors
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Image, Paragraph
import os
class Createfapiaopdf(object):
'''生成pdf'''
def __init__(self, order):
self.cache_pdf = os.path.join(MEDIA_ROOT, 'default/.cache.pdf') # 临时缓存pdf
self.pdf_name = create_random_file_name('.pdf') # 生成pdf的名称
self.pdf_path = os.path.join(MEDIA_ROOT, self.pdf_name) # 保存pdf的路径
self.pdf_url = f'{SERVER_URL}/media/{self.pdf_name}' # 访问pdf的url
pdfmetrics.registerFont(TTFont('SimHei', FontPath)) # 注册中文字体
self.pdf = SimpleDocTemplate(self.cache_pdf,
pagesize=letter,
leftMargin=8, # 左边距 36pt ≈ 0.5英寸
rightMargin=8,
topMargin=36,
bottomMargin=8)
self.order = order
self.user = User.objects.get(user_id=order.user_id)
self.gongzhang = Gongzhang.objects.filter(zhuangtai=1).first()
self.jieyu = Order.objects.filter(zhuangtai=4).aggregate(res=Sum('zongjin_e')).get('res')
self.jieyu = f'{self.jieyu:.2f}' if self.jieyu else '0.00'
self.tongji_table = None
def create_biaotou(self):
'''生成表头'''
gongsiming_style = ParagraphStyle(
name='TitleStyle',
fontName='SimHei',
fontSize=20,
alignment=0,
spaceAfter=11,
fontWeight='Bold' # 设置加粗
)
dierhang_style = ParagraphStyle(
name='TitleStyle',
fontName='SimHei',
fontSize=13,
alignment=0,
spaceAfter=5,
fontWeight='Bold' # 设置加粗
)
fapiao_style = ParagraphStyle(
name='TitleStyle',
fontName='SimHei',
fontSize=20,
alignment=1,
spaceAfter=11,
fontWeight='Bold' # 设置加粗
)
text_style = ParagraphStyle(
name='TitleStyle',
fontName='SimHei',
fontSize=13,
alignment=0,
spaceAfter=5,
)
nbsp = ' '
text = [
Paragraph(f'{nbsp * 6}萬利龍有限公司', gongsiming_style),
Paragraph(f'{nbsp * 9}Profit Dragon Inc. Limited', dierhang_style),
Paragraph(f'{nbsp * 9}香港葵湧永業街14-20號華榮工業大廈4樓4A室', text_style),
Paragraph(f'<u>{nbsp * 9}TEL:26665357 / 36151094 {nbsp * 41} FAX: 3003 3061</u>', text_style),
Paragraph(f'發票INVOICE', fapiao_style),
]
return text
def kehuxinxi(self):
'''客户信息'''
# 定义样式
left_style = ParagraphStyle(
name='LeftStyle',
fontName='SimHei',
fontSize=12,
alignment=0 # 左对齐
)
right_style = ParagraphStyle(
name='RightStyle',
fontName='SimHei',
fontSize=12,
alignment=2 # 右对齐
)
data = [
[Paragraph(f'客戶名稱:{order.kehumingcheng}', left_style), Paragraph(f'到貨日期: {order.daohuoriqi} ', right_style)],
[Paragraph(f'客戶編號:{order.kehubianhao}', left_style), Paragraph(f'發票號碼: KL632B03001', right_style)]
]
table = Table(data, colWidths=[300, 300]) # 创建表格 元素为列的数量
return table
def create_shangpin(self):
'''添加商品信息'''
style = TableStyle([ # 定义表格样式
('BACKGROUND', (0, 0), (-1, 0), '#D3D3D3'),# (起始列, 起始行) (结束列, 结束行)
('ALIGN', (0, 0), (-1, -1), 'CENTER'), # 水平居中对齐
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), # 垂直居中对齐
('FONT', (0, 0), (-1, -1), 'SimHei'), # 字体
('GRID', (0, 0), (-1, -1), 1, 'black'), # 加网格线 0,0开始 (-1, -1) 表示到最后一个单元格(负值表示最后的单元格) 1线条宽度
])
data = [
['序號', '名稱', '規格', '單價HK$', '單位', '數量', '金額HK$'],
]
queryset = OrderInfo.objects.filter(order_id=order.order_id)
for index, value in enumerate(queryset, start=1):
data.append([index,
value.shangpingmingcheng,
value.beizhu,
f'{value.danjia:.2f}',
value.danwei,
value.shuliang,
f'{value.jin_e:.2f}' if value.ctypes == 1 else f'-{value.jin_e:.2f}',
])
table = Table(data, colWidths=[50, 70, 170, 70, 70, 70, 90]) # 创建表格 元素为列的数量
table.setStyle(style)
return table
def tongji(self):
'''统计信息'''
style = TableStyle([ # 定义表格样式
('ALIGN', (4, 0), (4, -1), 'RIGHT'), # 第5列所有行 水平居中
('VALIGN', (4, 0), (4, -1), 'MIDDLE'), # 第5列所有行 垂直居中
('ALIGN', (5, 0), (5, -1), 'LEFT'), # 第6列所有行 水平居中
('VALIGN', (5, 0), (5, -1), 'MIDDLE'), # 第6列所有行 垂直居中
('ALIGN', (6, 0), (6, -1), 'RIGHT'), # 第7列所有行 水平居中
('VALIGN', (6, 0), (6, -1), 'MIDDLE'), # 第7列所有行 垂直居中
('FONT', (0, 0), (-1, -1), 'SimHei'), # 字体
('LINEBELOW', (4, 2), (-1, 2), 1, colors.black), # 第三行加底边框 (起始列, 行索引), (结束列, 行索引), 线宽, 颜色)
])
data = [
['', '', '', '', '總金額:', 'HKD', f'{self.order.zongjin_e:.2f}'], # {:,.2f} 带千分隔符
['', '', '', '', '折扣:', 'HKD', f'{self.user.zhekou:.2f}'],
['', '', '', '', '運輸費:', 'HKD', f'{self.user.yunfei:.2f}'],
['', '', '', '', '結餘:', 'HKD', self.jieyu],
]
# if self.gongzhang: # 直接给表格中添加图片 表格会随着图片的尺寸变宽高
# img = Image(os.path.join(MEDIA_ROOT, os.path.split(self.gongzhang.img)[1]))
# img.drawWidth = 38
# img.drawHeight = 38
# data[0][3] = img
self.tongji_table = Table(data, colWidths=[50, 70, 170, 70, 70, 70, 90]) # 创建表格 元素为列的数量
self.tongji_table.setStyle(style)
return self.tongji_table
def fukuanfangshi(self):
'''付款方式'''
text_style = ParagraphStyle(
name='TitleStyle',
fontName='SimHei',
fontSize=13,
alignment=0,
spaceAfter=5,
fontWeight='Bold' # 设置加粗
)
nbsp = ' '
text = [
Paragraph(f'《付款方式》', text_style),
Paragraph(f'BANK ACCOUNT: 收支票或現金存入帳戶', text_style),
Paragraph(f'BENEFICIARY NAME 戶名: PROFIT DRAGON INC LIMITED', text_style),
Paragraph(f'A/C NO 賬號: 012-588-2-0235787', text_style),
Paragraph(f'BANK NAME 銀行: BANK OF CHINA(HONG KONG)LIMITED 香港中國銀行', text_style),
Paragraph(f'FPS 轉數快: 105408397', text_style),
]
return text
def insert_img(self):
'''插入图片'''
# fitz.Rect(123, 250, 200, 1003) # 左上角x,y 和右下角的x,y 右下角必须大于左上角
pdf_document = fitz.open(self.cache_pdf)
first_page = pdf_document[0] # 选择要插入图片的页面(这里选择第一页)
first_page.insert_image(fitz.Rect(10, 39, 70, 120), filename=os.path.join(MEDIA_ROOT, 'default/logo.png'))
for page_num in range(len(pdf_document)): # 提取文字的实际坐标
page = pdf_document[page_num]
words = page.get_text('words')
text = () # 提取总金额的总坐标
for i, v in enumerate(words):
if v[4] == '總' and words[i + 1][4] == '金' and words[i + 2][4] == '額':
text = v
print(f'公章提取坐标:【{text}】')
break
x1, y1, x2, y2 = text[:4]
x1 = x1 - 50
x2 = x2 - 38
y2 = y2 + 38
page.insert_image(fitz.Rect(x1, y1, x2, y2),
filename=os.path.join(MEDIA_ROOT, os.path.split(self.gongzhang.img)[1]))
pdf_document.save(self.pdf_path) # 保存修改后的 PDF 文件
pdf_document.close()
return self.pdf_url
def start(self):
elements = []
elements.extend(self.create_biaotou())
elements.append(self.kehuxinxi())
elements.append(self.create_shangpin())
elements.append(self.tongji())
elements.extend(self.fukuanfangshi())
self.pdf.build(elements)
order = Order.objects.filter(order_id=2).first()
a = Createfapiaopdf(order)
a.start()
url = a.insert_img()
print(a.pdf_url)


1401

被折叠的 条评论
为什么被折叠?



