Skip to content

Commit 593fe91

Browse files
[3.13] gh-156961: Fix tkinter.font.Font for a font name returned as a Tcl object (GH-157028) (GH-157405)
Tk can return a font name as a Tcl object, for example from ttk.Style().lookup("TButton", "font"), Menu.entrycget("font"), ttk.Entry.cget("font"), or the default value in the result of configure(). Such an object does not compare equal to a string, so it was not recognized as the name of an existing named font. Keep it as is, so that it is passed back to Tk, and only convert it where it is compared with a string. (cherry picked from commit 62cbd34)
1 parent 2f52843 commit 593fe91

3 files changed

Lines changed: 44 additions & 3 deletions

File tree

Lib/test/test_tkinter/test_font.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ def setUpClass(cls):
1919
except tkinter.TclError:
2020
cls.font = font.Font(root=cls.root, name=fontname, exists=False)
2121

22+
def tcl_font_object(self, desc):
23+
# Return a font name or description as a Tcl object representing a
24+
# font, as Tk returns for example from ttk.Style().lookup().
25+
tk = self.root.tk
26+
tk.call('set', '_font', desc)
27+
tk.eval('font measure $_font x') # convert the Tcl object to a font
28+
obj = tk.call('set', '_font')
29+
tk.call('unset', '_font')
30+
return obj
31+
2232
def test_configure(self):
2333
self.assertEqual(self.font.config, self.font.configure)
2434
options = self.font.configure()
@@ -72,6 +82,27 @@ def test_create_from_description(self):
7282
f = font.Font(root=self.root, font=desc)
7383
self.assertGreater(int(f.cget('size')), 0) # pixels -> points
7484

85+
def test_tcl_object(self):
86+
# Tk can return a font as a Tcl object (gh-156961).
87+
if not self.wantobjects:
88+
self.skipTest('Tcl objects are converted to strings')
89+
obj = self.tcl_font_object(fontname)
90+
self.assertEqual(obj.typename, 'font')
91+
92+
# It can be used as the name of an existing named font.
93+
for f in (font.Font(root=self.root, name=obj, exists=True),
94+
font.nametofont(obj, root=self.root)):
95+
# The Tcl object is kept as is, so that it is passed back to Tk.
96+
self.assertIs(f.name, obj)
97+
self.assertEqual(str(f), fontname)
98+
self.assertEqual(f.actual(), self.font.actual())
99+
self.assertEqual(f, self.font)
100+
self.assertEqual(self.font, f)
101+
# Referring to a non-existent named font still fails.
102+
self.assertRaisesRegex(tkinter.TclError, 'named font nosuchfont',
103+
font.Font, root=self.root, exists=True,
104+
name=self.tcl_font_object('nosuchfont'))
105+
75106
def test_copy(self):
76107
# size=-20 (pixels): copy() copies the configured options, so the
77108
# size is preserved rather than resolved (gh-143990).

Lib/tkinter/font.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ def __init__(self, root=None, font=None, name=None, exists=False,
9292
if exists:
9393
self.delete_font = False
9494
# confirm font exists
95-
if self.name not in tk.splitlist(tk.call("font", "names")):
95+
name = getattr(name, 'string', name) # can be a Tcl object
96+
if name not in tk.splitlist(tk.call("font", "names")):
9697
raise tkinter._tkinter.TclError(
9798
"named font %s does not already exist" % (self.name,))
9899
# if font config info supplied, apply it
@@ -107,7 +108,7 @@ def __init__(self, root=None, font=None, name=None, exists=False,
107108
self._call = tk.call
108109

109110
def __str__(self):
110-
return self.name
111+
return str(self.name)
111112

112113
def __repr__(self):
113114
return f"<{self.__class__.__module__}.{self.__class__.__qualname__}" \
@@ -116,7 +117,13 @@ def __repr__(self):
116117
def __eq__(self, other):
117118
if not isinstance(other, Font):
118119
return NotImplemented
119-
return self.name == other.name and self._tk == other._tk
120+
name = self.name
121+
other_name = other.name
122+
if type(name) is not type(other_name):
123+
# A Tcl object does not compare equal to a string.
124+
name = getattr(name, 'string', name)
125+
other_name = getattr(other_name, 'string', other_name)
126+
return name == other_name and self._tk == other._tk
120127

121128
def __getitem__(self, key):
122129
return self.cget(key)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`tkinter.font.nametofont` and the :class:`tkinter.font.Font`
2+
constructor for a font name returned by Tk as a Tcl object,
3+
for example by :meth:`ttk.Style.lookup() <tkinter.ttk.Style.lookup>`.

0 commit comments

Comments
 (0)