Skip to content

Commit 987b4c2

Browse files
gh-143990: Preserve the size when creating a Font from a named font
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. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 2cd5b79 commit 987b4c2

3 files changed

Lines changed: 50 additions & 2 deletions

File tree

Lib/test/test_tkinter/test_font.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,44 @@ def test_create(self):
7373
self.assertRaises(tkinter.TclError, font.Font, root=self.root,
7474
name='testfont', font=('Times', 10))
7575

76+
def test_create_from_named_font(self):
77+
# gh-143990: a font created from a named font copies its configured
78+
# options, preserving a size specified in pixels (a negative size).
79+
sizetype = int if self.wantobjects else str
80+
named = font.Font(root=self.root, name='my named font', # name with spaces
81+
family='Times', size=-20, weight='bold')
82+
# The source is the name of a named font or a Font representing one.
83+
for source in ['my named font', named]:
84+
with self.subTest(source=source):
85+
f = font.Font(root=self.root, font=source)
86+
self.assertEqual(f.cget('size'), sizetype(-20))
87+
self.assertEqual(f.actual('family'), named.actual('family'))
88+
self.assertEqual(f.actual('weight'), 'bold')
89+
# Explicit options still override the copied settings.
90+
f = font.Font(root=self.root, font=named, size=30)
91+
self.assertEqual(f.cget('size'), sizetype(30))
92+
93+
def test_create_from_description(self):
94+
# gh-143990: a font created from a font description is resolved via
95+
# "font actual", so a size in pixels (negative) becomes a size in points.
96+
descriptions = [
97+
('Times', -20), # tuple
98+
('Times', -20, 'bold'), # tuple with a style
99+
'Times -20', # string
100+
'Times -20 bold', # string with a style
101+
'{Times New Roman} -20', # string, family with spaces
102+
# a Font wrapping a description, as a tuple and as a string
103+
font.Font(root=self.root, font=('Times', -20), exists=True),
104+
font.Font(root=self.root, font='Times -20', exists=True),
105+
]
106+
for desc in descriptions:
107+
with self.subTest(font=desc):
108+
f = font.Font(root=self.root, font=desc)
109+
# resolved as if the description were wrapped by exists=True
110+
wrapped = font.Font(root=self.root, font=desc, exists=True)
111+
self.assertEqual(f.actual(), wrapped.actual())
112+
self.assertGreater(int(f.cget('size')), 0) # pixels -> points
113+
76114
def test_existing(self):
77115
sizetype = int if self.wantobjects else str
78116

Lib/tkinter/font.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,15 @@ def __init__(self, root=None, font=None, name=None, exists=False,
8383
self.name = font
8484
else:
8585
if font:
86-
# start from the actual settings of the given font
87-
font = tk.splitlist(tk.call("font", "actual", font))
86+
# start from the settings of the given font
87+
try:
88+
# a named font: copy its options, preserving the size,
89+
# which can be negative (specified in pixels)
90+
font = tk.splitlist(tk.call("font", "configure", font))
91+
except tkinter.TclError:
92+
# a font description: resolve it ("font configure" only
93+
# accepts a font name); this loses a size in pixels
94+
font = tk.splitlist(tk.call("font", "actual", font))
8895
if options:
8996
# explicit options override the corresponding settings
9097
settings = self._mkdict(font)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
A :class:`tkinter.font.Font` created from a named font
2+
now copies its configured options rather than the options resolved by Tcl's ``font actual``,
3+
preserving a size specified in pixels (a negative size).

0 commit comments

Comments
 (0)