]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-143990: Preserve the size when creating a Font from a named font (GH-153267...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 8 Jul 2026 13:22:24 +0000 (15:22 +0200)
committerGitHub <noreply@github.com>
Wed, 8 Jul 2026 13:22:24 +0000 (13:22 +0000)
tkinter.font.Font now copies the options of a named font (via "font
configure") instead of the options resolved by "font actual", which
would resolve a size specified in pixels (a negative size) to points.
A font description is still resolved, as it cannot be parsed otherwise.

Font.copy(), which has always been equivalent to constructing a Font
from the original font, is updated to match and now preserves the size
too.

(cherry picked from commit 45010f441d392558f1be8f3cea7d86954ed2217d)
(cherry picked from commit a9d4473232f86d6dfc234756f633b8dc5836ad32)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Lib/test/test_tkinter/test_font.py
Lib/tkinter/font.py
Misc/NEWS.d/next/Library/2026-07-07-17-50-54.gh-issue-143990.FoNtCf.rst [new file with mode: 0644]

index 8e1e3d56c07f1768ab7f492d86f3537482ead5b6..00150ce83d07c836e33d02954ae9bd9d266e1e83 100644 (file)
@@ -43,17 +43,49 @@ class FontTest(AbstractTkTest, unittest.TestCase):
         self.assertRaises(TypeError, self.font.cget)
         self.assertRaises(TypeError, self.font.cget, 'size', 'weight')
 
+    def test_create_from_named_font(self):
+        # gh-143990: a font created from a named font copies its configured
+        # options, preserving a size specified in pixels (a negative size).
+        sizetype = int if self.wantobjects else str
+        named = font.Font(root=self.root, name='my named font',  # name with spaces
+                          family='Times', size=-20, weight='bold')
+        # The source is the name of a named font or a Font representing one.
+        for source in ['my named font', named]:
+            with self.subTest(source=source):
+                f = font.Font(root=self.root, font=source)
+                self.assertEqual(f.cget('size'), sizetype(-20))
+                self.assertEqual(f.actual('family'), named.actual('family'))
+                self.assertEqual(f.actual('weight'), 'bold')
+
+    def test_create_from_description(self):
+        # gh-143990: a font created from a font description is resolved via
+        # "font actual", so a size in pixels (negative) becomes a size in points.
+        descriptions = [
+            ('Times', -20),                     # tuple
+            ('Times', -20, 'bold'),             # tuple with a style
+            'Times -20',                        # string
+            'Times -20 bold',                   # string with a style
+            '{Times New Roman} -20',            # string, family with spaces
+        ]
+        for desc in descriptions:
+            with self.subTest(font=desc):
+                f = font.Font(root=self.root, font=desc)
+                self.assertGreater(int(f.cget('size')), 0)  # pixels -> points
+
     def test_copy(self):
-        f = font.Font(root=self.root, family='Times', size=10, weight='bold')
+        # size=-20 (pixels): copy() copies the configured options, so the
+        # size is preserved rather than resolved (gh-143990).
+        f = font.Font(root=self.root, family='Times', size=-20, weight='bold')
         copied = f.copy()
         self.assertIsInstance(copied, font.Font)
         self.assertIsNot(copied, f)
         self.assertNotEqual(copied.name, f.name)
         self.assertEqual(copied.actual(), f.actual())
-        # The copy is independent of the original.
         sizetype = int if self.wantobjects else str
+        self.assertEqual(copied.cget('size'), sizetype(-20))
+        # The copy is independent of the original.
         copied.configure(size=20)
-        self.assertEqual(f.cget('size'), sizetype(10))
+        self.assertEqual(f.cget('size'), sizetype(-20))
         self.assertEqual(copied.cget('size'), sizetype(20))
         self.assertRaises(TypeError, f.copy, 'x')
 
index 3e24e28ef58cde90dafe78016ce38867a008b450..dcda8b554b7453941282cc1e31ba83c597cc4c8a 100644 (file)
@@ -72,8 +72,15 @@ class Font:
             root = tkinter._get_default_root('use font')
         tk = getattr(root, 'tk', root)
         if font:
-            # get actual settings corresponding to the given font
-            font = tk.splitlist(tk.call("font", "actual", font))
+            # start from the settings of the given font
+            try:
+                # a named font: copy its options, preserving the size,
+                # which can be negative (specified in pixels)
+                font = tk.splitlist(tk.call("font", "configure", font))
+            except tkinter.TclError:
+                # a font description: resolve it ("font configure" only
+                # accepts a font name); this loses a size in pixels
+                font = tk.splitlist(tk.call("font", "actual", font))
         else:
             font = self._set(options)
         if not name:
@@ -124,7 +131,7 @@ class Font:
 
     def copy(self):
         "Return a distinct copy of the current font"
-        return Font(self._tk, **self.actual())
+        return Font(self._tk, self.name)
 
     def actual(self, option=None, displayof=None):
         "Return actual font attributes"
diff --git a/Misc/NEWS.d/next/Library/2026-07-07-17-50-54.gh-issue-143990.FoNtCf.rst b/Misc/NEWS.d/next/Library/2026-07-07-17-50-54.gh-issue-143990.FoNtCf.rst
new file mode 100644 (file)
index 0000000..a78dccc
--- /dev/null
@@ -0,0 +1,4 @@
+A :class:`tkinter.font.Font` created from a named font,
+including by :meth:`~tkinter.font.Font.copy`,
+now copies its configured options rather than the options resolved by Tcl's ``font actual``,
+preserving a size specified in pixels (a negative size).