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() `.