Skip to content

Commit d55a988

Browse files
miss-islingtonserhiy-storchakaclaude
authored
[3.14] gh-143990: Preserve the size when creating a Font from a named font (GH-153267) (GH-153323) (GH-153341)
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 45010f4) (cherry picked from commit a9d4473) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0dbb00e commit d55a988

3 files changed

Lines changed: 49 additions & 6 deletions

File tree

Lib/test/test_tkinter/test_font.py

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,49 @@ def test_configure(self):
4343
self.assertRaises(TypeError, self.font.cget)
4444
self.assertRaises(TypeError, self.font.cget, 'size', 'weight')
4545

46+
def test_create_from_named_font(self):
47+
# gh-143990: a font created from a named font copies its configured
48+
# options, preserving a size specified in pixels (a negative size).
49+
sizetype = int if self.wantobjects else str
50+
named = font.Font(root=self.root, name='my named font', # name with spaces
51+
family='Times', size=-20, weight='bold')
52+
# The source is the name of a named font or a Font representing one.
53+
for source in ['my named font', named]:
54+
with self.subTest(source=source):
55+
f = font.Font(root=self.root, font=source)
56+
self.assertEqual(f.cget('size'), sizetype(-20))
57+
self.assertEqual(f.actual('family'), named.actual('family'))
58+
self.assertEqual(f.actual('weight'), 'bold')
59+
60+
def test_create_from_description(self):
61+
# gh-143990: a font created from a font description is resolved via
62+
# "font actual", so a size in pixels (negative) becomes a size in points.
63+
descriptions = [
64+
('Times', -20), # tuple
65+
('Times', -20, 'bold'), # tuple with a style
66+
'Times -20', # string
67+
'Times -20 bold', # string with a style
68+
'{Times New Roman} -20', # string, family with spaces
69+
]
70+
for desc in descriptions:
71+
with self.subTest(font=desc):
72+
f = font.Font(root=self.root, font=desc)
73+
self.assertGreater(int(f.cget('size')), 0) # pixels -> points
74+
4675
def test_copy(self):
47-
f = font.Font(root=self.root, family='Times', size=10, weight='bold')
76+
# size=-20 (pixels): copy() copies the configured options, so the
77+
# size is preserved rather than resolved (gh-143990).
78+
f = font.Font(root=self.root, family='Times', size=-20, weight='bold')
4879
copied = f.copy()
4980
self.assertIsInstance(copied, font.Font)
5081
self.assertIsNot(copied, f)
5182
self.assertNotEqual(copied.name, f.name)
5283
self.assertEqual(copied.actual(), f.actual())
53-
# The copy is independent of the original.
5484
sizetype = int if self.wantobjects else str
85+
self.assertEqual(copied.cget('size'), sizetype(-20))
86+
# The copy is independent of the original.
5587
copied.configure(size=20)
56-
self.assertEqual(f.cget('size'), sizetype(10))
88+
self.assertEqual(f.cget('size'), sizetype(-20))
5789
self.assertEqual(copied.cget('size'), sizetype(20))
5890
self.assertRaises(TypeError, f.copy, 'x')
5991

Lib/tkinter/font.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,15 @@ def __init__(self, root=None, font=None, name=None, exists=False,
7272
root = tkinter._get_default_root('use font')
7373
tk = getattr(root, 'tk', root)
7474
if font:
75-
# get actual settings corresponding to the given font
76-
font = tk.splitlist(tk.call("font", "actual", font))
75+
# start from the settings of the given font
76+
try:
77+
# a named font: copy its options, preserving the size,
78+
# which can be negative (specified in pixels)
79+
font = tk.splitlist(tk.call("font", "configure", font))
80+
except tkinter.TclError:
81+
# a font description: resolve it ("font configure" only
82+
# accepts a font name); this loses a size in pixels
83+
font = tk.splitlist(tk.call("font", "actual", font))
7784
else:
7885
font = self._set(options)
7986
if not name:
@@ -124,7 +131,7 @@ def __del__(self):
124131

125132
def copy(self):
126133
"Return a distinct copy of the current font"
127-
return Font(self._tk, **self.actual())
134+
return Font(self._tk, self.name)
128135

129136
def actual(self, option=None, displayof=None):
130137
"Return actual font attributes"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
A :class:`tkinter.font.Font` created from a named font,
2+
including by :meth:`~tkinter.font.Font.copy`,
3+
now copies its configured options rather than the options resolved by Tcl's ``font actual``,
4+
preserving a size specified in pixels (a negative size).

0 commit comments

Comments
 (0)