2020年12月4日 星期五

Python產生PDF套件Reportlab

有時真的是有需求才有動力
這次會去學這個套件
還是同事拜託能不能將舊有浮水印,印在現有的PDF文件上
然後關鍵字一下(python pdf watermark),答案就出來了
使用Reportlab搭配pypdf或pdfrw
哇靠,真是萬能的Python,好像沒什麼是搞不定的

再深入研究一下要怎麼做才可以將浮水印的圖與PDF結合
這才發現Reportlab不是單純的將圖檔合併轉成PDF的套件
而是可以獨立產生完整PDF的集合工具,舉凡寫字、畫圖、製表等等
ReportLab裡都有不少的套件可以使用
比想像中的強大非常多
那麼正式開始啦

如前所說套件很多
但我沒有需求做很華麗的PDF
所以主要使用的是pdfgen底下的Canvas(畫布)這個套件
畫布的主要參數有兩個,一個是存檔用,另一個是畫布本身的大小
存檔用的部分,可以僅使用字串,也就是檔名來處理,到時候使用save函數就可以存成檔案
或是使用byteio的套件,將PDF內容轉成binary形態,同樣也是下save函數才會有資料
畫布大小的參數,可以引入lib.pagesizes裡的A3、A4等來處理
不過都是直式的尺寸,要用橫式的,可以靠lib.pagesizes.landscape來轉換
簡易的使用流程如下:
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4, landscape

#PDF的檔案名稱是sample.pdf
pdf_name = 'sample.pdf'
#pdf的尺寸是A4大小的橫式
pdfsize = landscape(A4)
pdf_canvas = canvas.Canvas(pdf_name, pagesize=pdfsize)
"""
中間畫圖啊,寫字啊等等
"""
#產生pdf頁面
pdf_canvas.showPage() 
#存檔
pdf_canvas.save() 

主架構如上,再來就中間的畫圖與寫字
不論畫圖或寫字,顏色是很重要的,尤其是畫圖
不指定的話,畫出來就都是黑色,所以先談一下Reportlab的顏色使用
顏色函數主要有兩種形態,Stroke(筆畫)跟Fill(填滿)
依顏色形態與方式再分為CMYK(印刷四色),RGB(光三原色),直接填色跟Gray(灰階填色)
如列出來如下
canvas.setFillColorCMYK(c, m, y, k)
canvas.setStrikeColorCMYK(c, m, y, k)
canvas.setFillColorRGB(r, g, b)
canvas.setStrokeColorRGB(r, g, b)
canvas.setFillColor(acolor)
canvas.setStrokeColor(acolor)
canvas.setFillGray(gray)
canvas.setStrokeGray(gray)
填入acolor的函數,就是我稱為直接填色的部分
可以從lib.colors直接引入pink,red,blue,black等顏色
或是使用Color(r,g,b,alpha)的方式做出想要的顏色
其中r,g, b與alpha(透明)的部分一樣是從0~1的浮點數
alpha=1.0代表不透明,用0就是全透明
指定好顏色,畫出來的東西才顯示得出來

再來先講畫圖能用的函數
畫線的話是
canvas.line(x1,y1,x2,y2)
canvas.lines(linelist)
畫形狀的話有
canvas.grid(xlist, ylist) #網格
canvas.bezier(x1, y1, x2, y2, x3, y3, x4, y4) #貝茲曲線
canvas.arc(x1,y1,x2,y2) #圓弧
canvas.rect(x, y, width, height, stroke=1, fill=0) #方塊
canvas.ellipse(x1,y1, x2,y2, stroke=1, fill=0) #橢圓
canvas.wedge(x1,y1, x2,y2, startAng, extent, stroke=1, fill=0) #圓餅
canvas.circle(x_cen, y_cen, r, stroke=1, fill=0) #圓
canvas.roundRect(x, y, width, height, radius, stroke=1, fill=0)#圓角方塊
詳情的話就不講太多
簡單實作一下,畫個多啦A夢
from reportlab.lib.colors import black, red
from reportlab.lib.units import mm #避免畫太小,用mm當單位

pdf_canvas.setLineWidth(2) #因為線太細,變粗一點
pdf_canvas.setFillColorRGB(0,0.6313,0.9176) #多啦A夢的藍色
pdf_canvas.setStrokeColor(black) #線的黑色
pdf_canvas.circle(120*mm, 120*mm, 50*mm, stroke=1, fill=1)#頭
pdf_canvas.setFillColorRGB(1,1,1) #填充變白色
pdf_canvas.circle(120*mm, 107*mm, 36*mm, stroke=1, fill=1)#臉
pdf_canvas.circle(100*mm, 147*mm, 13*mm, stroke=1, fill=1)#左眼
pdf_canvas.circle(140*mm, 147*mm, 13*mm, stroke=1, fill=1)#右眼
pdf_canvas.setFillColor(red)#填充變紅色
pdf_canvas.circle(120*mm, 135*mm, 7*mm, stroke=1, fill=1)#鼻
pdf_canvas.wedge(90*mm,75*mm, 150*mm,145*mm, 0, -179, stroke=1, fill=1)#嘴
pdf_canvas.line(120*mm,128*mm,120*mm,110*mm)#正中直線
pdf_canvas.line(110*mm,127*mm,85*mm,132*mm)#左上鬍
pdf_canvas.line(110*mm,122*mm,85*mm,122*mm)#左中鬍
pdf_canvas.line(110*mm,117*mm,85*mm,112*mm)#左下鬍
pdf_canvas.line(130*mm,127*mm,155*mm,132*mm)#右上鬍
pdf_canvas.line(130*mm,122*mm,155*mm,122*mm)#右中鬍
pdf_canvas.line(130*mm,117*mm,155*mm,112*mm)#右下鬍
pdf_canvas.bezier(88*mm, 140*mm, 95*mm, 150*mm, 105*mm, 150*mm, 112*mm, 140*mm)#左微笑眼
pdf_canvas.bezier(128*mm, 140*mm, 135*mm, 150*mm, 145*mm, 150*mm, 152*mm, 140*mm)#右微笑眼



畫圖大概就這樣
比較需要解說的,大概是那個x,y位置為什麼要乘以mm
簡單地說,就是要讓畫出來的圖案確實等於印刷上的mm
如果不乘上mm呢?
那就會是常用排板軟體上的最小單位,點
根據維基百科上對於點_印刷的說明
https://zh.wikipedia.org/wiki/%E9%BB%9E_(%E5%8D%B0%E5%88%B7)
很清楚說明,1英吋等於72點
換言之1mm = 72 / 2.54 / 10 = 2.834645 點
這從Reportlib本身lib裡units.py這個檔案可以很明顯看到
裡面就是寫
inch = 72.0
cm = inch / 2.54
mm = cm * 0.1
所以前面pagesize的A4大小,就是(210*mm,297*mm)而來
直接print(A4)就會得到(595.2755905511812, 841.8897637795277)這樣的數值

畫圖完了,接下來是寫字
函數是下面這幾個
canvas.drawString(x, y, text)
canvas.drawRightString(x, y, text)
canvas.drawCentredString(x, y, text)
但,要實際使用之前還需要指定字型與大小
canvas.setFont('Times-Roman', 20)
後面的20就像上面所說,是排板軟體定義上的點
就是跟Office裡Word、Excel與Powerpoint裡面字型大小一樣的定義
如果想要縮放成自己想要的尺寸,一樣乘上mm或cm或inch就行了
當然,指定座標X,Y也是如此,跟畫圖一樣

只是文字上,對中文字來說比較麻煩的是,預設並沒有支援中文字型
這時候就需要另一個模組pdfbase裡的pdfmetrics與ttfonts.TTFont
然後指定給字型檔案位置,並註冊到系統裡
實作如下,我們使用標楷體
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

pdfmetrics.registerFont(TTFont('Kaiu', 'C:/Windows/Fonts/kaiu.ttf', subfontIndex=1)) # sufontIndex=0 會取到固定間距的「標楷體」
pdf_canvas.setFont('Kaiu', 20*mm)
pdf_canvas.drawString(50*mm, 50*mm, "這就是多啦A夢!")
我們並沒有指定顏色,所以如果在畫圖前寫字
那就會是預設的黑色
如果是在畫圖後加這段,那就會變成紅色
也就是文字的顏色是靠Fill(填滿)來決定的

以上是單頁的PDF做法,那多頁呢?
這個很簡單,在canvas.showPage()後加入新的內容
再進行canvas.showPage()就是新的一頁了
以上面畫圖跟文字顯示為例
在這兩段中間,多加上canvas.showPage()
就會發現,圖在第一頁,然後文字在第2頁
而在第2頁的文字,顏色會是預設的黑色
也就是showPage()後一切設定重來
最後要記得存檔,執行canvas.save()才會有檔案產生

Reportlab裡的功能不止這些
還有插入圖片,更方便的排板與繪圖功能
我就不一一說明了
在官方的網站上可以下載UserGuide去研究
https://www.reportlab.com/dev/docs/
算寫的蠻詳細的
還是看不太懂的話,稍微跑一下程式碼應該也能理解
有了這套件,對需要產生報表的人非常方便

沒有留言: