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            if not isinstance(v, basestring):
51                v = str(v)
52            options.append("-"+k)
53            options.append(v)
54        return tuple(options)
55
56    def _get(self, args):
57        options = []
58        for k in args:
59            options.append("-"+k)
60        return tuple(options)
61
62    def _mkdict(self, args):
63        options = {}
64        for i in range(0, len(args), 2):
65            options[args[i][1:]] = args[i+1]
66        return options
67
68    def __init__(self, root=None, font=None, name=None, exists=False, **options):
69        if not root:
70            root = Tkinter._default_root
71        tk = getattr(root, 'tk', root)
72        if font:
73            # get actual settings corresponding to the given font
74            font = tk.splitlist(tk.call("font", "actual", font))
75        else:
76            font = self._set(options)
77        if not name:
78            name = "font" + str(id(self))
79        self.name = name
80
81        if exists:
82            self.delete_font = False
83            # confirm font exists
84            if self.name not in tk.splitlist(tk.call("font", "names")):
85                raise Tkinter._tkinter.TclError, "named font %s does not already exist" % (self.name,)
86            # if font config info supplied, apply it
87            if font:
88                tk.call("font", "configure", self.name, *font)
89        else:
90            # create new font (raises TclError if the font exists)
91            tk.call("font", "create", self.name, *font)
92            self.delete_font = True
93        self._tk = tk
94        self._split = tk.splitlist
95        self._call  = tk.call
96
97    def __str__(self):
98        return self.name
99
100    def __eq__(self, other):
101        return isinstance(other, Font) and self.name == other.name
102
103    def __getitem__(self, key):
104        return self.cget(key)
105
106    def __setitem__(self, key, value):
107        self.configure(**{key: value})
108
109    def __del__(self):
110        try:
111            if self.delete_font:
112                self._call("font", "delete", self.name)
113        except (KeyboardInterrupt, SystemExit):
114            raise
115        except Exception:
116            pass
117
118    def copy(self):
119        "Return a distinct copy of the current font"
120        return Font(self._tk, **self.actual())
121
122    def actual(self, option=None):
123        "Return actual font attributes"
124        if option:
125            return self._call("font", "actual", self.name, "-"+option)
126        else:
127            return self._mkdict(
128                self._split(self._call("font", "actual", self.name))
129                )
130
131    def cget(self, option):
132        "Get font attribute"
133        return self._call("font", "config", self.name, "-"+option)
134
135    def config(self, **options):
136        "Modify font attributes"
137        if options:
138            self._call("font", "config", self.name,
139                  *self._set(options))
140        else:
141            return self._mkdict(
142                self._split(self._call("font", "config", self.name))
143                )
144
145    configure = config
146
147    def measure(self, text):
148        "Return text width"
149        return int(self._call("font", "measure", self.name, text))
150
151    def metrics(self, *options):
152        """Return font metrics.
153
154        For best performance, create a dummy widget
155        using this font before calling this method."""
156
157        if options:
158            return int(
159                self._call("font", "metrics", self.name, self._get(options))
160                )
161        else:
162            res = self._split(self._call("font", "metrics", self.name))
163            options = {}
164            for i in range(0, len(res), 2):
165                options[res[i][1:]] = int(res[i+1])
166            return options
167
168def families(root=None):
169    "Get font families (as a tuple)"
170    if not root:
171        root = Tkinter._default_root
172    return root.tk.splitlist(root.tk.call("font", "families"))
173
174def names(root=None):
175    "Get names of defined fonts (as a tuple)"
176    if not root:
177        root = Tkinter._default_root
178    return root.tk.splitlist(root.tk.call("font", "names"))
179
180# --------------------------------------------------------------------
181# test stuff
182
183if __name__ == "__main__":
184
185    root = Tkinter.Tk()
186
187    # create a font
188    f = Font(family="times", size=30, weight=NORMAL)
189
190    print f.actual()
191    print f.actual("family")
192    print f.actual("weight")
193
194    print f.config()
195    print f.cget("family")
196    print f.cget("weight")
197
198    print names()
199
200    print f.measure("hello"), f.metrics("linespace")
201
202    print f.metrics()
203
204    f = Font(font=("Courier", 20, "bold"))
205    print f.measure("hello"), f.metrics("linespace")
206
207    w = Tkinter.Label(root, text="Hello, world", font=f)
208    w.pack()
209
210    w = Tkinter.Button(root, text="Quit!", command=root.destroy)
211    w.pack()
212
213    fb = Font(font=w["font"]).copy()
214    fb.config(weight=BOLD)
215
216    w.config(font=fb)
217
218    Tkinter.mainloop()
219