檔案建立日期: 2019/12/22
最後修訂日期: None
相關軟體資訊:
| Windows 10 | Python 3.7.2 | PySimpleGUI 4.13.1
說明: 所有內容歡迎引用, 只需註明來源及作者, 本文內容如有錯誤或用詞不當, 敬請指正.
標題: 002.08 文字輸出排版 PySimpleGUI
有些圖形面的文字輸出常有困難, 如:
- 寛度設定時, 文字字數及畫面點數混用, 重點是兩者之間的轉換常常都有問題
- 文字輸出有按字母, 也有按字來轉換行, 對ASCII和Unicode間的處理又不是很恰當.
- 輸出常沒作右邊對齊.
因此利用繪圖的方式來輸出文字, 自己作所有的處理. 以下是輸出的畫面, 上面是所有可以設定的引數. 文字塊可以重迭也可以移動, 也可以是透明的.
程式碼
import PySimpleGUI as sg
import ctypes
ctypes.windll.user32.SetProcessDPIAware() # Set unit of GUI to pixels
alphabet = [chr(i) for i in range(48, 58)]+[
chr(i) for i in range(65, 91)]+[
chr(i) for i in range(97,123)]
ASCII = [chr(i) for i in range(256)]
class Text():
def __init__(self, draw):
self.draw = draw # where to draw
self.data = {} # All information
def clear(self):
self.bg_color = '' # background color
self.chars = 0 # line width in char
self.distance = [] # pixels to insert into lines
self.font = '' # Font
self.font_size = 0 # Font size
self.height = 0 # Height of Text area in pixel
self.ids = [] # All id drawn
self.lines = 0 # Lines after wrapped
self.pad = 0 # pixels offset to position
self.position = (0,0) # Left-top position of text
self.text = '' # Text
self.color = '' # Text Color
self.text_wrap = [] # Wrapped text
self.width = 0 # Line Width in pixel
def Delete(self, index):
if index not in self.data:
return
for i in self.data[index]['ids']:
self.draw.DeleteFigure(i)
del self.data[index]
def DrawText(self, text, chars=40, pad=6, x=0, y=0,
color='black', bg_color=None, line_color=None,
family='Courier', font_size=12,
bold=False, italic=False, underline=False, overstrike=False):
# Draw text on canvas
if text is '':
return None
self.clear()
self.width = chars*font_size+2*pad
self.text = text
self.chars = chars
self.pad = pad
self.color = color
self.bg_color = bg_color
self.position = (x, y)
self.font_size = font_size
bold = 'bold ' if bold else ''
italic = 'italic ' if italic else ''
underline = 'underline ' if underline else ''
overstrike = 'overstrike ' if overstrike else ''
style = (bold + italic + underline + overstrike).strip()
if style is '':
font = (family, font_size)
else:
font = (family, font_size, style)
self.font = font
X = x + pad
Y = y - pad
self.text_wrap, self.distance, self.lines = self.wrap(
text, chars, font_size)
self.height = self.lines*2*self.font_size+2*self.pad
if self.bg_color is not 'None':
id_rectangle = self.draw.DrawRectangle((x, y),
(x+self.width,y-self.height), line_width=1,
fill_color=bg_color, line_color=line_color)
self.ids.append(id_rectangle)
for i in range(self.lines):
self.DrawTextLine(self.text_wrap[i], X, Y, self.distance[i],
font, font_size, color, bg_color)
Y -= font_size*2
index_dict = 0 if len(self.data)==0 else max(self.data.keys())+1
self.save_data(index_dict)
return index_dict
def DrawTextLine(self, text, x, y, dist, font, font_size, color, bg_color):
# Draw one text line
if text == '':
return
X = x
Y = y - font_size
step = dist//len(text) + 1
for i in range(len(text)):
offset = font_size//2 if text[i] in ASCII else font_size
id_text = self.draw.DrawText(text[i], (X+offset,Y), font=font,
text_location='center',color = color)
self.ids.append(id_text)
d = step if dist > 0 else 0
X += offset*2+d
dist -= step
def length(self, text, font_size):
# Caluclate width in pixel just by font size
width = 0
for i in range(len(text)):
width += font_size if text[i] in ASCII else 2*font_size
return width
def move_delta(self, index, dx, dy):
# Move object on canvas by delta position
if index not in self.data:
return
for i in self.data[index]['ids']:
self.draw.MoveFigure(i, dx, dy)
self.data[index]['position'] = (
self.data[index]['position'][0]+dx,
self.data[index]['position'][1]+dy)
def move(self, index, x, y):
# Move object on canvas by absolute position
dx = x - self.data[index]['position'][0]
dy = y - self.data[index]['position'][1]
self.move_delta(index, dx, dy)
def save_data(self, index):
# Save all data for text draw
self.data[index] = {}
self.data[index]['bg_color' ] = self.bg_color
self.data[index]['chars' ] = self.chars
self.data[index]['distance' ] = self.distance
self.data[index]['font' ] = self.font
self.data[index]['font_size' ] = self.font_size
self.data[index]['height' ] = self.height
self.data[index]['ids' ] = self.ids
self.data[index]['lines' ] = self.lines
self.data[index]['pad' ] = self.pad
self.data[index]['position' ] = self.position
self.data[index]['text' ] = self.text
self.data[index]['color' ] = self.color
self.data[index]['text_wrap' ] = self.text_wrap
self.data[index]['width' ] = self.width
def split(self, text):
# Consecutive numbers and English letters can be used as one word,
# and every other letter is considered as one word.
if text is '':
return []
result = []
string = ''
for i in range(len(text)):
if text[i] in alphabet:
string += text[i]
else:
if string != '':
result.append(string)
string = ''
result.append(text[i])
if string != '':
result.append(string)
return result
def wrap(self, text, chars, font_size):
# wrap text to lines and record the pixels required for right alignment.
# Get the number of lines of formatted text at the same time.
words = self.split(text)
long = 0
temp = ''
result = []
distance = []
width = chars*font_size
for word in words:
l = self.length(word, font_size)
if word in '\r\n':
result.append(temp.strip())
distance.append(0)
temp = ''
long = 0
elif long+l > width:
temp = temp.strip()
result.append(temp)
distance.append(width-self.length(temp, font_size))
temp = word
long = l
else:
temp += word
long += l
if temp != '':
result.append(temp)
distance.append(0)
return result , distance, len(result)
text1 = ("Python(英國發音:/ˈpaɪθən/ 美國發音:/ˈpaɪθɑːn/)是一種廣泛使用的"
"解釋型、高階程式設計、通用型程式語言,由吉多·範羅蘇姆創造,第一版釋出於"
"1991年。\n可以視之為一種改良(加入一些其他程式語言的優點,如面向對"
"象)的LISP。[來源請求]Python的設計哲學強調程式碼的可讀性和簡潔的語法("
"尤其是使用空格縮排劃分程式碼塊,而非使用大括號或者關鍵詞)。相比於C++"
"或Java,Python讓開發者能夠用更少的程式碼表達想法。\n不管是小型還是大型"
"程式,該語言都試圖讓程式的結構清晰明瞭。")
text2 = ("Python is an interpreted, high-level, general-purpose programming "
"language. Created by Guido van Rossum and first released in 1991, "
"Python's design philosophy emphasizes code readability with its "
"notable use of significant whitespace.\nIts language constructs and "
"object-oriented approach aim to help programmers write clear, "
"logical code for small and large-scale projects.")
font = 'courier 12 bold'
sg.change_look_and_feel('Dark Blue 3')
height = 800
def txt(text):
return sg.Text(text, font=font, size=(18,1), justification='right')
def cmb(default, key, value):
return sg.Combo(default_value=default, size=(12,1), values=value,
enable_events=True, key=key, font=font)
layout = [[
txt('chars' ), cmb( 80 ,'chars' ,(20,40,60,80)),
txt('pad' ), cmb( 20 ,'pad' ,(20,40,60,80)),
txt('x' ), cmb(150 ,'x' ,(0, 150, 300, 800, 1200)),
txt('y' ), cmb(700 ,'y' ,(200,500,700,800))],
[txt('family' ), cmb('times','family' ,('courier','arial','times')),
txt('font_size' ), cmb(16 ,'font_size' ,(8, 10, 12, 16, 24)),
txt(' ' ), sg.Button('Move', font=font, size=(13,1))],
[txt('bold' ), cmb('False','bold' ,('False','True')),
txt('italic' ), cmb('False','italic' ,('False','True')),
txt('underline' ), cmb('False','underline' ,('False','True')),
txt('overstrike'), cmb('False','overstrike',('False','True'))],
[txt('color' ), cmb('black','color' ,('black','blue','white')),
txt('bg_color' ), cmb('white','bg_color' ,('white','blue','green','No')),
txt('line_color'), cmb('black','line_color',('black','blue','green'))],
[sg.Graph(canvas_size=(1600, height),pad=(0,0), key='Graph',
graph_bottom_left=(0,0), graph_top_right=(1600, height))]]
window = sg.Window('Wrap Text by DrawText', layout=layout, finalize=True)
draw = window['Graph']
T = Text(draw)
chars = 80
pad = 20
x = 150
y = 700
color = 'black'
bg_color = 'white'
line_color = 'black'
family = 'courier'
font_size = 16
bold = False
italic = False
underline = False
overstrike = False
index1 = T.DrawText(text1, chars=chars, pad=pad, x=x, y=y,
color=color, bg_color=bg_color, line_color=line_color,
family=family, font_size=font_size, bold=bold, italic=italic,
underline=underline, overstrike=overstrike)
index2 = T.DrawText(text2, chars=chars, pad=pad, x=x, y=y-300,
color=color, bg_color=bg_color, line_color=line_color,
family=family, font_size=font_size, bold=bold, italic=italic,
underline=underline, overstrike=overstrike)
while True:
event, values = window.read()
if event == None : break
elif event == 'chars' : chars = values['chars']
elif event == 'pad' : pad = values['pad']
elif event == 'x' : x = values['x']
elif event == 'y' : y = values['y']
elif event == 'color' : color = values['color']
elif event == 'bg_color' : bg_color = values['bg_color']
elif event == 'line_color' : line_color = values['line_color']
elif event == 'family' : family = values['family']
elif event == 'font_size' : font_size = values['font_size']
elif event == 'bold' :
bold = True if values['bold']=='True' else False
elif event == 'italic' :
italic = True if values['italic']=='True' else False
elif event == 'underline' :
underline = True if values['underline']=='True' else False
elif event == 'overstrike' :
overstrike = True if values['overstrike']=='True' else False
if event == 'Move':
pos = ((20, 0),(20, 0),(20, 0),(20, 0),(10, 0),
(0,-20),(0,-20),(0,-20),(0,-20),(0,-20),
(-20,0),(-20,0),(-20,0),(-20,0),(-20,0),
(0, 20),(0, 20),(0, 20),(0, 20),(0, 20))
for dx, dy in pos:
T.move_delta(index1, dx, dy)
T.move_delta(index2, dx, dy)
window.refresh()
else:
T.Delete(index1)
T.Delete(index2)
index1 = T.DrawText(text1, chars=chars, pad=pad, x=x, y=y,
color=color, bg_color=bg_color, line_color=line_color,
family=family, font_size=font_size, bold=bold, italic=italic,
underline=underline, overstrike=overstrike)
index2 = T.DrawText(text2, chars=chars, pad=pad, x=x, y=y-300,
color=color, bg_color=bg_color, line_color=line_color,
family=family, font_size=font_size, bold=bold, italic=italic,
underline=underline, overstrike=overstrike)
window.close()