From 266cf833f72e0de2f4f045d878706d3ec3ec7376 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 13 Sep 2026 11:05:25 +0300 Subject: [PATCH] [3.15] gh-156961: Fix tkinter.font.Font for a font name returned as a Tcl object (GH-157028) 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 62cbd34df74e4e07bb50edcaeec7454e71da47ce) --- Lib/test/test_tkinter/test_font.py | 31 +++++++++++++++++++ Lib/tkinter/font.py | 13 ++++++-- ...-09-05-22-39-22.gh-issue-156961.ZtYqoA.rst | 3 ++ 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-09-05-22-39-22.gh-issue-156961.ZtYqoA.rst diff --git a/Lib/test/test_tkinter/test_font.py b/Lib/test/test_tkinter/test_font.py index 6423a4e70af01ca..db9fc35934c947a 100644 --- a/Lib/test/test_tkinter/test_font.py +++ b/Lib/test/test_tkinter/test_font.py @@ -20,6 +20,16 @@ def setUpClass(cls): except tkinter.TclError: cls.font = font.Font(root=cls.root, name=fontname, exists=False) + def tcl_font_object(self, desc): + # Return a font name or description as a Tcl object representing a + # font, as Tk returns for example from ttk.Style().lookup(). + tk = self.root.tk + tk.call('set', '_font', desc) + tk.eval('font measure $_font x') # convert the Tcl object to a font + obj = tk.call('set', '_font') + tk.call('unset', '_font') + return obj + def test_configure(self): self.assertEqual(self.font.config, self.font.configure) options = self.font.configure() @@ -73,6 +83,27 @@ def test_create_from_description(self): f = font.Font(root=self.root, font=desc) self.assertGreater(int(f.cget('size')), 0) # pixels -> points + def test_tcl_object(self): + # Tk can return a font as a Tcl object (gh-156961). + if not self.wantobjects: + self.skipTest('Tcl objects are converted to strings') + obj = self.tcl_font_object(fontname) + self.assertEqual(obj.typename, 'font') + + # It can be used as the name of an existing named font. + for f in (font.Font(root=self.root, name=obj, exists=True), + font.nametofont(obj, root=self.root)): + # The Tcl object is kept as is, so that it is passed back to Tk. + self.assertIs(f.name, obj) + self.assertEqual(str(f), fontname) + self.assertEqual(f.actual(), self.font.actual()) + self.assertEqual(f, self.font) + self.assertEqual(self.font, f) + # Referring to a non-existent named font still fails. + self.assertRaisesRegex(tkinter.TclError, 'named font nosuchfont', + font.Font, root=self.root, exists=True, + name=self.tcl_font_object('nosuchfont')) + def test_copy(self): # size=-20 (pixels): copy() copies the configured options, so the # size is preserved rather than resolved (gh-143990). diff --git a/Lib/tkinter/font.py b/Lib/tkinter/font.py index 2bbdbd560436af7..6199a04feaa4b26 100644 --- a/Lib/tkinter/font.py +++ b/Lib/tkinter/font.py @@ -91,7 +91,8 @@ def __init__(self, root=None, font=None, name=None, exists=False, if exists: self.delete_font = False # confirm font exists - if self.name not in tk.splitlist(tk.call("font", "names")): + name = getattr(name, 'string', name) # can be a Tcl object + if name not in tk.splitlist(tk.call("font", "names")): raise tkinter._tkinter.TclError( "named font %s does not already exist" % (self.name,)) # if font config info supplied, apply it @@ -106,7 +107,7 @@ def __init__(self, root=None, font=None, name=None, exists=False, self._call = tk.call def __str__(self): - return self.name + return str(self.name) def __repr__(self): return f"<{self.__class__.__module__}.{self.__class__.__qualname__}" \ @@ -115,7 +116,13 @@ def __repr__(self): def __eq__(self, other): if not isinstance(other, Font): return NotImplemented - return self.name == other.name and self._tk == other._tk + name = self.name + other_name = other.name + if type(name) is not type(other_name): + # A Tcl object does not compare equal to a string. + name = getattr(name, 'string', name) + other_name = getattr(other_name, 'string', other_name) + return name == other_name and self._tk == other._tk def __getitem__(self, key): return self.cget(key) diff --git a/Misc/NEWS.d/next/Library/2026-09-05-22-39-22.gh-issue-156961.ZtYqoA.rst b/Misc/NEWS.d/next/Library/2026-09-05-22-39-22.gh-issue-156961.ZtYqoA.rst new file mode 100644 index 000000000000000..20ded0775d7a70a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-05-22-39-22.gh-issue-156961.ZtYqoA.rst @@ -0,0 +1,3 @@ +Fix :func:`tkinter.font.nametofont` and the :class:`tkinter.font.Font` +constructor for a font name returned by Tk as a Tcl object, +for example by :meth:`ttk.Style.lookup() `.