]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-143990: Preserve the size when creating a Font from a named font (GH-153267)
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 8 Jul 2026 08:30:12 +0000 (11:30 +0300)
committerGitHub <noreply@github.com>
Wed, 8 Jul 2026 08:30:12 +0000 (11:30 +0300)
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.

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 e6f331332eb20a9d2c53b0da042e71f3b88689d5..8e278456f383a1c945c80de7e51984c65c0d8a20 100644 (file)
@@ -73,6 +73,44 @@ class FontTest(AbstractTkTest, unittest.TestCase):
         self.assertRaises(tkinter.TclError, font.Font, root=self.root,
                           name='testfont', font=('Times', 10))
 
+    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')
+        # Explicit options still override the copied settings.
+        f = font.Font(root=self.root, font=named, size=30)
+        self.assertEqual(f.cget('size'), sizetype(30))
+
+    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
+            # a Font wrapping a description, as a tuple and as a string
+            font.Font(root=self.root, font=('Times', -20), exists=True),
+            font.Font(root=self.root, font='Times -20', exists=True),
+        ]
+        for desc in descriptions:
+            with self.subTest(font=desc):
+                f = font.Font(root=self.root, font=desc)
+                # resolved as if the description were wrapped by exists=True
+                wrapped = font.Font(root=self.root, font=desc, exists=True)
+                self.assertEqual(f.actual(), wrapped.actual())
+                self.assertGreater(int(f.cget('size')), 0)  # pixels -> points
+
     def test_existing(self):
         sizetype = int if self.wantobjects else str
 
@@ -109,16 +147,19 @@ class FontTest(AbstractTkTest, unittest.TestCase):
         self.assertRaises(TypeError, font.Font, root=self.root, exists=True)
 
     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 d59b04d36638e79891bb4bbd74afb9f39e0b760b..7ce7047885fce5fca668e412025c5b8c36be8f4f 100644 (file)
@@ -83,8 +83,15 @@ class Font:
             self.name = font
         else:
             if font:
-                # start from the actual settings of 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))
                 if options:
                     # explicit options override the corresponding settings
                     settings = self._mkdict(font)
@@ -146,7 +153,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).