Tkinter和32位Unicode复制 - 有任何修复方法吗?

7

我只想展示芯片,但是我得到了芯片戴尔。

无论我放入哪个32位字符,看起来tkinter都会复制它们 - 不仅仅是花栗鼠

我在想,也许我必须将它们渲染为png,然后将它们作为图像放置,但那似乎有点...笨重。

还有其他解决方案吗? Tkinter计划修复这个问题吗?

import tkinter as tk

# Python 3.8.3
class Application(tk.Frame):
    def __init__(self, master=None):
        self.canvas = None
        self.quit_button = None
        tk.Frame.__init__(self, master)
        self.grid()
        self.create_widgets()

    def create_widgets(self):
        self.canvas = tk.Canvas(self, width=500, height=420, bg='yellow')
        self.canvas.create_text(250, 200, font="* 180", text='\U0001F43F')
        self.canvas.grid()

        self.quit_button = tk.Button(self, text='Quit', command=self.quit)
        self.quit_button.grid()

app = Application()
app.master.title('Emoji')
app.mainloop()

Chip and Dale on Mac OS

  • 显然,在Windows上可以正常运行-所以可能是一个MacOS的问题。
  • 我在两台不同的Mac上运行了它-它们都是最新的操作系统Catalina 10.15.5-并且两者都显示出了这个问题。
  • 这个Bug在标准的Python安装程序中显示,它是从python.org下载的-Python 3.8.3和Tcl/Tk 8.6.8。
  • 据说使用Tcl/Tk 8.6.10可能会修复此问题,但我不知道如何使用常规安装程序升级Tcl/Tk。
  • 这也被报告为缺陷cf。https://bugs.python.org/issue41212

其中一位Python贡献者认为TCL/Tk不支持可变宽度编码(它总是内部转换固定宽度编码),这表明Tcl/Tk不适用于通用的UTF-8开发。


无法重现您的问题。在我的电脑上只会显示一个松鼠。 - Kevin Mayo
1
你试过使用等效的\uD83D\uDC3F吗? - Jerry
2
我在我的Mac(Catalina)上遇到了同样的问题。有趣的是,如果在其前面放置两个空格,则可以正确地工作(text =' \ U0001F43F'),但对于多个32位代码点的更复杂的表情符号,这种漏洞不起作用。 - jdaz
@Konchog,您能否澄清一下为什么UTF-16解决方案不适用于您的情况? - Space
@Space,是的-这并没有真正解决问题-即TCL/TK不支持星际字符。其次,它在我的机器上不起作用-我只看到一个黄色矩形。如果我按照您的描述操作,我会看到错误:“UnicodeEncodeError:'utf-8'编解码器无法在位置0-1中编码字符:不允许代理项”。最好的解决方法是由jdaz提供的,但正如他所指出的那样,这只是一个解决方法。错误报告消息遵循Donal的相同分析。 - Konchog
显示剩余5条评论
3个回答

7
基本问题是Tcl和Tk对非BMP(Unicode基本多语言平面)字符不太友好。在8.6.10之前,会发生什么是任何人的猜测;实现简单地假设这样的字符不存在,并且当它们实际出现时已知存在错误(有几个与此相关的票据)。8.7将有更强的修复措施(请参见TIP#389了解详情)-基本目标是,如果您输入非BMP字符,则可以在另一侧得到它们,因此可以将它们写入UTF-8文件或由Tk显示(如果字体引擎愿意支持它们)-但某些操作仍将出错,因为字符串实现仍将使用代理项。 9.0将彻底解决问题(通过更改基本字符存储单元的大小以容纳任何Unicode代码点),但这是一种具有破坏性的变化。

对于已发布的版本,如果能够将代理从Python传递到Tcl,它们可能最终会进入GUI引擎,这可能会做正确的事情。在某些情况下(不包括我当前拥有的任何构建,顺便说一句,但我有奇怪的构建,所以不要过多解读)。使用8.7,发送UTF-8将能够正常工作;这是将得到保证的功能配置文件的一部分。(编码函数存在于旧版本中,但对于非BMP UTF-8,它们会在8.6版本中执行错误操作,并且在早于8.6版本的旧版本上出现错误。)


1
这本来是一条评论,因为它没有提供任何解决方法,但是太长了! - Donal Fellows
非常有趣。由于这个问题,我们可能会“跳船”。Tk在如此基本的事情上无法跟上时代的步伐是一个真正的问题。虽然我更喜欢使用Python内置的解决方案,但我猜我们可能会迁移到PyQt(毫无疑问还会遇到一堆其他问题)。 - Konchog
1
Tk一直以来更新速度都很慢,部分原因是因为跨平台的变更难度较大。上次我在Tk上进行重要工作时,我不得不重新实现特定于平台的部分4次,并修复其中3个的奇怪错误。(字体渲染引擎真是可怕。)UTF-8修复一直很慢,因为对于大多数开发人员来说并不是非常紧迫的问题;作为一个社区,编写代码的人们可能不会使用太多表情符号... - Donal Fellows
我相信你。然而,仅仅展示表情符号是不够的 - 你可能同意未定义行为通常表示安全威胁。Tk作为Python默认/内置GUI库得到了巨大的推动 - 但我担心它缓慢的发展可能会拖累Python:在这些项目中积极的开发非常重要 - 而开发人员则分散很多。我猜我们(OSS开发社区)可以停止发明更多需要维护的东西 - 但不管怎样,我都怀疑那会发生... - Konchog
我奖励你赏金是因为你回答了问题最好,但同时,你也应该因为曾经辛苦开发 Tk 而得到某种形式的认可。 - Konchog

1

问题

  • 这就是表情符号的问题,除了更改源表情符号外,没有其他解决方法。
  • Tk和/或Tcl对表情符号感到困惑。这意味着它不确定要放哪个表情符号,所以它放了两只花栗鼠。当我在我的Linux电脑上尝试该表情符号时,它抛出了一个错误。

解决方案


1
@jdaz找到了解决单个表情符号的简单方法,但我的担忧是存在一般性问题,这对我们来说并不真正可行。 - Konchog

0

正如您所指出的,您的代码在 Windows 上可以正常工作(在 Windows 10 上测试过),但是对于 macOS,以下解决方法应该可以解决问题:

  1. 将表情符号的编码从UTF-32转换为UTF-16(不会丢失任何功能,因为UTF-16是一种可变长度编码,因此可以将任何可以用UTF-32表示的代码点转换为UTF-16,只有在涉及现代表情符号的情况下,UTF-16编码值将使用32位,与UTF-32相同,这意味着它应该支持Unicode v11字符表示)。
  2. 将结果字符串传递给嵌入式的Tcl/Tk解释器。

UTF-16 使用Unicode进行编程

在UTF-16中,范围为U+0000—U+D7FF和U+E000—U+FFFD的字符被存储为单个16位单位。非BMP字符(范围为U+10000—U+10FFFF)被存储为“代理对”,即两个16位单位:高代理项(范围为U+D800—U+DBFF),后跟低代理项(范围为U+DC00—U+DFFF)。
对于Tcl来说,要执行unicode转义字符串(带有其字符/表情符号表示)的替换,字符串本身必须采用形式"\uXXXX"或"\uXXXX\uXXXX"。
花栗鼠表情符号的编码必须转换为UTF-16 => "\ud83d\udc3f"。

    # The tcl/tk code
    set chipmunk "\ud83d\udc3f"
    
    pack [set c [canvas .c -highlightcolor blue -highlightbackground black -background yellow]] -padx 4cm -pady 4cm -expand 1 -fill both
    
    set text_id [$c create text 0 0 -text $chipmunk -font [list * 180]]
    
    $c moveto $text_id 0 0

Unicode chipmunk in Tcl/Tk

相当于Python中的代码,在某些时候需要绕过Tkinter并向嵌入/链接解释器发出直接的Tcl命令。

import tkinter as tk

# the top-level window
top = tk.Tk()

# the canvas
c = tk.Canvas(top, highlightcolor = 'blue', highlightbackground = 'black', background = 'yellow')

# create the text item, with placeholder text
text_id = c.create_text(0,0, font = '* 180', text = 'to be replaced')

# pack it
c.pack(side = 'top', fill = 'both' , expand = 1, padx = '4c' , pady = '4c')

# The 'Bypassing' aka issuing tcl/tk calls directly
# For Tk calls use => c.tk.cal(...), we will not use this.
# For bare Tcl => c.tk.eval(...)

# chipmunk in UTF-16 (in this instance it is using 32-bits to represent the codepoint)
# as a raw string

chipmunk = r"\ud83d\udc3f"

# create another variable in tcl/tk
c.tk.eval('set the_tcl_chipmunk {}'.format(chipmunk))

# set the text_id item's -text property/option as the value of variable the_tcl_chipmunk, gotten by calling the tcl's set command

c.tk.eval( '{} itemconfig {} -text [set the_tcl_chipmunk]'.format( str(c), text_id ) )

# Apparently a hack to get the chipmunk in position
c.tk.eval( '{} moveto {} 0 0'.format( str(c), text_id ) )

# the main gui event loop
top.mainloop()

Unicode chipmunk in python

获取花栗鼠UTF-16

有两种途径可供选择:

  1. 从网站获取,我经常使用 fileformat.info 查看chipmunk在fileformat.info的页面 并复制显示的值C/C++/Java源代码

  2. Python中执行从UTF-32UTF-16的转换


# A UTF-32 string, since it's of the form "\UXXXX_XXXX" ( _ is not part of the syntax, a mere visual aide fo illustrative purposes)
chipmunk_utf_32 = '\U0001F43F'

# convert/encode it to UTF-16 (big endiann), to get a bytes object

chipmunk_utf_16 = chipmunk_utf_32.encode('utf-16-be')

# obtain the hex representation
chipmunk_utf_16 = chipmunk_utf_16.hex()

#format it to be an escaped UTF-16 tcl string
chipmunk = '\\u{}\\u{}'.format(chipmunk_utf_16[0:4], chipmunk_utf_16[4:8])

编辑:整个脚本

import tkinter as tk

# A UTF-32 string, since it's of the form "\UXXXX_XXXX" ( _ is not part of the syntax, a mere visual aide fo illustrative purposes)
chipmunk_utf_32 = '\U0001F43F'

# convert/encode it to UTF-16 (big endiann), to get a bytes object

chipmunk_utf_16 = chipmunk_utf_32.encode('utf-16-be')

# obtain the hex representation
chipmunk_utf_16 = chipmunk_utf_16.hex()

#format it to be an escaped UTF-16 tcl string
chipmunk = '\\u{}\\u{}'.format(chipmunk_utf_16[0:4], chipmunk_utf_16[4:8])

# the top-level window
top = tk.Tk()

# the canvas
c = tk.Canvas(top, highlightcolor = 'blue', highlightbackground = 'black', background = 'yellow')

# create the text item, with placeholder text
text_id = c.create_text(0,0, font = '* 180', text = 'to be replaced')

# pack it
c.pack(side = 'top', fill = 'both' , expand = 1, padx = '4c' , pady = '4c')

# The 'Bypassing' aka issuing tcl/tk calls directly
# For Tk calls use => c.tk.cal(...), we will not use this.
# For bare Tcl => c.tk.eval(...)

# chipmunk in UTF-16 (in this instance it is using 32-bits to represent the codepoint)
# as a raw string

#print(chipmunk)
#chipmunk = r"\ud83d\udc3f"

# create another variable in tcl/tk
c.tk.eval('set the_tcl_chipmunk {}'.format(chipmunk))

# set the text_id item's -text property/option as the value of variable the_tcl_chipmunk, gotten by calling the tcl's set command

c.tk.eval( '{} itemconfig {} -text [set the_tcl_chipmunk]'.format( str(c), text_id ) )

# Apparently a hack to get the chipmunk in position
c.tk.eval( '{} moveto {} 0 0'.format( str(c), text_id ) )

top.mainloop()

分别在Tcl/Tk 8.6.10和Python 3.8.3上进行了测试。 - Space
你的Python代码对我来说不起作用,它只是一个空白画布。并且它会在运行c.tk.eval('{} itemconfig {} -text [set the_tcl_chipmunk]'.format(str(c), text_id))时立即显示出来。 - jdaz
整个脚本都在编辑中(我使用PyScripter作为我的IDE,但这不应该有影响)。如果您仍然遇到空白(黄色)画布的问题,可以将“padx = 4c”更改为“padx = 0”,同样也适用于“pady”。 - Space
即使在我的 Mac 上使用 padx = 0pady = 0,仍然无法显示。 - jdaz
没有集成开发环境,只需将代码保存并从命令行运行。 - jdaz
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接