Skip to content

Commit a9d4473

Browse files
[3.15] gh-143990: Preserve the size when creating a Font from a named font (GH-153267) (GH-153323)
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) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 217472d commit a9d4473

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
@@ -44,17 +44,49 @@ def test_configure(self):
4444
self.assertRaises(TypeError, self.font.cget)
4545
self.assertRaises(TypeError, self.font.cget, 'size', 'weight')
4646

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

Lib/tkinter/font.py

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

126133
def copy(self):
127134
"Return a distinct copy of the current font"
128-
return Font(self._tk, **self.actual())
135+
return Font(self._tk, self.name)
129136

130137
def actual(self, option=None, displayof=None):
131138
"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)