font.py revision 14fc4270da5db43574d46d055a5f794ed33b271f
1# Tkinter font wrapper
2#
3# written by Fredrik Lundh, February 1998
4#
5# FIXME: should add 'displayof' option where relevant (actual, families,
6#        measure, and metrics)
7#
8
9__version__ = "0.9"
10
11import tkinter
12
13# weight/slant
14NORMAL = "normal"
15ROMAN = "roman"
16BOLD   = "bold"
17ITALIC = "italic"
18
19def nametofont(name):
20    """Given the name of a tk named font, returns a Font representation.
21    """
22    return Font(name=name, exists=True)
23
24class Font:
25
26    """Represents a named font.
27
28    Constructor options are:
29
30    font -- font specifier (name, system font, or (family, size, style)-tuple)
31    name -- name to use for this font configuration (defaults to a unique name)
32    exists -- does a named font by this name already exist?
33       Creates a new named font if False, points to the existing font if True.
34       Raises _tkinter.TclError if the assertion is false.
35
36       the following are ignored if font is specified:
37
38    family -- font 'family', e.g. Courier, Times, Helvetica
39    size -- font size in points
40    weight -- font thickness: NORMAL, BOLD
41    slant -- font slant: ROMAN, ITALIC
42    underline -- font underlining: false (0), true (1)
43    overstrike -- font strikeout: false (0), true (1)
44
45    """
46
47    def _set(self, kw):
48        options = []
49        for k, v in kw.items():
50            options.append("-"+k)
51            options.append(str(v))
52        return tuple(options)
53
54    def _get(self, args):
55        options = []
56        for k in args:
57            options.append("-"+k)
58        return tuple(options)
59
60    def _mkdict(self, args):
61        options = {}
62        for i in range(0, len(args), 2):
63            options[args[i][1:]] = args[i+1]
64        return options
65
66    def __init__(self, root=None, font=None, name=None, exists=False, **options):
67        if not root:
68            root = tkinter._default_root
69        if font:
70            # get actual settings corresponding to the given font
71            font = root.tk.splitlist(root.tk.call("font", "actual", font))
72        else:
73            font = self._set(options)
74        if not name:
75            name = "font" + str(id(self))
76        self.name = name
77
78        if exists:
79            self.delete_font = False
80            # confirm font exists
81            if self.name not in root.tk.call("font", "names"):
82                raise tkinter._tkinter.TclError(
83                    "named font %s does not already exist" % (self.name,))
84            # if font config info supplied, apply it
85            if font:
86                root.tk.call("font", "configure", self.name, *font)
87        else:
88            # create new font (raises TclError if the font exists)
89            root.tk.call("font", "create", self.name, *font)
90            self.delete_font = True
91        # backlinks!
92        self._root  = root
93        self._split = root.tk.splitlist
94        self._call  = root.tk.call
95
96    def __str__(self):
97        return self.name
98
99    def __eq__(self, other):
100        return self.name == other.name and isinstance(other, Font)
101
102    def __getitem__(self, key):
103        return self.cget(key)
104
105    def __setitem__(self, key, value):
106        self.configure(**{key: value})
107
108    def __del__(self):
109        try:
110            if self.delete_font:
111                self._call("font", "delete", self.name)
112        except (KeyboardInterrupt, SystemExit):
113            raise
114        except Exception:
115            pass
116
117    def copy(self):
118        "Return a distinct copy of the current font"
119        return Font(self._root, **self.actual())
120
121    def actual(self, option=None):
122        "Return actual font attributes"
123        if option:
124            return self._call("font", "actual", self.name, "-"+option)
125        else:
126            return self._mkdict(
127                self._split(self._call("font", "actual", self.name))
128                )
129
130    def cget(self, option):
131        "Get font attribute"
132        return self._call("font", "config", self.name, "-"+option)
133
134    def config(self, **options):
135        "Modify font attributes"
136        if options:
137            self._call("font", "config", self.name,
138                  *self._set(options))
139        else:
140            return self._mkdict(
141                self._split(self._call("font", "config", self.name))
142                )
143
144    configure = config
145
146    def measure(self, text):
147        "Return text width"
148        return int(self._call("font", "measure", self.name, text))
149
150    def metrics(self, *options):
151        """Return font metrics.
152
153        For best performance, create a dummy widget
154        using this font before calling this method."""
155
156        if options:
157            return int(
158                self._call("font", "metrics", self.name, self._get(options))
159                )
160        else:
161            res = self._split(self._call("font", "metrics", self.name))
162            options = {}
163            for i in range(0, len(res), 2):
164                options[res[i][1:]] = int(res[i+1])
165            return options
166
167def families(root=None):
168    "Get font families (as a tuple)"
169    if not root:
170        root = tkinter._default_root
171    return root.tk.splitlist(root.tk.call("font", "families"))
172
173def names(root=None):
174    "Get names of defined fonts (as a tuple)"
175    if not root:
176        root = tkinter._default_root
177    return root.tk.splitlist(root.tk.call("font", "names"))
178
179# --------------------------------------------------------------------
180# test stuff
181
182if __name__ == "__main__":
183
184    root = tkinter.Tk()
185
186    # create a font
187    f = Font(family="times", size=30, weight=NORMAL)
188
189    print(f.actual())
190    print(f.actual("family"))
191    print(f.actual("weight"))
192
193    print(f.config())
194    print(f.cget("family"))
195    print(f.cget("weight"))
196
197    print(names())
198
199    print(f.measure("hello"), f.metrics("linespace"))
200
201    print(f.metrics())
202
203    f = Font(font=("Courier", 20, "bold"))
204    print(f.measure("hello"), f.metrics("linespace"))
205
206    w = tkinter.Label(root, text="Hello, world", font=f)
207    w.pack()
208
209    w = tkinter.Button(root, text="Quit!", command=root.destroy)
210    w.pack()
211
212    fb = Font(font=w["font"]).copy()
213    fb.config(weight=BOLD)
214
215    w.config(font=fb)
216
217    tkinter.mainloop()
218