【问题标题】:Why does wx.TextCtrl.SetStyle mishandle emojis?为什么 wx.TextCtrl.SetStyle 对表情符号处理不当?
【发布时间】:2020-06-14 20:19:36
【问题描述】:

我正在 Python 3.7.3 中使用 wxPython 4.0.4 开发应用程序,但在尝试为 wx.TextCtrl 中的 UTF-8 文本着色时遇到了问题。基本上,似乎某些字符在 wxPython 中被错误地计算,尽管它们在 Python 中被正确计算。

我最初认为是所有多字节字符都被错误计数,但是,我下面的示例代码表明情况并非如此。这似乎是 wx.TextCtrl.SetStyle 函数中的一个问题。

import wx
import wx.richtext as rt
app = wx.App()

test_str1 = '''There are no multibyte characters '''
test_str2 = '''blah ble blah\n'''
test_str3 = '''“these are multibyte quotes” '''
test_str4 = '''more single byte chars!\n'''
test_str5 = '''this comma’s represented by multiple bytes\n'''
test_str6 = '''why do emojis ???? ???? ???? seem to break TextCtrl.SetStyle ???? ???? ????\n'''
test_str7 = '''more single byte characters\n'''
test_str8 = '''to demonstrate the issue.'''

def main():
    main = TestFrame()
    main.Show()
    app.MainLoop()

class TestFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title="TestFrame")
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.panel = TestPanel(self)
        sizer.Add(self.panel, proportion=1, flag=wx.EXPAND)

class TestPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.text = wx.TextCtrl(self, wx.ID_ANY, style=(wx.TE_MULTILINE|wx.TE_RICH|wx.TE_READONLY))
        self.raw_text = ""
        self.styles = []
        self.AddColorText(test_str1, wx.BLUE)
        self.AddColorText(test_str2, wx.RED)
        self.AddColorText(test_str3, wx.BLUE)
        self.AddColorText(test_str4, wx.RED)
        self.AddColorText(test_str5, wx.BLUE)
        self.AddColorText(test_str6, wx.RED)
        self.AddColorText(test_str7, wx.BLUE)
        self.AddColorText(test_str8, wx.RED)
        self.text.SetValue(self.raw_text)
        for s in self.styles:
            self.text.SetStyle(s[0], s[1], s[2])
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.text, proportion=1, flag=wx.EXPAND)
        self.SetSizer(sizer)

    def AddColorText(self, text, wx_color):
        start = len(self.raw_text)
        self.raw_text += text
        end = len(self.raw_text)
        self.styles.append([start, end, wx.TextAttr(wx_color)])

if __name__ == "__main__":
    main()

【问题讨论】:

  • 在您的问题中添加屏幕截图以演示您的问题,因为我认为 Linux 上的 wxPython 4.1.0 没有问题
  • @RolfofSaxony 更新了截图,另外我在 Windows 10 中运行。
  • 似乎是 Windows 问题。我所有的表情都是红色的。倒数第二行全是蓝色,最后一行全是红色。抱歉帮不上忙,我只是Linux。您是否尝试过在程序或桌面中更改字体?

标签: python-3.x wxpython wxtextctrl


【解决方案1】:

MS Windows 在内部使用 UTF-16,在 PEP 393 之前,CPython Unicode 在 Windows 上也是 16 位的,因此。但是使用 PEP 393,CPython 现在可以更清晰地表示所有 Unicode 代码点,因此一个 Unicode 代码点的字符串长度始终为 1。

另一方面,MSWin 不能。因此 wxPython 必须在将字符串发送到操作系统之前将其转换为 UTF-16。对于 Unicode 的 basic multilingual plane 中的所有内容,这是您将遇到的大部分内容,这很好,因为一个 Unicode 代码点变成了一个 UTF-16 字符(两个字节)。

但是那些新的 Emoji 不在 BMP 中,所以它们在 UTF-16 中变得超过两个字节。而 wxPython 无法解释这一点:如果 wxPython 将 startend 计数器直接传递给底层 Windows 函数,那么它们将在 Emoji 之后关闭,因为给定的值是 Unicode 代码点,并且Windows 期望的值是 UTF-16 字符数。

您可以自行计算 UTF-16 偏移量以传递给 SetStyle:

utf16start = len(self.raw_text[:start].encode('utf-16'))
utf16end = utf16start + len(self.raw_text[start:end].encode('utf-16'))

可以说这是 wxPython 中的一个错误,您应该将其报告给wxPython issue tracker

【讨论】:

  • 我尝试在我的测试用例中使用此修复程序,它导致文本颜色位于不同的位置,但仍然不正确。我同意这是一个错误,我已通过 github.com/wxWidgets/Phoenix/issues/1691 将其报告给 wxPython
【解决方案2】:

看来我的问题是使用 python 标准函数而不是 wx 库函数来计算长度。 下面的代码解决了我的问题。

import wx
import wx.richtext as rt
app = wx.App()

test_str1 = '''There are no multibyte characters '''
test_str2 = '''blah ble blah\n'''
test_str3 = '''“these are multibyte quotes” '''
test_str4 = '''more single byte chars!\n'''
test_str5 = '''this comma’s represented by multiple bytes\n'''
test_str6 = '''why do emojis ? ? ? seem to break TextCtrl.SetStyle ? ? ?\n'''
test_str7 = '''more single byte characters\n'''
test_str8 = '''to demonstrate the issue.'''

def main():
    main = TestFrame()
    main.Show()
    app.MainLoop()

class TestFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title="TestFrame")
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.panel = TestPanel(self)
        sizer.Add(self.panel, proportion=1, flag=wx.EXPAND)

class TestPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.text = wx.TextCtrl(self, wx.ID_ANY, style=(wx.TE_MULTILINE|wx.TE_RICH|wx.TE_READONLY))
        self.raw_text = ""
        self.styles = []
        self.AddColorText(test_str1, wx.BLUE)
        self.AddColorText(test_str2, wx.RED)
        self.AddColorText(test_str3, wx.BLUE)
        self.AddColorText(test_str4, wx.RED)
        self.AddColorText(test_str5, wx.BLUE)
        self.AddColorText(test_str6, wx.RED)
        self.AddColorText(test_str7, wx.BLUE)
        self.AddColorText(test_str8, wx.RED)
        for s in self.styles:
            self.text.SetStyle(s[0], s[1], s[2])
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.text, proportion=1, flag=wx.EXPAND)
        self.SetSizer(sizer)

    def AddColorText(self, text, wx_color):
        start = self.text.GetLastPosition()
        self.text.AppendText(text)
        end = self.text.GetLastPosition()
        self.styles.append([start, end, wx.TextAttr(wx_color)])

if __name__ == "__main__":
    main()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-02
    • 2016-10-05
    • 1970-01-01
    • 2014-04-15
    • 2020-03-15
    • 2020-03-22
    相关资源
    最近更新 更多