002.08 文字輸出排版 PySimpleGUI

Jason990420發表於2019-12-22

檔案建立日期: 2019/12/22
最後修訂日期: None
相關軟體資訊:

Win 10 Python 3.7.2 PySimpleGUI 4.13.1

說明: 所有內容歡迎引用, 只需註明來源及作者, 本文內容如有錯誤或用詞不當, 敬請指正.

標題: 002.08 文字輸出排版 PySimpleGUI

有些圖形面的文字輸出常有困難, 如:

  1. 寛度設定時, 文字字數及畫面點數混用, 重點是兩者之間的轉換常常都有問題
  2. 文字輸出有按字母, 也有按字來轉換行, 對ASCII和Unicode間的處理又不是很恰當.
  3. 輸出常沒作右邊對齊.

因此利用繪圖的方式來輸出文字, 自己作所有的處理. 以下是輸出的畫面, 上面是所有可以設定的引數. 文字塊可以重迭也可以移動, 也可以是透明的.

002.08 文字輸出排版 PySimpleGUI

程式碼

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()
本作品採用《CC 協議》,轉載必須註明作者和本文連結
Jason Yang

相關文章