1#!/usr/bin/env python
2""" pygame.examples.font_viewer
3Scroll through your system fonts from a list of surfaces or one huge buffer.
4
5This example exhibits:
6* iterate over available fonts using font.get_fonts and font.SysFont()
7* click and drag using mouse input
8* scrolling with the scroll wheel
9* save a surface to disk
10* work with a very large surface
11* simple mouse and keyboard scroll speed acceleration
12
13By default this example uses the fonts returned by pygame.font.get_fonts()
14and opens them using pygame.font.SysFont().
15Alternatively, you may pass a path to the command line. The TTF files found
16in that directory will be used instead.
17
18Mouse Controls:
19* Use the mouse wheel or click and drag to scroll
20
21Keyboard Controls:
22* Press up or down to scroll
23* Press escape to exit
24"""
25import sys
26import os
27
28import pygame as pg
29
30use_big_surface = False  # draw into large buffer and save png file
31
32
33class FontViewer:
34    """
35    This example is encapsulated by the fontviewer class
36    It initializes the pygame window, handles input, and draws itself
37    to the screen.
38    """
39
40    KEY_SCROLL_SPEED = 10
41    MOUSE_SCROLL_SPEED = 50
42
43    def __init__(self, **dparams):
44        pg.init()
45        self.font_dir = dparams.get("folder", None)
46
47        # create a window that uses 80 percent of the screen
48        info = pg.display.Info()
49        w = info.current_w
50        h = info.current_h
51        pg.display.set_mode((int(w * 0.8), int(h * 0.8)))
52        self.font_size = h // 20
53
54        self.clock = pg.time.Clock()
55        self.y_offset = 0
56        self.grabbed = False
57        self.render_fonts("&N abcDEF789")
58
59        if use_big_surface or "big" in sys.argv:
60            self.render_surface()
61            self.display_surface()
62            self.save_png()
63        else:
64            self.display_fonts()
65
66    def get_font_list(self):
67        """
68        Generate a font list using font.get_fonts() for system fonts or
69        from a path from the command line.
70        """
71        path = ""
72        if len(sys.argv) > 1 and os.path.exists(sys.argv[1]):
73            path = os.path.join(sys.argv[1], "")
74        fonts = []
75        if os.path.exists(path):
76            # this list comprehension could replace the following loop
77            # fonts = [f in os.listdir(path) if f.endswith('.ttf')]
78            for font in os.listdir(path):
79                if font.endswith(".ttf"):
80                    fonts.append(font)
81        return fonts or pg.font.get_fonts(), path
82
83    def render_fonts(self, text="A display of font &N", **dparams):
84        """
85        Build a list that includes a surface and the running total of their
86        height for each font in the font list. Store the largest width and
87        other variables for later use.
88        """
89        font_size = dparams.get("size", 0) or self.font_size
90        color = dparams.get("color", (255, 255, 255))
91        self.back_color = dparams.get("back_color", (0, 0, 0))
92
93        fonts, path = self.get_font_list()
94        font_surfaces = []
95        total_height = 0
96        max_width = 0
97
98        load_font = pg.font.Font if path else pg.font.SysFont
99
100        # display instructions at the top of the display
101        font = pg.font.SysFont(pg.font.get_default_font(), font_size)
102        lines = (
103            "Use the scroll wheel or click and drag",
104            "to scroll up and down",
105            "Foreign fonts might render incorrectly",
106            "Here are your {} fonts".format(len(fonts)),
107            "",
108        )
109        for line in lines:
110            surf = font.render(line, 1, color, self.back_color)
111            font_surfaces.append((surf, total_height))
112            total_height += surf.get_height()
113            max_width = max(max_width, surf.get_width())
114
115        # render all the fonts and store them with the total height
116        for name in sorted(fonts):
117            try:
118                font = load_font(path + name, font_size)
119            except IOError:
120                continue
121            line = text.replace("&N", name)
122            try:
123                surf = font.render(line, 1, color, self.back_color)
124            except pg.error as e:
125                print(e)
126                break
127
128            max_width = max(max_width, surf.get_width())
129            font_surfaces.append((surf, total_height))
130            total_height += surf.get_height()
131
132        # store variables for later usage
133        self.total_height = total_height
134        self.max_width = max_width
135        self.font_surfaces = font_surfaces
136        self.max_y = total_height - pg.display.get_surface().get_height()
137
138    def display_fonts(self):
139        """
140        Display the visible fonts based on the y_offset value(updated in
141        handle_events) and the height of the pygame window.
142        """
143        display = pg.display.get_surface()
144        clock = pg.time.Clock()
145        center = display.get_width() // 2
146
147        while True:
148            # draw visible surfaces
149            display.fill(self.back_color)
150            for surface, top in self.font_surfaces:
151                bottom = top + surface.get_height()
152                if (
153                    bottom >= self.y_offset
154                    and top <= self.y_offset + display.get_height()
155                ):
156                    x = center - surface.get_width() // 2
157                    display.blit(surface, (x, top - self.y_offset))
158            # get input and update the screen
159            if not self.handle_events():
160                break
161            pg.display.flip()
162            clock.tick(30)
163
164    def render_surface(self):
165        """
166        Note: this method uses twice the memory and is only called if
167        big_surface is set to true or big is added to the command line.
168
169        Optionally generates one large buffer to draw all the font surfaces
170        into. This is necessary to save the display to a png file and may
171        be useful for testing large surfaces.
172        """
173
174        large_surface = pg.surface.Surface(
175            (self.max_width, self.total_height)
176        ).convert()
177        large_surface.fill(self.back_color)
178        print("scrolling surface created")
179
180        # display the surface size and memory usage
181        byte_size = large_surface.get_bytesize()
182        total_size = byte_size * (self.max_width * self.total_height)
183        print(
184            "Surface Size = {}x{} @ {}bpp: {:,.3f}mb".format(
185                self.max_width, self.total_height, byte_size, total_size / 1000000.0
186            )
187        )
188
189        y = 0
190        center = int(self.max_width / 2)
191        for surface, top in self.font_surfaces:
192            w = surface.get_width()
193            x = center - int(w / 2)
194            large_surface.blit(surface, (x, y))
195            y += surface.get_height()
196        self.max_y = large_surface.get_height() - pg.display.get_surface().get_height()
197        self.surface = large_surface
198
199    def display_surface(self, time=10):
200        """
201        Display the large surface created by the render_surface method. Scrolls
202        based on the y_offset value(set in handle_events) and the height of the
203        pygame window.
204        """
205        screen = pg.display.get_surface()
206
207        # Create a Rect equal to size of screen. Then we can just change its
208        # top attribute to draw the desired part of the rendered font surface
209        # to the display surface
210        rect = pg.rect.Rect(
211            0,
212            0,
213            self.surface.get_width(),
214            min(self.surface.get_height(), screen.get_height()),
215        )
216
217        x = int((screen.get_width() - self.surface.get_width()) / 2)
218        going = True
219        while going:
220            if not self.handle_events():
221                going = False
222            screen.fill(self.back_color)
223            rect.top = self.y_offset
224            screen.blit(self.surface, (x, 0), rect)
225            pg.display.flip()
226            self.clock.tick(20)
227
228    def save_png(self, name="font_viewer.png"):
229        pg.image.save(self.surface, name)
230        file_size = os.path.getsize(name) // 1024
231        print("font surface saved to {}\nsize: {:,}Kb".format(name, file_size))
232
233    def handle_events(self):
234        """
235        This method handles user input. It returns False when it receives
236        a pygame.QUIT event or the user presses escape. The y_offset is
237        changed based on mouse and keyboard input. display_fonts() and
238        display_surface() use the y_offset to scroll display.
239        """
240        events = pg.event.get()
241        for e in events:
242            if e.type == pg.QUIT:
243                return False
244            elif e.type == pg.KEYDOWN:
245                if e.key == pg.K_ESCAPE:
246                    return False
247            elif e.type == pg.MOUSEWHEEL:
248                self.y_offset += e.y * self.MOUSE_SCROLL_SPEED * -1
249            elif e.type == pg.MOUSEBUTTONDOWN:
250                # enter dragging mode on mouse down
251                self.grabbed = True
252                pg.event.set_grab(True)
253            elif e.type == pg.MOUSEBUTTONUP:
254                # exit drag mode on mouse up
255                self.grabbed = False
256                pg.event.set_grab(False)
257
258        # allow simple accelerated scrolling with the keyboard
259        keys = pg.key.get_pressed()
260        if keys[pg.K_UP]:
261            self.key_held += 1
262            self.y_offset -= int(self.KEY_SCROLL_SPEED * (self.key_held // 10))
263        elif keys[pg.K_DOWN]:
264            self.key_held += 1
265            self.y_offset += int(self.KEY_SCROLL_SPEED * (self.key_held // 10))
266        else:
267            self.key_held = 20
268
269        # set the y_offset for scrolling and keep it between 0 and max_y
270        y = pg.mouse.get_rel()[1]
271        if y and self.grabbed:
272            self.y_offset -= y
273
274        self.y_offset = min((max(self.y_offset, 0), self.max_y))
275        return True
276
277
278viewer = FontViewer()
279pg.quit()
280