Word 转手写体
文章目錄
- 介紹與準備
- 聲明
- Tkinter 指南
- 開發環境
- Word 轉手寫體代碼
- 主程序
- 背景圖像
- 字體下載與設置
- 主程序啦!!
- 代碼測試與結果
- 鏈接 Word 文檔
- 從 Word 文檔獲取輸入的代碼
- 新建一個 Word 測試文檔
- 代碼調試
- 代碼運行準備
- GUI 設計
- 整體代碼
- GUI 骨架
- 文件準備
- 代碼
- 軟件的使用與后續開發
- 使用效果
- 后續完善
介紹與準備
聲明
要源碼請關注后留言
不用做任何商業用途
贈人玫瑰,手有余香,點個贊再走唄
Tkinter 指南
這里使用的是 Tkinter 來開發一款軟件的,如果不是用 Tkinter,請移步。
首先推薦一些 Tkinter 的教程吧:
Tkinter GUI 01
Tkinter GUI 02
Tkinter GUI 03
Tkinter GUI 04
Tkinter GUI 05
Tkinter GUI 06
Tkinter GUI 07
Tkinter GUI 08
然后,是一些有用的參考資料和書籍的下載地址:
Python GUI Tkinter 參考資料
再安裝如下模塊;
- handright
- docx:用于打開 Word
- PIL:用于保存圖像,創建手寫字體模板
開發環境
這里使用 Eclipse 的 PyDev 開發我們的 GUI 應用,有關 Eclipse 的內容,可以參閱:
Eclipse 安裝教程、PyDev 插件下載教程與有關設置
另外 PyDev 由于是一個插件,所以,其版本更新需要我們重新卸載、再安裝。讀者們下載了 PyDev 插件之后,過一陣子,就會彈出下面的一個對話框:
Word 轉手寫體代碼
首先,我們忽略 GUI,看一下我們的 Word 轉手寫體代碼。首先,在 Eclipse 中新建一個 Python 的項目:
主程序
首先,在我們新建的項目中,添加一個 .py 文件,如下所示:
背景圖像
為了讓 Word 轉換起來更像是手寫體,我們還要添加一個背景,這里用的背景是我們童年的回憶——貓狗本,如下所示。將這個背景圖像,命名為 background_01.jpg,并放在我們項目文件的 src(需要自己新建)文件中。
字體下載與設置
首先要轉換成手寫體,我們需要用一個·手寫體的字體,再將其加入隨機擾動,來模擬手寫體的效果。
大家可以從這個網站上下載一些手寫體,免費的:
- 手寫體下載地址
- 推薦一款瘦金體:http://www.zhaozi.cn/html/fonts/china/ruizibige/2020-05-25/26424.html
然后,同樣在項目文件夾中,添加一個 font 文件夾。然后,將下載完的手寫體字體,放到文件夾中。我大概是下載了這么多的手寫體字體,大家可以參考一下:
主程序啦!!
之后,我們要往這個文件中,加入我們的主要程序代碼,如下所示:
from PIL import Image, ImageFont import numpy as np from handright import Template, handwrite from multiprocessing import Pool import time text = """ 鳥飛鵝跳,月上中梢,目上朱砂,已異非巳,勺旁傍白,萬事開頭,工戈不全,雨下摯友,稱斷人和 """background=Image.open(r'./src/background_01.jpg') width, height = background.size background = background.resize((np.int(3*width),np.int(2.5*height)),resample=Image.LANCZOS)if __name__ == "__main__":time_start = time.time() #計時開始template = Template(background=background, #選擇北京font_size=100, #選擇字體大小font=ImageFont.truetype(r".\font\瘦金簡體.ttf"), #選擇字體line_spacing=150,fill=0, # 字體“顏色”left_margin=250, #選擇字體于背景邊緣的間距。top_margin=-30,right_margin=100,bottom_margin=100,word_spacing=15,line_spacing_sigma=6, # 行間距隨機擾動font_size_sigma=1, # 字體大小隨機擾動word_spacing_sigma=3, # 字間距隨機擾動end_chars=",。;、“” ", # 防止特定字符因排版算法的自動換行而出現在行首perturb_x_sigma=3, # 筆畫橫向偏移隨機擾動perturb_y_sigma=3, # 筆畫縱向偏移隨機擾動perturb_theta_sigma=0.03, # 筆畫旋轉偏移隨機擾動)with Pool() as p: #多線程,加快程序的運行。images = handwrite(text, template, mapper=p.map)for i, im in enumerate(images): #輸出結果assert isinstance(im, Image.Image)im.save(r".\output\{}.jpg".format(i)) #將輸出結果保存到路徑中time_end = time.time() #計時結束print(time_end-time_start) #輸出運行時間這里要注意,im.save(r".\output\{}.jpg".format(i)) 將手寫體弄成 jpg 圖片之后,就會保存到一個叫 output 的文件夾中,并以 0 開始,命名圖像。這里的 output 文件夾,要事先創建好,否則報錯。當然,也可以添加一條代碼來防止這樣的情況。這條代碼是什么呢?評論區走起~~
代碼測試與結果
然后,運行一下我們的代碼,會跳出一下錯誤:
File “E:\java-2020-03\eclipse\workspace\Word2write_Principle\principle1.py”, line 7
SyntaxError: Non-UTF-8 code starting with ‘\xc4’ in file E:\java-2020-03\eclipse\workspace\Word2write_Principle\principle1.py on line 8, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
這是因為,我們的代碼的第7句,text = """ 鳥飛鵝跳,月上中梢,目上朱砂,已異非巳,勺旁傍白,萬事開頭,工戈不全,雨下摯友,稱斷人和 """出現了中文,所以識別不出來。只要在代碼的開頭,加入一句 #coding=gbk或者# -*- coding:utf-8 -*,再次運行即可。
效果如下:
怎么樣?心曠神怡對不對?除了有點東倒西歪以外,基本上是滿足了我們的需求啊。大家也可以用其他字體,來體驗一下效果。這里用李國夫手寫體,來實驗一下,效果如下:
鏈接 Word 文檔
我們的需求是,輸入一個 Word 文檔,再將其轉換為手寫體。而不是每一次運行,都要設置 text 變量。為了實現這個目的,我們需要另外寫一個程序,來讀寫 Word 文檔。
從 Word 文檔獲取輸入的代碼
再次新建一個 .py 文件,命名為 principle2.py ,并鍵入如下代碼:
# coding: utf-8 import docx def read_docx(docx_path = r'./input/demo.docx'):docStr = docx.Document(docx_path)txt = []for para in docStr.paragraphs: # 如果檢測到 word 中有居中的,就在兩邊加上空格。parStr = para.textif_center = para.paragraph_format.alignmentif if_center:parStr = parStr.center(48)else:parStr = ' ' + parStrtxt.append(parStr)return '\n'.join(txt)if __name__ == "__main__":txt = read_docx()新建一個 Word 測試文檔
為了保證程序的正確運行,我們還需要在項目文件夾中,創建一個新的文件夾,命名為 input。之后,再在里面添加一個 .docx 文件,命名為 demo.docx,輸入以下文字:
代碼調試
之后,我們可以運行一下 principle2.py 文件,用 debug 來觀察程序是否運行順暢。首先,在 return 處設置斷點,按 F11 進行調試,可以看到程序運行到斷點出,停止運行。并在 Varialbe 窗口出現一些變量(右邊)
我們可以直接在 Variable 窗口,點擊 txt,來觀察變量 txt 的取值。也可以在 command 窗口,輸入變量名 txt,來觀察變量的取值。結果如下:
[’ 謎題一個 ‘, ’ 鳥飛鵝跳,月上中梢,目上朱砂,已異非巳,勺旁傍白,萬事開頭,工戈不全,雨下摯友,稱斷人和?!? ’ 答案是:—— 我要用自己的方式來愛你!!!’]
之后,在按一下 F5(step into吧,好像是),再按一次(共兩次),就可以結束了。有關調試的內容,就不再過多說明了。
代碼運行準備
測試完畢,效果可嘉。為了讓上述的 txt 變量讀入到 principle1.py 中,以供我們的程序使用,我們還要在 principle1.py 文件中,添加一些代碼,以保證程序的運行,如下:
...... from principle2 import * #text = """ #鳥飛鵝跳,月上中梢,目上朱砂,已異非巳,勺旁傍白,萬事開頭,工戈不全,雨下摯友,稱斷人和 #""" ...... text = read_docx(r'./input/demo.docx') ......再次運行 principle1.py,在 console 那里,可以看到程序運行總時間為 4.211 秒,不知道是字數多了,還是程序變復雜的緣故?想知道答案嗎?評論區提問一下吧!
來看一下輸出結果,在 output 文件夾中,打開圖像 0.jpg:
課題看到,我們在 docx 文件中,第一行即標題行,并沒有用兩個回車,但是為什么會空了一行,我們的理想效果應該是這樣的:
那么,怎么做呢?評論區回答或者提問一下吧!
GUI 設計
從代碼中,可以看出,我們需要的輸入變量一共有:
text:docx 文件路徑或者直接輸入
background:背景
fontsize:字體大小
font :字體
line_spacing : 行間距
fill : 字體顏色
當然,還有其他的。為了簡化設計,我們將不考慮其他東西~
為此,我們畫出 GUI 的草圖,如下所示:
草圖要有草圖的樣子,不要笑了[笑哭]。上面的草圖是頁面1,頁面2 我打算弄成一個預覽窗口。這個窗口能夠展示一個 demo,從而方便我們調整字體大小等參數。
整體代碼
我們再次新建一個項目,命名為 Word2write,并在項目文件下面,新建一個 GUI 文件夾,再在文件夾下創建兩個 .py 文件: Tab1.py,Tab2.py。
GUI 骨架
至于具體實現,我們大致可以按照如下骨架來弄,如果覺得下圖很難看懂,那么就乖乖看一下上面羅列的教程吧:
文件準備
首先,我們的項目文件已經有 GUI 文件夾,此后還有 Tab1.py Tab2.py
之后,我們還需要創建一個 Fun 文件夾,用來保存一些邏輯功能。我們在下面創建兩個文件: fun1.py fun2.py
之后,還需要在項目文件中,創建 Input、Output、Font、Background 文件夾。
在 Backgroun 文件夾中放入背景圖片文件:background_01.jpg
并在項目文件中,放入 test.jpg 文件
代碼
首先是 fun1.py:
# coding: utf-8 from PIL import Image, ImageFont import numpy as np from handright import Template, handwrite from multiprocessing import Pool import time from Fun.fun2 import *def trans(input_path,output_path,font_path,line_spacing,if_test=False):background=Image.open(r'../Background/background_01.jpg')width, height = background.sizebackground = background.resize((np.int(3*width),np.int(2.5*height)),resample=Image.LANCZOS)if not if_test:text = read_docx(input_path)else:text = """卿尋鯉影剔浮英, 我恨浮英掩玉卿。難教芳心知我心, 孤燭半影又天明。"""time_start = time.time()template = Template(background=background,font_size=100,font=ImageFont.truetype(font_path),line_spacing=line_spacing,fill=0, # ���塰��?��left_margin=250,top_margin=-30,right_margin=100,bottom_margin=100,word_spacing=15,line_spacing_sigma=6, # �м������?�font_size_sigma=1, # �����С����?�word_spacing_sigma=3, # �?������?�end_chars=",。;、“”", # ��?�?��?����?��?���?����ж�����������perturb_x_sigma=3, # �?�����?������?�perturb_y_sigma=3, # �?�����?������?�perturb_theta_sigma=0.03, # �?���??������?�)with Pool() as p:images = handwrite(text, template,mapper=p.map)for i, im in enumerate(images):assert isinstance(im, Image.Image)if not if_test:im.save(output_path+"\{}.jpg".format(i))else:im.save(r"../test.jpg")time_end = time.time()print(time_end-time_start)然后是 fun2.py:
import docx def read_docx(docx_path):docStr = docx.Document(docx_path)txt = []for para in docStr.paragraphs:parStr = para.textif_center = para.paragraph_format.alignmentif if_center:parStr = parStr.center(30)else:parStr = ' ' + parStrtxt.append(parStr)return '\n'.join(txt)if __name__ == "__main__":txt = read_docx()然后是 Tab1.py:
# -*- coding: utf-8 -*-import tkinter as tk from tkinter import ttk from tkinter import messagebox as mBox from tkinter import Menu from sys import exit from threading import Thread from time import sleep from queue import Queue import os from os import path from tkinter import filedialog as fd from Tab2 import Tab2 import sys sys.path.append('../') from PIL import Image, ImageFont import numpy as np from handright import Template, handwrite from multiprocessing import Pool import time from Fun.fun1 import * from PIL import ImageTk,Imageclass OOP():def __init__(self):self.win = tk.Tk()self.win.title('妙筆生花')self.win.iconbitmap(r'./app.ico')self._createWidget()def _runTrans(self,input_path,output_path,font_path,line_spacing): trans(input_path,output_path,font_path,line_spacing)def _testTrans(self,input_path,output_path,font_path,line_spacing): trans(input_path,output_path,font_path,line_spacing,if_test=True)self.tab2Widget.createImage()self.tab2Widget._image = ImageTk.PhotoImage(self.tab2Widget.pil_image)self.tab2Widget.canvas.create_image(20,20,anchor='nw',image=self.tab2Widget._image)def createRunThread(self):input_path = str(self.inEntry.get())output_path = str(self.outEntry.get())font_path = str(self.fntEntry.get()) line_spacing = float(str(self.lineSpcEty.get())) run = Thread(target=self._runTrans,args=(input_path,output_path,font_path,line_spacing)) #創建一個線程,線程運行 methodInAThreadrun.setDaemon(True) #將線程設置成守護線程run.start()def createTestThread(self):input_path = str(self.inEntry.get())output_path = str(self.outEntry.get())font_path = str(self.fntEntry.get()) line_spacing = float(str(self.lineSpcEty.get())) test = Thread(target=self._testTrans,args=(input_path,output_path,font_path,line_spacing))test.setDaemon(True)test.start()def _clickRunBut(self):self.createRunThread()def _clickTstBut(self):self.createTestThread()def _getFileName(self):fDir = os.path.join( os.path.dirname(__file__),'../..') #���?��?�??��fName = fd.askopenfilename(parent=self.inOuFrm,initialdir=fDir)fPath = path.dirname(fName)self.inEntry.delete(0,tk.END) self.inEntry.insert(0,fName) def _getFileName2(self):fDir = os.path.join( os.path.dirname(__file__),'..') #���?��?�??��fName = fd.askdirectory(parent=self.inOuFrm,initialdir=fDir)fPath = path.dirname(fName)self.outEntry.delete(0,tk.END) self.outEntry.insert(0,fName) def _getFileName3(self):fDir = os.path.join( os.path.dirname(__file__),'..') #���?��?�??��fName = fd.askopenfilename(parent=self.inOuFrm,initialdir=fDir)fPath = path.dirname(fName)self.fntEntry.delete(0,tk.END) self.fntEntry.insert(0,fName) def _quit(self):self.win.quit()self.win.destroy()exit()def _createWidget(self):self.menuBar = Menu(self.win)self.win.configure(menu=self.menuBar)self.startMenu = Menu(self.menuBar,tearoff=0)self.startMenu.add_command(label='保存為')self.startMenu.add_separator()self.startMenu.add_command(label='退出',command=self._quit)self.menuBar.add_cascade(label='開始',menu=self.startMenu)self.helpMenu = Menu(self.menuBar,tearoff=0)self.helpMenu.add_command(label='幫助')self.helpMenu.add_command(label='關于')self.menuBar.add_cascade(label='其他',menu=self.helpMenu)self.tabControl = ttk.Notebook(self.win)self.tab1 = ttk.LabelFrame(self.tabControl)self.tab2 = ttk.LabelFrame(self.tabControl)self.tabControl.add(self.tab1,text='參數設置')self.tabControl.add(self.tab2,text='效果預覽')self.tabControl.pack(fill='both',expand=1)self.zhuo = ttk.Frame(self.tab1)self.zhuo.grid(column=0,row=0)self.inOuFrm = ttk.LabelFrame(self.zhuo,text='文件管理')self.inOuFrm.grid(column=0,row=0,sticky='W')self.inBut = ttk.Button(self.inOuFrm,text='輸入文件',command=self._getFileName)self.inBut.grid(column=0,row=0)self.fDir = os.path.abspath(os.path.join( os.path.dirname(__file__),".."))self.default_value = tk.StringVar()self.default_value.set(self.fDir+'\Input\demo.docx')self.default_value2 = tk.StringVar()self.default_value2.set(self.fDir+'\Output')self.default_value3 = tk.StringVar()self.default_value3.set(self.fDir+'\Font\瘦金簡體.ttf')self.inEntry = ttk.Entry(self.inOuFrm,width=60,textvariable=self.default_value)self.inEntry.grid(column=1,row=0)self.inEntry.focus()self.outBut = ttk.Button(self.inOuFrm,text='輸出文件夾',command=self._getFileName2)self.outBut.grid(column=0,row=1)self.outEntry = ttk.Entry(self.inOuFrm,width=60,textvariable=self.default_value2)self.outEntry.grid(column=1,row=1)self.fntBut = ttk.Button(self.inOuFrm,text='搜索字體',command=self._getFileName3)self.fntBut.grid(column=0,row=2)self.fntEntry = ttk.Entry(self.inOuFrm,width=60,textvariable=self.default_value3)self.fntEntry.grid(column=1,row=2)for child in self.inOuFrm.winfo_children():child.grid_configure(padx=10,pady=10,sticky='W')self.miniCon = tk.Frame(self.zhuo)self.miniCon.grid(column=0,row=1,sticky='W')ttk.Label(self.miniCon,text='字體大小').grid(column=0,row=0)self.fntSizeCom = ttk.Combobox(self.miniCon)self.fntSizeCom['value']=(80,90,100)self.fntSizeCom.grid(column=1,row=0)self.fntSizeCom.current(2)ttk.Label(self.miniCon,text='字體顏色').grid(column=2,row=0)self.fntColCom = ttk.Combobox(self.miniCon)self.fntColCom['value']=(0)self.fntColCom.grid(column=3,row=0,sticky='W')self.fntColCom.current(0)ttk.Label(self.miniCon,text='行間距').grid(column=0,row=1)self.default_value4 = tk.StringVar()self.default_value4.set(150)self.lineSpcEty = ttk.Entry(self.miniCon,textvariable=self.default_value4)self.lineSpcEty.grid(column=1,row=1) self.runBut = ttk.Button(self.miniCon,text='轉換',command=self._clickRunBut)self.runBut.grid(column=0,row=2)self.testBut = ttk.Button(self.miniCon,text='測試',command=self._clickTstBut)self.testBut.grid(column=1,row=2)for child in self.miniCon.winfo_children():child.grid_configure(padx=10,pady=10,sticky='W') """頁面2開發"""self.tab2Widget = Tab2(self.tab2) oop = OOP() oop.win.mainloop()然后是Tab2.py
# -*- coding: utf-8 -*-import tkinter as tk from tkinter import ttkfrom PIL import ImageTk,Image class Tab2():def __init__(self,tab):self._createWidget(tab)def _createWidget(self,tab):self.zhuo = tk.Frame(tab,bg='red')self.zhuo.grid(column=0,row=0)self.canvas = tk.Canvas(self.zhuo)self.createImage()self.image = ImageTk.PhotoImage(self.pil_image) #這里就不是 tk.PhotoImage 了self.canvas.create_image(20,20,anchor='nw',image=self.image) #打開圖像self.canvas.grid(row=0,column=0,columnspan=2)def _resize(self,w, h, w_box, h_box, pil_image): f1 = 1.0*w_box/w f2 = 1.0*h_box/h factor = min([f1, f2]) width = int(w*factor) height = int(h*factor) self.pil_image = pil_image.resize((width, height), Image.ANTIALIAS) def createImage(self): pil_image = Image.open(r'../test.jpg') #記得在縮放圖像之前,要用 Image 模塊打開。w, h = pil_image.sizew_box = 400 #大伙可以調節這個,來設置圖片的大小。當然,也建議讀者們把他設為 公有的。h_box = 500self._resize(w,h,w_box,h_box,pil_image)運行 Tab1.py,再點擊運行按鈕時,會出現多個彈窗的情況,如下所示:
而我們要的效果是,點擊后不能出現這樣的彈窗。原因在哪里呢?,請到評論區提問或回答,我會回復的哦。
軟件的使用與后續開發
使用效果
解決完上述彈窗問題之后,我們就來驗證一下我們的軟件吧:
首先運行 Tab1.py,就會彈出軟件窗口,再點擊測試,并在效果預覽頁面觀察效果圖,如下所示:
也可以修改字體,點擊搜索字體,選擇李國夫手寫體,再點測試:
在 Input 中新建一個 demo.docx 文件,并任意輸入內容,我這里輸入的是:
然后,點擊轉換,就可以在 Input 文件夾中,生成一個 0.jpg 的文件,就可以啦:
后續完善
其實還有很多要完善的地方。比如運行的時候,顯示進度條。而且還要提高運行效率。除此之外,一些參數的設置要更加完善。如果大家喜歡這個軟件的話,可以關注我。我把 Github 源碼鏈接發給你們,咱們一起開發和完善。
另外,還要轉換成 exe 文件,這里就不再展示了。大家可以參考上面羅列的教程。
為了提高效率、以及實用性,我們還需要用 Java 來開發一個一模一樣的。
總結
- 上一篇: springboot 分页查询参数_10
- 下一篇: ADO.NET多值查询