報表列印是程式設計師的痛, 難寫又難調整。本篇提供一個方式,將要列印的資料先寫入 pdf 檔案進行編排, 然後再將整個 pdf 檔案列出。
安裝套件
本例需安裝套件
pip install reportlab
Linux 安裝字型
在 Linux 系統沒有漂亮的向量字体,還好有 “文泉驛” 開源免費向量字体,使用如下指令安裝系統字型
sudo apt-get install ttf-wqy-zenhei
pdf 註冊字型
Windows 跟 Linux 的字型存放位置不一樣,所以需先使用 platform.system() 判斷作業系統,再使用pdfmetrics.registerFont() 註冊字型的位置
useros=platform.system() if useros == "Linux": pdfmetrics.registerFont(TTFont('hei', '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc')) else: pdfmetrics.registerFont(TTFont('hei', 'simsun.ttc'))
SimpleDocTemplate
SimpleDocTemplate 用來產生一份文件檔案,後續所有的內容都放在這個文件檔中。產生文件檔物件需傳入檔案名稱及紙張格式 (A4, A5…)
doc=SimpleDocTemplate( 'tmp.pdf', pagesize=letter, rightMargin=72, leftMargin=72, topMargin=72, bottomMargin=18 )
常見的紙張規格如下
letter : 279.4mm * 216mm
A4 : 297mm * 210mm
A5 : 210mm * 148mm
B5 : 257mm * 182mm
B6 : 182mm * 128mm
內容
文件裏的內容可以是段落、表格、空格、Title、圖片等,這些內容都放在 list 中,再使用 doc.build(content) 將內容放入文件中並儲存成檔案。
content = [] content.append(Paragraph('富立金履歷管制系統', style_title)) content.append(Spacer(1, 15)) doc.build(story)
內容可以包含Title, 段落, 表格, 圖片等資訊, 將分別說明如下
段落Paragraph
段落內的格式都必需一樣,如果想要不一樣的格式,就必需產生另一個段落。比如第一段字体大小是 8,第二段字体大小是 12。
Paragram 的語法為 Paragram(“文字”,樣式),樣式必需包含 name, fontSize, fotnName
txt1=""" 日本九州宮崎縣外海8日下午發生規模7.1強震,日本氣象廳隨後發布「南海海槽地震臨時情報」,呼籲民眾做好可能還會有大型地震的準備。 """ txt2=""" 儘管在今(15)日下午,日本氣象廳已經解除「南海海槽地震臨時情報」,但這一週正值日本盂蘭盆節假期,不少旅客也因為嚇得取消訂房,旅宿業者也因此損失了超過5億日圓(約台幣9811萬元)。 """ content=[] content.append(Paragraph( txt1, ParagraphStyle( name="p1", fontSize = 8, fontName="hei") ) ) #content.append(Spacer(1,15)) content.append(Paragraph( txt2, ParagraphStyle( name="p2", fontSize = 12, fontName="hei") ) )
結果如下
每個段落都要給一個名字 name,如果沒有給 name 變數會發生錯誤。
段落內的間距預設是 12,所以果字体大小超過 12,每行字就會重疊,這時就要設定間距 leading,如下所示
content.append(Paragraph(
txt1,
ParagraphStyle(
name="p1",
fontSize = 16,
leading=16,
fontName="hei")
)
)
間距
段落與段落之間如果要加上段落間距,需使用 Spacer(寬, 高),寬只要設為 1 即可,高就是距間。
ontent.append(Paragraph(...))
content.append(Spacer(1,15))
content.append(Paragraph(...))
表格
表格裏的資料, 使用二維 list 儲存,再將資料置入 Table 中。
data = [["第一格","第二格","第三格"],["第四格","第五格","第六格"],["第七格","第八格","第九格"]] table = Table( data=data, style=[ ('FONTNAME',(0, 0), (-1, -1), 'hei'), ('GRID',(0, 0), (-1, -1), 0.5, colors.black), ] )
style 的設置為 (格式名, (啟始儲存格), (結束儲存格), 樣式)。
格式名必需為大寫,常用的格式名有
FONTNAME : 字型名稱
FONTSIZE : 字型大小
GRID : 指定儲存格的格線 ,寬度可為小數
BOX : 指定儲存格的外框線,寬度可為小數
SPAN : 要合併的儲存格
VALIGN : 垂直對齊方式, 有 TOP, MIDDLE, BOTTOM三種
每一個儲存格都有其座標,座標格式為(行, 列),左上角為(0,0),第一列往右依序為(1, 0),(2, 0),(3, 0)。右下角的座標除了使用行列外,亦可使用負數表示,最右下角座標為 (-1,-1)
data=[....] table = Table(data, colWidths=[70, 150, 70, 150], rowHeights=[80,80,80,80,80, 80], style=[ ('FONTNAME', (0, 0), (-1, -1), 'hei'), ('FONTSIZE', (0, 0), (-1, -1), 20), ('GRID', (0, 0), (-1, -1), 1, colors.black), ('BOX', (0, 0), (-1, -1), 2, colors.black), ('SPAN', (1, 0), (3, 0)), ('SPAN', (1, 1), (3, 1)), ('SPAN', (1, 2), (3, 2)), ('SPAN', (1, 4), (3, 4)), ('SPAN', (1, 5), (3, 5)), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ]) content.append(t)
圖片
使用 Image()取得檔案圖片, 並設定寬高像素,最後加入 content 即可
img=Image("tiger.jpg", width=200,height=150)
content.append(img)
doc.build(content)
圖片亦可加入表格中
img=Image("tiger.jpg", width=100, height=75)
data = [[img,"第二格","第三格"],["第四格","第五格","第六格"],["第七格","第八格","第九格"]]
table = Table(
data=data,
style=[
('FONTNAME',(0,0), (-1, -1), 'hei'),
('GRID',(0, 0), (-1, -1), 0.5, colors.black),
('BOX',(1, 1), (-1, -1), 0.5, colors.blue),
]
)
pillow 轉 pdf Image
Pillow 是通用的圖片格式,比如 python-barcoce 最終都轉成 Pillow 格式再顯圖或存檔,那如何將 Pillow 置入 pdf 文件中?
Pillow 跟 reportlab 並不相容,所以需將 Pillow 儲存成檔案,再由 reportlab 讀取檔案。但硬碟這一存一讀,會讓效能降低很多,所以最通用的方法就是使用 BytesIO() 產生一個類似 ramdisk 的儲存空間,將 Pillow 以存檔的方式寫入這個記憶体,再使用 reportlab 以讀檔的方式讀取這個記憶体取得檔案內容,代碼如下。
ramdisk = BytesIO()
self.imgPil.save(ramdisk, 'JPEG')
ramdisk.seek(0)
imgPdf=pdf.Image(ramdisk)
doc = pdf.SimpleDocTemplate(
'tmp.pdf',
#imgIo,
pagesize=A4,
rightMargin=72,
leftMargin=72,
topMargin=72,
bottomMargin=18
)
content = []
content.append(imgPdf)
doc.build(content)
完整代碼如下
import copy import time from reportlab.lib import utils from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER, TA_LEFT from reportlab.lib.pagesizes import letter from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, PageBreak, Table, TableStyle from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib import colors from reportlab.graphics.shapes import Drawing from reportlab.graphics.charts.lineplots import LinePlot from reportlab.graphics.widgets.markers import makeMarker from reportlab.lib.units import inch import reportlab.rl_config reportlab.rl_config.warnOnMissingFontGlyphs = 0 from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfgen import canvas from reportlab.lib import fonts import platform from datetime import datetime class ResumeLabel(): def __init__(self, info): useros=platform.system() if useros == "Linux": pdfmetrics.registerFont(TTFont('hei', '/usr/share/fonts-droid-fallback/truetype/DroidSansFallback.ttf')) else: pdfmetrics.registerFont(TTFont('hei', 'simsun.ttc')) fonts.addMapping('song', 1, 0, 'hei') fonts.addMapping('song', 1, 1, 'hei') stylesheet = getSampleStyleSheet() normalStyle = copy.deepcopy(stylesheet['Normal']) normalStyle.fontName ='hei' normalStyle.fontSize = 20 doc = SimpleDocTemplate("tmp.pdf", pagesize=letter, rightMargin=72, leftMargin=72, topMargin=72, bottomMargin=18) story = [] styles = getSampleStyleSheet() styles.add(ParagraphStyle(name='Justify', alignment=TA_JUSTIFY)) style_title = ParagraphStyle('parrafos', alignment = TA_CENTER, fontSize = 24, fontName="hei") style_left = ParagraphStyle('parrafos', alignment = TA_LEFT, fontSize = 12, fontName="hei") story.append(Paragraph('Mahal履歷管制系統', style_title)) story.append(Spacer(1, 15)) img=Image("tmp.png", 2*inch, 0.5*inch) story.append(img) story.append(Paragraph(info["barcode"], style_title)) story.append(Spacer(1, 12)) story.append(Paragraph('Date : {0}'.format(datetime.now().strftime("%Y-%m-%d %H:%M:%S")), style_left)) story.append(Spacer(1, 12)) data=[ ['尺寸', info['dimension']], ['顏色', info['color']], ['版號', info['version']], ['半打', '{0}(KG)'.format(info['wetHalfDozen']),'一打', '{0}(KG)'.format(info['wetDozen'])], ['淨重', '{0}'.format(info['net'])], ['數量', '{0}打 {1}雙'.format(info['qtyDozen'], info['qtyPair'])] ] t = Table(data, colWidths=[70, 150, 70, 150], rowHeights=[80,80,80,80,80, 80], style=[ ('FONTNAME', (0, 0), (-1, -1), 'hei'), ('FONTSIZE', (0, 0), (-1, -1), 20), ('GRID', (0, 0), (-1, -1), 1, colors.black), ('BOX', (0, 0), (-1, -1), 2, colors.black), ('SPAN', (1, 0), (3, 0)), ('SPAN', (1, 1), (3, 1)), ('SPAN', (1, 2), (3, 2)), ('SPAN', (1, 4), (3, 4)), ('SPAN', (1, 5), (3, 5)), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ]) story.append(t) doc.build(story)
todo
import
開頭需先 import一些常用的套件, 如下
import copy import time from reportlab.lib import utils from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER, TA_LEFT from reportlab.lib.pagesizes import letter from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, PageBreak, Table, TableStyle from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib import colors from reportlab.graphics.shapes import Drawing from reportlab.graphics.charts.lineplots import LinePlot from reportlab.graphics.widgets.markers import makeMarker from reportlab.lib.units import inch import reportlab.rl_config reportlab.rl_config.warnOnMissingFontGlyphs = 0 from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfgen import canvas from reportlab.lib import fonts import platform from datetime import datetime